2023-08-24 10:54:59 +10:00
|
|
|
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
|
|
2024-10-04 15:48:22 +02:00
|
|
|
#pragma once
|
|
|
|
|
|
2025-09-25 10:57:02 +02:00
|
|
|
#include "infos/eevee_common_infos.hh"
|
2024-12-02 21:26:15 +01:00
|
|
|
|
|
|
|
|
SHADER_LIBRARY_CREATE_INFO(eevee_global_ubo)
|
|
|
|
|
|
2024-10-04 15:48:22 +02:00
|
|
|
#include "draw_view_lib.glsl"
|
|
|
|
|
#include "eevee_sampling_lib.glsl"
|
|
|
|
|
#include "eevee_spherical_harmonics_lib.glsl"
|
2025-09-15 12:07:26 +02:00
|
|
|
#include "gpu_shader_math_matrix_transform_lib.glsl"
|
2023-08-04 16:47:16 +02:00
|
|
|
|
|
|
|
|
/* Based on Frosbite Unified Volumetric.
|
|
|
|
|
* https://www.ea.com/frostbite/news/physically-based-unified-volumetric-rendering-in-frostbite */
|
|
|
|
|
|
2024-04-05 16:33:58 +02:00
|
|
|
/* Per froxel jitter to break slices and flickering.
|
|
|
|
|
* Wrapped so that changing it is easier. */
|
2025-04-14 13:46:41 +02:00
|
|
|
float volume_froxel_jitter(int2 froxel, float offset)
|
2024-04-05 16:33:58 +02:00
|
|
|
{
|
2025-09-10 12:47:49 +02:00
|
|
|
return interleaved_gradient_noise(float2(froxel), 0.0f, offset);
|
2024-04-05 16:33:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Volume froxel texture normalized linear Z to view space Z.
|
2024-04-08 16:23:25 +10:00
|
|
|
* Not dependent on projection matrix (as long as drw_view_is_perspective is consistent). */
|
2023-08-04 16:47:16 +02:00
|
|
|
float volume_z_to_view_z(float z)
|
|
|
|
|
{
|
2023-09-08 21:03:37 +02:00
|
|
|
float near = uniform_buf.volumes.depth_near;
|
|
|
|
|
float far = uniform_buf.volumes.depth_far;
|
|
|
|
|
float distribution = uniform_buf.volumes.depth_distribution;
|
2024-04-05 16:33:58 +02:00
|
|
|
if (drw_view_is_perspective()) {
|
|
|
|
|
/* Exponential distribution. */
|
|
|
|
|
return (exp2(z / distribution) - near) / far;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* Linear distribution. */
|
|
|
|
|
return near + (far - near) * z;
|
|
|
|
|
}
|
2023-08-04 16:47:16 +02:00
|
|
|
}
|
|
|
|
|
|
2024-04-05 16:33:58 +02:00
|
|
|
/* View space Z to volume froxel texture normalized linear Z.
|
2024-04-08 16:23:25 +10:00
|
|
|
* Not dependent on projection matrix (as long as drw_view_is_perspective is consistent). */
|
2024-04-09 19:36:45 +02:00
|
|
|
float view_z_to_volume_z(float depth, float near, float far, float distribution)
|
2023-08-04 16:47:16 +02:00
|
|
|
{
|
2024-04-05 16:33:58 +02:00
|
|
|
if (drw_view_is_perspective()) {
|
|
|
|
|
/* Exponential distribution. */
|
|
|
|
|
return distribution * log2(depth * far + near);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* Linear distribution. */
|
|
|
|
|
return (depth - near) / (far - near);
|
|
|
|
|
}
|
2023-08-04 16:47:16 +02:00
|
|
|
}
|
2024-04-09 19:36:45 +02:00
|
|
|
float view_z_to_volume_z(float depth)
|
|
|
|
|
{
|
|
|
|
|
return view_z_to_volume_z(depth,
|
|
|
|
|
uniform_buf.volumes.depth_near,
|
|
|
|
|
uniform_buf.volumes.depth_far,
|
|
|
|
|
uniform_buf.volumes.depth_distribution);
|
|
|
|
|
}
|
2023-08-04 16:47:16 +02:00
|
|
|
|
2024-04-05 16:33:58 +02:00
|
|
|
/* Jittered volume texture normalized coordinates to view space position. */
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 volume_jitter_to_view(float3 coord)
|
2024-04-05 16:33:58 +02:00
|
|
|
{
|
|
|
|
|
/* 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. */
|
2025-04-14 13:46:41 +02:00
|
|
|
float4x4 winmat = uniform_buf.volumes.winmat_finite;
|
|
|
|
|
float4x4 wininv = uniform_buf.volumes.wininv_finite;
|
2024-04-05 16:33:58 +02:00
|
|
|
/* 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. */
|
2025-04-14 13:46:41 +02:00
|
|
|
float ndc_z = drw_perspective_divide(winmat * float4(0.0f, 0.0f, view_z, 1.0f)).z;
|
|
|
|
|
float2 ndc_xy = drw_screen_to_ndc(coord.xy);
|
2024-04-05 16:33:58 +02:00
|
|
|
/* NDC to view. */
|
2025-04-14 13:46:41 +02:00
|
|
|
return drw_perspective_divide(wininv * float4(ndc_xy, ndc_z, 1.0f)).xyz;
|
2024-04-05 16:33:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* View space position to jittered volume texture normalized coordinates. */
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 volume_view_to_jitter(float3 vP)
|
2024-04-05 16:33:58 +02:00
|
|
|
{
|
|
|
|
|
/* 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. */
|
2025-04-14 13:46:41 +02:00
|
|
|
float4x4 winmat = uniform_buf.volumes.winmat_finite;
|
2024-04-05 16:33:58 +02:00
|
|
|
/* View to ndc. */
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 ndc_P = drw_perspective_divide(winmat * float4(vP, 1.0f));
|
2024-04-05 16:33:58 +02:00
|
|
|
/* Here, screen is the same as volume texture UVW space. */
|
2025-04-14 13:46:41 +02:00
|
|
|
return float3(drw_ndc_to_screen(ndc_P.xy), view_z_to_volume_z(vP.z));
|
2024-04-05 16:33:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Volume texture normalized coordinates (UVW) to render screen (UV).
|
|
|
|
|
* Expect active view to be the main view. */
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 volume_resolve_to_screen(float3 coord)
|
2023-08-04 16:47:16 +02:00
|
|
|
{
|
|
|
|
|
coord.z = volume_z_to_view_z(coord.z);
|
2023-10-13 17:59:46 +02:00
|
|
|
coord.z = drw_depth_view_to_screen(coord.z);
|
2023-09-08 21:03:37 +02:00
|
|
|
coord.xy /= uniform_buf.volumes.coord_scale;
|
2023-08-04 16:47:16 +02:00
|
|
|
return coord;
|
|
|
|
|
}
|
2024-04-05 16:33:58 +02:00
|
|
|
/* Render screen (UV) to volume texture normalized coordinates (UVW).
|
|
|
|
|
* Expect active view to be the main view. */
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 volume_screen_to_resolve(float3 coord)
|
2024-04-05 16:33:58 +02:00
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
2023-08-04 16:47:16 +02:00
|
|
|
|
2024-04-05 16:33:58 +02:00
|
|
|
/* Returns the uvw (normalized coordinate) of a froxel in the previous frame.
|
2025-04-14 13:46:41 +02:00
|
|
|
* Returns float3(-1) if history is unavailable. */
|
|
|
|
|
float3 volume_history_uvw_get(int3 froxel)
|
2023-08-04 16:47:16 +02:00
|
|
|
{
|
2025-04-14 13:46:41 +02:00
|
|
|
float4x4 wininv = uniform_buf.volumes.wininv_stable;
|
|
|
|
|
float4x4 winmat = uniform_buf.volumes.winmat_stable;
|
2024-04-05 16:33:58 +02:00
|
|
|
/* 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. */
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 uvw = (float3(froxel) + 0.5f) * uniform_buf.volumes.inv_tex_size;
|
|
|
|
|
float3 ndc_P = drw_screen_to_ndc(uvw);
|
2024-04-09 19:36:45 +02:00
|
|
|
/* We need to recover the NDC position for correct perspective divide. */
|
|
|
|
|
float view_z = volume_z_to_view_z(uvw.z);
|
2025-04-14 13:46:41 +02:00
|
|
|
ndc_P.z = drw_perspective_divide(winmat * float4(0.0f, 0.0f, view_z, 1.0f)).z;
|
2024-04-09 19:36:45 +02:00
|
|
|
/* NDC to view. */
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 vs_P = project_point(wininv, ndc_P);
|
2024-04-09 19:36:45 +02:00
|
|
|
|
|
|
|
|
/* Transform to previous camera view space. */
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 vs_P_history = transform_point(uniform_buf.volumes.curr_view_to_past_view, vs_P);
|
2024-04-05 16:33:58 +02:00
|
|
|
|
2024-04-09 19:36:45 +02:00
|
|
|
/* View to NDC. */
|
2025-04-14 13:46:41 +02:00
|
|
|
float4 hs_P_history = uniform_buf.volumes.history_winmat_stable * float4(vs_P_history, 1.0f);
|
|
|
|
|
float3 ndc_P_history = drw_perspective_divide(hs_P_history);
|
2024-04-09 19:36:45 +02:00
|
|
|
|
2025-04-14 13:46:41 +02:00
|
|
|
if (hs_P_history.w < 0.0f || any(greaterThan(abs(ndc_P_history.xy), float2(1.0f)))) {
|
|
|
|
|
return float3(-1.0f);
|
2024-04-09 19:36:45 +02:00
|
|
|
}
|
|
|
|
|
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 uvw_history;
|
2024-04-09 19:36:45 +02:00
|
|
|
uvw_history.xy = drw_ndc_to_screen(ndc_P_history.xy);
|
|
|
|
|
uvw_history.z = view_z_to_volume_z(vs_P_history.z,
|
|
|
|
|
uniform_buf.volumes.history_depth_near,
|
|
|
|
|
uniform_buf.volumes.history_depth_far,
|
|
|
|
|
uniform_buf.volumes.history_depth_distribution);
|
|
|
|
|
|
2025-04-11 18:28:45 +02:00
|
|
|
if (uvw_history.z < 0.0f || uvw_history.z > 1.0f) {
|
2025-04-14 13:46:41 +02:00
|
|
|
return float3(-1.0f);
|
2024-04-09 19:36:45 +02:00
|
|
|
}
|
2024-04-05 16:33:58 +02:00
|
|
|
return uvw_history;
|
2023-08-04 16:47:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float volume_phase_function_isotropic()
|
|
|
|
|
{
|
2025-04-11 18:28:45 +02:00
|
|
|
return 1.0f / (4.0f * M_PI);
|
2023-08-04 16:47:16 +02:00
|
|
|
}
|
|
|
|
|
|
2025-04-14 13:46:41 +02:00
|
|
|
float volume_phase_function(float3 V, float3 L, float g)
|
2023-08-04 16:47:16 +02:00
|
|
|
{
|
|
|
|
|
/* Henyey-Greenstein. */
|
2023-10-08 00:15:41 +02:00
|
|
|
float cos_theta = dot(V, L);
|
2025-04-11 18:28:45 +02:00
|
|
|
g = clamp(g, -1.0f + 1e-3f, 1.0f - 1e-3f);
|
2023-08-04 16:47:16 +02:00
|
|
|
float sqr_g = g * g;
|
2025-04-11 18:28:45 +02:00
|
|
|
return (1 - sqr_g) / max(1e-8f, 4.0f * M_PI * pow(1 + sqr_g - 2 * g * cos_theta, 3.0f / 2.0f));
|
2023-08-04 16:47:16 +02:00
|
|
|
}
|
|
|
|
|
|
2025-04-14 13:46:41 +02:00
|
|
|
SphericalHarmonicL1 volume_phase_function_as_sh_L1(float3 V, float g)
|
2024-03-19 19:01:05 +01:00
|
|
|
{
|
|
|
|
|
/* Compute rotated zonal harmonic.
|
|
|
|
|
* From Bartlomiej Wronsky
|
|
|
|
|
* "Volumetric Fog: Unified compute shader based solution to atmospheric scattering" page 55
|
2024-03-21 10:01:43 +11:00
|
|
|
* SIGGRAPH 2014
|
2024-03-19 19:01:05 +01:00
|
|
|
* https://bartwronski.files.wordpress.com/2014/08/bwronski_volumetric_fog_siggraph2014.pdf
|
|
|
|
|
*/
|
|
|
|
|
SphericalHarmonicL1 sh;
|
2025-04-14 13:46:41 +02:00
|
|
|
sh.L0.M0 = spherical_harmonics_L0_M0(V) * float4(1.0f);
|
|
|
|
|
sh.L1.Mn1 = spherical_harmonics_L1_Mn1(V) * float4(g);
|
|
|
|
|
sh.L1.M0 = spherical_harmonics_L1_M0(V) * float4(g);
|
|
|
|
|
sh.L1.Mp1 = spherical_harmonics_L1_Mp1(V) * float4(g);
|
2024-03-19 19:01:05 +01:00
|
|
|
return sh;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-04 16:47:16 +02:00
|
|
|
struct VolumeResolveSample {
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 transmittance;
|
|
|
|
|
float3 scattering;
|
2023-08-04 16:47:16 +02:00
|
|
|
};
|
|
|
|
|
|
2025-04-14 13:46:41 +02:00
|
|
|
VolumeResolveSample volume_resolve(float3 ndc_P,
|
|
|
|
|
sampler3D transmittance_tx,
|
|
|
|
|
sampler3D scattering_tx)
|
2023-08-04 16:47:16 +02:00
|
|
|
{
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 coord = volume_screen_to_resolve(ndc_P);
|
2024-04-05 16:33:58 +02:00
|
|
|
|
|
|
|
|
/* 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. */
|
2023-08-04 16:47:16 +02:00
|
|
|
|
|
|
|
|
VolumeResolveSample volume;
|
|
|
|
|
volume.scattering = texture(scattering_tx, coord).rgb;
|
|
|
|
|
volume.transmittance = texture(transmittance_tx, coord).rgb;
|
|
|
|
|
return volume;
|
|
|
|
|
}
|