EEVEE Next: Hair & Curves

Finalize the hair & curves implementation for EEVEE Next.

- Ensure Hair particles have their own `ResourceHandle` and
  `ObjectKey`, so Motion Blur works correctly.
  (Note that the `ObjectHandle` and the `ObjectKey` are always created
  "on the fly" instead of being stored as `DrawData`, since it's not
  supported for particle system `ID`s).
- The (unused) `ObjectKey::use_particle_hair` has been replaced by an
  integer `sub_key`, so multiple particle systems per object can be
  supported.
- `VelocityModule::step_object_sync` now has 2 extra optional
  parameters for syncing Hair particle systems.
- Update `DRW_curves_update` so it's safe to call it from "Next"
  engines.
- Disable the `sampling.reset()` call from `step_object_sync`, since
  `is_deform` is always true for objects with particle modifiers, and
  this causes the renderer to get stuck at sample 1.

Pull Request: https://projects.blender.org/blender/blender/pulls/109833
This commit is contained in:
Miguel Pozo
2023-07-14 18:23:26 +02:00
parent e48d0c3cda
commit 1a24b5f81f
11 changed files with 111 additions and 67 deletions

View File

@@ -23,6 +23,8 @@
#include "eevee_engine.h"
#include "eevee_instance.hh"
#include "DNA_particle_types.h"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
@@ -193,9 +195,24 @@ void Instance::object_sync(Object *ob)
ObjectHandle &ob_handle = sync.sync_object(ob);
if (partsys_is_visible && ob != DRW_context_state_get()->object_edit) {
int sub_key = 1;
LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) {
if (md->type == eModifierType_ParticleSystem) {
sync.sync_curves(ob, ob_handle, res_handle, md);
ParticleSystem *particle_sys = reinterpret_cast<ParticleSystemModifierData *>(md)->psys;
ParticleSettings *part_settings = particle_sys->part;
const int draw_as = (part_settings->draw_as == PART_DRAW_REND) ? part_settings->ren_as :
part_settings->draw_as;
if (draw_as != PART_DRAW_PATH ||
!DRW_object_is_visible_psys_in_active_context(ob, particle_sys)) {
continue;
}
ObjectHandle _ob_handle = {0};
_ob_handle.object_key = ObjectKey(ob_handle.object_key.ob, sub_key++);
_ob_handle.recalc = particle_sys->recalc;
ResourceHandle _res_handle = manager->resource_handle(float4x4(ob->object_to_world));
sync.sync_curves(ob, _ob_handle, _res_handle, md, particle_sys);
}
}
}
@@ -270,9 +287,7 @@ void Instance::render_sync()
/* TODO: Remove old draw manager calls. */
DRW_render_instance_buffer_finish();
/* Also we weed to have a correct FBO bound for #DRW_hair_update */
// GPU_framebuffer_bind();
// DRW_hair_update();
DRW_curves_update();
}
bool Instance::do_probe_sync() const

View File

