This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/draw/engines/eevee/shaders/volumetric_lib.glsl
Lukas Stockner 5505ba8d47 Cycles/Eevee: Implement disk and ellipse shapes for area lamps
The implementation is pretty straightforward.

In Cycles, sampling the shapes is currently done w.r.t. area instead of solid angle.

There is a paper on solid angle sampling for disks [1], but the described algorithm is based on
simply sampling the enclosing square and rejecting samples outside of the disk, which is not exactly
great for Cycles' RNG (we'd need to setup a LCG for the repeated sampling) and for GPU divergence.

Even worse, the algorithm is only defined for disks. For ellipses, the basic idea still works, but a
way to analytically calculate the solid angle is required. This is technically possible [2], but the
calculation is extremely complex and still requires a lookup table for the Heuman Lambda function.

Therefore, I've decided to not implement that for now, we could still look into it later on.

In Eevee, the code uses the existing ltc_evaluate_disk to implement the lighting calculations.

[1]: "Solid Angle Sampling of Disk and Cylinder Lights"
[2]: "Analytical solution for the solid angle subtended at any point by an ellipse via a point source radiation vector potential"

Reviewers: sergey, brecht, fclem

Differential Revision: https://developer.blender.org/D3171
2018-05-24 16:43:47 +02:00

154 lines
4.4 KiB
GLSL

/* Based on Frosbite Unified Volumetric.
* https://www.ea.com/frostbite/news/physically-based-unified-volumetric-rendering-in-frostbite */
/* 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 / volDepthParameters.z) - volDepthParameters.x) / volDepthParameters.y;
}
else {
/* Linear distribution */
return mix(volDepthParameters.x, volDepthParameters.y, z);
}
}
float view_z_to_volume_z(float depth)
{
if (ProjectionMatrix[3][3] == 0.0) {
/* Exponential distribution */
return volDepthParameters.z * log2(depth * volDepthParameters.y + volDepthParameters.x);
}
else {
/* Linear distribution */
return (depth - volDepthParameters.x) * volDepthParameters.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 /= volCoordScale.xy;
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 *= volCoordScale.xy;
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) / max(1e-8, 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;
/* TODO : Area lighting ? */
/* XXX : Removing Area Power. */
/* TODO : put this out of the shader. */
/* See eevee_light_setup(). */
if (ld.l_type == AREA_RECT || ld.l_type == AREA_ELLIPSE) {
power = (ld.l_sizex * ld.l_sizey * 4.0 * M_PI) * (1.0 / 80.0);
if (ld.l_type == AREA_ELLIPSE) {
power *= M_PI * 0.25;
}
power *= 20.0 * max(0.0, dot(-ld.l_forward, l_vector.xyz / l_vector.w)); /* XXX ad hoc, empirical */
}
else if (ld.l_type == SUN) {
power = (4.0f * ld.l_radius * ld.l_radius * M_2PI) * (1.0 / 12.5); /* Removing area light power*/
power *= M_2PI * 0.78; /* Matching cycles with point light. */
}
else {
power = (4.0 * ld.l_radius * ld.l_radius) * (1.0 /10.0);
}
/* 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 */
power /= (l_vector.w * l_vector.w);
lum = min(lum * power, volLightClamp);
return tint * lum;
}
#define VOLUMETRIC_SHADOW_MAX_STEP 32.0
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 / volShadowSteps;
vec3 L = l_vector.xyz * l_vector.w;
vec3 shadow = vec3(1.0);
for (float s = 0.5; s < VOLUMETRIC_SHADOW_MAX_STEP && s < (volShadowSteps - 0.1); s += 1.0) {
vec3 pos = ray_wpos + L * (s / volShadowSteps);
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)
{
#ifdef IRRADIANCE_HL2
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;
#else
return vec3(0.0);
#endif
}
#endif
uniform sampler3D inScattering;
uniform sampler3D inTransmittance;
vec4 volumetric_resolve(vec4 scene_color, vec2 frag_uvs, float frag_depth)
{
vec3 volume_cos = ndc_to_volume(vec3(frag_uvs, frag_depth));
vec3 scattering = texture(inScattering, volume_cos).rgb;
vec3 transmittance = texture(inTransmittance, volume_cos).rgb;
return vec4(scene_color.rgb * transmittance + scattering, scene_color.a);
}