EEVEE-Next: Fix Hair and Curves motion vectors
Hair and Curves need to call `DRW_curves_update` before `geometry_steps_fill`, otherwise the copied geometry is just uninitialized data. However, doing so triggers an assertion: > DRW_render_instance_buffer_finish had not been called before drawing. This PR ports the `DRW_hair/curves_pos_buffer_get` functions to the new `draw::Manager` API, so it can be called at any arbitrary point. It also changes `VelocityModule::geometry_map` to use `uint64` keys instead of `ID` pointers, since the same particle system can be used on different objects and have multiple geometries. Notes: * The new functions are only used for `VelocityModule::step_object_sync` on image renders. Using them elsewhere would require modifying the old draw manager to do the init/update/free setup. * Only the compute shader version has been ported. Pull Request: https://projects.blender.org/blender/blender/pulls/112425
This commit is contained in:
parent
05a4609583
commit
f07542d8cb
|
@ -25,6 +25,8 @@
|
|||
|
||||
#include "DNA_particle_types.h"
|
||||
|
||||
#include "draw_common.hh"
|
||||
|
||||
namespace blender::eevee {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
@ -291,9 +293,20 @@ void Instance::render_sync()
|
|||
|
||||
manager->begin_sync();
|
||||
|
||||
draw::hair_init();
|
||||
draw::curves_init();
|
||||
|
||||
begin_sync();
|
||||
|
||||
DRW_render_object_iter(this, render, depsgraph, object_sync_render);
|
||||
|
||||
draw::hair_update(*manager);
|
||||
draw::curves_update(*manager);
|
||||
draw::hair_free();
|
||||
draw::curves_free();
|
||||
|
||||
velocity.geometry_steps_fill();
|
||||
|
||||
end_sync();
|
||||
|
||||
manager->end_sync();
|
||||
|
|
|
@ -27,6 +27,8 @@
|
|||
#include "eevee_shader_shared.hh"
|
||||
#include "eevee_velocity.hh"
|
||||
|
||||
#include "draw_common.hh"
|
||||
|
||||
namespace blender::eevee {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
@ -100,7 +102,17 @@ void VelocityModule::step_sync(eVelocityStep step, float time)
|
|||
step_ = step;
|
||||
object_steps_usage[step_] = 0;
|
||||
step_camera_sync();
|
||||
|
||||
draw::hair_init();
|
||||
draw::curves_init();
|
||||
|
||||
DRW_render_object_iter(&inst_, inst_.render, inst_.depsgraph, step_object_sync_render);
|
||||
|
||||
draw::hair_update(*inst_.manager);
|
||||
draw::curves_update(*inst_.manager);
|
||||
draw::hair_free();
|
||||
draw::curves_free();
|
||||
|
||||
geometry_steps_fill();
|
||||
}
|
||||
|
||||
|
@ -142,7 +154,7 @@ bool VelocityModule::step_object_sync(Object *ob,
|
|||
VelocityObjectData &vel = velocity_map.lookup_or_add_default(object_key);
|
||||
vel.obj.ofs[step_] = object_steps_usage[step_]++;
|
||||
vel.obj.resource_id = resource_handle.resource_index();
|
||||
vel.id = particle_sys ? &particle_sys->part->id : &ob->id;
|
||||
vel.id = object_key.hash_value;
|
||||
object_steps[step_]->get_or_resize(vel.obj.ofs[step_]) = float4x4_view(ob->object_to_world);
|
||||
if (step_ == STEP_CURRENT) {
|
||||
/* Replace invalid steps. Can happen if object was hidden in one of those steps. */
|
||||
|
@ -163,12 +175,22 @@ bool VelocityModule::step_object_sync(Object *ob,
|
|||
auto add_cb = [&]() {
|
||||
VelocityGeometryData data;
|
||||
if (particle_sys) {
|
||||
data.pos_buf = DRW_hair_pos_buffer_get(ob, particle_sys, modifier_data);
|
||||
if (inst_.is_viewport()) {
|
||||
data.pos_buf = DRW_hair_pos_buffer_get(ob, particle_sys, modifier_data);
|
||||
}
|
||||
else {
|
||||
data.pos_buf = draw::hair_pos_buffer_get(inst_.scene, ob, particle_sys, modifier_data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
switch (ob->type) {
|
||||
case OB_CURVES:
|
||||
data.pos_buf = DRW_curves_pos_buffer_get(ob);
|
||||
if (inst_.is_viewport()) {
|
||||
data.pos_buf = DRW_curves_pos_buffer_get(ob);
|
||||
}
|
||||
else {
|
||||
data.pos_buf = draw::curves_pos_buffer_get(inst_.scene, ob);
|
||||
}
|
||||
break;
|
||||
case OB_POINTCLOUD:
|
||||
data.pos_buf = DRW_pointcloud_position_and_radius_buffer_get(ob);
|
||||
|
@ -251,7 +273,7 @@ void VelocityModule::geometry_steps_fill()
|
|||
vel.geo.len[step_] = geom.len;
|
||||
vel.geo.ofs[step_] = geom.ofs;
|
||||
/* Avoid reuse. */
|
||||
vel.id = nullptr;
|
||||
vel.id = 0;
|
||||
}
|
||||
|
||||
geometry_map.clear();
|
||||
|
@ -263,7 +285,6 @@ void VelocityModule::geometry_steps_fill()
|
|||
*/
|
||||
void VelocityModule::step_swap()
|
||||
{
|
||||
|
||||
auto swap_steps = [&](eVelocityStep step_a, eVelocityStep step_b) {
|
||||
std::swap(object_steps[step_a], object_steps[step_b]);
|
||||
std::swap(geometry_steps[step_a], geometry_steps[step_b]);
|
||||
|
|
|
@ -29,8 +29,8 @@ namespace blender::eevee {
|
|||
class VelocityModule {
|
||||
public:
|
||||
struct VelocityObjectData : public VelocityIndex {
|
||||
/** ID to retrieve the corresponding #VelocityGeometryData after copy. */
|
||||
ID *id;
|
||||
/** ID key to retrieve the corresponding #VelocityGeometryData after copy. */
|
||||
uint64_t id;
|
||||
};
|
||||
struct VelocityGeometryData {
|
||||
/** VertBuf not yet ready to be copied to the #VelocityGeometryBuf. */
|
||||
|
@ -46,8 +46,8 @@ class VelocityModule {
|
|||
* geometry offset.
|
||||
*/
|
||||
Map<ObjectKey, VelocityObjectData> velocity_map;
|
||||
/** Geometry to be copied to VelocityGeometryBuf. Indexed by evaluated ID *. Empty after */
|
||||
Map<ID *, VelocityGeometryData> geometry_map;
|
||||
/** Geometry to be copied to VelocityGeometryBuf. Indexed by evaluated ID hash. Empty after */
|
||||
Map<uint64_t, VelocityGeometryData> geometry_map;
|
||||
/** Contains all objects matrices for each time step. */
|
||||
std::array<VelocityObjectBuf *, 3> object_steps;
|
||||
/** Contains all Geometry steps from deforming objects for each time step. */
|
||||
|
|
|
@ -14,6 +14,19 @@
|
|||
|
||||
namespace blender::draw {
|
||||
|
||||
/** Hair. */
|
||||
|
||||
void hair_init();
|
||||
|
||||
GPUVertBuf *hair_pos_buffer_get(Scene *scene,
|
||||
Object *object,
|
||||
ParticleSystem *psys,
|
||||
ModifierData *md);
|
||||
|
||||
void hair_update(Manager &manager);
|
||||
|
||||
void hair_free();
|
||||
|
||||
GPUBatch *hair_sub_pass_setup(PassMain::Sub &sub_ps,
|
||||
const Scene *scene,
|
||||
Object *object,
|
||||
|
@ -28,6 +41,16 @@ GPUBatch *hair_sub_pass_setup(PassSimple::Sub &sub_ps,
|
|||
ModifierData *md,
|
||||
GPUMaterial *gpu_material = nullptr);
|
||||
|
||||
/** Curves. */
|
||||
|
||||
void curves_init();
|
||||
|
||||
GPUVertBuf *curves_pos_buffer_get(Scene *scene, Object *object);
|
||||
|
||||
void curves_update(Manager &manager);
|
||||
|
||||
void curves_free();
|
||||
|
||||
GPUBatch *curves_sub_pass_setup(PassMain::Sub &ps,
|
||||
const Scene *scene,
|
||||
Object *ob,
|
||||
|
@ -38,6 +61,8 @@ GPUBatch *curves_sub_pass_setup(PassSimple::Sub &ps,
|
|||
Object *ob,
|
||||
GPUMaterial *gpu_material = nullptr);
|
||||
|
||||
/* Point cloud. */
|
||||
|
||||
GPUBatch *point_cloud_sub_pass_setup(PassMain::Sub &sub_ps,
|
||||
Object *object,
|
||||
GPUMaterial *gpu_material = nullptr);
|
||||
|
@ -46,6 +71,8 @@ GPUBatch *point_cloud_sub_pass_setup(PassSimple::Sub &sub_ps,
|
|||
Object *object,
|
||||
GPUMaterial *gpu_material = nullptr);
|
||||
|
||||
/** Volume. */
|
||||
|
||||
/**
|
||||
* Add attribute bindings of volume grids to an existing pass.
|
||||
* No draw call is added so the caller can decide how to use the data.
|
||||
|
|
|
@ -88,6 +88,25 @@ static GPUShader *curves_eval_shader_get(CurvesEvalShader type)
|
|||
return DRW_shader_curves_refine_get(type, drw_curves_shader_type_get());
|
||||
}
|
||||
|
||||
static void drw_curves_ensure_dummy_vbo()
|
||||
{
|
||||
if (g_dummy_vbo != nullptr) {
|
||||
return;
|
||||
}
|
||||
/* initialize vertex format */
|
||||
GPUVertFormat format = {0};
|
||||
uint dummy_id = GPU_vertformat_attr_add(&format, "dummy", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
|
||||
|
||||
g_dummy_vbo = GPU_vertbuf_create_with_format_ex(
|
||||
&format, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
|
||||
const float vert[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
GPU_vertbuf_data_alloc(g_dummy_vbo, 1);
|
||||
GPU_vertbuf_attr_fill(g_dummy_vbo, dummy_id, vert);
|
||||
/* Create vbo immediately to bind to texture buffer. */
|
||||
GPU_vertbuf_use(g_dummy_vbo);
|
||||
}
|
||||
|
||||
void DRW_curves_init(DRWData *drw_data)
|
||||
{
|
||||
/* Initialize legacy hair too, to avoid verbosity in callers. */
|
||||
|
@ -106,20 +125,7 @@ void DRW_curves_init(DRWData *drw_data)
|
|||
g_tf_pass = DRW_pass_create("Update Curves Pass", DRW_STATE_WRITE_COLOR);
|
||||
}
|
||||
|
||||
if (g_dummy_vbo == nullptr) {
|
||||
/* initialize vertex format */
|
||||
GPUVertFormat format = {0};
|
||||
uint dummy_id = GPU_vertformat_attr_add(&format, "dummy", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
|
||||
|
||||
g_dummy_vbo = GPU_vertbuf_create_with_format_ex(
|
||||
&format, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
|
||||
const float vert[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
GPU_vertbuf_data_alloc(g_dummy_vbo, 1);
|
||||
GPU_vertbuf_attr_fill(g_dummy_vbo, dummy_id, vert);
|
||||
/* Create vbo immediately to bind to texture buffer. */
|
||||
GPU_vertbuf_use(g_dummy_vbo);
|
||||
}
|
||||
drw_curves_ensure_dummy_vbo();
|
||||
}
|
||||
|
||||
void DRW_curves_ubos_pool_free(CurvesUniformBufPool *pool)
|
||||
|
@ -553,12 +559,102 @@ void DRW_curves_free()
|
|||
|
||||
namespace blender::draw {
|
||||
|
||||
static PassSimple *g_pass = nullptr;
|
||||
|
||||
void curves_init()
|
||||
{
|
||||
if (!g_pass) {
|
||||
g_pass = MEM_new<PassSimple>("drw_curves g_pass", "Update Curves Pass");
|
||||
}
|
||||
g_pass->init();
|
||||
g_pass->state_set(DRW_STATE_NO_DRAW);
|
||||
}
|
||||
|
||||
static CurvesEvalCache *curves_cache_get(Curves &curves,
|
||||
GPUMaterial *gpu_material,
|
||||
int subdiv,
|
||||
int thickness_res)
|
||||
{
|
||||
CurvesEvalCache *cache;
|
||||
const bool update = curves_ensure_procedural_data(
|
||||
&curves, &cache, gpu_material, subdiv, thickness_res);
|
||||
|
||||
if (!update) {
|
||||
return cache;
|
||||
}
|
||||
|
||||
const int strands_len = cache->strands_len;
|
||||
const int final_points_len = cache->final[subdiv].strands_res * strands_len;
|
||||
|
||||
auto cache_update = [&](GPUVertBuf *output_buf, GPUVertBuf *input_buf) {
|
||||
PassSimple::Sub &ob_ps = g_pass->sub("Object Pass");
|
||||
|
||||
ob_ps.shader_set(
|
||||
DRW_shader_curves_refine_get(CURVES_EVAL_CATMULL_ROM, PART_REFINE_SHADER_COMPUTE));
|
||||
|
||||
ob_ps.bind_texture("hairPointBuffer", input_buf);
|
||||
ob_ps.bind_texture("hairStrandBuffer", cache->proc_strand_buf);
|
||||
ob_ps.bind_texture("hairStrandSegBuffer", cache->proc_strand_seg_buf);
|
||||
ob_ps.push_constant("hairStrandsRes", &cache->final[subdiv].strands_res);
|
||||
ob_ps.bind_ssbo("posTime", output_buf);
|
||||
|
||||
const int max_strands_per_call = GPU_max_work_group_count(0);
|
||||
int strands_start = 0;
|
||||
while (strands_start < strands_len) {
|
||||
int batch_strands_len = std::min(strands_len - strands_start, max_strands_per_call);
|
||||
PassSimple::Sub &sub_ps = ob_ps.sub("Sub Pass");
|
||||
sub_ps.push_constant("hairStrandOffset", strands_start);
|
||||
sub_ps.dispatch(int3(batch_strands_len, cache->final[subdiv].strands_res, 1));
|
||||
strands_start += batch_strands_len;
|
||||
}
|
||||
};
|
||||
|
||||
if (final_points_len > 0) {
|
||||
cache_update(cache->final[subdiv].proc_buf, cache->proc_point_buf);
|
||||
|
||||
const DRW_Attributes &attrs = cache->final[subdiv].attr_used;
|
||||
for (int i : IndexRange(attrs.num_requests)) {
|
||||
/* Only refine point attributes. */
|
||||
if (attrs.requests[i].domain != ATTR_DOMAIN_CURVE) {
|
||||
cache_update(cache->final[subdiv].attributes_buf[i], cache->proc_attributes_buf[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
GPUVertBuf *curves_pos_buffer_get(Scene *scene, Object *object)
|
||||
{
|
||||
const int subdiv = scene->r.hair_subdiv;
|
||||
const int thickness_res = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 1 : 2;
|
||||
|
||||
Curves &curves = *static_cast<Curves *>(object->data);
|
||||
CurvesEvalCache *cache = curves_cache_get(curves, nullptr, subdiv, thickness_res);
|
||||
|
||||
return cache->final[subdiv].proc_buf;
|
||||
}
|
||||
|
||||
void curves_update(Manager &manager)
|
||||
{
|
||||
manager.submit(*g_pass);
|
||||
GPU_memory_barrier(GPU_BARRIER_SHADER_STORAGE);
|
||||
}
|
||||
|
||||
void curves_free()
|
||||
{
|
||||
MEM_delete(g_pass);
|
||||
g_pass = nullptr;
|
||||
}
|
||||
|
||||
template<typename PassT>
|
||||
GPUBatch *curves_sub_pass_setup_implementation(PassT &sub_ps,
|
||||
const Scene *scene,
|
||||
Object *ob,
|
||||
GPUMaterial *gpu_material)
|
||||
{
|
||||
/** NOTE: This still relies on the old DRW_curves implementation. */
|
||||
|
||||
CurvesUniformBufPool *pool = DST.vmempool->curves_ubos;
|
||||
CurvesInfosBuf &curves_infos = pool->alloc();
|
||||
BLI_assert(ob->type == OB_CURVES);
|
||||
|
|
|
@ -71,6 +71,30 @@ static GPUShader *hair_refine_shader_get(ParticleRefineShader refinement)
|
|||
return DRW_shader_hair_refine_get(refinement, drw_hair_shader_type_get());
|
||||
}
|
||||
|
||||
static void drw_hair_ensure_vbo()
|
||||
{
|
||||
if (g_dummy_vbo != nullptr) {
|
||||
return;
|
||||
}
|
||||
/* initialize vertex format */
|
||||
GPUVertFormat format = {0};
|
||||
uint dummy_id = GPU_vertformat_attr_add(&format, "dummy", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
|
||||
|
||||
g_dummy_vbo = GPU_vertbuf_create_with_format_ex(
|
||||
&format, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
|
||||
const float vert[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
GPU_vertbuf_data_alloc(g_dummy_vbo, 1);
|
||||
GPU_vertbuf_attr_fill(g_dummy_vbo, dummy_id, vert);
|
||||
/* Create VBO immediately to bind to texture buffer. */
|
||||
GPU_vertbuf_use(g_dummy_vbo);
|
||||
|
||||
g_dummy_curves_info = MEM_new<blender::draw::UniformBuffer<CurvesInfos>>("g_dummy_curves_info");
|
||||
memset(
|
||||
g_dummy_curves_info->is_point_attribute, 0, sizeof(g_dummy_curves_info->is_point_attribute));
|
||||
g_dummy_curves_info->push_update();
|
||||
}
|
||||
|
||||
void DRW_hair_init()
|
||||
{
|
||||
if (GPU_transform_feedback_support() || GPU_compute_shader_support()) {
|
||||
|
@ -80,27 +104,7 @@ void DRW_hair_init()
|
|||
g_tf_pass = DRW_pass_create("Update Hair Pass", DRW_STATE_WRITE_COLOR);
|
||||
}
|
||||
|
||||
if (g_dummy_vbo == nullptr) {
|
||||
/* initialize vertex format */
|
||||
GPUVertFormat format = {0};
|
||||
uint dummy_id = GPU_vertformat_attr_add(&format, "dummy", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
|
||||
|
||||
g_dummy_vbo = GPU_vertbuf_create_with_format_ex(
|
||||
&format, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
|
||||
const float vert[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
GPU_vertbuf_data_alloc(g_dummy_vbo, 1);
|
||||
GPU_vertbuf_attr_fill(g_dummy_vbo, dummy_id, vert);
|
||||
/* Create VBO immediately to bind to texture buffer. */
|
||||
GPU_vertbuf_use(g_dummy_vbo);
|
||||
|
||||
g_dummy_curves_info = MEM_new<blender::draw::UniformBuffer<CurvesInfos>>(
|
||||
"g_dummy_curves_info");
|
||||
memset(g_dummy_curves_info->is_point_attribute,
|
||||
0,
|
||||
sizeof(g_dummy_curves_info->is_point_attribute));
|
||||
g_dummy_curves_info->push_update();
|
||||
}
|
||||
drw_hair_ensure_vbo();
|
||||
}
|
||||
|
||||
static void drw_hair_particle_cache_shgrp_attach_resources(DRWShadingGroup *shgrp,
|
||||
|
@ -437,6 +441,86 @@ void DRW_hair_free()
|
|||
|
||||
namespace blender::draw {
|
||||
|
||||
static PassSimple *g_pass = nullptr;
|
||||
|
||||
void hair_init()
|
||||
{
|
||||
if (!g_pass) {
|
||||
g_pass = MEM_new<PassSimple>("drw_hair g_pass", "Update Hair Pass");
|
||||
}
|
||||
g_pass->init();
|
||||
g_pass->state_set(DRW_STATE_NO_DRAW);
|
||||
}
|
||||
|
||||
static ParticleHairCache *hair_particle_cache_get(Object *object,
|
||||
ParticleSystem *psys,
|
||||
ModifierData *md,
|
||||
GPUMaterial *gpu_material,
|
||||
int subdiv,
|
||||
int thickness_res)
|
||||
{
|
||||
ParticleHairCache *cache;
|
||||
bool update = particles_ensure_procedural_data(
|
||||
object, psys, md, &cache, gpu_material, subdiv, thickness_res);
|
||||
|
||||
if (!update) {
|
||||
return cache;
|
||||
}
|
||||
|
||||
const int strands_len = cache->strands_len;
|
||||
const int final_points_len = cache->final[subdiv].strands_res * strands_len;
|
||||
if (final_points_len > 0) {
|
||||
PassSimple::Sub &ob_ps = g_pass->sub("Object Pass");
|
||||
|
||||
ob_ps.shader_set(
|
||||
DRW_shader_hair_refine_get(PART_REFINE_CATMULL_ROM, PART_REFINE_SHADER_COMPUTE));
|
||||
|
||||
ob_ps.bind_texture("hairPointBuffer", cache->proc_point_buf);
|
||||
ob_ps.bind_texture("hairStrandBuffer", cache->proc_strand_buf);
|
||||
ob_ps.bind_texture("hairStrandSegBuffer", cache->proc_strand_seg_buf);
|
||||
ob_ps.push_constant("hairStrandsRes", &cache->final[subdiv].strands_res);
|
||||
ob_ps.bind_ssbo("posTime", cache->final[subdiv].proc_buf);
|
||||
|
||||
const int max_strands_per_call = GPU_max_work_group_count(0);
|
||||
int strands_start = 0;
|
||||
while (strands_start < strands_len) {
|
||||
int batch_strands_len = std::min(strands_len - strands_start, max_strands_per_call);
|
||||
PassSimple::Sub &sub_ps = ob_ps.sub("Sub Pass");
|
||||
sub_ps.push_constant("hairStrandOffset", strands_start);
|
||||
sub_ps.dispatch(int3(batch_strands_len, cache->final[subdiv].strands_res, 1));
|
||||
strands_start += batch_strands_len;
|
||||
}
|
||||
}
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
GPUVertBuf *hair_pos_buffer_get(Scene *scene,
|
||||
Object *object,
|
||||
ParticleSystem *psys,
|
||||
ModifierData *md)
|
||||
{
|
||||
int subdiv = scene->r.hair_subdiv;
|
||||
int thickness_res = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 1 : 2;
|
||||
|
||||
ParticleHairCache *cache = hair_particle_cache_get(
|
||||
object, psys, md, nullptr, subdiv, thickness_res);
|
||||
|
||||
return cache->final[subdiv].proc_buf;
|
||||
}
|
||||
|
||||
void hair_update(Manager &manager)
|
||||
{
|
||||
manager.submit(*g_pass);
|
||||
GPU_memory_barrier(GPU_BARRIER_SHADER_STORAGE);
|
||||
}
|
||||
|
||||
void hair_free()
|
||||
{
|
||||
MEM_delete(g_pass);
|
||||
g_pass = nullptr;
|
||||
}
|
||||
|
||||
template<typename PassT>
|
||||
GPUBatch *hair_sub_pass_setup_implementation(PassT &sub_ps,
|
||||
const Scene *scene,
|
||||
|
@ -445,6 +529,8 @@ GPUBatch *hair_sub_pass_setup_implementation(PassT &sub_ps,
|
|||
ModifierData *md,
|
||||
GPUMaterial *gpu_material)
|
||||
{
|
||||
/** NOTE: This still relies on the old DRW_hair implementation. */
|
||||
|
||||
int subdiv = scene->r.hair_subdiv;
|
||||
int thickness_res = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 1 : 2;
|
||||
ParticleHairCache *hair_cache = drw_hair_particle_cache_get(
|
||||
|
|
Loading…
Reference in New Issue