OSDN Git Service

Added Assets for main menu
[mindgames/Mindgames_main.git] / Mindgames / Library / PackageCache / com.unity.render-pipelines.high-definition@6.9.0-preview / Runtime / Lighting / LightEvaluation.hlsl
1 // This files include various function uses to evaluate lights
2 // use #define LIGHT_EVALUATION_NO_HEIGHT_FOG to disable Height fog attenuation evaluation
3 // use #define LIGHT_EVALUATION_NO_COOKIE to disable cookie evaluation
4 // use #define LIGHT_EVALUATION_NO_CONTACT_SHADOWS to disable contact shadow evaluation
5 // use #define LIGHT_EVALUATION_NO_SHADOWS to disable evaluation of shadow including contact shadow (but not micro shadow)
6 // use #define OVERRIDE_EVALUATE_ENV_INTERSECTION to provide a new version of EvaluateLight_EnvIntersection
7
8 // Samples the area light's associated cookie
9 //  cookieIndex, the index of the cookie texture in the Texture2DArray
10 //  L, the 4 local-space corners of the area light polygon transformed by the LTC M^-1 matrix
11 //  F, the *normalized* vector irradiance
12 float3 SampleAreaLightCookie(int cookieIndex, float4x3 L, float3 F)
13 {
14     // L[0] = top-right
15     // L[1] = bottom-right
16     // L[2] = bottom-left
17     // L[3] = top-left
18     float3  origin = L[2];
19     float3  right = L[1] - origin;
20     float3  up = L[3] - origin;
21
22     float3  normal = cross(right, up);
23     float   sqArea = dot(normal, normal);
24     normal *= rsqrt(sqArea);
25
26     // Compute intersection of irradiance vector with the area light plane
27     float   hitDistance = dot(origin, normal) / dot(F, normal);
28     float3  hitPosition = hitDistance * normal;
29     hitPosition -= origin;  // Relative to bottom-left corner
30
31                             // Here, right and up vectors are not necessarily orthonormal
32                             // We create the orthogonal vector "ortho" by projecting "up" onto the vector orthogonal to "right"
33                             //  ortho = up - (up.right') * right'
34                             // Where right' = right / sqrt( dot( right, right ) ), the normalized right vector
35     float   recSqLengthRight = 1.0 / dot(right, right);
36     float   upRightMixing = dot(up, right);
37     float3  ortho = up - upRightMixing * right * recSqLengthRight;
38
39     // The V coordinate along the "up" vector is simply the projection against the ortho vector
40     float   v = dot(hitPosition, ortho) / dot(ortho, ortho);
41
42     // The U coordinate is not only the projection against the right vector
43     //  but also the subtraction of the influence of the up vector upon the right vector
44     //  (indeed, if the up & right vectors are not orthogonal then a certain amount of
45     //  the up coordinate also influences the right coordinate)
46     //
47     //       |    up
48     // ortho ^....*--------*
49     //       |   /:       /
50     //       |  / :      /
51     //       | /  :     /
52     //       |/   :    /
53     //       +----+-->*----->
54     //            : right
55     //          mix of up into right that needs to be subtracted from simple projection on right vector
56     //
57     float   u = (dot(hitPosition, right) - upRightMixing * v) * recSqLengthRight;
58     // Currently the texture happens to be reversed when comparing it to the area light emissive mesh itself. This needs
59     // Further investigation to solve the problem. So for the moment we simply decided to inverse the x coordinate of hitUV
60     // as a temporary solution
61     // TODO: Invesigate more!
62     float2  hitUV = float2(1.0 - u, v);
63
64     // Assuming the original cosine lobe distribution Do is enclosed in a cone of 90° aperture,
65     //  following the idea of orthogonal projection upon the area light's plane we find the intersection
66     //  of the cone to be a disk of area PI*d² where d is the hit distance we computed above.
67     // We also know the area of the transformed polygon A = sqrt( sqArea ) and we pose the ratio of covered area as PI.d² / A.
68     //
69     // Knowing the area in square texels of the cookie texture A_sqTexels = texture width * texture height (default is 128x128 square texels)
70     //  we can deduce the actual area covered by the cone in square texels as:
71     //  A_covered = Pi.d² / A * A_sqTexels
72     //
73     // From this, we find the mip level as: mip = log2( sqrt( A_covered ) ) = log2( A_covered ) / 2
74     // Also, assuming that A_sqTexels is of the form 2^n * 2^n we get the simplified expression: mip = log2( Pi.d² / A ) / 2 + n
75     //
76     const float COOKIE_MIPS_COUNT = _CookieSizePOT;
77     float   mipLevel = 0.5 * log2(1e-8 + PI * hitDistance*hitDistance * rsqrt(sqArea)) + COOKIE_MIPS_COUNT;
78
79     return SAMPLE_TEXTURE2D_ARRAY_LOD(_AreaCookieTextures, s_trilinear_clamp_sampler, hitUV, cookieIndex, mipLevel).xyz;
80 }
81
82 //-----------------------------------------------------------------------------
83 // Directional Light evaluation helper
84 //-----------------------------------------------------------------------------
85
86 float3 EvaluateCookie_Directional(LightLoopContext lightLoopContext, DirectionalLightData light,
87                                   float3 lightToSample)
88 {
89
90     // Translate and rotate 'positionWS' into the light space.
91     // 'light.right' and 'light.up' are pre-scaled on CPU.
92     float3x3 lightToWorld = float3x3(light.right, light.up, light.forward);
93     float3   positionLS   = mul(lightToSample, transpose(lightToWorld));
94
95     // Perform orthographic projection.
96     float2 positionCS  = positionLS.xy;
97
98     // Remap the texture coordinates from [-1, 1]^2 to [0, 1]^2.
99     float2 positionNDC = positionCS * 0.5 + 0.5;
100
101     // We let the sampler handle clamping to border.
102     return SampleCookie2D(lightLoopContext, positionNDC, light.cookieIndex, light.tileCookie);
103 }
104
105 // Returns unassociated (non-premultiplied) color with alpha (attenuation).
106 // The calling code must perform alpha-compositing.
107 float4 EvaluateLight_Directional(LightLoopContext lightLoopContext, PositionInputs posInput,
108                                  DirectionalLightData light)
109 {
110     float4 color = float4(light.color, 1.0);
111
112 #ifndef LIGHT_EVALUATION_NO_HEIGHT_FOG
113     // Height fog attenuation.
114     // TODO: add an if()?
115     {
116         float cosZenithAngle = -light.forward.y;
117         float fragmentHeight = posInput.positionWS.y;
118         color.a *= TransmittanceHeightFog(_HeightFogBaseExtinction, _HeightFogBaseHeight,
119                                           _HeightFogExponents, cosZenithAngle, fragmentHeight);
120     }
121 #endif
122
123 #ifndef LIGHT_EVALUATION_NO_COOKIE
124     if (light.cookieIndex >= 0)
125     {
126         float3 lightToSample = posInput.positionWS - light.positionRWS;
127         float3 cookie = EvaluateCookie_Directional(lightLoopContext, light, lightToSample);
128
129         color.rgb *= cookie;
130     }
131 #endif
132
133     return color;
134 }
135
136 float EvaluateShadow_Directional(LightLoopContext lightLoopContext, PositionInputs posInput,
137                                  DirectionalLightData light, BuiltinData builtinData, float3 N)
138 {
139 #ifndef LIGHT_EVALUATION_NO_SHADOWS
140     float shadow     = 1.0;
141     float shadowMask = 1.0;
142     float NdotL      = dot(N, -light.forward); // Disable contact shadow and shadow mask when facing away from light (i.e transmission)
143
144 #ifdef SHADOWS_SHADOWMASK
145     // shadowMaskSelector.x is -1 if there is no shadow mask
146     // Note that we override shadow value (in case we don't have any dynamic shadow)
147     shadow = shadowMask = (light.shadowMaskSelector.x >= 0.0 && NdotL > 0.0) ? dot(BUILTIN_DATA_SHADOW_MASK, light.shadowMaskSelector) : 1.0;
148 #endif
149
150     if ((light.shadowIndex >= 0) && (light.shadowDimmer > 0))
151     {
152         shadow = lightLoopContext.shadowValue;
153
154     #ifdef SHADOWS_SHADOWMASK
155         // TODO: Optimize this code! Currently it is a bit like brute force to get the last transistion and fade to shadow mask, but there is
156         // certainly more efficient to do
157         // We reuse the transition from the cascade system to fade between shadow mask at max distance
158         uint  payloadOffset;
159         real  fade;
160         int cascadeCount;
161         int shadowSplitIndex = 0;
162
163         shadowSplitIndex = EvalShadow_GetSplitIndex(lightLoopContext.shadowContext, light.shadowIndex, posInput.positionWS, fade, cascadeCount);
164
165         // we have a fade caclulation for each cascade but we must lerp with shadow mask only for the last one
166         // if shadowSplitIndex is -1 it mean we are outside cascade and should return 1.0 to use shadowmask: saturate(-shadowSplitIndex) return 0 for >= 0 and 1 for -1
167         fade = ((shadowSplitIndex + 1) == cascadeCount) ? fade : saturate(-shadowSplitIndex);
168
169         // In the transition code (both dithering and blend) we use shadow = lerp( shadow, 1.0, fade ) for last transition
170         // mean if we expend the code we have (shadow * (1 - fade) + fade). Here to make transition with shadow mask
171         // we will remove fade and add fade * shadowMask which mean we do a lerp with shadow mask
172         shadow = shadow - fade + fade * shadowMask;
173
174         // See comment in EvaluateBSDF_Punctual
175         shadow = light.nonLightMappedOnly ? min(shadowMask, shadow) : shadow;
176     #endif
177
178         shadow = lerp(shadowMask, shadow, light.shadowDimmer);
179     }
180
181     // Transparents have no contact shadow information
182 #if !defined(_SURFACE_TYPE_TRANSPARENT) && !defined(LIGHT_EVALUATION_NO_CONTACT_SHADOWS)
183     shadow = min(shadow, NdotL > 0.0 ? GetContactShadow(lightLoopContext, light.contactShadowMask) : 1.0);
184 #endif
185
186 #ifdef DEBUG_DISPLAY
187     if (_DebugShadowMapMode == SHADOWMAPDEBUGMODE_SINGLE_SHADOW && light.shadowIndex == _DebugSingleShadowIndex)
188         g_DebugShadowAttenuation = shadow;
189 #endif
190
191     return shadow;
192 #else // LIGHT_EVALUATION_NO_SHADOWS
193     return 1.0;
194 #endif
195 }
196
197 //-----------------------------------------------------------------------------
198 // Punctual Light evaluation helper
199 //-----------------------------------------------------------------------------
200
201 // distances = {d, d^2, 1/d, d_proj}
202 void ModifyDistancesForFillLighting(inout float4 distances, float lightSqRadius)
203 {
204     // Apply the sphere light hack to soften the core of the punctual light.
205     // It is not physically plausible (using max() is more correct, but looks worse).
206     // See https://www.desmos.com/calculator/otqhxunqhl
207     // We only modify 1/d for performance reasons.
208     float sqDist = distances.y;
209     distances.z = rsqrt(sqDist + lightSqRadius); // Recompute 1/d
210 }
211
212 // Returns the normalized light vector L and the distances = {d, d^2, 1/d, d_proj}.
213 void GetPunctualLightVectors(float3 positionWS, LightData light, out float3 L, out float4 distances)
214 {
215     float3 lightToSample = positionWS - light.positionRWS;
216
217     distances.w = dot(lightToSample, light.forward);
218
219     if (light.lightType == GPULIGHTTYPE_PROJECTOR_BOX)
220     {
221         L = -light.forward;
222         distances.xyz = 1; // No distance or angle attenuation
223     }
224     else
225     {
226         float3 unL     = -lightToSample;
227         float  distSq  = dot(unL, unL);
228         float  distRcp = rsqrt(distSq);
229         float  dist    = distSq * distRcp;
230
231         L = unL * distRcp;
232         distances.xyz = float3(dist, distSq, distRcp);
233
234         ModifyDistancesForFillLighting(distances, light.size.x);
235     }
236 }
237
238 float4 EvaluateCookie_Punctual(LightLoopContext lightLoopContext, LightData light,
239                                float3 lightToSample)
240 {
241 #ifndef LIGHT_EVALUATION_NO_COOKIE
242     int lightType = light.lightType;
243
244     // Translate and rotate 'positionWS' into the light space.
245     // 'light.right' and 'light.up' are pre-scaled on CPU.
246     float3x3 lightToWorld = float3x3(light.right, light.up, light.forward);
247     float3   positionLS   = mul(lightToSample, transpose(lightToWorld));
248
249     float4 cookie;
250
251     UNITY_BRANCH if (lightType == GPULIGHTTYPE_POINT)
252     {
253         cookie.rgb = SampleCookieCube(lightLoopContext, positionLS, light.cookieIndex);
254         cookie.a   = 1;
255     }
256     else
257     {
258         // Perform orthographic or perspective projection.
259         float  perspectiveZ = (lightType != GPULIGHTTYPE_PROJECTOR_BOX) ? positionLS.z : 1.0;
260         float2 positionCS   = positionLS.xy / perspectiveZ;
261         bool   isInBounds   = Max3(abs(positionCS.x), abs(positionCS.y), 1.0 - positionLS.z) <= 1.0;
262
263         // Remap the texture coordinates from [-1, 1]^2 to [0, 1]^2.
264         float2 positionNDC = positionCS * 0.5 + 0.5;
265
266         // Manually clamp to border (black).
267         cookie.rgb = SampleCookie2D(lightLoopContext, positionNDC, light.cookieIndex, false);
268         cookie.a   = isInBounds ? 1.0 : 0.0;
269     }
270
271 #else
272
273     // When we disable cookie, we must still perform border attenuation for pyramid and box
274     // as by default we always bind a cookie white texture for them to mimic it.
275     float4 cookie = float4(1.0, 1.0, 1.0, 1.0);
276
277     int lightType = light.lightType;
278
279     if (lightType == GPULIGHTTYPE_PROJECTOR_PYRAMID || lightType == GPULIGHTTYPE_PROJECTOR_BOX)
280     { 
281         // Translate and rotate 'positionWS' into the light space.
282         // 'light.right' and 'light.up' are pre-scaled on CPU.
283         float3x3 lightToWorld = float3x3(light.right, light.up, light.forward);
284         float3 positionLS     = mul(lightToSample, transpose(lightToWorld));
285
286         // Perform orthographic or perspective projection.
287         float  perspectiveZ = (lightType != GPULIGHTTYPE_PROJECTOR_BOX) ? positionLS.z : 1.0;
288         float2 positionCS   = positionLS.xy / perspectiveZ;
289         bool   isInBounds   = Max3(abs(positionCS.x), abs(positionCS.y), 1.0 - positionLS.z) <= 1.0;
290
291         // Manually clamp to border (black).
292         cookie.a = isInBounds ? 1.0 : 0.0;
293     }
294 #endif
295
296     return cookie;
297 }
298
299 // Returns unassociated (non-premultiplied) color with alpha (attenuation).
300 // The calling code must perform alpha-compositing.
301 // distances = {d, d^2, 1/d, d_proj}, where d_proj = dot(lightToSample, light.forward).
302 float4 EvaluateLight_Punctual(LightLoopContext lightLoopContext, PositionInputs posInput,
303     LightData light, float3 L, float4 distances)
304 {
305     float4 color = float4(light.color, 1.0);
306
307     color.a *= PunctualLightAttenuation(distances, light.rangeAttenuationScale, light.rangeAttenuationBias,
308                                         light.angleScale, light.angleOffset);
309
310 #ifndef LIGHT_EVALUATION_NO_HEIGHT_FOG
311     // Height fog attenuation.
312     // TODO: add an if()?
313     {
314         float cosZenithAngle = L.y;
315         float distToLight = (light.lightType == GPULIGHTTYPE_PROJECTOR_BOX) ? distances.w : distances.x;
316         float fragmentHeight = posInput.positionWS.y;
317         color.a *= TransmittanceHeightFog(_HeightFogBaseExtinction, _HeightFogBaseHeight,
318                                           _HeightFogExponents, cosZenithAngle,
319                                           fragmentHeight, distToLight);
320     }
321 #endif
322
323     // Projector lights (box, pyramid) always have cookies, so we can perform clipping inside the if().
324     // Thus why we don't disable the code here based on LIGHT_EVALUATION_NO_COOKIE but we do it
325     // inside the EvaluateCookie_Punctual call
326     if (light.cookieIndex >= 0)
327     {
328         float3 lightToSample = posInput.positionWS - light.positionRWS;
329         float4 cookie = EvaluateCookie_Punctual(lightLoopContext, light, lightToSample);
330
331         color *= cookie;
332     }
333
334     return color;
335 }
336
337 // distances = {d, d^2, 1/d, d_proj}, where d_proj = dot(lightToSample, light.forward).
338 float EvaluateShadow_Punctual(LightLoopContext lightLoopContext, PositionInputs posInput,
339                               LightData light, BuiltinData builtinData, float3 N, float3 L, float4 distances)
340 {
341 #ifndef LIGHT_EVALUATION_NO_SHADOWS
342     float shadow     = 1.0;
343     float shadowMask = 1.0;
344     float NdotL      = dot(N, L); // Disable contact shadow and shadow mask when facing away from light (i.e transmission)
345
346
347 #ifdef SHADOWS_SHADOWMASK
348     // shadowMaskSelector.x is -1 if there is no shadow mask
349     // Note that we override shadow value (in case we don't have any dynamic shadow)
350     shadow = shadowMask = (light.shadowMaskSelector.x >= 0.0 && NdotL > 0.0) ? dot(BUILTIN_DATA_SHADOW_MASK, light.shadowMaskSelector) : 1.0;
351 #endif
352
353     if ((light.shadowIndex >= 0) && (light.shadowDimmer > 0))
354     {
355         shadow = GetPunctualShadowAttenuation(lightLoopContext.shadowContext, posInput.positionSS, posInput.positionWS, N, light.shadowIndex, L, distances.x, light.lightType == GPULIGHTTYPE_POINT, light.lightType != GPULIGHTTYPE_PROJECTOR_BOX);
356
357 #ifdef SHADOWS_SHADOWMASK
358         // Note: Legacy Unity have two shadow mask mode. ShadowMask (ShadowMask contain static objects shadow and ShadowMap contain only dynamic objects shadow, final result is the minimun of both value)
359         // and ShadowMask_Distance (ShadowMask contain static objects shadow and ShadowMap contain everything and is blend with ShadowMask based on distance (Global distance setup in QualitySettigns)).
360         // HDRenderPipeline change this behavior. Only ShadowMask mode is supported but we support both blend with distance AND minimun of both value. Distance is control by light.
361         // The following code do this.
362         // The min handle the case of having only dynamic objects in the ShadowMap
363         // The second case for blend with distance is handled with ShadowDimmer. ShadowDimmer is define manually and by shadowDistance by light.
364         // With distance, ShadowDimmer become one and only the ShadowMask appear, we get the blend with distance behavior.
365         shadow = light.nonLightMappedOnly ? min(shadowMask, shadow) : shadow;
366     #endif
367
368         shadow = lerp(shadowMask, shadow, light.shadowDimmer);
369     }
370
371     // Transparents have no contact shadow information
372 #if !defined(_SURFACE_TYPE_TRANSPARENT) && !defined(LIGHT_EVALUATION_NO_CONTACT_SHADOWS)
373     shadow = min(shadow, NdotL > 0.0 ? GetContactShadow(lightLoopContext, light.contactShadowMask) : 1.0);
374 #endif
375
376 #ifdef DEBUG_DISPLAY
377     if (_DebugShadowMapMode == SHADOWMAPDEBUGMODE_SINGLE_SHADOW && light.shadowIndex == _DebugSingleShadowIndex)
378         g_DebugShadowAttenuation = shadow;
379 #endif
380     return shadow;
381 #else // LIGHT_EVALUATION_NO_SHADOWS
382     return 1.0;
383 #endif
384 }
385
386 //-----------------------------------------------------------------------------
387 // Reflection probe evaluation helper
388 //-----------------------------------------------------------------------------
389
390 #ifndef OVERRIDE_EVALUATE_ENV_INTERSECTION
391 // Environment map share function
392 #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/Reflection/VolumeProjection.hlsl"
393
394 void EvaluateLight_EnvIntersection(float3 positionWS, float3 normalWS, EnvLightData light, int influenceShapeType, inout float3 R, inout float weight)
395 {
396     // Guideline for reflection volume: In HDRenderPipeline we separate the projection volume (the proxy of the scene) from the influence volume (what pixel on the screen is affected)
397     // However we add the constrain that the shape of the projection and influence volume is the same (i.e if we have a sphere shape projection volume, we have a shape influence).
398     // It allow to have more coherence for the dynamic if in shader code.
399     // Users can also chose to not have any projection, in this case we use the property minProjectionDistance to minimize code change. minProjectionDistance is set to huge number
400     // that simulate effect of no shape projection
401
402     float3x3 worldToIS = WorldToInfluenceSpace(light); // IS: Influence space
403     float3 positionIS = WorldToInfluencePosition(light, worldToIS, positionWS);
404     float3 dirIS = normalize(mul(R, worldToIS));
405
406     float3x3 worldToPS = WorldToProxySpace(light); // PS: Proxy space
407     float3 positionPS = WorldToProxyPosition(light, worldToPS, positionWS);
408     float3 dirPS = mul(R, worldToPS);
409
410     float projectionDistance = 0;
411
412     // Process the projection
413     // In Unity the cubemaps are capture with the localToWorld transform of the component.
414     // This mean that location and orientation matter. So after intersection of proxy volume we need to convert back to world.
415     if (influenceShapeType == ENVSHAPETYPE_SPHERE)
416     {
417         projectionDistance = IntersectSphereProxy(light, dirPS, positionPS);
418         // We can reuse dist calculate in LS directly in WS as there is no scaling. Also the offset is already include in light.capturePositionRWS
419         R = (positionWS + projectionDistance * R) - light.capturePositionRWS;
420
421         weight = InfluenceSphereWeight(light, normalWS, positionWS, positionIS, dirIS);
422     }
423     else if (influenceShapeType == ENVSHAPETYPE_BOX)
424     {
425         projectionDistance = IntersectBoxProxy(light, dirPS, positionPS);
426         // No need to normalize for fetching cubemap
427         // We can reuse dist calculate in LS directly in WS as there is no scaling. Also the offset is already include in light.capturePositionRWS
428         R = (positionWS + projectionDistance * R) - light.capturePositionRWS;
429
430         weight = InfluenceBoxWeight(light, normalWS, positionWS, positionIS, dirIS);
431     }
432
433     // Smooth weighting
434     weight = Smoothstep01(weight);
435     weight *= light.weight;
436 }
437 #endif