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:
Clément Foucault
2023-10-19 19:22:14 +02:00
committed by Clément Foucault
parent dbd775c708
commit f79b86553a
39 changed files with 1356 additions and 229 deletions

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. */
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);
}
}
/* 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;
}
/** \} */

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

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);
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)) {
/* 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
* \{ */

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(