Cycles: add support for volume motion blur

This adds support for rendering motion blur for volumes, using their
velocity field. This works for fluid simulations and imported VDB
volumes. For the latter, the name of the velocity field can be set per
volume object, with automatic detection of velocity fields that are
split into 3 scalar grids.

A new parameter is also added to scale velocity for more artistic control.

Like for Alembic and USD caches, a parameter to set the unit of time in
which the velocity vectors are expressed is also added. For Blender gas
simulations, the velocity unit should always be in seconds, so this is
only exposed for volume objects which may come from external OpenVDB
files.

These parameters are available under the `Render` panels for the fluid
domain and the volume object data properties respectively.

Credits: kernel advection code from Tangent Animation's Blackbird based
on earlier work by Geraldine Chua

Differential Revision: https://developer.blender.org/D14629
This commit is contained in:
Kévin Dietrich 2022-04-19 16:28:14 +02:00
parent 56cfd60d43
commit 2890c11cd7
32 changed files with 511 additions and 48 deletions

View File

@ -1034,7 +1034,7 @@ class CYCLES_OBJECT_PT_motion_blur(CyclesButtonsPanel, Panel):
def poll(cls, context):
ob = context.object
if CyclesButtonsPanel.poll(context) and ob:
if ob.type in {'MESH', 'CURVE', 'CURVE', 'SURFACE', 'FONT', 'META', 'CAMERA', 'CURVES', 'POINTCLOUD'}:
if ob.type in {'MESH', 'CURVE', 'CURVE', 'SURFACE', 'FONT', 'META', 'CAMERA', 'CURVES', 'POINTCLOUD', 'VOLUME'}:
return True
if ob.instance_type == 'COLLECTION' and ob.instance_collection:
return True

View File

