RenderKit / embree

Embree ray tracing kernels repository.
Apache License 2.0
2.32k stars 383 forks source link

Does Embree support geometry instancing? #463

Closed StudenteChamp2 closed 9 months ago

StudenteChamp2 commented 9 months ago

I implemented geometry instancing inside my renderer. So multiple instances of a same mesh can share the same memory. This allow to reduce the memory footprint when rendering vegetation. Since now I have been creating an RTCGeometry for each mesh regardless they are instances or not. I am using Embree v3.1.0 and I am open to upgrade to a newer version. The Api would allow to fork an existing geometry and give it a transform.

In term of code here what it could look like(check the "New function" tags).


    CPUIntersector::CPUIntersector(const Model::BaseModel* model)
    : BaseIntersector(IntersectorType::Cpu)
    {
        m_model = model;

        // Initialize embree
        m_device = rtcNewDevice(nullptr);
        m_scene = rtcNewScene(m_device);

        // Scene build quality.
        rtcSetSceneBuildQuality(
            m_scene,
            RTCBuildQuality::RTC_BUILD_QUALITY_HIGH);

        // The root geometries.
        std::unordered_map<Model::Mesh*, RTCGeometry> rootGeometries;

        // Iterate over mesh groups.
        for (const EntityIdentifier& groupEntityId : m_model->getMeshGroups())
        {
            const auto group = Model::getMeshGroupPtr_FromEntity(groupEntityId);
            if (group->isInstance())
            {
                for (Model::Mesh* mesh : group->m_meshes)
                {
                    auto it = rootGeometries.find(mesh);
                    assert(it != rootGeometries.end());

                    const RTCGeometry& srcGeometry = it->second;
                    RTCGeometry forkedGeometry = rtcForkGeometry(srcGeometry);// New function

                    const Math::Mat4& transform = mesh->getTransform()->getMatrix();
                    rtcSetGeometryTransform(forkedGeometry, &transform[0][0]);// New function

                    rtcCommitGeometry(forkedGeometry);
                    rtcAttachGeometryByID(m_scene, forkedGeometry, mesh->getFlatId());
                }

                continue;
            }

            for (Model::Mesh* mesh : group->m_meshes)
            {
                // Create geometry
                RTCGeometry geometry = rtcNewGeometry(m_device, RTC_GEOMETRY_TYPE_TRIANGLE);

                // The geometry build quality.
                rtcSetGeometryBuildQuality(
                    geometry,
                    RTCBuildQuality::RTC_BUILD_QUALITY_HIGH);

                // The geometry transform.
                const Math::Mat4& transform = mesh->getTransform()->getMatrix();
                rtcSetGeometryTransform(geometry, &transform[0][0]);// New function

                // Set vertices.
                {
                    const auto& meshVertices = mesh->getVertices();

                    rtcSetSharedGeometryBuffer(
                        geometry,
                        RTC_BUFFER_TYPE_VERTEX,
                        0,
                        RTC_FORMAT_FLOAT3,
                        meshVertices.data(),
                        0,
                        sizeof(Graphics::Vertex),
                        meshVertices.size());
                }

                // Set indices.
                {
                    const auto& meshIndices = mesh->getIndices();

                    rtcSetSharedGeometryBuffer(
                        geometry,
                        RTC_BUFFER_TYPE_INDEX,
                        0,
                        RTC_FORMAT_UINT3,
                        meshIndices.data(),
                        0,
                        3 * sizeof(unsigned int),
                        meshIndices.size() / 3);
                }

                rtcCommitGeometry(geometry);
                rtcAttachGeometryByID(m_scene, geometry, mesh->getFlatId());
            }
        }

        // Release pending geometry memory.
        for (auto& it : rootGeometries)
            rtcReleaseGeometry(it.second);

        // Commit the scene.
        rtcCommitScene(m_scene);

        // Init the intersection context.
        m_context = new RTCIntersectContext();
        rtcInitIntersectContext(m_context);
    }