@@ -127,6 +127,8 @@ void Sampling::step()
/* TODO de-correlate. */
data_.dimensions[SAMPLING_AO_U] = r[0];
data_.dimensions[SAMPLING_AO_V] = r[1];
/* TODO de-correlate. */
data_.dimensions[SAMPLING_CURVES_U] = r[0];
}
{
/* Using leaped Halton sequence so we can reused the same primes as lens. */

View File

@@ -101,6 +101,7 @@ enum eSamplingDimension : uint32_t {
SAMPLING_RAYTRACE_X = 18u,
SAMPLING_AO_U = 19u,
SAMPLING_AO_V = 20u,
SAMPLING_CURVES_U = 21u,
};
/**

View File

@@ -343,52 +343,41 @@ void SyncModule::sync_gpencil(Object *ob, ObjectHandle &ob_handle, ResourceHandl
/** \name Hair
* \{ */
static void shgroup_curves_call(MaterialPass &matpass,
Object *ob,
ParticleSystem *part_sys = nullptr,
ModifierData *modifier_data = nullptr)
{
UNUSED_VARS(ob, modifier_data);
if (matpass.sub_pass == nullptr) {
return;
}
if (part_sys != nullptr) {
// DRW_shgroup_hair_create_sub(ob, part_sys, modifier_data, matpass.sub_pass, matpass.gpumat);
}
else {
// DRW_shgroup_curves_create_sub(ob, matpass.sub_pass, matpass.gpumat);
}
}
void SyncModule::sync_curves(Object *ob,
ObjectHandle &ob_handle,
ResourceHandle res_handle,
ModifierData *modifier_data)
ModifierData *modifier_data,
ParticleSystem *particle_sys)
{
UNUSED_VARS(res_handle);
int mat_nr = CURVES_MATERIAL_NR;
ParticleSystem *part_sys = nullptr;
if (modifier_data != nullptr) {
part_sys = reinterpret_cast<ParticleSystemModifierData *>(modifier_data)->psys;
if (!DRW_object_is_visible_psys_in_active_context(ob, part_sys)) {
return;
}
ParticleSettings *part_settings = part_sys->part;
const int draw_as = (part_settings->draw_as == PART_DRAW_REND) ? part_settings->ren_as :
part_settings->draw_as;
if (draw_as != PART_DRAW_PATH) {
return;
}
mat_nr = part_settings->omat;
if (particle_sys != nullptr) {
mat_nr = particle_sys->part->omat;
}
bool has_motion = inst_.velocity.step_object_sync(ob, ob_handle.object_key, ob_handle.recalc);
bool has_motion = inst_.velocity.step_object_sync(
ob, ob_handle.object_key, res_handle, ob_handle.recalc, modifier_data, particle_sys);
Material &material = inst_.materials.material_get(ob, has_motion, mat_nr - 1, MAT_GEOM_CURVES);
shgroup_curves_call(material.shading, ob, part_sys, modifier_data);
shgroup_curves_call(material.prepass, ob, part_sys, modifier_data);
shgroup_curves_call(material.shadow, ob, part_sys, modifier_data);
auto drawcall_add = [&](MaterialPass &matpass) {
if (matpass.sub_pass == nullptr) {
return;
}
if (particle_sys != nullptr) {
PassMain::Sub &sub_pass = matpass.sub_pass->sub("Hair SubPass");
GPUBatch *geometry = hair_sub_pass_setup(
sub_pass, inst_.scene, ob, particle_sys, modifier_data, matpass.gpumat);
sub_pass.draw(geometry, res_handle);
}
else {
PassMain::Sub &sub_pass = matpass.sub_pass->sub("Curves SubPass");
GPUBatch *geometry = curves_sub_pass_setup(sub_pass, inst_.scene, ob, matpass.gpumat);
sub_pass.draw(geometry, res_handle);
}
};
drawcall_add(material.shading);
drawcall_add(material.prepass);
drawcall_add(material.shadow);
inst_.cryptomatte.sync_object(ob, res_handle);
GPUMaterial *gpu_material =
@@ -396,9 +385,6 @@ void SyncModule::sync_curves(Object *ob,
::Material *mat = GPU_material_get_material(gpu_material);
inst_.cryptomatte.sync_material(mat);
/* TODO(fclem) Hair velocity. */
// shading_passes.velocity.gpencil_add(ob, ob_handle);
bool is_caster = material.shadow.sub_pass != nullptr;
bool is_alpha_blend = material.is_alpha_blend_transparent;
inst_.shadows.sync_object(ob_handle, res_handle, is_caster, is_alpha_blend);

View File

@@ -40,15 +40,15 @@ struct ObjectKey {
Object *parent;
/** Dupli objects recursive unique identifier */
int id[MAX_DUPLI_RECUR];
/** If object uses particle system hair. */
bool use_particle_hair;
/** Used for particle system hair. */
int sub_key_;
#ifdef DEBUG
char name[64];
#endif
ObjectKey() : ob(nullptr), parent(nullptr){};
ObjectKey(Object *ob_, Object *parent_, int id_[MAX_DUPLI_RECUR], bool use_particle_hair_)
: ob(ob_), parent(parent_), use_particle_hair(use_particle_hair_)
ObjectKey(Object *ob_, Object *parent_, int id_[MAX_DUPLI_RECUR], int sub_key_ = 0)
: ob(ob_), parent(parent_), sub_key_(sub_key_)
{
if (id_) {
memcpy(id, id_, sizeof(id));
@@ -67,16 +67,19 @@ struct ObjectKey {
break;
}
}
if (sub_key_ != 0) {
hash_value = BLI_ghashutil_combine_hash(hash_value, sub_key_);
}
#ifdef DEBUG
STRNCPY(name, ob->id.name);
#endif
}
ObjectKey(Object *ob, DupliObject *dupli, Object *parent)
: ObjectKey(ob, parent, dupli ? dupli->persistent_id : nullptr, false){};
ObjectKey(Object *ob, DupliObject *dupli, Object *parent, int sub_key_ = 0)
: ObjectKey(ob, parent, dupli ? dupli->persistent_id : nullptr, sub_key_){};
ObjectKey(Object *ob)
: ObjectKey(ob, DRW_object_get_dupli(ob), DRW_object_get_dupli_parent(ob)){};
ObjectKey(Object *ob, int sub_key_ = 0)
: ObjectKey(ob, DRW_object_get_dupli(ob), DRW_object_get_dupli_parent(ob), sub_key_){};
uint64_t hash() const
{
@@ -91,8 +94,8 @@ struct ObjectKey {
if (parent != k.parent) {
return (parent < k.parent);
}
if (use_particle_hair != k.use_particle_hair) {
return (use_particle_hair < k.use_particle_hair);
if (sub_key_ != k.sub_key_) {
return (sub_key_ < k.sub_key_);
}
return memcmp(id, k.id, sizeof(id)) < 0;
}
@@ -105,7 +108,7 @@ struct ObjectKey {
if (parent != k.parent) {
return false;
}
if (use_particle_hair != k.use_particle_hair) {
if (sub_key_ != k.sub_key_) {
return false;
}
return memcmp(id, k.id, sizeof(id)) == 0;
@@ -172,7 +175,8 @@ class SyncModule {
void sync_curves(Object *ob,
ObjectHandle &ob_handle,
ResourceHandle res_handle,
ModifierData *modifier_data = nullptr);
ModifierData *modifier_data = nullptr,
ParticleSystem *particle_sys = nullptr);
void sync_light_probe(Object *ob, ObjectHandle &ob_handle);
};

View File

@@ -15,6 +15,7 @@
#include "BKE_object.h"
#include "BLI_map.hh"
#include "DEG_depsgraph_query.h"
#include "DNA_particle_types.h"
#include "DNA_rigidbody_types.h"
#include "draw_cache_impl.h"
@@ -87,7 +88,9 @@ void VelocityModule::step_camera_sync()
bool VelocityModule::step_object_sync(Object *ob,
ObjectKey &object_key,
ResourceHandle resource_handle,
int /*IDRecalcFlag*/ recalc)
int /*IDRecalcFlag*/ recalc,
ModifierData *modifier_data /*= nullptr*/,
ParticleSystem *particle_sys /*= nullptr*/)
{
bool has_motion = object_has_velocity(ob) || (recalc & ID_RECALC_TRANSFORM);
/* NOTE: Fragile. This will only work with 1 frame of lag since we can't record every geometry
@@ -107,7 +110,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 = (ID *)ob->data;
vel.id = particle_sys ? &particle_sys->part->id : &ob->id;
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. */
@@ -127,6 +130,10 @@ bool VelocityModule::step_object_sync(Object *ob,
if (has_deform) {
auto add_cb = [&]() {
VelocityGeometryData data;
if (particle_sys) {
data.pos_buf = DRW_hair_pos_buffer_get(ob, particle_sys, modifier_data);
return data;
}
switch (ob->type) {
case OB_CURVES:
data.pos_buf = DRW_curves_pos_buffer_get(ob);
@@ -178,7 +185,9 @@ bool VelocityModule::step_object_sync(Object *ob,
}
/* TODO(@fclem): Reset sampling here? Should ultimately be covered by depsgraph update tags. */
inst_.sampling.reset();
/* NOTE(Miguel Pozo): Disable, since is_deform is always true for objects with particle
* modifiers, and this causes the renderer to get stuck at sample 1. */
// inst_.sampling.reset();
return true;
}

View File

@@ -108,7 +108,9 @@ class VelocityModule {
bool step_object_sync(Object *ob,
ObjectKey &object_key,
ResourceHandle resource_handle,
int recalc = 0);
int recalc = 0,
ModifierData *modifier_data = nullptr,
ParticleSystem *particle_sys = nullptr);
/* Moves next frame data to previous frame data. Nullify next frame data. */
void step_swap();

View File

@@ -16,8 +16,6 @@ void main()
init_interface();
vec3 T;
bool is_persp = (ProjectionMatrix[3][3] == 0.0);
hair_get_pos_tan_binor_time(is_persp,
ModelMatrixInverse,
@@ -30,14 +28,15 @@ void main()
interp.curves_thickness,
interp.curves_time_width);
interp.N = cross(T, interp.curves_binormal);
interp.N = cross(interp.curves_tangent, interp.curves_binormal);
interp.curves_strand_id = hair_get_strand_id();
interp.barycentric_coords = hair_get_barycentric();
#ifdef MAT_VELOCITY
/* Due to the screen space nature of the vertex positioning, we compute only the motion of curve
* strand, not its cylinder. Otherwise we would add the rotation velocity. */
int vert_idx = hair_get_base_id();
vec3 prv, nxt, pos = texelFetch(hairPointBuffer, vert_idx).point_position;
vec3 prv, nxt;
vec3 pos = texelFetch(hairPointBuffer, vert_idx).point_position;
velocity_local_pos_get(pos, vert_idx, prv, nxt);
/* FIXME(fclem): Evaluating before displacement avoid displacement being treated as motion but
* ignores motion from animated displacement. Supporting animated displacement motion vectors

View File

@@ -2,6 +2,7 @@
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#if defined(USE_BARYCENTRICS) && defined(GPU_FRAGMENT_SHADER) && defined(MAT_GEOM_MESH)
vec3 barycentric_distances_get()
@@ -39,6 +40,18 @@ void init_globals_curves()
{
/* Shade as a cylinder. */
float cos_theta = interp.curves_time_width / interp.curves_thickness;
#if defined(GPU_FRAGMENT_SHADER) && defined(MAT_GEOM_CURVES)
if (hairThicknessRes == 1) {
/* Random cosine normal distribution on the hair surface. */
float noise = utility_tx_fetch(utility_tx, gl_FragCoord.xy, UTIL_BLUE_NOISE_LAYER).x;
# ifdef EEVEE_SAMPLING_DATA
/* Needs to check for SAMPLING_DATA,
* otherwise Surfel and World (?!?!) shader validation fails. */
noise = fract(noise + sampling_rng_1D_get(SAMPLING_CURVES_U));
# endif
cos_theta = noise * 2.0 - 1.0;
}
#endif
float sin_theta = sqrt(max(0.0, 1.0 - cos_theta * cos_theta));
g_data.N = g_data.Ni = normalize(interp.N * sin_theta + interp.curves_binormal * cos_theta);

View File

@@ -67,10 +67,11 @@ GPU_SHADER_CREATE_INFO(eevee_geom_curves)
.additional_info("eevee_shared")
.define("MAT_GEOM_CURVES")
.vertex_source("eevee_geom_curves_vert.glsl")
.additional_info("draw_hair",
"draw_curves_infos",
.additional_info("draw_modelmat_new",
"draw_resource_id_varying",
"draw_resource_id_new");
"draw_view",
"draw_hair_new",
"draw_curves_infos");
GPU_SHADER_CREATE_INFO(eevee_geom_world)
.additional_info("eevee_shared")

View File

@@ -410,6 +410,18 @@ DRWShadingGroup *DRW_shgroup_curves_create_sub(Object *object,
void DRW_curves_update()
{
/* Ensure there's a valid active view.
* "Next" engines use this function, but this still uses the old Draw Manager. */
if (DRW_view_default_get() == nullptr) {
/* Create a dummy default view, it's not really used. */
DRW_view_default_set(DRW_view_create(
float4x4::identity().ptr(), float4x4::identity().ptr(), nullptr, nullptr, nullptr));
}
if (DRW_view_get_active() == nullptr) {
DRW_view_set_active(DRW_view_default_get());
}
/* Update legacy hair too, to avoid verbosity in callers. */
DRW_hair_update();