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:
parent
56cfd60d43
commit
2890c11cd7
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -187,6 +187,7 @@
|
|||
.cache_comp = SM_CACHE_LIGHT, \
|
||||
.cache_high_comp = SM_CACHE_LIGHT, \
|
||||
.cache_file_format = 0, \
|
||||
.velocity_scale = 1.0f, \
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -36,7 +36,8 @@
|
|||
.frame_duration = 0, \
|
||||
.display = _DNA_DEFAULT_VolumeDisplay, \
|
||||
.render = _DNA_DEFAULT_VolumeRender, \
|
||||
}
|
||||
.velocity_scale = 1.0f, \
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue