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

190 lines
7.0 KiB
HLSL

// Based on 'Real Shading in Unreal Engine 4'
// http://blog.selfshadow.com/publications/s2013-shading-course/#course_content
#include "TubeLightAttenuation.cginc"
#ifndef SHADOW_PLANES
#define SHADOW_PLANES 1
#endif
#if SHADOW_PLANES
#include "TubeLightShadowPlanes.cginc"
#endif
#define SHARP_EDGE_FIX 1
void SphereLightToPointLight(float3 pos, float3 lightPos, float3 eyeVec, half3 normal, float sphereRad, half rangeSqInv, inout UnityLight light, out half lightDist)
{
half3 viewDir = -eyeVec;
half3 r = reflect (viewDir, normal);
float3 L = lightPos - pos;
float3 centerToRay = dot (L, r) * r - L;
float3 closestPoint = L + centerToRay * saturate(sphereRad / length(centerToRay));
lightDist = length(closestPoint);
light.dir = closestPoint / lightDist;
half distLSq = dot(L, L);
light.ndotl = saturate(dot(normal, L/sqrt(distLSq)));
float distNorm = distLSq * rangeSqInv;
float atten = AttenuationToZero(distNorm);
light.color *= atten;
}
void TubeLightToPointLight(float3 pos, float3 tubeStart, float3 tubeEnd, float3 normal, float tubeRad, float rangeSqInv, float3 representativeDir, float3 lightColor, out float3 outLightColor, out float3 outLightDir, out float outNdotL, out half outLightDist)
{
half3 N = normal;
float3 L0 = tubeStart - pos;
float3 L1 = tubeEnd - pos;
float L0dotL0 = dot(L0, L0);
float distL0 = sqrt(L0dotL0);
float distL1 = length(L1);
float NdotL0 = dot(L0, N) / (2.0 * distL0);
float NdotL1 = dot(L1, N) / (2.0 * distL1);
outNdotL = saturate(NdotL0 + NdotL1);
float3 Ldir = L1 - L0;
float RepdotL0 = dot(representativeDir, L0);
float RepdotLdir = dot(representativeDir, Ldir);
float L0dotLdir = dot(L0, Ldir);
float LdirdotLdir = dot(Ldir, Ldir);
float distLdir = sqrt(LdirdotLdir);
#if SHARP_EDGE_FIX
// There's a very visible discontinuity if we just take the closest distance to ray,
// as the original paper suggests. This is an attempt to fix it, but it gets slightly more expensive and
// has its own artifact, although this time at least C0 smooth.
// Smallest angle to ray
float t = (L0dotLdir * RepdotL0 - L0dotL0 * RepdotLdir) / (L0dotLdir * RepdotLdir - LdirdotLdir * RepdotL0);
t = saturate(t);
// As representativeDir becomes parallel (well, in some plane) to Ldir and then points away, t flips from 0 to 1 (or vv) and a discontinuity shows up.
// Counteract by detecting that relative angle/position and flip t. The discontinuity in t moves to the back side.
float3 L0xLdir = cross(L0, Ldir);
float3 LdirxR = cross(Ldir, representativeDir);
float RepAtLdir = dot(L0xLdir, LdirxR);
// RepAtLdir is negative if R points away from Ldir.
// TODO: check if lerp below is indeed cheaper.
// if (RepAtLdir < 0)
// t = 1 - t;
t = lerp(1 - t, t, step(0, RepAtLdir));
#else
// Original by Karis
// Closest distance to ray
float t = (RepdotL0 * RepdotLdir - L0dotLdir) / (distLdir * distLdir - RepdotLdir * RepdotLdir);
t = saturate(t);
#endif
float3 closestPoint = L0 + Ldir * t;
float3 centerToRay = dot(closestPoint, representativeDir) * representativeDir - closestPoint;
closestPoint = closestPoint + centerToRay * saturate(tubeRad / length(centerToRay));
outLightDist = length(closestPoint);
outLightDir = closestPoint / outLightDist;
float distNorm = 0.5f * (distL0 * distL1 + dot(L0, L1)) * rangeSqInv;
outLightColor = lightColor * AttenuationToZero(distNorm);
}
void TubeLightToPointLight(float3 pos, float3 tubeStart, float3 tubeEnd, float3 eyeVec, float3 normal, float tubeRad, float rangeSqInv, float3 lightColor, out float3 outLightColor, out float3 outLightDir, out float outNdotL, out half outLightDist)
{
half3 viewDir = -eyeVec;
half3 representativeDir = reflect (viewDir, normal);
TubeLightToPointLight(pos, tubeStart, tubeEnd, normal, tubeRad, rangeSqInv, representativeDir, lightColor, outLightColor, outLightDir, outNdotL, outLightDist);
}
inline half GGXTerm_Area (half NdotH, half roughness, half lightDist, half lightRadius)
{
half a = roughness * roughness;
half a2 = a * a;
half d = NdotH * NdotH * (a2 - 1.f) + 1.f;
d = max(d, 0.000001);
half aP = saturate( lightRadius / (lightDist*2.0) + a);
half aP2 = aP * aP;
return a2 * a2 / (UNITY_PI * d * d * aP2);
}
half4 BRDF1_Unity_PBS_Area (half3 diffColor, half3 specColor, half oneMinusReflectivity, half oneMinusRoughness,
half3 normal, half3 viewDir,
UnityLight light, UnityIndirect gi, half lightDist, half lightRadius)
{
half roughness = 1-oneMinusRoughness;
half3 halfDir = Unity_SafeNormalize (light.dir + viewDir);
half nl = light.ndotl;
half nh = BlinnTerm (normal, halfDir);
half nv = DotClamped (normal, viewDir);
half lv = DotClamped (light.dir, viewDir);
half lh = DotClamped (light.dir, halfDir);
half V = SmithGGXVisibilityTerm (nl, nv, roughness);
half D = GGXTerm_Area (nh, roughness, lightDist, lightRadius);
half nlPow5 = Pow5 (1-nl);
half nvPow5 = Pow5 (1-nv);
half Fd90 = 0.5 + 2 * lh * lh * roughness;
half disneyDiffuse = (1 + (Fd90-1) * nlPow5) * (1 + (Fd90-1) * nvPow5);
// HACK: theoretically we should divide by Pi diffuseTerm and not multiply specularTerm!
// BUT 1) that will make shader look significantly darker than Legacy ones
// and 2) on engine side "Non-important" lights have to be divided by Pi to in cases when they are injected into ambient SH
// NOTE: multiplication by Pi is part of single constant together with 1/4 now
half specularTerm = (V * D) * (UNITY_PI/4); // Torrance-Sparrow model, Fresnel is applied later (for optimization reasons)
if (IsGammaSpace())
specularTerm = sqrt(max(1e-4h, specularTerm));
specularTerm = max(0, specularTerm * nl);
half diffuseTerm = disneyDiffuse * nl;
half grazingTerm = saturate(oneMinusRoughness + (1-oneMinusReflectivity));
half3 color = diffColor * (gi.diffuse + light.color * diffuseTerm)
+ specularTerm * light.color * FresnelTerm (specColor, lh)
+ gi.specular * FresnelLerp (specColor, grazingTerm, nv);
return half4(color, 1);
}
half4 CalculateLight (float3 worldPos, float2 uv, half3 baseColor, half3 specColor, half oneMinusRoughness, half3 normalWorld,
half3 lightStart, half3 lightEnd, half3 lightColor, half lightRadius, half lightRangeSqInv)
{
UnityLight light = (UnityLight)0;
float3 eyeVec = normalize(worldPos - _WorldSpaceCameraPos);
half lightDist = 0;
#if 0
// Can't use a keyword, because no keywords in material property blocks.
// TODO: is it worth the dynamic branch?
if (sphereLight)
SphereLightToPointLight (worldPos, lightStart, eyeVec, normalWorld, lightRadius, lightRangeSqInv, light, lightDist);
else
#else
TubeLightToPointLight (worldPos, lightStart, lightEnd, eyeVec, normalWorld, lightRadius, lightRangeSqInv, lightColor, light.color, light.dir, light.ndotl, lightDist);
#endif
#if SHADOW_PLANES
light.color *= ShadowPlanes(worldPos);
#endif
half oneMinusReflectivity = 1 - SpecularStrength(specColor.rgb);
UnityIndirect ind;
UNITY_INITIALIZE_OUTPUT(UnityIndirect, ind);
ind.diffuse = 0;
ind.specular = 0;
half4 res = BRDF1_Unity_PBS_Area (baseColor, specColor, oneMinusReflectivity, oneMinusRoughness, normalWorld, -eyeVec, light, ind, lightDist, lightRadius);
return res;
}