mitsuba-renderer / mitsuba2

Mitsuba 2: A Retargetable Forward and Inverse Renderer
Other
2.05k stars 266 forks source link

Simple Mesh plugin doesn't seem to work #604

Closed anderslanglands closed 2 years ago

anderslanglands commented 2 years ago

Summary

I'm trying to create a new Shape plugin, inheriting from Mesh. I've taken the OBJ as a starting point and stripped it down to a minimum and am just trying to manually create a rect at the origin for now, only nothing renders. I know the camera is pointed the right way because if I replace the mesh with a sphere in the test script below, I see it. The plugin is loading ok and prints out as part of the scene, and everything looks ok there, no errors. I just don't see my mesh.

Description

Here's the plugin source:

#include <mitsuba/render/mesh.h>
#include <mitsuba/core/fresolver.h>
#include <mitsuba/core/properties.h>
#include <mitsuba/core/mmap.h>
#include <mitsuba/core/util.h>
#include <mitsuba/core/timer.h>
#include <mitsuba/render/bsdf.h>

NAMESPACE_BEGIN(mitsuba)

template <typename Float, typename Spectrum>
class DynMesh final : public Mesh<Float, Spectrum> {
public:
    MTS_IMPORT_BASE(Mesh, m_name, m_bbox, m_to_world, m_vertex_count,
                    m_face_count, m_vertex_positions_buf, m_vertex_normals_buf,
                    m_vertex_texcoords_buf, m_faces_buf, add_attribute,
                    m_disable_vertex_normals, has_vertex_normals,
                    has_vertex_texcoords, recompute_vertex_normals,
                    set_children)
    MTS_IMPORT_TYPES(BSDF)

    using typename Base::ScalarIndex;
    using typename Base::ScalarSize;
    using ScalarIndex3 = Array<ScalarIndex, 3>;
    using typename Base::FloatStorage;
    using typename Base::InputFloat;
    using typename Base::InputNormal3f;
    using typename Base::InputPoint3f;
    using typename Base::InputVector2f;
    using typename Base::InputVector3f;

    DynMesh(const Properties &props) : Base(props) {
        m_name = "blah";
        m_vertex_count = 4;
        m_face_count   = 2;
        m_disable_vertex_normals = true;

        std::vector<ScalarIndex3> triangles{ 0, 1, 2, 0, 2, 3 };
        std::vector<InputVector3f> vertices{
            { -1, -1, 0 }, { 1, -1, 0 }, { 1, 1, 0 }, { -1, 1, 0 }
        };
        std::vector<InputNormal3f> normals;

        m_faces_buf =
            DynamicBuffer<UInt32>::copy(triangles.data(), m_face_count * 3);
        m_vertex_positions_buf =
            DynamicBuffer<FloatStorage>::copy(vertices.data(), m_vertex_count * 3);
        if (!m_disable_vertex_normals)
            m_vertex_normals_buf = empty<FloatStorage>(m_vertex_count * 3);

        // TODO this is needed for the bbox(..) methods, but is it slower?
        m_faces_buf.managed();
        m_vertex_positions_buf.managed();
        m_vertex_normals_buf.managed();
        m_vertex_texcoords_buf.managed();

        if constexpr (is_cuda_array_v<Float>)
            cuda_sync();

        if (!m_disable_vertex_normals && normals.empty()) {
            Timer timer2;
            recompute_vertex_normals();
            Log(Debug, "\"%s\": computed vertex normals (took %s)", m_name,
                util::time_string(timer2.value()));
        }

        recompute_bbox();

        set_children();
    }

    std::string to_string() const {
        std::ostringstream oss;
        oss << class_()->name() << "[" << std::endl
            << "  name = \"" << m_name << "\"," << std::endl
            << "  bbox = " << string::indent(m_bbox) << "," << std::endl
            << "  vertex_count = " << m_vertex_count << "," << std::endl
            << "  vertices = ["
            << util::mem_string(vertex_data_bytes() * m_vertex_count)
            << " of vertex data]," << std::endl
            << "  face_count = " << m_face_count << "," << std::endl
            << "  faces = ["
            << util::mem_string(face_data_bytes() * m_face_count)
            << " of face data]," << std::endl;

        if (!m_area_pmf.empty())
            oss << "  surface_area = " << m_area_pmf.sum() << "," << std::endl;

        oss << "  disable_vertex_normals = " << m_disable_vertex_normals;

        if (!m_mesh_attributes.empty()) {
            oss << "," << std::endl << "  mesh attributes = [" << std::endl;
            size_t i = 0;
            for (const auto &[name, attribute] : m_mesh_attributes)
                oss << "    " << name << ": " << attribute.size
                    << (attribute.size == 1 ? " float" : " floats")
                    << (++i == m_mesh_attributes.size() ? "" : ",")
                    << std::endl;
            oss << "  ]" << std::endl;
        } else {
            oss << std::endl;
        }

        oss << "  bsdf = ";
        if (m_bsdf) {
            oss << string::indent(m_bsdf);
        }
        oss << "," << std::endl;

        oss << "  to_world = \n  " << string::indent(m_to_world) << std::endl;

        oss << "]";
        return oss.str();
    }

private:
    MTS_DECLARE_CLASS()
};

MTS_EXPORT_PLUGIN(DynMesh, "DynMesh")
MTS_IMPLEMENT_CLASS_VARIANT(DynMesh, Mesh)

NAMESPACE_END(mitsuba)

and here's a test script:

import os

