A game about forced loneliness, made by TACStudios
1#ifndef UNITY_COMMON_LIGHTING_INCLUDED 2#define UNITY_COMMON_LIGHTING_INCLUDED 3 4#if SHADER_API_MOBILE || SHADER_API_GLES3 || SHADER_API_SWITCH || defined(UNITY_UNIFIED_SHADER_PRECISION_MODEL) 5#pragma warning (disable : 3205) // conversion of larger type to smaller 6#endif 7 8// Ligthing convention 9// Light direction is oriented backward (-Z). i.e in shader code, light direction is -lightData.forward 10 11//----------------------------------------------------------------------------- 12// Helper functions 13//----------------------------------------------------------------------------- 14 15// Performs the mapping of the vector 'v' centered within the axis-aligned cube 16// of dimensions [-1, 1]^3 to a vector centered within the unit sphere. 17// The function expects 'v' to be within the cube (possibly unexpected results otherwise). 18// Ref: http://mathproofs.blogspot.com/2005/07/mapping-cube-to-sphere.html 19real3 MapCubeToSphere(real3 v) 20{ 21 real3 v2 = v * v; 22 real2 vr3 = v2.xy * rcp(3.0); 23 return v * sqrt((real3)1.0 - 0.5 * v2.yzx - 0.5 * v2.zxy + vr3.yxx * v2.zzy); 24} 25 26// Computes the squared magnitude of the vector computed by MapCubeToSphere(). 27real ComputeCubeToSphereMapSqMagnitude(real3 v) 28{ 29 real3 v2 = v * v; 30 // Note: dot(v, v) is often computed before this function is called, 31 // so the compiler should optimize and use the precomputed result here. 32 return dot(v, v) - v2.x * v2.y - v2.y * v2.z - v2.z * v2.x + v2.x * v2.y * v2.z; 33} 34 35// texelArea = 4.0 / (resolution * resolution). 36// Ref: http://bpeers.com/blog/?itemid=1017 37// This version is less accurate, but much faster than this one: 38// http://www.rorydriscoll.com/2012/01/15/cubemap-texel-solid-angle/ 39real ComputeCubemapTexelSolidAngle(real3 L, real texelArea) 40{ 41 // Stretch 'L' by (1/d) so that it points at a side of a [-1, 1]^2 cube. 42 real d = Max3(abs(L.x), abs(L.y), abs(L.z)); 43 // Since 'L' is a unit vector, we can directly compute its 44 // new (inverse) length without dividing 'L' by 'd' first. 45 real invDist = d; 46 47 // dw = dA * cosTheta / (dist * dist), cosTheta = 1.0 / dist, 48 // where 'dA' is the area of the cube map texel. 49 return texelArea * invDist * invDist * invDist; 50} 51 52// Only makes sense for Monte-Carlo integration. 53// Normalize by dividing by the total weight (or the number of samples) in the end. 54// Integrate[6*(u^2+v^2+1)^(-3/2), {u,-1,1},{v,-1,1}] = 4 * Pi 55// Ref: "Stupid Spherical Harmonics Tricks", p. 9. 56real ComputeCubemapTexelSolidAngle(real2 uv) 57{ 58 real u = uv.x, v = uv.y; 59 return pow(1 + u * u + v * v, -1.5); 60} 61 62real ConvertEvToLuminance(real ev) 63{ 64 return exp2(ev - 3.0); 65} 66 67real ConvertLuminanceToEv(real luminance) 68{ 69 real k = 12.5f; 70 71 return log2((luminance * 100.0) / k); 72} 73 74//----------------------------------------------------------------------------- 75// Attenuation functions 76//----------------------------------------------------------------------------- 77 78// Ref: Moving Frostbite to PBR. 79 80// Non physically based hack to limit light influence to attenuationRadius. 81// Square the result to smoothen the function. 82real DistanceWindowing(real distSquare, real rangeAttenuationScale, real rangeAttenuationBias) 83{ 84 // If (range attenuation is enabled) 85 // rangeAttenuationScale = 1 / r^2 86 // rangeAttenuationBias = 1 87 // Else 88 // rangeAttenuationScale = 2^12 / r^2 89 // rangeAttenuationBias = 2^24 90 return saturate(rangeAttenuationBias - Sq(distSquare * rangeAttenuationScale)); 91} 92 93real SmoothDistanceWindowing(real distSquare, real rangeAttenuationScale, real rangeAttenuationBias) 94{ 95 real factor = DistanceWindowing(distSquare, rangeAttenuationScale, rangeAttenuationBias); 96 return Sq(factor); 97} 98 99#define PUNCTUAL_LIGHT_THRESHOLD 0.01 // 1cm (in Unity 1 is 1m) 100 101// Return physically based quadratic attenuation + influence limit to reach 0 at attenuationRadius 102real SmoothWindowedDistanceAttenuation(real distSquare, real distRcp, real rangeAttenuationScale, real rangeAttenuationBias) 103{ 104 real attenuation = min(distRcp, 1.0 / PUNCTUAL_LIGHT_THRESHOLD); 105 attenuation *= DistanceWindowing(distSquare, rangeAttenuationScale, rangeAttenuationBias); 106 107 // Effectively results in (distRcp)^2 * SmoothDistanceWindowing(...). 108 return Sq(attenuation); 109} 110 111// Square the result to smoothen the function. 112real AngleAttenuation(real cosFwd, real lightAngleScale, real lightAngleOffset) 113{ 114 return saturate(cosFwd * lightAngleScale + lightAngleOffset); 115} 116 117real SmoothAngleAttenuation(real cosFwd, real lightAngleScale, real lightAngleOffset) 118{ 119 real attenuation = AngleAttenuation(cosFwd, lightAngleScale, lightAngleOffset); 120 return Sq(attenuation); 121} 122 123// Combines SmoothWindowedDistanceAttenuation() and SmoothAngleAttenuation() in an efficient manner. 124// distances = {d, d^2, 1/d, d_proj}, where d_proj = dot(lightToSample, lightData.forward). 125real PunctualLightAttenuation(real4 distances, real rangeAttenuationScale, real rangeAttenuationBias, 126 real lightAngleScale, real lightAngleOffset) 127{ 128 real distSq = distances.y; 129 real distRcp = distances.z; 130 real distProj = distances.w; 131 real cosFwd = distProj * distRcp; 132 133 real attenuation = min(distRcp, 1.0 / PUNCTUAL_LIGHT_THRESHOLD); 134 attenuation *= DistanceWindowing(distSq, rangeAttenuationScale, rangeAttenuationBias); 135 attenuation *= AngleAttenuation(cosFwd, lightAngleScale, lightAngleOffset); 136 137 // Effectively results in SmoothWindowedDistanceAttenuation(...) * SmoothAngleAttenuation(...). 138 return Sq(attenuation); 139} 140 141// A hack to smoothly limit the influence of the light to the interior of a capsule. 142// A capsule is formed by sweeping a ball along a line segment. 143// This function behaves like SmoothWindowedDistanceAttenuation() for a short line segment. 144// Convention: the surface point is located at the origin of the coordinate system. 145real CapsuleWindowing(real3 center, real3 xAxis, real halfLength, 146 real rangeAttenuationScale, real rangeAttenuationBias) 147{ 148 // Conceptually, the idea is very simple: after taking the symmetry 149 // of the capsule into account, it is clear that the problem can be 150 // reduced to finding the closest sphere inside the capsule. 151 // We begin our search at the center of the capsule, and then translate 152 // this point along the line of symmetry until we either 153 // a) find the closest point on the line, or b) hit an endpoint of the line segment. 154 // The problem is simplified by working in the coordinate system of the capsule. 155 real x = dot(center, xAxis); // -x, strictly speaking 156 real dx = max(0, abs(x) - halfLength); 157 real r2 = dot(center, center); // r^2 158 real z2 = max(0, r2 - x * x); // z^2 159 real d2 = z2 + dx * dx; // Squared distance to the center of the closest sphere 160 161 return SmoothDistanceWindowing(d2, rangeAttenuationScale, rangeAttenuationBias); 162} 163 164// A hack to smoothly limit the influence of the light to the interior of a pillow. 165// A "pillow" (for the lack of a better name) is formed by sweeping a ball across a rectangle. 166// This function behaves like CapsuleAttenuation() for a narrow rectangle. 167// This function behaves like SmoothWindowedDistanceAttenuation() for a small rectangle. 168// Convention: the surface point is located at the origin of the coordinate system. 169real PillowWindowing(real3 center, real3 xAxis, real3 yAxis, real halfLength, real halfHeight, 170 real rangeAttenuationScale, real rangeAttenuationBias) 171{ 172 // Conceptually, the idea is very simple: after taking the symmetry 173 // of the pillow into account, it is clear that the problem can be 174 // reduced to finding the closest sphere inside the pillow. 175 // We begin our search at the center of the pillow, and then translate 176 // this point along and across the plane of symmetry until we either 177 // a) find the closest point on the plane, or b) hit an edge of the rectangle. 178 // The problem is simplified by working in the coordinate system of the pillow. 179 real x = dot(center, xAxis); // -x, strictly speaking 180 real dx = max(0, abs(x) - halfLength); 181 real y = dot(center, yAxis); // -y, strictly speaking 182 real dy = max(0, abs(y) - halfHeight); 183 real r2 = dot(center, center); // r^2 184 real z2 = max(0, r2 - x * x - y * y); // z^2 185 real d2 = z2 + dx * dx + dy * dy; // Squared distance to the center of the closest sphere 186 187 return SmoothDistanceWindowing(d2, rangeAttenuationScale, rangeAttenuationBias); 188} 189 190// Applies SmoothDistanceWindowing() after transforming the attenuation ellipsoid into a sphere. 191// If r = rsqrt(invSqRadius), then the ellipsoid is defined s.t. r1 = r / invAspectRatio, r2 = r3 = r. 192// The transformation is performed along the major axis of the ellipsoid (corresponding to 'r1'). 193// Both the ellipsoid (e.i. 'axis') and 'unL' should be in the same coordinate system. 194// 'unL' should be computed from the center of the ellipsoid. 195real EllipsoidalDistanceAttenuation(real3 unL, real3 axis, real invAspectRatio, 196 real rangeAttenuationScale, real rangeAttenuationBias) 197{ 198 // Project the unnormalized light vector onto the axis. 199 real projL = dot(unL, axis); 200 201 // Transform the light vector so that we can work with 202 // with the ellipsoid as if it was a sphere with the radius of light's range. 203 real diff = projL - projL * invAspectRatio; 204 unL -= diff * axis; 205 206 real sqDist = dot(unL, unL); 207 return SmoothDistanceWindowing(sqDist, rangeAttenuationScale, rangeAttenuationBias); 208} 209 210// Applies SmoothDistanceWindowing() using the axis-aligned ellipsoid of the given dimensions. 211// Both the ellipsoid and 'unL' should be in the same coordinate system. 212// 'unL' should be computed from the center of the ellipsoid. 213real EllipsoidalDistanceAttenuation(real3 unL, real3 invHalfDim, 214 real rangeAttenuationScale, real rangeAttenuationBias) 215{ 216 // Transform the light vector so that we can work with 217 // with the ellipsoid as if it was a unit sphere. 218 unL *= invHalfDim; 219 220 real sqDist = dot(unL, unL); 221 return SmoothDistanceWindowing(sqDist, rangeAttenuationScale, rangeAttenuationBias); 222} 223 224// Applies SmoothDistanceWindowing() after mapping the axis-aligned box to a sphere. 225// If the diagonal of the box is 'd', invHalfDim = rcp(0.5 * d). 226// Both the box and 'unL' should be in the same coordinate system. 227// 'unL' should be computed from the center of the box. 228real BoxDistanceAttenuation(real3 unL, real3 invHalfDim, 229 real rangeAttenuationScale, real rangeAttenuationBias) 230{ 231 real attenuation = 0.0; 232 233 // Transform the light vector so that we can work with 234 // with the box as if it was a [-1, 1]^2 cube. 235 unL *= invHalfDim; 236 237 // Our algorithm expects the input vector to be within the cube. 238 if (!(Max3(abs(unL.x), abs(unL.y), abs(unL.z)) > 1.0)) 239 { 240 real sqDist = ComputeCubeToSphereMapSqMagnitude(unL); 241 attenuation = SmoothDistanceWindowing(sqDist, rangeAttenuationScale, rangeAttenuationBias); 242 } 243 return attenuation; 244} 245 246//----------------------------------------------------------------------------- 247// IES Helper 248//----------------------------------------------------------------------------- 249 250real2 GetIESTextureCoordinate(real3x3 lightToWord, real3 L) 251{ 252 // IES need to be sample in light space 253 real3 dir = mul(lightToWord, -L); // Using matrix on left side do a transpose 254 255 // convert to spherical coordinate 256 real2 sphericalCoord; // .x is theta, .y is phi 257 // Texture is encoded with cos(phi), scale from -1..1 to 0..1 258 sphericalCoord.y = (dir.z * 0.5) + 0.5; 259 real theta = atan2(dir.y, dir.x); 260 sphericalCoord.x = theta * INV_TWO_PI; 261 262 return sphericalCoord; 263} 264 265//----------------------------------------------------------------------------- 266// Lighting functions 267//----------------------------------------------------------------------------- 268 269// Ref: Horizon Occlusion for Normal Mapped Reflections: http://marmosetco.tumblr.com/post/81245981087 270real GetHorizonOcclusion(real3 V, real3 normalWS, real3 vertexNormal, real horizonFade) 271{ 272 real3 R = reflect(-V, normalWS); 273 real specularOcclusion = saturate(1.0 + horizonFade * dot(R, vertexNormal)); 274 // smooth it 275 return specularOcclusion * specularOcclusion; 276} 277 278// Ref: Moving Frostbite to PBR - Gotanda siggraph 2011 279// Return specular occlusion based on ambient occlusion (usually get from SSAO) and view/roughness info 280real GetSpecularOcclusionFromAmbientOcclusion(real NdotV, real ambientOcclusion, real roughness) 281{ 282 return saturate(PositivePow(NdotV + ambientOcclusion, exp2(-16.0 * roughness - 1.0)) - 1.0 + ambientOcclusion); 283} 284 285// ref: Practical Realtime Strategies for Accurate Indirect Occlusion 286// Update ambient occlusion to colored ambient occlusion based on statitics of how light is bouncing in an object and with the albedo of the object 287real3 GTAOMultiBounce(real visibility, real3 albedo) 288{ 289 real3 a = 2.0404 * albedo - 0.3324; 290 real3 b = -4.7951 * albedo + 0.6417; 291 real3 c = 2.7552 * albedo + 0.6903; 292 293 real x = visibility; 294 return max(x, ((x * a + b) * x + c) * x); 295} 296 297// Based on Oat and Sander's 2007 technique 298// Area/solidAngle of intersection of two cone 299real SphericalCapIntersectionSolidArea(real cosC1, real cosC2, real cosB) 300{ 301 real r1 = FastACos(cosC1); 302 real r2 = FastACos(cosC2); 303 real rd = FastACos(cosB); 304 real area = 0.0; 305 306 if (rd <= max(r1, r2) - min(r1, r2)) 307 { 308 // One cap is completely inside the other 309 area = TWO_PI - TWO_PI * max(cosC1, cosC2); 310 } 311 else if (rd >= r1 + r2) 312 { 313 // No intersection exists 314 area = 0.0; 315 } 316 else 317 { 318 real diff = abs(r1 - r2); 319 real den = r1 + r2 - diff; 320 real x = 1.0 - saturate((rd - diff) / max(den, 0.0001)); 321 area = smoothstep(0.0, 1.0, x); 322 area *= TWO_PI - TWO_PI * max(cosC1, cosC2); 323 } 324 325 return area; 326} 327 328// ref: Practical Realtime Strategies for Accurate Indirect Occlusion 329// http://blog.selfshadow.com/publications/s2016-shading-course/#course_content 330// Original Cone-Cone method with cosine weighted assumption (p129 s2016_pbs_activision_occlusion) 331real GetSpecularOcclusionFromBentAO_ConeCone(real3 V, real3 bentNormalWS, real3 normalWS, real ambientOcclusion, real roughness) 332{ 333 // Retrieve cone angle 334 // Ambient occlusion is cosine weighted, thus use following equation. See slide 129 335 real cosAv = sqrt(1.0 - ambientOcclusion); 336 roughness = max(roughness, 0.01); // Clamp to 0.01 to avoid edge cases 337 real cosAs = exp2((-log(10.0) / log(2.0)) * Sq(roughness)); 338 real cosB = dot(bentNormalWS, reflect(-V, normalWS)); 339 return SphericalCapIntersectionSolidArea(cosAv, cosAs, cosB) / (TWO_PI * (1.0 - cosAs)); 340} 341 342real GetSpecularOcclusionFromBentAO(real3 V, real3 bentNormalWS, real3 normalWS, real ambientOcclusion, real roughness) 343{ 344 // Pseudo code: 345 //SphericalGaussian NDF = WarpedGGXDistribution(normalWS, roughness, V); 346 //SphericalGaussian Visibility = VisibilityConeSG(bentNormalWS, ambientOcclusion); 347 //SphericalGaussian UpperHemisphere = UpperHemisphereSG(normalWS); 348 //return saturate( InnerProduct(NDF, Visibility) / InnerProduct(NDF, UpperHemisphere) ); 349 350 // 1. Approximate visibility cone with a spherical gaussian of amplitude A=1 351 // For a cone angle X, we can determine sharpness so that all point inside the cone have a value > Y 352 // sharpness = (log(Y) - log(A)) / (cos(X) - 1) 353 // For AO cone, cos(X) = sqrt(1 - ambientOcclusion) 354 // -> for Y = 0.1, sharpness = -1.0 / (sqrt(1-ao) - 1) 355 float vs = -1.0f / min(sqrt(1.0f - ambientOcclusion) - 1.0f, -0.001f); 356 357 // 2. Approximate upper hemisphere with sharpness = 0.8 and amplitude = 1 358 float us = 0.8f; 359 360 // 3. Compute warped SG Axis of GGX distribution 361 // Ref: All-Frequency Rendering of Dynamic, Spatially-Varying Reflectance 362 // https://www.microsoft.com/en-us/research/wp-content/uploads/2009/12/sg.pdf 363 float NoV = dot(V, normalWS); 364 float3 NDFAxis = (2 * NoV * normalWS - V) * (0.5f / max(roughness * roughness * NoV, 0.001f)); 365 366 float umLength1 = length(NDFAxis + vs * bentNormalWS); 367 float umLength2 = length(NDFAxis + us * normalWS); 368 float d1 = 1 - exp(-2 * umLength1); 369 float d2 = 1 - exp(-2 * umLength2); 370 371 float expFactor1 = exp(umLength1 - umLength2 + us - vs); 372 373 return saturate(expFactor1 * (d1 * umLength2) / (d2 * umLength1)); 374} 375 376// Ref: Steve McAuley - Energy-Conserving Wrapped Diffuse 377real ComputeWrappedDiffuseLighting(real NdotL, real w) 378{ 379 return saturate((NdotL + w) / ((1.0 + w) * (1.0 + w))); 380} 381 382// Ref: Stephen McAuley - Advances in Rendering: Graphics Research and Video Game Production 383real3 ComputeWrappedNormal(real3 N, real3 L, real w) 384{ 385 real NdotL = dot(N, L); 386 real wrappedNdotL = saturate((NdotL + w) / (1 + w)); 387 real sinPhi = lerp(w, 0.f, wrappedNdotL); 388 real cosPhi = sqrt(1.0f - sinPhi * sinPhi); 389 return normalize(cosPhi * N + sinPhi * cross(cross(N, L), N)); 390} 391 392// Jimenez variant for eye 393real ComputeWrappedPowerDiffuseLighting(real NdotL, real w, real p) 394{ 395 return pow(saturate((NdotL + w) / (1.0 + w)), p) * (p + 1) / (w * 2.0 + 2.0); 396} 397 398// Ref: The Technical Art of Uncharted 4 - Brinck and Maximov 2016 399real ComputeMicroShadowing(real AO, real NdotL, real opacity) 400{ 401 real aperture = 2.0 * AO * AO; 402 real microshadow = saturate(NdotL + aperture - 1.0); 403 return lerp(1.0, microshadow, opacity); 404} 405 406real3 ComputeShadowColor(real shadow, real3 shadowTint, real penumbraFlag) 407{ 408 // The origin expression is 409 // lerp(real3(1.0, 1.0, 1.0) - ((1.0 - shadow) * (real3(1.0, 1.0, 1.0) - shadowTint)) 410 // , shadow * lerp(shadowTint, lerp(shadowTint, real3(1.0, 1.0, 1.0), shadow), shadow) 411 // , penumbraFlag); 412 // it has been simplified to this 413 real3 invTint = real3(1.0, 1.0, 1.0) - shadowTint; 414 real shadow3 = shadow * shadow * shadow; 415 return lerp(real3(1.0, 1.0, 1.0) - ((1.0 - shadow) * invTint) 416 , shadow3 * invTint + shadow * shadowTint, 417 penumbraFlag); 418 419} 420 421// This is the same method as the one above. Simply the shadow is a real3 to support colored shadows. 422real3 ComputeShadowColor(real3 shadow, real3 shadowTint, real penumbraFlag) 423{ 424 // The origin expression is 425 // lerp(real3(1.0, 1.0, 1.0) - ((1.0 - shadow) * (real3(1.0, 1.0, 1.0) - shadowTint)) 426 // , shadow * lerp(shadowTint, lerp(shadowTint, real3(1.0, 1.0, 1.0), shadow), shadow) 427 // , penumbraFlag); 428 // it has been simplified to this 429 real3 invTint = real3(1.0, 1.0, 1.0) - shadowTint; 430 real3 shadow3 = shadow * shadow * shadow; 431 return lerp(real3(1.0, 1.0, 1.0) - ((1.0 - shadow) * invTint) 432 , shadow3 * invTint + shadow * shadowTint, 433 penumbraFlag); 434 435} 436 437//----------------------------------------------------------------------------- 438// Helper functions 439//--------------------------------------------------------------------------- -- 440 441// Ref: "Crafting a Next-Gen Material Pipeline for The Order: 1886". 442real ClampNdotV(real NdotV) 443{ 444 return max(NdotV, 0.0001); // Approximately 0.0057 degree bias 445} 446 447// Helper function to return a set of common angle used when evaluating BSDF 448// NdotL and NdotV are unclamped 449void GetBSDFAngle(real3 V, real3 L, real NdotL, real NdotV, 450 out real LdotV, out real NdotH, out real LdotH, out real invLenLV) 451{ 452 // Optimized math. Ref: PBR Diffuse Lighting for GGX + Smith Microsurfaces (slide 114), assuming |L|=1 and |V|=1 453 LdotV = dot(L, V); 454 invLenLV = rsqrt(max(2.0 * LdotV + 2.0, FLT_EPS)); // invLenLV = rcp(length(L + V)), clamp to avoid rsqrt(0) = inf, inf * 0 = NaN 455 NdotH = saturate((NdotL + NdotV) * invLenLV); 456 LdotH = saturate(invLenLV * LdotV + invLenLV); 457} 458 459// Inputs: normalized normal and view vectors. 460// Outputs: front-facing normal, and the new non-negative value of the cosine of the view angle. 461// Important: call Orthonormalize() on the tangent and recompute the bitangent afterwards. 462real3 GetViewReflectedNormal(real3 N, real3 V, out real NdotV) 463{ 464 // Fragments of front-facing geometry can have back-facing normals due to interpolation, 465 // normal mapping and decals. This can cause visible artifacts from both direct (negative or 466 // extremely high values) and indirect (incorrect lookup direction) lighting. 467 // There are several ways to avoid this problem. To list a few: 468 // 469 // 1. Setting { NdotV = max(<N,V>, SMALL_VALUE) }. This effectively removes normal mapping 470 // from the affected fragments, making the surface appear flat. 471 // 472 // 2. Setting { NdotV = abs(<N,V>) }. This effectively reverses the convexity of the surface. 473 // It also reduces light leaking from non-shadow-casting lights. Note that 'NdotV' can still 474 // be 0 in this case. 475 // 476 // It's important to understand that simply changing the value of the cosine is insufficient. 477 // For one, it does not solve the incorrect lookup direction problem, since the normal itself 478 // is not modified. There is a more insidious issue, however. 'NdotV' is a constituent element 479 // of the mathematical system describing the relationships between different vectors - and 480 // not just normal and view vectors, but also light vectors, half vectors, tangent vectors, etc. 481 // Changing only one angle (or its cosine) leaves the system in an inconsistent state, where 482 // certain relationships can take on different values depending on whether 'NdotV' is used 483 // in the calculation or not. Therefore, it is important to change the normal (or another 484 // vector) in order to leave the system in a consistent state. 485 // 486 // We choose to follow the conceptual approach (2) by reflecting the normal around the 487 // (<N,V> = 0) boundary if necessary, as it allows us to preserve some normal mapping details. 488 489 NdotV = dot(N, V); 490 491 // N = (NdotV >= 0.0) ? N : (N - 2.0 * NdotV * V); 492 N += (2.0 * saturate(-NdotV)) * V; 493 NdotV = abs(NdotV); 494 495 return N; 496} 497 498// Generates an orthonormal (row-major) basis from a unit vector. TODO: make it column-major. 499// The resulting rotation matrix has the determinant of +1. 500// Ref: 'ortho_basis_pixar_r2' from http://marc-b-reynolds.github.io/quaternions/2016/07/06/Orthonormal.html 501real3x3 GetLocalFrame(real3 localZ) 502{ 503 real x = localZ.x; 504 real y = localZ.y; 505 real z = localZ.z; 506 real sz = FastSign(z); 507 real a = 1 / (sz + z); 508 real ya = y * a; 509 real b = x * ya; 510 real c = x * sz; 511 512 real3 localX = real3(c * x * a - 1, sz * b, c); 513 real3 localY = real3(b, y * ya - sz, y); 514 515 // Note: due to the quaternion formulation, the generated frame is rotated by 180 degrees, 516 // s.t. if localZ = {0, 0, 1}, then localX = {-1, 0, 0} and localY = {0, -1, 0}. 517 return real3x3(localX, localY, localZ); 518} 519 520// Generates an orthonormal (row-major) basis from a unit vector. TODO: make it column-major. 521// The resulting rotation matrix has the determinant of +1. 522real3x3 GetLocalFrame(real3 localZ, real3 localX) 523{ 524 real3 localY = cross(localZ, localX); 525 526 return real3x3(localX, localY, localZ); 527} 528 529// Construct a right-handed view-dependent orthogonal basis around the normal: 530// b0-b2 is the view-normal aka reflection plane. 531real3x3 GetOrthoBasisViewNormal(real3 V, real3 N, real unclampedNdotV, bool testSingularity = true) 532{ 533 real3x3 orthoBasisViewNormal; 534 if (testSingularity && (abs(1.0 - unclampedNdotV) <= FLT_EPS)) 535 { 536 // In this case N == V, and azimuth orientation around N shouldn't matter for the caller, 537 // we can use any quaternion-based method, like Frisvad or Reynold's (Pixar): 538 orthoBasisViewNormal = GetLocalFrame(N); 539 } 540 else 541 { 542 orthoBasisViewNormal[0] = normalize(V - N * unclampedNdotV); 543 orthoBasisViewNormal[2] = N; 544 orthoBasisViewNormal[1] = cross(orthoBasisViewNormal[2], orthoBasisViewNormal[0]); 545 } 546 return orthoBasisViewNormal; 547} 548 549// Move this here since it's used by both LightLoop.hlsl and RaytracingLightLoop.hlsl 550bool IsMatchingLightLayer(uint lightLayers, uint renderingLayers) 551{ 552 return (lightLayers & renderingLayers) != 0; 553} 554 555#if SHADER_API_MOBILE || SHADER_API_GLES3 || SHADER_API_SWITCH 556#pragma warning (enable : 3205) // conversion of larger type to smaller 557#endif 558 559#endif // UNITY_COMMON_LIGHTING_INCLUDED