From aa1e4baa22b3393dc723d48061c9781f4b8b42c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dietrich?= Date: Wed, 19 Aug 2020 15:46:50 +0200 Subject: Cycles : add a Volume Geometry Node This splits the volume related data (properties for rendering and attributes) of the Mesh node into a new `Volume` node type. This `Volume` node derives from the `Mesh` class since we generate a mesh for the bounds of the volume, as such we can safely work on `Volumes` as if they were `Meshes`, e.g. for BVH creation. However such code should still check for the geometry type of the object to be `MESH` or `VOLUME` which may be bug prone if this is forgotten. This is part of T79131. Reviewed By: brecht Maniphest Tasks: T79131 Differential Revision: https://developer.blender.org/D8538 --- intern/cycles/blender/blender_geometry.cpp | 29 +- intern/cycles/blender/blender_sync.h | 2 +- intern/cycles/blender/blender_volume.cpp | 49 +-- intern/cycles/bvh/bvh_build.cpp | 4 +- intern/cycles/bvh/bvh_embree.cpp | 6 +- intern/cycles/bvh/bvh_optix.cpp | 2 +- intern/cycles/bvh/bvh_split.cpp | 2 +- intern/cycles/device/device_optix.cpp | 2 +- intern/cycles/render/CMakeLists.txt | 3 +- intern/cycles/render/attribute.cpp | 29 +- intern/cycles/render/geometry.cpp | 31 +- intern/cycles/render/geometry.h | 4 +- intern/cycles/render/light.cpp | 2 +- intern/cycles/render/mesh.cpp | 11 +- intern/cycles/render/mesh.h | 7 +- intern/cycles/render/mesh_volume.cpp | 583 --------------------------- intern/cycles/render/object.cpp | 71 ++-- intern/cycles/render/volume.cpp | 611 +++++++++++++++++++++++++++++ intern/cycles/render/volume.h | 38 ++ 19 files changed, 797 insertions(+), 689 deletions(-) delete mode 100644 intern/cycles/render/mesh_volume.cpp create mode 100644 intern/cycles/render/volume.cpp create mode 100644 intern/cycles/render/volume.h (limited to 'intern') diff --git a/intern/cycles/blender/blender_geometry.cpp b/intern/cycles/blender/blender_geometry.cpp index f7e4623024d..9a82c5e9371 100644 --- a/intern/cycles/blender/blender_geometry.cpp +++ b/intern/cycles/blender/blender_geometry.cpp @@ -19,6 +19,7 @@ #include "render/hair.h" #include "render/mesh.h" #include "render/object.h" +#include "render/volume.h" #include "blender/blender_sync.h" #include "blender/blender_util.h" @@ -27,6 +28,19 @@ CCL_NAMESPACE_BEGIN +static Geometry::Type determine_geom_type(BL::Object &b_ob, bool use_particle_hair) +{ + if (b_ob.type() == BL::Object::type_HAIR || use_particle_hair) { + return Geometry::HAIR; + } + + if (b_ob.type() == BL::Object::type_VOLUME || object_fluid_gas_domain_find(b_ob)) { + return Geometry::VOLUME; + } + + return Geometry::MESH; +} + Geometry *BlenderSync::sync_geometry(BL::Depsgraph &b_depsgraph, BL::Object &b_ob, BL::Object &b_ob_instance, @@ -40,9 +54,7 @@ Geometry *BlenderSync::sync_geometry(BL::Depsgraph &b_depsgraph, BL::Material material_override = view_layer.material_override; Shader *default_shader = (b_ob.type() == BL::Object::type_VOLUME) ? scene->default_volume : scene->default_surface; - Geometry::Type geom_type = (b_ob.type() == BL::Object::type_HAIR || use_particle_hair) ? - Geometry::HAIR : - Geometry::MESH; + Geometry::Type geom_type = determine_geom_type(b_ob, use_particle_hair); /* Find shader indices. */ vector used_shaders; @@ -73,6 +85,9 @@ Geometry *BlenderSync::sync_geometry(BL::Depsgraph &b_depsgraph, if (geom_type == Geometry::HAIR) { geom = new Hair(); } + else if (geom_type == Geometry::VOLUME) { + geom = new Volume(); + } else { geom = new Mesh(); } @@ -121,13 +136,13 @@ Geometry *BlenderSync::sync_geometry(BL::Depsgraph &b_depsgraph, geom->name = ustring(b_ob_data.name().c_str()); - if (b_ob.type() == BL::Object::type_HAIR || use_particle_hair) { + if (geom_type == Geometry::HAIR) { Hair *hair = static_cast(geom); sync_hair(b_depsgraph, b_ob, hair, used_shaders); } - else if (b_ob.type() == BL::Object::type_VOLUME || object_fluid_gas_domain_find(b_ob)) { - Mesh *mesh = static_cast(geom); - sync_volume(b_ob, mesh, used_shaders); + else if (geom_type == Geometry::VOLUME) { + Volume *volume = static_cast(geom); + sync_volume(b_ob, volume, used_shaders); } else { Mesh *mesh = static_cast(geom); diff --git a/intern/cycles/blender/blender_sync.h b/intern/cycles/blender/blender_sync.h index a551ec31e04..62fd1ac2351 100644 --- a/intern/cycles/blender/blender_sync.h +++ b/intern/cycles/blender/blender_sync.h @@ -148,7 +148,7 @@ class BlenderSync { bool *use_portal); /* Volume */ - void sync_volume(BL::Object &b_ob, Mesh *mesh, const vector &used_shaders); + void sync_volume(BL::Object &b_ob, Volume *volume, const vector &used_shaders); /* Mesh */ void sync_mesh(BL::Depsgraph b_depsgraph, diff --git a/intern/cycles/blender/blender_volume.cpp b/intern/cycles/blender/blender_volume.cpp index d0e1e4d6131..e039d8a4895 100644 --- a/intern/cycles/blender/blender_volume.cpp +++ b/intern/cycles/blender/blender_volume.cpp @@ -17,8 +17,8 @@ #include "render/colorspace.h" #include "render/image.h" #include "render/image_vdb.h" -#include "render/mesh.h" #include "render/object.h" +#include "render/volume.h" #include "blender/blender_sync.h" #include "blender/blender_util.h" @@ -181,7 +181,7 @@ class BlenderSmokeLoader : public ImageLoader { AttributeStandard attribute; }; -static void sync_smoke_volume(Scene *scene, BL::Object &b_ob, Mesh *mesh, float frame) +static void sync_smoke_volume(Scene *scene, BL::Object &b_ob, Volume *volume, float frame) { BL::FluidDomainSettings b_domain = object_fluid_gas_domain_find(b_ob); if (!b_domain) { @@ -198,13 +198,13 @@ static void sync_smoke_volume(Scene *scene, BL::Object &b_ob, Mesh *mesh, float for (int i = 0; attributes[i] != ATTR_STD_NONE; i++) { AttributeStandard std = attributes[i]; - if (!mesh->need_attribute(scene, std)) { + if (!volume->need_attribute(scene, std)) { continue; } - mesh->volume_clipping = b_domain.clipping(); + volume->clipping = b_domain.clipping(); - Attribute *attr = mesh->attributes.add(std); + Attribute *attr = volume->attributes.add(std); ImageLoader *loader = new BlenderSmokeLoader(b_ob, std); ImageParams params; @@ -228,7 +228,7 @@ class BlenderVolumeLoader : public VDBImageLoader { if (b_volume_grid.name() == grid_name) { const bool unload = !b_volume_grid.is_loaded(); - Volume *volume = (Volume *)b_volume.ptr.data; + ::Volume *volume = (::Volume *)b_volume.ptr.data; VolumeGrid *volume_grid = (VolumeGrid *)b_volume_grid.ptr.data; grid = BKE_volume_grid_openvdb_for_read(volume, volume_grid); @@ -252,16 +252,19 @@ class BlenderVolumeLoader : public VDBImageLoader { BL::Volume b_volume; }; -static void sync_volume_object(BL::BlendData &b_data, BL::Object &b_ob, Scene *scene, Mesh *mesh) +static void sync_volume_object(BL::BlendData &b_data, + BL::Object &b_ob, + Scene *scene, + Volume *volume) { BL::Volume b_volume(b_ob.data()); b_volume.grids.load(b_data.ptr.data); BL::VolumeRender b_render(b_volume.render()); - mesh->volume_clipping = b_render.clipping(); - mesh->volume_step_size = b_render.step_size(); - mesh->volume_object_space = (b_render.space() == BL::VolumeRender::space_OBJECT); + volume->clipping = b_render.clipping(); + volume->step_size = b_render.step_size(); + volume->object_space = (b_render.space() == BL::VolumeRender::space_OBJECT); /* Find grid with matching name. */ BL::Volume::grids_iterator b_grid_iter; @@ -289,11 +292,11 @@ static void sync_volume_object(BL::BlendData &b_data, BL::Object &b_ob, Scene *s std = ATTR_STD_VOLUME_VELOCITY; } - if ((std != ATTR_STD_NONE && mesh->need_attribute(scene, std)) || - mesh->need_attribute(scene, name)) { + if ((std != ATTR_STD_NONE && volume->need_attribute(scene, std)) || + volume->need_attribute(scene, name)) { Attribute *attr = (std != ATTR_STD_NONE) ? - mesh->attributes.add(std) : - mesh->attributes.add(name, TypeDesc::TypeFloat, ATTR_ELEMENT_VOXEL); + volume->attributes.add(std) : + volume->attributes.add(name, TypeDesc::TypeFloat, ATTR_ELEMENT_VOXEL); ImageLoader *loader = new BlenderVolumeLoader(b_data, b_volume, name.string()); ImageParams params; @@ -317,28 +320,30 @@ static vector get_voxel_image_slots(Mesh *mesh) return slots; } -void BlenderSync::sync_volume(BL::Object &b_ob, Mesh *mesh, const vector &used_shaders) +void BlenderSync::sync_volume(BL::Object &b_ob, + Volume *volume, + const vector &used_shaders) { - vector old_voxel_slots = get_voxel_image_slots(mesh); + vector old_voxel_slots = get_voxel_image_slots(volume); - mesh->clear(); - mesh->used_shaders = used_shaders; + volume->clear(); + volume->used_shaders = used_shaders; if (view_layer.use_volumes) { if (b_ob.type() == BL::Object::type_VOLUME) { /* Volume object. Create only attributes, bounding mesh will then * be automatically generated later. */ - sync_volume_object(b_data, b_ob, scene, mesh); + sync_volume_object(b_data, b_ob, scene, volume); } else { /* Smoke domain. */ - sync_smoke_volume(scene, b_ob, mesh, b_scene.frame_current()); + sync_smoke_volume(scene, b_ob, volume, b_scene.frame_current()); } } /* Tag update. */ - bool rebuild = (old_voxel_slots != get_voxel_image_slots(mesh)); - mesh->tag_update(scene, rebuild); + bool rebuild = (old_voxel_slots != get_voxel_image_slots(volume)); + volume->tag_update(scene, rebuild); } CCL_NAMESPACE_END diff --git a/intern/cycles/bvh/bvh_build.cpp b/intern/cycles/bvh/bvh_build.cpp index 86ab7b00815..360cac59e9b 100644 --- a/intern/cycles/bvh/bvh_build.cpp +++ b/intern/cycles/bvh/bvh_build.cpp @@ -270,7 +270,7 @@ void BVHBuild::add_reference_curves(BoundBox &root, BoundBox ¢er, Hair *hair void BVHBuild::add_reference_geometry(BoundBox &root, BoundBox ¢er, Geometry *geom, int i) { - if (geom->type == Geometry::MESH) { + if (geom->type == Geometry::MESH || geom->type == Geometry::VOLUME) { Mesh *mesh = static_cast(geom); add_reference_triangles(root, center, mesh, i); } @@ -299,7 +299,7 @@ static size_t count_curve_segments(Hair *hair) static size_t count_primitives(Geometry *geom) { - if (geom->type == Geometry::MESH) { + if (geom->type == Geometry::MESH || geom->type == Geometry::VOLUME) { Mesh *mesh = static_cast(geom); return mesh->num_triangles(); } diff --git a/intern/cycles/bvh/bvh_embree.cpp b/intern/cycles/bvh/bvh_embree.cpp index 4ef873634f0..d5c442516c7 100644 --- a/intern/cycles/bvh/bvh_embree.cpp +++ b/intern/cycles/bvh/bvh_embree.cpp @@ -306,7 +306,7 @@ thread_mutex BVHEmbree::rtc_shared_mutex; static size_t count_primitives(Geometry *geom) { - if (geom->type == Geometry::MESH) { + if (geom->type == Geometry::MESH || geom->type == Geometry::VOLUME) { Mesh *mesh = static_cast(geom); return mesh->num_triangles(); } @@ -531,7 +531,7 @@ void BVHEmbree::add_object(Object *ob, int i) { Geometry *geom = ob->geometry; - if (geom->type == Geometry::MESH) { + if (geom->type == Geometry::MESH || geom->type == Geometry::VOLUME) { Mesh *mesh = static_cast(geom); if (mesh->num_triangles() > 0) { add_triangles(ob, mesh, i); @@ -979,7 +979,7 @@ void BVHEmbree::refit_nodes() if (!params.top_level || (ob->is_traceable() && !ob->geometry->is_instanced())) { Geometry *geom = ob->geometry; - if (geom->type == Geometry::MESH) { + if (geom->type == Geometry::MESH || geom->type == Geometry::VOLUME) { Mesh *mesh = static_cast(geom); if (mesh->num_triangles() > 0) { update_tri_vertex_buffer(rtcGetGeometry(scene, geom_id), mesh); diff --git a/intern/cycles/bvh/bvh_optix.cpp b/intern/cycles/bvh/bvh_optix.cpp index ccb7ae08625..0527c0eeda8 100644 --- a/intern/cycles/bvh/bvh_optix.cpp +++ b/intern/cycles/bvh/bvh_optix.cpp @@ -95,7 +95,7 @@ void BVHOptiX::pack_blas() } } } - else if (geom->type == Geometry::MESH) { + else if (geom->type == Geometry::MESH || geom->type == Geometry::VOLUME) { Mesh *const mesh = static_cast(geom); if (mesh->num_triangles() > 0) { const size_t num_triangles = mesh->num_triangles(); diff --git a/intern/cycles/bvh/bvh_split.cpp b/intern/cycles/bvh/bvh_split.cpp index 4b21f852d7a..2f1960d664e 100644 --- a/intern/cycles/bvh/bvh_split.cpp +++ b/intern/cycles/bvh/bvh_split.cpp @@ -458,7 +458,7 @@ void BVHSpatialSplit::split_object_reference( { Geometry *geom = object->geometry; - if (geom->type == Geometry::MESH) { + if (geom->type == Geometry::MESH || geom->type == Geometry::VOLUME) { Mesh *mesh = static_cast(geom); for (int tri_idx = 0; tri_idx < mesh->num_triangles(); ++tri_idx) { split_triangle_primitive(mesh, &object->tfm, tri_idx, dim, pos, left_bounds, right_bounds); diff --git a/intern/cycles/device/device_optix.cpp b/intern/cycles/device/device_optix.cpp index 1cc45983565..43b1fb30baf 100644 --- a/intern/cycles/device/device_optix.cpp +++ b/intern/cycles/device/device_optix.cpp @@ -1389,7 +1389,7 @@ class OptiXDevice : public CUDADevice { return false; } } - else if (geom->type == Geometry::MESH) { + else if (geom->type == Geometry::MESH || geom->type == Geometry::VOLUME) { // Build BLAS for triangle primitives Mesh *const mesh = static_cast(ob->geometry); if (mesh->num_triangles() == 0) { diff --git a/intern/cycles/render/CMakeLists.txt b/intern/cycles/render/CMakeLists.txt index 6a1335dc5dd..46d9b503c03 100644 --- a/intern/cycles/render/CMakeLists.txt +++ b/intern/cycles/render/CMakeLists.txt @@ -34,7 +34,6 @@ set(SRC mesh.cpp mesh_displace.cpp mesh_subdivision.cpp - mesh_volume.cpp nodes.cpp object.cpp osl.cpp @@ -48,6 +47,7 @@ set(SRC svm.cpp tables.cpp tile.cpp + volume.cpp ) set(SRC_HEADERS @@ -86,6 +86,7 @@ set(SRC_HEADERS svm.h tables.h tile.h + volume.h ) set(LIB diff --git a/intern/cycles/render/attribute.cpp b/intern/cycles/render/attribute.cpp index 4c26d5e8365..cdef1036647 100644 --- a/intern/cycles/render/attribute.cpp +++ b/intern/cycles/render/attribute.cpp @@ -167,7 +167,7 @@ size_t Attribute::element_size(Geometry *geom, AttributePrimitive prim) const size = 1; break; case ATTR_ELEMENT_VERTEX: - if (geom->type == Geometry::MESH) { + if (geom->type == Geometry::MESH || geom->type == Geometry::VOLUME) { Mesh *mesh = static_cast(geom); size = mesh->verts.size() + mesh->num_ngons; if (prim == ATTR_PRIM_SUBD) { @@ -185,7 +185,7 @@ size_t Attribute::element_size(Geometry *geom, AttributePrimitive prim) const } break; case ATTR_ELEMENT_FACE: - if (geom->type == Geometry::MESH) { + if (geom->type == Geometry::MESH || geom->type == Geometry::VOLUME) { Mesh *mesh = static_cast(geom); if (prim == ATTR_PRIM_GEOMETRY) { size = mesh->num_triangles(); @@ -485,6 +485,25 @@ Attribute *AttributeSet::add(AttributeStandard std, ustring name) case ATTR_STD_GENERATED_TRANSFORM: attr = add(name, TypeDesc::TypeMatrix, ATTR_ELEMENT_MESH); break; + case ATTR_STD_POINTINESS: + attr = add(name, TypeDesc::TypeFloat, ATTR_ELEMENT_VERTEX); + break; + case ATTR_STD_RANDOM_PER_ISLAND: + attr = add(name, TypeDesc::TypeFloat, ATTR_ELEMENT_FACE); + break; + default: + assert(0); + break; + } + } + else if (geometry->type == Geometry::VOLUME) { + switch (std) { + case ATTR_STD_VERTEX_NORMAL: + attr = add(name, TypeDesc::TypeNormal, ATTR_ELEMENT_VERTEX); + break; + case ATTR_STD_FACE_NORMAL: + attr = add(name, TypeDesc::TypeNormal, ATTR_ELEMENT_FACE); + break; case ATTR_STD_VOLUME_DENSITY: case ATTR_STD_VOLUME_FLAME: case ATTR_STD_VOLUME_HEAT: @@ -497,12 +516,6 @@ Attribute *AttributeSet::add(AttributeStandard std, ustring name) case ATTR_STD_VOLUME_VELOCITY: attr = add(name, TypeDesc::TypeVector, ATTR_ELEMENT_VOXEL); break; - case ATTR_STD_POINTINESS: - attr = add(name, TypeDesc::TypeFloat, ATTR_ELEMENT_VERTEX); - break; - case ATTR_STD_RANDOM_PER_ISLAND: - attr = add(name, TypeDesc::TypeFloat, ATTR_ELEMENT_FACE); - break; default: assert(0); break; diff --git a/intern/cycles/render/geometry.cpp b/intern/cycles/render/geometry.cpp index 145b1fa492c..71540dc9c7b 100644 --- a/intern/cycles/render/geometry.cpp +++ b/intern/cycles/render/geometry.cpp @@ -31,6 +31,7 @@ #include "render/scene.h" #include "render/shader.h" #include "render/stats.h" +#include "render/volume.h" #include "subd/subd_patch_table.h" #include "subd/subd_split.h" @@ -796,7 +797,7 @@ void GeometryManager::mesh_calc_offset(Scene *scene) size_t optix_prim_size = 0; foreach (Geometry *geom, scene->geometry) { - if (geom->type == Geometry::MESH) { + if (geom->type == Geometry::MESH || geom->type == Geometry::VOLUME) { Mesh *mesh = static_cast(geom); mesh->vert_offset = vert_size; @@ -854,7 +855,7 @@ void GeometryManager::device_update_mesh( size_t patch_size = 0; foreach (Geometry *geom, scene->geometry) { - if (geom->type == Geometry::MESH) { + if (geom->type == Geometry::MESH || geom->type == Geometry::VOLUME) { Mesh *mesh = static_cast(geom); vert_size += mesh->verts.size(); @@ -888,7 +889,7 @@ void GeometryManager::device_update_mesh( * really use same semantic of arrays. */ foreach (Geometry *geom, scene->geometry) { - if (geom->type == Geometry::MESH) { + if (geom->type == Geometry::MESH || geom->type == Geometry::VOLUME) { Mesh *mesh = static_cast(geom); for (size_t i = 0; i < mesh->num_triangles(); ++i) { tri_prim_index[i + mesh->prim_offset] = 3 * (i + mesh->prim_offset); @@ -916,7 +917,7 @@ void GeometryManager::device_update_mesh( float2 *tri_patch_uv = dscene->tri_patch_uv.alloc(vert_size); foreach (Geometry *geom, scene->geometry) { - if (geom->type == Geometry::MESH) { + if (geom->type == Geometry::MESH || geom->type == Geometry::VOLUME) { Mesh *mesh = static_cast(geom); mesh->pack_shaders(scene, &tri_shader[mesh->prim_offset]); mesh->pack_normals(&vnormal[mesh->vert_offset]); @@ -992,7 +993,7 @@ void GeometryManager::device_update_mesh( if (for_displacement) { float4 *prim_tri_verts = dscene->prim_tri_verts.alloc(tri_size * 3); foreach (Geometry *geom, scene->geometry) { - if (geom->type == Geometry::MESH) { + if (geom->type == Geometry::MESH || geom->type == Geometry::VOLUME) { Mesh *mesh = static_cast(geom); for (size_t i = 0; i < mesh->num_triangles(); ++i) { Mesh::Triangle t = mesh->get_triangle(i); @@ -1122,18 +1123,16 @@ void GeometryManager::device_update_preprocess(Device *device, Scene *scene, Pro } } - if (need_update && geom->has_volume && geom->type == Geometry::MESH) { + if (need_update && geom->type == Geometry::VOLUME) { /* Create volume meshes if there is voxel data. */ - if (geom->has_voxel_attributes()) { - if (!volume_images_updated) { - progress.set_status("Updating Meshes Volume Bounds"); - device_update_volume_images(device, scene, progress); - volume_images_updated = true; - } - - Mesh *mesh = static_cast(geom); - create_volume_mesh(mesh, progress); + if (!volume_images_updated) { + progress.set_status("Updating Meshes Volume Bounds"); + device_update_volume_images(device, scene, progress); + volume_images_updated = true; } + + Volume *volume = static_cast(geom); + create_volume_mesh(volume, progress); } if (geom->type == Geometry::HAIR) { @@ -1238,7 +1237,7 @@ void GeometryManager::device_update(Device *device, geom->need_update = true; } - if (geom->need_update && geom->type == Geometry::MESH) { + if (geom->need_update && (geom->type == Geometry::MESH || geom->type == Geometry::VOLUME)) { Mesh *mesh = static_cast(geom); /* Update normals. */ diff --git a/intern/cycles/render/geometry.h b/intern/cycles/render/geometry.h index b0284304843..bcadb3a8051 100644 --- a/intern/cycles/render/geometry.h +++ b/intern/cycles/render/geometry.h @@ -40,6 +40,7 @@ class RenderStats; class Scene; class SceneParams; class Shader; +class Volume; /* Geometry * @@ -52,6 +53,7 @@ class Geometry : public Node { enum Type { MESH, HAIR, + VOLUME, }; Type type; @@ -166,7 +168,7 @@ class GeometryManager { protected: bool displace(Device *device, DeviceScene *dscene, Scene *scene, Mesh *mesh, Progress &progress); - void create_volume_mesh(Mesh *mesh, Progress &progress); + void create_volume_mesh(Volume *volume, Progress &progress); /* Attributes */ void update_osl_attributes(Device *device, diff --git a/intern/cycles/render/light.cpp b/intern/cycles/render/light.cpp index 567a53afc98..5f586f98b13 100644 --- a/intern/cycles/render/light.cpp +++ b/intern/cycles/render/light.cpp @@ -250,7 +250,7 @@ void LightManager::test_enabled_lights(Scene *scene) bool LightManager::object_usable_as_light(Object *object) { Geometry *geom = object->geometry; - if (geom->type != Geometry::MESH) { + if (geom->type != Geometry::MESH && geom->type != Geometry::VOLUME) { return false; } /* Skip objects with NaNs */ diff --git a/intern/cycles/render/mesh.cpp b/intern/cycles/render/mesh.cpp index c262d770331..3015ac5e569 100644 --- a/intern/cycles/render/mesh.cpp +++ b/intern/cycles/render/mesh.cpp @@ -135,7 +135,8 @@ NODE_DEFINE(Mesh) return type; } -Mesh::Mesh() : Geometry(node_type, Geometry::MESH), subd_attributes(this, ATTR_PRIM_SUBD) +Mesh::Mesh(const NodeType *node_type_, Type geom_type_) + : Geometry(node_type_, geom_type_), subd_attributes(this, ATTR_PRIM_SUBD) { vert_offset = 0; @@ -145,10 +146,6 @@ Mesh::Mesh() : Geometry(node_type, Geometry::MESH), subd_attributes(this, ATTR_P num_subd_verts = 0; - volume_clipping = 0.001f; - volume_step_size = 0.0f; - volume_object_space = false; - num_ngons = 0; subdivision_type = SUBDIVISION_NONE; @@ -157,6 +154,10 @@ Mesh::Mesh() : Geometry(node_type, Geometry::MESH), subd_attributes(this, ATTR_P patch_table = NULL; } +Mesh::Mesh() : Mesh(node_type, Geometry::MESH) +{ +} + Mesh::~Mesh() { delete patch_table; diff --git a/intern/cycles/render/mesh.h b/intern/cycles/render/mesh.h index d0cf4d557aa..c8286a01e2c 100644 --- a/intern/cycles/render/mesh.h +++ b/intern/cycles/render/mesh.h @@ -52,6 +52,9 @@ struct PackedPatchTable; /* Mesh */ class Mesh : public Geometry { + protected: + Mesh(const NodeType *node_type_, Type geom_type_); + public: NODE_DECLARE @@ -133,10 +136,6 @@ class Mesh : public Geometry { array triangle_patch; /* must be < 0 for non subd triangles */ array vert_patch_uv; - float volume_clipping; - float volume_step_size; - bool volume_object_space; - array subd_faces; array subd_face_corners; int num_ngons; diff --git a/intern/cycles/render/mesh_volume.cpp b/intern/cycles/render/mesh_volume.cpp deleted file mode 100644 index 567ed7738f3..00000000000 --- a/intern/cycles/render/mesh_volume.cpp +++ /dev/null @@ -1,583 +0,0 @@ -/* - * Copyright 2011-2016 Blender Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "render/attribute.h" -#include "render/image_vdb.h" -#include "render/mesh.h" -#include "render/scene.h" - -#ifdef WITH_OPENVDB -# include -# include -# include -#endif - -#include "util/util_foreach.h" -#include "util/util_hash.h" -#include "util/util_logging.h" -#include "util/util_openvdb.h" -#include "util/util_progress.h" -#include "util/util_types.h" - -CCL_NAMESPACE_BEGIN - -struct QuadData { - int v0, v1, v2, v3; - - float3 normal; -}; - -enum { - QUAD_X_MIN = 0, - QUAD_X_MAX = 1, - QUAD_Y_MIN = 2, - QUAD_Y_MAX = 3, - QUAD_Z_MIN = 4, - QUAD_Z_MAX = 5, -}; - -const int quads_indices[6][4] = { - /* QUAD_X_MIN */ - {4, 0, 3, 7}, - /* QUAD_X_MAX */ - {1, 5, 6, 2}, - /* QUAD_Y_MIN */ - {4, 5, 1, 0}, - /* QUAD_Y_MAX */ - {3, 2, 6, 7}, - /* QUAD_Z_MIN */ - {0, 1, 2, 3}, - /* QUAD_Z_MAX */ - {5, 4, 7, 6}, -}; - -const float3 quads_normals[6] = { - /* QUAD_X_MIN */ - make_float3(-1.0f, 0.0f, 0.0f), - /* QUAD_X_MAX */ - make_float3(1.0f, 0.0f, 0.0f), - /* QUAD_Y_MIN */ - make_float3(0.0f, -1.0f, 0.0f), - /* QUAD_Y_MAX */ - make_float3(0.0f, 1.0f, 0.0f), - /* QUAD_Z_MIN */ - make_float3(0.0f, 0.0f, -1.0f), - /* QUAD_Z_MAX */ - make_float3(0.0f, 0.0f, 1.0f), -}; - -static int add_vertex(int3 v, - vector &vertices, - int3 res, - unordered_map &used_verts) -{ - size_t vert_key = v.x + v.y * (res.x + 1) + v.z * (res.x + 1) * (res.y + 1); - unordered_map::iterator it = used_verts.find(vert_key); - - if (it != used_verts.end()) { - return it->second; - } - - int vertex_offset = vertices.size(); - used_verts[vert_key] = vertex_offset; - vertices.push_back(v); - return vertex_offset; -} - -static void create_quad(int3 corners[8], - vector &vertices, - vector &quads, - int3 res, - unordered_map &used_verts, - int face_index) -{ - QuadData quad; - quad.v0 = add_vertex(corners[quads_indices[face_index][0]], 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.v3 = add_vertex(corners[quads_indices[face_index][3]], vertices, res, used_verts); - quad.normal = quads_normals[face_index]; - - quads.push_back(quad); -} - -/* Create a mesh from a volume. - * - * The way the algorithm works is as follows: - * - * - The topologies of input OpenVDB grids are merged into a temporary grid. - * - Voxels of the temporary grid are dilated to account for the padding necessary for volume - * sampling. - * - Quads are created on the boundary between active and inactive leaf nodes of the temporary - * grid. - */ -class VolumeMeshBuilder { - public: -#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, bool do_clipping, float volume_clipping); -#endif - - void add_padding(int pad_size); - - void create_mesh(vector &vertices, - vector &indices, - vector &face_normals, - const float face_overlap_avoidance); - - void generate_vertices_and_quads(vector &vertices_is, vector &quads); - - void convert_object_space(const vector &vertices, - vector &out_vertices, - const float face_overlap_avoidance); - - void convert_quads_to_tris(const vector &quads, - vector &tris, - vector &face_normals); - - bool empty_grid() const; - -#ifdef WITH_OPENVDB - template - void merge_grid(openvdb::GridBase::ConstPtr grid, bool do_clipping, float volume_clipping) - { - typename GridType::ConstPtr typed_grid = openvdb::gridConstPtrCast(grid); - - if (do_clipping) { - using ValueType = typename GridType::ValueType; - typename GridType::Ptr copy = typed_grid->deepCopy(); - typename GridType::ValueOnIter iter = copy->beginValueOn(); - - for (; iter; ++iter) { - if (iter.getValue() < ValueType(volume_clipping)) { - iter.setValueOff(); - } - } - - typed_grid = copy; - } - - topology_grid->topologyUnion(*typed_grid); - } -#endif -}; - -VolumeMeshBuilder::VolumeMeshBuilder() -{ - first_grid = true; -} - -#ifdef WITH_OPENVDB -void VolumeMeshBuilder::add_grid(openvdb::GridBase::ConstPtr grid, - bool do_clipping, - float volume_clipping) -{ - /* set the transform of our grid from the first one */ - if (first_grid) { - topology_grid = openvdb::MaskGrid::create(); - topology_grid->setTransform(grid->transform().copy()); - first_grid = false; - } - /* if the transforms do not match, we need to resample one of the grids so that - * its index space registers with that of the other, here we resample our mask - * grid so memory usage is kept low */ - else if (topology_grid->transform() != grid->transform()) { - openvdb::MaskGrid::Ptr temp_grid = topology_grid->copyWithNewTree(); - temp_grid->setTransform(grid->transform().copy()); - openvdb::tools::resampleToMatch(*topology_grid, *temp_grid); - topology_grid = temp_grid; - topology_grid->setTransform(grid->transform().copy()); - } - - if (grid->isType()) { - merge_grid(grid, do_clipping, volume_clipping); - } - else if (grid->isType()) { - merge_grid(grid, do_clipping, volume_clipping); - } - else if (grid->isType()) { - merge_grid(grid, do_clipping, volume_clipping); - } - else if (grid->isType()) { - merge_grid(grid, do_clipping, volume_clipping); - } - else if (grid->isType()) { - merge_grid(grid, do_clipping, volume_clipping); - } - else if (grid->isType()) { - merge_grid(grid, do_clipping, volume_clipping); - } - else if (grid->isType()) { - merge_grid(grid, do_clipping, volume_clipping); - } - else if (grid->isType()) { - merge_grid(grid, do_clipping, volume_clipping); - } - else if (grid->isType()) { - merge_grid(grid, do_clipping, volume_clipping); - } - else if (grid->isType()) { - topology_grid->topologyUnion(*openvdb::gridConstPtrCast(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 &vertices, - vector &indices, - vector &face_normals, - const float face_overlap_avoidance) -{ - /* We create vertices in index space (is), and only convert them to object - * space when done. */ - vector vertices_is; - vector quads; - - generate_vertices_and_quads(vertices_is, quads); - - convert_object_space(vertices_is, vertices, face_overlap_avoidance); - - convert_quads_to_tris(quads, indices, face_normals); -} - -void VolumeMeshBuilder::generate_vertices_and_quads(vector &vertices_is, - vector &quads) -{ -#ifdef WITH_OPENVDB - const openvdb::MaskGrid::TreeType &tree = topology_grid->tree(); - tree.evalLeafBoundingBox(bbox); - - const int3 resolution = make_int3(bbox.dim().x(), bbox.dim().y(), bbox.dim().z()); - - unordered_map used_verts; - - for (auto iter = tree.cbeginLeaf(); iter; ++iter) { - openvdb::CoordBBox leaf_bbox = iter->getNodeBoundingBox(); - /* +1 to convert from exclusive to include bounds. */ - leaf_bbox.max() = leaf_bbox.max().offsetBy(1); - - int3 min = make_int3(leaf_bbox.min().x(), leaf_bbox.min().y(), leaf_bbox.min().z()); - int3 max = make_int3(leaf_bbox.max().x(), leaf_bbox.max().y(), leaf_bbox.max().z()); - - int3 corners[8] = { - make_int3(min[0], min[1], min[2]), - make_int3(max[0], min[1], min[2]), - make_int3(max[0], max[1], min[2]), - make_int3(min[0], max[1], min[2]), - make_int3(min[0], min[1], max[2]), - make_int3(max[0], min[1], max[2]), - make_int3(max[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 an inactive leaf. - * - * 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); - - if (!tree.probeLeaf(openvdb::Coord(center.x() - LEAF_DIM, center.y(), center.z()))) { - create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_X_MIN); - } - - if (!tree.probeLeaf(openvdb::Coord(center.x() + LEAF_DIM, center.y(), center.z()))) { - create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_X_MAX); - } - - if (!tree.probeLeaf(openvdb::Coord(center.x(), center.y() - LEAF_DIM, center.z()))) { - create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Y_MIN); - } - - if (!tree.probeLeaf(openvdb::Coord(center.x(), center.y() + LEAF_DIM, center.z()))) { - create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Y_MAX); - } - - if (!tree.probeLeaf(openvdb::Coord(center.x(), center.y(), center.z() - LEAF_DIM))) { - create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Z_MIN); - } - - if (!tree.probeLeaf(openvdb::Coord(center.x(), center.y(), center.z() + LEAF_DIM))) { - create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Z_MAX); - } - } -#else - (void)vertices_is; - (void)quads; -#endif -} - -void VolumeMeshBuilder::convert_object_space(const vector &vertices, - vector &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()); - - for (size_t i = 0; i < vertices.size(); ++i) { - openvdb::math::Vec3d p = topology_grid->indexToWorld( - openvdb::math::Vec3d(vertices[i].x, vertices[i].y, vertices[i].z)); - float3 vertex = make_float3((float)p.x(), (float)p.y(), (float)p.z()); - out_vertices.push_back(vertex + point_offset); - } -#else - (void)vertices; - (void)out_vertices; - (void)face_overlap_avoidance; -#endif -} - -void VolumeMeshBuilder::convert_quads_to_tris(const vector &quads, - vector &tris, - vector &face_normals) -{ - int index_offset = 0; - tris.resize(quads.size() * 6); - face_normals.reserve(quads.size() * 2); - - for (size_t i = 0; i < quads.size(); ++i) { - tris[index_offset++] = quads[i].v0; - tris[index_offset++] = quads[i].v2; - tris[index_offset++] = quads[i].v1; - - face_normals.push_back(quads[i].normal); - - tris[index_offset++] = quads[i].v0; - tris[index_offset++] = quads[i].v3; - tris[index_offset++] = quads[i].v2; - - face_normals.push_back(quads[i].normal); - } -} - -bool VolumeMeshBuilder::empty_grid() const -{ -#ifdef WITH_OPENVDB - return !topology_grid || topology_grid->tree().leafCount() == 0; -#else - return true; -#endif -} - -#ifdef WITH_OPENVDB -template -static openvdb::GridBase::ConstPtr openvdb_grid_from_device_texture(device_texture *image_memory, - float volume_clipping, - Transform transform_3d) -{ - using ValueType = typename GridType::ValueType; - - openvdb::CoordBBox dense_bbox(0, - 0, - 0, - image_memory->data_width - 1, - image_memory->data_height - 1, - image_memory->data_depth - 1); - openvdb::tools::Dense dense( - dense_bbox, static_cast(image_memory->host_pointer)); - - typename GridType::Ptr sparse = GridType::create(ValueType(0.0f)); - openvdb::tools::copyFromDense(dense, *sparse, ValueType(volume_clipping)); - - /* #copyFromDense will remove any leaf node that contains constant data and replace it with a - * tile, however, we need to preserve the leaves in order to generate the mesh, so re-voxelize - * the leaves that were pruned. This should not affect areas that were skipped due to the - * volume_clipping parameter. */ - sparse->tree().voxelizeActiveTiles(); - - /* Compute index to world matrix. */ - float3 voxel_size = make_float3(1.0f / image_memory->data_width, - 1.0f / image_memory->data_height, - 1.0f / image_memory->data_depth); - - transform_3d = transform_inverse(transform_3d); - - openvdb::Mat4R index_to_world_mat((double)(voxel_size.x * transform_3d[0][0]), - 0.0, - 0.0, - 0.0, - 0.0, - (double)(voxel_size.y * transform_3d[1][1]), - 0.0, - 0.0, - 0.0, - 0.0, - (double)(voxel_size.z * transform_3d[2][2]), - 0.0, - (double)transform_3d[0][3], - (double)transform_3d[1][3], - (double)transform_3d[2][3], - 1.0); - - openvdb::math::Transform::Ptr index_to_world_tfm = - openvdb::math::Transform::createLinearTransform(index_to_world_mat); - - sparse->setTransform(index_to_world_tfm); - - return sparse; -} -#endif - -/* ************************************************************************** */ - -void GeometryManager::create_volume_mesh(Mesh *mesh, Progress &progress) -{ - string msg = string_printf("Computing Volume Mesh %s", mesh->name.c_str()); - progress.set_status("Updating Mesh", msg); - - VolumeMeshBuilder builder; - -#ifdef WITH_OPENVDB - foreach (Attribute &attr, mesh->attributes.attributes) { - if (attr.element != ATTR_ELEMENT_VOXEL) { - continue; - } - - bool do_clipping = false; - - ImageHandle &handle = attr.data_voxel(); - - /* Try building from OpenVDB grid directly. */ - VDBImageLoader *vdb_loader = handle.vdb_loader(); - openvdb::GridBase::ConstPtr grid; - if (vdb_loader) { - grid = vdb_loader->get_grid(); - - /* If building from an OpenVDB grid, we need to manually clip the values. */ - do_clipping = true; - } - - /* Else fall back to creating an OpenVDB grid from the dense volume data. */ - if (!grid) { - device_texture *image_memory = handle.image_memory(); - - if (image_memory->data_elements == 1) { - grid = openvdb_grid_from_device_texture( - image_memory, mesh->volume_clipping, handle.metadata().transform_3d); - } - else if (image_memory->data_elements == 3) { - grid = openvdb_grid_from_device_texture( - image_memory, mesh->volume_clipping, handle.metadata().transform_3d); - } - else if (image_memory->data_elements == 4) { - grid = openvdb_grid_from_device_texture( - image_memory, mesh->volume_clipping, handle.metadata().transform_3d); - } - } - - if (grid) { - builder.add_grid(grid, do_clipping, mesh->volume_clipping); - } - } -#endif - - if (builder.empty_grid()) { - return; - } - - /* Compute padding. */ - Shader *volume_shader = NULL; - int pad_size = 0; - - foreach (Shader *shader, mesh->used_shaders) { - if (!shader->has_volume) { - continue; - } - - volume_shader = shader; - - if (shader->volume_interpolation_method == VOLUME_INTERPOLATION_LINEAR) { - pad_size = max(1, pad_size); - } - else if (shader->volume_interpolation_method == VOLUME_INTERPOLATION_CUBIC) { - pad_size = max(2, pad_size); - } - - break; - } - - if (!volume_shader) { - return; - } - - builder.add_padding(pad_size); - - /* Slightly offset vertex coordinates to avoid overlapping faces with other - * volumes or meshes. The proper solution would be to improve intersection in - * the kernel to support robust handling of multiple overlapping faces or use - * an all-hit intersection similar to shadows. */ - const float face_overlap_avoidance = 0.1f * hash_uint_to_float(hash_string(mesh->name.c_str())); - - /* Create mesh. */ - vector vertices; - vector indices; - vector face_normals; - builder.create_mesh(vertices, indices, face_normals, face_overlap_avoidance); - - mesh->clear(true); - mesh->reserve_mesh(vertices.size(), indices.size() / 3); - mesh->used_shaders.push_back(volume_shader); - mesh->need_update_rebuild = true; - - for (size_t i = 0; i < vertices.size(); ++i) { - mesh->add_vertex(vertices[i]); - } - - for (size_t i = 0; i < indices.size(); i += 3) { - mesh->add_triangle(indices[i], indices[i + 1], indices[i + 2], 0, false); - } - - Attribute *attr_fN = mesh->attributes.add(ATTR_STD_FACE_NORMAL); - float3 *fN = attr_fN->data_float3(); - - for (size_t i = 0; i < face_normals.size(); ++i) { - fN[i] = face_normals[i]; - } - - /* Print stats. */ - VLOG(1) << "Memory usage volume mesh: " - << ((vertices.size() + face_normals.size()) * sizeof(float3) + - indices.size() * sizeof(int)) / - (1024.0 * 1024.0) - << "Mb."; -} - -CCL_NAMESPACE_END diff --git a/intern/cycles/render/object.cpp b/intern/cycles/render/object.cpp index f200e409b9e..9396ae49288 100644 --- a/intern/cycles/render/object.cpp +++ b/intern/cycles/render/object.cpp @@ -24,6 +24,7 @@ #include "render/mesh.h" #include "render/particles.h" #include "render/scene.h" +#include "render/volume.h" #include "util/util_foreach.h" #include "util/util_logging.h" @@ -270,7 +271,7 @@ uint Object::visibility_for_tracing() const float Object::compute_volume_step_size() const { - if (geometry->type != Geometry::MESH) { + if (geometry->type != Geometry::MESH && geometry->type != Geometry::VOLUME) { return FLT_MAX; } @@ -299,37 +300,41 @@ float Object::compute_volume_step_size() const /* Compute step size from voxel grids. */ float step_size = FLT_MAX; - foreach (Attribute &attr, mesh->attributes.attributes) { - if (attr.element == ATTR_ELEMENT_VOXEL) { - ImageHandle &handle = attr.data_voxel(); - const ImageMetaData &metadata = handle.metadata(); - if (metadata.width == 0 || metadata.height == 0 || metadata.depth == 0) { - continue; - } + if (geometry->type == Geometry::VOLUME) { + Volume *volume = static_cast(geometry); - /* User specified step size. */ - float voxel_step_size = mesh->volume_step_size; + foreach (Attribute &attr, volume->attributes.attributes) { + if (attr.element == ATTR_ELEMENT_VOXEL) { + ImageHandle &handle = attr.data_voxel(); + const ImageMetaData &metadata = handle.metadata(); + if (metadata.width == 0 || metadata.height == 0 || metadata.depth == 0) { + continue; + } - if (voxel_step_size == 0.0f) { - /* Auto detect step size. */ - float3 size = make_float3( - 1.0f / metadata.width, 1.0f / metadata.height, 1.0f / metadata.depth); + /* User specified step size. */ + float voxel_step_size = volume->step_size; - /* Step size is transformed from voxel to world space. */ - Transform voxel_tfm = tfm; - if (metadata.use_transform_3d) { - voxel_tfm = tfm * transform_inverse(metadata.transform_3d); + if (voxel_step_size == 0.0f) { + /* Auto detect step size. */ + float3 size = make_float3( + 1.0f / metadata.width, 1.0f / metadata.height, 1.0f / metadata.depth); + + /* Step size is transformed from voxel to world space. */ + Transform voxel_tfm = tfm; + if (metadata.use_transform_3d) { + voxel_tfm = tfm * transform_inverse(metadata.transform_3d); + } + voxel_step_size = min3(fabs(transform_direction(&voxel_tfm, size))); + } + else if (volume->object_space) { + /* User specified step size in object space. */ + float3 size = make_float3(voxel_step_size, voxel_step_size, voxel_step_size); + voxel_step_size = min3(fabs(transform_direction(&tfm, size))); } - voxel_step_size = min3(fabs(transform_direction(&voxel_tfm, size))); - } - else if (mesh->volume_object_space) { - /* User specified step size in object space. */ - float3 size = make_float3(voxel_step_size, voxel_step_size, voxel_step_size); - voxel_step_size = min3(fabs(transform_direction(&tfm, size))); - } - if (voxel_step_size > 0.0f) { - step_size = fminf(voxel_step_size, step_size); + if (voxel_step_size > 0.0f) { + step_size = fminf(voxel_step_size, step_size); + } } } } @@ -365,14 +370,14 @@ static float object_surface_area(UpdateObjectTransformState *state, const Transform &tfm, Geometry *geom) { - if (geom->type != Geometry::MESH) { + if (geom->type != Geometry::MESH && geom->type != Geometry::VOLUME) { return 0.0f; } Mesh *mesh = static_cast(geom); - if (mesh->has_volume) { + if (mesh->has_volume || geom->type == Geometry::VOLUME) { /* Volume density automatically adjust to object scale. */ - if (mesh->volume_object_space) { + if (geom->type == Geometry::VOLUME && static_cast(geom)->object_space) { const float3 unit = normalize(make_float3(1.0f, 1.0f, 1.0f)); return 1.0f / len(transform_direction(&tfm, unit)); } @@ -527,7 +532,9 @@ void ObjectManager::device_update_object_transform(UpdateObjectTransformState *s kobject.dupli_uv[1] = ob->dupli_uv[1]; int totalsteps = geom->motion_steps; kobject.numsteps = (totalsteps - 1) / 2; - kobject.numverts = (geom->type == Geometry::MESH) ? static_cast(geom)->verts.size() : 0; + kobject.numverts = (geom->type == Geometry::MESH || geom->type == Geometry::VOLUME) ? + static_cast(geom)->verts.size() : + 0; kobject.patch_map_offset = 0; kobject.attribute_map_offset = 0; uint32_t hash_name = util_murmur_hash3(ob->name.c_str(), ob->name.length(), 0); @@ -819,7 +826,7 @@ void ObjectManager::apply_static_transforms(DeviceScene *dscene, Scene *scene, P bool apply = (geometry_users[geom] == 1) && !geom->has_surface_bssrdf && !geom->has_true_displacement(); - if (geom->type == Geometry::MESH) { + if (geom->type == Geometry::MESH || geom->type == Geometry::VOLUME) { Mesh *mesh = static_cast(geom); apply = apply && mesh->subdivision_type == Mesh::SUBDIVISION_NONE; } diff --git a/intern/cycles/render/volume.cpp b/intern/cycles/render/volume.cpp new file mode 100644 index 00000000000..89777d8669b --- /dev/null +++ b/intern/cycles/render/volume.cpp @@ -0,0 +1,611 @@ +/* + * Copyright 2020 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "render/volume.h" +#include "render/attribute.h" +#include "render/image_vdb.h" +#include "render/scene.h" + +#ifdef WITH_OPENVDB +# include +# include +# include +#endif + +#include "util/util_foreach.h" +#include "util/util_hash.h" +#include "util/util_logging.h" +#include "util/util_openvdb.h" +#include "util/util_progress.h" +#include "util/util_types.h" + +CCL_NAMESPACE_BEGIN + +NODE_DEFINE(Volume) +{ + NodeType *type = NodeType::add("volume", create, NodeType::NONE, Geometry::node_base_type); + + SOCKET_INT_ARRAY(triangles, "Triangles", array()); + SOCKET_POINT_ARRAY(verts, "Vertices", array()); + SOCKET_INT_ARRAY(shader, "Shader", array()); + + SOCKET_FLOAT(clipping, "Clipping", 0.001f); + SOCKET_FLOAT(step_size, "Step Size", 0.0f); + SOCKET_BOOLEAN(object_space, "Object Space", false); + + return type; +} + +Volume::Volume() : Mesh(node_type, Geometry::VOLUME) +{ + clipping = 0.001f; + step_size = 0.0f; + object_space = false; +} + +void Volume::clear() +{ + Mesh::clear(true); +} + +struct QuadData { + int v0, v1, v2, v3; + + float3 normal; +}; + +enum { + QUAD_X_MIN = 0, + QUAD_X_MAX = 1, + QUAD_Y_MIN = 2, + QUAD_Y_MAX = 3, + QUAD_Z_MIN = 4, + QUAD_Z_MAX = 5, +}; + +const int quads_indices[6][4] = { + /* QUAD_X_MIN */ + {4, 0, 3, 7}, + /* QUAD_X_MAX */ + {1, 5, 6, 2}, + /* QUAD_Y_MIN */ + {4, 5, 1, 0}, + /* QUAD_Y_MAX */ + {3, 2, 6, 7}, + /* QUAD_Z_MIN */ + {0, 1, 2, 3}, + /* QUAD_Z_MAX */ + {5, 4, 7, 6}, +}; + +const float3 quads_normals[6] = { + /* QUAD_X_MIN */ + make_float3(-1.0f, 0.0f, 0.0f), + /* QUAD_X_MAX */ + make_float3(1.0f, 0.0f, 0.0f), + /* QUAD_Y_MIN */ + make_float3(0.0f, -1.0f, 0.0f), + /* QUAD_Y_MAX */ + make_float3(0.0f, 1.0f, 0.0f), + /* QUAD_Z_MIN */ + make_float3(0.0f, 0.0f, -1.0f), + /* QUAD_Z_MAX */ + make_float3(0.0f, 0.0f, 1.0f), +}; + +static int add_vertex(int3 v, + vector &vertices, + int3 res, + unordered_map &used_verts) +{ + size_t vert_key = v.x + v.y * (res.x + 1) + v.z * (res.x + 1) * (res.y + 1); + unordered_map::iterator it = used_verts.find(vert_key); + + if (it != used_verts.end()) { + return it->second; + } + + int vertex_offset = vertices.size(); + used_verts[vert_key] = vertex_offset; + vertices.push_back(v); + return vertex_offset; +} + +static void create_quad(int3 corners[8], + vector &vertices, + vector &quads, + int3 res, + unordered_map &used_verts, + int face_index) +{ + QuadData quad; + quad.v0 = add_vertex(corners[quads_indices[face_index][0]], 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.v3 = add_vertex(corners[quads_indices[face_index][3]], vertices, res, used_verts); + quad.normal = quads_normals[face_index]; + + quads.push_back(quad); +} + +/* Create a mesh from a volume. + * + * The way the algorithm works is as follows: + * + * - The topologies of input OpenVDB grids are merged into a temporary grid. + * - Voxels of the temporary grid are dilated to account for the padding necessary for volume + * sampling. + * - Quads are created on the boundary between active and inactive leaf nodes of the temporary + * grid. + */ +class VolumeMeshBuilder { + public: +#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, bool do_clipping, float volume_clipping); +#endif + + void add_padding(int pad_size); + + void create_mesh(vector &vertices, + vector &indices, + vector &face_normals, + const float face_overlap_avoidance); + + void generate_vertices_and_quads(vector &vertices_is, vector &quads); + + void convert_object_space(const vector &vertices, + vector &out_vertices, + const float face_overlap_avoidance); + + void convert_quads_to_tris(const vector &quads, + vector &tris, + vector &face_normals); + + bool empty_grid() const; + +#ifdef WITH_OPENVDB + template + void merge_grid(openvdb::GridBase::ConstPtr grid, bool do_clipping, float volume_clipping) + { + typename GridType::ConstPtr typed_grid = openvdb::gridConstPtrCast(grid); + + if (do_clipping) { + using ValueType = typename GridType::ValueType; + typename GridType::Ptr copy = typed_grid->deepCopy(); + typename GridType::ValueOnIter iter = copy->beginValueOn(); + + for (; iter; ++iter) { + if (iter.getValue() < ValueType(volume_clipping)) { + iter.setValueOff(); + } + } + + typed_grid = copy; + } + + topology_grid->topologyUnion(*typed_grid); + } +#endif +}; + +VolumeMeshBuilder::VolumeMeshBuilder() +{ + first_grid = true; +} + +#ifdef WITH_OPENVDB +void VolumeMeshBuilder::add_grid(openvdb::GridBase::ConstPtr grid, + bool do_clipping, + float volume_clipping) +{ + /* set the transform of our grid from the first one */ + if (first_grid) { + topology_grid = openvdb::MaskGrid::create(); + topology_grid->setTransform(grid->transform().copy()); + first_grid = false; + } + /* if the transforms do not match, we need to resample one of the grids so that + * its index space registers with that of the other, here we resample our mask + * grid so memory usage is kept low */ + else if (topology_grid->transform() != grid->transform()) { + openvdb::MaskGrid::Ptr temp_grid = topology_grid->copyWithNewTree(); + temp_grid->setTransform(grid->transform().copy()); + openvdb::tools::resampleToMatch(*topology_grid, *temp_grid); + topology_grid = temp_grid; + topology_grid->setTransform(grid->transform().copy()); + } + + if (grid->isType()) { + merge_grid(grid, do_clipping, volume_clipping); + } + else if (grid->isType()) { + merge_grid(grid, do_clipping, volume_clipping); + } + else if (grid->isType()) { + merge_grid(grid, do_clipping, volume_clipping); + } + else if (grid->isType()) { + merge_grid(grid, do_clipping, volume_clipping); + } + else if (grid->isType()) { + merge_grid(grid, do_clipping, volume_clipping); + } + else if (grid->isType()) { + merge_grid(grid, do_clipping, volume_clipping); + } + else if (grid->isType()) { + merge_grid(grid, do_clipping, volume_clipping); + } + else if (grid->isType()) { + merge_grid(grid, do_clipping, volume_clipping); + } + else if (grid->isType()) { + merge_grid(grid, do_clipping, volume_clipping); + } + else if (grid->isType()) { + topology_grid->topologyUnion(*openvdb::gridConstPtrCast(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 &vertices, + vector &indices, + vector &face_normals, + const float face_overlap_avoidance) +{ + /* We create vertices in index space (is), and only convert them to object + * space when done. */ + vector vertices_is; + vector quads; + + generate_vertices_and_quads(vertices_is, quads); + + convert_object_space(vertices_is, vertices, face_overlap_avoidance); + + convert_quads_to_tris(quads, indices, face_normals); +} + +void VolumeMeshBuilder::generate_vertices_and_quads(vector &vertices_is, + vector &quads) +{ +#ifdef WITH_OPENVDB + const openvdb::MaskGrid::TreeType &tree = topology_grid->tree(); + tree.evalLeafBoundingBox(bbox); + + const int3 resolution = make_int3(bbox.dim().x(), bbox.dim().y(), bbox.dim().z()); + + unordered_map used_verts; + + for (auto iter = tree.cbeginLeaf(); iter; ++iter) { + openvdb::CoordBBox leaf_bbox = iter->getNodeBoundingBox(); + /* +1 to convert from exclusive to include bounds. */ + leaf_bbox.max() = leaf_bbox.max().offsetBy(1); + + int3 min = make_int3(leaf_bbox.min().x(), leaf_bbox.min().y(), leaf_bbox.min().z()); + int3 max = make_int3(leaf_bbox.max().x(), leaf_bbox.max().y(), leaf_bbox.max().z()); + + int3 corners[8] = { + make_int3(min[0], min[1], min[2]), + make_int3(max[0], min[1], min[2]), + make_int3(max[0], max[1], min[2]), + make_int3(min[0], max[1], min[2]), + make_int3(min[0], min[1], max[2]), + make_int3(max[0], min[1], max[2]), + make_int3(max[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 an inactive leaf. + * + * 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); + + if (!tree.probeLeaf(openvdb::Coord(center.x() - LEAF_DIM, center.y(), center.z()))) { + create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_X_MIN); + } + + if (!tree.probeLeaf(openvdb::Coord(center.x() + LEAF_DIM, center.y(), center.z()))) { + create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_X_MAX); + } + + if (!tree.probeLeaf(openvdb::Coord(center.x(), center.y() - LEAF_DIM, center.z()))) { + create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Y_MIN); + } + + if (!tree.probeLeaf(openvdb::Coord(center.x(), center.y() + LEAF_DIM, center.z()))) { + create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Y_MAX); + } + + if (!tree.probeLeaf(openvdb::Coord(center.x(), center.y(), center.z() - LEAF_DIM))) { + create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Z_MIN); + } + + if (!tree.probeLeaf(openvdb::Coord(center.x(), center.y(), center.z() + LEAF_DIM))) { + create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Z_MAX); + } + } +#else + (void)vertices_is; + (void)quads; +#endif +} + +void VolumeMeshBuilder::convert_object_space(const vector &vertices, + vector &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()); + + for (size_t i = 0; i < vertices.size(); ++i) { + openvdb::math::Vec3d p = topology_grid->indexToWorld( + openvdb::math::Vec3d(vertices[i].x, vertices[i].y, vertices[i].z)); + float3 vertex = make_float3((float)p.x(), (float)p.y(), (float)p.z()); + out_vertices.push_back(vertex + point_offset); + } +#else + (void)vertices; + (void)out_vertices; + (void)face_overlap_avoidance; +#endif +} + +void VolumeMeshBuilder::convert_quads_to_tris(const vector &quads, + vector &tris, + vector &face_normals) +{ + int index_offset = 0; + tris.resize(quads.size() * 6); + face_normals.reserve(quads.size() * 2); + + for (size_t i = 0; i < quads.size(); ++i) { + tris[index_offset++] = quads[i].v0; + tris[index_offset++] = quads[i].v2; + tris[index_offset++] = quads[i].v1; + + face_normals.push_back(quads[i].normal); + + tris[index_offset++] = quads[i].v0; + tris[index_offset++] = quads[i].v3; + tris[index_offset++] = quads[i].v2; + + face_normals.push_back(quads[i].normal); + } +} + +bool VolumeMeshBuilder::empty_grid() const +{ +#ifdef WITH_OPENVDB + return !topology_grid || topology_grid->tree().leafCount() == 0; +#else + return true; +#endif +} + +#ifdef WITH_OPENVDB +template +static openvdb::GridBase::ConstPtr openvdb_grid_from_device_texture(device_texture *image_memory, + float volume_clipping, + Transform transform_3d) +{ + using ValueType = typename GridType::ValueType; + + openvdb::CoordBBox dense_bbox(0, + 0, + 0, + image_memory->data_width - 1, + image_memory->data_height - 1, + image_memory->data_depth - 1); + openvdb::tools::Dense dense( + dense_bbox, static_cast(image_memory->host_pointer)); + + typename GridType::Ptr sparse = GridType::create(ValueType(0.0f)); + openvdb::tools::copyFromDense(dense, *sparse, ValueType(volume_clipping)); + + /* #copyFromDense will remove any leaf node that contains constant data and replace it with a + * tile, however, we need to preserve the leaves in order to generate the mesh, so re-voxelize + * the leaves that were pruned. This should not affect areas that were skipped due to the + * volume_clipping parameter. */ + sparse->tree().voxelizeActiveTiles(); + + /* Compute index to world matrix. */ + float3 voxel_size = make_float3(1.0f / image_memory->data_width, + 1.0f / image_memory->data_height, + 1.0f / image_memory->data_depth); + + transform_3d = transform_inverse(transform_3d); + + openvdb::Mat4R index_to_world_mat((double)(voxel_size.x * transform_3d[0][0]), + 0.0, + 0.0, + 0.0, + 0.0, + (double)(voxel_size.y * transform_3d[1][1]), + 0.0, + 0.0, + 0.0, + 0.0, + (double)(voxel_size.z * transform_3d[2][2]), + 0.0, + (double)transform_3d[0][3], + (double)transform_3d[1][3], + (double)transform_3d[2][3], + 1.0); + + openvdb::math::Transform::Ptr index_to_world_tfm = + openvdb::math::Transform::createLinearTransform(index_to_world_mat); + + sparse->setTransform(index_to_world_tfm); + + return sparse; +} +#endif + +/* ************************************************************************** */ + +void GeometryManager::create_volume_mesh(Volume *volume, Progress &progress) +{ + string msg = string_printf("Computing Volume Mesh %s", volume->name.c_str()); + progress.set_status("Updating Mesh", msg); + + VolumeMeshBuilder builder; + +#ifdef WITH_OPENVDB + foreach (Attribute &attr, volume->attributes.attributes) { + if (attr.element != ATTR_ELEMENT_VOXEL) { + continue; + } + + bool do_clipping = false; + + ImageHandle &handle = attr.data_voxel(); + + /* Try building from OpenVDB grid directly. */ + VDBImageLoader *vdb_loader = handle.vdb_loader(); + openvdb::GridBase::ConstPtr grid; + if (vdb_loader) { + grid = vdb_loader->get_grid(); + + /* If building from an OpenVDB grid, we need to manually clip the values. */ + do_clipping = true; + } + + /* Else fall back to creating an OpenVDB grid from the dense volume data. */ + if (!grid) { + device_texture *image_memory = handle.image_memory(); + + if (image_memory->data_elements == 1) { + grid = openvdb_grid_from_device_texture( + image_memory, volume->clipping, handle.metadata().transform_3d); + } + else if (image_memory->data_elements == 3) { + grid = openvdb_grid_from_device_texture( + image_memory, volume->clipping, handle.metadata().transform_3d); + } + else if (image_memory->data_elements == 4) { + grid = openvdb_grid_from_device_texture( + image_memory, volume->clipping, handle.metadata().transform_3d); + } + } + + if (grid) { + builder.add_grid(grid, do_clipping, volume->clipping); + } + } +#endif + + if (builder.empty_grid()) { + return; + } + + /* Compute padding. */ + Shader *volume_shader = NULL; + int pad_size = 0; + + foreach (Shader *shader, volume->used_shaders) { + if (!shader->has_volume) { + continue; + } + + volume_shader = shader; + + if (shader->volume_interpolation_method == VOLUME_INTERPOLATION_LINEAR) { + pad_size = max(1, pad_size); + } + else if (shader->volume_interpolation_method == VOLUME_INTERPOLATION_CUBIC) { + pad_size = max(2, pad_size); + } + + break; + } + + if (!volume_shader) { + return; + } + + builder.add_padding(pad_size); + + /* Slightly offset vertex coordinates to avoid overlapping faces with other + * volumes or meshes. The proper solution would be to improve intersection in + * the kernel to support robust handling of multiple overlapping faces or use + * an all-hit intersection similar to shadows. */ + const float face_overlap_avoidance = 0.1f * + hash_uint_to_float(hash_string(volume->name.c_str())); + + /* Create mesh. */ + vector vertices; + vector indices; + vector face_normals; + builder.create_mesh(vertices, indices, face_normals, face_overlap_avoidance); + + volume->clear(); + volume->reserve_mesh(vertices.size(), indices.size() / 3); + volume->used_shaders.push_back(volume_shader); + volume->need_update_rebuild = true; + + for (size_t i = 0; i < vertices.size(); ++i) { + volume->add_vertex(vertices[i]); + } + + for (size_t i = 0; i < indices.size(); i += 3) { + volume->add_triangle(indices[i], indices[i + 1], indices[i + 2], 0, false); + } + + Attribute *attr_fN = volume->attributes.add(ATTR_STD_FACE_NORMAL); + float3 *fN = attr_fN->data_float3(); + + for (size_t i = 0; i < face_normals.size(); ++i) { + fN[i] = face_normals[i]; + } + + /* Print stats. */ + VLOG(1) << "Memory usage volume mesh: " + << ((vertices.size() + face_normals.size()) * sizeof(float3) + + indices.size() * sizeof(int)) / + (1024.0 * 1024.0) + << "Mb."; +} + +CCL_NAMESPACE_END diff --git a/intern/cycles/render/volume.h b/intern/cycles/render/volume.h new file mode 100644 index 00000000000..05157eb948f --- /dev/null +++ b/intern/cycles/render/volume.h @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "graph/node.h" + +#include "render/mesh.h" + +CCL_NAMESPACE_BEGIN + +class Volume : public Mesh { + public: + NODE_DECLARE + + Volume(); + + float clipping; + float step_size; + bool object_space; + + virtual void clear() override; +}; + +CCL_NAMESPACE_END -- cgit v1.2.3