mirror of
				https://github.com/maxartz15/VolumetricLighting.git
				synced 2025-10-31 05:26:00 +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;
 | |
| }
 |