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:
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
134
source/blender/draw/engines/eevee/shaders/volumetric_lib.glsl
Normal file
134
source/blender/draw/engines/eevee/shaders/volumetric_lib.glsl
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user