// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt) #ifndef UNITY_IMAGE_BASED_LIGHTING_INCLUDED #define UNITY_IMAGE_BASED_LIGHTING_INCLUDED #include "UnityCG.cginc" #include "UnityStandardConfig.cginc" #include "UnityStandardBRDF.cginc" // ---------------------------------------------------------------------------- #if 0 // ---------------------------------------------------------------------------- // Unity is Y up - left handed //----------------------------------------------------------------------------- // Sample generator //----------------------------------------------------------------------------- // Ref: http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html uint ReverseBits32(uint bits) { #if 0 // Shader model 5 return reversebits(bits); #else bits = ( bits << 16) | ( bits >> 16); bits = ((bits & 0x00ff00ff) << 8) | ((bits & 0xff00ff00) >> 8); bits = ((bits & 0x0f0f0f0f) << 4) | ((bits & 0xf0f0f0f0) >> 4); bits = ((bits & 0x33333333) << 2) | ((bits & 0xcccccccc) >> 2); bits = ((bits & 0x55555555) << 1) | ((bits & 0xaaaaaaaa) >> 1); return bits; #endif } //----------------------------------------------------------------------------- float RadicalInverse_VdC(uint bits) { return float(ReverseBits32(bits)) * 2.3283064365386963e-10; // 0x100000000 } //----------------------------------------------------------------------------- float2 Hammersley2d(uint i, uint maxSampleCount) { return float2(float(i) / float(maxSampleCount), RadicalInverse_VdC(i)); } //----------------------------------------------------------------------------- float Hash(uint s) { s = s ^ 2747636419u; s = s * 2654435769u; s = s ^ (s >> 16); s = s * 2654435769u; s = s ^ (s >> 16); s = s * 2654435769u; return float(s) / 4294967295.0f; } //----------------------------------------------------------------------------- float2 InitRandom(float2 input) { float2 r; r.x = Hash(uint(input.x * 4294967295.0f)); r.y = Hash(uint(input.y * 4294967295.0f)); return r; } //----------------------------------------------------------------------------- // Util //----------------------------------------------------------------------------- // generate an orthonormalBasis from 3d unit vector. void GetLocalFrame(float3 N, out float3 tangentX, out float3 tangentY) { float3 upVector = abs(N.z) < 0.999f ? float3(0.0f, 0.0f, 1.0f) : float3(1.0f, 0.0f, 0.0f); tangentX = normalize(cross(upVector, N)); tangentY = cross(N, tangentX); } /* // http://orbit.dtu.dk/files/57573287/onb_frisvad_jgt2012.pdf void GetLocalFrame(float3 N, out float3 tangentX, out float3 tangentY) { if (N.z < -0.999f) // Handle the singularity { tangentX = Vec3f (0.0f, -1.0f, 0.0f); tangentY = Vec3f (-1.0f, 0.0f, 0.0f); return ; } float a = 1.0f / (1.0f + N.z); float b = -N.x * N.y * a ; tangentX = float3(1.0f - N.x * N.x * a , b, -N.x); tangentY = float3(b, 1.0f - N.y * N.y * a, -N.y); } */ // ---------------------------------------------------------------------------- // Sampling // ---------------------------------------------------------------------------- void ImportanceSampleCosDir(float2 u, float3 N, float3 tangentX, float3 tangentY, out float3 L) { // Cosine sampling - ref: http://www.rorydriscoll.com/2009/01/07/better-sampling/ float cosTheta = sqrt(max(0.0f, 1.0f - u.x)); float sinTheta = sqrt(u.x); float phi = UNITY_TWO_PI * u.y; // Transform from spherical into cartesian L = float3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta); // Local to world L = tangentX * L.x + tangentY * L.y + N * L.z; } //------------------------------------------------------------------------------------- void ImportanceSampleGGXDir(float2 u, float3 V, float3 N, float3 tangentX, float3 tangentY, float roughness, out float3 H, out float3 L) { // GGX NDF sampling float cosThetaH = sqrt((1.0f - u.x) / (1.0f + (roughness * roughness - 1.0f) * u.x)); float sinThetaH = sqrt(max(0.0f, 1.0f - cosThetaH * cosThetaH)); float phiH = UNITY_TWO_PI * u.y; // Transform from spherical into cartesian H = float3(sinThetaH * cos(phiH), sinThetaH * sin(phiH), cosThetaH); // Local to world H = tangentX * H.x + tangentY * H.y + N * H.z; // Convert sample from half angle to incident angle L = 2.0f * dot(V, H) * H - V; } // ---------------------------------------------------------------------------- // weightOverPdf return the weight (without the diffuseAlbedo term) over pdf. diffuseAlbedo term must be apply by the caller. void ImportanceSampleLambert( float2 u, float3 N, float3 tangentX, float3 tangentY, out float3 L, out float NdotL, out float weightOverPdf) { ImportanceSampleCosDir(u, N, tangentX, tangentY, L); NdotL = saturate(dot(N, L)); // Importance sampling weight for each sample // pdf = N.L / PI // weight = fr * (N.L) with fr = diffuseAlbedo / PI // weight over pdf is: // weightOverPdf = (diffuseAlbedo / PI) * (N.L) / (N.L / PI) // weightOverPdf = diffuseAlbedo // diffuseAlbedo is apply outside the function weightOverPdf = 1.0f; } // ---------------------------------------------------------------------------- // weightOverPdf return the weight (without the Fresnel term) over pdf. Fresnel term must be apply by the caller. void ImportanceSampleGGX( float2 u, float3 V, float3 N, float3 tangentX, float3 tangentY, float roughness, float NdotV, out float3 L, out float VdotH, out float NdotL, out float weightOverPdf) { float3 H; ImportanceSampleGGXDir(u, V, N, tangentX, tangentY, roughness, H, L); float NdotH = saturate(dot(N, H)); // Note: since L and V are symmetric around H, LdotH == VdotH VdotH = saturate(dot(V, H)); NdotL = saturate(dot(N, L)); // Importance sampling weight for each sample // pdf = D(H) * (N.H) / (4 * (L.H)) // weight = fr * (N.L) with fr = F(H) * G(V, L) * D(H) / (4 * (N.L) * (N.V)) // weight over pdf is: // weightOverPdf = F(H) * G(V, L) * (L.H) / ((N.H) * (N.V)) // weightOverPdf = F(H) * 4 * (N.L) * V(V, L) * (L.H) / (N.H) with V(V, L) = G(V, L) / (4 * (N.L) * (N.V)) // F is apply outside the function float Vis = SmithJointGGXVisibilityTerm(NdotL, NdotV, roughness); weightOverPdf = 4.0f * Vis * NdotL * VdotH / NdotH; } //----------------------------------------------------------------------------- // Reference // ---------------------------------------------------------------------------- // Ref: Moving Frostbite to PBR (Appendix A) void IntegrateLambertDiffuseIBLRef( out float3 diffuseLighting, UNITY_ARGS_TEXCUBE(tex), float4 texHdrParam, // Multiplier to apply on hdr texture (in case of rgbm) float3 N, float3 diffuseAlbedo, uint sampleCount = 2048) { float3 acc = float3(0.0f, 0.0f, 0.0f); // Add some jittering on Hammersley2d float2 randNum = InitRandom(N.xy * 0.5f + 0.5f); float3 tangentX, tangentY; GetLocalFrame(N, tangentX, tangentY); for (uint i = 0; i < sampleCount; ++i) { float2 u = Hammersley2d(i, sampleCount); u = frac(u + randNum + 0.5f); float3 L; float NdotL; float weightOverPdf; ImportanceSampleLambert(u, N, tangentX, tangentY, L, NdotL, weightOverPdf); if (NdotL > 0.0f) { float4 rgbm = UNITY_SAMPLE_TEXCUBE_LOD(tex, L, 0).rgba; float3 val = DecodeHDR(rgbm, texHdrParam); // diffuse Albedo is apply here as describe in ImportanceSampleLambert function acc += diffuseAlbedo * weightOverPdf * val; } } diffuseLighting = acc / sampleCount; } // ---------------------------------------------------------------------------- void IntegrateDisneyDiffuseIBLRef( out float3 diffuseLighting, UNITY_ARGS_TEXCUBE(tex), float4 texHdrParam, // Multiplier to apply on hdr texture (in case of rgbm) float3 N, float3 V, float roughness, float3 diffuseAlbedo, uint sampleCount = 2048) { float NdotV = dot(N, V); float3 acc = float3(0.0f, 0.0f, 0.0f); // Add some jittering on Hammersley2d float2 randNum = InitRandom(N.xy * 0.5f + 0.5f); float3 tangentX, tangentY; GetLocalFrame(N, tangentX, tangentY); for (uint i = 0; i < sampleCount; ++i) { float2 u = Hammersley2d(i, sampleCount); u = frac(u + randNum + 0.5f); float3 L; float NdotL; float weightOverPdf; // for Disney we still use a Cosine importance sampling, true Disney importance sampling imply a look up table ImportanceSampleLambert(u, N, tangentX, tangentY, L, NdotL, weightOverPdf); if (NdotL > 0.0f) { float4 rgbm = UNITY_SAMPLE_TEXCUBE_LOD(tex, L, 0).rgba; float3 val = DecodeHDR(rgbm, texHdrParam); float3 H = normalize(L + V); float LdotH = dot(L, H); // Note: we call DisneyDiffuse that require to multiply by Albedo / PI. Divide by PI is already taken into account // in weightOverPdf of ImportanceSampleLambert call. float disneyDiffuse = DisneyDiffuse(NdotV, NdotL, LdotH, RoughnessToPerceptualRoughness(roughness)); // diffuse Albedo is apply here as describe in ImportanceSampleLambert function acc += diffuseAlbedo * disneyDiffuse * weightOverPdf * val; } } diffuseLighting = acc / sampleCount; } // ---------------------------------------------------------------------------- // Ref: Moving Frostbite to PBR (Appendix A) void IntegrateSpecularGGXIBLRef(out float3 specularLighting, UNITY_ARGS_TEXCUBE(tex), float4 texHdrParam, // Multiplier to apply on hdr texture (in case of rgbm) float3 N, float3 V, float roughness, float3 f0, float f90, uint sampleCount = 2048) { float NdotV = saturate(dot(N, V)); float3 acc = float3(0.0f, 0.0f, 0.0f); // Add some jittering on Hammersley2d float2 randNum = InitRandom(V.xy * 0.5f + 0.5f); float3 tangentX, tangentY; GetLocalFrame(N, tangentX, tangentY); for (uint i = 0; i < sampleCount; ++i) { float2 u = Hammersley2d(i, sampleCount); u = frac(u + randNum + 0.5f); float VdotH; float NdotL; float3 L; float weightOverPdf; // GGX BRDF ImportanceSampleGGX(u, V, N, tangentX, tangentY, roughness, NdotV, L, VdotH, NdotL, weightOverPdf); if (NdotL > 0.0f) { // Fresnel component is apply here as describe in ImportanceSampleGGX function float3 FweightOverPdf = FresnelLerp(f0, f90, VdotH) * weightOverPdf; float4 rgbm = UNITY_SAMPLE_TEXCUBE_LOD(tex, L, 0).rgba; float3 val = DecodeHDR(rgbm, texHdrParam); acc += FweightOverPdf * val; } } specularLighting = acc / sampleCount; } // ---------------------------------------------------------------------------- // Pre-integration // ---------------------------------------------------------------------------- // Ref: Listing 18 in "Moving Frostbite to PBR" + https://knarkowicz.wordpress.com/2014/12/27/analytical-dfg-term-for-ibl/ float4 IntegrateDFG(float3 V, float3 N, float roughness, uint sampleCount) { float NdotV = saturate(dot(N, V)); float4 acc = float4(0.0f, 0.0f, 0.0f, 0.0f); // Add some jittering on Hammersley2d float2 randNum = InitRandom(V.xy * 0.5f + 0.5f); float3 tangentX, tangentY; GetLocalFrame(N, tangentX, tangentY); for (uint i = 0; i < sampleCount; ++i) { float2 u = Hammersley2d(i, sampleCount); u = frac(u + randNum + 0.5f); float VdotH; float NdotL; float weightOverPdf; float3 L; // Unused ImportanceSampleGGX(u, V, N, tangentX, tangentY, roughness, NdotV, L, VdotH, NdotL, weightOverPdf); if (NdotL > 0.0f) { // Integral is // 1 / NumSample * \int[ L * fr * (N.L) / pdf ] with pdf = D(H) * (N.H) / (4 * (L.H)) and fr = F(H) * G(V, L) * D(H) / (4 * (N.L) * (N.V)) // This is split in two part: // A) \int[ L * (N.L) ] // B) \int[ F(H) * 4 * (N.L) * V(V, L) * (L.H) / (N.H) ] with V(V, L) = G(V, L) / (4 * (N.L) * (N.V)) // = \int[ F(H) * weightOverPdf ] // Recombine at runtime with: ( f0 * weightOverPdf * (1 - Fc) + f90 * weightOverPdf * Fc ) with Fc =(1 - V.H)^5 float Fc = pow(1.0f - VdotH, 5.0f); acc.x += (1.0f - Fc) * weightOverPdf; acc.y += Fc * weightOverPdf; } // for Disney we still use a Cosine importance sampling, true Disney importance sampling imply a look up table ImportanceSampleLambert(u, N, tangentX, tangentY, L, NdotL, weightOverPdf); if (NdotL > 0.0f) { float3 H = normalize(L + V); float LdotH = dot(L, H); float disneyDiffuse = DisneyDiffuse(NdotV, NdotL, LdotH, RoughnessToPerceptualRoughness(roughness)); acc.z += disneyDiffuse * weightOverPdf; } } return acc / sampleCount; } // ---------------------------------------------------------------------------- // Ref: Listing 19 in "Moving Frostbite to PBR" // IntegrateLD will not work with RGBM cubemap. For now it is use with fp16 cubemap such as those use for real time cubemap. float4 IntegrateLD( UNITY_ARGS_TEXCUBE(tex), float3 V, float3 N, float roughness, float mipmapcount, float invOmegaP, uint sampleCount, bool prefilter = true) // static bool { float3 acc = float3(0.0f, 0.0f, 0.0f); float accWeight = 0; float2 randNum = InitRandom(V.xy * 0.5f + 0.5f); float3 tangentX, tangentY; GetLocalFrame(N, tangentX, tangentY); for (uint i = 0; i < sampleCount; ++i) { float2 u = Hammersley2d(i, sampleCount); u = frac(u + randNum + 0.5f); float3 H; float3 L; ImportanceSampleGGXDir(u, V, N, tangentX, tangentY, roughness, H, L); float NdotL = saturate(dot(N,L)); float mipLevel; if (!prefilter) // BRDF importance sampling { mipLevel = 0.0f; } else // Prefiltered BRDF importance sampling { float NdotH = saturate(dot(N, H)); // Note: since L and V are symmetric around H, LdotH == VdotH float LdotH = saturate(dot(L, H)); // Use pre - filtered importance sampling (i.e use lower mipmap // level for fetching sample with low probability in order // to reduce the variance ). // ( Reference : GPU Gem3: http://http.developer.nvidia.com/GPUGems3/gpugems3_ch20.html) // // Since we pre - integrate the result for normal direction , // N == V and then NdotH == LdotH . This is why the BRDF pdf // can be simplifed from : // pdf = D * NdotH /(4* LdotH ) to pdf = D / 4; // // - OmegaS : Solid angle associated to a sample // - OmegaP : Solid angle associated to a pixel of the cubemap float pdf = GGXTerm(NdotH, roughness) * NdotH / (4 * LdotH); float omegaS = 1.0f / (sampleCount * pdf); // Solid angle associated to a sample // invOmegaP is precomputed on CPU and provide as a parameter of the function // float omegaP = UNITY_FOUR_PI / (6.0f * cubemapWidth * cubemapWidth); // Solid angle associated to a pixel of the cubemap // Clamp is not necessary as the hardware will do it. // mipLevel = clamp(0.5f * log2(omegaS * invOmegaP), 0, mipmapcount); mipLevel = 0.5f * log2(omegaS * invOmegaP); // Clamp is not necessary as the hardware will do it. } if (NdotL > 0.0f) { // No rgbm format here, only fp16 float3 val = UNITY_SAMPLE_TEXCUBE_LOD(tex, L, mipLevel).rgba; // See p63 equation (53) of moving Frostbite to PBR v2 for the extra NdotL here (both in weight and value) acc += val * NdotL; accWeight += NdotL; } } return float4(acc * (1.0f / accWeight), 1.0f); } #endif // 0 // ---------------------------------------------------------------------------- // GlossyEnvironment - Function to integrate the specular lighting with default sky or reflection probes // ---------------------------------------------------------------------------- struct Unity_GlossyEnvironmentData { // - Deferred case have one cubemap // - Forward case can have two blended cubemap (unusual should be deprecated). // Surface properties use for cubemap integration half roughness; // CAUTION: This is perceptualRoughness but because of compatibility this name can't be change :( half3 reflUVW; }; // ---------------------------------------------------------------------------- Unity_GlossyEnvironmentData UnityGlossyEnvironmentSetup(half Smoothness, half3 worldViewDir, half3 Normal, half3 fresnel0) { Unity_GlossyEnvironmentData g; g.roughness /* perceptualRoughness */ = SmoothnessToPerceptualRoughness(Smoothness); g.reflUVW = reflect(-worldViewDir, Normal); return g; } // ---------------------------------------------------------------------------- half perceptualRoughnessToMipmapLevel(half perceptualRoughness) { return perceptualRoughness * UNITY_SPECCUBE_LOD_STEPS; } // ---------------------------------------------------------------------------- half mipmapLevelToPerceptualRoughness(half mipmapLevel) { return mipmapLevel / UNITY_SPECCUBE_LOD_STEPS; } // ---------------------------------------------------------------------------- half3 Unity_GlossyEnvironment (UNITY_ARGS_TEXCUBE(tex), half4 hdr, Unity_GlossyEnvironmentData glossIn) { half perceptualRoughness = glossIn.roughness /* perceptualRoughness */ ; // TODO: CAUTION: remap from Morten may work only with offline convolution, see impact with runtime convolution! // For now disabled #if 0 float m = PerceptualRoughnessToRoughness(perceptualRoughness); // m is the real roughness parameter const float fEps = 1.192092896e-07F; // smallest such that 1.0+FLT_EPSILON != 1.0 (+1e-4h is NOT good here. is visibly very wrong) float n = (2.0/max(fEps, m*m))-2.0; // remap to spec power. See eq. 21 in --> https://dl.dropboxusercontent.com/u/55891920/papers/mm_brdf.pdf n /= 4; // remap from n_dot_h formulatino to n_dot_r. See section "Pre-convolved Cube Maps vs Path Tracers" --> https://s3.amazonaws.com/docs.knaldtech.com/knald/1.0.0/lys_power_drops.html perceptualRoughness = pow( 2/(n+2), 0.25); // remap back to square root of real roughness (0.25 include both the sqrt root of the conversion and sqrt for going from roughness to perceptualRoughness) #else // MM: came up with a surprisingly close approximation to what the #if 0'ed out code above does. perceptualRoughness = perceptualRoughness*(1.7 - 0.7*perceptualRoughness); #endif half mip = perceptualRoughnessToMipmapLevel(perceptualRoughness); half3 R = glossIn.reflUVW; half4 rgbm = UNITY_SAMPLE_TEXCUBE_LOD(tex, R, mip); return DecodeHDR(rgbm, hdr); } // ---------------------------------------------------------------------------- // Include deprecated function #define INCLUDE_UNITY_IMAGE_BASED_LIGHTING_DEPRECATED #include "UnityDeprecated.cginc" #undef INCLUDE_UNITY_IMAGE_BASED_LIGHTING_DEPRECATED // ---------------------------------------------------------------------------- #endif // UNITY_IMAGE_BASED_LIGHTING_INCLUDED