mirror of
https://github.com/maxartz15/VolumetricLighting.git
synced 2024-11-14 10:45:35 +01:00
190 lines
7.0 KiB
HLSL
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;
|
|
}
|