EEVEE-Next: Add mesh volume bounds estimation #113731

Merged
Clément Foucault merged 27 commits from fclem/blender:eevee-next-volume-object-bounds into main 2023-10-19 19:22:22 +02:00
39 changed files with 1356 additions and 229 deletions

View File

@ -273,6 +273,7 @@ class EEVEE_NEXT_MATERIAL_PT_settings(MaterialButtonsPanel, Panel):
layout.prop(mat, "show_transparent_back")
layout.prop(mat, "use_screen_refraction")
layout.prop(mat, "volume_intersection_method")
layout.prop(mat, "pass_index")

View File

@ -461,6 +461,7 @@ class RENDER_PT_eevee_next_volumes(RenderButtonsPanel, Panel):
col.prop(props, "volumetric_tile_size")
col.prop(props, "volumetric_samples")
col.prop(props, "volumetric_sample_distribution", text="Distribution")
col.prop(props, "volumetric_ray_depth", text="Max Depth")
class RENDER_PT_eevee_next_volumes_lighting(RenderButtonsPanel, Panel):

View File

@ -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)

View File

@ -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

View File

@ -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;
}
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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. */
fclem marked this conversation as resolved Outdated

MAT_GEOM_VOLUME_WORLD is a compute shader.

`MAT_GEOM_VOLUME_WORLD` is a compute shader.

The goal would be to port them as vertex shader so that we could shift some computation of the material towards the vertex shader. But that's something still in design so I'll just replace it with "special shader".

The goal would be to port them as vertex shader so that we could shift some computation of the material towards the vertex shader. But that's something still in design so I'll just replace it with "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 {

View File

@ -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);
fclem marked this conversation as resolved Outdated

Why do this instead of layer->sync()?

Why do this instead of `layer->sync()`?
}
}
/* TODO(fclem): Move this clear inside the render pass. */
occupancy_tx.clear(uint4(0u));
inst_.manager->submit(volume_layer_ps_, view);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Volume Pipeline
pragma37 marked this conversation as resolved
Review

Remainder that we saw that this kind of texture clears without a framebuffer was quite slower. Probably not something we should address in this PR, though.

Remainder that we saw that this kind of texture clears without a framebuffer was quite slower. Probably not something we should address in this PR, though.

Yes, but that's quite an ambiguous position. This texture is not part of any framebuffer. So we need to manually clear it.

Yes, but that's quite an ambiguous position. This texture is not part of any framebuffer. So we need to manually clear it.
* \{ */
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;
}
/** \} */

View File

@ -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
fclem marked this conversation as resolved Outdated

I think intersection would make more sense.

I think `intersection` would make more sense.
{
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)) {
pragma37 marked this conversation as resolved Outdated

I don't get the removals from this file. Do they belong to this PR?

I don't get the removals from this file. Do they belong to this PR?

Kind of. They are related to the way the transparent material gets added to the pipeline which is similar to the volume one. This function shouldn't add any transparent object.

Kind of. They are related to the way the transparent material gets added to the pipeline which is similar to the volume one. This function shouldn't add any transparent object.
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;
}

View File

@ -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);
fclem marked this conversation as resolved Outdated

Repeated.

Repeated.
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,

View File

@ -130,6 +130,7 @@ enum eShaderType {
SURFEL_RAY,
VOLUME_INTEGRATION,
VOLUME_OCCUPANCY_CONVERT,
VOLUME_RESOLVE,
VOLUME_SCATTER,
VOLUME_SCATTER_WITH_LIGHTS,

View File

@ -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)) {
fclem marked this conversation as resolved Outdated

Shouldn't this be done only if the material has a volume output?

Shouldn't this be done only if the material has a volume output?

If the material has no volume, the shading group will be nullptr and this call will be a noop.

If the material has no volume, the shading group will be `nullptr` and this call will be a noop.
/* 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);
Review

Why do regular Volume objects need an occupancy pass?

Why do regular Volume objects need an occupancy pass?

Because the material compute shader is dispatched on the BBox converted to the froxel grid AABB. This inflates the bounds quite a bit in any case. So using the occupancy map this makes sure to trim the excess. Also this will help further optimization.

Because the material compute shader is dispatched on the BBox converted to the froxel grid AABB. This inflates the bounds quite a bit in any case. So using the occupancy map this makes sure to trim the excess. Also this will help further optimization.
inst_.pipelines.volume.material_call(material.volume_material, ob, res_handle);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name GPencil
* \{ */

View File

@ -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,

View File

@ -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)

View File

@ -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();

View File

@ -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);
}

View File

@ -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

View File

@ -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);
}

View File

@ -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) :

View File

@ -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]);
}
}
}

View File

@ -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;
}

View File

@ -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));
}
}

View File

@ -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));

View File

@ -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));
}
}
}

View File

@ -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);

View File

@ -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__)

View File

@ -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")

View File

@ -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.

View File

@ -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);

View File

@ -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,

View File

@ -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)

View File

@ -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,

View File

@ -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, \
\

View File

@ -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;

View File

@ -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");

View File

@ -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(