VolumetricLighting/Assets/AreaLight/Shaders/AreaLight.cginc
2016-11-01 13:25:56 +01:00

224 lines
5.5 KiB
HLSL

// Based on 'Real-Time Polygonal-Light Shading with Linearly Transformed Cosines'
// https://labs.unity.com/article/real-time-polygonal-light-shading-linearly-transformed-cosines
#if AREA_LIGHT_ENABLE_DIFFUSE
sampler2D _TransformInv_Diffuse;
#endif
sampler2D _TransformInv_Specular;
sampler2D _AmpDiffAmpSpecFresnel;
float4x4 _LightVerts;
half IntegrateEdge(half3 v1, half3 v2)
{
half d = dot(v1,v2);
half theta = acos(max(-0.9999, dot(v1,v2)));
half theta_sintheta = theta / sin(theta);
return theta_sintheta * (v1.x*v2.y - v1.y*v2.x);
}
// Baum's equation
// Expects non-normalized vertex positions
half PolygonRadiance(half4x3 L)
{
// detect clipping config
uint config = 0;
if (L[0].z > 0) config += 1;
if (L[1].z > 0) config += 2;
if (L[2].z > 0) config += 4;
if (L[3].z > 0) config += 8;
// The fifth vertex for cases when clipping cuts off one corner.
// Due to a compiler bug, copying L into a vector array with 5 rows
// messes something up, so we need to stick with the matrix + the L4 vertex.
half3 L4 = L[3];
// This switch is surprisingly fast. Tried replacing it with a lookup array of vertices.
// Even though that replaced the switch with just some indexing and no branches, it became
// way, way slower - mem fetch stalls?
// clip
uint n = 0;
switch(config)
{
case 0: // clip all
break;
case 1: // V1 clip V2 V3 V4
n = 3;
L[1] = -L[1].z * L[0] + L[0].z * L[1];
L[2] = -L[3].z * L[0] + L[0].z * L[3];
break;
case 2: // V2 clip V1 V3 V4
n = 3;
L[0] = -L[0].z * L[1] + L[1].z * L[0];
L[2] = -L[2].z * L[1] + L[1].z * L[2];
break;
case 3: // V1 V2 clip V3 V4
n = 4;
L[2] = -L[2].z * L[1] + L[1].z * L[2];
L[3] = -L[3].z * L[0] + L[0].z * L[3];
break;
case 4: // V3 clip V1 V2 V4
n = 3;
L[0] = -L[3].z * L[2] + L[2].z * L[3];
L[1] = -L[1].z * L[2] + L[2].z * L[1];
break;
case 5: // V1 V3 clip V2 V4: impossible
break;
case 6: // V2 V3 clip V1 V4
n = 4;
L[0] = -L[0].z * L[1] + L[1].z * L[0];
L[3] = -L[3].z * L[2] + L[2].z * L[3];
break;
case 7: // V1 V2 V3 clip V4
n = 5;
L4 = -L[3].z * L[0] + L[0].z * L[3];
L[3] = -L[3].z * L[2] + L[2].z * L[3];
break;
case 8: // V4 clip V1 V2 V3
n = 3;
L[0] = -L[0].z * L[3] + L[3].z * L[0];
L[1] = -L[2].z * L[3] + L[3].z * L[2];
L[2] = L[3];
break;
case 9: // V1 V4 clip V2 V3
n = 4;
L[1] = -L[1].z * L[0] + L[0].z * L[1];
L[2] = -L[2].z * L[3] + L[3].z * L[2];
break;
case 10: // V2 V4 clip V1 V3: impossible
break;
case 11: // V1 V2 V4 clip V3
n = 5;
L[3] = -L[2].z * L[3] + L[3].z * L[2];
L[2] = -L[2].z * L[1] + L[1].z * L[2];
break;
case 12: // V3 V4 clip V1 V2
n = 4;
L[1] = -L[1].z * L[2] + L[2].z * L[1];
L[0] = -L[0].z * L[3] + L[3].z * L[0];
break;
case 13: // V1 V3 V4 clip V2
n = 5;
L[3] = L[2];
L[2] = -L[1].z * L[2] + L[2].z * L[1];
L[1] = -L[1].z * L[0] + L[0].z * L[1];
break;
case 14: // V2 V3 V4 clip V1
n = 5;
L4 = -L[0].z * L[3] + L[3].z * L[0];
L[0] = -L[0].z * L[1] + L[1].z * L[0];
break;
case 15: // V1 V2 V3 V4
n = 4;
break;
}
if (n == 0)
return 0;
// normalize
L[0] = normalize(L[0]);
L[1] = normalize(L[1]);
L[2] = normalize(L[2]);
if(n == 3)
L[3] = L[0];
else
{
L[3] = normalize(L[3]);
if (n == 4)
L4 = L[0];
else
L4 = normalize(L4);
}
// integrate
half sum = 0;
sum += IntegrateEdge(L[0], L[1]);
sum += IntegrateEdge(L[1], L[2]);
sum += IntegrateEdge(L[2], L[3]);
if(n >= 4)
sum += IntegrateEdge(L[3], L4);
if(n == 5)
sum += IntegrateEdge(L4, L[0]);
sum *= 0.15915; // 1/2pi
return max(0, sum);
}
half TransformedPolygonRadiance(half4x3 L, half2 uv, sampler2D transformInv, half amplitude)
{
// Get the inverse LTC matrix M
half3x3 Minv = 0;
Minv._m22 = 1;
Minv._m00_m02_m11_m20 = tex2D(transformInv, uv);
// Transform light vertices into diffuse configuration
half4x3 LTransformed = mul(L, Minv);
// Polygon radiance in transformed configuration - specular
return PolygonRadiance(LTransformed) * amplitude;
}
half3 CalculateLight (half3 position, half3 diffColor, half3 specColor, half oneMinusRoughness, half3 N,
half3 lightPos, half3 lightColor)
{
#if AREA_LIGHT_SHADOWS
half shadow = Shadow(position);
if (shadow == 0.0)
return 0;
#endif
// TODO: larger and smaller values cause artifacts - why?
oneMinusRoughness = clamp(oneMinusRoughness, 0.01, 0.93);
half roughness = 1 - oneMinusRoughness;
half3 V = normalize(_WorldSpaceCameraPos - position);
// Construct orthonormal basis around N, aligned with V
half3x3 basis;
basis[0] = normalize(V - N * dot(V, N));
basis[1] = normalize(cross(N, basis[0]));
basis[2] = N;
// Transform light vertices into that space
half4x3 L;
L = _LightVerts - half4x3(position, position, position, position);
L = mul(L, transpose(basis));
// UVs for sampling the LUTs
half theta = acos(dot(V, N));
half2 uv = half2(roughness, theta/1.57);
half3 AmpDiffAmpSpecFresnel = tex2D(_AmpDiffAmpSpecFresnel, uv).rgb;
half3 result = 0;
#if AREA_LIGHT_ENABLE_DIFFUSE
half diffuseTerm = TransformedPolygonRadiance(L, uv, _TransformInv_Diffuse, AmpDiffAmpSpecFresnel.x);
result = diffuseTerm * diffColor;
#endif
half specularTerm = TransformedPolygonRadiance(L, uv, _TransformInv_Specular, AmpDiffAmpSpecFresnel.y);
half fresnelTerm = specColor + (1.0 - specColor) * AmpDiffAmpSpecFresnel.z;
result += specularTerm * fresnelTerm * UNITY_PI;
#if AREA_LIGHT_SHADOWS
result *= shadow;
#endif
return result * lightColor;
}