EEVEE-Next: Volume: Fragment shader voxelization
This replaces the compute shader pass for volume material properties voxelization by a fragment shader that is run only once per pixel. The fragment shader then execute the nodetree in a loop for each individual froxel. The motivations are: - faster evaluation of homogenous materials: can evaluate nodetree once and fast write the properties for all froxel in a loop. This matches cycles homogenous material optimization (except that it only considers the first hit). - no invocations for empty froxels: not restricted to box dispach. - support for more than one material: invocations are per pixel. - cleaner implementation (no compute shader specific paths). Implementation wise, this is done by adding a stencil texture when rendering volumetric objects. It is populated during the occupancy phase but it is not directly used (the stencil test is enabled but since we use `imageAtomic` to set the occupancy bits, the fragment shader is forced to be run). The early depth-test is then turned on for the material properties pass, allowing only one fragment to be invoked. This fragment runs the nodetree at the desired frequency: once per direction (homogenous), or once per froxel (heterogenous). Note that I tried to use the frontmost fragment using a depth equal test but it was failing for some reason on Apple silicon producing flickering artifacts. We might reconsider this frontmost fragment approach later since the result is now face order dependant when an object has multiple materials. Pull Request: https://projects.blender.org/blender/blender/pulls/119439
This commit is contained in:
committed by
Clément Foucault
parent
5f23e0fa8d
commit
4a7e98be40
@@ -42,6 +42,16 @@ template<typename T>
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] inline std::optional<Bounds<T>> min_max(const std::optional<Bounds<T>> &a,
|
||||
const T &b)
|
||||
{
|
||||
if (a.has_value()) {
|
||||
return merge(*a, {b, b});
|
||||
}
|
||||
return Bounds<T>{b, b};
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the smallest and largest values element-wise in the span.
|
||||
*/
|
||||
@@ -117,12 +127,32 @@ template<typename T, typename RadiusT>
|
||||
[](const Bounds<T> &a, const Bounds<T> &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<typename T>
|
||||
[[nodiscard]] inline std::optional<Bounds<T>> intersect(const std::optional<Bounds<T>> &a,
|
||||
const std::optional<Bounds<T>> &b)
|
||||
{
|
||||
if (!a.has_value() || !b.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const Bounds<T> 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<typename T, int Size>
|
||||
[[nodiscard]] inline bool less_or_equal_than(const VecBase<T, Size> &a, const VecBase<T, Size> &b)
|
||||
[[nodiscard]] inline bool any_less_or_equal_than(const VecBase<T, Size> &a,
|
||||
const VecBase<T, Size> &b)
|
||||
{
|
||||
for (int i = 0; i < Size; i++) {
|
||||
if (a[i] <= b[i]) {
|
||||
@@ -140,7 +170,7 @@ template<typename T> inline bool Bounds<T>::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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -444,6 +444,14 @@ template<typename T>
|
||||
template<typename T>
|
||||
[[nodiscard]] MatBase<T, 4, 4> 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<typename T>
|
||||
[[nodiscard]] MatBase<T, 4, 4> translate(const MatBase<T, 4, 4> &mat, const VecBase<T, 2> &offset);
|
||||
|
||||
} // namespace projection
|
||||
|
||||
/** \} */
|
||||
@@ -1649,6 +1657,23 @@ template<typename T>
|
||||
return mat;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] MatBase<T, 4, 4> translate(const MatBase<T, 4, 4> &mat, const VecBase<T, 2> &offset)
|
||||
{
|
||||
MatBase<T, 4, 4> 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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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<const ::Camera *>(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. */
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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 <iostream>
|
||||
|
||||
#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<Bounds<float2>> &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<float3> bounds = BKE_object_boundbox_get(ob).value_or(Bounds(float3(0)));
|
||||
int3 min = int3(INT32_MAX);
|
||||
int3 max = int3(INT32_MIN);
|
||||
const Bounds<float3> 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>(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<VolumeLayer>(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<Bounds<float>> 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
|
||||
|
||||
@@ -341,28 +341,13 @@ class DeferredPipeline {
|
||||
*
|
||||
* \{ */
|
||||
|
||||
struct GridAABB {
|
||||
int3 min, max;
|
||||
struct VolumeObjectBounds {
|
||||
/* Screen 2D bounds for layer intersection check. */
|
||||
std::optional<Bounds<float2>> screen_bounds;
|
||||
/* Combined bounds in Z. Allow tighter integration bounds. */
|
||||
std::optional<Bounds<float>> 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<GridAABB> object_bounds_;
|
||||
Vector<std::optional<Bounds<float2>>> object_bounds_;
|
||||
/* Combined bounds from object_bounds_. */
|
||||
std::optional<Bounds<float2>> 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<std::unique_ptr<VolumeLayer>> layers_;
|
||||
|
||||
/* Combined bounds in Z. Allow tighter integration bounds. */
|
||||
std::optional<Bounds<float>> 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<Bounds<float>> 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();
|
||||
};
|
||||
|
||||
/** \} */
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "eevee_pipeline.hh"
|
||||
|
||||
#include "eevee_volume.hh"
|
||||
#include <iostream>
|
||||
|
||||
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<Bounds<float>> 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);
|
||||
|
||||
@@ -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<Texture, 2> scatter_tx_;
|
||||
SwapChain<Texture, 2> 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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
@@ -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
|
||||
|
||||
/** \} */
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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. */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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__)
|
||||
|
||||
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user