mitsuba_root = os.getenv("MITSUBA_DIR")
cuda_path = os.getenv("CUDA_PATH")
os.add_dll_directory(mitsuba_root)
os.add_dll_directory(os.path.join(cuda_path, "bin"))

import enoki as ek
import mitsuba
import numpy as np

mitsuba.set_variant("gpu_rgb")

from mitsuba.core import Float, Thread, ScalarTransform4f, Bitmap, Struct, TraversalCallback
from mitsuba.core.xml import load_file, load_dict
from mitsuba.python.util import traverse
from mitsuba.core import Color3f
from mitsuba.python.autodiff import render, write_bitmap, Adam
from ipc import TevIpc

WIDTH = 256
HEIGHT = 256

scene = load_dict(
    {
        "type": "scene",
        "myintegrator": {
            "type": "path",
        },
        "mysensor": {
            "type": "perspective",
            "near_clip": 1.0,
            "far_clip": 1000.0,
            "to_world": ScalarTransform4f.look_at(
                origin=[5, 5, 5], target=[0, 0, 0], up=[0, 0, 1]
            ),
            "myfilm": {
                "type": "hdrfilm",
                "rfilter": {"type": "box"},
                "width": 1280,
                "height": 720,
                "pixel_format": "rgba",
            },
            "mysampler": {
                "type": "independent",
                "sample_count": 4,
            },
        },

        'bsdf_key_0': {
            'type': 'diffuse'
        },

        "myemitter": {"type": "constant"},
        "myshape": { 
            "type": "dyn",
            'bsdf': {
                'type': 'ref',
                'id': 'bsdf_key_0',
            }
        }

    }
)

print(scene)

sensor = scene.sensors()[0]
film = sensor.film()

width = film.crop_size()[0]
height = film.crop_size()[1]
scene.integrator().render(scene, sensor)

bitmap = film.bitmap(raw=True).convert(
    Bitmap.PixelFormat.RGBA, Struct.Type.Float32, srgb_gamma=False
)

with TevIpc() as tev:
    channels = ["R", "G", "B", "A"]
    tev.create_image("ref", width=width, height=height, channel_names=channels)
    tev.update_image(
        "ref", np.array(bitmap).reshape(height, width, len(channels)), channels
    )

Steps to reproduce

  1. Compile the above code as a new plugin
  2. Render a scene using hte above script
Speierers commented 2 years ago

Hi @anderslanglands ,

Could you try rendering with another variant, e.g. scalar_rgb? I suspect it could be an issue on how you create/upload the mesh buffers on the GPU.

anderslanglands commented 2 years ago

Hi @Speierers, I get the same result with scalar_rgb

Speierers commented 2 years ago

I would suggest you try to rotate the object / camera by 90 degree, it could be that you are looking at the rectangle from the side.

anderslanglands commented 2 years ago

The vertices I have there should be a match for the default "rectangle" shape right? (i.e. rectangle in the XY plane). If I change "dyn" to "rectangle" in the script above I get the attached image

image

Speierers commented 2 years ago

One way to debug this further would be to use the Mesh::write_ply() method and open the output file (e.g. in blender) to see what's going on.

Speierers commented 2 years ago

Taking a quick look at your code, it could be that InputVector3f doesn't have the size that you expect. IIRC it allocates an extra float (e.g. padding).

anderslanglands commented 2 years ago

Thanks, I ended up writing the ply out and inspecting it directly. Turns out there were a couple of problems: 1) Both InputVector3f and ScalarIndex3 are padded 2) When I was creating the triangles list:

std::vector<ScalarIndex3> triangles{ 0, 1, 2, 0, 2, 3 };

This was actually creating a vector of 6 triangles. I guess ScalarIndex3 has a constructor that implicitly converts from a single int?

anderslanglands commented 2 years ago

The working constructor, for posterity's sake:

    DynMesh(const Properties &props) : Base(props) {
        std::vector<ScalarIndex3> triangles{ {0, 1, 2}, {0, 2, 3} };
        std::vector<InputPoint3f> vertices{
            { -1, -1, 0 }, { 1, -1, 0 }, { 1, 1, 0 }, { -1, 1, 0 }
        };
        std::vector<InputNormal3f> normals;

        m_name                   = "blah";
        m_vertex_count           = vertices.size();
        m_face_count             = triangles.size();
        m_disable_vertex_normals = true;

        m_faces_buf = empty<UInt32>(m_face_count * 3);
        m_vertex_positions_buf = empty<FloatStorage>(m_vertex_count * 3);
        if (!m_disable_vertex_normals)
            m_vertex_normals_buf = empty<FloatStorage>(m_vertex_count * 3);

        m_faces_buf.managed();
        m_vertex_positions_buf.managed();
        m_vertex_normals_buf.managed();
        m_vertex_texcoords_buf.managed();

        if constexpr (is_cuda_array_v<Float>)
            cuda_sync();

        for (size_t i = 0; i < triangles.size(); ++i) {
            for (int e = 0; e < 3; ++e) {
                m_faces_buf.data()[i*3+e] = triangles[i][e];
            }
        }

        for (size_t i = 0; i < vertices.size(); ++i) {
            m_bbox.expand(vertices[i]);

            InputFloat *dst_v = m_vertex_positions_buf.data() + i * 3;
            store_unaligned(dst_v+0, vertices[i].x());
            store_unaligned(dst_v+1, vertices[i].y());
            store_unaligned(dst_v+2, vertices[i].z());
        }

        set_children();
    }
Speierers commented 2 years ago

Great to hear that it works now. I will close this issue.