A game about forced loneliness, made by TACStudios
1#ifndef UNITY_VOLUME_RENDERING_INCLUDED
2#define UNITY_VOLUME_RENDERING_INCLUDED
3
4#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonLighting.hlsl"
5
6// Reminder:
7// OpticalDepth(x, y) = Integral{x, y}{Extinction(t) dt}
8// Transmittance(x, y) = Exp(-OpticalDepth(x, y))
9// Transmittance(x, z) = Transmittance(x, y) * Transmittance(y, z)
10// Integral{a, b}{Transmittance(0, t) dt} = Transmittance(0, a) * Integral{a, b}{Transmittance(0, t - a) dt}
11
12float TransmittanceFromOpticalDepth(float opticalDepth)
13{
14 return exp(-opticalDepth);
15}
16
17float3 TransmittanceFromOpticalDepth(float3 opticalDepth)
18{
19 return exp(-opticalDepth);
20}
21
22float OpacityFromOpticalDepth(float opticalDepth)
23{
24 return 1 - TransmittanceFromOpticalDepth(opticalDepth);
25}
26
27float3 OpacityFromOpticalDepth(float3 opticalDepth)
28{
29 return 1 - TransmittanceFromOpticalDepth(opticalDepth);
30}
31
32float OpticalDepthFromOpacity(float opacity)
33{
34 return -log(1 - opacity);
35}
36
37float3 OpticalDepthFromOpacity(float3 opacity)
38{
39 return -log(1 - opacity);
40}
41
42//
43// ---------------------------------- Deep Pixel Compositing ---------------------------------------
44//
45
46// TODO: it would be good to improve the perf and numerical stability
47// of approximations below by finding a polynomial approximation.
48
49// input = {radiance, opacity}
50// Note that opacity must be less than 1 (not fully opaque).
51real4 LinearizeRGBA(real4 value)
52{
53 // See "Deep Compositing Using Lie Algebras".
54 // log(A) = {OpticalDepthFromOpacity(A.a) / A.a * A.rgb, -OpticalDepthFromOpacity(A.a)}.
55 // We drop redundant negations.
56 real a = value.a;
57 real d = -log(1 - a);
58 real r = (a >= REAL_EPS) ? (d * rcp(a)) : 1; // Prevent numerical explosion
59 return real4(r * value.rgb, d);
60}
61
62// input = {radiance, optical_depth}
63// Note that opacity must be less than 1 (not fully opaque).
64real4 LinearizeRGBD(real4 value)
65{
66 // See "Deep Compositing Using Lie Algebras".
67 // log(A) = {A.a / OpacityFromOpticalDepth(A.a) * A.rgb, -A.a}.
68 // We drop redundant negations.
69 real d = value.a;
70 real a = 1 - exp(-d);
71 real r = (a >= REAL_EPS) ? (d * rcp(a)) : 1; // Prevent numerical explosion
72 return real4(r * value.rgb, d);
73}
74
75// output = {radiance, opacity}
76// Note that opacity must be less than 1 (not fully opaque).
77real4 DelinearizeRGBA(real4 value)
78{
79 // See "Deep Compositing Using Lie Algebras".
80 // exp(B) = {OpacityFromOpticalDepth(-B.a) / -B.a * B.rgb, OpacityFromOpticalDepth(-B.a)}.
81 // We drop redundant negations.
82 real d = value.a;
83 real a = 1 - exp(-d);
84 real i = (a >= REAL_EPS) ? (a * rcp(d)) : 1; // Prevent numerical explosion
85 return real4(i * value.rgb, a);
86}
87
88// input = {radiance, optical_depth}
89// Note that opacity must be less than 1 (not fully opaque).
90real4 DelinearizeRGBD(real4 value)
91{
92 // See "Deep Compositing Using Lie Algebras".
93 // exp(B) = {OpacityFromOpticalDepth(-B.a) / -B.a * B.rgb, -B.a}.
94 // We drop redundant negations.
95 real d = value.a;
96 real a = 1 - exp(-d);
97 real i = (a >= REAL_EPS) ? (a * rcp(d)) : 1; // Prevent numerical explosion
98 return real4(i * value.rgb, d);
99}
100
101//
102// ----------------------------- Homogeneous Participating Media -----------------------------------
103//
104
105real OpticalDepthHomogeneousMedium(real extinction, real intervalLength)
106{
107 return extinction * intervalLength;
108}
109
110real TransmittanceHomogeneousMedium(real extinction, real intervalLength)
111{
112 return TransmittanceFromOpticalDepth(OpticalDepthHomogeneousMedium(extinction, intervalLength));
113}
114
115// Integral{a, b}{TransmittanceHomogeneousMedium(k, t - a) dt}.
116real TransmittanceIntegralHomogeneousMedium(real extinction, real intervalLength)
117{
118 // Note: when multiplied by the extinction coefficient, it becomes
119 // Albedo * (1 - TransmittanceFromOpticalDepth(d)) = Albedo * Opacity(d).
120 return rcp(extinction) - rcp(extinction) * exp(-extinction * intervalLength);
121}
122
123//
124// ----------------------------------- Height Fog --------------------------------------------------
125//
126
127// Can be used to scale base extinction and scattering coefficients.
128float ComputeHeightFogMultiplier(real height, real baseHeight, real2 heightExponents)
129{
130 real h = max(height - baseHeight, 0);
131 float rcpH = heightExponents.x;
132
133 return exp(-h * rcpH);
134}
135
136// Optical depth between two endpoints.
137float OpticalDepthHeightFog(real baseExtinction, real baseHeight, real2 heightExponents,
138 real cosZenith, real startHeight, real intervalLength)
139{
140 // Height fog is composed of two slices of optical depth:
141 // - homogeneous fog below 'baseHeight': d = k * t
142 // - exponential fog above 'baseHeight': d = Integrate[k * e^(-(h + z * x) / H) dx, {x, 0, t}]
143
144 float H = heightExponents.y;
145 float rcpH = heightExponents.x;
146 real Z = cosZenith;
147 real absZ = max(abs(cosZenith), 0.001f);
148 real rcpAbsZ = rcp(absZ);
149
150 real endHeight = startHeight + intervalLength * Z;
151 real minHeight = min(startHeight, endHeight);
152 real h = max(minHeight - baseHeight, 0);
153
154 real homFogDist = clamp((baseHeight - minHeight) * rcpAbsZ, 0, intervalLength);
155 real expFogDist = intervalLength - homFogDist;
156 float expFogMult = exp(-h * rcpH) * (1 - exp(-expFogDist * absZ * rcpH)) * (rcpAbsZ * H);
157
158 return baseExtinction * (homFogDist + expFogMult);
159}
160
161// This version of the function assumes the interval of infinite length.
162float OpticalDepthHeightFog(real baseExtinction, real baseHeight, real2 heightExponents,
163 real cosZenith, real startHeight)
164{
165 float H = heightExponents.y;
166 float rcpH = heightExponents.x;
167 real Z = cosZenith;
168 real absZ = max(abs(cosZenith), REAL_EPS);
169 real rcpAbsZ = rcp(absZ);
170
171 real minHeight = (Z >= 0) ? startHeight : -rcp(REAL_EPS);
172 real h = max(minHeight - baseHeight, 0);
173
174 real homFogDist = max((baseHeight - minHeight) * rcpAbsZ, 0);
175 float expFogMult = exp(-h * rcpH) * (rcpAbsZ * H);
176
177 return baseExtinction * (homFogDist + expFogMult);
178}
179
180float TransmittanceHeightFog(real baseExtinction, real baseHeight, real2 heightExponents,
181 real cosZenith, real startHeight, real intervalLength)
182{
183 float od = OpticalDepthHeightFog(baseExtinction, baseHeight, heightExponents,
184 cosZenith, startHeight, intervalLength);
185 return TransmittanceFromOpticalDepth(od);
186}
187
188float TransmittanceHeightFog(real baseExtinction, real baseHeight, real2 heightExponents,
189 real cosZenith, real startHeight)
190{
191 float od = OpticalDepthHeightFog(baseExtinction, baseHeight, heightExponents,
192 cosZenith, startHeight);
193 return TransmittanceFromOpticalDepth(od);
194}
195
196//
197// ----------------------------------- Phase Functions ---------------------------------------------
198//
199
200real IsotropicPhaseFunction()
201{
202 return INV_FOUR_PI;
203}
204
205real RayleighPhaseFunction(real cosTheta)
206{
207 real k = 3 / (16 * PI);
208 return k * (1 + cosTheta * cosTheta);
209}
210
211real HenyeyGreensteinPhasePartConstant(real anisotropy)
212{
213 real g = anisotropy;
214
215 return INV_FOUR_PI * (1 - g * g);
216}
217
218real HenyeyGreensteinPhasePartVarying(real anisotropy, real cosTheta)
219{
220 real g = anisotropy;
221 real x = 1 + g * g - 2 * g * cosTheta;
222 real f = rsqrt(max(x, REAL_EPS)); // x^(-1/2)
223
224 return f * f * f; // x^(-3/2)
225}
226
227real HenyeyGreensteinPhaseFunction(real anisotropy, real cosTheta)
228{
229 return HenyeyGreensteinPhasePartConstant(anisotropy) *
230 HenyeyGreensteinPhasePartVarying(anisotropy, cosTheta);
231}
232
233// "Physically Based Rendering, 15.2.3 Sampling Phase Functions"
234bool SampleHenyeyGreenstein(real3 incomingDir,
235 real anisotropy,
236 real3 inputSample,
237 out real3 outgoingDir,
238 out real pdf)
239{
240 real g = anisotropy;
241
242 // Compute costheta
243 real cosTheta;
244 if (abs(g) < 0.001)
245 {
246 cosTheta = 1.0 - 2.0 * inputSample.x;
247 }
248 else
249 {
250 real sqrTerm = (1.0 - g * g) / (1.0 - g + 2.0 * g * inputSample.x);
251 cosTheta = (1.0 + g * g - sqrTerm * sqrTerm) / (2 * g);
252 }
253
254 // Compute direction
255 real sinTheta = sqrt(max(0.0, 1.0 - cosTheta * cosTheta));
256 real phi = 2.0 * PI * inputSample.y;
257
258 real3 wc = normalize(incomingDir);
259 real3x3 coordsys = GetLocalFrame(wc);
260
261 real sinPhi, cosPhi;
262 sincos(phi, sinPhi, cosPhi);
263 outgoingDir = sinTheta * cosPhi * coordsys[0] +
264 sinTheta * sinPhi * coordsys[1] +
265 cosTheta * coordsys[2];
266 pdf = HenyeyGreensteinPhaseFunction(g, cosTheta);
267
268 return any(pdf);
269}
270
271real CornetteShanksPhasePartConstant(real anisotropy)
272{
273 real g = anisotropy;
274
275 return (3 / (8 * PI)) * (1 - g * g) / (2 + g * g);
276}
277
278// Similar to the RayleighPhaseFunction.
279real CornetteShanksPhasePartSymmetrical(real cosTheta)
280{
281 real h = 1 + cosTheta * cosTheta;
282 return h;
283}
284
285real CornetteShanksPhasePartAsymmetrical(real anisotropy, real cosTheta)
286{
287 real g = anisotropy;
288 real x = 1 + g * g - 2 * g * cosTheta;
289 real f = rsqrt(max(x, REAL_EPS)); // x^(-1/2)
290 return f * f * f; // x^(-3/2)
291}
292
293real CornetteShanksPhasePartVarying(real anisotropy, real cosTheta)
294{
295 return CornetteShanksPhasePartSymmetrical(cosTheta) *
296 CornetteShanksPhasePartAsymmetrical(anisotropy, cosTheta); // h * x^(-3/2)
297}
298
299// A better approximation of the Mie phase function.
300// Ref: Henyey-Greenstein and Mie phase functions in Monte Carlo radiative transfer computations
301real CornetteShanksPhaseFunction(real anisotropy, real cosTheta)
302{
303 return CornetteShanksPhasePartConstant(anisotropy) *
304 CornetteShanksPhasePartVarying(anisotropy, cosTheta);
305}
306
307//
308// --------------------------------- Importance Sampling -------------------------------------------
309//
310
311// Samples the interval of homogeneous participating medium using the closed-form tracking approach
312// (proportionally to the transmittance).
313// Returns the offset from the start of the interval and the weight = (transmittance / pdf).
314// Ref: Monte Carlo Methods for Volumetric Light Transport Simulation, p. 5.
315void ImportanceSampleHomogeneousMedium(real rndVal, real extinction, real intervalLength,
316 out real offset, out real weight)
317{
318 // pdf = extinction * exp(extinction * (intervalLength - t)) / (exp(intervalLength * extinction) - 1)
319 // pdf = extinction * exp(-extinction * t) / (1 - exp(-extinction * intervalLength))
320 // weight = TransmittanceFromOpticalDepth(t) / pdf
321 // weight = exp(-extinction * t) / pdf
322 // weight = (1 - exp(-extinction * intervalLength)) / extinction
323 // weight = OpacityFromOpticalDepth(extinction * intervalLength) / extinction
324
325 real x = 1 - exp(-extinction * intervalLength);
326 real c = rcp(extinction);
327
328 // TODO: return 'rcpPdf' to support imperfect importance sampling...
329 weight = x * c;
330 offset = -log(1 - rndVal * x) * c;
331}
332
333void ImportanceSampleExponentialMedium(real rndVal, real extinction, real falloff,
334 out real offset, out real rcpPdf)
335{
336
337 // Extinction[t] = Extinction[0] * exp(-falloff * t).
338 real c = extinction;
339 real a = falloff;
340
341 // TODO: optimize...
342 offset = -log(1 - a / c * log(rndVal)) / a;
343 rcpPdf = rcp(c * exp(-a * offset) * exp(-c / a * (1 - exp(-a * offset))));
344}
345
346// Implements equiangular light sampling.
347// Returns the distance from the origin of the ray, the squared distance from the light,
348// and the reciprocal of the PDF.
349// Ref: Importance Sampling of Area Lights in Participating Medium.
350void ImportanceSamplePunctualLight(real rndVal, real3 lightPosition, real lightSqRadius,
351 real3 rayOrigin, real3 rayDirection,
352 real tMin, real tMax,
353 out real t, out real sqDist, out real rcpPdf)
354{
355 real3 originToLight = lightPosition - rayOrigin;
356 real originToLightProjDist = dot(originToLight, rayDirection);
357 real originToLightSqDist = dot(originToLight, originToLight);
358 real rayToLightSqDist = originToLightSqDist - originToLightProjDist * originToLightProjDist;
359
360 // Virtually offset the light to modify the PDF distribution.
361 real sqD = max(rayToLightSqDist + lightSqRadius, REAL_EPS);
362 real rcpD = rsqrt(sqD);
363 real d = sqD * rcpD;
364 real a = tMin - originToLightProjDist;
365 real b = tMax - originToLightProjDist;
366 real x = a * rcpD;
367 real y = b * rcpD;
368
369#if 0
370 real theta0 = FastATan(x);
371 real theta1 = FastATan(y);
372 real gamma = theta1 - theta0;
373 real tanTheta = tan(theta0 + rndVal * gamma);
374#else
375 // Same but faster:
376 // atan(y) - atan(x) = atan((y - x) / (1 + x * y))
377 // tan(atan(x) + z) = (x * cos(z) + sin(z)) / (cos(z) - x * sin(z))
378 // Both the tangent and the angle cannot be negative.
379 real tanGamma = abs((y - x) * rcp(max(0, 1 + x * y)));
380 real gamma = FastATanPos(tanGamma);
381 real z = rndVal * gamma;
382 real numer = x * cos(z) + sin(z);
383 real denom = cos(z) - x * sin(z);
384 real tanTheta = numer * rcp(denom);
385#endif
386
387 real tRelative = d * tanTheta;
388
389 sqDist = sqD + tRelative * tRelative;
390 rcpPdf = gamma * rcpD * sqDist;
391 t = originToLightProjDist + tRelative;
392
393 // Remove the virtual light offset to obtain the real geometric distance.
394 sqDist = max(sqDist - lightSqRadius, REAL_EPS);
395}
396
397// Returns the cosine.
398// Weight = Phase / Pdf = 1.
399real ImportanceSampleRayleighPhase(real rndVal)
400{
401 // real a = sqrt(16 * (rndVal - 1) * rndVal + 5);
402 // real b = -4 * rndVal + a + 2;
403 // real c = PositivePow(b, 0.33333333);
404 // return rcp(c) - c;
405
406 // Approximate...
407 return lerp(cos(PI * rndVal + PI), 2 * rndVal - 1, 0.5);
408}
409
410//
411// ------------------------------------ Miscellaneous ----------------------------------------------
412//
413
414// Absorption coefficient from Disney: http://blog.selfshadow.com/publications/s2015-shading-course/burley/s2015_pbs_disney_bsdf_notes.pdf
415real3 TransmittanceColorAtDistanceToAbsorption(real3 transmittanceColor, real atDistance)
416{
417 return -log(transmittanceColor + REAL_EPS) / max(atDistance, REAL_EPS);
418}
419
420float ApplyExponentialFadeFactor(float fade, bool exponential, bool multiplyBlendMode)
421{
422 if (exponential)
423 {
424 if (multiplyBlendMode)
425 fade = 1 - PositivePow(abs(fade - 1), 2.2);
426 else
427 fade = PositivePow(fade, 2.2);
428 }
429 return fade;
430}
431
432float ComputeVolumeFadeFactor(float3 coordNDC, float dist,
433 float3 rcpPosFaceFade, float3 rcpNegFaceFade, bool invertFade,
434 float rcpDistFadeLen, float endTimesRcpDistFadeLen,
435 bool exponentialFalloff, bool multiplyBlendMode)
436{
437 float3 posF = Remap10(coordNDC, rcpPosFaceFade, rcpPosFaceFade);
438 float3 negF = Remap01(coordNDC, rcpNegFaceFade, 0);
439 float dstF = Remap10(dist, rcpDistFadeLen, endTimesRcpDistFadeLen);
440 float fade = posF.x * posF.y * posF.z * negF.x * negF.y * negF.z;
441
442 // We only apply exponential falloff on the Blend Distance and not Distance Fade
443 fade = ApplyExponentialFadeFactor(fade, exponentialFalloff, multiplyBlendMode);
444
445 fade = dstF * (invertFade ? (1 - fade) : fade);
446
447 return fade;
448}
449
450float ExtinctionFromMeanFreePath(float meanFreePath)
451{
452 // Keep in sync with kMinFogDistance
453 return rcp(max(0.05, meanFreePath));
454}
455
456#endif // UNITY_VOLUME_RENDERING_INCLUDED