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