Changeset View
Changeset View
Standalone View
Standalone View
intern/cycles/render/mesh_volume.cpp
| Show All 9 Lines | |||||
| * Unless required by applicable law or agreed to in writing, software | * Unless required by applicable law or agreed to in writing, software | ||||
| * distributed under the License is distributed on an "AS IS" BASIS, | * distributed under the License is distributed on an "AS IS" BASIS, | ||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| * See the License for the specific language governing permissions and | * See the License for the specific language governing permissions and | ||||
| * limitations under the License. | * limitations under the License. | ||||
| */ | */ | ||||
| #include "render/attribute.h" | #include "render/attribute.h" | ||||
| #include "render/image_vdb.h" | |||||
| #include "render/mesh.h" | #include "render/mesh.h" | ||||
| #include "render/scene.h" | #include "render/scene.h" | ||||
| #ifdef WITH_OPENVDB | |||||
| #include <openvdb/tools/Dense.h> | |||||
| #include <openvdb/tools/GridTransformer.h> | |||||
brecht: Cycles should continue to build without OpenVDB, even if it's without optimized bounding mesh. | |||||
| #include <openvdb/tools/Morphology.h> | |||||
| #endif | |||||
| #include "util/util_foreach.h" | #include "util/util_foreach.h" | ||||
| #include "util/util_hash.h" | #include "util/util_hash.h" | ||||
| #include "util/util_logging.h" | #include "util/util_logging.h" | ||||
| #include "util/util_progress.h" | #include "util/util_progress.h" | ||||
| #include "util/util_types.h" | #include "util/util_types.h" | ||||
| CCL_NAMESPACE_BEGIN | CCL_NAMESPACE_BEGIN | ||||
| const int64_t VOXEL_INDEX_NONE = -1; | |||||
| static int64_t compute_voxel_index(const int3 &resolution, int64_t x, int64_t y, int64_t z) | |||||
| { | |||||
| if (x < 0 || x >= resolution.x) { | |||||
| return VOXEL_INDEX_NONE; | |||||
| } | |||||
| else if (y < 0 || y >= resolution.y) { | |||||
| return VOXEL_INDEX_NONE; | |||||
| } | |||||
| else if (z < 0 || z >= resolution.z) { | |||||
| return VOXEL_INDEX_NONE; | |||||
| } | |||||
| return x + y * resolution.x + z * resolution.x * resolution.y; | |||||
| } | |||||
| struct QuadData { | struct QuadData { | ||||
| int v0, v1, v2, v3; | int v0, v1, v2, v3; | ||||
| float3 normal; | float3 normal; | ||||
| }; | }; | ||||
| enum { | enum { | ||||
| QUAD_X_MIN = 0, | QUAD_X_MIN = 0, | ||||
| ▲ Show 20 Lines • Show All 64 Lines • ▼ Show 20 Lines | static void create_quad(int3 corners[8], | ||||
| quad.v1 = add_vertex(corners[quads_indices[face_index][1]], vertices, res, used_verts); | quad.v1 = add_vertex(corners[quads_indices[face_index][1]], vertices, res, used_verts); | ||||
| quad.v2 = add_vertex(corners[quads_indices[face_index][2]], vertices, res, used_verts); | quad.v2 = add_vertex(corners[quads_indices[face_index][2]], vertices, res, used_verts); | ||||
| quad.v3 = add_vertex(corners[quads_indices[face_index][3]], vertices, res, used_verts); | quad.v3 = add_vertex(corners[quads_indices[face_index][3]], vertices, res, used_verts); | ||||
| quad.normal = quads_normals[face_index]; | quad.normal = quads_normals[face_index]; | ||||
| quads.push_back(quad); | quads.push_back(quad); | ||||
| } | } | ||||
| struct VolumeParams { | |||||
| int3 resolution; | |||||
| float3 cell_size; | |||||
| float3 start_point; | |||||
| int pad_size; | |||||
| }; | |||||
| static const int CUBE_SIZE = 8; | |||||
| /* Create a mesh from a volume. | /* Create a mesh from a volume. | ||||
| * | * | ||||
| * The way the algorithm works is as follows: | * The way the algorithm works is as follows: | ||||
| * | * | ||||
| * - The coordinates of active voxels from a dense volume (or 3d image) are | * - The topologies of input OpenVDB grids are merged into a temporary grid. | ||||
| * gathered inside an auxiliary volume. | * - Voxels of the temporary grid are dilated to account for the padding necessary for volume | ||||
| * - Each set of coordinates of an CUBE_SIZE cube are mapped to the same | * sampling. | ||||
| * coordinate of the auxiliary volume. | * - Quads are created on the boundary between active and inactive leaf nodes of the temporary | ||||
| * - Quads are created between active and non-active voxels in the auxiliary | * grid. | ||||
| * volume to generate a tight mesh around the volume. | |||||
| */ | */ | ||||
| class VolumeMeshBuilder { | class VolumeMeshBuilder { | ||||
| /* Auxiliary volume that is used to check if a node already added. */ | |||||
| vector<char> grid; | |||||
| /* The resolution of the auxiliary volume, set to be equal to 1/CUBE_SIZE | |||||
| * of the original volume on each axis. */ | |||||
| int3 res; | |||||
| size_t number_of_nodes; | |||||
| /* Offset due to padding in the original grid. Padding will transform the | |||||
| * coordinates of the original grid from 0...res to -padding...res+padding, | |||||
| * so some coordinates are negative, and we need to properly account for | |||||
| * them. */ | |||||
| int3 pad_offset; | |||||
| VolumeParams *params; | |||||
| public: | public: | ||||
| VolumeMeshBuilder(VolumeParams *volume_params); | #ifdef WITH_OPENVDB | ||||
| /* use a MaskGrid to store the topology to save memory */ | |||||
| openvdb::MaskGrid::Ptr topology_grid; | |||||
| openvdb::CoordBBox bbox; | |||||
| #endif | |||||
| bool first_grid; | |||||
| VolumeMeshBuilder(); | |||||
| #ifdef WITH_OPENVDB | |||||
| void add_grid(openvdb::GridBase::ConstPtr grid); | |||||
| #endif | |||||
| void add_node(int x, int y, int z); | void add_padding(int pad_size); | ||||
| void add_node_with_padding(int x, int y, int z); | void create_mesh(vector<float3> &vertices, | ||||
| vector<int> &indices, | |||||
| void create_mesh(vector<float3> &vertices, vector<int> &indices, vector<float3> &face_normals); | vector<float3> &face_normals, | ||||
| const float face_overlap_avoidance); | |||||
| private: | |||||
| void generate_vertices_and_quads(vector<int3> &vertices_is, vector<QuadData> &quads); | void generate_vertices_and_quads(vector<int3> &vertices_is, vector<QuadData> &quads); | ||||
| void convert_object_space(const vector<int3> &vertices, vector<float3> &out_vertices); | void convert_object_space(const vector<int3> &vertices, | ||||
| vector<float3> &out_vertices, | |||||
| const float face_overlap_avoidance); | |||||
| void convert_quads_to_tris(const vector<QuadData> &quads, | void convert_quads_to_tris(const vector<QuadData> &quads, | ||||
| vector<int> &tris, | vector<int> &tris, | ||||
| vector<float3> &face_normals); | vector<float3> &face_normals); | ||||
| bool empty_grid() const; | |||||
| }; | }; | ||||
| VolumeMeshBuilder::VolumeMeshBuilder(VolumeParams *volume_params) | VolumeMeshBuilder::VolumeMeshBuilder() | ||||
| { | { | ||||
| params = volume_params; | first_grid = true; | ||||
| number_of_nodes = 0; | |||||
| const int64_t x = divide_up(params->resolution.x, CUBE_SIZE); | |||||
| const int64_t y = divide_up(params->resolution.y, CUBE_SIZE); | |||||
| const int64_t z = divide_up(params->resolution.z, CUBE_SIZE); | |||||
| /* Adding 2*pad_size since we pad in both positive and negative directions | |||||
| * along the axis. */ | |||||
| const int64_t px = divide_up(params->resolution.x + 2 * params->pad_size, CUBE_SIZE); | |||||
| const int64_t py = divide_up(params->resolution.y + 2 * params->pad_size, CUBE_SIZE); | |||||
| const int64_t pz = divide_up(params->resolution.z + 2 * params->pad_size, CUBE_SIZE); | |||||
| res = make_int3(px, py, pz); | |||||
| pad_offset = make_int3(px - x, py - y, pz - z); | |||||
| grid.resize(px * py * pz, 0); | |||||
| } | } | ||||
| void VolumeMeshBuilder::add_node(int x, int y, int z) | #ifdef WITH_OPENVDB | ||||
| void VolumeMeshBuilder::add_grid(openvdb::GridBase::ConstPtr grid) | |||||
| { | { | ||||
| /* Map coordinates to index space. */ | /* set the transform of our grid from the first one */ | ||||
| const int index_x = (x / CUBE_SIZE) + pad_offset.x; | if (first_grid) { | ||||
| const int index_y = (y / CUBE_SIZE) + pad_offset.y; | topology_grid = openvdb::MaskGrid::create(); | ||||
| const int index_z = (z / CUBE_SIZE) + pad_offset.z; | topology_grid->setTransform(grid->transform().copy()); | ||||
| first_grid = false; | |||||
| assert((index_x >= 0) && (index_y >= 0) && (index_z >= 0)); | |||||
| const int64_t index = compute_voxel_index(res, index_x, index_y, index_z); | |||||
| if (index == VOXEL_INDEX_NONE) { | |||||
| return; | |||||
| } | } | ||||
| /* if the transforms do not match, we need to resample one of the grids so that | |||||
| /* We already have a node here. */ | * its index space registers with that of the other, here we resample our mask | ||||
| if (grid[index] == 1) { | * grid so memory usage is kept low */ | ||||
| return; | else if (topology_grid->transform() != grid->transform()) { | ||||
| openvdb::MaskGrid::Ptr temp_grid = topology_grid->copyWithNewTree(); | |||||
| temp_grid->setTransform(grid->transform().copy()); | |||||
| openvdb::tools::resampleToMatch<openvdb::tools::BoxSampler>(*topology_grid, *temp_grid); | |||||
| topology_grid = temp_grid; | |||||
| topology_grid->setTransform(grid->transform().copy()); | |||||
| } | } | ||||
| ++number_of_nodes; | if (grid->isType<openvdb::FloatGrid>()) { | ||||
| topology_grid->topologyUnion(*openvdb::gridConstPtrCast<openvdb::FloatGrid>(grid)); | |||||
| grid[index] = 1; | |||||
| } | } | ||||
| else if (grid->isType<openvdb::Vec3SGrid>()) { | |||||
| void VolumeMeshBuilder::add_node_with_padding(int x, int y, int z) | topology_grid->topologyUnion(*openvdb::gridConstPtrCast<openvdb::Vec3SGrid>(grid)); | ||||
| { | } | ||||
| for (int px = x - params->pad_size; px < x + params->pad_size; ++px) { | else if (grid->isType<openvdb::BoolGrid>()) { | ||||
| for (int py = y - params->pad_size; py < y + params->pad_size; ++py) { | topology_grid->topologyUnion(*openvdb::gridConstPtrCast<openvdb::BoolGrid>(grid)); | ||||
| for (int pz = z - params->pad_size; pz < z + params->pad_size; ++pz) { | } | ||||
| add_node(px, py, pz); | else if (grid->isType<openvdb::DoubleGrid>()) { | ||||
| topology_grid->topologyUnion(*openvdb::gridConstPtrCast<openvdb::DoubleGrid>(grid)); | |||||
| } | |||||
| else if (grid->isType<openvdb::Int32Grid>()) { | |||||
| topology_grid->topologyUnion(*openvdb::gridConstPtrCast<openvdb::Int32Grid>(grid)); | |||||
| } | } | ||||
| else if (grid->isType<openvdb::Int64Grid>()) { | |||||
| topology_grid->topologyUnion(*openvdb::gridConstPtrCast<openvdb::Int64Grid>(grid)); | |||||
| } | } | ||||
| else if (grid->isType<openvdb::Vec3IGrid>()) { | |||||
| topology_grid->topologyUnion(*openvdb::gridConstPtrCast<openvdb::Vec3IGrid>(grid)); | |||||
| } | } | ||||
| else if (grid->isType<openvdb::Vec3dGrid>()) { | |||||
| topology_grid->topologyUnion(*openvdb::gridConstPtrCast<openvdb::Vec3dGrid>(grid)); | |||||
| } | |||||
| else if (grid->isType<openvdb::MaskGrid>()) { | |||||
| topology_grid->topologyUnion(*openvdb::gridConstPtrCast<openvdb::MaskGrid>(grid)); | |||||
| } | |||||
| } | |||||
| #endif | |||||
| void VolumeMeshBuilder::add_padding(int pad_size) | |||||
| { | |||||
| #ifdef WITH_OPENVDB | |||||
| openvdb::tools::dilateVoxels(topology_grid->tree(), pad_size); | |||||
| #else | |||||
| (void)pad_size; | |||||
| #endif | |||||
| } | } | ||||
| void VolumeMeshBuilder::create_mesh(vector<float3> &vertices, | void VolumeMeshBuilder::create_mesh(vector<float3> &vertices, | ||||
| vector<int> &indices, | vector<int> &indices, | ||||
| vector<float3> &face_normals) | vector<float3> &face_normals, | ||||
| const float face_overlap_avoidance) | |||||
| { | { | ||||
| /* We create vertices in index space (is), and only convert them to object | /* We create vertices in index space (is), and only convert them to object | ||||
| * space when done. */ | * space when done. */ | ||||
| vector<int3> vertices_is; | vector<int3> vertices_is; | ||||
| vector<QuadData> quads; | vector<QuadData> quads; | ||||
| generate_vertices_and_quads(vertices_is, quads); | generate_vertices_and_quads(vertices_is, quads); | ||||
| convert_object_space(vertices_is, vertices); | convert_object_space(vertices_is, vertices, face_overlap_avoidance); | ||||
| convert_quads_to_tris(quads, indices, face_normals); | convert_quads_to_tris(quads, indices, face_normals); | ||||
| } | } | ||||
| void VolumeMeshBuilder::generate_vertices_and_quads(vector<ccl::int3> &vertices_is, | void VolumeMeshBuilder::generate_vertices_and_quads(vector<ccl::int3> &vertices_is, | ||||
| vector<QuadData> &quads) | vector<QuadData> &quads) | ||||
| { | { | ||||
| unordered_map<size_t, int> used_verts; | #ifdef WITH_OPENVDB | ||||
| const openvdb::MaskGrid::TreeType &tree = topology_grid->tree(); | |||||
| tree.evalLeafBoundingBox(bbox); | |||||
| for (int z = 0; z < res.z; ++z) { | const int3 resolution = make_int3(bbox.dim().x(), bbox.dim().y(), bbox.dim().z()); | ||||
| for (int y = 0; y < res.y; ++y) { | |||||
| for (int x = 0; x < res.x; ++x) { | |||||
| int64_t voxel_index = compute_voxel_index(res, x, y, z); | |||||
| if (grid[voxel_index] == 0) { | |||||
| continue; | |||||
| } | |||||
| /* Compute min and max coords of the node in index space. */ | unordered_map<size_t, int> used_verts; | ||||
| int3 min = make_int3((x - pad_offset.x) * CUBE_SIZE, | |||||
| (y - pad_offset.y) * CUBE_SIZE, | for (auto iter = tree.cbeginLeaf(); iter; ++iter) { | ||||
| (z - pad_offset.z) * CUBE_SIZE); | openvdb::CoordBBox leaf_bbox = iter->getNodeBoundingBox(); | ||||
| /* +1 to convert from exclusive to include bounds. */ | |||||
| leaf_bbox.max() = leaf_bbox.max().offsetBy(1); | |||||
| /* Maximum is just CUBE_SIZE voxels away from minimum on each axis. */ | int3 min = make_int3(leaf_bbox.min().x(), leaf_bbox.min().y(), leaf_bbox.min().z()); | ||||
| int3 max = make_int3(min.x + CUBE_SIZE, min.y + CUBE_SIZE, min.z + CUBE_SIZE); | int3 max = make_int3(leaf_bbox.max().x(), leaf_bbox.max().y(), leaf_bbox.max().z()); | ||||
| int3 corners[8] = { | int3 corners[8] = { | ||||
| make_int3(min[0], min[1], min[2]), | make_int3(min[0], min[1], min[2]), | ||||
| make_int3(max[0], min[1], min[2]), | make_int3(max[0], min[1], min[2]), | ||||
| make_int3(max[0], max[1], min[2]), | make_int3(max[0], max[1], min[2]), | ||||
| make_int3(min[0], max[1], min[2]), | make_int3(min[0], max[1], min[2]), | ||||
| make_int3(min[0], min[1], max[2]), | make_int3(min[0], min[1], max[2]), | ||||
| make_int3(max[0], min[1], max[2]), | make_int3(max[0], min[1], max[2]), | ||||
| make_int3(max[0], max[1], max[2]), | make_int3(max[0], max[1], max[2]), | ||||
| make_int3(min[0], max[1], max[2]), | make_int3(min[0], max[1], max[2]), | ||||
| }; | }; | ||||
| /* Only create a quad if on the border between an active and | /* Only create a quad if on the border between an active and an inactive leaf. | ||||
| * an inactive node. | * | ||||
| * We verify that a leaf exists by probing a coordinate that is at its center, | |||||
| * to do so we compute the center of the current leaf and offset this coordinate | |||||
| * by the size of a leaf in each direction. | |||||
| */ | */ | ||||
| static const int LEAF_DIM = openvdb::MaskGrid::TreeType::LeafNodeType::DIM; | |||||
| auto center = leaf_bbox.min() + openvdb::Coord(LEAF_DIM / 2); | |||||
| voxel_index = compute_voxel_index(res, x - 1, y, z); | if (!tree.probeLeaf(openvdb::Coord(center.x() - LEAF_DIM, center.y(), center.z()))) { | ||||
| if (voxel_index == VOXEL_INDEX_NONE || grid[voxel_index] == 0) { | create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_X_MIN); | ||||
| create_quad(corners, vertices_is, quads, res, used_verts, QUAD_X_MIN); | |||||
| } | } | ||||
| voxel_index = compute_voxel_index(res, x + 1, y, z); | if (!tree.probeLeaf(openvdb::Coord(center.x() + LEAF_DIM, center.y(), center.z()))) { | ||||
| if (voxel_index == VOXEL_INDEX_NONE || grid[voxel_index] == 0) { | create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_X_MAX); | ||||
| create_quad(corners, vertices_is, quads, res, used_verts, QUAD_X_MAX); | |||||
| } | } | ||||
| voxel_index = compute_voxel_index(res, x, y - 1, z); | if (!tree.probeLeaf(openvdb::Coord(center.x(), center.y() - LEAF_DIM, center.z()))) { | ||||
| if (voxel_index == VOXEL_INDEX_NONE || grid[voxel_index] == 0) { | create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Y_MIN); | ||||
| create_quad(corners, vertices_is, quads, res, used_verts, QUAD_Y_MIN); | |||||
| } | } | ||||
| voxel_index = compute_voxel_index(res, x, y + 1, z); | if (!tree.probeLeaf(openvdb::Coord(center.x(), center.y() + LEAF_DIM, center.z()))) { | ||||
| if (voxel_index == VOXEL_INDEX_NONE || grid[voxel_index] == 0) { | create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Y_MAX); | ||||
| create_quad(corners, vertices_is, quads, res, used_verts, QUAD_Y_MAX); | |||||
| } | } | ||||
| voxel_index = compute_voxel_index(res, x, y, z - 1); | if (!tree.probeLeaf(openvdb::Coord(center.x(), center.y(), center.z() - LEAF_DIM))) { | ||||
| if (voxel_index == VOXEL_INDEX_NONE || grid[voxel_index] == 0) { | create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Z_MIN); | ||||
| create_quad(corners, vertices_is, quads, res, used_verts, QUAD_Z_MIN); | |||||
| } | } | ||||
| voxel_index = compute_voxel_index(res, x, y, z + 1); | if (!tree.probeLeaf(openvdb::Coord(center.x(), center.y(), center.z() + LEAF_DIM))) { | ||||
| if (voxel_index == VOXEL_INDEX_NONE || grid[voxel_index] == 0) { | create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Z_MAX); | ||||
| create_quad(corners, vertices_is, quads, res, used_verts, QUAD_Z_MAX); | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| #else | |||||
| (void)vertices_is; | |||||
| (void)quads; | |||||
| #endif | |||||
| } | } | ||||
| void VolumeMeshBuilder::convert_object_space(const vector<int3> &vertices, | void VolumeMeshBuilder::convert_object_space(const vector<int3> &vertices, | ||||
| vector<float3> &out_vertices) | vector<float3> &out_vertices, | ||||
| const float face_overlap_avoidance) | |||||
| { | { | ||||
| #ifdef WITH_OPENVDB | |||||
| /* compute the offset for the face overlap avoidance */ | |||||
| bbox = topology_grid->evalActiveVoxelBoundingBox(); | |||||
| openvdb::Coord dim = bbox.dim(); | |||||
| float3 cell_size = make_float3(1.0f / dim.x(), 1.0f / dim.y(), 1.0f / dim.z()); | |||||
| float3 point_offset = cell_size * face_overlap_avoidance; | |||||
| out_vertices.reserve(vertices.size()); | out_vertices.reserve(vertices.size()); | ||||
| for (size_t i = 0; i < vertices.size(); ++i) { | for (size_t i = 0; i < vertices.size(); ++i) { | ||||
| float3 vertex = make_float3(vertices[i].x, vertices[i].y, vertices[i].z); | openvdb::math::Vec3d p = topology_grid->indexToWorld( | ||||
| vertex *= params->cell_size; | openvdb::math::Vec3d(vertices[i].x, vertices[i].y, vertices[i].z)); | ||||
| vertex += params->start_point; | float3 vertex = make_float3((float)p.x(), (float)p.y(), (float)p.z()); | ||||
| out_vertices.push_back(vertex + point_offset); | |||||
| out_vertices.push_back(vertex); | } | ||||
| } | #else | ||||
| (void)vertices; | |||||
| (void)out_vertices; | |||||
| (void)face_overlap_avoidance; | |||||
| #endif | |||||
| } | } | ||||
| void VolumeMeshBuilder::convert_quads_to_tris(const vector<QuadData> &quads, | void VolumeMeshBuilder::convert_quads_to_tris(const vector<QuadData> &quads, | ||||
| vector<int> &tris, | vector<int> &tris, | ||||
| vector<float3> &face_normals) | vector<float3> &face_normals) | ||||
| { | { | ||||
| int index_offset = 0; | int index_offset = 0; | ||||
| tris.resize(quads.size() * 6); | tris.resize(quads.size() * 6); | ||||
| Show All 9 Lines | for (size_t i = 0; i < quads.size(); ++i) { | ||||
| tris[index_offset++] = quads[i].v0; | tris[index_offset++] = quads[i].v0; | ||||
| tris[index_offset++] = quads[i].v3; | tris[index_offset++] = quads[i].v3; | ||||
| tris[index_offset++] = quads[i].v2; | tris[index_offset++] = quads[i].v2; | ||||
| face_normals.push_back(quads[i].normal); | face_normals.push_back(quads[i].normal); | ||||
| } | } | ||||
| } | } | ||||
| /* ************************************************************************** */ | bool VolumeMeshBuilder::empty_grid() const | ||||
| { | |||||
| #ifdef WITH_OPENVDB | |||||
| return topology_grid->tree().leafCount() == 0; | |||||
| #else | |||||
| return true; | |||||
| #endif | |||||
| } | |||||
| struct VoxelAttributeGrid { | /* ************************************************************************** */ | ||||
| float *data; | |||||
| int channels; | |||||
| }; | |||||
| void GeometryManager::create_volume_mesh(Mesh *mesh, Progress &progress) | void GeometryManager::create_volume_mesh(Mesh *mesh, Progress &progress) | ||||
| { | { | ||||
| string msg = string_printf("Computing Volume Mesh %s", mesh->name.c_str()); | string msg = string_printf("Computing Volume Mesh %s", mesh->name.c_str()); | ||||
| progress.set_status("Updating Mesh", msg); | progress.set_status("Updating Mesh", msg); | ||||
| vector<VoxelAttributeGrid> voxel_grids; | VolumeMeshBuilder builder; | ||||
| /* Compute volume parameters. */ | |||||
| VolumeParams volume_params; | |||||
| volume_params.resolution = make_int3(0, 0, 0); | |||||
| Transform transform = transform_identity(); | |||||
| foreach (Attribute &attr, mesh->attributes.attributes) { | foreach (Attribute &attr, mesh->attributes.attributes) { | ||||
| if (attr.element != ATTR_ELEMENT_VOXEL) { | if (attr.element != ATTR_ELEMENT_VOXEL) { | ||||
| continue; | continue; | ||||
| } | } | ||||
| ImageHandle &handle = attr.data_voxel(); | ImageHandle &handle = attr.data_voxel(); | ||||
| device_texture *image_memory = handle.image_memory(); | VDBImageLoader *vdb_loader = handle.vdb_loader(); | ||||
| int3 resolution = make_int3( | |||||
| image_memory->data_width, image_memory->data_height, image_memory->data_depth); | |||||
| if (volume_params.resolution == make_int3(0, 0, 0)) { | if (vdb_loader) { | ||||
| volume_params.resolution = resolution; | /* make sure the grid is loaded */ | ||||
| ImageMetaData metadata; | |||||
| if (vdb_loader->load_metadata(metadata)) { | |||||
| #ifdef WITH_OPENVDB | |||||
| builder.add_grid(vdb_loader->get_grid()); | |||||
| #endif | |||||
| vdb_loader->cleanup(); | |||||
| } | } | ||||
| else if (volume_params.resolution != resolution) { | |||||
| /* TODO: support this as it's common for OpenVDB. */ | |||||
| VLOG(1) << "Can't create accurate volume mesh, all voxel grid resolutions must be equal\n"; | |||||
| continue; | |||||
| } | } | ||||
| else { | |||||
| #ifdef WITH_OPENVDB | |||||
| device_texture *image_memory = handle.image_memory(); | |||||
| int3 image_resolution = make_int3( | |||||
| image_memory->data_width, image_memory->data_height, image_memory->data_depth); | |||||
| VoxelAttributeGrid voxel_grid; | openvdb::CoordBBox dense_bbox( | ||||
| voxel_grid.data = static_cast<float *>(image_memory->host_pointer); | 0, 0, 0, image_resolution.x, image_resolution.y, image_resolution.z); | ||||
| voxel_grid.channels = image_memory->data_elements; | openvdb::tools::Dense<float, openvdb::tools::MemoryLayout::LayoutXYZ> dense(dense_bbox); | ||||
| voxel_grids.push_back(voxel_grid); | openvdb::FloatGrid::Ptr sparse = openvdb::FloatGrid::create(mesh->volume_clipping); | ||||
| openvdb::tools::copyFromDense(dense, *sparse, mesh->volume_clipping); | |||||
| /* TODO: support multiple transforms. */ | builder.add_grid(sparse); | ||||
| if (image_memory->info.use_transform_3d) { | #endif | ||||
| transform = image_memory->info.transform_3d; | |||||
| } | } | ||||
| } | } | ||||
| if (voxel_grids.empty()) { | if (builder.empty_grid()) { | ||||
| return; | return; | ||||
| } | } | ||||
| /* Compute padding. */ | /* Compute padding. */ | ||||
| Shader *volume_shader = NULL; | Shader *volume_shader = NULL; | ||||
| int pad_size = 0; | int pad_size = 0; | ||||
| foreach (Shader *shader, mesh->used_shaders) { | foreach (Shader *shader, mesh->used_shaders) { | ||||
| Show All 12 Lines | foreach (Shader *shader, mesh->used_shaders) { | ||||
| break; | break; | ||||
| } | } | ||||
| if (!volume_shader) { | if (!volume_shader) { | ||||
| return; | return; | ||||
| } | } | ||||
| /* Compute start point and cell size from transform. */ | builder.add_padding(pad_size); | ||||
| const int3 resolution = volume_params.resolution; | |||||
| float3 start_point = make_float3(0.0f, 0.0f, 0.0f); | |||||
| float3 cell_size = make_float3(1.0f / resolution.x, 1.0f / resolution.y, 1.0f / resolution.z); | |||||
| /* TODO: support arbitrary transforms, not just scale + translate. */ | |||||
| const Transform itfm = transform_inverse(transform); | |||||
| start_point = transform_point(&itfm, start_point); | |||||
| cell_size = transform_direction(&itfm, cell_size); | |||||
| /* Slightly offset vertex coordinates to avoid overlapping faces with other | /* Slightly offset vertex coordinates to avoid overlapping faces with other | ||||
| * volumes or meshes. The proper solution would be to improve intersection in | * volumes or meshes. The proper solution would be to improve intersection in | ||||
| * the kernel to support robust handling of multiple overlapping faces or use | * the kernel to support robust handling of multiple overlapping faces or use | ||||
| * an all-hit intersection similar to shadows. */ | * an all-hit intersection similar to shadows. */ | ||||
| const float3 face_overlap_avoidance = cell_size * 0.1f * | const float face_overlap_avoidance = 0.1f * hash_uint_to_float(hash_string(mesh->name.c_str())); | ||||
| hash_uint_to_float(hash_string(mesh->name.c_str())); | |||||
| volume_params.start_point = start_point + face_overlap_avoidance; | |||||
| volume_params.cell_size = cell_size; | |||||
| volume_params.pad_size = pad_size; | |||||
| /* Build bounding mesh around non-empty volume cells. */ | |||||
| VolumeMeshBuilder builder(&volume_params); | |||||
| const float clipping = mesh->volume_clipping; | |||||
| for (int z = 0; z < resolution.z; ++z) { | |||||
| for (int y = 0; y < resolution.y; ++y) { | |||||
| for (int x = 0; x < resolution.x; ++x) { | |||||
| int64_t voxel_index = compute_voxel_index(resolution, x, y, z); | |||||
| for (size_t i = 0; i < voxel_grids.size(); ++i) { | |||||
| const VoxelAttributeGrid &voxel_grid = voxel_grids[i]; | |||||
| const int channels = voxel_grid.channels; | |||||
| for (int c = 0; c < channels; c++) { | |||||
| if (voxel_grid.data[voxel_index * channels + c] >= clipping) { | |||||
| builder.add_node_with_padding(x, y, z); | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| /* Create mesh. */ | /* Create mesh. */ | ||||
| vector<float3> vertices; | vector<float3> vertices; | ||||
| vector<int> indices; | vector<int> indices; | ||||
| vector<float3> face_normals; | vector<float3> face_normals; | ||||
| builder.create_mesh(vertices, indices, face_normals); | builder.create_mesh(vertices, indices, face_normals, face_overlap_avoidance); | ||||
| mesh->clear(true); | mesh->clear(true); | ||||
| mesh->reserve_mesh(vertices.size(), indices.size() / 3); | mesh->reserve_mesh(vertices.size(), indices.size() / 3); | ||||
| mesh->used_shaders.push_back(volume_shader); | mesh->used_shaders.push_back(volume_shader); | ||||
| for (size_t i = 0; i < vertices.size(); ++i) { | for (size_t i = 0; i < vertices.size(); ++i) { | ||||
| mesh->add_vertex(vertices[i]); | mesh->add_vertex(vertices[i]); | ||||
| } | } | ||||
| Show All 10 Lines | #endif | ||||
| } | } | ||||
| /* Print stats. */ | /* Print stats. */ | ||||
| VLOG(1) << "Memory usage volume mesh: " | VLOG(1) << "Memory usage volume mesh: " | ||||
| << ((vertices.size() + face_normals.size()) * sizeof(float3) + | << ((vertices.size() + face_normals.size()) * sizeof(float3) + | ||||
| indices.size() * sizeof(int)) / | indices.size() * sizeof(int)) / | ||||
| (1024.0 * 1024.0) | (1024.0 * 1024.0) | ||||
| << "Mb."; | << "Mb."; | ||||
| VLOG(1) << "Memory usage volume grid: " | |||||
| << (resolution.x * resolution.y * resolution.z * sizeof(float)) / (1024.0 * 1024.0) | |||||
| << "Mb."; | |||||
| } | } | ||||
| CCL_NAMESPACE_END | CCL_NAMESPACE_END | ||||
Cycles should continue to build without OpenVDB, even if it's without optimized bounding mesh.
So there needs to be #ifdef WITH_OPENVDB in this file.