Eevee: Overhaul the volumetric system.

The system now uses several 3D textures in order to decouple every steps of the volumetric rendering.

See https://www.ea.com/frostbite/news/physically-based-unified-volumetric-rendering-in-frostbite for more details.

On the technical side, instead of using a compute shader to populate the 3D textures we use layered rendering with a geometry shader to render 1 fullscreen triangle per 3D texture slice.
This commit is contained in:
2017-10-24 14:49:00 +02:00
parent 1c0c63ce5b
commit 66d8f82b83
20 changed files with 798 additions and 593 deletions

View File

@@ -303,6 +303,17 @@ float get_view_z_from_depth(float depth)
}
}
float get_depth_from_view_z(float z)
{
if (ProjectionMatrix[3][3] == 0.0) {
float d = (-ProjectionMatrix[3][2] / z) - ProjectionMatrix[2][2];
return d * 0.5 + 0.5;
}
else {
return (z - viewvecs[0].z) / viewvecs[1].z;
}
}
vec2 get_uvs_from_view(vec3 view)
{
vec3 ndc = project_point(ProjectionMatrix, view);

View File

@@ -59,8 +59,7 @@ float direct_diffuse_sphere(LightData ld, vec3 N, vec4 l_vector)
return bsdf;
}
/* From Frostbite PBR Course
* http://www.frostbite.com/wp-content/uploads/2014/11/course_notes_moving_frostbite_to_pbr.pdf */
#ifdef USE_LTC
float direct_diffuse_rectangle(LightData ld, vec3 N, vec3 V, vec4 l_vector)
{
vec3 corners[4];
@@ -73,7 +72,7 @@ float direct_diffuse_rectangle(LightData ld, vec3 N, vec3 V, vec4 l_vector)
bsdf *= M_1_2PI;
return bsdf;
}
#endif
#if 0
float direct_diffuse_unit_disc(vec3 N, vec3 L)
@@ -104,6 +103,7 @@ vec3 direct_ggx_sun(LightData ld, vec3 N, vec3 V, float roughness, vec3 f0)
return F_schlick(f0, VH) * bsdf;
}
#ifdef USE_LTC
vec3 direct_ggx_sphere(LightData ld, vec3 N, vec3 V, vec4 l_vector, float roughness, vec3 f0)
{
vec3 L = l_vector.xyz / l_vector.w;
@@ -173,6 +173,7 @@ vec3 direct_ggx_rectangle(LightData ld, vec3 N, vec3 V, vec4 l_vector, float rou
return spec;
}
#endif
#if 0
float direct_ggx_disc(vec3 N, vec3 L)

View File

@@ -1,6 +1,8 @@
uniform sampler2DArray shadowTexture;
#define LAMPS_LIB
layout(std140) uniform shadow_block {
ShadowData shadows_data[MAX_SHADOW];
ShadowCubeData shadows_cube_data[MAX_SHADOW_CUBE];
@@ -244,7 +246,7 @@ float light_diffuse(LightData ld, vec3 N, vec3 V, vec4 l_vector)
}
#else
if (ld.l_type == SUN) {
return direct_diffuse_sun(ld, N, V);
return direct_diffuse_sun(ld, N);
}
else {
return direct_diffuse_point(N, l_vector);

View File

@@ -1,396 +1,38 @@
#ifdef VOLUMETRICS
/* Based on Frosbite Unified Volumetric.
* https://www.ea.com/frostbite/news/physically-based-unified-volumetric-rendering-in-frostbite */
#define NODETREE_EXEC
#define VOLUMETRIC_INTEGRATION_MAX_STEP 256
#define VOLUMETRIC_SHADOW_MAX_STEP 128
uniform ivec3 volumeTextureSize;
uniform vec3 volume_jitter;
uniform int light_count;
uniform vec2 volume_start_end;
uniform vec4 volume_samples_clamp;
#define volume_start volume_start_end.x
#define volume_end volume_start_end.y
#define volume_integration_steps volume_samples_clamp.x
#define volume_shadows_steps volume_samples_clamp.y
#define volume_sample_distribution volume_samples_clamp.z
#define volume_light_clamp volume_samples_clamp.w
#ifdef COLOR_TRANSMITTANCE
layout(location = 0) out vec4 outScattering;
layout(location = 1) out vec4 outTransmittance;
#else
out vec4 outScatteringTransmittance;
#endif
flat in int slice;
/* Warning: theses are not attributes, theses are global vars. */
vec3 worldPosition = vec3(0.0);
vec3 viewPosition = vec3(0.0);
vec3 viewNormal = vec3(0.0);
uniform sampler2D depthFull;
layout(location = 0) out vec4 volumeScattering;
layout(location = 1) out vec4 volumeExtinction;
layout(location = 2) out vec4 volumeEmissive;
layout(location = 3) out vec4 volumePhase;
void participating_media_properties(vec3 wpos, out vec3 extinction, out vec3 scattering, out vec3 emission, out float anisotropy)
{
#ifndef VOLUME_HOMOGENEOUS
worldPosition = wpos;
viewPosition = (ViewMatrix * vec4(wpos, 1.0)).xyz; /* warning, Perf. */
#endif
Closure cl = nodetree_exec();
scattering = cl.scatter;
emission = cl.emission;
anisotropy = cl.anisotropy;
extinction = max(vec3(1e-4), cl.absorption + cl.scatter);
}
vec3 participating_media_extinction(vec3 wpos)
{
#ifndef VOLUME_HOMOGENEOUS
worldPosition = wpos;
viewPosition = (ViewMatrix * vec4(wpos, 1.0)).xyz; /* warning, Perf. */
#endif
Closure cl = nodetree_exec();
return max(vec3(1e-4), cl.absorption + cl.scatter);
}
float phase_function_isotropic()
{
return 1.0 / (4.0 * M_PI);
}
float phase_function(vec3 v, vec3 l, float g)
{
#ifndef VOLUME_ISOTROPIC /* TODO Use this flag when only isotropic closures are used */
/* Henyey-Greenstein */
float cos_theta = dot(v, l);
g = clamp(g, -1.0 + 1e-3, 1.0 - 1e-3);
float sqr_g = g * g;
return (1- sqr_g) / (4.0 * M_PI * pow(1 + sqr_g - 2 * g * cos_theta, 3.0 / 2.0));
#else
return phase_function_isotropic();
#endif
}
float light_volume(LightData ld, vec4 l_vector)
{
float power;
float dist = max(1e-4, abs(l_vector.w - ld.l_radius));
/* TODO : Area lighting ? */
/* Removing Area Power. */
/* TODO : put this out of the shader. */
if (ld.l_type == AREA) {
power = 0.0962 * (ld.l_sizex * ld.l_sizey * 4.0f * M_PI);
}
else {
power = 0.0248 * (4.0 * ld.l_radius * ld.l_radius * M_PI * M_PI);
}
return min(power / (l_vector.w * l_vector.w), volume_light_clamp);
}
vec3 irradiance_volumetric(vec3 wpos)
{
IrradianceData ir_data = load_irradiance_cell(0, vec3(1.0));
vec3 irradiance = ir_data.cubesides[0] + ir_data.cubesides[1] + ir_data.cubesides[2];
ir_data = load_irradiance_cell(0, vec3(-1.0));
irradiance += ir_data.cubesides[0] + ir_data.cubesides[1] + ir_data.cubesides[2];
irradiance *= 0.16666666; /* 1/6 */
return irradiance;
}
vec3 light_volume_shadow(LightData ld, vec3 ray_wpos, vec4 l_vector, vec3 s_extinction)
{
#ifdef VOLUME_SHADOW
#ifdef VOLUME_HOMOGENEOUS
/* Simple extinction */
return exp(-s_extinction * l_vector.w);
#else
/* Heterogeneous volume shadows */
float dd = l_vector.w / volume_shadows_steps;
vec3 L = l_vector.xyz * l_vector.w;
vec3 shadow = vec3(1.0);
for (float s = 0.5; s < VOLUMETRIC_SHADOW_MAX_STEP && s < (volume_shadows_steps - 0.1); s += 1.0) {
vec3 pos = ray_wpos + L * (s / volume_shadows_steps);
vec3 s_extinction = participating_media_extinction(pos);
shadow *= exp(-s_extinction * dd);
}
return shadow;
#endif /* VOLUME_HOMOGENEOUS */
#else
return vec3(1.0);
#endif /* VOLUME_SHADOW */
}
float find_next_step(float iter, float noise)
{
float progress = (iter + noise) / volume_integration_steps;
float linear_split = mix(volume_start, volume_end, progress);
if (ProjectionMatrix[3][3] == 0.0) {
float exp_split = volume_start * pow(volume_end / volume_start, progress);
return mix(linear_split, exp_split, volume_sample_distribution);
}
else {
return linear_split;
}
}
/* Based on Frosbite Unified Volumetric.
* https://www.ea.com/frostbite/news/physically-based-unified-volumetric-rendering-in-frostbite */
void main()
{
vec2 uv = (gl_FragCoord.xy * 2.0) / ivec2(textureSize(depthFull, 0));
float scene_depth = texelFetch(depthFull, ivec2(gl_FragCoord.xy) * 2, 0).r; /* use the same depth as in the upsample step */
vec3 vpos = get_view_space_from_depth(uv, scene_depth);
vec3 wpos = (ViewMatrixInverse * vec4(vpos, 1.0)).xyz;
vec3 wdir = (ProjectionMatrix[3][3] == 0.0) ? normalize(cameraPos - wpos) : cameraForward;
/* Note: this is NOT the distance to the camera. */
float max_z = vpos.z;
/* project ray to clip plane so we can integrate in even steps in clip space. */
vec3 wdir_proj = wdir / abs(dot(cameraForward, wdir));
float wlen = length(wdir_proj);
/* Transmittance: How much light can get through. */
vec3 transmittance = vec3(1.0);
/* Scattering: Light that has been accumulated from scattered light sources. */
vec3 scattering = vec3(0.0);
vec3 ray_origin = (ProjectionMatrix[3][3] == 0.0)
? cameraPos
: (ViewMatrixInverse * vec4(get_view_space_from_depth(uv, 0.5), 1.0)).xyz;
#ifdef VOLUME_HOMOGENEOUS
/* Put it out of the loop for homogeneous media. */
vec3 s_extinction, s_scattering, s_emission;
float s_anisotropy;
participating_media_properties(vec3(0.0), s_extinction, s_scattering, s_emission, s_anisotropy);
#endif
/* Start from near clip. TODO make start distance an option. */
float rand = texture(utilTex, vec3(gl_FragCoord.xy / LUT_SIZE, 2.0)).r;
/* Less noisy but noticeable patterns, could work better with temporal AA. */
// float rand = (1.0 / 16.0) * float(((int(gl_FragCoord.x + gl_FragCoord.y) & 0x3) << 2) + (int(gl_FragCoord.x) & 0x3));
float dist = volume_start;
for (float i = 0.5; i < VOLUMETRIC_INTEGRATION_MAX_STEP && i < (volume_integration_steps - 0.1); ++i) {
float new_dist = max(max_z, find_next_step(rand, i));
float step = dist - new_dist; /* Marching step */
dist = new_dist;
vec3 ray_wpos = ray_origin + wdir_proj * dist;
#ifndef VOLUME_HOMOGENEOUS
vec3 s_extinction, s_scattering, s_emission;
float s_anisotropy;
participating_media_properties(ray_wpos, s_extinction, s_scattering, s_emission, s_anisotropy);
#endif
/* Evaluate each light */
vec3 Lscat = s_emission;
#ifdef VOLUME_LIGHTING /* Lights */
for (int i = 0; i < MAX_LIGHT && i < light_count; ++i) {
LightData ld = lights_data[i];
vec4 l_vector;
l_vector.xyz = ld.l_position - ray_wpos;
l_vector.w = length(l_vector.xyz);
float Vis = light_visibility(ld, ray_wpos, l_vector);
vec3 Li = ld.l_color * light_volume(ld, l_vector) * light_volume_shadow(ld, ray_wpos, l_vector, s_extinction);
Lscat += Li * Vis * s_scattering * phase_function(-wdir, l_vector.xyz / l_vector.w, s_anisotropy);
}
#endif
/* Environment : Average color. */
Lscat += irradiance_volumetric(wpos) * s_scattering * phase_function_isotropic();
/* Evaluate Scattering */
float s_len = wlen * step;
vec3 Tr = exp(-s_extinction * s_len);
/* integrate along the current step segment */
Lscat = (Lscat - Lscat * Tr) / s_extinction;
/* accumulate and also take into account the transmittance from previous steps */
scattering += transmittance * Lscat;
/* Evaluate transmittance to view independantely */
transmittance *= Tr;
if (dist <= max_z)
break;
}
#ifdef COLOR_TRANSMITTANCE
outScattering = vec4(scattering, 1.0);
outTransmittance = vec4(transmittance, 1.0);
#else
float mono_transmittance = dot(transmittance, vec3(1.0)) / 3.0;
outScatteringTransmittance = vec4(scattering, mono_transmittance);
#endif
}
#else /* STEP_UPSAMPLE */
out vec4 FragColor;
uniform sampler2D depthFull;
uniform sampler2D volumetricBuffer;
uniform mat4 ProjectionMatrix;
vec4 get_view_z_from_depth(vec4 depth)
{
vec4 d = 2.0 * depth - 1.0;
return -ProjectionMatrix[3][2] / (d + ProjectionMatrix[2][2]);
}
/* Store volumetric properties into the froxel textures. */
void main()
{
#if 0 /* 2 x 2 with bilinear */
ivec3 volume_cell = ivec3(gl_FragCoord.xy, slice);
vec3 ndc_cell = volume_to_ndc((vec3(volume_cell) + volume_jitter) / volumeTextureSize);
const vec4 bilinear_weights[4] = vec4[4](
vec4(9.0 / 16.0, 3.0 / 16.0, 3.0 / 16.0, 1.0 / 16.0 ),
vec4(3.0 / 16.0, 9.0 / 16.0, 1.0 / 16.0, 3.0 / 16.0 ),
vec4(3.0 / 16.0, 1.0 / 16.0, 9.0 / 16.0, 3.0 / 16.0 ),
vec4(1.0 / 16.0, 3.0 / 16.0, 3.0 / 16.0, 9.0 / 16.0 )
);
viewPosition = get_view_space_from_depth(ndc_cell.xy, ndc_cell.z);
worldPosition = transform_point(ViewMatrixInverse, viewPosition);
/* Depth aware upsampling */
vec4 depths;
ivec2 texel_co = ivec2(gl_FragCoord.xy * 0.5) * 2;
Closure cl = nodetree_exec();
/* TODO use textureGather on glsl 4.0 */
depths.x = texelFetchOffset(depthFull, texel_co, 0, ivec2(0, 0)).r;
depths.y = texelFetchOffset(depthFull, texel_co, 0, ivec2(2, 0)).r;
depths.z = texelFetchOffset(depthFull, texel_co, 0, ivec2(0, 2)).r;
depths.w = texelFetchOffset(depthFull, texel_co, 0, ivec2(2, 2)).r;
vec4 target_depth = texelFetch(depthFull, ivec2(gl_FragCoord.xy), 0).rrrr;
depths = get_view_z_from_depth(depths);
target_depth = get_view_z_from_depth(target_depth);
vec4 weights = 1.0 - step(0.05, abs(depths - target_depth));
/* Index in range [0-3] */
int pix_id = int(dot(mod(ivec2(gl_FragCoord.xy), 2), ivec2(1, 2)));
weights *= bilinear_weights[pix_id];
float weight_sum = dot(weights, vec4(1.0));
if (weight_sum == 0.0) {
weights.x = 1.0;
weight_sum = 1.0;
}
texel_co = ivec2(gl_FragCoord.xy * 0.5);
vec4 integration_result;
integration_result = texelFetchOffset(volumetricBuffer, texel_co, 0, ivec2(0, 0)) * weights.x;
integration_result += texelFetchOffset(volumetricBuffer, texel_co, 0, ivec2(1, 0)) * weights.y;
integration_result += texelFetchOffset(volumetricBuffer, texel_co, 0, ivec2(0, 1)) * weights.z;
integration_result += texelFetchOffset(volumetricBuffer, texel_co, 0, ivec2(1, 1)) * weights.w;
#else /* 4 x 4 */
/* Depth aware upsampling */
vec4 depths[4];
ivec2 texel_co = ivec2(gl_FragCoord.xy * 0.5) * 2;
/* TODO use textureGather on glsl 4.0 */
texel_co += ivec2(-2, -2);
depths[0].x = texelFetchOffset(depthFull, texel_co, 0, ivec2(0, 0)).r;
depths[0].y = texelFetchOffset(depthFull, texel_co, 0, ivec2(2, 0)).r;
depths[0].z = texelFetchOffset(depthFull, texel_co, 0, ivec2(0, 2)).r;
depths[0].w = texelFetchOffset(depthFull, texel_co, 0, ivec2(2, 2)).r;
texel_co += ivec2(4, 0);
depths[1].x = texelFetchOffset(depthFull, texel_co, 0, ivec2(0, 0)).r;
depths[1].y = texelFetchOffset(depthFull, texel_co, 0, ivec2(2, 0)).r;
depths[1].z = texelFetchOffset(depthFull, texel_co, 0, ivec2(0, 2)).r;
depths[1].w = texelFetchOffset(depthFull, texel_co, 0, ivec2(2, 2)).r;
texel_co += ivec2(-4, 4);
depths[2].x = texelFetchOffset(depthFull, texel_co, 0, ivec2(0, 0)).r;
depths[2].y = texelFetchOffset(depthFull, texel_co, 0, ivec2(2, 0)).r;
depths[2].z = texelFetchOffset(depthFull, texel_co, 0, ivec2(0, 2)).r;
depths[2].w = texelFetchOffset(depthFull, texel_co, 0, ivec2(2, 2)).r;
texel_co += ivec2(4, 0);
depths[3].x = texelFetchOffset(depthFull, texel_co, 0, ivec2(0, 0)).r;
depths[3].y = texelFetchOffset(depthFull, texel_co, 0, ivec2(2, 0)).r;
depths[3].z = texelFetchOffset(depthFull, texel_co, 0, ivec2(0, 2)).r;
depths[3].w = texelFetchOffset(depthFull, texel_co, 0, ivec2(2, 2)).r;
vec4 target_depth = texelFetch(depthFull, ivec2(gl_FragCoord.xy), 0).rrrr;
depths[0] = get_view_z_from_depth(depths[0]);
depths[1] = get_view_z_from_depth(depths[1]);
depths[2] = get_view_z_from_depth(depths[2]);
depths[3] = get_view_z_from_depth(depths[3]);
target_depth = get_view_z_from_depth(target_depth);
vec4 weights[4];
weights[0] = 1.0 - step(0.05, abs(depths[0] - target_depth));
weights[1] = 1.0 - step(0.05, abs(depths[1] - target_depth));
weights[2] = 1.0 - step(0.05, abs(depths[2] - target_depth));
weights[3] = 1.0 - step(0.05, abs(depths[3] - target_depth));
float weight_sum;
weight_sum = dot(weights[0], vec4(1.0));
weight_sum += dot(weights[1], vec4(1.0));
weight_sum += dot(weights[2], vec4(1.0));
weight_sum += dot(weights[3], vec4(1.0));
if (weight_sum == 0.0) {
weights[0].x = 1.0;
weight_sum = 1.0;
}
texel_co = ivec2(gl_FragCoord.xy * 0.5);
vec4 integration_result;
texel_co += ivec2(-1, -1);
integration_result = texelFetchOffset(volumetricBuffer, texel_co, 0, ivec2(0, 0)) * weights[0].x;
integration_result += texelFetchOffset(volumetricBuffer, texel_co, 0, ivec2(1, 0)) * weights[0].y;
integration_result += texelFetchOffset(volumetricBuffer, texel_co, 0, ivec2(0, 1)) * weights[0].z;
integration_result += texelFetchOffset(volumetricBuffer, texel_co, 0, ivec2(1, 1)) * weights[0].w;
texel_co += ivec2(2, 0);
integration_result += texelFetchOffset(volumetricBuffer, texel_co, 0, ivec2(0, 0)) * weights[1].x;
integration_result += texelFetchOffset(volumetricBuffer, texel_co, 0, ivec2(1, 0)) * weights[1].y;
integration_result += texelFetchOffset(volumetricBuffer, texel_co, 0, ivec2(0, 1)) * weights[1].z;
integration_result += texelFetchOffset(volumetricBuffer, texel_co, 0, ivec2(1, 1)) * weights[1].w;
texel_co += ivec2(-2, 2);
integration_result += texelFetchOffset(volumetricBuffer, texel_co, 0, ivec2(0, 0)) * weights[2].x;
integration_result += texelFetchOffset(volumetricBuffer, texel_co, 0, ivec2(1, 0)) * weights[2].y;
integration_result += texelFetchOffset(volumetricBuffer, texel_co, 0, ivec2(0, 1)) * weights[2].z;
integration_result += texelFetchOffset(volumetricBuffer, texel_co, 0, ivec2(1, 1)) * weights[2].w;
texel_co += ivec2(2, 0);
integration_result += texelFetchOffset(volumetricBuffer, texel_co, 0, ivec2(0, 0)) * weights[3].x;
integration_result += texelFetchOffset(volumetricBuffer, texel_co, 0, ivec2(1, 0)) * weights[3].y;
integration_result += texelFetchOffset(volumetricBuffer, texel_co, 0, ivec2(0, 1)) * weights[3].z;
integration_result += texelFetchOffset(volumetricBuffer, texel_co, 0, ivec2(1, 1)) * weights[3].w;
#endif
FragColor = integration_result / weight_sum;
volumeScattering = vec4(cl.scatter, 1.0);
volumeExtinction = vec4(max(vec3(1e-4), cl.absorption + cl.scatter), 1.0);
volumeEmissive = vec4(cl.emission, 1.0);
volumePhase = vec4(cl.anisotropy, vec3(1.0));
}
#endif

View File

@@ -0,0 +1,25 @@
layout(triangles) in;
layout(triangle_strip, max_vertices=3) out;
in vec4 vPos[];
flat out int slice;
/* This is just a pass-through geometry shader that send the geometry
* to the layer corresponding to it's depth. */
void main() {
gl_Layer = slice = int(vPos[0].z);
gl_Position = vPos[0].xyww;
EmitVertex();
gl_Position = vPos[1].xyww;
EmitVertex();
gl_Position = vPos[2].xyww;
EmitVertex();
EndPrimitive();
}

View File

@@ -0,0 +1,63 @@
/* Based on Frosbite Unified Volumetric.
* https://www.ea.com/frostbite/news/physically-based-unified-volumetric-rendering-in-frostbite */
/* Step 3 : Integrate for each froxel the final amount of light
* scattered back to the viewer and the amout of transmittance. */
uniform sampler3D volumeScattering; /* Result of the scatter step */
uniform sampler3D volumeExtinction;
flat in int slice;
layout(location = 0) out vec4 finalScattering;
layout(location = 1) out vec4 finalTransmittance;
void main()
{
/* Start with full transmittance and no scattered light. */
finalScattering = vec4(0.0);
finalTransmittance = vec4(1.0);
vec3 tex_size = vec3(textureSize(volumeScattering, 0).xyz);
/* Compute view ray. */
vec2 uvs = gl_FragCoord.xy / tex_size.xy;
vec3 ndc_cell = volume_to_ndc(vec3(uvs, 1e-5));
vec3 view_cell = get_view_space_from_depth(ndc_cell.xy, ndc_cell.z);
/* Ortho */
float prev_ray_len = view_cell.z;
float orig_ray_len = 1.0;
/* Persp */
if (ProjectionMatrix[3][3] == 0.0) {
prev_ray_len = length(view_cell);
orig_ray_len = prev_ray_len / view_cell.z;
}
/* Without compute shader and arbitrary write we need to
* accumulate from the beginning of the ray for each cell. */
float integration_end = float(slice);
for (int i = 0; i < slice; ++i) {
ivec3 volume_cell = ivec3(gl_FragCoord.xy, i);
vec4 Lscat = texelFetch(volumeScattering, volume_cell, 0);
vec4 s_extinction = texelFetch(volumeExtinction, volume_cell, 0);
float cell_depth = volume_z_to_view_z((float(i) + 1.0) / tex_size.z);
float ray_len = orig_ray_len * cell_depth;
/* Evaluate Scattering */
float s_len = abs(ray_len - prev_ray_len);
prev_ray_len = ray_len;
vec4 Tr = exp(-s_extinction * s_len);
/* integrate along the current step segment */
Lscat = (Lscat - Lscat * Tr) / s_extinction;
/* accumulate and also take into account the transmittance from previous steps */
finalScattering += finalTransmittance * Lscat;
finalTransmittance *= Tr;
}
}

View File

@@ -0,0 +1,134 @@
/* Based on Frosbite Unified Volumetric.
* https://www.ea.com/frostbite/news/physically-based-unified-volumetric-rendering-in-frostbite */
uniform float volume_light_clamp;
uniform vec3 volume_param; /* Parameters to the volume Z equation */
uniform vec2 volume_uv_ratio; /* To convert volume uvs to screen uvs */
/* Volume slice to view space depth. */
float volume_z_to_view_z(float z)
{
if (ProjectionMatrix[3][3] == 0.0) {
/* Exponential distribution */
return (exp2(z / volume_param.z) - volume_param.x) / volume_param.y;
}
else {
/* Linear distribution */
return mix(volume_param.x, volume_param.y, z);
}
}
float view_z_to_volume_z(float depth)
{
if (ProjectionMatrix[3][3] == 0.0) {
/* Exponential distribution */
return volume_param.z * log2(depth * volume_param.y + volume_param.x);
}
else {
/* Linear distribution */
return (depth - volume_param.x) * volume_param.z;
}
}
/* Volume texture normalized coordinates to NDC (special range [0, 1]). */
vec3 volume_to_ndc(vec3 cos)
{
cos.z = volume_z_to_view_z(cos.z);
cos.z = get_depth_from_view_z(cos.z);
cos.xy /= volume_uv_ratio;
return cos;
}
vec3 ndc_to_volume(vec3 cos)
{
cos.z = get_view_z_from_depth(cos.z);
cos.z = view_z_to_volume_z(cos.z);
cos.xy *= volume_uv_ratio;
return cos;
}
float phase_function_isotropic()
{
return 1.0 / (4.0 * M_PI);
}
float phase_function(vec3 v, vec3 l, float g)
{
/* Henyey-Greenstein */
float cos_theta = dot(v, l);
g = clamp(g, -1.0 + 1e-3, 1.0 - 1e-3);
float sqr_g = g * g;
return (1- sqr_g) / (4.0 * M_PI * pow(1 + sqr_g - 2 * g * cos_theta, 3.0 / 2.0));
}
#ifdef LAMPS_LIB
vec3 light_volume(LightData ld, vec4 l_vector)
{
float power;
float dist = max(1e-4, abs(l_vector.w - ld.l_radius));
/* TODO : Area lighting ? */
/* XXX : Removing Area Power. */
/* TODO : put this out of the shader. */
if (ld.l_type == AREA) {
power = 0.0962 * (ld.l_sizex * ld.l_sizey * 4.0f * M_PI);
}
else {
power = 0.0248 * (4.0 * ld.l_radius * ld.l_radius * M_PI * M_PI);
}
/* OPTI: find a better way than calculating this on the fly */
float lum = dot(ld.l_color, vec3(0.3, 0.6, 0.1)); /* luminance approx. */
vec3 tint = (lum > 0.0) ? ld.l_color / lum : vec3(1.0); /* normalize lum. to isolate hue+sat */
lum = min(lum * power / (l_vector.w * l_vector.w), volume_light_clamp);
return tint * lum;
}
#define VOLUMETRIC_SHADOW_MAX_STEP 32.0
uniform float volume_shadows_steps;
vec3 participating_media_extinction(vec3 wpos, sampler3D volume_extinction)
{
/* Waiting for proper volume shadowmaps and out of frustum shadow map. */
vec3 ndc = project_point(ViewProjectionMatrix, wpos);
vec3 volume_co = ndc_to_volume(ndc * 0.5 + 0.5);
/* Let the texture be clamped to edge. This reduce visual glitches. */
return texture(volume_extinction, volume_co).rgb;
}
vec3 light_volume_shadow(LightData ld, vec3 ray_wpos, vec4 l_vector, sampler3D volume_extinction)
{
#if defined(VOLUME_SHADOW)
/* Heterogeneous volume shadows */
float dd = l_vector.w / volume_shadows_steps;
vec3 L = l_vector.xyz * l_vector.w;
vec3 shadow = vec3(1.0);
for (float s = 0.5; s < VOLUMETRIC_SHADOW_MAX_STEP && s < (volume_shadows_steps - 0.1); s += 1.0) {
vec3 pos = ray_wpos + L * (s / volume_shadows_steps);
vec3 s_extinction = participating_media_extinction(pos, volume_extinction);
shadow *= exp(-s_extinction * dd);
}
return shadow;
#else
return vec3(1.0);
#endif /* VOLUME_SHADOW */
}
#endif
#ifdef IRRADIANCE_LIB
vec3 irradiance_volumetric(vec3 wpos)
{
IrradianceData ir_data = load_irradiance_cell(0, vec3(1.0));
vec3 irradiance = ir_data.cubesides[0] + ir_data.cubesides[1] + ir_data.cubesides[2];
ir_data = load_irradiance_cell(0, vec3(-1.0));
irradiance += ir_data.cubesides[0] + ir_data.cubesides[1] + ir_data.cubesides[2];
irradiance *= 0.16666666; /* 1/6 */
return irradiance;
}
#endif

View File

@@ -0,0 +1,27 @@
/* Based on Frosbite Unified Volumetric.
* https://www.ea.com/frostbite/news/physically-based-unified-volumetric-rendering-in-frostbite */
/* Step 4 : Apply final integration on top of the scene color.
* Note that we do the blending ourself instead of relying
* on hardware blending which would require 2 pass. */
uniform sampler3D inScattering;
uniform sampler3D inTransmittance;
uniform sampler2D inSceneColor;
uniform sampler2D inSceneDepth;
out vec4 FragColor;
void main()
{
vec2 uvs = gl_FragCoord.xy / vec2(textureSize(inSceneDepth, 0));
vec3 volume_cos = ndc_to_volume(vec3(uvs, texture(inSceneDepth, uvs).r));
vec3 scene_color = texture(inSceneColor, uvs).rgb;
vec3 scattering = texture(inScattering, volume_cos).rgb;
vec3 transmittance = texture(inTransmittance, volume_cos).rgb;
FragColor = vec4(scene_color * transmittance + scattering, 1.0);
}

View File

@@ -0,0 +1,83 @@
/* Based on Frosbite Unified Volumetric.
* https://www.ea.com/frostbite/news/physically-based-unified-volumetric-rendering-in-frostbite */
/* Step 2 : Evaluate all light scattering for each froxels.
* Also do the temporal reprojection to fight aliasing artifacts. */
uniform sampler3D volumeScattering;
uniform sampler3D volumeExtinction;
uniform sampler3D volumeEmission;
uniform sampler3D volumePhase;
uniform sampler3D historyScattering;
uniform sampler3D historyTransmittance;
uniform vec3 volume_jitter;
uniform float volume_history_alpha;
uniform int light_count;
uniform mat4 PastViewProjectionMatrix;
flat in int slice;
layout(location = 0) out vec4 outScattering;
layout(location = 1) out vec4 outTransmittance;
#define VOLUME_LIGHTING
void main()
{
vec3 volume_tex_size = vec3(textureSize(volumeScattering, 0));
ivec3 volume_cell = ivec3(gl_FragCoord.xy, slice);
/* Emission */
outScattering = texelFetch(volumeEmission, volume_cell, 0);
outTransmittance = texelFetch(volumeExtinction, volume_cell, 0);
vec3 s_scattering = texelFetch(volumeScattering, volume_cell, 0).rgb;
vec3 volume_ndc = volume_to_ndc((vec3(volume_cell) + volume_jitter) / volume_tex_size);
vec3 worldPosition = get_world_space_from_depth(volume_ndc.xy, volume_ndc.z);
vec3 wdir = cameraVec;
vec2 phase = texelFetch(volumePhase, volume_cell, 0).rg;
float s_anisotropy = phase.x / phase.y;
/* Environment : Average color. */
outScattering.rgb += irradiance_volumetric(worldPosition) * s_scattering * phase_function_isotropic();
#ifdef VOLUME_LIGHTING /* Lights */
for (int i = 0; i < MAX_LIGHT && i < light_count; ++i) {
LightData ld = lights_data[i];
vec4 l_vector;
l_vector.xyz = ld.l_position - worldPosition;
l_vector.w = length(l_vector.xyz);
float Vis = light_visibility(ld, worldPosition, l_vector);
vec3 Li = light_volume(ld, l_vector) * light_volume_shadow(ld, worldPosition, l_vector, volumeExtinction);
outScattering.rgb += Li * Vis * s_scattering * phase_function(-wdir, l_vector.xyz / l_vector.w, s_anisotropy);
}
#endif
/* Temporal supersampling */
/* Note : this uses the cell non-jittered position (texel center). */
vec3 curr_ndc = volume_to_ndc(vec3(gl_FragCoord.xy, float(slice) + 0.5) / volume_tex_size);
vec3 wpos = get_world_space_from_depth(curr_ndc.xy, curr_ndc.z);
vec3 prev_ndc = project_point(PastViewProjectionMatrix, wpos);
vec3 prev_volume = ndc_to_volume(prev_ndc * 0.5 + 0.5);
if ((volume_history_alpha > 0.0) && all(greaterThan(prev_volume, vec3(0.0))) && all(lessThan(prev_volume, vec3(1.0)))) {
vec4 h_Scattering = texture(historyScattering, prev_volume);
vec4 h_Transmittance = texture(historyTransmittance, prev_volume);
outScattering = mix(outScattering, h_Scattering, volume_history_alpha);
outTransmittance = mix(outTransmittance, h_Transmittance, volume_history_alpha);
}
/* Catch NaNs */
if (any(isnan(outScattering)) || any(isnan(outTransmittance))) {
outScattering = vec4(0.0);
outTransmittance = vec4(0.0);
}
}

View File

@@ -0,0 +1,27 @@
out vec4 vPos;
void main()
{
/* Generate Triangle : less memory fetches from a VBO */
int v_id = gl_VertexID % 3; /* Vertex Id */
int t_id = gl_VertexID / 3; /* Triangle Id */
/* Crappy diagram
* ex 1
* | \
* | \
* 1 | \
* | \
* | \
* 0 | \
* | \
* | \
* -1 0 --------------- 2
* -1 0 1 ex
**/
vPos.x = float(v_id / 2) * 4.0 - 1.0; /* int divisor round down */
vPos.y = float(v_id % 2) * 4.0 - 1.0;
vPos.z = float(t_id);
vPos.w = 1.0;
}