![vege](https://github.com/embree/embree/assets/144755653/587fb9b1-fd7b-46d1-80e5-4428d78956bc)
svenwoop commented 9 months ago

To achieve this you can just use normal instances, which would be even more compact. Add the geometry to some scene, and then instantiate that scene inside a top-level scene. See embree_instance_geometry tutorial for an example. What you propose would still internally required data for each triangle, as the BVH would be build over all the geometries. In the instancing case, you would just have one BVH per geometry, and also that BVH would be re-used multiple times.

StudenteChamp2 commented 9 months ago

I tried what is done in the instanced_geometry tutorial. Basically i just need to give each instance a geometry transform. I did so but unfortunately my intersection queries does not return what's expected.
The problem is that the rtcSetGeometryTransform function seems to not work. When I set the transform using rtcSetGeometryTransform then i read it using rtcGetGeometryTransform i do not obtain the original matrix. Here code snippet:


{
    // Create the geometry.
    RTCGeometry geometry = rtcNewGeometry(m_device, RTC_GEOMETRY_TYPE_TRIANGLE);

    // Geometry build quality.
    rtcSetGeometryBuildQuality(geometry, RTCBuildQuality::RTC_BUILD_QUALITY_MEDIUM);

    // Set the tranform.
    const float transform[] = {  1.0f, 0.0f, 0.0f, 0.0f,
                          0.0f, 1.0f, 0.0f, 0.0f,
                          0.0f, 0.0f, 1.0f, 0.0f,
                          0.0f, 0.0f, 0.0f, 1.0f };

    rtcSetGeometryTransform(geometry, 0, RTC_FORMAT_FLOAT4X4_COLUMN_MAJOR, &transform[0]);

    // Read the transform.
    std::vector<float> retMatrix(16, -1.0f);
    rtcGetGeometryTransform(geometry, 0, RTC_FORMAT_FLOAT4X4_COLUMN_MAJOR, retMatrix.data());
}

*rtcGetGeometryTransform* returns the input retMatrix matrix. So a matrix with -1.0f everywhere.  
Is it a bug or i am doing something wrong?  I have the same behavior under Embree 4.3.0.

HERE the full code:

    m_device = rtcNewDevice(nullptr);
    m_scene = rtcNewScene(m_device);

    for (Model::Mesh* mesh : meshes)
    {
        // Create geometry.
        RTCGeometry geometry = rtcNewGeometry(m_device, RTC_GEOMETRY_TYPE_TRIANGLE);

        rtcSetGeometryBuildQuality(
            geometry,
            RTCBuildQuality::RTC_BUILD_QUALITY_MEDIUM);

        // Set the tranform.
        {
            const Math::Mat4& transform = mesh->getTransform()->getMatrix();
            rtcSetGeometryTransform(geometry, 0, RTC_FORMAT_FLOAT4X4_COLUMN_MAJOR, (float*)&transform[0][0]);
        }

        // Set vertices.
        {
            const auto& meshVertices = mesh->getVertices();

            rtcSetSharedGeometryBuffer(
                        geometry,
                        RTC_BUFFER_TYPE_VERTEX,
                        0,
                        RTC_FORMAT_FLOAT3,
                        meshVertices.data(),
                        0,
                        sizeof(Math::Vec3),
                        meshVertices.size());
        }

        // Set indices.
        {
            const auto& meshIndices = mesh->getIndices();

            rtcSetSharedGeometryBuffer(
                        geometry,
                        RTC_BUFFER_TYPE_INDEX,
                        0,
                        RTC_FORMAT_UINT3,
                        meshIndices.data(),
                        0,
                        3 * sizeof(unsigned int),
                        meshIndices.size() / 3);
        }

        rtcCommitGeometry(geometry);
        rtcAttachGeometryByID(m_scene, geometry, mesh->getFlatId());
        rtcReleaseGeometry(geometry);
    }

    rtcCommitScene(m_scene);

    // Init the intersection context
    m_context = new RTCIntersectContext();
    rtcInitIntersectContext(m_context);`
svenwoop commented 9 months ago

You cannot set the transformation of a triangle geometry, but only of an instance geometry. What you have to do is to take your single geometry, put that into a scene, then commit that scene. Now build a second scene, add an instance to that which refers to that previously build scene. Add a transformation to that instance.