diff --git a/source/blender/blenlib/BLI_bounds.hh b/source/blender/blenlib/BLI_bounds.hh index 808659fbf65..710a48fdd22 100644 --- a/source/blender/blenlib/BLI_bounds.hh +++ b/source/blender/blenlib/BLI_bounds.hh @@ -42,6 +42,16 @@ template return std::nullopt; } +template +[[nodiscard]] inline std::optional> min_max(const std::optional> &a, + const T &b) +{ + if (a.has_value()) { + return merge(*a, {b, b}); + } + return Bounds{b, b}; +} + /** * Find the smallest and largest values element-wise in the span. */ @@ -117,12 +127,32 @@ template [](const Bounds &a, const Bounds &b) { return merge(a, b); }); } +/** + * Returns a new bound that contains the intersection of the two given bound. + * Returns no box if there are no overlap. + */ +template +[[nodiscard]] inline std::optional> intersect(const std::optional> &a, + const std::optional> &b) +{ + if (!a.has_value() || !b.has_value()) { + return std::nullopt; + } + const Bounds result{math::max(a.value().min, b.value().min), + math::min(a.value().max, b.value().max)}; + if (result.is_empty()) { + return std::nullopt; + } + return result; +} + } // namespace bounds namespace detail { template -[[nodiscard]] inline bool less_or_equal_than(const VecBase &a, const VecBase &b) +[[nodiscard]] inline bool any_less_or_equal_than(const VecBase &a, + const VecBase &b) { for (int i = 0; i < Size; i++) { if (a[i] <= b[i]) { @@ -140,7 +170,7 @@ template inline bool Bounds::is_empty() const return this->max <= this->min; } else { - return detail::less_or_equal_than(this->max, this->min); + return detail::any_less_or_equal_than(this->max, this->min); } } diff --git a/source/blender/blenlib/BLI_math_matrix.hh b/source/blender/blenlib/BLI_math_matrix.hh index 693047ecf62..9ec4606cc27 100644 --- a/source/blender/blenlib/BLI_math_matrix.hh +++ b/source/blender/blenlib/BLI_math_matrix.hh @@ -444,6 +444,14 @@ template template [[nodiscard]] MatBase perspective_infinite(T left, T right, T bottom, T top, T near_clip); +/** + * \brief Translate a projection matrix after creation in the screen plane. + * Usually used for anti-aliasing jittering. + * `offset` is the translation vector in projected space. + */ +template +[[nodiscard]] MatBase translate(const MatBase &mat, const VecBase &offset); + } // namespace projection /** \} */ @@ -1649,6 +1657,23 @@ template return mat; } +template +[[nodiscard]] MatBase translate(const MatBase &mat, const VecBase &offset) +{ + MatBase result = mat; + const bool is_perspective = mat[2][3] == -1.0f; + const bool is_perspective_infinite = mat[2][2] == -1.0f; + if (is_perspective | is_perspective_infinite) { + result[2][0] -= mat[0][0] * offset.x / math::length(float3(mat[0][0], mat[1][0], mat[2][0])); + result[2][1] -= mat[1][1] * offset.y / math::length(float3(mat[0][1], mat[1][1], mat[2][1])); + } + else { + result[3][0] += offset.x; + result[3][1] += offset.y; + } + return result; +} + extern template float4x4 orthographic( float left, float right, float bottom, float top, float near_clip, float far_clip); extern template float4x4 perspective( diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt index 834035bfc29..e32cb4c5536 100644 --- a/source/blender/draw/CMakeLists.txt +++ b/source/blender/draw/CMakeLists.txt @@ -603,6 +603,7 @@ set(GLSL_SRC 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_surf_volume_frag.glsl engines/eevee_next/shaders/eevee_shadow_page_tile_vert.glsl engines/eevee_next/shaders/eevee_shadow_page_tile_frag.glsl engines/eevee_next/shaders/eevee_surf_world_frag.glsl @@ -618,7 +619,6 @@ set(GLSL_SRC engines/eevee_next/shaders/eevee_vertex_copy_comp.glsl engines/eevee_next/shaders/eevee_volume_integration_comp.glsl engines/eevee_next/shaders/eevee_volume_lib.glsl - engines/eevee_next/shaders/eevee_volume_material_comp.glsl engines/eevee_next/shaders/eevee_volume_resolve_frag.glsl engines/eevee_next/shaders/eevee_volume_scatter_comp.glsl diff --git a/source/blender/draw/engines/eevee_next/eevee_camera.cc b/source/blender/draw/engines/eevee_next/eevee_camera.cc index bba33b0085f..ece3162c2d1 100644 --- a/source/blender/draw/engines/eevee_next/eevee_camera.cc +++ b/source/blender/draw/engines/eevee_next/eevee_camera.cc @@ -166,6 +166,7 @@ void Camera::sync() data.persmat = data.winmat * data.viewmat; data.persinv = math::invert(data.persmat); + is_camera_object_ = false; if (camera_eval && camera_eval->type == OB_CAMERA) { const ::Camera *cam = reinterpret_cast(camera_eval->data); data.clip_near = cam->clip_start; @@ -187,6 +188,7 @@ void Camera::sync() data.equirect_bias = float2(0.0f); data.equirect_scale = float2(0.0f); #endif + is_camera_object_ = true; } else if (inst_.drw_view) { /* \note: Follow camera parameters where distances are positive in front of the camera. */ diff --git a/source/blender/draw/engines/eevee_next/eevee_camera.hh b/source/blender/draw/engines/eevee_next/eevee_camera.hh index 14f4a805453..0cd87cf060c 100644 --- a/source/blender/draw/engines/eevee_next/eevee_camera.hh +++ b/source/blender/draw/engines/eevee_next/eevee_camera.hh @@ -102,6 +102,8 @@ class Camera { float overscan_; bool overscan_changed_; + /** Whether or not the camera was synced from a camera object. */ + bool is_camera_object_ = false; public: Camera(Instance &inst, CameraData &data) : inst_(inst), data_(data){}; @@ -130,6 +132,10 @@ class Camera { { return data_.type == CAMERA_PERSP; } + bool is_camera_object() const + { + return is_camera_object_; + } const float3 &position() const { return data_.viewinv.location(); diff --git a/source/blender/draw/engines/eevee_next/eevee_material.cc b/source/blender/draw/engines/eevee_next/eevee_material.cc index 70f4e62dce6..846cf8ddf9c 100644 --- a/source/blender/draw/engines/eevee_next/eevee_material.cc +++ b/source/blender/draw/engines/eevee_next/eevee_material.cc @@ -251,7 +251,7 @@ Material &MaterialModule::material_sync(Object *ob, 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); + ob, blender_mat, MAT_PIPE_VOLUME_MATERIAL, MAT_GEOM_VOLUME); return mat; }); @@ -338,7 +338,7 @@ Material &MaterialModule::material_sync(Object *ob, 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); + ob, blender_mat, MAT_PIPE_VOLUME_MATERIAL, geometry_type); } else { mat.volume_occupancy = MaterialPass(); diff --git a/source/blender/draw/engines/eevee_next/eevee_material.hh b/source/blender/draw/engines/eevee_next/eevee_material.hh index 456cd1cff70..1e2a1db31a9 100644 --- a/source/blender/draw/engines/eevee_next/eevee_material.hh +++ b/source/blender/draw/engines/eevee_next/eevee_material.hh @@ -51,8 +51,6 @@ enum eMaterialGeometry { MAT_GEOM_VOLUME, /* These maps to special shader. */ - MAT_GEOM_VOLUME_OBJECT, - MAT_GEOM_VOLUME_WORLD, MAT_GEOM_WORLD, }; diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc index 275ef59d185..77f663e8fa0 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc @@ -10,10 +10,14 @@ * This file is only for shading passes. Other passes are declared in their own module. */ -#include "eevee_pipeline.hh" +#include "BLI_bounds.hh" + #include "eevee_instance.hh" +#include "eevee_pipeline.hh" #include "eevee_shadow.hh" +#include + #include "draw_common.hh" namespace blender::eevee { @@ -124,7 +128,8 @@ void WorldPipeline::render(View &view) void WorldVolumePipeline::sync(GPUMaterial *gpumat) { - is_valid_ = (gpumat != nullptr) && (GPU_material_status(gpumat) == GPU_MAT_SUCCESS); + is_valid_ = (gpumat != nullptr) && (GPU_material_status(gpumat) == GPU_MAT_SUCCESS) && + GPU_material_has_volume_output(gpumat); if (!is_valid_) { /* Skip if the material has not compiled yet. */ return; @@ -138,9 +143,10 @@ void WorldVolumePipeline::sync(GPUMaterial *gpumat) world_ps_.bind_resources(inst_.sampling); world_ps_.material_set(*inst_.manager, gpumat); + /* Bind correct dummy texture for attributes defaults. */ volume_sub_pass(world_ps_, nullptr, nullptr, gpumat); - world_ps_.dispatch(math::divide_ceil(inst_.volume.grid_size(), int3(VOLUME_GROUP_SIZE))); + world_ps_.draw_procedural(GPU_PRIM_TRIS, 1, 3); /* Sync with object property pass. */ world_ps_.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); } @@ -926,15 +932,19 @@ void DeferredPipeline::render(View &main_view, void VolumeLayer::sync() { object_bounds_.clear(); + combined_screen_bounds_ = std::nullopt; use_hit_list = false; is_empty = true; finalized = false; + has_scatter = false; + has_absorption = false; draw::PassMain &layer_pass = volume_layer_ps_; layer_pass.init(); + layer_pass.clear_stencil(0x0u); { PassMain::Sub &pass = layer_pass.sub("occupancy_ps"); - /* Double sided without depth test. */ + /* Always double sided to let all fragments be invoked. */ pass.state_set(DRW_STATE_WRITE_DEPTH); pass.bind_resources(inst_.uniform_data); pass.bind_resources(inst_.volume.occupancy); @@ -943,6 +953,9 @@ void VolumeLayer::sync() } { PassMain::Sub &pass = layer_pass.sub("material_ps"); + /* Double sided with stencil equal to ensure only one fragment is nvoked per pixel. */ + pass.state_set(DRW_STATE_WRITE_STENCIL | DRW_STATE_STENCIL_NEQUAL); + pass.state_stencil(0x1u, 0x1u, 0x1u); pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); pass.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx); pass.bind_resources(inst_.uniform_data); @@ -977,9 +990,36 @@ PassMain::Sub *VolumeLayer::material_add(const Object * /*ob*/, "Only volume material should be added here"); PassMain::Sub *pass = &material_ps_->sub(GPU_material_get_name(gpumat)); pass->material_set(*inst_.manager, gpumat); + if (GPU_material_flag_get(gpumat, GPU_MATFLAG_VOLUME_SCATTER)) { + has_scatter = true; + } + if (GPU_material_flag_get(gpumat, GPU_MATFLAG_VOLUME_ABSORPTION)) { + has_absorption = true; + } return pass; } +bool VolumeLayer::bounds_overlaps(const VolumeObjectBounds &object_bounds) const +{ + /* First check the biggest area. */ + if (bounds::intersect(object_bounds.screen_bounds, combined_screen_bounds_)) { + return true; + } + /* Check against individual bounds to try to squeeze the new object between them. */ + for (const std::optional> &other_aabb : object_bounds_) { + if (bounds::intersect(object_bounds.screen_bounds, other_aabb)) { + return true; + } + } + return false; +} + +void VolumeLayer::add_object_bound(const VolumeObjectBounds &object_bounds) +{ + object_bounds_.append(object_bounds.screen_bounds); + combined_screen_bounds_ = bounds::merge(combined_screen_bounds_, object_bounds.screen_bounds); +} + void VolumeLayer::render(View &view, Texture &occupancy_tx) { if (is_empty) { @@ -1007,6 +1047,7 @@ void VolumeLayer::render(View &view, Texture &occupancy_tx) void VolumePipeline::sync() { + object_integration_range_ = std::nullopt; enabled_ = false; has_scatter_ = false; has_absorption_ = false; @@ -1024,107 +1065,63 @@ void VolumePipeline::render(View &view, Texture &occupancy_tx) } } -GridAABB VolumePipeline::grid_aabb_from_object(Object *ob) +VolumeObjectBounds::VolumeObjectBounds(const Camera &camera, Object *ob) { - 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; + /* TODO(fclem): For panoramic camera, we will have to do this check for each cubeface. */ + const float4x4 &view_matrix = camera.data_get().viewmat; + /* Note in practice we only care about the projection type since we only care about 2D overlap, + * and this is independant of FOV. */ + 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 Bounds bounds = BKE_object_boundbox_get(ob).value_or(Bounds(float3(0))); - int3 min = int3(INT32_MAX); - int3 max = int3(INT32_MIN); + const Bounds bounds = BKE_object_boundbox_get(ob).value_or(Bounds(float3(0.0f))); BoundBox bb; BKE_boundbox_init_from_minmax(&bb, bounds.min, bounds.max); - for (float3 l_corner : bb.vec) { - float3 w_corner = math::transform_point(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}; + screen_bounds = std::nullopt; + z_range = std::nullopt; + + for (float3 l_corner : bb.vec) { + float3 ws_corner = math::transform_point(ob->object_to_world(), l_corner); + /* Split view and projection for percision. */ + float3 vs_corner = math::transform_point(view_matrix, ws_corner); + float3 ss_corner = math::project_point(projection_matrix, vs_corner); + + z_range = bounds::min_max(z_range, vs_corner.z); + if (camera.is_perspective() && vs_corner.z >= 1.0e-8f) { + /* If the object is crossing the z=0 plane, we can't determine its 2D bounds easily. + * In this case, consider the object covering the whole screen. + * Still continue the loop for the Z range. */ + screen_bounds = Bounds(float2(-1.0f), float2(1.0f)); + } + else { + screen_bounds = bounds::min_max(screen_bounds, ss_corner.xy()); + } + } } 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; - } + VolumeObjectBounds object_bounds(inst_.camera, ob); + object_integration_range_ = bounds::merge(object_integration_range_, object_bounds.z_range); + + enabled_ = true; /* 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); + if (!layer->bounds_overlaps(object_bounds)) { + layer->add_object_bound(object_bounds); return layer.get(); } } /* No non-overlapping layer found. Create new one. */ int64_t index = layers_.append_and_get_index(std::make_unique(inst_)); - (*layers_[index]).add_object_bound(object_aabb); + (*layers_[index]).add_object_bound(object_bounds); return layers_[index].get(); } -void VolumePipeline::material_call(MaterialPass &volume_material_pass, - Object *ob, - ResourceHandle res_handle) +std::optional> VolumePipeline::object_integration_range() const { - 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; - if (GPU_material_flag_get(volume_material_pass.gpumat, GPU_MATFLAG_VOLUME_SCATTER)) { - has_scatter_ = true; - } - if (GPU_material_flag_get(volume_material_pass.gpumat, GPU_MATFLAG_VOLUME_ABSORPTION)) { - has_absorption_ = true; - } - } + return object_integration_range_; } bool VolumePipeline::use_hit_list() const diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.hh b/source/blender/draw/engines/eevee_next/eevee_pipeline.hh index 9293c349654..b058829ba81 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.hh +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.hh @@ -341,28 +341,13 @@ class DeferredPipeline { * * \{ */ -struct GridAABB { - int3 min, max; +struct VolumeObjectBounds { + /* Screen 2D bounds for layer intersection check. */ + std::optional> screen_bounds; + /* Combined bounds in Z. Allow tighter integration bounds. */ + std::optional> z_range; - 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; - } + VolumeObjectBounds(const Camera &camera, Object *ob); }; /** @@ -373,6 +358,8 @@ class VolumeLayer { bool use_hit_list = false; bool is_empty = true; bool finalized = false; + bool has_scatter = false; + bool has_absorption = false; private: Instance &inst_; @@ -382,7 +369,9 @@ class VolumeLayer { PassMain::Sub *occupancy_ps_; PassMain::Sub *material_ps_; /* List of bounds from all objects contained inside this pass. */ - Vector object_bounds_; + Vector>> object_bounds_; + /* Combined bounds from object_bounds_. */ + std::optional> combined_screen_bounds_; public: VolumeLayer(Instance &inst) : inst_(inst) @@ -398,20 +387,9 @@ class VolumeLayer { 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; - } + bool bounds_overlaps(const VolumeObjectBounds &object_aabb) const; - void add_object_bound(const GridAABB &object_aabb) - { - object_bounds_.append(object_aabb); - } + void add_object_bound(const VolumeObjectBounds &object_aabb); void sync(); void render(View &view, Texture &occupancy_tx); @@ -423,6 +401,8 @@ class VolumePipeline { Vector> layers_; + /* Combined bounds in Z. Allow tighter integration bounds. */ + std::optional> object_integration_range_; /* True if any volume (any object type) creates a volume draw-call. Enables the volume module. */ bool enabled_ = false; /* Aggregated properties of all volume objects. */ @@ -441,12 +421,7 @@ class VolumePipeline { */ 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); + std::optional> object_integration_range() const; bool is_enabled() const { @@ -454,31 +429,25 @@ class VolumePipeline { } bool has_scatter() const { - return has_scatter_; + for (auto &layer : layers_) { + if (layer->has_scatter) { + return true; + } + } + return false; } bool has_absorption() const { - return has_absorption_; + for (auto &layer : layers_) { + if (layer->has_absorption) { + return true; + } + } + return false; } /* 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(); }; /** \} */ diff --git a/source/blender/draw/engines/eevee_next/eevee_sampling.cc b/source/blender/draw/engines/eevee_next/eevee_sampling.cc index 6d7b4a259eb..3356a2b8500 100644 --- a/source/blender/draw/engines/eevee_next/eevee_sampling.cc +++ b/source/blender/draw/engines/eevee_next/eevee_sampling.cc @@ -149,7 +149,7 @@ void Sampling::step() double3 r, offset = {0, 0, 0}; uint64_t leap = 13; uint3 primes = {5, 7, 11}; - BLI_halton_3d(primes, offset, sample_raytrace * leap, r); + BLI_halton_3d(primes, offset, sample_raytrace * leap + 1, r); data_.dimensions[SAMPLING_SHADOW_U] = r[0]; data_.dimensions[SAMPLING_SHADOW_V] = r[1]; data_.dimensions[SAMPLING_SHADOW_W] = r[2]; @@ -157,17 +157,26 @@ void Sampling::step() data_.dimensions[SAMPLING_RAYTRACE_U] = r[0]; data_.dimensions[SAMPLING_RAYTRACE_V] = r[1]; data_.dimensions[SAMPLING_RAYTRACE_W] = r[2]; - /* TODO de-correlate. */ - data_.dimensions[SAMPLING_VOLUME_U] = r[0]; - data_.dimensions[SAMPLING_VOLUME_V] = r[1]; - data_.dimensions[SAMPLING_VOLUME_W] = r[2]; + } + { + uint64_t sample_volume = sample_; + if (interactive_mode()) { + sample_volume = sample_volume % interactive_sample_volume_; + } + double3 r, offset = {0, 0, 0}; + uint3 primes = {2, 3, 5}; + BLI_halton_3d(primes, offset, sample_volume + 1, r); + /* WORKAROUND: We offset the distribution to make the first sample (0,0,0). */ + data_.dimensions[SAMPLING_VOLUME_U] = fractf(r[0] + (1.0 / 2.0)); + data_.dimensions[SAMPLING_VOLUME_V] = fractf(r[1] + (2.0 / 3.0)); + data_.dimensions[SAMPLING_VOLUME_W] = fractf(r[2] + (4.0 / 5.0)); } { /* Using leaped Halton sequence so we can reused the same primes. */ double2 r, offset = {0, 0}; uint64_t leap = 5; uint2 primes = {2, 3}; - BLI_halton_2d(primes, offset, sample_ * leap, r); + BLI_halton_2d(primes, offset, sample_ * leap + 1, r); data_.dimensions[SAMPLING_SHADOW_X] = r[0]; data_.dimensions[SAMPLING_SHADOW_Y] = r[1]; /* TODO de-correlate. */ diff --git a/source/blender/draw/engines/eevee_next/eevee_sampling.hh b/source/blender/draw/engines/eevee_next/eevee_sampling.hh index 0981eae22bc..0fa0a26cf2f 100644 --- a/source/blender/draw/engines/eevee_next/eevee_sampling.hh +++ b/source/blender/draw/engines/eevee_next/eevee_sampling.hh @@ -32,8 +32,10 @@ class Sampling { /* During interactive rendering, loop over the first few samples. */ static constexpr uint64_t interactive_sample_aa_ = 8; static constexpr uint64_t interactive_sample_raytrace_ = 32; + static constexpr uint64_t interactive_sample_volume_ = 32; static constexpr uint64_t interactive_sample_max_ = interactive_sample_aa_ * - interactive_sample_raytrace_; + interactive_sample_raytrace_ * + interactive_sample_volume_; /** 0 based current sample. Might not increase sequentially in viewport. */ uint64_t sample_ = 0; diff --git a/source/blender/draw/engines/eevee_next/eevee_shader.cc b/source/blender/draw/engines/eevee_next/eevee_shader.cc index 9589d4fbd87..77db8caf54f 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader.cc +++ b/source/blender/draw/engines/eevee_next/eevee_shader.cc @@ -508,7 +508,22 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu std::stringstream global_vars; switch (geometry_type) { case MAT_GEOM_MESH: - /** Noop. */ + if (pipeline_type == MAT_PIPE_VOLUME_MATERIAL) { + /* If mesh has a volume output, it can receive volume grid attributes from smoke + * simulation modifier. But the vertex shader might still need access to the vertex + * attribute for displacement. */ + /* TODO(fclem): Eventually, we could add support for loading both. For now, remove the + * vertex inputs after conversion (avoid name collision). */ + for (auto &input : info.vertex_inputs_) { + info.sampler(sampler_slot--, ImageType::FLOAT_3D, input.name, Frequency::BATCH); + } + info.vertex_inputs_.clear(); + /* Volume materials require these for loading the grid attributes from smoke sims. */ + info.additional_info("draw_volume_infos"); + if (ob_info_index == -1) { + info.additional_info("draw_object_infos_new"); + } + } break; case MAT_GEOM_POINT_CLOUD: case MAT_GEOM_CURVES: @@ -525,6 +540,14 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu info.vertex_inputs_.clear(); break; case MAT_GEOM_WORLD: + if (pipeline_type == MAT_PIPE_VOLUME_MATERIAL) { + /* Even if world do not have grid attributes, we use dummy texture binds to pass correct + * defaults. So we have to replace all attributes as samplers. */ + for (auto &input : info.vertex_inputs_) { + info.sampler(sampler_slot--, ImageType::FLOAT_3D, input.name, Frequency::BATCH); + } + info.vertex_inputs_.clear(); + } /** * Only orco layer is supported by world and it is procedurally generated. These are here to * make the attribs_load function calls valid. @@ -542,8 +565,6 @@ 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. */ for (auto &input : info.vertex_inputs_) { info.sampler(sampler_slot--, ImageType::FLOAT_3D, input.name, Frequency::BATCH); @@ -552,11 +573,8 @@ 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, - MAT_GEOM_VOLUME); + const bool do_vertex_attrib_load = !ELEM(geometry_type, MAT_GEOM_WORLD, MAT_GEOM_VOLUME) && + (pipeline_type != MAT_PIPE_VOLUME_MATERIAL); if (!do_vertex_attrib_load && !info.vertex_out_interfaces_.is_empty()) { /* Codegen outputs only one interface. */ @@ -580,26 +598,19 @@ 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_MATERIAL; - if (do_vertex_attrib_load) { vert_gen << global_vars.str() << attr_load.str(); - } - else if (!is_compute) { - frag_gen << global_vars.str() << attr_load.str(); + frag_gen << "void attrib_load() {}\n"; /* Placeholder. */ } else { - comp_gen << global_vars.str() << attr_load.str(); + vert_gen << "void attrib_load() {}\n"; /* Placeholder. */ + frag_gen << global_vars.str() << attr_load.str(); } - if (!is_compute) { + { const bool use_vertex_displacement = (!codegen.displacement.empty()) && (displacement_type != MAT_DISPLACEMENT_BUMP) && - (!ELEM(geometry_type, - MAT_GEOM_WORLD, - MAT_GEOM_VOLUME_WORLD, - MAT_GEOM_VOLUME_OBJECT, - MAT_GEOM_VOLUME)); + (!ELEM(geometry_type, MAT_GEOM_WORLD, MAT_GEOM_VOLUME)); vert_gen << "vec3 nodetree_displacement()\n"; vert_gen << "{\n"; @@ -609,7 +620,7 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu info.vertex_source_generated = vert_gen.str(); } - if (!is_compute && pipeline_type != MAT_PIPE_VOLUME_OCCUPANCY) { + if (pipeline_type != MAT_PIPE_VOLUME_OCCUPANCY) { frag_gen << ((!codegen.material_functions.empty()) ? codegen.material_functions : "\n"); if (!codegen.displacement.empty()) { @@ -634,32 +645,20 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu frag_gen << ((!codegen.thickness.empty()) ? codegen.thickness : "return 0.1;\n"); frag_gen << "}\n\n"; + frag_gen << "Closure nodetree_volume()\n"; + frag_gen << "{\n"; + frag_gen << " closure_weights_reset(0.0);\n"; + frag_gen << ((!codegen.volume.empty()) ? codegen.volume : "return Closure(0);\n"); + frag_gen << "}\n\n"; + info.fragment_source_generated = frag_gen.str(); } - if (is_compute) { - comp_gen << ((!codegen.material_functions.empty()) ? codegen.material_functions : "\n"); - - comp_gen << "Closure nodetree_volume()\n"; - comp_gen << "{\n"; - comp_gen << " closure_weights_reset(0.0);\n"; - comp_gen << ((!codegen.volume.empty()) ? codegen.volume : "return Closure(0);\n"); - comp_gen << "}\n\n"; - - info.compute_source_generated = comp_gen.str(); - } - /* Geometry Info. */ switch (geometry_type) { case MAT_GEOM_WORLD: info.additional_info("eevee_geom_world"); break; - case MAT_GEOM_VOLUME_WORLD: - info.additional_info("eevee_volume_world"); - break; - case MAT_GEOM_VOLUME_OBJECT: - info.additional_info("eevee_volume_object"); - break; case MAT_GEOM_GPENCIL: info.additional_info("eevee_geom_gpencil"); break; @@ -679,10 +678,14 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu /* Pipeline Info. */ switch (geometry_type) { case MAT_GEOM_WORLD: - info.additional_info("eevee_surf_world"); - break; - case MAT_GEOM_VOLUME_OBJECT: - case MAT_GEOM_VOLUME_WORLD: + switch (pipeline_type) { + case MAT_PIPE_VOLUME_MATERIAL: + info.additional_info("eevee_surf_volume"); + break; + default: + info.additional_info("eevee_surf_world"); + break; + } break; default: switch (pipeline_type) { @@ -715,6 +718,9 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu case MAT_PIPE_VOLUME_OCCUPANCY: info.additional_info("eevee_surf_occupancy"); break; + case MAT_PIPE_VOLUME_MATERIAL: + info.additional_info("eevee_surf_volume"); + break; case MAT_PIPE_CAPTURE: info.additional_info("eevee_surf_capture"); break; @@ -774,9 +780,7 @@ GPUMaterial *ShaderModule::world_shader_get(::World *blender_world, 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; - - uint64_t shader_uuid = shader_uuid_from_material_type(pipeline_type, geometry_type); + uint64_t shader_uuid = shader_uuid_from_material_type(pipeline_type, MAT_GEOM_WORLD); return DRW_shader_from_world(blender_world, nodetree, diff --git a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh index 50c5b6f4a3c..df6b07abf26 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh +++ b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh @@ -501,74 +501,38 @@ BLI_STATIC_ASSERT_ALIGN(MotionBlurTileIndirection, 16) * \{ */ struct VolumesInfoData { - float2 coord_scale; - float2 viewport_size_inv; + /* During object voxelization, we need to use an infinite projection matrix to avoid clipping + * faces. But they cannot be used for recovering the view position from froxel position as they + * are not invertible. We store the finite projection matrix and use it for this purpose. */ + float4x4 winmat_finite; + float4x4 wininv_finite; + /* Convert volume frustum UV(+ linear Z) coordinates into previous frame UV(+ linear Z). */ + float4x4 history_matrix; + /* Size of the froxel grid texture. */ packed_int3 tex_size; + /* Maximum light intensity during volume lighting evaluation. */ float light_clamp; + /* Inverse of size of the froxel grid. */ packed_float3 inv_tex_size; - int tile_size; - int tile_size_lod; + /* Maximum light intensity during volume lighting evaluation. */ float shadow_steps; + /* 2D scaling factor to make froxel squared. */ + float2 coord_scale; + /* Extent and inverse extent of the main shading view (render extent, not film extent). */ + float2 main_view_extent; + float2 main_view_extent_inv; + /* Size in main view pixels of one froxel in XY. */ + int tile_size; + /* Hi-Z LOD to use during volume shadow tagging. */ + int tile_size_lod; + /* Depth to froxel mapping. */ float depth_near; float depth_far; float depth_distribution; float _pad0; - float _pad1; - float _pad2; }; BLI_STATIC_ASSERT_ALIGN(VolumesInfoData, 16) -/* Volume slice to view space depth. */ -static inline float volume_z_to_view_z( - float near, float far, float distribution, bool is_persp, float z) -{ - if (is_persp) { - /* Exponential distribution. */ - return (exp2(z / distribution) - near) / far; - } - else { - /* Linear distribution. */ - return near + (far - near) * z; - } -} - -static inline float view_z_to_volume_z( - float near, float far, float distribution, bool is_persp, float depth) -{ - if (is_persp) { - /* Exponential distribution. */ - return distribution * log2(depth * far + near); - } - else { - /* Linear distribution. */ - return (depth - near) * distribution; - } -} - -static inline float3 screen_to_volume(const float4x4 projection_matrix, - float near, - float far, - float distribution, - const float2 coord_scale, - float3 coord) -{ - bool is_persp = projection_matrix[3][3] == 0.0; - - /* get_view_z_from_depth */ - float d = 2.0 * coord.z - 1.0; - if (is_persp) { - coord.z = -projection_matrix[3][2] / (d + projection_matrix[2][2]); - } - else { - coord.z = (d - projection_matrix[3][2]) / projection_matrix[2][2]; - } - - coord.z = view_z_to_volume_z(near, far, distribution, is_persp, coord.z); - coord.x *= coord_scale.x; - coord.y *= coord_scale.y; - return coord; -} - /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/draw/engines/eevee_next/eevee_sync.cc b/source/blender/draw/engines/eevee_next/eevee_sync.cc index d6ae4c6ebf3..685389c8b1f 100644 --- a/source/blender/draw/engines/eevee_next/eevee_sync.cc +++ b/source/blender/draw/engines/eevee_next/eevee_sync.cc @@ -79,6 +79,15 @@ static inline void geometry_call(PassMain::Sub *sub_pass, } } +static inline void volume_call( + MaterialPass &matpass, Scene *scene, Object *ob, gpu::Batch *geom, ResourceHandle res_handle) +{ + if (matpass.sub_pass != nullptr) { + PassMain::Sub *object_pass = volume_sub_pass(*matpass.sub_pass, scene, ob, matpass.gpumat); + object_pass->draw(geom, res_handle); + } +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -125,13 +134,12 @@ void SyncModule::sync_mesh(Object *ob, Material &material = material_array.materials[i]; GPUMaterial *gpu_material = material_array.gpu_materials[i]; - if (material.has_volume && (i == 0)) { - /* Only support single volume material for now. */ - geometry_call(material.volume_occupancy.sub_pass, geom, res_handle); - inst_.pipelines.volume.material_call(material.volume_material, ob, res_handle); + if (material.has_volume) { + volume_call(material.volume_occupancy, inst_.scene, ob, geom, res_handle); + volume_call(material.volume_material, inst_.scene, ob, geom, 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)) { + if (!material.has_surface) { continue; } } @@ -206,10 +214,9 @@ bool SyncModule::sync_sculpt(Object *ob, Material &material = material_array.materials[batch.material_slot]; - if (material.has_volume && (batch.material_slot == 0)) { - /* Only support single volume material for now. */ - geometry_call(material.volume_occupancy.sub_pass, geom, res_handle); - inst_.pipelines.volume.material_call(material.volume_material, ob, res_handle); + if (material.has_volume) { + volume_call(material.volume_occupancy, inst_.scene, ob, geom, res_handle); + volume_call(material.volume_material, inst_.scene, ob, geom, res_handle); /* Do not render surface if we are rendering a volume object * and do not have a surface closure. */ if (material.has_surface == false) { @@ -286,7 +293,7 @@ void SyncModule::sync_point_cloud(Object *ob, if (material.has_volume) { /* Only support single volume material for now. */ drawcall_add(material.volume_occupancy); - inst_.pipelines.volume.material_call(material.volume_material, ob, res_handle); + drawcall_add(material.volume_material); /* Do not render surface if we are rendering a volume object * and do not have a surface closure. */ @@ -346,9 +353,17 @@ void SyncModule::sync_volume(Object *ob, ObjectHandle & /*ob_handle*/, ResourceH /* Use bounding box tag empty spaces. */ gpu::Batch *geom = DRW_cache_cube_get(); - geometry_call(material.volume_occupancy.sub_pass, geom, res_handle); + auto drawcall_add = [&](MaterialPass &matpass, gpu::Batch *geom, ResourceHandle res_handle) { + if (matpass.sub_pass == nullptr) { + return; + } + PassMain::Sub *object_pass = volume_sub_pass( + *matpass.sub_pass, inst_.scene, ob, matpass.gpumat); + object_pass->draw(geom, res_handle); + }; - inst_.pipelines.volume.material_call(material.volume_material, ob, res_handle); + drawcall_add(material.volume_occupancy, geom, res_handle); + drawcall_add(material.volume_material, geom, res_handle); } /** \} */ @@ -542,7 +557,7 @@ void SyncModule::sync_curves(Object *ob, if (material.has_volume) { /* Only support single volume material for now. */ drawcall_add(material.volume_occupancy); - inst_.pipelines.volume.material_call(material.volume_material, ob, res_handle); + drawcall_add(material.volume_material); /* Do not render surface if we are rendering a volume object * and do not have a surface closure. */ if (material.has_surface == false) { diff --git a/source/blender/draw/engines/eevee_next/eevee_volume.cc b/source/blender/draw/engines/eevee_next/eevee_volume.cc index 55e902a5d70..1399ab3a6f2 100644 --- a/source/blender/draw/engines/eevee_next/eevee_volume.cc +++ b/source/blender/draw/engines/eevee_next/eevee_volume.cc @@ -19,6 +19,7 @@ #include "eevee_pipeline.hh" #include "eevee_volume.hh" +#include namespace blender::eevee { @@ -44,7 +45,8 @@ void VolumeModule::init() tex_size = math::min(tex_size, max_size); data_.coord_scale = float2(extent) / float2(tile_size * tex_size); - data_.viewport_size_inv = 1.0f / float2(extent); + data_.main_view_extent = float2(extent); + data_.main_view_extent_inv = 1.0f / float2(extent); /* TODO: compute snap to maxZBuffer for clustered rendering. */ if (data_.tex_size != tex_size) { @@ -62,8 +64,12 @@ void VolumeModule::init() data_.light_clamp = scene_eval->eevee.volumetric_light_clamp; } -void VolumeModule::begin_sync() +void VolumeModule::begin_sync() {} + +void VolumeModule::end_sync() { + enabled_ = inst_.world.has_volume() || inst_.pipelines.volume.is_enabled(); + const Scene *scene_eval = inst_.scene; /* Negate clip values (View matrix forward vector is -Z). */ @@ -72,30 +78,34 @@ void VolumeModule::begin_sync() float integration_start = scene_eval->eevee.volumetric_start; float integration_end = scene_eval->eevee.volumetric_end; + if (!inst_.camera.is_camera_object() && inst_.camera.is_orthographic()) { + integration_start = -integration_end; + } + + std::optional> volume_bounds = inst_.pipelines.volume.object_integration_range(); + + if (volume_bounds && !inst_.world.has_volume()) { + /* Restrict integration range to the object volume range. This increases precision. */ + integration_start = math::max(integration_start, -volume_bounds.value().max); + integration_end = math::min(integration_end, -volume_bounds.value().min); + } + + float near = math::min(-integration_start, clip_start + 1e-4f); + float far = math::max(-integration_end, clip_end - 1e-4f); + if (inst_.camera.is_perspective()) { float sample_distribution = scene_eval->eevee.volumetric_sample_distribution; - sample_distribution = 4.0f * std::max(1.0f - sample_distribution, 1e-2f); - - float near = integration_start = std::min(-integration_start, clip_start - 1e-4f); - float far = integration_end = std::min(-integration_end, near - 1e-4f); + sample_distribution = 4.0f * math::max(1.0f - sample_distribution, 1e-2f); data_.depth_near = (far - near * exp2(1.0f / sample_distribution)) / (far - near); data_.depth_far = (1.0f - data_.depth_near) / near; data_.depth_distribution = sample_distribution; } else { - integration_start = std::min(integration_end, clip_start); - integration_end = std::max(-integration_end, clip_end); - - data_.depth_near = integration_start; - data_.depth_far = integration_end; - data_.depth_distribution = 1.0f / (integration_end - integration_start); + data_.depth_near = near; + data_.depth_far = far; + data_.depth_distribution = 0.0f; /* Unused. */ } -} - -void VolumeModule::end_sync() -{ - enabled_ = inst_.world.has_volume() || inst_.pipelines.volume.is_enabled(); if (!enabled_) { occupancy_tx_.free(); @@ -103,8 +113,10 @@ void VolumeModule::end_sync() prop_extinction_tx_.free(); prop_emission_tx_.free(); prop_phase_tx_.free(); - scatter_tx_.free(); - extinction_tx_.free(); + scatter_tx_.current().free(); + scatter_tx_.previous().free(); + extinction_tx_.current().free(); + extinction_tx_.previous().free(); integrated_scatter_tx_.free(); integrated_transmit_tx_.free(); @@ -160,18 +172,17 @@ void VolumeModule::end_sync() } } - 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 frame-buffer. */ - occupancy_fb_.ensure(data_.tex_size.xy()); - } + eGPUTextureUsage front_depth_usage = GPU_TEXTURE_USAGE_SHADER_READ | + GPU_TEXTURE_USAGE_ATTACHMENT; + front_depth_tx_.ensure_2d(GPU_DEPTH24_STENCIL8, data_.tex_size.xy(), front_depth_usage); + occupancy_fb_.ensure(GPU_ATTACHMENT_TEXTURE(front_depth_tx_)); - scatter_tx_.ensure_3d(GPU_R11F_G11F_B10F, data_.tex_size, usage); - extinction_tx_.ensure_3d(GPU_R11F_G11F_B10F, data_.tex_size, usage); + scatter_tx_.current().ensure_3d(GPU_R11F_G11F_B10F, data_.tex_size, usage); + extinction_tx_.current().ensure_3d(GPU_R11F_G11F_B10F, data_.tex_size, usage); + scatter_tx_.previous().ensure_3d(GPU_R11F_G11F_B10F, data_.tex_size, usage); + extinction_tx_.previous().ensure_3d(GPU_R11F_G11F_B10F, data_.tex_size, usage); + + data_.history_matrix = float4x4::identity(); integrated_scatter_tx_.ensure_3d(GPU_R11F_G11F_B10F, data_.tex_size, usage); integrated_transmit_tx_.ensure_3d(GPU_R11F_G11F_B10F, data_.tex_size, usage); @@ -188,21 +199,29 @@ void VolumeModule::end_sync() occupancy.hit_depth_tx_ = hit_depth_tx_; occupancy.hit_count_tx_ = hit_count_tx_; + /* Use custom sampler to simplify and speedup the shader. + * - Set extend mode to clamp to border color to reject samples with invalid re-projection. + * - Set filtering mode to none to avoid over-blur during re-projection. */ + const GPUSamplerState history_sampler = {GPU_SAMPLER_FILTERING_DEFAULT, + GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER, + GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER}; scatter_ps_.init(); scatter_ps_.shader_set( inst_.shaders.static_shader_get(use_lights_ ? VOLUME_SCATTER_WITH_LIGHTS : VOLUME_SCATTER)); - inst_.lights.bind_resources(scatter_ps_); - inst_.sphere_probes.bind_resources(scatter_ps_); - inst_.volume_probes.bind_resources(scatter_ps_); - inst_.shadows.bind_resources(scatter_ps_); - inst_.sampling.bind_resources(scatter_ps_); + scatter_ps_.bind_resources(inst_.lights); + scatter_ps_.bind_resources(inst_.sphere_probes); + scatter_ps_.bind_resources(inst_.volume_probes); + scatter_ps_.bind_resources(inst_.shadows); + scatter_ps_.bind_resources(inst_.sampling); scatter_ps_.bind_image("in_scattering_img", &prop_scattering_tx_); scatter_ps_.bind_image("in_extinction_img", &prop_extinction_tx_); scatter_ps_.bind_texture("extinction_tx", &prop_extinction_tx_); scatter_ps_.bind_image("in_emission_img", &prop_emission_tx_); 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("scattering_history_tx", &scatter_tx_.previous(), history_sampler); + scatter_ps_.bind_texture("extinction_history_tx", &extinction_tx_.previous(), history_sampler); + scatter_ps_.bind_image("out_scattering_img", &scatter_tx_.current()); + scatter_ps_.bind_image("out_extinction_img", &extinction_tx_.current()); 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); @@ -211,8 +230,9 @@ void VolumeModule::end_sync() integration_ps_.init(); integration_ps_.shader_set(inst_.shaders.static_shader_get(VOLUME_INTEGRATION)); integration_ps_.bind_resources(inst_.uniform_data); - integration_ps_.bind_texture("in_scattering_tx", &scatter_tx_); - integration_ps_.bind_texture("in_extinction_tx", &extinction_tx_); + integration_ps_.bind_resources(inst_.sampling); + integration_ps_.bind_texture("in_scattering_tx", &scatter_tx_.current()); + integration_ps_.bind_texture("in_extinction_tx", &extinction_tx_.current()); integration_ps_.bind_image("out_scattering_img", &integrated_scatter_tx_); integration_ps_.bind_image("out_transmittance_img", &integrated_transmit_tx_); /* Sync with the scatter pass. */ @@ -243,15 +263,44 @@ void VolumeModule::draw_prepass(View &view) inst_.pipelines.world_volume.render(view); float left, right, bottom, top, near, far; - float4x4 winmat = view.winmat(); - projmat_dimensions(winmat.ptr(), &left, &right, &bottom, &top, &near, &far); + const float4x4 winmat_view = view.winmat(); + projmat_dimensions(winmat_view.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); + /* Just like up-sampling matrix computation, we have to be careful to where to put the bounds of + * our froxel volume so that a 2D pixel covers exactly the number of pixel in a tile. */ + float2 render_size = float2(right - left, top - bottom); + float2 volume_size = render_size * float2(data_.tex_size.xy() * data_.tile_size) / + float2(inst_.film.render_extent_get()); + /* Change to the padded extends. */ + right = left + volume_size.x; + top = bottom + volume_size.y; + + /* TODO(fclem): These new matrices are created from the jittered main view matrix. It should be + * better to create them from the non-jittered one to avoid over-blurring. */ + float4x4 winmat_infinite, winmat_finite; + /* Create an infinite projection matrix to avoid far clipping plane clipping the object. This + * way, surfaces that are further away than the far clip plane will still be voxelized.*/ + winmat_infinite = view.is_persp() ? + math::projection::perspective_infinite(left, right, bottom, top, near) : + math::projection::orthographic_infinite(left, right, bottom, top); + /* We still need a bounded projection matrix to get correct froxel location. */ + winmat_finite = view.is_persp() ? + math::projection::perspective(left, right, bottom, top, near, far) : + math::projection::orthographic(left, right, bottom, top, near, far); + /* Anti-Aliasing / Super-Sampling jitter. */ + float2 jitter = inst_.sampling.rng_2d_get(SAMPLING_VOLUME_U); + /* Wrap to keep first sample centered (0,0) and scale to convert to NDC. */ + jitter = math::fract(jitter + 0.5f) * 2.0f - 1.0f; + /* Convert to pixel size. */ + jitter *= data_.inv_tex_size.xy(); + /* Apply jitter to both matrices. */ + winmat_infinite = math::projection::translate(winmat_infinite, jitter); + winmat_finite = math::projection::translate(winmat_finite, jitter); + + data_.winmat_finite = winmat_finite; + data_.wininv_finite = math::invert(winmat_finite); + inst_.uniform_data.push_update(); - View volume_view = {"Volume View"}; volume_view.sync(view.viewmat(), winmat_infinite); if (inst_.pipelines.volume.is_enabled()) { @@ -266,6 +315,8 @@ void VolumeModule::draw_compute(View &view) if (!enabled_) { return; } + scatter_tx_.swap(); + extinction_tx_.swap(); inst_.manager->submit(scatter_ps_, view); inst_.manager->submit(integration_ps_, view); diff --git a/source/blender/draw/engines/eevee_next/eevee_volume.hh b/source/blender/draw/engines/eevee_next/eevee_volume.hh index 47bbc64817c..1257f55910e 100644 --- a/source/blender/draw/engines/eevee_next/eevee_volume.hh +++ b/source/blender/draw/engines/eevee_next/eevee_volume.hh @@ -68,7 +68,7 @@ class VolumeModule { */ Texture hit_count_tx_ = {"hit_count_tx"}; Texture hit_depth_tx_ = {"hit_depth_tx"}; - /** Empty frame-buffer for occupancy pass. */ + Texture front_depth_tx_ = {"front_depth_tx"}; Framebuffer occupancy_fb_ = {"occupancy_fb"}; /* Material Parameters */ @@ -79,8 +79,8 @@ class VolumeModule { /* Light Scattering. */ PassSimple scatter_ps_ = {"Volumes.Scatter"}; - Texture scatter_tx_; - Texture extinction_tx_; + SwapChain scatter_tx_; + SwapChain extinction_tx_; /* Volume Integration */ PassSimple integration_ps_ = {"Volumes.Integration"}; @@ -94,6 +94,8 @@ class VolumeModule { Texture dummy_scatter_tx_; Texture dummy_transmit_tx_; + View volume_view = {"Volume View"}; + public: VolumeModule(Instance &inst, VolumesInfoData &data) : inst_(inst), data_(data) { diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_attributes_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_attributes_lib.glsl index 594fe5d819e..23f04d9e213 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_attributes_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_attributes_lib.glsl @@ -13,7 +13,88 @@ #define EEVEE_ATTRIBUTE_LIB -#if defined(MAT_GEOM_MESH) +/* All attributes are loaded in order. This allow us to use a global counter to retrieve the + * correct grid xform. */ +/* TODO(fclem): This is very dangerous as it requires a reset for each time `attrib_load` is + * called. Instead, the right attribute index should be passed to attr_load_* functions. */ +int g_attr_id = 0; + +/* Point clouds and curves are not compatible with volume grids. + * They will fallback to their own attributes loading. */ +#if defined(MAT_VOLUME) && !defined(MAT_GEOM_CURVES) && !defined(MAT_GEOM_POINT_CLOUD) +# if defined(OBINFO_LIB) && !defined(MAT_GEOM_WORLD) +# define GRID_ATTRIBUTES +# endif + +/* -------------------------------------------------------------------- */ +/** \name Volume + * + * Volume objects loads attributes from "grids" in the form of 3D textures. + * Per grid transform order is following loading order. + * \{ */ + +# ifdef GRID_ATTRIBUTES +vec3 g_lP = vec3(0.0); +# else +vec3 g_wP = vec3(0.0); +# endif + +vec3 grid_coordinates() +{ +# ifdef GRID_ATTRIBUTES + vec3 co = (drw_volume.grids_xform[g_attr_id] * vec4(g_lP, 1.0)).xyz; +# else + /* Only for test shaders. All the runtime shaders require `draw_object_infos` and + * `draw_volume_infos`. */ + vec3 co = vec3(0.0); +# endif + g_attr_id += 1; + return co; +} + +vec3 attr_load_orco(sampler3D tex) +{ + g_attr_id += 1; +# ifdef GRID_ATTRIBUTES + return OrcoTexCoFactors[0].xyz + g_lP * OrcoTexCoFactors[1].xyz; +# else + return g_wP; +# endif +} +vec4 attr_load_tangent(sampler3D tex) +{ + g_attr_id += 1; + return vec4(0); +} +vec4 attr_load_vec4(sampler3D tex) +{ + return texture(tex, grid_coordinates()); +} +vec3 attr_load_vec3(sampler3D tex) +{ + return texture(tex, grid_coordinates()).rgb; +} +vec2 attr_load_vec2(sampler3D tex) +{ + return texture(tex, grid_coordinates()).rg; +} +float attr_load_float(sampler3D tex) +{ + return texture(tex, grid_coordinates()).r; +} +vec4 attr_load_color(sampler3D tex) +{ + return texture(tex, grid_coordinates()); +} +vec3 attr_load_uv(sampler3D attr) +{ + g_attr_id += 1; + return vec3(0); +} + +/** \} */ + +#elif defined(MAT_GEOM_MESH) /* -------------------------------------------------------------------- */ /** \name Mesh @@ -233,67 +314,6 @@ float attr_load_float(samplerBuffer cd_buf) /** \} */ -#elif defined(MAT_GEOM_VOLUME) || defined(MAT_GEOM_VOLUME_OBJECT) || defined(MAT_GEOM_VOLUME_WORLD) - -/* -------------------------------------------------------------------- */ -/** \name Volume - * - * Volume objects loads attributes from "grids" in the form of 3D textures. - * Per grid transform order is following loading order. - * \{ */ - -vec3 g_lP = vec3(0.0); -vec3 g_orco = vec3(0.0); -int g_attr_id = 0; - -vec3 grid_coordinates() -{ - vec3 co = g_orco; -# ifdef MAT_GEOM_VOLUME_OBJECT - co = (drw_volume.grids_xform[g_attr_id] * vec4(g_lP, 1.0)).xyz; -# endif - g_attr_id += 1; - return co; -} - -vec3 attr_load_orco(sampler3D tex) -{ - g_attr_id += 1; - return g_orco; -} -vec4 attr_load_tangent(sampler3D tex) -{ - g_attr_id += 1; - return vec4(0); -} -vec4 attr_load_vec4(sampler3D tex) -{ - return texture(tex, grid_coordinates()); -} -vec3 attr_load_vec3(sampler3D tex) -{ - return texture(tex, grid_coordinates()).rgb; -} -vec2 attr_load_vec2(sampler3D tex) -{ - return texture(tex, grid_coordinates()).rg; -} -float attr_load_float(sampler3D tex) -{ - return texture(tex, grid_coordinates()).r; -} -vec4 attr_load_color(sampler3D tex) -{ - return texture(tex, grid_coordinates()); -} -vec3 attr_load_uv(sampler3D attr) -{ - g_attr_id += 1; - return vec3(0); -} - -/** \} */ - #elif defined(MAT_GEOM_WORLD) /* -------------------------------------------------------------------- */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl index 87ffbf7e8f5..239123550bb 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl @@ -388,7 +388,7 @@ float ambient_occlusion_eval(vec3 normal, #ifndef GPU_METAL void attrib_load(); Closure nodetree_surface(float closure_rand); -/* Closure nodetree_volume(); */ +Closure nodetree_volume(); vec3 nodetree_displacement(); float nodetree_thickness(); vec4 closure_to_rgba(Closure cl); @@ -713,39 +713,35 @@ float film_scaling_factor_get() * * \{ */ -#if defined(MAT_GEOM_VOLUME_OBJECT) || defined(MAT_GEOM_VOLUME_WORLD) +/* Point clouds and curves are not compatible with volume grids. + * They will fallback to their own attributes loading. */ +#if defined(MAT_VOLUME) && !defined(MAT_GEOM_CURVES) && !defined(MAT_GEOM_POINT_CLOUD) +# if defined(OBINFO_LIB) && !defined(MAT_GEOM_WORLD) +/* We could just check for GRID_ATTRIBUTES but this avoids for header dependency. */ +# define GRID_ATTRIBUTES_LOAD_POST +# endif +#endif float attr_load_temperature_post(float attr) { -# ifdef MAT_GEOM_VOLUME_OBJECT +#ifdef GRID_ATTRIBUTES_LOAD_POST /* Bring the into standard range without having to modify the grid values */ attr = (attr > 0.01) ? (attr * drw_volume.temperature_mul + drw_volume.temperature_bias) : 0.0; -# endif +#endif return attr; } vec4 attr_load_color_post(vec4 attr) { -# ifdef MAT_GEOM_VOLUME_OBJECT +#ifdef GRID_ATTRIBUTES_LOAD_POST /* Density is premultiplied for interpolation, divide it out here. */ attr.rgb *= safe_rcp(attr.a); attr.rgb *= drw_volume.color_mul.rgb; attr.a = 1.0; -# endif - return attr; -} - -#else /* NOP for any other surface. */ - -float attr_load_temperature_post(float attr) -{ - return attr; -} -vec4 attr_load_color_post(vec4 attr) -{ - return attr; -} - #endif + return attr; +} + +#undef GRID_ATTRIBUTES_LOAD_POST /** \} */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_volume_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_volume_comp.glsl index 505e91b7257..90397756a7e 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_volume_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_volume_comp.glsl @@ -28,18 +28,21 @@ void main() return; } - vec3 jitter = sampling_rng_3D_get(SAMPLING_VOLUME_U); - vec3 volume_ndc = volume_to_screen((vec3(froxel) + jitter) * uniform_buf.volumes.inv_tex_size); - vec3 vP = drw_point_screen_to_view(vec3(volume_ndc.xy, volume_ndc.z)); + float offset = sampling_rng_1D_get(SAMPLING_VOLUME_W); + float jitter = interlieved_gradient_noise(vec2(froxel.xy), 0.0, offset); + + vec3 uvw = (vec3(froxel) + vec3(0.5, 0.5, jitter)) * uniform_buf.volumes.inv_tex_size; + vec3 ss_P = volume_resolve_to_screen(uvw); + vec3 vP = drw_point_screen_to_view(vec3(ss_P.xy, ss_P.z)); vec3 P = drw_point_view_to_world(vP); float depth = texelFetch(hiz_tx, froxel.xy, uniform_buf.volumes.tile_size_lod).r; - if (depth < volume_ndc.z) { + if (depth < ss_P.z) { return; } - vec2 pixel = (vec2(froxel.xy) + vec2(0.5)) / vec2(uniform_buf.volumes.tex_size.xy) / - uniform_buf.volumes.viewport_size_inv; + vec2 pixel = ((vec2(froxel.xy) + 0.5) * uniform_buf.volumes.inv_tex_size.xy) * + uniform_buf.volumes.main_view_extent; int bias = uniform_buf.volumes.tile_size_lod; shadow_tag_usage(vP, P, drw_world_incident_vector(P), 0.01, length(vP), pixel, bias); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_forward_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_forward_frag.glsl index cb5477724fd..d2440fed483 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_forward_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_forward_frag.glsl @@ -53,7 +53,7 @@ void main() forward_lighting_eval(g_thickness, radiance, transmittance); /* Volumetric resolve and compositing. */ - vec2 uvs = gl_FragCoord.xy * uniform_buf.volumes.viewport_size_inv; + vec2 uvs = gl_FragCoord.xy * uniform_buf.volumes.main_view_extent_inv; VolumeResolveSample vol = volume_resolve( vec3(uvs, gl_FragCoord.z), volume_transmittance_tx, volume_scattering_tx); /* Removes the part of the volume scattering that has diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl index ec39e3c232c..ff78169070e 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl @@ -46,11 +46,10 @@ 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; + + float offset = sampling_rng_1D_get(SAMPLING_VOLUME_W); + float jitter = volume_froxel_jitter(texel, offset) * 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); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_volume_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_volume_frag.glsl new file mode 100644 index 00000000000..2f84b049fb2 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_volume_frag.glsl @@ -0,0 +1,145 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/* Based on Frosbite Unified Volumetric. + * https://www.ea.com/frostbite/news/physically-based-unified-volumetric-rendering-in-frostbite */ + +/* Store volumetric properties into the froxel textures. */ + +#pragma BLENDER_REQUIRE(eevee_volume_lib.glsl) + +/* Needed includes for shader nodes. */ +#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) + +GlobalData init_globals(vec3 wP) +{ + GlobalData surf; + surf.P = wP; + surf.N = vec3(0.0); + surf.Ng = vec3(0.0); + surf.is_strand = false; + surf.hair_time = 0.0; + surf.hair_thickness = 0.0; + surf.hair_strand_id = 0; + surf.barycentric_coords = vec2(0.0); + surf.barycentric_dists = vec3(0.0); + surf.ray_type = RAY_TYPE_CAMERA; + surf.ray_depth = 0.0; + surf.ray_length = distance(surf.P, drw_view_position()); + return surf; +} + +struct VolumeProperties { + vec3 scattering; + vec3 absorption; + vec3 emission; + float anisotropy; +}; + +VolumeProperties eval_froxel(ivec3 froxel, float jitter) +{ + vec3 uvw = (vec3(froxel) + vec3(0.5, 0.5, 0.5 - jitter)) * uniform_buf.volumes.inv_tex_size; + + vec3 vP = volume_jitter_to_view(uvw); + vec3 wP = point_view_to_world(vP); +#if !defined(MAT_GEOM_CURVES) && !defined(MAT_GEOM_POINT_CLOUD) +# ifdef GRID_ATTRIBUTES + g_lP = point_world_to_object(wP); +# else + g_wP = wP; +# endif + /* TODO(fclem): This is very dangerous as it requires a reset for each time `attrib_load` is + * called. Instead, the right attribute index should be passed to attr_load_* functions. */ + g_attr_id = 0; +#endif + + g_data = init_globals(wP); + attrib_load(); + nodetree_volume(); + +#if defined(MAT_GEOM_VOLUME) + g_volume_scattering *= drw_volume.density_scale; + g_volume_absorption *= drw_volume.density_scale; + g_emission *= drw_volume.density_scale; +#endif + + VolumeProperties prop; + prop.scattering = g_volume_scattering; + prop.absorption = g_volume_absorption; + prop.emission = g_emission; + prop.anisotropy = g_volume_anisotropy; + return prop; +} + +void write_froxel(ivec3 froxel, VolumeProperties prop) +{ + vec2 phase = vec2(prop.anisotropy, 1.0); + + /* Do not add phase weight if there's no scattering. */ + if (all(equal(prop.scattering, vec3(0.0)))) { + phase = vec2(0.0); + } + + vec3 extinction = prop.scattering + prop.absorption; + +#ifndef MAT_GEOM_WORLD + /* Additive Blending. No race condition since we have a barrier between each conflicting + * invocations. */ + prop.scattering += imageLoad(out_scattering_img, froxel).rgb; + prop.emission += imageLoad(out_emissive_img, froxel).rgb; + extinction += imageLoad(out_extinction_img, froxel).rgb; + phase += imageLoad(out_phase_img, froxel).rg; +#endif + + imageStore(out_scattering_img, froxel, prop.scattering.xyzz); + imageStore(out_extinction_img, froxel, extinction.xyzz); + imageStore(out_emissive_img, froxel, prop.emission.xyzz); + imageStore(out_phase_img, froxel, phase.xyyy); +} + +void main() +{ + ivec3 froxel = ivec3(ivec2(gl_FragCoord.xy), 0); + float offset = sampling_rng_1D_get(SAMPLING_VOLUME_W); + float jitter = volume_froxel_jitter(froxel.xy, offset); + +#ifdef VOLUME_HOMOGENOUS + /* Homogenous volumes only evaluate properties at volume entrance and write the same values for + * each froxel. */ + VolumeProperties prop = eval_froxel(froxel, jitter); +#endif + +#ifndef MAT_GEOM_WORLD + OccupancyBits occupancy; + for (int j = 0; j < 8; j++) { + occupancy.bits[j] = imageLoad(occupancy_img, ivec3(froxel.xy, j)).r; + } +#endif + + /* Check all occupancy bits. */ + for (int j = 0; j < 8; j++) { + for (int i = 0; i < 32; i++) { + froxel.z = j * 32 + i; + + if (froxel.z >= imageSize(out_scattering_img).z) { + break; + } + +#ifndef MAT_GEOM_WORLD + if (((occupancy.bits[j] >> i) & 1u) == 0) { + continue; + } +#endif + +#ifndef VOLUME_HOMOGENOUS + /* Heterogenous volumes evaluate properties at every froxel position. */ + VolumeProperties prop = eval_froxel(froxel, jitter); +#endif + write_froxel(froxel, prop); + } + } +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_integration_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_integration_comp.glsl index 66777d6be04..39909fcfd8b 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_integration_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_integration_comp.glsl @@ -14,9 +14,8 @@ void main() { ivec2 texel = ivec2(gl_GlobalInvocationID.xy); - ivec3 tex_size = uniform_buf.volumes.tex_size; - if (any(greaterThanEqual(texel, tex_size.xy))) { + if (any(greaterThanEqual(texel, uniform_buf.volumes.tex_size.xy))) { return; } @@ -24,16 +23,14 @@ void main() vec3 scattering = vec3(0.0); vec3 transmittance = vec3(1.0); - /* Compute view ray. */ - vec2 uvs = (vec2(texel) + vec2(0.5)) / vec2(tex_size.xy); - vec3 ss_cell = volume_to_screen(vec3(uvs, 1e-5)); - vec3 view_cell = drw_point_screen_to_view(ss_cell); + /* Compute view ray. Note that jittering the position of the first voxel doesn't bring any + * benefit here. */ + vec3 uvw = (vec3(texel, 0.0) + vec3(0.5, 0.5, 0.0)) * uniform_buf.volumes.inv_tex_size; + vec3 view_cell = volume_jitter_to_view(uvw); float prev_ray_len; float orig_ray_len; - - bool is_persp = ProjectionMatrix[3][3] == 0.0; - if (is_persp) { + if (drw_view_is_perspective()) { prev_ray_len = length(view_cell); orig_ray_len = prev_ray_len / view_cell.z; } @@ -42,13 +39,13 @@ void main() orig_ray_len = 1.0; } - for (int i = 0; i <= tex_size.z; i++) { + for (int i = 0; i <= uniform_buf.volumes.tex_size.z; i++) { ivec3 froxel = ivec3(texel, i); vec3 froxel_scattering = texelFetch(in_scattering_tx, froxel, 0).rgb; vec3 extinction = texelFetch(in_extinction_tx, froxel, 0).rgb; - float cell_depth = volume_z_to_view_z((float(i) + 1.0) / tex_size.z); + float cell_depth = volume_z_to_view_z((float(i) + 1.0) * uniform_buf.volumes.inv_tex_size.z); float ray_len = orig_ray_len * cell_depth; /* Evaluate Scattering. */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_lib.glsl index 0556aff8713..2c5bf9533fc 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_lib.glsl @@ -7,6 +7,7 @@ * - uniform_buf.volumes */ +#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) #pragma BLENDER_REQUIRE(draw_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_light_lib.glsl) #pragma BLENDER_REQUIRE(eevee_shadow_lib.glsl) @@ -15,44 +16,109 @@ /* Based on Frosbite Unified Volumetric. * https://www.ea.com/frostbite/news/physically-based-unified-volumetric-rendering-in-frostbite */ -/* Volume slice to view space depth. */ +/* Per froxel jitter to break slices and flickering. + * Wrapped so that changing it is easier. */ +float volume_froxel_jitter(ivec2 froxel, float offset) +{ + return interlieved_gradient_noise(vec2(froxel), 0.0, offset); +} + +/* Volume froxel texture normalized linear Z to view space Z. + * Not dependant on projection matrix (as long as drw_view_is_perspective is consistent). */ float volume_z_to_view_z(float z) { float near = uniform_buf.volumes.depth_near; float far = uniform_buf.volumes.depth_far; float distribution = uniform_buf.volumes.depth_distribution; - bool is_persp = ProjectionMatrix[3][3] == 0.0; - /* Implemented in eevee_shader_shared.cc */ - return volume_z_to_view_z(near, far, distribution, is_persp, z); + if (drw_view_is_perspective()) { + /* Exponential distribution. */ + return (exp2(z / distribution) - near) / far; + } + else { + /* Linear distribution. */ + return near + (far - near) * z; + } } +/* View space Z to volume froxel texture normalized linear Z. + * Not dependant on projection matrix (as long as drw_view_is_perspective is consistent). */ float view_z_to_volume_z(float depth) { float near = uniform_buf.volumes.depth_near; float far = uniform_buf.volumes.depth_far; float distribution = uniform_buf.volumes.depth_distribution; - bool is_persp = ProjectionMatrix[3][3] == 0.0; - /* Implemented in eevee_shader_shared.cc */ - return view_z_to_volume_z(near, far, distribution, is_persp, depth); + if (drw_view_is_perspective()) { + /* Exponential distribution. */ + return distribution * log2(depth * far + near); + } + else { + /* Linear distribution. */ + return (depth - near) / (far - near); + } } -/* Volume texture normalized coordinates to screen UVs (special range [0, 1]). */ -vec3 volume_to_screen(vec3 coord) +/* Jittered volume texture normalized coordinates to view space position. */ +vec3 volume_jitter_to_view(vec3 coord) +{ + /* Since we use an infinite projection matrix for rendering inside the jittered volumes, + * we need to use a different matrix to reconstruct positions as the infinite matrix is not + * always invertible. */ + mat4x4 winmat = uniform_buf.volumes.winmat_finite; + mat4x4 wininv = uniform_buf.volumes.wininv_finite; + /* Input coordinates are in jittered volume texture space. */ + float view_z = volume_z_to_view_z(coord.z); + /* We need to recover the NDC position for correct perspective divide. */ + float ndc_z = drw_perspective_divide(winmat * vec4(0.0, 0.0, view_z, 1.0)).z; + vec2 ndc_xy = drw_screen_to_ndc(coord.xy); + /* NDC to view. */ + return drw_perspective_divide(wininv * vec4(ndc_xy, ndc_z, 1.0)).xyz; +} + +/* View space position to jittered volume texture normalized coordinates. */ +vec3 volume_view_to_jitter(vec3 vP) +{ + /* Since we use an infinite projection matrix for rendering inside the jittered volumes, + * we need to use a different matrix to reconstruct positions as the infinite matrix is not + * always invertible. */ + mat4x4 winmat = uniform_buf.volumes.winmat_finite; + /* View to ndc. */ + vec3 ndc_P = drw_perspective_divide(winmat * vec4(vP, 1.0)); + /* Here, screen is the same as volume texture UVW space. */ + return vec3(drw_ndc_to_screen(ndc_P.xy), view_z_to_volume_z(vP.z)); +} + +/* Volume texture normalized coordinates (UVW) to render screen (UV). + * Expect active view to be the main view. */ +vec3 volume_resolve_to_screen(vec3 coord) { coord.z = volume_z_to_view_z(coord.z); coord.z = drw_depth_view_to_screen(coord.z); coord.xy /= uniform_buf.volumes.coord_scale; return coord; } - -vec3 screen_to_volume(vec3 coord) +/* Render screen (UV) to volume texture normalized coordinates (UVW). + * Expect active view to be the main view. */ +vec3 volume_screen_to_resolve(vec3 coord) { - float near = uniform_buf.volumes.depth_near; - float far = uniform_buf.volumes.depth_far; - float distribution = uniform_buf.volumes.depth_distribution; - vec2 coord_scale = uniform_buf.volumes.coord_scale; - /* Implemented in eevee_shader_shared.cc */ - return screen_to_volume(ProjectionMatrix, near, far, distribution, coord_scale, coord); + coord.xy *= uniform_buf.volumes.coord_scale; + coord.z = drw_depth_screen_to_view(coord.z); + coord.z = view_z_to_volume_z(coord.z); + return coord; +} + +/* Returns the uvw (normalized coordinate) of a froxel in the previous frame. + * If no history exists, it will return out of bounds sampling coordinates. */ +vec3 volume_history_position_get(ivec3 froxel) +{ + /* We can't reproject by a simple matrix multiplication. We first need to remap to the view Z, + * then transform, then remap back to Volume range. */ + vec3 uvw = (vec3(froxel) + 0.5) * uniform_buf.volumes.inv_tex_size; + uvw.z = volume_z_to_view_z(uvw.z); + + vec3 uvw_history = transform_point(uniform_buf.volumes.history_matrix, uvw); + /* TODO(fclem): For now assume same distribution settings. */ + uvw_history.z = view_z_to_volume_z(uvw_history.z); + return uvw_history; } float volume_phase_function_isotropic() @@ -155,10 +221,10 @@ vec3 volume_shadow( for (float t = 1.0; t < VOLUMETRIC_SHADOW_MAX_STEP && t <= uniform_buf.volumes.shadow_steps; t += 1.0) { - vec3 pos = P + L * t; + vec3 w_pos = P + L * t; - vec3 ndc = drw_point_world_to_ndc(pos); - vec3 volume_co = screen_to_volume(drw_ndc_to_screen(ndc)); + vec3 v_pos = drw_point_world_to_view(w_pos); + vec3 volume_co = volume_view_to_jitter(v_pos); /* Let the texture be clamped to edge. This reduce visual glitches. */ vec3 s_extinction = texture(extinction_tx, volume_co).rgb; @@ -177,7 +243,13 @@ struct VolumeResolveSample { VolumeResolveSample volume_resolve(vec3 ndc_P, sampler3D transmittance_tx, sampler3D scattering_tx) { - vec3 coord = screen_to_volume(ndc_P); + vec3 coord = volume_screen_to_resolve(ndc_P); + + /* Volumes objects have the same aliasing problems has shadow maps. + * To fix this we need a quantization bias (the size of a step in Z) and a slope bias + * (multiplied by the size of a froxel in 2D). */ + coord.z -= uniform_buf.volumes.inv_tex_size.z; + /* TODO(fclem): Slope bias. */ VolumeResolveSample volume; volume.scattering = texture(scattering_tx, coord).rgb; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_material_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_material_comp.glsl deleted file mode 100644 index 7f4edabb99c..00000000000 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_material_comp.glsl +++ /dev/null @@ -1,120 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#pragma BLENDER_REQUIRE(eevee_volume_lib.glsl) - -/* Needed includes for shader nodes. */ -#pragma BLENDER_REQUIRE(common_attribute_lib.glsl) -#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 */ - -/* Store volumetric properties into the froxel textures. */ - -GlobalData init_globals(vec3 wP) -{ - GlobalData surf; - surf.P = wP; - surf.N = vec3(0.0); - surf.Ng = vec3(0.0); - surf.is_strand = false; - surf.hair_time = 0.0; - surf.hair_thickness = 0.0; - surf.hair_strand_id = 0; - surf.barycentric_coords = vec2(0.0); - surf.barycentric_dists = vec3(0.0); - surf.ray_type = RAY_TYPE_CAMERA; - surf.ray_depth = 0.0; - surf.ray_length = distance(surf.P, drw_view_position()); - return surf; -} - -#ifndef GPU_METAL -Closure nodetree_volume(); -void attrib_load(); -#endif - -void main() -{ - ivec3 froxel = ivec3(gl_GlobalInvocationID); -#ifdef MAT_GEOM_VOLUME_OBJECT - froxel += grid_coords_min; -#endif - - if (any(greaterThanEqual(froxel, uniform_buf.volumes.tex_size))) { - 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); - - vec3 vP = get_view_space_from_depth(ndc_cell.xy, ndc_cell.z); - vec3 wP = point_view_to_world(vP); -#ifdef MAT_GEOM_VOLUME_OBJECT - g_lP = point_world_to_object(wP); - g_orco = OrcoTexCoFactors[0].xyz + g_lP * OrcoTexCoFactors[1].xyz; - - if (any(lessThan(g_orco, vec3(0.0))) || any(greaterThan(g_orco, vec3(1.0)))) { - return; - } -#else /* WORLD_SHADER */ - g_orco = wP; -#endif - - g_data = init_globals(wP); - attrib_load(); - nodetree_volume(); - - vec3 scattering = g_volume_scattering; - float anisotropy = g_volume_anisotropy; - vec3 absorption = g_volume_absorption; - vec3 emission = g_emission; - vec2 phase = vec2(anisotropy, 1.0); - -#ifdef MAT_GEOM_VOLUME_OBJECT - scattering *= drw_volume.density_scale; - absorption *= drw_volume.density_scale; - emission *= drw_volume.density_scale; -#endif - - vec3 extinction = scattering + absorption; - - /* Do not add phase weight if there's no scattering. */ - if (all(equal(scattering, vec3(0.0)))) { - phase = vec2(0.0); - } - -#ifdef MAT_GEOM_VOLUME_OBJECT - /* Additive Blending. - * No race condition since each invocation only handles its own froxel. */ - scattering += imageLoad(out_scattering_img, froxel).rgb; - extinction += imageLoad(out_extinction_img, froxel).rgb; - emission += imageLoad(out_emissive_img, froxel).rgb; - phase += imageLoad(out_phase_img, froxel).rg; -#endif - - imageStore(out_scattering_img, froxel, vec4(scattering, 1.0)); - imageStore(out_extinction_img, froxel, vec4(extinction, 1.0)); - imageStore(out_emissive_img, froxel, vec4(emission, 1.0)); - imageStore(out_phase_img, froxel, vec4(phase, vec2(1.0))); -} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_resolve_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_resolve_frag.glsl index 5d32243b95a..6184157b9f8 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_resolve_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_resolve_frag.glsl @@ -11,7 +11,7 @@ void main() { - vec2 uvs = gl_FragCoord.xy * uniform_buf.volumes.viewport_size_inv; + vec2 uvs = gl_FragCoord.xy * uniform_buf.volumes.main_view_extent_inv; float scene_depth = texelFetch(hiz_tx, ivec2(gl_FragCoord.xy), 0).r; VolumeResolveSample vol = volume_resolve( diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_scatter_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_scatter_comp.glsl index ddde3e64f8c..bc950f7af05 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_scatter_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_scatter_comp.glsl @@ -66,10 +66,10 @@ void main() vec3 extinction = imageLoad(in_extinction_img, froxel).rgb; vec3 s_scattering = imageLoad(in_scattering_img, froxel).rgb; - vec3 jitter = sampling_rng_3D_get(SAMPLING_VOLUME_U); - vec3 volume_screen = volume_to_screen((vec3(froxel) + jitter) * - uniform_buf.volumes.inv_tex_size); - vec3 vP = drw_point_screen_to_view(volume_screen); + float offset = sampling_rng_1D_get(SAMPLING_VOLUME_W); + float jitter = volume_froxel_jitter(froxel.xy, offset); + vec3 uvw = (vec3(froxel) + vec3(0.5, 0.5, 0.5 - jitter)) * uniform_buf.volumes.inv_tex_size; + vec3 vP = volume_jitter_to_view(uvw); vec3 P = drw_point_view_to_world(vP); vec3 V = drw_world_incident_vector(P); @@ -88,8 +88,8 @@ void main() } LIGHT_FOREACH_END - vec2 pixel = (vec2(froxel.xy) + vec2(0.5)) / vec2(uniform_buf.volumes.tex_size.xy) / - uniform_buf.volumes.viewport_size_inv; + vec2 pixel = ((vec2(froxel.xy) + 0.5) * uniform_buf.volumes.inv_tex_size.xy) * + uniform_buf.volumes.main_view_extent; LIGHT_FOREACH_BEGIN_LOCAL (light_cull_buf, light_zbin_buf, light_tile_buf, pixel, vP.z, l_idx) { light_scattering += volume_scatter_light_eval(false, P, V, l_idx, s_anisotropy); @@ -99,6 +99,18 @@ void main() scattering += light_scattering * s_scattering; #endif +#if 0 /* TODO */ + { + /* Temporal reprojection. */ + vec3 uvw_history = volume_history_position_get(froxel); + vec4 scattering_history = texture(scattering_history_tx, uvw_history); + vec4 extinction_history = texture(extinction_history_tx, uvw_history); + float history_opacity = 0.95 * scattering_history.a; + scattering = mix(scattering, scattering_history.rgb, history_opacity); + extinction = mix(extinction, extinction_history.rgb, history_opacity); + } +#endif + /* Catch NaNs. */ if (any(isnan(scattering)) || any(isnan(extinction))) { scattering = vec3(0.0); diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh index 9a00c7d1d73..ed4b7b2a08f 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh @@ -87,6 +87,7 @@ GPU_SHADER_CREATE_INFO(eevee_geom_volume) .additional_info("draw_modelmat_new", "draw_object_infos_new", "draw_resource_id_varying", + "draw_volume_infos", "draw_view"); GPU_SHADER_CREATE_INFO(eevee_geom_gpencil) @@ -279,22 +280,10 @@ GPU_SHADER_CREATE_INFO(eevee_surf_shadow_tbdr) /** \name Volume * \{ */ -GPU_SHADER_CREATE_INFO(eevee_volume_material_common) - .compute_source("eevee_volume_material_comp.glsl") - .local_group_size(VOLUME_GROUP_SIZE, VOLUME_GROUP_SIZE, VOLUME_GROUP_SIZE) - .define("VOLUMETRICS") - .additional_info("draw_modelmat_new_common", - /* TODO(fclem): Legacy API. To remove. */ - "draw_resource_id_uniform", - "draw_view", - "eevee_shared", - "eevee_global_ubo", - "eevee_sampling_data", - "eevee_utility_texture"); - -GPU_SHADER_CREATE_INFO(eevee_volume_object) - .define("MAT_GEOM_VOLUME_OBJECT") - .push_constant(Type::IVEC3, "grid_coords_min") +GPU_SHADER_CREATE_INFO(eevee_surf_volume) + .define("MAT_VOLUME") + /* Only the front fragments have to be invoked. */ + .early_fragment_test(true) .image(VOLUME_PROP_SCATTERING_IMG_SLOT, GPU_R11F_G11F_B10F, Qualifier::READ_WRITE, @@ -320,34 +309,18 @@ GPU_SHADER_CREATE_INFO(eevee_volume_object) Qualifier::READ, ImageType::UINT_3D_ATOMIC, "occupancy_img") - .additional_info("eevee_volume_material_common", "draw_object_infos_new", "draw_volume_infos"); - -GPU_SHADER_CREATE_INFO(eevee_volume_world) - .image(VOLUME_PROP_SCATTERING_IMG_SLOT, - GPU_R11F_G11F_B10F, - Qualifier::WRITE, - ImageType::FLOAT_3D, - "out_scattering_img") - .image(VOLUME_PROP_EXTINCTION_IMG_SLOT, - GPU_R11F_G11F_B10F, - Qualifier::WRITE, - ImageType::FLOAT_3D, - "out_extinction_img") - .image(VOLUME_PROP_EMISSION_IMG_SLOT, - GPU_R11F_G11F_B10F, - Qualifier::WRITE, - ImageType::FLOAT_3D, - "out_emissive_img") - .image(VOLUME_PROP_PHASE_IMG_SLOT, - GPU_RG16F, - Qualifier::WRITE, - ImageType::FLOAT_3D, - "out_phase_img") - .define("MAT_GEOM_VOLUME_WORLD") - .additional_info("eevee_volume_material_common"); + .fragment_source("eevee_surf_volume_frag.glsl") + .additional_info("draw_modelmat_new_common", + "draw_view", + "eevee_shared", + "eevee_global_ubo", + "eevee_sampling_data", + "eevee_utility_texture"); GPU_SHADER_CREATE_INFO(eevee_surf_occupancy) .define("MAT_OCCUPANCY") + /* All fragments need to be invoked even if we write to the depth buffer. */ + .early_fragment_test(false) .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") @@ -398,6 +371,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##_volume, "eevee_surf_volume", __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__) diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh index 634ec9d349e..8c89b889e40 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh @@ -36,6 +36,7 @@ GPU_SHADER_CREATE_INFO(eevee_volume_properties_data) "in_phase_img"); GPU_SHADER_CREATE_INFO(eevee_volume_scatter) + .local_group_size(VOLUME_GROUP_SIZE, VOLUME_GROUP_SIZE, VOLUME_GROUP_SIZE) .additional_info("eevee_shared") .additional_info("eevee_global_ubo") .additional_info("draw_resource_id_varying") @@ -45,11 +46,12 @@ GPU_SHADER_CREATE_INFO(eevee_volume_scatter) .additional_info("eevee_shadow_data") .additional_info("eevee_sampling_data") .additional_info("eevee_utility_texture") - .compute_source("eevee_volume_scatter_comp.glsl") - .local_group_size(VOLUME_GROUP_SIZE, VOLUME_GROUP_SIZE, VOLUME_GROUP_SIZE) .additional_info("eevee_volume_properties_data") + .sampler(0, ImageType::FLOAT_3D, "scattering_history_tx") + .sampler(1, ImageType::FLOAT_3D, "extinction_history_tx") .image(4, GPU_R11F_G11F_B10F, Qualifier::WRITE, ImageType::FLOAT_3D, "out_scattering_img") .image(5, GPU_R11F_G11F_B10F, Qualifier::WRITE, ImageType::FLOAT_3D, "out_extinction_img") + .compute_source("eevee_volume_scatter_comp.glsl") .do_static_compilation(true); GPU_SHADER_CREATE_INFO(eevee_volume_scatter_with_lights) @@ -57,7 +59,7 @@ GPU_SHADER_CREATE_INFO(eevee_volume_scatter_with_lights) .define("VOLUME_LIGHTING") .define("VOLUME_IRRADIANCE") .define("VOLUME_SHADOW") - .sampler(0, ImageType::FLOAT_3D, "extinction_tx") + .sampler(9, ImageType::FLOAT_3D, "extinction_tx") .do_static_compilation(true); GPU_SHADER_CREATE_INFO(eevee_volume_occupancy_convert) @@ -79,6 +81,7 @@ GPU_SHADER_CREATE_INFO(eevee_volume_occupancy_convert) GPU_SHADER_CREATE_INFO(eevee_volume_integration) .additional_info("eevee_shared", "eevee_global_ubo", "draw_view") + .additional_info("eevee_sampling_data") .compute_source("eevee_volume_integration_comp.glsl") .local_group_size(VOLUME_INTEGRATION_GROUP_SIZE, VOLUME_INTEGRATION_GROUP_SIZE, 1) /* Inputs. */