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:
@@ -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
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -101,6 +101,7 @@ enum eSamplingDimension : uint32_t {
|
||||
SAMPLING_RAYTRACE_X = 18u,
|
||||
SAMPLING_AO_U = 19u,
|
||||
SAMPLING_AO_V = 20u,
|
||||
SAMPLING_CURVES_U = 21u,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user