EEVEE-Next: Add mesh volume bounds estimation
This adds correct object bounds estimation. This works by creating an occupancy texture where one bit represents one froxel. A geometry pre-pass fill this occupancy texture and doesn't do any shading. Each bit set to 0 will not be considered occupied by the object volume and will discard the material compute shader for this froxel. There is 2 method of computing the occupancy map: - Atomic XOR: For each fragment we compute the amount of froxels **center** in-front of it. We then convert that into occupancy bitmask that we apply to the occupancy texture using `imageAtomicXor`. This is straight forward and works well for any manifold geometry. - Hit List: For each fragment we write the fragment depth in a list (contained in one array texture). This list is then processed by a fullscreen pass (see `eevee_occupancy_convert_frag.glsl`) that sorts and converts all the hits to the occupancy bits. This emulate Cycles behavior by considering only back-face hits as exit events and front-face hits as entry events. The result stores it to the occupancy texture using bit-wise `OR` operation to compose it with other non-hit list objects. This also decouple the hit-list evaluation complexity from the material evaluation shader. ## Limitations ### Fast - Non-manifolds geometry objects are rendered incorrectly. - Non-manifolds geometry objects will affect other objects in front of them. ### Accurate - Limited to 16 hits per layer for now. - Non-manifolds geometry objects will affect other objects in front of them. Pull Request: https://projects.blender.org/blender/blender/pulls/113731
This commit is contained in:
committed by
Clément Foucault
parent
dbd775c708
commit
f79b86553a
@@ -405,6 +405,15 @@ template<typename T>
|
||||
[[nodiscard]] MatBase<T, 4, 4> orthographic(
|
||||
T left, T right, T bottom, T top, T near_clip, T far_clip);
|
||||
|
||||
/**
|
||||
* \brief Create an orthographic projection matrix using OpenGL coordinate convention:
|
||||
* Maps each axis range to [-1..1] range for all axes except Z.
|
||||
* The Z axis is collapsed to 0 which eliminates the depth component. So it cannot be used with
|
||||
* depth testing.
|
||||
* The resulting matrix can be used with either #project_point or #transform_point.
|
||||
*/
|
||||
template<typename T> MatBase<T, 4, 4> orthographic_infinite(T left, T right, T bottom, T top);
|
||||
|
||||
/**
|
||||
* \brief Create a perspective projection matrix using OpenGL coordinate convention:
|
||||
* Maps each axis range to [-1..1] range for all axes.
|
||||
@@ -425,6 +434,16 @@ template<typename T>
|
||||
[[nodiscard]] MatBase<T, 4, 4> perspective_fov(
|
||||
T angle_left, T angle_right, T angle_bottom, T angle_top, T near_clip, T far_clip);
|
||||
|
||||
/**
|
||||
* \brief Create a perspective projection matrix using OpenGL coordinate convention:
|
||||
* Maps each axis range to [-1..1] range for all axes except for the Z where [near_clip..inf] is
|
||||
* mapped to [-1..1].
|
||||
* `left`, `right`, `bottom`, `top` are frustum side distances at `z=near_clip`.
|
||||
* The resulting matrix can be used with #project_point.
|
||||
*/
|
||||
template<typename T>
|
||||
[[nodiscard]] MatBase<T, 4, 4> perspective_infinite(T left, T right, T bottom, T top, T near_clip);
|
||||
|
||||
} // namespace projection
|
||||
|
||||
/** \} */
|
||||
@@ -1554,6 +1573,23 @@ MatBase<T, 4, 4> orthographic(T left, T right, T bottom, T top, T near_clip, T f
|
||||
return mat;
|
||||
}
|
||||
|
||||
template<typename T> MatBase<T, 4, 4> orthographic_infinite(T left, T right, T bottom, T top)
|
||||
{
|
||||
const T x_delta = right - left;
|
||||
const T y_delta = top - bottom;
|
||||
|
||||
MatBase<T, 4, 4> mat = MatBase<T, 4, 4>::identity();
|
||||
if (x_delta != 0 && y_delta != 0) {
|
||||
mat[0][0] = T(2.0) / x_delta;
|
||||
mat[3][0] = -(right + left) / x_delta;
|
||||
mat[1][1] = T(2.0) / y_delta;
|
||||
mat[3][1] = -(top + bottom) / y_delta;
|
||||
mat[2][2] = 0.0f;
|
||||
mat[3][2] = 0.0f;
|
||||
}
|
||||
return mat;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
MatBase<T, 4, 4> perspective(T left, T right, T bottom, T top, T near_clip, T far_clip)
|
||||
{
|
||||
@@ -1575,6 +1611,29 @@ MatBase<T, 4, 4> perspective(T left, T right, T bottom, T top, T near_clip, T fa
|
||||
return mat;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
MatBase<T, 4, 4> perspective_infinite(T left, T right, T bottom, T top, T near_clip)
|
||||
{
|
||||
const T x_delta = right - left;
|
||||
const T y_delta = top - bottom;
|
||||
|
||||
/* From "Projection Matrix Tricks" by Eric Lengyel GDC 2007. */
|
||||
MatBase<T, 4, 4> mat = MatBase<T, 4, 4>::identity();
|
||||
if (x_delta != 0 && y_delta != 0) {
|
||||
mat[0][0] = near_clip * T(2.0) / x_delta;
|
||||
mat[1][1] = near_clip * T(2.0) / y_delta;
|
||||
mat[2][0] = (right + left) / x_delta; /* NOTE: negate Z. */
|
||||
mat[2][1] = (top + bottom) / y_delta;
|
||||
/* Page 17. Choosing an epsilon for 32 bit floating-point precision. */
|
||||
constexpr float eps = 2.4e-7f;
|
||||
mat[2][2] = -1.0f;
|
||||
mat[2][3] = (eps - 1.0f);
|
||||
mat[3][2] = (eps - 2.0f) * near_clip;
|
||||
mat[3][3] = 0.0f;
|
||||
}
|
||||
return mat;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] MatBase<T, 4, 4> perspective_fov(
|
||||
T angle_left, T angle_right, T angle_bottom, T angle_top, T near_clip, T far_clip)
|
||||
|
||||
@@ -505,6 +505,8 @@ template float4x4 orthographic(
|
||||
float left, float right, float bottom, float top, float near_clip, float far_clip);
|
||||
template float4x4 perspective(
|
||||
float left, float right, float bottom, float top, float near_clip, float far_clip);
|
||||
template float4x4 perspective_infinite(
|
||||
float left, float right, float bottom, float top, float near_clip);
|
||||
|
||||
} // namespace projection
|
||||
|
||||
|
||||
@@ -1728,5 +1728,12 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
|
||||
*/
|
||||
{
|
||||
/* Keep this block, even when empty. */
|
||||
|
||||
if (!DNA_struct_member_exists(fd->filesdna, "SceneEEVEE", "int", "volumetric_ray_depth")) {
|
||||
SceneEEVEE default_eevee = *DNA_struct_default_get(SceneEEVEE);
|
||||
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
|
||||
scene->eevee.volumetric_ray_depth = default_eevee.volumetric_ray_depth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,6 +500,7 @@ set(GLSL_SRC
|
||||
engines/eevee_next/shaders/eevee_geom_gpencil_vert.glsl
|
||||
engines/eevee_next/shaders/eevee_geom_mesh_vert.glsl
|
||||
engines/eevee_next/shaders/eevee_geom_point_cloud_vert.glsl
|
||||
engines/eevee_next/shaders/eevee_geom_volume_vert.glsl
|
||||
engines/eevee_next/shaders/eevee_geom_world_vert.glsl
|
||||
engines/eevee_next/shaders/eevee_hiz_debug_frag.glsl
|
||||
engines/eevee_next/shaders/eevee_hiz_update_comp.glsl
|
||||
@@ -524,6 +525,9 @@ set(GLSL_SRC
|
||||
engines/eevee_next/shaders/eevee_motion_blur_gather_comp.glsl
|
||||
engines/eevee_next/shaders/eevee_motion_blur_lib.glsl
|
||||
engines/eevee_next/shaders/eevee_nodetree_lib.glsl
|
||||
engines/eevee_next/shaders/eevee_occupancy_convert_frag.glsl
|
||||
engines/eevee_next/shaders/eevee_occupancy_lib.glsl
|
||||
engines/eevee_next/shaders/eevee_occupancy_test.glsl
|
||||
engines/eevee_next/shaders/eevee_octahedron_lib.glsl
|
||||
engines/eevee_next/shaders/eevee_ray_denoise_bilateral_comp.glsl
|
||||
engines/eevee_next/shaders/eevee_ray_denoise_spatial_comp.glsl
|
||||
@@ -575,6 +579,7 @@ set(GLSL_SRC
|
||||
engines/eevee_next/shaders/eevee_surf_depth_frag.glsl
|
||||
engines/eevee_next/shaders/eevee_surf_forward_frag.glsl
|
||||
engines/eevee_next/shaders/eevee_surf_lib.glsl
|
||||
engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl
|
||||
engines/eevee_next/shaders/eevee_surf_shadow_frag.glsl
|
||||
engines/eevee_next/shaders/eevee_shadow_page_tile_vert.glsl
|
||||
engines/eevee_next/shaders/eevee_shadow_page_tile_frag.glsl
|
||||
|
||||
@@ -154,6 +154,7 @@
|
||||
/* Volumes. */
|
||||
#define VOLUME_GROUP_SIZE 4
|
||||
#define VOLUME_INTEGRATION_GROUP_SIZE 8
|
||||
#define VOLUME_HIT_DEPTH_MAX 16
|
||||
|
||||
/* Resource bindings. */
|
||||
|
||||
@@ -184,6 +185,10 @@
|
||||
#define VOLUME_PROP_EXTINCTION_IMG_SLOT 1
|
||||
#define VOLUME_PROP_EMISSION_IMG_SLOT 2
|
||||
#define VOLUME_PROP_PHASE_IMG_SLOT 3
|
||||
#define VOLUME_OCCUPANCY_SLOT 4
|
||||
/* Only during volume prepass. */
|
||||
#define VOLUME_HIT_DEPTH_SLOT 0
|
||||
#define VOLUME_HIT_COUNT_SLOT 1
|
||||
/* Only during shadow rendering. */
|
||||
#define SHADOW_ATLAS_IMG_SLOT 4
|
||||
|
||||
|
||||
@@ -235,7 +235,7 @@ void Instance::object_sync(Object *ob)
|
||||
sync.sync_point_cloud(ob, ob_handle, res_handle, ob_ref);
|
||||
break;
|
||||
case OB_VOLUME:
|
||||
volume.sync_object(ob, ob_handle, res_handle);
|
||||
sync.sync_volume(ob, ob_handle, res_handle);
|
||||
break;
|
||||
case OB_CURVES:
|
||||
sync.sync_curves(ob, ob_handle, res_handle);
|
||||
@@ -273,7 +273,8 @@ void Instance::object_sync_render(void *instance_,
|
||||
void Instance::end_sync()
|
||||
{
|
||||
velocity.end_sync();
|
||||
shadows.end_sync(); /** \note: Needs to be before lights. */
|
||||
volume.end_sync(); /* Needs to be before shadows. */
|
||||
shadows.end_sync(); /* Needs to be before lights. */
|
||||
lights.end_sync();
|
||||
sampling.end_sync();
|
||||
subsurface.end_sync();
|
||||
@@ -283,7 +284,6 @@ void Instance::end_sync()
|
||||
light_probes.end_sync();
|
||||
reflection_probes.end_sync();
|
||||
planar_probes.end_sync();
|
||||
volume.end_sync();
|
||||
|
||||
global_ubo_.push_update();
|
||||
}
|
||||
|
||||
@@ -172,6 +172,12 @@ MaterialPass MaterialModule::material_pass_get(Object *ob,
|
||||
matpass.gpumat = inst_.shaders.material_shader_get(
|
||||
blender_mat, ntree, pipeline_type, geometry_type, use_deferred_compilation);
|
||||
|
||||
const bool is_volume = ELEM(pipeline_type, MAT_PIPE_VOLUME_OCCUPANCY, MAT_PIPE_VOLUME_MATERIAL);
|
||||
const bool is_forward = ELEM(pipeline_type,
|
||||
MAT_PIPE_FORWARD,
|
||||
MAT_PIPE_FORWARD_PREPASS,
|
||||
MAT_PIPE_FORWARD_PREPASS_VELOCITY);
|
||||
|
||||
switch (GPU_material_status(matpass.gpumat)) {
|
||||
case GPU_MAT_SUCCESS: {
|
||||
/* Determine optimization status for remaining compilations counter. */
|
||||
@@ -183,8 +189,7 @@ MaterialPass MaterialModule::material_pass_get(Object *ob,
|
||||
}
|
||||
case GPU_MAT_QUEUED:
|
||||
queued_shaders_count++;
|
||||
blender_mat = (geometry_type == MAT_GEOM_VOLUME_OBJECT) ? BKE_material_default_volume() :
|
||||
BKE_material_default_surface();
|
||||
blender_mat = (is_volume) ? BKE_material_default_volume() : BKE_material_default_surface();
|
||||
matpass.gpumat = inst_.shaders.material_shader_get(
|
||||
blender_mat, blender_mat->nodetree, pipeline_type, geometry_type, false);
|
||||
break;
|
||||
@@ -203,13 +208,9 @@ MaterialPass MaterialModule::material_pass_get(Object *ob,
|
||||
inst_.sampling.reset();
|
||||
}
|
||||
|
||||
if (ELEM(pipeline_type,
|
||||
MAT_PIPE_FORWARD,
|
||||
MAT_PIPE_FORWARD_PREPASS,
|
||||
MAT_PIPE_FORWARD_PREPASS_VELOCITY) &&
|
||||
GPU_material_flag_get(matpass.gpumat, GPU_MATFLAG_TRANSPARENT))
|
||||
{
|
||||
/* Transparent pass is generated later. */
|
||||
const bool is_transparent = GPU_material_flag_get(matpass.gpumat, GPU_MATFLAG_TRANSPARENT);
|
||||
if (is_volume || (is_forward && is_transparent)) {
|
||||
/* Sub pass is generated later. */
|
||||
matpass.sub_pass = nullptr;
|
||||
}
|
||||
else {
|
||||
@@ -240,13 +241,31 @@ Material &MaterialModule::material_sync(Object *ob,
|
||||
eMaterialGeometry geometry_type,
|
||||
bool has_motion)
|
||||
{
|
||||
if (geometry_type == MAT_GEOM_VOLUME_OBJECT) {
|
||||
MaterialKey material_key(blender_mat, geometry_type, MAT_PIPE_VOLUME);
|
||||
return material_map_.lookup_or_add_cb(material_key, [&]() {
|
||||
if (geometry_type == MAT_GEOM_VOLUME) {
|
||||
MaterialKey material_key(blender_mat, geometry_type, MAT_PIPE_VOLUME_MATERIAL);
|
||||
Material &mat = material_map_.lookup_or_add_cb(material_key, [&]() {
|
||||
Material mat = {};
|
||||
mat.volume = material_pass_get(ob, blender_mat, MAT_PIPE_VOLUME, MAT_GEOM_VOLUME_OBJECT);
|
||||
mat.volume_occupancy = material_pass_get(
|
||||
ob, blender_mat, MAT_PIPE_VOLUME_OCCUPANCY, MAT_GEOM_VOLUME);
|
||||
mat.volume_material = material_pass_get(
|
||||
ob, blender_mat, MAT_PIPE_VOLUME_MATERIAL, MAT_GEOM_VOLUME_OBJECT);
|
||||
return mat;
|
||||
});
|
||||
|
||||
/* Volume needs to use one sub pass per object to support layering. */
|
||||
VolumeLayer *layer = inst_.pipelines.volume.register_and_get_layer(ob);
|
||||
if (layer) {
|
||||
mat.volume_occupancy.sub_pass = layer->occupancy_add(
|
||||
ob, blender_mat, mat.volume_occupancy.gpumat);
|
||||
mat.volume_material.sub_pass = layer->material_add(
|
||||
ob, blender_mat, mat.volume_material.gpumat);
|
||||
}
|
||||
else {
|
||||
/* Culled volumes. */
|
||||
mat.volume_occupancy.sub_pass = nullptr;
|
||||
mat.volume_material.sub_pass = nullptr;
|
||||
}
|
||||
return mat;
|
||||
}
|
||||
|
||||
eMaterialPipeline surface_pipe = (blender_mat->blend_method == MA_BM_BLEND) ? MAT_PIPE_FORWARD :
|
||||
@@ -271,7 +290,9 @@ Material &MaterialModule::material_sync(Object *ob,
|
||||
mat.reflection_probe_shading = MaterialPass();
|
||||
mat.planar_probe_prepass = MaterialPass();
|
||||
mat.planar_probe_shading = MaterialPass();
|
||||
mat.volume = MaterialPass();
|
||||
mat.volume_occupancy = MaterialPass();
|
||||
mat.volume_material = MaterialPass();
|
||||
mat.is_volume = false;
|
||||
}
|
||||
else {
|
||||
/* Order is important for transparent. */
|
||||
@@ -296,11 +317,16 @@ Material &MaterialModule::material_sync(Object *ob,
|
||||
ob, blender_mat, MAT_PIPE_DEFERRED, geometry_type, MAT_PROBE_PLANAR);
|
||||
}
|
||||
|
||||
if (GPU_material_has_volume_output(mat.shading.gpumat)) {
|
||||
mat.volume = material_pass_get(ob, blender_mat, MAT_PIPE_VOLUME, MAT_GEOM_VOLUME_OBJECT);
|
||||
mat.is_volume = GPU_material_has_volume_output(mat.shading.gpumat);
|
||||
if (mat.is_volume) {
|
||||
mat.volume_occupancy = material_pass_get(
|
||||
ob, blender_mat, MAT_PIPE_VOLUME_OCCUPANCY, geometry_type);
|
||||
mat.volume_material = material_pass_get(
|
||||
ob, blender_mat, MAT_PIPE_VOLUME_MATERIAL, MAT_GEOM_VOLUME_OBJECT);
|
||||
}
|
||||
else {
|
||||
mat.volume = MaterialPass();
|
||||
mat.volume_occupancy = MaterialPass();
|
||||
mat.volume_material = MaterialPass();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,6 +358,21 @@ Material &MaterialModule::material_sync(Object *ob,
|
||||
ob, blender_mat, mat.shading.gpumat);
|
||||
}
|
||||
|
||||
if (mat.is_volume) {
|
||||
/* Volume needs to use one sub pass per object to support layering. */
|
||||
VolumeLayer *layer = inst_.pipelines.volume.register_and_get_layer(ob);
|
||||
if (layer) {
|
||||
mat.volume_occupancy.sub_pass = layer->occupancy_add(
|
||||
ob, blender_mat, mat.volume_occupancy.gpumat);
|
||||
mat.volume_material.sub_pass = layer->material_add(
|
||||
ob, blender_mat, mat.volume_material.gpumat);
|
||||
}
|
||||
else {
|
||||
/* Culled volumes. */
|
||||
mat.volume_occupancy.sub_pass = nullptr;
|
||||
mat.volume_material.sub_pass = nullptr;
|
||||
}
|
||||
}
|
||||
return mat;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,17 +32,22 @@ enum eMaterialPipeline {
|
||||
MAT_PIPE_DEFERRED_PREPASS_VELOCITY,
|
||||
MAT_PIPE_FORWARD_PREPASS,
|
||||
MAT_PIPE_FORWARD_PREPASS_VELOCITY,
|
||||
MAT_PIPE_VOLUME,
|
||||
MAT_PIPE_VOLUME_MATERIAL,
|
||||
MAT_PIPE_VOLUME_OCCUPANCY,
|
||||
MAT_PIPE_SHADOW,
|
||||
MAT_PIPE_CAPTURE,
|
||||
MAT_PIPE_PLANAR_PREPASS,
|
||||
};
|
||||
|
||||
enum eMaterialGeometry {
|
||||
/* These maps directly to object types. */
|
||||
MAT_GEOM_MESH = 0,
|
||||
MAT_GEOM_POINT_CLOUD,
|
||||
MAT_GEOM_CURVES,
|
||||
MAT_GEOM_GPENCIL,
|
||||
MAT_GEOM_VOLUME,
|
||||
|
||||
/* These maps to special shader. */
|
||||
MAT_GEOM_VOLUME_OBJECT,
|
||||
MAT_GEOM_VOLUME_WORLD,
|
||||
MAT_GEOM_WORLD,
|
||||
@@ -67,6 +72,7 @@ static inline void material_type_from_shader_uuid(uint64_t shader_uuid,
|
||||
static inline uint64_t shader_uuid_from_material_type(eMaterialPipeline pipeline_type,
|
||||
eMaterialGeometry geometry_type)
|
||||
{
|
||||
BLI_assert(geometry_type < (1 << 4));
|
||||
return geometry_type | (pipeline_type << 4);
|
||||
}
|
||||
|
||||
@@ -108,7 +114,7 @@ static inline eMaterialGeometry to_material_geometry(const Object *ob)
|
||||
case OB_CURVES:
|
||||
return MAT_GEOM_CURVES;
|
||||
case OB_VOLUME:
|
||||
return MAT_GEOM_VOLUME_OBJECT;
|
||||
return MAT_GEOM_VOLUME;
|
||||
case OB_GPENCIL_LEGACY:
|
||||
return MAT_GEOM_GPENCIL;
|
||||
case OB_POINTCLOUD:
|
||||
@@ -227,8 +233,17 @@ struct MaterialPass {
|
||||
|
||||
struct Material {
|
||||
bool is_alpha_blend_transparent;
|
||||
MaterialPass shadow, shading, prepass, capture, reflection_probe_prepass,
|
||||
reflection_probe_shading, planar_probe_prepass, planar_probe_shading, volume;
|
||||
bool is_volume;
|
||||
MaterialPass shadow;
|
||||
MaterialPass shading;
|
||||
MaterialPass prepass;
|
||||
MaterialPass capture;
|
||||
MaterialPass reflection_probe_prepass;
|
||||
MaterialPass reflection_probe_shading;
|
||||
MaterialPass planar_probe_prepass;
|
||||
MaterialPass planar_probe_shading;
|
||||
MaterialPass volume_occupancy;
|
||||
MaterialPass volume_material;
|
||||
};
|
||||
|
||||
struct MaterialArray {
|
||||
|
||||
@@ -331,6 +331,9 @@ PassMain::Sub *ForwardPipeline::prepass_opaque_add(::Material *blender_mat,
|
||||
|
||||
PassMain::Sub *ForwardPipeline::material_opaque_add(::Material *blender_mat, GPUMaterial *gpumat)
|
||||
{
|
||||
BLI_assert_msg(GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSPARENT) == false,
|
||||
"Forward Transparent should be registered directly without calling "
|
||||
"PipelineModule::material_add()");
|
||||
PassMain::Sub *pass = (blender_mat->blend_flag & MA_BL_CULL_BACKFACE) ? opaque_single_sided_ps_ :
|
||||
opaque_double_sided_ps_;
|
||||
return &pass->sub(GPU_material_get_name(gpumat));
|
||||
@@ -755,27 +758,212 @@ void DeferredPipeline::render(View &main_view,
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Volume Pipeline
|
||||
/** \name Volume Layer
|
||||
*
|
||||
* \{ */
|
||||
|
||||
void VolumeLayer::sync()
|
||||
{
|
||||
object_bounds_.clear();
|
||||
use_hit_list = false;
|
||||
is_empty = true;
|
||||
finalized = false;
|
||||
|
||||
draw::PassMain &layer_pass = volume_layer_ps_;
|
||||
layer_pass.init();
|
||||
{
|
||||
PassMain::Sub &pass = layer_pass.sub("occupancy_ps");
|
||||
/* Double sided without depth test. */
|
||||
pass.state_set(DRW_STATE_WRITE_DEPTH);
|
||||
inst_.bind_uniform_data(&pass);
|
||||
inst_.volume.bind_occupancy_buffers(pass);
|
||||
inst_.sampling.bind_resources(pass);
|
||||
occupancy_ps_ = &pass;
|
||||
}
|
||||
{
|
||||
PassMain::Sub &pass = layer_pass.sub("material_ps");
|
||||
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
|
||||
pass.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
|
||||
inst_.bind_uniform_data(&pass);
|
||||
inst_.volume.bind_properties_buffers(pass);
|
||||
inst_.sampling.bind_resources(pass);
|
||||
material_ps_ = &pass;
|
||||
}
|
||||
}
|
||||
|
||||
PassMain::Sub *VolumeLayer::occupancy_add(const Object *ob,
|
||||
const ::Material *blender_mat,
|
||||
GPUMaterial *gpumat)
|
||||
{
|
||||
BLI_assert_msg(GPU_material_has_volume_output(gpumat) == true,
|
||||
"Only volume material should be added here");
|
||||
bool use_fast_occupancy = (ob->type == OB_VOLUME) ||
|
||||
(blender_mat->volume_intersection_method == MA_VOLUME_ISECT_FAST);
|
||||
use_hit_list |= !use_fast_occupancy;
|
||||
is_empty = false;
|
||||
|
||||
PassMain::Sub *pass = &occupancy_ps_->sub(GPU_material_get_name(gpumat));
|
||||
pass->material_set(*inst_.manager, gpumat);
|
||||
pass->push_constant("use_fast_method", use_fast_occupancy);
|
||||
return pass;
|
||||
}
|
||||
|
||||
PassMain::Sub *VolumeLayer::material_add(const Object * /*ob*/,
|
||||
const ::Material * /*blender_mat*/,
|
||||
GPUMaterial *gpumat)
|
||||
{
|
||||
BLI_assert_msg(GPU_material_has_volume_output(gpumat) == true,
|
||||
"Only volume material should be added here");
|
||||
PassMain::Sub *pass = &material_ps_->sub(GPU_material_get_name(gpumat));
|
||||
pass->material_set(*inst_.manager, gpumat);
|
||||
return pass;
|
||||
}
|
||||
|
||||
void VolumeLayer::render(View &view, Texture &occupancy_tx)
|
||||
{
|
||||
if (is_empty) {
|
||||
return;
|
||||
}
|
||||
if (finalized == false) {
|
||||
finalized = true;
|
||||
if (use_hit_list) {
|
||||
/* Add resolve pass only when needed. Insert after occupancy, before material pass. */
|
||||
occupancy_ps_->shader_set(inst_.shaders.static_shader_get(VOLUME_OCCUPANCY_CONVERT));
|
||||
occupancy_ps_->barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
|
||||
occupancy_ps_->draw_procedural(GPU_PRIM_TRIS, 1, 3);
|
||||
}
|
||||
}
|
||||
/* TODO(fclem): Move this clear inside the render pass. */
|
||||
occupancy_tx.clear(uint4(0u));
|
||||
inst_.manager->submit(volume_layer_ps_, view);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Volume Pipeline
|
||||
* \{ */
|
||||
|
||||
void VolumePipeline::sync()
|
||||
{
|
||||
volume_ps_.init();
|
||||
volume_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
|
||||
inst_.bind_uniform_data(&volume_ps_);
|
||||
inst_.volume.bind_properties_buffers(volume_ps_);
|
||||
inst_.sampling.bind_resources(volume_ps_);
|
||||
enabled_ = false;
|
||||
for (auto &layer : layers_) {
|
||||
layer->sync();
|
||||
}
|
||||
}
|
||||
|
||||
PassMain::Sub *VolumePipeline::volume_material_add(GPUMaterial *gpumat)
|
||||
void VolumePipeline::render(View &view, Texture &occupancy_tx)
|
||||
{
|
||||
return &volume_ps_.sub(GPU_material_get_name(gpumat));
|
||||
BLI_assert_msg(enabled_, "Trying to run the volume object pipeline with no actual volume calls");
|
||||
|
||||
for (auto &layer : layers_) {
|
||||
layer->render(view, occupancy_tx);
|
||||
}
|
||||
}
|
||||
|
||||
void VolumePipeline::render(View &view)
|
||||
GridAABB VolumePipeline::grid_aabb_from_object(Object *ob)
|
||||
{
|
||||
inst_.manager->submit(volume_ps_, view);
|
||||
const Camera &camera = inst_.camera;
|
||||
const VolumesInfoData &data = inst_.volume.data_;
|
||||
/* Returns the unified volume grid cell corner of a world space coordinate. */
|
||||
auto to_global_grid_coords = [&](float3 wP) -> int3 {
|
||||
/* TODO(fclem): Should we use the render view winmat and not the camera one? */
|
||||
const float4x4 &view_matrix = camera.data_get().viewmat;
|
||||
const float4x4 &projection_matrix = camera.data_get().winmat;
|
||||
|
||||
float3 ndc_coords = math::project_point(projection_matrix * view_matrix, wP);
|
||||
ndc_coords = (ndc_coords * 0.5f) + float3(0.5f);
|
||||
|
||||
float3 grid_coords = screen_to_volume(projection_matrix,
|
||||
data.depth_near,
|
||||
data.depth_far,
|
||||
data.depth_distribution,
|
||||
data.coord_scale,
|
||||
ndc_coords);
|
||||
/* Round to nearest grid corner. */
|
||||
return int3(grid_coords * float3(data.tex_size) + 0.5);
|
||||
};
|
||||
|
||||
const BoundBox bbox = *BKE_object_boundbox_get(ob);
|
||||
int3 min = int3(INT32_MAX);
|
||||
int3 max = int3(INT32_MIN);
|
||||
|
||||
for (float3 l_corner : bbox.vec) {
|
||||
float3 w_corner = math::transform_point(float4x4(ob->object_to_world), l_corner);
|
||||
/* Note that this returns the nearest cell corner coordinate.
|
||||
* So sub-froxel AABB will effectively return the same coordinate
|
||||
* for each corner (making it empty and skipped) unless it
|
||||
* cover the center of the froxel. */
|
||||
math::min_max(to_global_grid_coords(w_corner), min, max);
|
||||
}
|
||||
return {min, max};
|
||||
}
|
||||
|
||||
GridAABB VolumePipeline::grid_aabb_from_view()
|
||||
{
|
||||
return {int3(0), inst_.volume.data_.tex_size};
|
||||
}
|
||||
|
||||
VolumeLayer *VolumePipeline::register_and_get_layer(Object *ob)
|
||||
{
|
||||
GridAABB object_aabb = grid_aabb_from_object(ob);
|
||||
GridAABB view_aabb = grid_aabb_from_view();
|
||||
if (object_aabb.intersection(view_aabb).is_empty()) {
|
||||
/* Skip invisible object with respect to raster grid and bounds density. */
|
||||
return nullptr;
|
||||
}
|
||||
/* Do linear search in all layers in order. This can be optimized. */
|
||||
for (auto &layer : layers_) {
|
||||
if (!layer->bounds_overlaps(object_aabb)) {
|
||||
layer->add_object_bound(object_aabb);
|
||||
return layer.get();
|
||||
}
|
||||
}
|
||||
/* No non-overlapping layer found. Create new one. */
|
||||
int64_t index = layers_.append_and_get_index(std::make_unique<VolumeLayer>(inst_));
|
||||
(*layers_[index]).add_object_bound(object_aabb);
|
||||
return layers_[index].get();
|
||||
}
|
||||
|
||||
void VolumePipeline::material_call(MaterialPass &volume_material_pass,
|
||||
Object *ob,
|
||||
ResourceHandle res_handle)
|
||||
{
|
||||
if (volume_material_pass.sub_pass == nullptr) {
|
||||
/* Can happen if shader is not compiled, or if object has been culled. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* TODO(fclem): This should be revisited, `volume_sub_pass()` should not decide on the volume
|
||||
* visibility. Instead, we should query visibility upstream and not try to even compile the
|
||||
* shader. */
|
||||
PassMain::Sub *object_pass = volume_sub_pass(
|
||||
*volume_material_pass.sub_pass, inst_.scene, ob, volume_material_pass.gpumat);
|
||||
if (object_pass) {
|
||||
/* Possible double work here. Should be relatively insignificant in practice. */
|
||||
GridAABB object_aabb = grid_aabb_from_object(ob);
|
||||
GridAABB view_aabb = grid_aabb_from_view();
|
||||
GridAABB visible_aabb = object_aabb.intersection(view_aabb);
|
||||
/* Invisible volumes should already have been clipped. */
|
||||
BLI_assert(visible_aabb.is_empty() == false);
|
||||
/* TODO(fclem): Use graphic pipeline instead of compute so we can leverage GPU culling,
|
||||
* resource indexing and other further optimizations. */
|
||||
object_pass->push_constant("drw_ResourceID", int(res_handle.resource_index()));
|
||||
object_pass->push_constant("grid_coords_min", visible_aabb.min);
|
||||
object_pass->dispatch(math::divide_ceil(visible_aabb.extent(), int3(VOLUME_GROUP_SIZE)));
|
||||
/* Notify the volume module to enable itself. */
|
||||
enabled_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool VolumePipeline::use_hit_list() const
|
||||
{
|
||||
for (auto &layer : layers_) {
|
||||
if (layer->use_hit_list) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
@@ -274,19 +274,133 @@ class DeferredPipeline {
|
||||
*
|
||||
* \{ */
|
||||
|
||||
struct GridAABB {
|
||||
int3 min, max;
|
||||
|
||||
GridAABB(int3 min_, int3 max_) : min(min_), max(max_){};
|
||||
|
||||
/** Returns the intersection between this AABB and the \a other AABB. */
|
||||
GridAABB intersection(const GridAABB &other) const
|
||||
{
|
||||
return {math::max(this->min, other.min), math::min(this->max, other.max)};
|
||||
}
|
||||
|
||||
/** Returns the extent of the volume. Undefined if AABB is empty. */
|
||||
int3 extent() const
|
||||
{
|
||||
return max - min;
|
||||
}
|
||||
|
||||
/** Returns true if volume covers nothing or is negative. */
|
||||
bool is_empty() const
|
||||
{
|
||||
return math::reduce_min(max - min) <= 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A volume layer contains a list of non-overlapping volume objects.
|
||||
*/
|
||||
class VolumeLayer {
|
||||
public:
|
||||
bool use_hit_list = false;
|
||||
bool is_empty = true;
|
||||
bool finalized = false;
|
||||
|
||||
private:
|
||||
Instance &inst_;
|
||||
|
||||
PassMain volume_layer_ps_ = {"Volume.Layer"};
|
||||
/* Sub-passes of volume_layer_ps. */
|
||||
PassMain::Sub *occupancy_ps_;
|
||||
PassMain::Sub *material_ps_;
|
||||
/* List of bounds from all objects contained inside this pass. */
|
||||
Vector<GridAABB> object_bounds_;
|
||||
|
||||
public:
|
||||
VolumeLayer(Instance &inst) : inst_(inst)
|
||||
{
|
||||
this->sync();
|
||||
}
|
||||
|
||||
PassMain::Sub *occupancy_add(const Object *ob,
|
||||
const ::Material *blender_mat,
|
||||
GPUMaterial *gpumat);
|
||||
PassMain::Sub *material_add(const Object *ob,
|
||||
const ::Material *blender_mat,
|
||||
GPUMaterial *gpumat);
|
||||
|
||||
/* Return true if the given bounds overlaps any of the contained object in this layer. */
|
||||
bool bounds_overlaps(const GridAABB &object_aabb) const
|
||||
{
|
||||
for (const GridAABB &other_aabb : object_bounds_) {
|
||||
if (object_aabb.intersection(other_aabb).is_empty() == false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void add_object_bound(const GridAABB &object_aabb)
|
||||
{
|
||||
object_bounds_.append(object_aabb);
|
||||
}
|
||||
|
||||
void sync();
|
||||
void render(View &view, Texture &occupancy_tx);
|
||||
};
|
||||
|
||||
class VolumePipeline {
|
||||
private:
|
||||
Instance &inst_;
|
||||
|
||||
PassMain volume_ps_ = {"Volume.Objects"};
|
||||
Vector<std::unique_ptr<VolumeLayer>> layers_;
|
||||
|
||||
/* True if any volume (any object type) creates a volume draw-call. Enables the volume module. */
|
||||
bool enabled_ = false;
|
||||
|
||||
public:
|
||||
VolumePipeline(Instance &inst) : inst_(inst){};
|
||||
|
||||
PassMain::Sub *volume_material_add(GPUMaterial *gpumat);
|
||||
|
||||
void sync();
|
||||
void render(View &view);
|
||||
void render(View &view, Texture &occupancy_tx);
|
||||
|
||||
/**
|
||||
* Returns correct volume layer for a given object and add the object to the layer.
|
||||
* Returns nullptr if the object is not visible at all.
|
||||
*/
|
||||
VolumeLayer *register_and_get_layer(Object *ob);
|
||||
|
||||
/**
|
||||
* Creates a volume material call.
|
||||
* If any call to this function result in a valid draw-call, then the volume module will be
|
||||
* enabled.
|
||||
*/
|
||||
void material_call(MaterialPass &volume_material_pass, Object *ob, ResourceHandle res_handle);
|
||||
|
||||
bool is_enabled() const
|
||||
{
|
||||
return enabled_;
|
||||
}
|
||||
|
||||
/* Returns true if any volume layer uses the hist list. */
|
||||
bool use_hit_list() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Returns Axis aligned bounding box in the volume grid.
|
||||
* Used for frustum culling and volumes overlapping detection.
|
||||
* Represents min and max grid corners covered by a volume.
|
||||
* So a volume covering the first froxel will have min={0,0,0} and max={1,1,1}.
|
||||
* A volume with min={0,0,0} and max={0,0,0} covers nothing.
|
||||
*/
|
||||
GridAABB grid_aabb_from_object(Object *ob);
|
||||
|
||||
/**
|
||||
* Returns the view entire AABB. Used for clipping object bounds.
|
||||
* Remember that these are cells corners, so this extents to `tex_size`.
|
||||
*/
|
||||
GridAABB grid_aabb_from_view();
|
||||
};
|
||||
|
||||
/** \} */
|
||||
@@ -509,7 +623,7 @@ class PipelineModule {
|
||||
deferred.end_sync();
|
||||
}
|
||||
|
||||
PassMain::Sub *material_add(Object *ob,
|
||||
PassMain::Sub *material_add(Object * /*ob*/ /* TODO remove. */,
|
||||
::Material *blender_mat,
|
||||
GPUMaterial *gpumat,
|
||||
eMaterialPipeline pipeline_type,
|
||||
@@ -542,33 +656,29 @@ class PipelineModule {
|
||||
case MAT_PIPE_DEFERRED_PREPASS:
|
||||
return deferred.prepass_add(blender_mat, gpumat, false);
|
||||
case MAT_PIPE_FORWARD_PREPASS:
|
||||
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSPARENT)) {
|
||||
return forward.prepass_transparent_add(ob, blender_mat, gpumat);
|
||||
}
|
||||
return forward.prepass_opaque_add(blender_mat, gpumat, false);
|
||||
|
||||
case MAT_PIPE_DEFERRED_PREPASS_VELOCITY:
|
||||
return deferred.prepass_add(blender_mat, gpumat, true);
|
||||
case MAT_PIPE_FORWARD_PREPASS_VELOCITY:
|
||||
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSPARENT)) {
|
||||
return forward.prepass_transparent_add(ob, blender_mat, gpumat);
|
||||
}
|
||||
return forward.prepass_opaque_add(blender_mat, gpumat, true);
|
||||
|
||||
case MAT_PIPE_DEFERRED:
|
||||
return deferred.material_add(blender_mat, gpumat);
|
||||
case MAT_PIPE_FORWARD:
|
||||
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSPARENT)) {
|
||||
return forward.material_transparent_add(ob, blender_mat, gpumat);
|
||||
}
|
||||
return forward.material_opaque_add(blender_mat, gpumat);
|
||||
case MAT_PIPE_VOLUME:
|
||||
return volume.volume_material_add(gpumat);
|
||||
case MAT_PIPE_SHADOW:
|
||||
return shadow.surface_material_add(gpumat);
|
||||
case MAT_PIPE_CAPTURE:
|
||||
return capture.surface_material_add(blender_mat, gpumat);
|
||||
|
||||
case MAT_PIPE_VOLUME_OCCUPANCY:
|
||||
case MAT_PIPE_VOLUME_MATERIAL:
|
||||
BLI_assert_msg(0, "Volume shaders must register to the volume pipeline directly.");
|
||||
return nullptr;
|
||||
|
||||
case MAT_PIPE_PLANAR_PREPASS:
|
||||
/* Should be handled by the `probe_capture == MAT_PROBE_PLANAR` case. */
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -262,6 +262,8 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_
|
||||
return "eevee_surfel_ray";
|
||||
case VOLUME_INTEGRATION:
|
||||
return "eevee_volume_integration";
|
||||
case VOLUME_OCCUPANCY_CONVERT:
|
||||
return "eevee_volume_occupancy_convert";
|
||||
case VOLUME_RESOLVE:
|
||||
return "eevee_volume_resolve";
|
||||
case VOLUME_SCATTER:
|
||||
@@ -426,6 +428,7 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
|
||||
}
|
||||
info.vertex_inputs_.clear();
|
||||
break;
|
||||
case MAT_GEOM_VOLUME:
|
||||
case MAT_GEOM_VOLUME_OBJECT:
|
||||
case MAT_GEOM_VOLUME_WORLD:
|
||||
/** Volume grid attributes come from 3D textures. Transfer attributes to samplers. */
|
||||
@@ -436,8 +439,11 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
|
||||
break;
|
||||
}
|
||||
|
||||
const bool do_vertex_attrib_load = !ELEM(
|
||||
geometry_type, MAT_GEOM_WORLD, MAT_GEOM_VOLUME_WORLD, MAT_GEOM_VOLUME_OBJECT);
|
||||
const bool do_vertex_attrib_load = !ELEM(geometry_type,
|
||||
MAT_GEOM_WORLD,
|
||||
MAT_GEOM_VOLUME_WORLD,
|
||||
MAT_GEOM_VOLUME_OBJECT,
|
||||
MAT_GEOM_VOLUME);
|
||||
|
||||
if (!do_vertex_attrib_load && !info.vertex_out_interfaces_.is_empty()) {
|
||||
/* Codegen outputs only one interface. */
|
||||
@@ -461,7 +467,7 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
|
||||
|
||||
std::stringstream vert_gen, frag_gen, comp_gen;
|
||||
|
||||
bool is_compute = pipeline_type == MAT_PIPE_VOLUME;
|
||||
bool is_compute = pipeline_type == MAT_PIPE_VOLUME_MATERIAL;
|
||||
|
||||
if (do_vertex_attrib_load) {
|
||||
vert_gen << global_vars.str() << attr_load.str();
|
||||
@@ -474,7 +480,12 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
|
||||
}
|
||||
|
||||
if (!is_compute) {
|
||||
if (!ELEM(geometry_type, MAT_GEOM_WORLD, MAT_GEOM_VOLUME_WORLD, MAT_GEOM_VOLUME_OBJECT)) {
|
||||
if (!ELEM(geometry_type,
|
||||
MAT_GEOM_WORLD,
|
||||
MAT_GEOM_VOLUME_WORLD,
|
||||
MAT_GEOM_VOLUME_OBJECT,
|
||||
MAT_GEOM_VOLUME))
|
||||
{
|
||||
vert_gen << "vec3 nodetree_displacement()\n";
|
||||
vert_gen << "{\n";
|
||||
vert_gen << ((codegen.displacement) ? codegen.displacement : "return vec3(0);\n");
|
||||
@@ -547,6 +558,9 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
|
||||
case MAT_GEOM_POINT_CLOUD:
|
||||
info.additional_info("eevee_geom_point_cloud");
|
||||
break;
|
||||
case MAT_GEOM_VOLUME:
|
||||
info.additional_info("eevee_geom_volume");
|
||||
break;
|
||||
}
|
||||
/* Pipeline Info. */
|
||||
switch (geometry_type) {
|
||||
@@ -583,6 +597,9 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
|
||||
} break;
|
||||
}
|
||||
break;
|
||||
case MAT_PIPE_VOLUME_OCCUPANCY:
|
||||
info.additional_info("eevee_surf_occupancy");
|
||||
break;
|
||||
case MAT_PIPE_CAPTURE:
|
||||
info.additional_info("eevee_surf_capture");
|
||||
break;
|
||||
@@ -613,7 +630,7 @@ GPUMaterial *ShaderModule::material_shader_get(::Material *blender_mat,
|
||||
eMaterialGeometry geometry_type,
|
||||
bool deferred_compilation)
|
||||
{
|
||||
bool is_volume = (pipeline_type == MAT_PIPE_VOLUME);
|
||||
bool is_volume = ELEM(pipeline_type, MAT_PIPE_VOLUME_MATERIAL, MAT_PIPE_VOLUME_OCCUPANCY);
|
||||
|
||||
uint64_t shader_uuid = shader_uuid_from_material_type(pipeline_type, geometry_type);
|
||||
|
||||
@@ -625,7 +642,7 @@ GPUMaterial *ShaderModule::world_shader_get(::World *blender_world,
|
||||
bNodeTree *nodetree,
|
||||
eMaterialPipeline pipeline_type)
|
||||
{
|
||||
bool is_volume = (pipeline_type == MAT_PIPE_VOLUME);
|
||||
bool is_volume = (pipeline_type == MAT_PIPE_VOLUME_MATERIAL);
|
||||
bool defer_compilation = is_volume;
|
||||
|
||||
eMaterialGeometry geometry_type = is_volume ? MAT_GEOM_VOLUME_WORLD : MAT_GEOM_WORLD;
|
||||
@@ -647,7 +664,7 @@ GPUMaterial *ShaderModule::material_shader_get(const char *name,
|
||||
{
|
||||
uint64_t shader_uuid = shader_uuid_from_material_type(pipeline_type, geometry_type);
|
||||
|
||||
bool is_volume = (pipeline_type == MAT_PIPE_VOLUME);
|
||||
bool is_volume = ELEM(pipeline_type, MAT_PIPE_VOLUME_MATERIAL, MAT_PIPE_VOLUME_OCCUPANCY);
|
||||
|
||||
GPUMaterial *gpumat = GPU_material_from_nodetree(nullptr,
|
||||
nullptr,
|
||||
|
||||
@@ -130,6 +130,7 @@ enum eShaderType {
|
||||
SURFEL_RAY,
|
||||
|
||||
VOLUME_INTEGRATION,
|
||||
VOLUME_OCCUPANCY_CONVERT,
|
||||
VOLUME_RESOLVE,
|
||||
VOLUME_SCATTER,
|
||||
VOLUME_SCATTER_WITH_LIGHTS,
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
#include "DNA_gpencil_legacy_types.h"
|
||||
#include "DNA_modifier_types.h"
|
||||
#include "DNA_particle_types.h"
|
||||
#include "DNA_pointcloud_types.h"
|
||||
#include "DNA_volume_types.h"
|
||||
|
||||
#include "draw_common.hh"
|
||||
#include "draw_sculpt.hh"
|
||||
@@ -153,9 +155,10 @@ void SyncModule::sync_mesh(Object *ob,
|
||||
Material &material = material_array.materials[i];
|
||||
GPUMaterial *gpu_material = material_array.gpu_materials[i];
|
||||
|
||||
if (material.volume.gpumat && i == 0) {
|
||||
if (material.is_volume && (i == 0)) {
|
||||
/* Only support single volume material for now. */
|
||||
inst_.volume.sync_object(ob, ob_handle, res_handle, &material.volume);
|
||||
geometry_call(material.volume_occupancy.sub_pass, geom, res_handle);
|
||||
inst_.pipelines.volume.material_call(material.volume_material, ob, res_handle);
|
||||
/* Do not render surface if we are rendering a volume object
|
||||
* and do not have a surface closure. */
|
||||
if (gpu_material && !GPU_material_has_surface_output(gpu_material)) {
|
||||
@@ -278,7 +281,7 @@ void SyncModule::sync_point_cloud(Object *ob,
|
||||
ResourceHandle res_handle,
|
||||
const ObjectRef & /*ob_ref*/)
|
||||
{
|
||||
int material_slot = 1;
|
||||
const int material_slot = POINTCLOUD_MATERIAL_NR;
|
||||
|
||||
bool has_motion = inst_.velocity.step_object_sync(
|
||||
ob, ob_handle.object_key, res_handle, ob_handle.recalc);
|
||||
@@ -312,6 +315,30 @@ void SyncModule::sync_point_cloud(Object *ob,
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Volume Objects
|
||||
* \{ */
|
||||
|
||||
void SyncModule::sync_volume(Object *ob, ObjectHandle & /*ob_handle*/, ResourceHandle res_handle)
|
||||
{
|
||||
const int material_slot = VOLUME_MATERIAL_NR;
|
||||
|
||||
/* Motion is not supported on volumes yet. */
|
||||
const bool has_motion = false;
|
||||
|
||||
Material &material = inst_.materials.material_get(
|
||||
ob, has_motion, material_slot - 1, MAT_GEOM_VOLUME);
|
||||
|
||||
/* Use bounding box tag empty spaces. */
|
||||
GPUBatch *geom = DRW_cache_cube_get();
|
||||
|
||||
geometry_call(material.volume_occupancy.sub_pass, geom, res_handle);
|
||||
|
||||
inst_.pipelines.volume.material_call(material.volume_material, ob, res_handle);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name GPencil
|
||||
* \{ */
|
||||
|
||||
@@ -156,6 +156,7 @@ class SyncModule {
|
||||
ObjectHandle &ob_handle,
|
||||
ResourceHandle res_handle,
|
||||
const ObjectRef &ob_ref);
|
||||
void sync_volume(Object *ob, ObjectHandle &ob_handle, ResourceHandle res_handle);
|
||||
void sync_gpencil(Object *ob, ObjectHandle &ob_handle, ResourceHandle res_handle);
|
||||
void sync_curves(Object *ob,
|
||||
ObjectHandle &ob_handle,
|
||||
|
||||
@@ -22,54 +22,9 @@
|
||||
|
||||
namespace blender::eevee {
|
||||
|
||||
VolumeModule::GridAABB::GridAABB(Object *ob, const Camera &camera, const VolumesInfoData &data)
|
||||
{
|
||||
/* Returns the unified volume grid cell corner of a world space coordinate. */
|
||||
auto to_global_grid_coords = [&](float3 wP) -> int3 {
|
||||
const float4x4 &view_matrix = camera.data_get().viewmat;
|
||||
const float4x4 &projection_matrix = camera.data_get().winmat;
|
||||
|
||||
float3 ndc_coords = math::project_point(projection_matrix * view_matrix, wP);
|
||||
ndc_coords = (ndc_coords * 0.5f) + float3(0.5f);
|
||||
|
||||
float3 grid_coords = screen_to_volume(projection_matrix,
|
||||
data.depth_near,
|
||||
data.depth_far,
|
||||
data.depth_distribution,
|
||||
data.coord_scale,
|
||||
ndc_coords);
|
||||
/* Round to nearest grid corner. */
|
||||
return int3(grid_coords * float3(data.tex_size) + 0.5);
|
||||
};
|
||||
|
||||
const BoundBox bbox = *BKE_object_boundbox_get(ob);
|
||||
min = int3(INT32_MAX);
|
||||
max = int3(INT32_MIN);
|
||||
|
||||
for (float3 l_corner : bbox.vec) {
|
||||
float3 w_corner = math::transform_point(float4x4(ob->object_to_world), l_corner);
|
||||
/* Note that this returns the nearest cell corner coordinate.
|
||||
* So sub-froxel AABB will effectively return the same coordinate
|
||||
* for each corner (making it empty and skipped) unless it
|
||||
* cover the center of the froxel. */
|
||||
math::min_max(to_global_grid_coords(w_corner), min, max);
|
||||
}
|
||||
}
|
||||
|
||||
bool VolumeModule::GridAABB::is_empty() const
|
||||
{
|
||||
return math::reduce_min(max - min) <= 0;
|
||||
}
|
||||
|
||||
VolumeModule::GridAABB VolumeModule::GridAABB::intersect(const GridAABB &other) const
|
||||
{
|
||||
return {math::min(this->max, other.max), math::max(this->min, other.min)};
|
||||
}
|
||||
|
||||
void VolumeModule::init()
|
||||
{
|
||||
enabled_ = false;
|
||||
subpass_aabbs_.clear();
|
||||
|
||||
const Scene *scene_eval = inst_.scene;
|
||||
|
||||
@@ -143,73 +98,12 @@ void VolumeModule::begin_sync()
|
||||
enabled_ = inst_.world.has_volume();
|
||||
}
|
||||
|
||||
void VolumeModule::sync_object(Object *ob,
|
||||
ObjectHandle & /*ob_handle*/,
|
||||
ResourceHandle res_handle,
|
||||
MaterialPass *material_pass /*=nullptr*/)
|
||||
{
|
||||
float3 size = math::to_scale(float4x4(ob->object_to_world));
|
||||
/* Check if any of the axes have 0 length. (see #69070) */
|
||||
const float epsilon = 1e-8f;
|
||||
if (size.x < epsilon || size.y < epsilon || size.z < epsilon) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (material_pass == nullptr) {
|
||||
Material material = inst_.materials.material_get(
|
||||
ob, false, VOLUME_MATERIAL_NR, MAT_GEOM_VOLUME_OBJECT);
|
||||
material_pass = &material.volume;
|
||||
}
|
||||
|
||||
/* If shader failed to compile or is currently compiling. */
|
||||
if (material_pass->gpumat == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
GPUShader *shader = GPU_material_get_shader(material_pass->gpumat);
|
||||
if (shader == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
GridAABB object_aabb(ob, inst_.camera, data_);
|
||||
/* Remember that these are cells corners, so this extents to `tex_size`. */
|
||||
GridAABB view_aabb(int3(0), data_.tex_size);
|
||||
if (object_aabb.intersect(view_aabb).is_empty()) {
|
||||
/* Skip invisible object with respect to raster grid and bounds density. */
|
||||
return;
|
||||
}
|
||||
|
||||
PassMain::Sub *object_pass = volume_sub_pass(
|
||||
*material_pass->sub_pass, inst_.scene, ob, material_pass->gpumat);
|
||||
if (object_pass) {
|
||||
enabled_ = true;
|
||||
|
||||
/* Add a barrier at the start of a subpass or when 2 volumes overlaps. */
|
||||
if (!subpass_aabbs_.contains_as(shader) == false) {
|
||||
object_pass->barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
|
||||
subpass_aabbs_.add(shader, {object_aabb});
|
||||
}
|
||||
else {
|
||||
Vector<GridAABB> &aabbs = subpass_aabbs_.lookup(shader);
|
||||
for (GridAABB &other_aabb : aabbs) {
|
||||
if (object_aabb.intersect(other_aabb).is_empty() == false) {
|
||||
object_pass->barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
|
||||
aabbs.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
aabbs.append(object_aabb);
|
||||
}
|
||||
|
||||
object_pass->push_constant("drw_ResourceID", int(res_handle.resource_index()));
|
||||
object_pass->push_constant("grid_coords_min", object_aabb.min);
|
||||
object_pass->dispatch(math::divide_ceil(object_aabb.extent(), int3(VOLUME_GROUP_SIZE)));
|
||||
}
|
||||
}
|
||||
|
||||
void VolumeModule::end_sync()
|
||||
{
|
||||
enabled_ = enabled_ || inst_.pipelines.volume.is_enabled();
|
||||
|
||||
if (!enabled_) {
|
||||
occupancy_tx_.free();
|
||||
prop_scattering_tx_.free();
|
||||
prop_extinction_tx_.free();
|
||||
prop_emission_tx_.free();
|
||||
@@ -233,6 +127,38 @@ void VolumeModule::end_sync()
|
||||
prop_emission_tx_.ensure_3d(GPU_R11F_G11F_B10F, data_.tex_size, usage);
|
||||
prop_phase_tx_.ensure_3d(GPU_RG16F, data_.tex_size, usage);
|
||||
|
||||
int occupancy_layers = divide_ceil_u(data_.tex_size.z, 32u);
|
||||
eGPUTextureUsage occupancy_usage = GPU_TEXTURE_USAGE_SHADER_READ |
|
||||
GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_ATOMIC;
|
||||
occupancy_tx_.ensure_3d(GPU_R32UI, int3(data_.tex_size.xy(), occupancy_layers), occupancy_usage);
|
||||
|
||||
{
|
||||
eGPUTextureUsage hit_count_usage = GPU_TEXTURE_USAGE_SHADER_READ |
|
||||
GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_ATOMIC;
|
||||
eGPUTextureUsage hit_depth_usage = GPU_TEXTURE_USAGE_SHADER_READ |
|
||||
GPU_TEXTURE_USAGE_SHADER_WRITE;
|
||||
int2 hit_list_size = int2(1);
|
||||
int hit_list_layer = 1;
|
||||
if (inst_.pipelines.volume.use_hit_list()) {
|
||||
hit_list_layer = clamp_i(inst_.scene->eevee.volumetric_ray_depth, 1, 16);
|
||||
hit_list_size = data_.tex_size.xy();
|
||||
}
|
||||
hit_depth_tx_.ensure_3d(GPU_R32F, int3(hit_list_size, hit_list_layer), hit_depth_usage);
|
||||
if (hit_count_tx_.ensure_2d(GPU_R32UI, hit_list_size, hit_count_usage)) {
|
||||
hit_count_tx_.clear(uint4(0u));
|
||||
}
|
||||
}
|
||||
|
||||
if (GPU_backend_get_type() == GPU_BACKEND_METAL) {
|
||||
/* Metal requires a dummy attachment. */
|
||||
occupancy_fb_.ensure(GPU_ATTACHMENT_NONE,
|
||||
GPU_ATTACHMENT_TEXTURE_LAYER(prop_extinction_tx_, 0));
|
||||
}
|
||||
else {
|
||||
/* Empty framebuffer. */
|
||||
occupancy_fb_.ensure(data_.tex_size.xy());
|
||||
}
|
||||
|
||||
if (!inst_.pipelines.world_volume.is_valid()) {
|
||||
prop_scattering_tx_.clear(float4(0.0f));
|
||||
prop_extinction_tx_.clear(float4(0.0f));
|
||||
@@ -264,6 +190,7 @@ void VolumeModule::end_sync()
|
||||
scatter_ps_.bind_image("in_phase_img", &prop_phase_tx_);
|
||||
scatter_ps_.bind_image("out_scattering_img", &scatter_tx_);
|
||||
scatter_ps_.bind_image("out_extinction_img", &extinction_tx_);
|
||||
scatter_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
|
||||
/* Sync with the property pass. */
|
||||
scatter_ps_.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS | GPU_BARRIER_TEXTURE_FETCH);
|
||||
scatter_ps_.dispatch(math::divide_ceil(data_.tex_size, int3(VOLUME_GROUP_SIZE)));
|
||||
@@ -287,6 +214,7 @@ void VolumeModule::end_sync()
|
||||
bind_resources(resolve_ps_);
|
||||
resolve_ps_.bind_texture("depth_tx", &inst_.render_buffers.depth_tx);
|
||||
resolve_ps_.bind_image(RBUFS_COLOR_SLOT, &inst_.render_buffers.rp_color_tx);
|
||||
resolve_ps_.bind_image(RBUFS_VALUE_SLOT, &inst_.render_buffers.rp_value_tx);
|
||||
/* Sync with the integration pass. */
|
||||
resolve_ps_.barrier(GPU_BARRIER_TEXTURE_FETCH);
|
||||
resolve_ps_.draw_procedural(GPU_PRIM_TRIS, 1, 3);
|
||||
@@ -298,8 +226,26 @@ void VolumeModule::draw_prepass(View &view)
|
||||
return;
|
||||
}
|
||||
|
||||
DRW_stats_group_start("Volumes");
|
||||
inst_.pipelines.world_volume.render(view);
|
||||
inst_.pipelines.volume.render(view);
|
||||
|
||||
float left, right, bottom, top, near, far;
|
||||
float4x4 winmat = view.winmat();
|
||||
projmat_dimensions(winmat.ptr(), &left, &right, &bottom, &top, &near, &far);
|
||||
|
||||
float4x4 winmat_infinite = view.is_persp() ?
|
||||
math::projection::perspective_infinite(
|
||||
left, right, bottom, top, near) :
|
||||
math::projection::orthographic_infinite(left, right, bottom, top);
|
||||
|
||||
View volume_view = {"Volume View"};
|
||||
volume_view.sync(view.viewmat(), winmat_infinite);
|
||||
|
||||
if (inst_.pipelines.volume.is_enabled()) {
|
||||
occupancy_fb_.bind();
|
||||
inst_.pipelines.volume.render(volume_view, occupancy_tx_);
|
||||
}
|
||||
DRW_stats_group_end();
|
||||
}
|
||||
|
||||
void VolumeModule::draw_compute(View &view)
|
||||
|
||||
@@ -40,8 +40,11 @@
|
||||
namespace blender::eevee {
|
||||
|
||||
class Instance;
|
||||
class VolumePipeline;
|
||||
|
||||
class VolumeModule {
|
||||
friend VolumePipeline;
|
||||
|
||||
private:
|
||||
Instance &inst_;
|
||||
|
||||
@@ -49,6 +52,22 @@ class VolumeModule {
|
||||
|
||||
VolumesInfoData &data_;
|
||||
|
||||
/**
|
||||
* Occupancy map that allows to fill froxels that are inside the geometry.
|
||||
* It is filled during a pre-pass using atomic operations.
|
||||
* Using a 3D bitfield, we only allocate one bit per froxel.
|
||||
*/
|
||||
Texture occupancy_tx_ = {"occupancy_tx"};
|
||||
/**
|
||||
* List of surface hit for correct occupancy determination.
|
||||
* One texture holds the number of hit count and the other the depth and
|
||||
* the facing of each hit.
|
||||
*/
|
||||
Texture hit_count_tx_ = {"hit_count_tx"};
|
||||
Texture hit_depth_tx_ = {"hit_depth_tx"};
|
||||
/** Empty framebuffer for occupancy pass. */
|
||||
Framebuffer occupancy_fb_ = {"occupancy_fb"};
|
||||
|
||||
/* Material Parameters */
|
||||
Texture prop_scattering_tx_;
|
||||
Texture prop_extinction_tx_;
|
||||
@@ -76,34 +95,6 @@ class VolumeModule {
|
||||
Texture dummy_scatter_tx_;
|
||||
Texture dummy_transmit_tx_;
|
||||
|
||||
/* Axis aligned bounding box in the volume grid.
|
||||
* Used for frustum culling and volumes overlapping detection. */
|
||||
struct GridAABB {
|
||||
/* Represent min and max grid corners covered by a volume.
|
||||
* So a volume covering the first froxel will have min={0,0,0} and max={1,1,1}.
|
||||
* A volume with min={0,0,0} and max={0,0,0} covers nothing. */
|
||||
int3 min, max;
|
||||
|
||||
GridAABB(int3 min_, int3 max_) : min(min_), max(max_){};
|
||||
GridAABB(Object *ob, const Camera &camera, const VolumesInfoData &data);
|
||||
|
||||
/** Returns the intersection between this AABB and the \a other AABB. */
|
||||
GridAABB intersect(const GridAABB &other) const;
|
||||
|
||||
/** Returns true if volume covers no froxel. */
|
||||
bool is_empty() const;
|
||||
|
||||
/** Returns the extent of the volume. */
|
||||
int3 extent() const
|
||||
{
|
||||
return max - min;
|
||||
}
|
||||
};
|
||||
/* Stores a vector of volume AABBs for each material pass,
|
||||
* so we can detect overlapping volumes and place GPU barriers where needed
|
||||
* (Only stores the AABBs for the volumes rendered since the last barrier). */
|
||||
Map<GPUShader *, Vector<GridAABB>> subpass_aabbs_;
|
||||
|
||||
public:
|
||||
VolumeModule(Instance &inst, VolumesInfoData &data) : inst_(inst), data_(data)
|
||||
{
|
||||
@@ -127,6 +118,14 @@ class VolumeModule {
|
||||
pass.bind_image(VOLUME_PROP_EXTINCTION_IMG_SLOT, &prop_extinction_tx_);
|
||||
pass.bind_image(VOLUME_PROP_EMISSION_IMG_SLOT, &prop_emission_tx_);
|
||||
pass.bind_image(VOLUME_PROP_PHASE_IMG_SLOT, &prop_phase_tx_);
|
||||
pass.bind_image(VOLUME_OCCUPANCY_SLOT, &occupancy_tx_);
|
||||
}
|
||||
|
||||
template<typename PassType> void bind_occupancy_buffers(PassType &pass)
|
||||
{
|
||||
pass.bind_image(VOLUME_OCCUPANCY_SLOT, &occupancy_tx_);
|
||||
pass.bind_image(VOLUME_HIT_DEPTH_SLOT, &hit_depth_tx_);
|
||||
pass.bind_image(VOLUME_HIT_COUNT_SLOT, &hit_count_tx_);
|
||||
}
|
||||
|
||||
bool needs_shadow_tagging()
|
||||
@@ -144,10 +143,8 @@ class VolumeModule {
|
||||
void begin_sync();
|
||||
|
||||
void sync_world();
|
||||
void sync_object(Object *ob,
|
||||
ObjectHandle &ob_handle,
|
||||
ResourceHandle res_handle,
|
||||
MaterialPass *material_pass = nullptr);
|
||||
|
||||
void material_call(MaterialPass &material_pass, Object *ob, ResourceHandle res_handle);
|
||||
|
||||
void end_sync();
|
||||
|
||||
|
||||
@@ -94,7 +94,8 @@ void World::sync()
|
||||
bNodeTree *ntree;
|
||||
world_and_ntree_get(bl_world, ntree);
|
||||
|
||||
GPUMaterial *volume_gpumat = inst_.shaders.world_shader_get(bl_world, ntree, MAT_PIPE_VOLUME);
|
||||
GPUMaterial *volume_gpumat = inst_.shaders.world_shader_get(
|
||||
bl_world, ntree, MAT_PIPE_VOLUME_MATERIAL);
|
||||
inst_.pipelines.world_volume.sync(volume_gpumat);
|
||||
|
||||
if (inst_.lookdev.sync_world()) {
|
||||
@@ -128,7 +129,7 @@ bool World::has_volume()
|
||||
bNodeTree *ntree;
|
||||
world_and_ntree_get(bl_world, ntree);
|
||||
|
||||
GPUMaterial *gpumat = inst_.shaders.world_shader_get(bl_world, ntree, MAT_PIPE_VOLUME);
|
||||
GPUMaterial *gpumat = inst_.shaders.world_shader_get(bl_world, ntree, MAT_PIPE_VOLUME_MATERIAL);
|
||||
return GPU_material_has_volume_output(gpumat);
|
||||
}
|
||||
|
||||
|
||||
@@ -230,7 +230,7 @@ float attr_load_float(samplerBuffer cd_buf)
|
||||
|
||||
/** \} */
|
||||
|
||||
#elif defined(MAT_GEOM_VOLUME_OBJECT) || defined(MAT_GEOM_VOLUME_WORLD)
|
||||
#elif defined(MAT_GEOM_VOLUME) || defined(MAT_GEOM_VOLUME_OBJECT) || defined(MAT_GEOM_VOLUME_WORLD)
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Volume
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma BLENDER_REQUIRE(draw_model_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(eevee_surf_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(common_pointcloud_lib.glsl)
|
||||
|
||||
void main()
|
||||
{
|
||||
DRW_VIEW_FROM_RESOURCE_ID;
|
||||
|
||||
init_interface();
|
||||
|
||||
/* TODO(fclem): Find a better way? This is reverting what draw_resource_finalize does. */
|
||||
vec3 size = safe_rcp(OrcoTexCoFactors[1].xyz * 2.0); /* Box half-extent. */
|
||||
vec3 loc = size + (OrcoTexCoFactors[0].xyz / -OrcoTexCoFactors[1].xyz); /* Box center. */
|
||||
|
||||
/* Use bounding box geometry for now. */
|
||||
vec3 lP = loc + pos * size;
|
||||
interp.P = drw_point_object_to_world(lP);
|
||||
|
||||
gl_Position = drw_point_world_to_homogenous(interp.P);
|
||||
}
|
||||
@@ -312,7 +312,7 @@ void brdf_f82_tint_lut(vec3 F0,
|
||||
#ifdef EEVEE_UTILITY_TX
|
||||
vec3 split_sum = utility_tx_sample_lut(utility_tx, cos_theta, roughness, UTIL_BSDF_LAYER).rgb;
|
||||
#else
|
||||
vec3 split_sum = vec2(1.0, 0.0, 0.0);
|
||||
vec3 split_sum = vec3(1.0, 0.0, 0.0);
|
||||
#endif
|
||||
|
||||
reflectance = do_multiscatter ? F_brdf_multi_scatter(F0, vec3(1.0), split_sum.xy) :
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/**
|
||||
* Convert hit list to occupancy bitfield for the material pass.
|
||||
*/
|
||||
|
||||
#pragma BLENDER_REQUIRE(eevee_occupancy_lib.glsl)
|
||||
|
||||
bool is_front_face_hit(float stored_hit_depth)
|
||||
{
|
||||
return stored_hit_depth < 0.0;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
float hit_depths[VOLUME_HIT_DEPTH_MAX];
|
||||
float hit_ordered[VOLUME_HIT_DEPTH_MAX];
|
||||
int hit_index[VOLUME_HIT_DEPTH_MAX];
|
||||
|
||||
ivec2 texel = ivec2(gl_FragCoord.xy);
|
||||
|
||||
int hit_count = int(imageLoad(hit_count_img, texel).x);
|
||||
hit_count = min(hit_count, VOLUME_HIT_DEPTH_MAX);
|
||||
|
||||
if (hit_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Clear the texture for next layer / frame. */
|
||||
imageStore(hit_count_img, texel, uvec4(0));
|
||||
|
||||
for (int i = 0; i < hit_count; i++) {
|
||||
hit_depths[i] = imageLoad(hit_depth_img, ivec3(texel, i)).r;
|
||||
}
|
||||
|
||||
for (int i = 0; i < hit_count; i++) {
|
||||
hit_index[i] = 0;
|
||||
for (int j = 0; j < hit_count; j++) {
|
||||
hit_index[i] += int(abs(hit_depths[i]) > abs(hit_depths[j]));
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < hit_count; i++) {
|
||||
hit_ordered[hit_index[i]] = hit_depths[i];
|
||||
}
|
||||
|
||||
#if 0 /* Debug. Need to adjust the qualifier of the texture adjusted. */
|
||||
for (int i = 0; i < hit_count; i++) {
|
||||
imageStore(hit_depth_img, ivec3(texel, i), vec4(hit_ordered[i]));
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Convert to occupancy bits. */
|
||||
OccupancyBits occupancy = occupancy_new();
|
||||
/* True if last interface was a volume entry. */
|
||||
/* Initialized to front facing if first hit is a backface to support camera inside the volume. */
|
||||
bool last_frontfacing = !is_front_face_hit(hit_ordered[0]);
|
||||
/* Bit index of the last interface. */
|
||||
int last_bit = 0;
|
||||
for (int i = 0; i < hit_count; i++) {
|
||||
bool frontfacing = is_front_face_hit(hit_ordered[i]);
|
||||
if (last_frontfacing == frontfacing) {
|
||||
/* Same facing, do not treat as a volume interface. */
|
||||
continue;
|
||||
}
|
||||
last_frontfacing = frontfacing;
|
||||
|
||||
int occupancy_bit_n = occupancy_bit_index_from_depth(abs(hit_ordered[i]),
|
||||
uniform_buf.volumes.tex_size.z);
|
||||
if (last_bit == occupancy_bit_n) {
|
||||
/* We did not cross a new voxel center. Do nothing. */
|
||||
continue;
|
||||
}
|
||||
int bit_start = last_bit;
|
||||
int bit_count = occupancy_bit_n - last_bit;
|
||||
last_bit = occupancy_bit_n;
|
||||
|
||||
if (last_frontfacing == false) {
|
||||
/* OccupancyBits is cleared by default. No need to do anything for empty regions. */
|
||||
continue;
|
||||
}
|
||||
occupancy = occupancy_set_bits_high(occupancy, bit_start, bit_count);
|
||||
}
|
||||
|
||||
/* Write the occupancy bits */
|
||||
for (int i = 0; i < imageSize(occupancy_img).z; i++) {
|
||||
if (occupancy.bits[i] != 0u) {
|
||||
/* Note: Doesn't have to be atomic but we need to blend with other method. */
|
||||
imageAtomicOr(occupancy_img, ivec3(texel, i), occupancy.bits[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
|
||||
|
||||
struct OccupancyBits {
|
||||
uint bits[8];
|
||||
};
|
||||
|
||||
int occupancy_bit_index_from_depth(float depth, int bit_count)
|
||||
{
|
||||
/* We want the occupancy at the center of each range a bit covers.
|
||||
* So we round the depth to the nearest bit. */
|
||||
return int(depth * float(bit_count) + 0.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Example with for 16bits per layer and 2 layers.
|
||||
* 0 Layer0 15 16 Layer1 31 < Bits index from LSB to MSB (left to right)
|
||||
* |--------------| |--------------|
|
||||
* 0000000001111111 1111111111111111 < Surf0
|
||||
* 0000000000000000 0000001111111111 < Surf1
|
||||
* 0000000001111111 1111110000000000 < Result occupancy at each froxel depths
|
||||
* \a depth in [0..1] range.
|
||||
* \a bit_count in [1..256] range.
|
||||
*/
|
||||
OccupancyBits occupancy_from_depth(float depth, int bit_count)
|
||||
{
|
||||
/* We want the occupancy at the center of each range a bit covers.
|
||||
* So we round the depth to the nearest bit. */
|
||||
int depth_bit_index = occupancy_bit_index_from_depth(depth, bit_count);
|
||||
|
||||
OccupancyBits occupancy;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int shift = clamp(depth_bit_index - i * 32, 0, 32);
|
||||
/* Cannot bit shift more than 31 positions. */
|
||||
occupancy.bits[i] = (shift == 32) ? 0x0u : (~0x0u << uint(shift));
|
||||
}
|
||||
return occupancy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an empty structure, cleared to 0.
|
||||
*/
|
||||
OccupancyBits occupancy_new()
|
||||
{
|
||||
OccupancyBits occupancy;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
occupancy.bits[i] = 0x0u;
|
||||
}
|
||||
return occupancy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Example with for 16bits per layer and 2 layers.
|
||||
* 0 Layer0 15 16 Layer1 31 < Bits index from LSB to MSB (left to right)
|
||||
* |--------------| |--------------|
|
||||
* 0000000001000010 0001000000000000 < Surf entry points
|
||||
* 0000000000000000 0100001000000000 < Surf exit points
|
||||
* 0000000001111111 1001110000000000 < Result occupancy at each froxel depths after resolve
|
||||
* \a depth in [0..1] range.
|
||||
* \a bit_count in [1..256] range.
|
||||
*/
|
||||
OccupancyBits occupancy_bit_from_depth(float depth, int bit_count)
|
||||
{
|
||||
/* We want the occupancy at the center of each range a bit covers.
|
||||
* So we round the depth to the nearest bit. */
|
||||
int depth_bit_index = occupancy_bit_index_from_depth(depth, bit_count);
|
||||
|
||||
OccupancyBits occupancy;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int shift = depth_bit_index - i * 32;
|
||||
/* Cannot bit shift more than 31 positions. */
|
||||
occupancy.bits[i] = (shift >= 0 && shift < 32) ? (0x1u << uint(shift)) : 0x0u;
|
||||
}
|
||||
return occupancy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as binary OR but for the whole OccupancyBits structure.
|
||||
*/
|
||||
OccupancyBits occupancy_or(OccupancyBits a, OccupancyBits b)
|
||||
{
|
||||
OccupancyBits occupancy;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
occupancy.bits[i] = a.bits[i] | b.bits[i];
|
||||
}
|
||||
return occupancy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a series of bits high inside the given OccupancyBits.
|
||||
*/
|
||||
OccupancyBits occupancy_set_bits_high(OccupancyBits occupancy, int bit_start, int bit_count)
|
||||
{
|
||||
for (int i = 0; i < bit_count; i++) {
|
||||
int bit = bit_start + i;
|
||||
occupancy.bits[bit / 32] |= 1u << uint(bit % 32);
|
||||
}
|
||||
return occupancy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as findLSB but for the whole OccupancyBits structure.
|
||||
*/
|
||||
int occupancy_find_lsb(OccupancyBits occupancy)
|
||||
{
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (occupancy.bits[i] != 0) {
|
||||
return findLSB(occupancy.bits[i]) + i * 32;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the first four occupancy words to a uvec4.
|
||||
*/
|
||||
uvec4 occupancy_to_uint4(OccupancyBits occupancy)
|
||||
{
|
||||
return uvec4(occupancy.bits[0], occupancy.bits[1], occupancy.bits[2], occupancy.bits[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* From a entry and exit occupancy tuple, returns if a specific bit is inside the volume.
|
||||
*/
|
||||
bool occupancy_bit_resolve(OccupancyBits entry, OccupancyBits exit, int bit_n, int bit_count)
|
||||
{
|
||||
int first_exit = occupancy_find_lsb(exit);
|
||||
int first_entry = occupancy_find_lsb(entry);
|
||||
first_exit = (first_exit == -1) ? 99999 : first_exit;
|
||||
/* Check if the first surface is an exit. If it is, initialize as inside the volume. */
|
||||
bool inside_volume = first_exit < first_entry;
|
||||
for (int j = 0; j <= bit_n / 32; j++) {
|
||||
uint entry_word = entry.bits[j];
|
||||
uint exit_word = exit.bits[j];
|
||||
/* TODO(fclem): Could use fewer iteration using findLSB and/or other intrinsics. */
|
||||
for (uint i = 0; i < 32; i++) {
|
||||
if (flag_test(exit_word, 1u << i) && flag_test(entry_word, 1u << i)) {
|
||||
/* Do nothing. */
|
||||
}
|
||||
else {
|
||||
if (flag_test(exit_word, 1u << i)) {
|
||||
inside_volume = false;
|
||||
}
|
||||
if (flag_test(entry_word, 1u << i)) {
|
||||
inside_volume = true;
|
||||
}
|
||||
}
|
||||
if (i + j * 32 == uint(bit_n)) {
|
||||
return inside_volume;
|
||||
}
|
||||
}
|
||||
}
|
||||
return inside_volume;
|
||||
}
|
||||
|
||||
/**
|
||||
* From a entry and exit occupancy tuple, returns the full occupancy map.
|
||||
*/
|
||||
OccupancyBits occupancy_resolve(OccupancyBits entry, OccupancyBits exit, int bit_count)
|
||||
{
|
||||
OccupancyBits occupancy;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
for (int i = 0; i < 32; i++) {
|
||||
bool test = false;
|
||||
if (i < bit_count - j * 32) {
|
||||
test = occupancy_bit_resolve(entry, exit, i + j * 32, bit_count);
|
||||
}
|
||||
set_flag_from_test(occupancy.bits[j], test, 1u << uint(i));
|
||||
}
|
||||
}
|
||||
return occupancy;
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/* Directive for resetting the line numbering so the failing tests lines can be printed.
|
||||
* This conflict with the shader compiler error logging scheme.
|
||||
* Comment out for correct compilation error line. */
|
||||
// #line 9
|
||||
|
||||
#pragma BLENDER_REQUIRE(eevee_occupancy_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(gpu_shader_test_lib.glsl)
|
||||
|
||||
#define TEST(a, b) if (true)
|
||||
|
||||
void main()
|
||||
{
|
||||
TEST(eevee_occupancy, Occupancy)
|
||||
{
|
||||
OccupancyBits occup;
|
||||
|
||||
/* occupancy_from_depth */
|
||||
occup = occupancy_from_depth(0.1, 1);
|
||||
EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0xFFFFFFFFu, ~0u, ~0u, ~0u));
|
||||
|
||||
occup = occupancy_from_depth(0.6, 1);
|
||||
EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0xFFFFFFFEu, ~0u, ~0u, ~0u));
|
||||
|
||||
occup = occupancy_from_depth(0.5, 32);
|
||||
EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0xFFFF0000u, ~0u, ~0u, ~0u));
|
||||
|
||||
occup = occupancy_from_depth(0.5, 64);
|
||||
EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0u, ~0u, ~0u, ~0u));
|
||||
|
||||
occup = occupancy_from_depth(0.5, 128);
|
||||
EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0u, 0u, ~0u, ~0u));
|
||||
|
||||
occup = occupancy_from_depth(33.0 / 64.0, 64);
|
||||
EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0u, 0xFFFFFFFEu, ~0u, ~0u));
|
||||
|
||||
/* occupancy_bit_from_depth */
|
||||
occup = occupancy_bit_from_depth(0.1, 1);
|
||||
EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0x00000001u, 0u, 0u, 0u));
|
||||
|
||||
occup = occupancy_bit_from_depth(0.6, 1);
|
||||
EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0x00000002u, 0u, 0u, 0u));
|
||||
|
||||
occup = occupancy_bit_from_depth(0.5, 32);
|
||||
EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0x00010000u, 0u, 0u, 0u));
|
||||
|
||||
occup = occupancy_bit_from_depth(0.5, 64);
|
||||
EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0x00000000u, 0x00000001u, 0u, 0u));
|
||||
|
||||
occup = occupancy_bit_from_depth(0.5, 128);
|
||||
EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0x00000000u, 0x00000000u, 0x00000001u, 0u));
|
||||
|
||||
occup = occupancy_bit_from_depth(33.0 / 64.0, 64);
|
||||
EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0x00000000u, 0x00000002u, 0u, 0u));
|
||||
|
||||
/* Test composing occupancy an the expected result. */
|
||||
/* Start empty. */
|
||||
OccupancyBits entry = occupancy_new();
|
||||
OccupancyBits exit = occupancy_new();
|
||||
entry = occupancy_or(entry, occupancy_bit_from_depth(1.0 / 32.0, 32));
|
||||
/* Second entry at the same depth. Should not change anything. */
|
||||
entry = occupancy_or(entry, occupancy_bit_from_depth(1.1 / 32.0, 32));
|
||||
/* Exit 2 bits later.*/
|
||||
exit = occupancy_or(exit, occupancy_bit_from_depth(3.0 / 32.0, 32));
|
||||
/* Second exit. Should not change anything. */
|
||||
exit = occupancy_or(exit, occupancy_bit_from_depth(5.0 / 32.0, 32));
|
||||
/* Third entry is valid. */
|
||||
entry = occupancy_or(entry, occupancy_bit_from_depth(7.0 / 32.0, 32));
|
||||
/* Third exit is valid. */
|
||||
exit = occupancy_or(exit, occupancy_bit_from_depth(9.0 / 32.0, 32));
|
||||
/* Fourth entry is valid. */
|
||||
entry = occupancy_or(entry, occupancy_bit_from_depth(11.0 / 32.0, 32));
|
||||
/* Fourth exit on the same depth. Cancels the occupancy. */
|
||||
exit = occupancy_or(exit, occupancy_bit_from_depth(11.0 / 32.0, 32));
|
||||
EXPECT_EQ(entry.bits[0], 2178u); /* 1000 1000 0010 */
|
||||
EXPECT_EQ(exit.bits[0], 2600u); /* 1010 0010 1000 */
|
||||
|
||||
occup = occupancy_resolve(entry, exit, 32);
|
||||
EXPECT_EQ(occup.bits[0], 390u); /* 0001 1000 0110 */
|
||||
|
||||
/* Start empty. */
|
||||
entry = occupancy_new();
|
||||
exit = occupancy_new();
|
||||
/* First exit. Anything prior should be considered in volume. */
|
||||
exit = occupancy_or(exit, occupancy_bit_from_depth(33.0 / 44.0, 44));
|
||||
/* First entry. */
|
||||
entry = occupancy_or(entry, occupancy_bit_from_depth(36.0 / 44.0, 44));
|
||||
/* Second exit. Should not change anything. */
|
||||
exit = occupancy_or(exit, occupancy_bit_from_depth(40.0 / 44.0, 44));
|
||||
/* 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0000 */
|
||||
EXPECT_EQ(occupancy_to_uint4(entry), uvec4(0x00000000u, 0x010u, 0u, 0u));
|
||||
/* 0001 0000 0010 0000 0000 0000 0000 0000 0000 0000 0000 */
|
||||
EXPECT_EQ(occupancy_to_uint4(exit), uvec4(0x00000000u, 0x102u, 0u, 0u));
|
||||
|
||||
EXPECT_EQ(occupancy_find_lsb(entry), 36);
|
||||
EXPECT_EQ(occupancy_find_lsb(exit), 33);
|
||||
|
||||
occup = occupancy_resolve(entry, exit, 44);
|
||||
/* 0000 1111 0001 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0xFFFFFFFFu, 0x0F1u, 0u, 0u));
|
||||
|
||||
occup = occupancy_new();
|
||||
occup = occupancy_set_bits_high(occup, 16, 32);
|
||||
occup = occupancy_set_bits_high(occup, 80, 16);
|
||||
EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0xFFFF0000u, 0x0000FFFFu, 0xFFFF0000u, 0u));
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ vec3 barycentric_distances_get()
|
||||
dists.x = rate_of_change * (1.0 - gpu_BaryCoord.x);
|
||||
dists.y = rate_of_change * (1.0 - gpu_BaryCoord.y);
|
||||
dists.z = rate_of_change * (1.0 - gpu_BaryCoord.z);
|
||||
# elif
|
||||
# else
|
||||
/* NOTE: No need to undo perspective divide since it has not been applied. */
|
||||
vec3 pos0 = (ProjectionMatrixInverse * gpu_position_at_vertex(0)).xyz;
|
||||
vec3 pos1 = (ProjectionMatrixInverse * gpu_position_at_vertex(1)).xyz;
|
||||
@@ -59,14 +59,16 @@ void init_globals_curves()
|
||||
float cos_theta = curve_interp.time_width / curve_interp.thickness;
|
||||
# if defined(GPU_FRAGMENT_SHADER)
|
||||
if (hairThicknessRes == 1) {
|
||||
# ifdef EEVEE_UTILITY_TX
|
||||
/* 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
|
||||
# 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
|
||||
# endif
|
||||
cos_theta = noise * 2.0 - 1.0;
|
||||
# endif
|
||||
}
|
||||
# endif
|
||||
float sin_theta = sqrt(max(0.0, 1.0 - cos_theta * cos_theta));
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/* SPDX-FileCopyrightText: 2017-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/**
|
||||
* Prepass that voxelizes an object on frustum aligned voxels.
|
||||
*
|
||||
* There is two method available:
|
||||
*
|
||||
* - Fast method: For each fragment we compute the amount of
|
||||
* froxels center in-front of it. We then convert that
|
||||
* into occupancy bitmask that we apply to the occupancy
|
||||
* texture using imageAtomicXor. This flips the bit for each
|
||||
* surfaces encountered along the camera ray.
|
||||
* This is straight forward and works well for any manifold
|
||||
* geometry.
|
||||
*
|
||||
* - Accurate method:
|
||||
* For each fragment we write the fragment depth
|
||||
* in a list (contained in one array texture). This list
|
||||
* is then processed by a fullscreen pass (see
|
||||
* eevee_occupancy_convert_frag.glsl) that sorts and
|
||||
* converts all the hits to the occupancy bits. This
|
||||
* emulate Cycles behavior by considering only back-face
|
||||
* hits as exit events and front-face hits as entry events.
|
||||
* The result stores it to the occupancy texture using
|
||||
* bit-wise OR operation to compose it with other non-hit
|
||||
* list objects. This also decouple the hit-list evaluation
|
||||
* complexity from the material evaluation shader.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(eevee_surf_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(eevee_attributes_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(eevee_volume_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(eevee_occupancy_lib.glsl)
|
||||
|
||||
vec4 closure_to_rgba(Closure cl)
|
||||
{
|
||||
return vec4(0.0);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 texel = ivec2(gl_FragCoord.xy);
|
||||
float vPz = dot(drw_view_forward(), interp.P) - dot(drw_view_forward(), drw_view_position());
|
||||
/* Apply jitter here instead of modifying the projection matrix.
|
||||
* This is because the depth range and mapping function changes. */
|
||||
/* TODO(fclem): Jitter the camera for the other 2 dimension. */
|
||||
float jitter = sampling_rng_1D_get(SAMPLING_VOLUME_W) * uniform_buf.volumes.inv_tex_size.z;
|
||||
float volume_z = view_z_to_volume_z(vPz) - jitter;
|
||||
|
||||
if (use_fast_method) {
|
||||
OccupancyBits occupancy_bits = occupancy_from_depth(volume_z, uniform_buf.volumes.tex_size.z);
|
||||
for (int i = 0; i < imageSize(occupancy_img).z; i++) {
|
||||
/* Negate occupancy bits before XORing so that meshes clipped by the near plane fill the
|
||||
* space betwen the inner part of the mesh and the near plane.
|
||||
* It doesn't change anything for closed meshes. */
|
||||
occupancy_bits.bits[i] = ~occupancy_bits.bits[i];
|
||||
if (occupancy_bits.bits[i] != 0u) {
|
||||
imageAtomicXor(occupancy_img, ivec3(texel, i), occupancy_bits.bits[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
uint hit_id = imageAtomicAdd(hit_count_img, texel, 1u);
|
||||
if (hit_id < VOLUME_HIT_DEPTH_MAX) {
|
||||
float value = gl_FrontFacing ? volume_z : -volume_z;
|
||||
imageStore(hit_depth_img, ivec3(texel, hit_id), vec4(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(eevee_attributes_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(eevee_occupancy_lib.glsl)
|
||||
|
||||
/* Based on Frosbite Unified Volumetric.
|
||||
* https://www.ea.com/frostbite/news/physically-based-unified-volumetric-rendering-in-frostbite */
|
||||
@@ -49,6 +50,21 @@ void main()
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef MAT_GEOM_VOLUME_OBJECT
|
||||
/** Check occupancy map. Discard thread if froxel is empty. */
|
||||
/* Shift for 32bits per layer. Avoid integer modulo and division. */
|
||||
const int shift = 5;
|
||||
const int mask = int(~(0xFFFFFFFFu << 5u));
|
||||
/* Divide by 32. */
|
||||
int occupancy_layer = froxel.z >> shift;
|
||||
/* Modulo 32. */
|
||||
uint occupancy_shift = froxel.z & mask;
|
||||
uint occupancy_bits = imageLoad(occupancy_img, ivec3(froxel.xy, occupancy_layer)).r;
|
||||
if (((occupancy_bits >> occupancy_shift) & 1u) == 0u) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
vec3 jitter = sampling_rng_3D_get(SAMPLING_VOLUME_U);
|
||||
vec3 ndc_cell = volume_to_screen((vec3(froxel) + jitter) * uniform_buf.volumes.inv_tex_size);
|
||||
|
||||
|
||||
@@ -78,6 +78,17 @@ GPU_SHADER_CREATE_INFO(eevee_geom_point_cloud)
|
||||
"draw_resource_id_varying",
|
||||
"draw_view");
|
||||
|
||||
GPU_SHADER_CREATE_INFO(eevee_geom_volume)
|
||||
.additional_info("eevee_shared")
|
||||
.define("MAT_GEOM_VOLUME")
|
||||
.vertex_in(0, Type::VEC3, "pos")
|
||||
.vertex_out(eevee_surf_iface)
|
||||
.vertex_source("eevee_geom_volume_vert.glsl")
|
||||
.additional_info("draw_modelmat_new",
|
||||
"draw_object_infos_new",
|
||||
"draw_resource_id_varying",
|
||||
"draw_view");
|
||||
|
||||
GPU_SHADER_CREATE_INFO(eevee_geom_gpencil)
|
||||
.additional_info("eevee_shared")
|
||||
.define("MAT_GEOM_GPENCIL")
|
||||
@@ -280,6 +291,7 @@ GPU_SHADER_CREATE_INFO(eevee_volume_object)
|
||||
Qualifier::READ_WRITE,
|
||||
ImageType::FLOAT_3D,
|
||||
"out_phase_img")
|
||||
.image(VOLUME_OCCUPANCY_SLOT, GPU_R32UI, Qualifier::READ, ImageType::UINT_3D, "occupancy_img")
|
||||
.additional_info("eevee_volume_material_common", "draw_object_infos_new", "draw_volume_infos");
|
||||
|
||||
GPU_SHADER_CREATE_INFO(eevee_volume_world)
|
||||
@@ -306,22 +318,23 @@ GPU_SHADER_CREATE_INFO(eevee_volume_world)
|
||||
.define("MAT_GEOM_VOLUME_WORLD")
|
||||
.additional_info("eevee_volume_material_common");
|
||||
|
||||
#if 0 /* TODO */
|
||||
GPU_SHADER_INTERFACE_INFO(eevee_volume_iface, "interp")
|
||||
.smooth(Type::VEC3, "P_start")
|
||||
.smooth(Type::VEC3, "P_end");
|
||||
|
||||
GPU_SHADER_CREATE_INFO(eevee_volume_deferred)
|
||||
.sampler(0, ImageType::DEPTH_2D, "depth_max_tx")
|
||||
.vertex_in(0, Type::VEC3, "pos")
|
||||
.vertex_out(eevee_volume_iface)
|
||||
.fragment_out(0, Type::UVEC4, "out_volume_data")
|
||||
.fragment_out(1, Type::VEC4, "out_transparency_data")
|
||||
.additional_info("eevee_shared")
|
||||
.vertex_source("eevee_volume_vert.glsl")
|
||||
.fragment_source("eevee_volume_deferred_frag.glsl")
|
||||
.additional_info("draw_fullscreen");
|
||||
#endif
|
||||
GPU_SHADER_CREATE_INFO(eevee_surf_occupancy)
|
||||
.define("MAT_OCCUPANCY")
|
||||
.builtins(BuiltinBits::TEXTURE_ATOMIC)
|
||||
.push_constant(Type::BOOL, "use_fast_method")
|
||||
.image(VOLUME_HIT_DEPTH_SLOT, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_3D, "hit_depth_img")
|
||||
.image(VOLUME_HIT_COUNT_SLOT,
|
||||
GPU_R32UI,
|
||||
Qualifier::READ_WRITE,
|
||||
ImageType::UINT_2D,
|
||||
"hit_count_img")
|
||||
.image(VOLUME_OCCUPANCY_SLOT,
|
||||
GPU_R32UI,
|
||||
Qualifier::READ_WRITE,
|
||||
ImageType::UINT_3D,
|
||||
"occupancy_img")
|
||||
.fragment_source("eevee_surf_occupancy_frag.glsl")
|
||||
.additional_info("eevee_global_ubo", "eevee_sampling_data");
|
||||
|
||||
/** \} */
|
||||
|
||||
@@ -348,7 +361,8 @@ GPU_SHADER_CREATE_INFO(eevee_material_stub)
|
||||
/* EEVEE_MAT_FINAL_VARIATION(prefix##_gpencil, "eevee_geom_gpencil", __VA_ARGS__) */ \
|
||||
EEVEE_MAT_FINAL_VARIATION(prefix##_curves, "eevee_geom_curves", __VA_ARGS__) \
|
||||
EEVEE_MAT_FINAL_VARIATION(prefix##_mesh, "eevee_geom_mesh", __VA_ARGS__) \
|
||||
EEVEE_MAT_FINAL_VARIATION(prefix##_point_cloud, "eevee_geom_point_cloud", __VA_ARGS__)
|
||||
EEVEE_MAT_FINAL_VARIATION(prefix##_point_cloud, "eevee_geom_point_cloud", __VA_ARGS__) \
|
||||
EEVEE_MAT_FINAL_VARIATION(prefix##_volume, "eevee_geom_volume", __VA_ARGS__)
|
||||
|
||||
# define EEVEE_MAT_PIPE_VARIATIONS(name, ...) \
|
||||
EEVEE_MAT_GEOM_VARIATIONS(name##_world, "eevee_surf_world", __VA_ARGS__) \
|
||||
@@ -356,6 +370,7 @@ GPU_SHADER_CREATE_INFO(eevee_material_stub)
|
||||
EEVEE_MAT_GEOM_VARIATIONS(name##_deferred, "eevee_surf_deferred", __VA_ARGS__) \
|
||||
EEVEE_MAT_GEOM_VARIATIONS(name##_forward, "eevee_surf_forward", __VA_ARGS__) \
|
||||
EEVEE_MAT_GEOM_VARIATIONS(name##_capture, "eevee_surf_capture", __VA_ARGS__) \
|
||||
EEVEE_MAT_GEOM_VARIATIONS(name##_occupancy, "eevee_surf_occupancy", __VA_ARGS__) \
|
||||
EEVEE_MAT_GEOM_VARIATIONS(name##_shadow_atomic, "eevee_surf_shadow_atomic", __VA_ARGS__) \
|
||||
EEVEE_MAT_GEOM_VARIATIONS(name##_shadow_tbdr, "eevee_surf_shadow_tbdr", __VA_ARGS__)
|
||||
|
||||
|
||||
@@ -60,6 +60,23 @@ GPU_SHADER_CREATE_INFO(eevee_volume_scatter_with_lights)
|
||||
.sampler(0, ImageType::FLOAT_3D, "extinction_tx")
|
||||
.do_static_compilation(true);
|
||||
|
||||
GPU_SHADER_CREATE_INFO(eevee_volume_occupancy_convert)
|
||||
.additional_info("eevee_shared", "eevee_global_ubo", "draw_fullscreen")
|
||||
.builtins(BuiltinBits::TEXTURE_ATOMIC)
|
||||
.image(VOLUME_HIT_DEPTH_SLOT, GPU_R32F, Qualifier::READ, ImageType::FLOAT_3D, "hit_depth_img")
|
||||
.image(VOLUME_HIT_COUNT_SLOT,
|
||||
GPU_R32UI,
|
||||
Qualifier::READ_WRITE,
|
||||
ImageType::UINT_2D,
|
||||
"hit_count_img")
|
||||
.image(VOLUME_OCCUPANCY_SLOT,
|
||||
GPU_R32UI,
|
||||
Qualifier::READ_WRITE,
|
||||
ImageType::UINT_3D,
|
||||
"occupancy_img")
|
||||
.fragment_source("eevee_occupancy_convert_frag.glsl")
|
||||
.do_static_compilation(true);
|
||||
|
||||
GPU_SHADER_CREATE_INFO(eevee_volume_integration)
|
||||
.additional_info("eevee_shared", "eevee_global_ubo", "draw_view")
|
||||
.compute_source("eevee_volume_integration_comp.glsl")
|
||||
|
||||
@@ -74,7 +74,7 @@ void set_flag_from_test(inout int value, bool test, int flag)
|
||||
}
|
||||
|
||||
/* Keep define to match C++ implementation. */
|
||||
#define SET_FLAG_FROM_TEST(value, test, flag) flag_test(value, test, flag)
|
||||
#define SET_FLAG_FROM_TEST(value, test, flag) set_flag_from_test(value, test, flag)
|
||||
|
||||
/**
|
||||
* Pack two 16-bit uint into one 32-bit uint.
|
||||
|
||||
@@ -98,3 +98,9 @@ GPU_SHADER_CREATE_INFO(eevee_shadow_test)
|
||||
.additional_info("gpu_shader_test")
|
||||
.additional_info("eevee_shared")
|
||||
.do_static_compilation(true);
|
||||
|
||||
GPU_SHADER_CREATE_INFO(eevee_occupancy_test)
|
||||
.fragment_source("eevee_occupancy_test.glsl")
|
||||
.additional_info("gpu_shader_test")
|
||||
.additional_info("eevee_shared")
|
||||
.do_static_compilation(true);
|
||||
|
||||
@@ -980,8 +980,93 @@ inline void _texture_write_internal_fast(thread _mtl_combined_image_sampler_3d<S
|
||||
|
||||
/* Image atomic operations. */
|
||||
# define imageAtomicMin(tex, coord, data) _texture_image_atomic_min_internal(tex, coord, data)
|
||||
# define imageAtomicAdd(tex, coord, data) _texture_image_atomic_add_internal(tex, coord, data)
|
||||
# define imageAtomicExchange(tex, coord, data) \
|
||||
_texture_image_atomic_exchange_internal(tex, coord, data)
|
||||
# define imageAtomicXor(tex, coord, data) _texture_image_atomic_xor_internal(tex, coord, data)
|
||||
# define imageAtomicOr(tex, coord, data) _texture_image_atomic_or_internal(tex, coord, data)
|
||||
|
||||
/* Atomic OR. */
|
||||
template<typename S, access A>
|
||||
S _texture_image_atomic_or_internal(thread _mtl_combined_image_sampler_1d<S, A> tex,
|
||||
int coord,
|
||||
S data)
|
||||
{
|
||||
return tex.texture->atomic_fetch_or(uint(coord), vec<S, 4>(data)).x;
|
||||
}
|
||||
|
||||
template<typename S, access A>
|
||||
S _texture_image_atomic_or_internal(thread _mtl_combined_image_sampler_1d_array<S, A> tex,
|
||||
int2 coord,
|
||||
S data)
|
||||
{
|
||||
return tex.texture->atomic_fetch_or(uint(coord.x), uint(coord.y), vec<S, 4>(data)).x;
|
||||
}
|
||||
|
||||
template<typename S, access A>
|
||||
S _texture_image_atomic_or_internal(thread _mtl_combined_image_sampler_2d<S, A> tex,
|
||||
int2 coord,
|
||||
S data)
|
||||
{
|
||||
return tex.texture->atomic_fetch_or(uint2(coord.xy), vec<S, 4>(data)).x;
|
||||
}
|
||||
|
||||
template<typename S, access A>
|
||||
S _texture_image_atomic_or_internal(thread _mtl_combined_image_sampler_2d_array<S, A> tex,
|
||||
int3 coord,
|
||||
S data)
|
||||
{
|
||||
return tex.texture->atomic_fetch_or(uint2(coord.xy), uint(coord.z), vec<S, 4>(data)).x;
|
||||
}
|
||||
|
||||
template<typename S, access A>
|
||||
S _texture_image_atomic_or_internal(thread _mtl_combined_image_sampler_3d<S, A> tex,
|
||||
int3 coord,
|
||||
S data)
|
||||
{
|
||||
return tex.texture->atomic_fetch_or(uint3(coord), vec<S, 4>(data)).x;
|
||||
}
|
||||
|
||||
/* Atomic XOR. */
|
||||
template<typename S, access A>
|
||||
S _texture_image_atomic_xor_internal(thread _mtl_combined_image_sampler_1d<S, A> tex,
|
||||
int coord,
|
||||
S data)
|
||||
{
|
||||
return tex.texture->atomic_fetch_xor(uint(coord), vec<S, 4>(data)).x;
|
||||
}
|
||||
|
||||
template<typename S, access A>
|
||||
S _texture_image_atomic_xor_internal(thread _mtl_combined_image_sampler_1d_array<S, A> tex,
|
||||
int2 coord,
|
||||
S data)
|
||||
{
|
||||
return tex.texture->atomic_fetch_xor(uint(coord.x), uint(coord.y), vec<S, 4>(data)).x;
|
||||
}
|
||||
|
||||
template<typename S, access A>
|
||||
S _texture_image_atomic_xor_internal(thread _mtl_combined_image_sampler_2d<S, A> tex,
|
||||
int2 coord,
|
||||
S data)
|
||||
{
|
||||
return tex.texture->atomic_fetch_xor(uint2(coord.xy), vec<S, 4>(data)).x;
|
||||
}
|
||||
|
||||
template<typename S, access A>
|
||||
S _texture_image_atomic_xor_internal(thread _mtl_combined_image_sampler_2d_array<S, A> tex,
|
||||
int3 coord,
|
||||
S data)
|
||||
{
|
||||
return tex.texture->atomic_fetch_xor(uint2(coord.xy), uint(coord.z), vec<S, 4>(data)).x;
|
||||
}
|
||||
|
||||
template<typename S, access A>
|
||||
S _texture_image_atomic_xor_internal(thread _mtl_combined_image_sampler_3d<S, A> tex,
|
||||
int3 coord,
|
||||
S data)
|
||||
{
|
||||
return tex.texture->atomic_fetch_xor(uint3(coord), vec<S, 4>(data)).x;
|
||||
}
|
||||
|
||||
/* Atomic Min. */
|
||||
template<typename S, access A>
|
||||
@@ -1024,6 +1109,47 @@ S _texture_image_atomic_min_internal(thread _mtl_combined_image_sampler_3d<S, A>
|
||||
return tex.texture->atomic_fetch_min(uint3(coord), vec<S, 4>(data)).x;
|
||||
}
|
||||
|
||||
/* Atomic Add. */
|
||||
template<typename S, access A>
|
||||
S _texture_image_atomic_add_internal(thread _mtl_combined_image_sampler_1d<S, A> tex,
|
||||
int coord,
|
||||
S data)
|
||||
{
|
||||
return tex.texture->atomic_fetch_add(uint(coord), vec<S, 4>(data)).x;
|
||||
}
|
||||
|
||||
template<typename S, access A>
|
||||
S _texture_image_atomic_add_internal(thread _mtl_combined_image_sampler_1d_array<S, A> tex,
|
||||
int2 coord,
|
||||
S data)
|
||||
{
|
||||
return tex.texture->atomic_fetch_add(uint(coord.x), uint(coord.y), vec<S, 4>(data)).x;
|
||||
}
|
||||
|
||||
template<typename S, access A>
|
||||
S _texture_image_atomic_add_internal(thread _mtl_combined_image_sampler_2d<S, A> tex,
|
||||
int2 coord,
|
||||
S data)
|
||||
{
|
||||
return tex.texture->atomic_fetch_add(uint2(coord.xy), vec<S, 4>(data)).x;
|
||||
}
|
||||
|
||||
template<typename S, access A>
|
||||
S _texture_image_atomic_add_internal(thread _mtl_combined_image_sampler_2d_array<S, A> tex,
|
||||
int3 coord,
|
||||
S data)
|
||||
{
|
||||
return tex.texture->atomic_fetch_add(uint2(coord.xy), uint(coord.z), vec<S, 4>(data)).x;
|
||||
}
|
||||
|
||||
template<typename S, access A>
|
||||
S _texture_image_atomic_add_internal(thread _mtl_combined_image_sampler_3d<S, A> tex,
|
||||
int3 coord,
|
||||
S data)
|
||||
{
|
||||
return tex.texture->atomic_fetch_add(uint3(coord), vec<S, 4>(data)).x;
|
||||
}
|
||||
|
||||
/* Atomic Exchange. */
|
||||
template<typename S, access A>
|
||||
S _texture_image_atomic_exchange_internal(thread _mtl_combined_image_sampler_1d<S, A> tex,
|
||||
|
||||
@@ -461,6 +461,7 @@ GPU_TEST(math_lib)
|
||||
static void test_eevee_lib()
|
||||
{
|
||||
gpu_shader_lib_test("eevee_shadow_test.glsl", "eevee_shared");
|
||||
gpu_shader_lib_test("eevee_occupancy_test.glsl");
|
||||
}
|
||||
GPU_TEST(eevee_lib)
|
||||
|
||||
|
||||
@@ -215,7 +215,9 @@ typedef struct Material {
|
||||
char blend_method;
|
||||
char blend_shadow;
|
||||
char blend_flag;
|
||||
char _pad3[1];
|
||||
|
||||
/* Volume. */
|
||||
char volume_intersection_method;
|
||||
|
||||
/**
|
||||
* Cached slots for texture painting, must be refreshed in
|
||||
@@ -326,6 +328,12 @@ enum {
|
||||
MA_PREVIEW_WORLD = 1 << 0,
|
||||
};
|
||||
|
||||
/** #Material::volume_intersection_method */
|
||||
enum {
|
||||
MA_VOLUME_ISECT_FAST = 0,
|
||||
MA_VOLUME_ISECT_ACCURATE = 1,
|
||||
};
|
||||
|
||||
/** #Material::blend_method */
|
||||
enum {
|
||||
MA_BM_SOLID = 0,
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
.volumetric_tile_size = 8, \
|
||||
.volumetric_samples = 64, \
|
||||
.volumetric_sample_distribution = 0.8f, \
|
||||
.volumetric_ray_depth = 16, \
|
||||
.volumetric_light_clamp = 0.0f, \
|
||||
.volumetric_shadow_samples = 16, \
|
||||
\
|
||||
|
||||
@@ -1855,6 +1855,7 @@ typedef struct SceneEEVEE {
|
||||
float volumetric_sample_distribution;
|
||||
float volumetric_light_clamp;
|
||||
int volumetric_shadow_samples;
|
||||
int volumetric_ray_depth;
|
||||
|
||||
float gtao_distance;
|
||||
float gtao_factor;
|
||||
@@ -1887,7 +1888,6 @@ typedef struct SceneEEVEE {
|
||||
int shadow_ray_count;
|
||||
int shadow_step_count;
|
||||
float shadow_normal_bias;
|
||||
char _pad[4];
|
||||
|
||||
int ray_split_settings;
|
||||
int ray_tracing_method;
|
||||
|
||||
@@ -781,6 +781,22 @@ void RNA_def_material(BlenderRNA *brna)
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static EnumPropertyItem prop_eevee_volume_isect_method_items[] = {
|
||||
{MA_VOLUME_ISECT_FAST,
|
||||
"FAST",
|
||||
0,
|
||||
"Fast",
|
||||
"Each face is considered as a medium interface. Gives correct results for manifold "
|
||||
"geometry that contains no inner parts"},
|
||||
{MA_VOLUME_ISECT_ACCURATE,
|
||||
"ACCURATE",
|
||||
0,
|
||||
"Accurate",
|
||||
"Faces are considered as medium interface only when they have different consecutive "
|
||||
"facing. Gives correct results as long as the max ray depth is not exceeded"},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static EnumPropertyItem prop_eevee_blend_items[] = {
|
||||
{MA_BM_SOLID, "OPAQUE", 0, "Opaque", "Render surface without transparency"},
|
||||
{MA_BM_CLIP,
|
||||
@@ -885,6 +901,14 @@ void RNA_def_material(BlenderRNA *brna)
|
||||
"events (0 is disabled)");
|
||||
RNA_def_property_update(prop, 0, "rna_Material_draw_update");
|
||||
|
||||
prop = RNA_def_property(srna, "volume_intersection_method", PROP_ENUM, PROP_NONE);
|
||||
RNA_def_property_enum_items(prop, prop_eevee_volume_isect_method_items);
|
||||
RNA_def_property_ui_text(
|
||||
prop,
|
||||
"Volume Intersection Method",
|
||||
"Determines which inner part of the mesh will produce volumetric effect");
|
||||
RNA_def_property_update(prop, 0, "rna_Material_draw_update");
|
||||
|
||||
/* For Preview Render */
|
||||
prop = RNA_def_property(srna, "preview_render_type", PROP_ENUM, PROP_NONE);
|
||||
RNA_def_property_enum_sdna(prop, nullptr, "pr_type");
|
||||
|
||||
@@ -7785,6 +7785,15 @@ static void rna_def_scene_eevee(BlenderRNA *brna)
|
||||
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr);
|
||||
|
||||
prop = RNA_def_property(srna, "volumetric_ray_depth", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_ui_text(prop,
|
||||
"Volume Max Ray Depth",
|
||||
"Maximum surface intersection count used by the accurate volume "
|
||||
"intersection method. Will create artifact if it is exceeded");
|
||||
RNA_def_property_range(prop, 1, 16);
|
||||
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr);
|
||||
|
||||
prop = RNA_def_property(srna, "use_volumetric_lights", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, nullptr, "flag", SCE_EEVEE_VOLUMETRIC_LIGHTS);
|
||||
RNA_def_property_ui_text(
|
||||
|
||||
Reference in New Issue
Block a user