@ -23,7 +23,7 @@ struct BlenderCamera {
float lens;
float shuttertime;
Camera::MotionPosition motion_position;
MotionPosition motion_position;
array<float> shutter_curve;
Camera::RollingShutterType rolling_shutter_type;
@ -114,7 +114,7 @@ static void blender_camera_init(BlenderCamera *bcam, BL::RenderSettings &b_rende
bcam->sensor_width = 36.0f;
bcam->sensor_height = 24.0f;
bcam->sensor_fit = BlenderCamera::AUTO;
bcam->motion_position = Camera::MOTION_POSITION_CENTER;
bcam->motion_position = MOTION_POSITION_CENTER;
bcam->border.right = 1.0f;
bcam->border.top = 1.0f;
bcam->viewport_camera_border.right = 1.0f;
@ -555,10 +555,8 @@ void BlenderSync::sync_camera(BL::RenderSettings &b_render,
curvemapping_to_array(b_shutter_curve, bcam.shutter_curve, RAMP_TABLE_SIZE);
PointerRNA cscene = RNA_pointer_get(&b_scene.ptr, "cycles");
bcam.motion_position = (Camera::MotionPosition)get_enum(cscene,
"motion_blur_position",
Camera::MOTION_NUM_POSITIONS,
Camera::MOTION_POSITION_CENTER);
bcam.motion_position = (MotionPosition)get_enum(
cscene, "motion_blur_position", MOTION_NUM_POSITIONS, MOTION_POSITION_CENTER);
bcam.rolling_shutter_type = (Camera::RollingShutterType)get_enum(
cscene,
"rolling_shutter_type",

View File

@ -16,6 +16,7 @@
#include "scene/shader.h"
#include "scene/shader_graph.h"
#include "scene/shader_nodes.h"
#include "scene/volume.h"
#include "util/foreach.h"
#include "util/hash.h"
@ -715,13 +716,13 @@ void BlenderSync::sync_motion(BL::RenderSettings &b_render,
float frame_center_delta = 0.0f;
if (scene->need_motion() != Scene::MOTION_PASS &&
scene->camera->get_motion_position() != Camera::MOTION_POSITION_CENTER) {
scene->camera->get_motion_position() != MOTION_POSITION_CENTER) {
float shuttertime = scene->camera->get_shuttertime();
if (scene->camera->get_motion_position() == Camera::MOTION_POSITION_END) {
if (scene->camera->get_motion_position() == MOTION_POSITION_END) {
frame_center_delta = -shuttertime * 0.5f;
}
else {
assert(scene->camera->get_motion_position() == Camera::MOTION_POSITION_START);
assert(scene->camera->get_motion_position() == MOTION_POSITION_START);
frame_center_delta = shuttertime * 0.5f;
}

View File

@ -272,7 +272,7 @@ void BlenderSync::sync_data(BL::RenderSettings &b_render,
geometry_synced.clear(); /* use for objects and motion sync */
if (scene->need_motion() == Scene::MOTION_PASS || scene->need_motion() == Scene::MOTION_NONE ||
scene->camera->get_motion_position() == Camera::MOTION_POSITION_CENTER) {
scene->camera->get_motion_position() == MOTION_POSITION_CENTER) {
sync_objects(b_depsgraph, b_v3d);
}
sync_motion(b_render, b_depsgraph, b_v3d, b_override, width, height, python_thread_state);

View File

@ -168,7 +168,8 @@ class BlenderSmokeLoader : public ImageLoader {
AttributeStandard attribute;
};
static void sync_smoke_volume(Scene *scene, BObjectInfo &b_ob_info, Volume *volume, float frame)
static void sync_smoke_volume(
BL::Scene &b_scene, Scene *scene, BObjectInfo &b_ob_info, Volume *volume, float frame)
{
if (!b_ob_info.is_real_object_data()) {
return;
@ -178,6 +179,18 @@ static void sync_smoke_volume(Scene *scene, BObjectInfo &b_ob_info, Volume *volu
return;
}
float velocity_scale = b_domain.velocity_scale();
/* Motion blur attribute is relative to seconds, we need it relative to frames. */
const bool need_motion = object_need_motion_attribute(b_ob_info, scene);
const float motion_scale = (need_motion) ?
scene->motion_shutter_time() /
(b_scene.render().fps() / b_scene.render().fps_base()) :
0.0f;
velocity_scale *= motion_scale;
volume->set_velocity_scale(velocity_scale);
AttributeStandard attributes[] = {ATTR_STD_VOLUME_DENSITY,
ATTR_STD_VOLUME_COLOR,
ATTR_STD_VOLUME_FLAME,
@ -234,6 +247,7 @@ class BlenderVolumeLoader : public VDBImageLoader {
};
static void sync_volume_object(BL::BlendData &b_data,
BL::Scene &b_scene,
BObjectInfo &b_ob_info,
Scene *scene,
Volume *volume)
@ -247,6 +261,20 @@ static void sync_volume_object(BL::BlendData &b_data,
volume->set_step_size(b_render.step_size());
volume->set_object_space((b_render.space() == BL::VolumeRender::space_OBJECT));
float velocity_scale = b_volume.velocity_scale();
if (b_volume.velocity_unit() == BL::Volume::velocity_unit_SECOND) {
/* Motion blur attribute is relative to seconds, we need it relative to frames. */
const bool need_motion = object_need_motion_attribute(b_ob_info, scene);
const float motion_scale = (need_motion) ?
scene->motion_shutter_time() /
(b_scene.render().fps() / b_scene.render().fps_base()) :
0.0f;
velocity_scale *= motion_scale;
}
volume->set_velocity_scale(velocity_scale);
/* Find grid with matching name. */
for (BL::VolumeGrid &b_grid : b_volume.grids) {
ustring name = ustring(b_grid.name());
@ -267,9 +295,22 @@ static void sync_volume_object(BL::BlendData &b_data,
else if (name == Attribute::standard_name(ATTR_STD_VOLUME_TEMPERATURE)) {
std = ATTR_STD_VOLUME_TEMPERATURE;
}
else if (name == Attribute::standard_name(ATTR_STD_VOLUME_VELOCITY)) {
else if (name == Attribute::standard_name(ATTR_STD_VOLUME_VELOCITY) ||
name == b_volume.velocity_grid()) {
std = ATTR_STD_VOLUME_VELOCITY;
}
else if (name == Attribute::standard_name(ATTR_STD_VOLUME_VELOCITY_X) ||
name == b_volume.velocity_x_grid()) {
std = ATTR_STD_VOLUME_VELOCITY_X;
}
else if (name == Attribute::standard_name(ATTR_STD_VOLUME_VELOCITY_Y) ||
name == b_volume.velocity_y_grid()) {
std = ATTR_STD_VOLUME_VELOCITY_Y;
}
else if (name == Attribute::standard_name(ATTR_STD_VOLUME_VELOCITY_Z) ||
name == b_volume.velocity_z_grid()) {
std = ATTR_STD_VOLUME_VELOCITY_Z;
}
if ((std != ATTR_STD_NONE && volume->need_attribute(scene, std)) ||
volume->need_attribute(scene, name)) {
@ -294,11 +335,11 @@ void BlenderSync::sync_volume(BObjectInfo &b_ob_info, Volume *volume)
if (b_ob_info.object_data.is_a(&RNA_Volume)) {
/* Volume object. Create only attributes, bounding mesh will then
* be automatically generated later. */
sync_volume_object(b_data, b_ob_info, scene, volume);
sync_volume_object(b_data, b_scene, b_ob_info, scene, volume);
}
else {
/* Smoke domain. */
sync_smoke_volume(scene, b_ob_info, volume, b_scene.frame_current());
sync_smoke_volume(b_scene, scene, b_ob_info, volume, b_scene.frame_current());
}
}

View File

@ -831,6 +831,65 @@ ccl_device_inline void shader_eval_volume(KernelGlobals kg,
/* todo: this is inefficient for motion blur, we should be
* caching matrices instead of recomputing them each step */
shader_setup_object_transforms(kg, sd, sd->time);
if ((sd->object_flag & SD_OBJECT_HAS_VOLUME_MOTION) != 0) {
AttributeDescriptor v_desc = find_attribute(kg, sd, ATTR_STD_VOLUME_VELOCITY);
kernel_assert(v_desc.offset != ATTR_STD_NOT_FOUND);
const float3 P = sd->P;
const float velocity_scale = kernel_tex_fetch(__objects, sd->object).velocity_scale;
const float time_offset = kernel_data.cam.motion_position == MOTION_POSITION_CENTER ?
0.5f :
0.0f;
const float time = kernel_data.cam.motion_position == MOTION_POSITION_END ?
(1.0f - kernel_data.cam.shuttertime) + sd->time :
sd->time;
/* Use a 1st order semi-lagrangian advection scheme to estimate what volume quantity
* existed, or will exist, at the given time:
*
* `phi(x, T) = phi(x - (T - t) * u(x, T), t)`
*
* where
*
* x : position
* T : super-sampled time (or ray time)
* t : current time of the simulation (in rendering we assume this is center frame with
* relative time = 0)
* phi : the volume quantity
* u : the velocity field
*
* But first we need to determine the velocity field `u(x, T)`, which we can estimate also
* using semi-lagrangian advection.
*
* `u(x, T) = u(x - (T - t) * u(x, T), t)`
*
* This is the typical way to model self-advection in fluid dynamics, however, we do not
* account for other forces affecting the velocity during simulation (pressure, buyoancy,
* etc.): this gives a linear interpolation when fluid are mostly "curvy". For better
* results, a higher order interpolation scheme can be used (at the cost of more lookups),
* or an interpolation of the velocity fields for the previous and next frames could also
* be used to estimate `u(x, T)` (which will cost more memory and lookups).
*
* References:
* "Eulerian Motion Blur", Kim and Ko, 2007
* "Production Volume Rendering", Wreninge et al., 2012
*/
/* Find velocity. */
float3 velocity = primitive_volume_attribute_float3(kg, sd, v_desc);
object_dir_transform(kg, sd, &velocity);
/* Find advected P. */
sd->P = P - (time - time_offset) * velocity_scale * velocity;
/* Find advected velocity. */
velocity = primitive_volume_attribute_float3(kg, sd, v_desc);
object_dir_transform(kg, sd, &velocity);
/* Find advected P. */
sd->P = P - (time - time_offset) * velocity_scale * velocity;
}
# endif
}

View File

@ -489,6 +489,18 @@ enum PanoramaType {
PANORAMA_NUM_TYPES,
};
/* Specifies an offset for the shutter's time interval. */
enum MotionPosition {
/* Shutter opens at the current frame. */
MOTION_POSITION_START = 0,
/* Shutter is fully open at the current frame. */
MOTION_POSITION_CENTER = 1,
/* Shutter closes at the current frame. */
MOTION_POSITION_END = 2,
MOTION_NUM_POSITIONS,
};
/* Direct Light Sampling */
enum DirectLightSamplingType {
@ -635,6 +647,9 @@ typedef enum AttributeStandard {
ATTR_STD_VOLUME_HEAT,
ATTR_STD_VOLUME_TEMPERATURE,
ATTR_STD_VOLUME_VELOCITY,
ATTR_STD_VOLUME_VELOCITY_X,
ATTR_STD_VOLUME_VELOCITY_Y,
ATTR_STD_VOLUME_VELOCITY_Z,
ATTR_STD_POINTINESS,
ATTR_STD_RANDOM_PER_ISLAND,
ATTR_STD_SHADOW_TRANSPARENCY,
@ -808,6 +823,8 @@ enum ShaderDataObjectFlag {
SD_OBJECT_CAUSTICS_CASTER = (1 << 9),
/* object is caustics receiver */
SD_OBJECT_CAUSTICS_RECEIVER = (1 << 10),
/* object has attribute for volume motion */
SD_OBJECT_HAS_VOLUME_MOTION = (1 << 11),
/* object is using caustics */
SD_OBJECT_CAUSTICS = (SD_OBJECT_CAUSTICS_CASTER | SD_OBJECT_CAUSTICS_RECEIVER),
@ -815,7 +832,8 @@ enum ShaderDataObjectFlag {
SD_OBJECT_FLAGS = (SD_OBJECT_HOLDOUT_MASK | SD_OBJECT_MOTION | SD_OBJECT_TRANSFORM_APPLIED |
SD_OBJECT_NEGATIVE_SCALE_APPLIED | SD_OBJECT_HAS_VOLUME |
SD_OBJECT_INTERSECTS_VOLUME | SD_OBJECT_SHADOW_CATCHER |
SD_OBJECT_HAS_VOLUME_ATTRIBUTES | SD_OBJECT_CAUSTICS)
SD_OBJECT_HAS_VOLUME_ATTRIBUTES | SD_OBJECT_CAUSTICS |
SD_OBJECT_HAS_VOLUME_MOTION)
};
typedef struct ccl_align(16) ShaderData
@ -1040,7 +1058,7 @@ typedef struct KernelCamera {
int rolling_shutter_type;
float rolling_shutter_duration;
int pad;
int motion_position;
} KernelCamera;
static_assert_align(KernelCamera, 16);
@ -1386,7 +1404,8 @@ typedef struct KernelObject {
uint visibility;
int primitive_type;
int pad1;
/* Volume velocity scale. */
float velocity_scale;
} KernelObject;
static_assert_align(KernelObject, 16);

View File

@ -360,6 +360,12 @@ const char *Attribute::standard_name(AttributeStandard std)
return "temperature";
case ATTR_STD_VOLUME_VELOCITY:
return "velocity";
case ATTR_STD_VOLUME_VELOCITY_X:
return "velocity_x";
case ATTR_STD_VOLUME_VELOCITY_Y:
return "velocity_y";
case ATTR_STD_VOLUME_VELOCITY_Z:
return "velocity_z";
case ATTR_STD_POINTINESS:
return "pointiness";
case ATTR_STD_RANDOM_PER_ISLAND:
@ -587,6 +593,9 @@ Attribute *AttributeSet::add(AttributeStandard std, ustring name)
case ATTR_STD_VOLUME_FLAME:
case ATTR_STD_VOLUME_HEAT:
case ATTR_STD_VOLUME_TEMPERATURE:
case ATTR_STD_VOLUME_VELOCITY_X:
case ATTR_STD_VOLUME_VELOCITY_Y:
case ATTR_STD_VOLUME_VELOCITY_Z:
attr = add(name, TypeDesc::TypeFloat, ATTR_ELEMENT_VOXEL);
break;
case ATTR_STD_VOLUME_COLOR:

View File

@ -397,6 +397,7 @@ void Camera::update(Scene *scene)
/* motion blur */
kcam->shuttertime = (need_motion == Scene::MOTION_BLUR) ? shuttertime : -1.0f;
kcam->motion_position = motion_position;
/* type */
kcam->type = camera_type;

View File

@ -30,18 +30,6 @@ class Camera : public Node {
public:
NODE_DECLARE
/* Specifies an offset for the shutter's time interval. */
enum MotionPosition {
/* Shutter opens at the current frame. */
MOTION_POSITION_START = 0,
/* Shutter is fully open at the current frame. */
MOTION_POSITION_CENTER = 1,
/* Shutter closes at the current frame. */
MOTION_POSITION_END = 2,
MOTION_NUM_POSITIONS,
};
/* Specifies rolling shutter effect. */
enum RollingShutterType {
/* No rolling shutter effect. */

View File

@ -1541,7 +1541,7 @@ void GeometryManager::device_update_preprocess(Device *device, Scene *scene, Pro
}
Volume *volume = static_cast<Volume *>(geom);
create_volume_mesh(volume, progress);
create_volume_mesh(scene, volume, progress);
/* always reallocate when we have a volume, as we need to rebuild the BVH */
device_update_flags |= DEVICE_MESH_DATA_NEEDS_REALLOC;

View File

@ -216,7 +216,7 @@ class GeometryManager {
protected:
bool displace(Device *device, Scene *scene, Mesh *mesh, Progress &progress);
void create_volume_mesh(Volume *volume, Progress &progress);
void create_volume_mesh(const Scene *scene, Volume *volume, Progress &progress);
/* Attributes */
void update_osl_attributes(Device *device,

View File

@ -66,6 +66,11 @@ struct ToNanoOp {
# endif
#endif
VDBImageLoader::VDBImageLoader(openvdb::GridBase::ConstPtr grid_, const string &grid_name)
: grid_name(grid_name), grid(grid_)
{
}
VDBImageLoader::VDBImageLoader(const string &grid_name) : grid_name(grid_name)
{
}

View File

@ -17,6 +17,7 @@ CCL_NAMESPACE_BEGIN
class VDBImageLoader : public ImageLoader {
public:
VDBImageLoader(openvdb::GridBase::ConstPtr grid_, const string &grid_name);
VDBImageLoader(const string &grid_name);
~VDBImageLoader();

View File

@ -439,6 +439,14 @@ void ObjectManager::device_update_object_transform(UpdateObjectTransformState *s
flag |= SD_OBJECT_HAS_VERTEX_MOTION;
}
}
else if (geom->is_volume()) {
Volume *volume = static_cast<Volume *>(geom);
if (volume->attributes.find(ATTR_STD_VOLUME_VELOCITY) &&
volume->get_velocity_scale() != 0.0f) {
flag |= SD_OBJECT_HAS_VOLUME_MOTION;
kobject.velocity_scale = volume->get_velocity_scale();
}
}
if (state->need_motion == Scene::MOTION_PASS) {
/* Clear motion array if there is no actual motion. */
@ -488,7 +496,7 @@ void ObjectManager::device_update_object_transform(UpdateObjectTransformState *s
kobject.dupli_generated[2] = ob->dupli_generated[2];
kobject.numkeys = (geom->geometry_type == Geometry::HAIR) ?
static_cast<Hair *>(geom)->get_curve_keys().size() :
(geom->geometry_type == Geometry::POINTCLOUD) ?
(geom->geometry_type == Geometry::POINTCLOUD) ?
static_cast<PointCloud *>(geom)->num_points() :
0;
kobject.dupli_uv[0] = ob->dupli_uv[0];

View File

@ -381,7 +381,7 @@ void Scene::device_update(Device *device_, Progress &progress)
}
}
Scene::MotionType Scene::need_motion()
Scene::MotionType Scene::need_motion() const
{
if (integrator->get_motion_blur())
return MOTION_BLUR;
@ -407,6 +407,10 @@ bool Scene::need_global_attribute(AttributeStandard std)
return need_motion() != MOTION_NONE;
else if (std == ATTR_STD_MOTION_VERTEX_NORMAL)
return need_motion() == MOTION_BLUR;
else if (std == ATTR_STD_VOLUME_VELOCITY || std == ATTR_STD_VOLUME_VELOCITY_X ||
std == ATTR_STD_VOLUME_VELOCITY_Y || std == ATTR_STD_VOLUME_VELOCITY_Z) {
return need_motion() != MOTION_NONE;
}
return false;
}

View File

@ -257,7 +257,7 @@ class Scene : public NodeOwner {
void need_global_attributes(AttributeRequestSet &attributes);
enum MotionType { MOTION_NONE = 0, MOTION_PASS, MOTION_BLUR };
MotionType need_motion();
MotionType need_motion() const;
float motion_shutter_time();
bool need_update();

View File

@ -10,9 +10,9 @@
# include <openvdb/tools/Dense.h>
# include <openvdb/tools/GridTransformer.h>
# include <openvdb/tools/Morphology.h>
# include <openvdb/tools/Statistics.h>
#endif
#include "util/foreach.h"
#include "util/hash.h"
#include "util/log.h"
#include "util/openvdb.h"
@ -28,6 +28,7 @@ NODE_DEFINE(Volume)
SOCKET_FLOAT(clipping, "Clipping", 0.001f);
SOCKET_FLOAT(step_size, "Step Size", 0.0f);
SOCKET_BOOLEAN(object_space, "Object Space", false);
SOCKET_FLOAT(velocity_scale, "Velocity Scale", 1.0f);
return type;
}
@ -482,11 +483,141 @@ static openvdb::GridBase::ConstPtr openvdb_grid_from_device_texture(device_textu
return sparse;
}
static int estimate_required_velocity_padding(openvdb::GridBase::ConstPtr grid,
float velocity_scale)
{
/* TODO: we may need to also find outliers and clamp them to avoid adding too much padding. */
openvdb::math::Extrema extrema;
openvdb::Vec3d voxel_size;
/* External .vdb files have a vec3 type for velocity, but the Blender exporter creates a vec4. */
if (grid->isType<openvdb::Vec3fGrid>()) {
openvdb::Vec3fGrid::ConstPtr vel_grid = openvdb::gridConstPtrCast<openvdb::Vec3fGrid>(grid);
extrema = openvdb::tools::extrema(vel_grid->cbeginValueOn());
voxel_size = vel_grid->voxelSize();
}
else if (grid->isType<openvdb::Vec4fGrid>()) {
openvdb::Vec4fGrid::ConstPtr vel_grid = openvdb::gridConstPtrCast<openvdb::Vec4fGrid>(grid);
extrema = openvdb::tools::extrema(vel_grid->cbeginValueOn());
voxel_size = vel_grid->voxelSize();
}
else {
assert(0);
return 0;
}
/* We should only have uniform grids, so x = y = z, but we never know. */
const double max_voxel_size = openvdb::math::Max(voxel_size.x(), voxel_size.y(), voxel_size.z());
if (max_voxel_size == 0.0) {
return 0;
}
const double estimated_padding = extrema.max() * static_cast<double>(velocity_scale) /
max_voxel_size;
return static_cast<int>(std::ceil(estimated_padding));
}
static openvdb::FloatGrid::ConstPtr get_vdb_for_attribute(Volume *volume, AttributeStandard std)
{
Attribute *attr = volume->attributes.find(std);
if (!attr) {
return nullptr;
}
ImageHandle &handle = attr->data_voxel();
VDBImageLoader *vdb_loader = handle.vdb_loader();
if (!vdb_loader) {
return nullptr;
}
openvdb::GridBase::ConstPtr grid = vdb_loader->get_grid();
if (!grid) {
return nullptr;
}
if (!grid->isType<openvdb::FloatGrid>()) {
return nullptr;
}
return openvdb::gridConstPtrCast<openvdb::FloatGrid>(grid);
}
class MergeScalarGrids {
typedef openvdb::FloatTree ScalarTree;
openvdb::tree::ValueAccessor<const ScalarTree> m_acc_x, m_acc_y, m_acc_z;
public:
MergeScalarGrids(const ScalarTree *x_tree, const ScalarTree *y_tree, const ScalarTree *z_tree)
: m_acc_x(*x_tree), m_acc_y(*y_tree), m_acc_z(*z_tree)
{
}
MergeScalarGrids(const MergeScalarGrids &other)
: m_acc_x(other.m_acc_x), m_acc_y(other.m_acc_y), m_acc_z(other.m_acc_z)
{
}
void operator()(const openvdb::Vec3STree::ValueOnIter &it) const
{
using namespace openvdb;
const math::Coord xyz = it.getCoord();
float x = m_acc_x.getValue(xyz);
float y = m_acc_y.getValue(xyz);
float z = m_acc_z.getValue(xyz);
it.setValue(math::Vec3s(x, y, z));
}
};
static void merge_scalar_grids_for_velocity(const Scene *scene, Volume *volume)
{
if (volume->attributes.find(ATTR_STD_VOLUME_VELOCITY)) {
/* A vector grid for velocity is already available. */
return;
}
openvdb::FloatGrid::ConstPtr vel_x_grid = get_vdb_for_attribute(volume,
ATTR_STD_VOLUME_VELOCITY_X);
openvdb::FloatGrid::ConstPtr vel_y_grid = get_vdb_for_attribute(volume,
ATTR_STD_VOLUME_VELOCITY_Y);
openvdb::FloatGrid::ConstPtr vel_z_grid = get_vdb_for_attribute(volume,
ATTR_STD_VOLUME_VELOCITY_Z);
if (!(vel_x_grid && vel_y_grid && vel_z_grid)) {
return;
}
openvdb::Vec3fGrid::Ptr vecgrid = openvdb::Vec3SGrid::create(openvdb::Vec3s(0.0f));
/* Activate voxels in the vector grid based on the scalar grids to ensure thread safety during
* the merge. */
vecgrid->tree().topologyUnion(vel_x_grid->tree());
vecgrid->tree().topologyUnion(vel_y_grid->tree());
vecgrid->tree().topologyUnion(vel_z_grid->tree());
MergeScalarGrids op(&vel_x_grid->tree(), &vel_y_grid->tree(), &vel_z_grid->tree());
openvdb::tools::foreach (vecgrid->beginValueOn(), op, true, false);
/* Assume all grids have the same transformation. */
openvdb::math::Transform::Ptr transform = openvdb::ConstPtrCast<openvdb::math::Transform>(
vel_x_grid->transformPtr());
vecgrid->setTransform(transform);
/* Make an attribute for it. */
Attribute *attr = volume->attributes.add(ATTR_STD_VOLUME_VELOCITY);
ImageLoader *loader = new VDBImageLoader(vecgrid, "merged_velocity");
ImageParams params;
attr->data_voxel() = scene->image_manager->add_image(loader, params);
}
#endif
/* ************************************************************************** */
void GeometryManager::create_volume_mesh(Volume *volume, Progress &progress)
void GeometryManager::create_volume_mesh(const Scene *scene, Volume *volume, Progress &progress)
{
string msg = string_printf("Computing Volume Mesh %s", volume->name.c_str());
progress.set_status("Updating Mesh", msg);
@ -495,7 +626,7 @@ void GeometryManager::create_volume_mesh(Volume *volume, Progress &progress)
Shader *volume_shader = NULL;
int pad_size = 0;
foreach (Node *node, volume->get_used_shaders()) {
for (Node *node : volume->get_used_shaders()) {
Shader *shader = static_cast<Shader *>(node);
if (!shader->has_volume) {
@ -529,7 +660,9 @@ void GeometryManager::create_volume_mesh(Volume *volume, Progress &progress)
VolumeMeshBuilder builder;
#ifdef WITH_OPENVDB
foreach (Attribute &attr, volume->attributes.attributes) {
merge_scalar_grids_for_velocity(scene, volume);
for (Attribute &attr : volume->attributes.attributes) {
if (attr.element != ATTR_ELEMENT_VOXEL) {
continue;
}
@ -567,9 +700,17 @@ void GeometryManager::create_volume_mesh(Volume *volume, Progress &progress)
}
if (grid) {
/* Add padding based on the maximum velocity vector. */
if (attr.std == ATTR_STD_VOLUME_VELOCITY && scene->need_motion() != Scene::MOTION_NONE) {
pad_size = max(pad_size,
estimate_required_velocity_padding(grid, volume->get_velocity_scale()));
}
builder.add_grid(grid, do_clipping, volume->get_clipping());
}
}
#else
(void)scene;
#endif
/* If nothing to build, early out. */

View File

@ -18,6 +18,7 @@ class Volume : public Mesh {
NODE_SOCKET_API(float, clipping)
NODE_SOCKET_API(float, step_size)
NODE_SOCKET_API(bool, object_space)
NODE_SOCKET_API(float, velocity_scale)
virtual void clear(bool preserve_shaders = false) override;
};

View File

@ -115,6 +115,12 @@ class DATA_PT_volume_render(DataButtonsPanel, Panel):
col = layout.column(align=True)
col.prop(render, "clipping")
col = layout.column(align=False)
col.prop(volume, "velocity_grid")
col.prop(volume, "velocity_unit")
col.prop(volume, "velocity_scale")
class DATA_PT_volume_viewport_display(DataButtonsPanel, Panel):
bl_label = "Viewport Display"

View File

@ -1485,6 +1485,27 @@ class PHYSICS_PT_viewport_display_advanced(PhysicButtonsPanel, Panel):
note.label(icon='INFO', text="Range highlighting for flags is not available!")
class PHYSICS_PT_fluid_domain_render(PhysicButtonsPanel, Panel):
bl_label = "Render"
bl_parent_id = 'PHYSICS_PT_fluid'
bl_options = {'DEFAULT_CLOSED'}
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
@classmethod
def poll(cls, context):
if not PhysicButtonsPanel.poll_gas_domain(context):
return False
return (context.engine in cls.COMPAT_ENGINES)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
domain = context.fluid.domain_settings
layout.prop(domain, "velocity_scale")
classes = (
FLUID_PT_presets,
PHYSICS_PT_fluid,
@ -1513,6 +1534,7 @@ classes = (
PHYSICS_PT_viewport_display_color,
PHYSICS_PT_viewport_display_debug,
PHYSICS_PT_viewport_display_advanced,
PHYSICS_PT_fluid_domain_render,
)

View File

@ -77,6 +77,11 @@ const VolumeGrid *BKE_volume_grid_active_get_for_read(const struct Volume *volum
/* Tries to find a grid with the given name. Make sure that the volume has been loaded. */
const VolumeGrid *BKE_volume_grid_find_for_read(const struct Volume *volume, const char *name);
/* Tries to set the name of the velocity field. If no such grid exists with the given base name,
* this will try common postfixes in order to detect velocity fields split into multiple grids.
* Return false if neither finding with the base name nor with the postfixes succeeded. */
bool BKE_volume_set_velocity_grid_by_name(struct Volume *volume, const char *base_name);
/* Grid
*
* By default only grid metadata is loaded, for access to the tree and voxels

View File

@ -5071,6 +5071,9 @@ void BKE_fluid_modifier_copy(const struct FluidModifierData *fmd,
tfds->openvdb_compression = fds->openvdb_compression;
tfds->clipping = fds->clipping;
tfds->openvdb_data_depth = fds->openvdb_data_depth;
/* Render options. */
tfds->velocity_scale = fds->velocity_scale;
}
else if (tfmd->flow) {
FluidFlowSettings *tffs = tfmd->flow;

View File

@ -60,6 +60,7 @@ using blender::float3;
using blender::float4x4;
using blender::IndexRange;
using blender::StringRef;
using blender::StringRefNull;
#ifdef WITH_OPENVDB
# include <atomic>
@ -517,6 +518,8 @@ static void volume_init_data(ID *id)
MEMCPY_STRUCT_AFTER(volume, DNA_struct_default_get(Volume), id);
BKE_volume_init_grids(volume);
BLI_strncpy(volume->velocity_grid, "velocity", sizeof(volume->velocity_grid));
}
static void volume_copy_data(Main *UNUSED(bmain),
@ -794,6 +797,57 @@ bool BKE_volume_is_loaded(const Volume *volume)
#endif
}
bool BKE_volume_set_velocity_grid_by_name(Volume *volume, const char *base_name)
{
const StringRefNull ref_base_name = base_name;
if (BKE_volume_grid_find_for_read(volume, base_name)) {
BLI_strncpy(volume->velocity_grid, base_name, sizeof(volume->velocity_grid));
volume->runtime.velocity_x_grid[0] = '\0';
volume->runtime.velocity_y_grid[0] = '\0';
volume->runtime.velocity_z_grid[0] = '\0';
return true;
}
/* It could be that the velocity grid is split in multiple grids, try with known postfixes. */
const StringRefNull postfixes[][3] = {{"x", "y", "z"}, {".x", ".y", ".z"}, {"_x", "_y", "_z"}};
for (const StringRefNull *postfix : postfixes) {
bool found = true;
for (int i = 0; i < 3; i++) {
std::string post_fixed_name = ref_base_name + postfix[i];
if (!BKE_volume_grid_find_for_read(volume, post_fixed_name.c_str())) {
found = false;
break;
}
}
if (!found) {
continue;
}
/* Save the base name as well. */
BLI_strncpy(volume->velocity_grid, base_name, sizeof(volume->velocity_grid));
BLI_strncpy(volume->runtime.velocity_x_grid,
(ref_base_name + postfix[0]).c_str(),
sizeof(volume->runtime.velocity_x_grid));
BLI_strncpy(volume->runtime.velocity_y_grid,
(ref_base_name + postfix[1]).c_str(),
sizeof(volume->runtime.velocity_y_grid));
BLI_strncpy(volume->runtime.velocity_z_grid,
(ref_base_name + postfix[2]).c_str(),
sizeof(volume->runtime.velocity_z_grid));
return true;
}
/* Reset to avoid potential issues. */
volume->velocity_grid[0] = '\0';
volume->runtime.velocity_x_grid[0] = '\0';
volume->runtime.velocity_y_grid[0] = '\0';
volume->runtime.velocity_z_grid[0] = '\0';
return false;
}
bool BKE_volume_load(const Volume *volume, const Main *bmain)
{
#ifdef WITH_OPENVDB
@ -857,6 +911,14 @@ bool BKE_volume_load(const Volume *volume, const Main *bmain)
}
}
/* Try to detect the velocity grid. */
const char *common_velocity_names[] = {"velocity", "vel", "v"};
for (const char *common_velocity_name : common_velocity_names) {
if (BKE_volume_set_velocity_grid_by_name(const_cast<Volume *>(volume), common_velocity_name)) {
break;
}
}
BLI_strncpy(grids.filepath, filepath, FILE_MAX);
return grids.error_msg.empty();

View File

@ -187,6 +187,7 @@
.cache_comp = SM_CACHE_LIGHT, \
.cache_high_comp = SM_CACHE_LIGHT, \
.cache_file_format = 0, \
.velocity_scale = 1.0f, \
}
/** \} */

View File

@ -670,7 +670,10 @@ typedef struct FluidDomainSettings {
char interp_method;
char gridlines_color_field; /* Simulation field used to color map onto gridlines. */
char gridlines_cell_filter;
char _pad10[7]; /* Unused. */
char _pad10[3]; /* Unused. */
/* Velocity factor for motion blur rendering. */
float velocity_scale;
/* OpenVDB cache options. */
int openvdb_compression;

View File

@ -36,7 +36,8 @@
.frame_duration = 0, \
.display = _DNA_DEFAULT_VolumeDisplay, \
.render = _DNA_DEFAULT_VolumeRender, \
}
.velocity_scale = 1.0f, \
}
/** \} */

View File

@ -24,6 +24,11 @@ typedef struct Volume_Runtime {
/** Default simplify level for volume grids loaded from files. */
int default_simplify_level;
/* Names for scalar grids which would need to be merged to recompose the velocity grid. */
char velocity_x_grid[64];
char velocity_y_grid[64];
char velocity_z_grid[64];
} Volume_Runtime;
typedef struct VolumeDisplay {
@ -75,6 +80,18 @@ typedef struct Volume {
VolumeRender render;
VolumeDisplay display;
/* Velocity field name. */
char velocity_grid[64];
char _pad3[3];
/* Unit of time the velocity vectors are expressed in.
* This uses the same enumeration values as #CacheFile.velocity_unit. */
char velocity_unit;
/* Factor for velocity vector for artistic control. */
float velocity_scale;
/* Draw Cache */
void *batch_cache;

View File

@ -212,6 +212,8 @@ DEF_ENUM(rna_enum_subdivision_boundary_smooth_items)
DEF_ENUM(rna_enum_transform_orientation_items)
DEF_ENUM(rna_enum_velocity_unit_items)
/* Not available to RNA pre-processing (`makesrna`).
* Defined in editors for example. */
#ifndef RNA_MAKESRNA

View File

@ -14,6 +14,12 @@
#include "rna_internal.h"
const EnumPropertyItem rna_enum_velocity_unit_items[] = {
{CACHEFILE_VELOCITY_UNIT_SECOND, "SECOND", 0, "Second", ""},
{CACHEFILE_VELOCITY_UNIT_FRAME, "FRAME", 0, "Frame", ""},
{0, NULL, 0, NULL, NULL},
};
#ifdef RNA_RUNTIME
# include "BLI_math.h"
@ -350,15 +356,9 @@ static void rna_def_cachefile(BlenderRNA *brna)
RNA_def_property_update(prop, 0, "rna_CacheFile_update");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
static const EnumPropertyItem velocity_unit_items[] = {
{CACHEFILE_VELOCITY_UNIT_SECOND, "SECOND", 0, "Second", ""},
{CACHEFILE_VELOCITY_UNIT_FRAME, "FRAME", 0, "Frame", ""},
{0, NULL, 0, NULL, NULL},
};
prop = RNA_def_property(srna, "velocity_unit", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "velocity_unit");
RNA_def_property_enum_items(prop, velocity_unit_items);
RNA_def_property_enum_items(prop, rna_enum_velocity_unit_items);
RNA_def_property_ui_text(
prop,
"Velocity Unit",

View File

@ -2644,6 +2644,12 @@ static void rna_def_fluid_domain_settings(BlenderRNA *brna)
RNA_def_property_enum_items(prop, gridlines_cell_filter_items);
RNA_def_property_ui_text(prop, "Cell Type", "Cell type to be highlighted");
RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, NULL);
prop = RNA_def_property(srna, "velocity_scale", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "velocity_scale");
RNA_def_property_range(prop, 0.0f, FLT_MAX);
RNA_def_property_ui_text(prop, "Velocity Scale", "Factor to control the amount of motion blur");
RNA_def_property_update(prop, NC_OBJECT | ND_MODIFIER, "rna_Fluid_update");
}
static void rna_def_fluid_flow_settings(BlenderRNA *brna)

View File

@ -78,6 +78,15 @@ static void rna_Volume_update_is_sequence(Main *bmain, Scene *scene, PointerRNA
DEG_relations_tag_update(bmain);
}
static void rna_Volume_velocity_grid_set(PointerRNA *ptr, const char *value)
{
Volume *volume = (Volume *)ptr->data;
if (!BKE_volume_set_velocity_grid_by_name(volume, value)) {
WM_reportf(RPT_ERROR, "Could not find grid with name %s", value);
}
WM_main_add_notifier(NC_GEOM | ND_DATA, volume);
}
/* Grid */
static void rna_VolumeGrid_name_get(PointerRNA *ptr, char *value)
@ -248,6 +257,7 @@ static void rna_def_volume_grid(BlenderRNA *brna)
RNA_def_property_string_funcs(
prop, "rna_VolumeGrid_name_get", "rna_VolumeGrid_name_length", NULL);
RNA_def_property_ui_text(prop, "Name", "Volume grid name");
RNA_def_struct_name_property(srna, prop);
prop = RNA_def_property(srna, "data_type", PROP_ENUM, PROP_NONE);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
@ -619,6 +629,55 @@ static void rna_def_volume(BlenderRNA *brna)
RNA_def_property_struct_type(prop, "VolumeRender");
RNA_def_property_ui_text(prop, "Render", "Volume render settings for 3D viewport");
/* Velocity. */
prop = RNA_def_property(srna, "velocity_grid", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "velocity_grid");
RNA_def_property_string_funcs(prop, NULL, NULL, "rna_Volume_velocity_grid_set");
RNA_def_property_ui_text(
prop,
"Velocity Grid",
"Name of the velocity field, or the base name if the velocity is split into multiple grids");
prop = RNA_def_property(srna, "velocity_unit", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "velocity_unit");
RNA_def_property_enum_items(prop, rna_enum_velocity_unit_items);
RNA_def_property_ui_text(
prop,
"Velocity Unit",
"Define how the velocity vectors are interpreted with regard to time, 'frame' means "
"the delta time is 1 frame, 'second' means the delta time is 1 / FPS");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
prop = RNA_def_property(srna, "velocity_scale", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "velocity_scale");
RNA_def_property_range(prop, 0.0f, FLT_MAX);
RNA_def_property_ui_text(prop, "Velocity Scale", "Factor to control the amount of motion blur");
/* Scalar grids for velocity. */
prop = RNA_def_property(srna, "velocity_x_grid", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "runtime.velocity_x_grid");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(prop,
"Velocity X Grid",
"Name of the grid for the X axis component of the velocity field if it "
"was split into multiple grids");
prop = RNA_def_property(srna, "velocity_y_grid", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "runtime.velocity_y_grid");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(prop,
"Velocity Y Grid",
"Name of the grid for the Y axis component of the velocity field if it "
"was split into multiple grids");
prop = RNA_def_property(srna, "velocity_z_grid", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "runtime.velocity_z_grid");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(prop,
"Velocity Z Grid",
"Name of the grid for the Z axis component of the velocity field if it "
"was split into multiple grids");
/* Common */
rna_def_animdata_common(srna);
}