457 lines
15 KiB
HLSL
457 lines
15 KiB
HLSL
|
#ifndef UNITY_VOLUME_RENDERING_INCLUDED
|
||
|
#define UNITY_VOLUME_RENDERING_INCLUDED
|
||
|
|
||
|
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonLighting.hlsl"
|
||
|
|
||
|
// Reminder:
|
||
|
// OpticalDepth(x, y) = Integral{x, y}{Extinction(t) dt}
|
||
|
// Transmittance(x, y) = Exp(-OpticalDepth(x, y))
|
||
|
// Transmittance(x, z) = Transmittance(x, y) * Transmittance(y, z)
|
||
|
// Integral{a, b}{Transmittance(0, t) dt} = Transmittance(0, a) * Integral{a, b}{Transmittance(0, t - a) dt}
|
||
|
|
||
|
float TransmittanceFromOpticalDepth(float opticalDepth)
|
||
|
{
|
||
|
return exp(-opticalDepth);
|
||
|
}
|
||
|
|
||
|
float3 TransmittanceFromOpticalDepth(float3 opticalDepth)
|
||
|
{
|
||
|
return exp(-opticalDepth);
|
||
|
}
|
||
|
|
||
|
float OpacityFromOpticalDepth(float opticalDepth)
|
||
|
{
|
||
|
return 1 - TransmittanceFromOpticalDepth(opticalDepth);
|
||
|
}
|
||
|
|
||
|
float3 OpacityFromOpticalDepth(float3 opticalDepth)
|
||
|
{
|
||
|
return 1 - TransmittanceFromOpticalDepth(opticalDepth);
|
||
|
}
|
||
|
|
||
|
float OpticalDepthFromOpacity(float opacity)
|
||
|
{
|
||
|
return -log(1 - opacity);
|
||
|
}
|
||
|
|
||
|
float3 OpticalDepthFromOpacity(float3 opacity)
|
||
|
{
|
||
|
return -log(1 - opacity);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// ---------------------------------- Deep Pixel Compositing ---------------------------------------
|
||
|
//
|
||
|
|
||
|
// TODO: it would be good to improve the perf and numerical stability
|
||
|
// of approximations below by finding a polynomial approximation.
|
||
|
|
||
|
// input = {radiance, opacity}
|
||
|
// Note that opacity must be less than 1 (not fully opaque).
|
||
|
real4 LinearizeRGBA(real4 value)
|
||
|
{
|
||
|
// See "Deep Compositing Using Lie Algebras".
|
||
|
// log(A) = {OpticalDepthFromOpacity(A.a) / A.a * A.rgb, -OpticalDepthFromOpacity(A.a)}.
|
||
|
// We drop redundant negations.
|
||
|
real a = value.a;
|
||
|
real d = -log(1 - a);
|
||
|
real r = (a >= REAL_EPS) ? (d * rcp(a)) : 1; // Prevent numerical explosion
|
||
|
return real4(r * value.rgb, d);
|
||
|
}
|
||
|
|
||
|
// input = {radiance, optical_depth}
|
||
|
// Note that opacity must be less than 1 (not fully opaque).
|
||
|
real4 LinearizeRGBD(real4 value)
|
||
|
{
|
||
|
// See "Deep Compositing Using Lie Algebras".
|
||
|
// log(A) = {A.a / OpacityFromOpticalDepth(A.a) * A.rgb, -A.a}.
|
||
|
// We drop redundant negations.
|
||
|
real d = value.a;
|
||
|
real a = 1 - exp(-d);
|
||
|
real r = (a >= REAL_EPS) ? (d * rcp(a)) : 1; // Prevent numerical explosion
|
||
|
return real4(r * value.rgb, d);
|
||
|
}
|
||
|
|
||
|
// output = {radiance, opacity}
|
||
|
// Note that opacity must be less than 1 (not fully opaque).
|
||
|
real4 DelinearizeRGBA(real4 value)
|
||
|
{
|
||
|
// See "Deep Compositing Using Lie Algebras".
|
||
|
// exp(B) = {OpacityFromOpticalDepth(-B.a) / -B.a * B.rgb, OpacityFromOpticalDepth(-B.a)}.
|
||
|
// We drop redundant negations.
|
||
|
real d = value.a;
|
||
|
real a = 1 - exp(-d);
|
||
|
real i = (a >= REAL_EPS) ? (a * rcp(d)) : 1; // Prevent numerical explosion
|
||
|
return real4(i * value.rgb, a);
|
||
|
}
|
||
|
|
||
|
// input = {radiance, optical_depth}
|
||
|
// Note that opacity must be less than 1 (not fully opaque).
|
||
|
real4 DelinearizeRGBD(real4 value)
|
||
|
{
|
||
|
// See "Deep Compositing Using Lie Algebras".
|
||
|
// exp(B) = {OpacityFromOpticalDepth(-B.a) / -B.a * B.rgb, -B.a}.
|
||
|
// We drop redundant negations.
|
||
|
real d = value.a;
|
||
|
real a = 1 - exp(-d);
|
||
|
real i = (a >= REAL_EPS) ? (a * rcp(d)) : 1; // Prevent numerical explosion
|
||
|
return real4(i * value.rgb, d);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// ----------------------------- Homogeneous Participating Media -----------------------------------
|
||
|
//
|
||
|
|
||
|
real OpticalDepthHomogeneousMedium(real extinction, real intervalLength)
|
||
|
{
|
||
|
return extinction * intervalLength;
|
||
|
}
|
||
|
|
||
|
real TransmittanceHomogeneousMedium(real extinction, real intervalLength)
|
||
|
{
|
||
|
return TransmittanceFromOpticalDepth(OpticalDepthHomogeneousMedium(extinction, intervalLength));
|
||
|
}
|
||
|
|
||
|
// Integral{a, b}{TransmittanceHomogeneousMedium(k, t - a) dt}.
|
||
|
real TransmittanceIntegralHomogeneousMedium(real extinction, real intervalLength)
|
||
|
{
|
||
|
// Note: when multiplied by the extinction coefficient, it becomes
|
||
|
// Albedo * (1 - TransmittanceFromOpticalDepth(d)) = Albedo * Opacity(d).
|
||
|
return rcp(extinction) - rcp(extinction) * exp(-extinction * intervalLength);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// ----------------------------------- Height Fog --------------------------------------------------
|
||
|
//
|
||
|
|
||
|
// Can be used to scale base extinction and scattering coefficients.
|
||
|
float ComputeHeightFogMultiplier(real height, real baseHeight, real2 heightExponents)
|
||
|
{
|
||
|
real h = max(height - baseHeight, 0);
|
||
|
float rcpH = heightExponents.x;
|
||
|
|
||
|
return exp(-h * rcpH);
|
||
|
}
|
||
|
|
||
|
// Optical depth between two endpoints.
|
||
|
float OpticalDepthHeightFog(real baseExtinction, real baseHeight, real2 heightExponents,
|
||
|
real cosZenith, real startHeight, real intervalLength)
|
||
|
{
|
||
|
// Height fog is composed of two slices of optical depth:
|
||
|
// - homogeneous fog below 'baseHeight': d = k * t
|
||
|
// - exponential fog above 'baseHeight': d = Integrate[k * e^(-(h + z * x) / H) dx, {x, 0, t}]
|
||
|
|
||
|
float H = heightExponents.y;
|
||
|
float rcpH = heightExponents.x;
|
||
|
real Z = cosZenith;
|
||
|
real absZ = max(abs(cosZenith), 0.001f);
|
||
|
real rcpAbsZ = rcp(absZ);
|
||
|
|
||
|
real endHeight = startHeight + intervalLength * Z;
|
||
|
real minHeight = min(startHeight, endHeight);
|
||
|
real h = max(minHeight - baseHeight, 0);
|
||
|
|
||
|
real homFogDist = clamp((baseHeight - minHeight) * rcpAbsZ, 0, intervalLength);
|
||
|
real expFogDist = intervalLength - homFogDist;
|
||
|
float expFogMult = exp(-h * rcpH) * (1 - exp(-expFogDist * absZ * rcpH)) * (rcpAbsZ * H);
|
||
|
|
||
|
return baseExtinction * (homFogDist + expFogMult);
|
||
|
}
|
||
|
|
||
|
// This version of the function assumes the interval of infinite length.
|
||
|
float OpticalDepthHeightFog(real baseExtinction, real baseHeight, real2 heightExponents,
|
||
|
real cosZenith, real startHeight)
|
||
|
{
|
||
|
float H = heightExponents.y;
|
||
|
float rcpH = heightExponents.x;
|
||
|
real Z = cosZenith;
|
||
|
real absZ = max(abs(cosZenith), REAL_EPS);
|
||
|
real rcpAbsZ = rcp(absZ);
|
||
|
|
||
|
real minHeight = (Z >= 0) ? startHeight : -rcp(REAL_EPS);
|
||
|
real h = max(minHeight - baseHeight, 0);
|
||
|
|
||
|
real homFogDist = max((baseHeight - minHeight) * rcpAbsZ, 0);
|
||
|
float expFogMult = exp(-h * rcpH) * (rcpAbsZ * H);
|
||
|
|
||
|
return baseExtinction * (homFogDist + expFogMult);
|
||
|
}
|
||
|
|
||
|
float TransmittanceHeightFog(real baseExtinction, real baseHeight, real2 heightExponents,
|
||
|
real cosZenith, real startHeight, real intervalLength)
|
||
|
{
|
||
|
float od = OpticalDepthHeightFog(baseExtinction, baseHeight, heightExponents,
|
||
|
cosZenith, startHeight, intervalLength);
|
||
|
return TransmittanceFromOpticalDepth(od);
|
||
|
}
|
||
|
|
||
|
float TransmittanceHeightFog(real baseExtinction, real baseHeight, real2 heightExponents,
|
||
|
real cosZenith, real startHeight)
|
||
|
{
|
||
|
float od = OpticalDepthHeightFog(baseExtinction, baseHeight, heightExponents,
|
||
|
cosZenith, startHeight);
|
||
|
return TransmittanceFromOpticalDepth(od);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// ----------------------------------- Phase Functions ---------------------------------------------
|
||
|
//
|
||
|
|
||
|
real IsotropicPhaseFunction()
|
||
|
{
|
||
|
return INV_FOUR_PI;
|
||
|
}
|
||
|
|
||
|
real RayleighPhaseFunction(real cosTheta)
|
||
|
{
|
||
|
real k = 3 / (16 * PI);
|
||
|
return k * (1 + cosTheta * cosTheta);
|
||
|
}
|
||
|
|
||
|
real HenyeyGreensteinPhasePartConstant(real anisotropy)
|
||
|
{
|
||
|
real g = anisotropy;
|
||
|
|
||
|
return INV_FOUR_PI * (1 - g * g);
|
||
|
}
|
||
|
|
||
|
real HenyeyGreensteinPhasePartVarying(real anisotropy, real cosTheta)
|
||
|
{
|
||
|
real g = anisotropy;
|
||
|
real x = 1 + g * g - 2 * g * cosTheta;
|
||
|
real f = rsqrt(max(x, REAL_EPS)); // x^(-1/2)
|
||
|
|
||
|
return f * f * f; // x^(-3/2)
|
||
|
}
|
||
|
|
||
|
real HenyeyGreensteinPhaseFunction(real anisotropy, real cosTheta)
|
||
|
{
|
||
|
return HenyeyGreensteinPhasePartConstant(anisotropy) *
|
||
|
HenyeyGreensteinPhasePartVarying(anisotropy, cosTheta);
|
||
|
}
|
||
|
|
||
|
// "Physically Based Rendering, 15.2.3 Sampling Phase Functions"
|
||
|
bool SampleHenyeyGreenstein(real3 incomingDir,
|
||
|
real anisotropy,
|
||
|
real3 inputSample,
|
||
|
out real3 outgoingDir,
|
||
|
out real pdf)
|
||
|
{
|
||
|
real g = anisotropy;
|
||
|
|
||
|
// Compute costheta
|
||
|
real cosTheta;
|
||
|
if (abs(g) < 0.001)
|
||
|
{
|
||
|
cosTheta = 1.0 - 2.0 * inputSample.x;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
real sqrTerm = (1.0 - g * g) / (1.0 - g + 2.0 * g * inputSample.x);
|
||
|
cosTheta = (1.0 + g * g - sqrTerm * sqrTerm) / (2 * g);
|
||
|
}
|
||
|
|
||
|
// Compute direction
|
||
|
real sinTheta = sqrt(max(0.0, 1.0 - cosTheta * cosTheta));
|
||
|
real phi = 2.0 * PI * inputSample.y;
|
||
|
|
||
|
real3 wc = normalize(incomingDir);
|
||
|
real3x3 coordsys = GetLocalFrame(wc);
|
||
|
|
||
|
real sinPhi, cosPhi;
|
||
|
sincos(phi, sinPhi, cosPhi);
|
||
|
outgoingDir = sinTheta * cosPhi * coordsys[0] +
|
||
|
sinTheta * sinPhi * coordsys[1] +
|
||
|
cosTheta * coordsys[2];
|
||
|
pdf = HenyeyGreensteinPhaseFunction(g, cosTheta);
|
||
|
|
||
|
return any(pdf);
|
||
|
}
|
||
|
|
||
|
real CornetteShanksPhasePartConstant(real anisotropy)
|
||
|
{
|
||
|
real g = anisotropy;
|
||
|
|
||
|
return (3 / (8 * PI)) * (1 - g * g) / (2 + g * g);
|
||
|
}
|
||
|
|
||
|
// Similar to the RayleighPhaseFunction.
|
||
|
real CornetteShanksPhasePartSymmetrical(real cosTheta)
|
||
|
{
|
||
|
real h = 1 + cosTheta * cosTheta;
|
||
|
return h;
|
||
|
}
|
||
|
|
||
|
real CornetteShanksPhasePartAsymmetrical(real anisotropy, real cosTheta)
|
||
|
{
|
||
|
real g = anisotropy;
|
||
|
real x = 1 + g * g - 2 * g * cosTheta;
|
||
|
real f = rsqrt(max(x, REAL_EPS)); // x^(-1/2)
|
||
|
return f * f * f; // x^(-3/2)
|
||
|
}
|
||
|
|
||
|
real CornetteShanksPhasePartVarying(real anisotropy, real cosTheta)
|
||
|
{
|
||
|
return CornetteShanksPhasePartSymmetrical(cosTheta) *
|
||
|
CornetteShanksPhasePartAsymmetrical(anisotropy, cosTheta); // h * x^(-3/2)
|
||
|
}
|
||
|
|
||
|
// A better approximation of the Mie phase function.
|
||
|
// Ref: Henyey-Greenstein and Mie phase functions in Monte Carlo radiative transfer computations
|
||
|
real CornetteShanksPhaseFunction(real anisotropy, real cosTheta)
|
||
|
{
|
||
|
return CornetteShanksPhasePartConstant(anisotropy) *
|
||
|
CornetteShanksPhasePartVarying(anisotropy, cosTheta);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// --------------------------------- Importance Sampling -------------------------------------------
|
||
|
//
|
||
|
|
||
|
// Samples the interval of homogeneous participating medium using the closed-form tracking approach
|
||
|
// (proportionally to the transmittance).
|
||
|
// Returns the offset from the start of the interval and the weight = (transmittance / pdf).
|
||
|
// Ref: Monte Carlo Methods for Volumetric Light Transport Simulation, p. 5.
|
||
|
void ImportanceSampleHomogeneousMedium(real rndVal, real extinction, real intervalLength,
|
||
|
out real offset, out real weight)
|
||
|
{
|
||
|
// pdf = extinction * exp(extinction * (intervalLength - t)) / (exp(intervalLength * extinction) - 1)
|
||
|
// pdf = extinction * exp(-extinction * t) / (1 - exp(-extinction * intervalLength))
|
||
|
// weight = TransmittanceFromOpticalDepth(t) / pdf
|
||
|
// weight = exp(-extinction * t) / pdf
|
||
|
// weight = (1 - exp(-extinction * intervalLength)) / extinction
|
||
|
// weight = OpacityFromOpticalDepth(extinction * intervalLength) / extinction
|
||
|
|
||
|
real x = 1 - exp(-extinction * intervalLength);
|
||
|
real c = rcp(extinction);
|
||
|
|
||
|
// TODO: return 'rcpPdf' to support imperfect importance sampling...
|
||
|
weight = x * c;
|
||
|
offset = -log(1 - rndVal * x) * c;
|
||
|
}
|
||
|
|
||
|
void ImportanceSampleExponentialMedium(real rndVal, real extinction, real falloff,
|
||
|
out real offset, out real rcpPdf)
|
||
|
{
|
||
|
|
||
|
// Extinction[t] = Extinction[0] * exp(-falloff * t).
|
||
|
real c = extinction;
|
||
|
real a = falloff;
|
||
|
|
||
|
// TODO: optimize...
|
||
|
offset = -log(1 - a / c * log(rndVal)) / a;
|
||
|
rcpPdf = rcp(c * exp(-a * offset) * exp(-c / a * (1 - exp(-a * offset))));
|
||
|
}
|
||
|
|
||
|
// Implements equiangular light sampling.
|
||
|
// Returns the distance from the origin of the ray, the squared distance from the light,
|
||
|
// and the reciprocal of the PDF.
|
||
|
// Ref: Importance Sampling of Area Lights in Participating Medium.
|
||
|
void ImportanceSamplePunctualLight(real rndVal, real3 lightPosition, real lightSqRadius,
|
||
|
real3 rayOrigin, real3 rayDirection,
|
||
|
real tMin, real tMax,
|
||
|
out real t, out real sqDist, out real rcpPdf)
|
||
|
{
|
||
|
real3 originToLight = lightPosition - rayOrigin;
|
||
|
real originToLightProjDist = dot(originToLight, rayDirection);
|
||
|
real originToLightSqDist = dot(originToLight, originToLight);
|
||
|
real rayToLightSqDist = originToLightSqDist - originToLightProjDist * originToLightProjDist;
|
||
|
|
||
|
// Virtually offset the light to modify the PDF distribution.
|
||
|
real sqD = max(rayToLightSqDist + lightSqRadius, REAL_EPS);
|
||
|
real rcpD = rsqrt(sqD);
|
||
|
real d = sqD * rcpD;
|
||
|
real a = tMin - originToLightProjDist;
|
||
|
real b = tMax - originToLightProjDist;
|
||
|
real x = a * rcpD;
|
||
|
real y = b * rcpD;
|
||
|
|
||
|
#if 0
|
||
|
real theta0 = FastATan(x);
|
||
|
real theta1 = FastATan(y);
|
||
|
real gamma = theta1 - theta0;
|
||
|
real tanTheta = tan(theta0 + rndVal * gamma);
|
||
|
#else
|
||
|
// Same but faster:
|
||
|
// atan(y) - atan(x) = atan((y - x) / (1 + x * y))
|
||
|
// tan(atan(x) + z) = (x * cos(z) + sin(z)) / (cos(z) - x * sin(z))
|
||
|
// Both the tangent and the angle cannot be negative.
|
||
|
real tanGamma = abs((y - x) * rcp(max(0, 1 + x * y)));
|
||
|
real gamma = FastATanPos(tanGamma);
|
||
|
real z = rndVal * gamma;
|
||
|
real numer = x * cos(z) + sin(z);
|
||
|
real denom = cos(z) - x * sin(z);
|
||
|
real tanTheta = numer * rcp(denom);
|
||
|
#endif
|
||
|
|
||
|
real tRelative = d * tanTheta;
|
||
|
|
||
|
sqDist = sqD + tRelative * tRelative;
|
||
|
rcpPdf = gamma * rcpD * sqDist;
|
||
|
t = originToLightProjDist + tRelative;
|
||
|
|
||
|
// Remove the virtual light offset to obtain the real geometric distance.
|
||
|
sqDist = max(sqDist - lightSqRadius, REAL_EPS);
|
||
|
}
|
||
|
|
||
|
// Returns the cosine.
|
||
|
// Weight = Phase / Pdf = 1.
|
||
|
real ImportanceSampleRayleighPhase(real rndVal)
|
||
|
{
|
||
|
// real a = sqrt(16 * (rndVal - 1) * rndVal + 5);
|
||
|
// real b = -4 * rndVal + a + 2;
|
||
|
// real c = PositivePow(b, 0.33333333);
|
||
|
// return rcp(c) - c;
|
||
|
|
||
|
// Approximate...
|
||
|
return lerp(cos(PI * rndVal + PI), 2 * rndVal - 1, 0.5);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// ------------------------------------ Miscellaneous ----------------------------------------------
|
||
|
//
|
||
|
|
||
|
// Absorption coefficient from Disney: http://blog.selfshadow.com/publications/s2015-shading-course/burley/s2015_pbs_disney_bsdf_notes.pdf
|
||
|
real3 TransmittanceColorAtDistanceToAbsorption(real3 transmittanceColor, real atDistance)
|
||
|
{
|
||
|
return -log(transmittanceColor + REAL_EPS) / max(atDistance, REAL_EPS);
|
||
|
}
|
||
|
|
||
|
float ApplyExponentialFadeFactor(float fade, bool exponential, bool multiplyBlendMode)
|
||
|
{
|
||
|
if (exponential)
|
||
|
{
|
||
|
if (multiplyBlendMode)
|
||
|
fade = 1 - PositivePow(abs(fade - 1), 2.2);
|
||
|
else
|
||
|
fade = PositivePow(fade, 2.2);
|
||
|
}
|
||
|
return fade;
|
||
|
}
|
||
|
|
||
|
float ComputeVolumeFadeFactor(float3 coordNDC, float dist,
|
||
|
float3 rcpPosFaceFade, float3 rcpNegFaceFade, bool invertFade,
|
||
|
float rcpDistFadeLen, float endTimesRcpDistFadeLen,
|
||
|
bool exponentialFalloff, bool multiplyBlendMode)
|
||
|
{
|
||
|
float3 posF = Remap10(coordNDC, rcpPosFaceFade, rcpPosFaceFade);
|
||
|
float3 negF = Remap01(coordNDC, rcpNegFaceFade, 0);
|
||
|
float dstF = Remap10(dist, rcpDistFadeLen, endTimesRcpDistFadeLen);
|
||
|
float fade = posF.x * posF.y * posF.z * negF.x * negF.y * negF.z;
|
||
|
|
||
|
// We only apply exponential falloff on the Blend Distance and not Distance Fade
|
||
|
fade = ApplyExponentialFadeFactor(fade, exponentialFalloff, multiplyBlendMode);
|
||
|
|
||
|
fade = dstF * (invertFade ? (1 - fade) : fade);
|
||
|
|
||
|
return fade;
|
||
|
}
|
||
|
|
||
|
float ExtinctionFromMeanFreePath(float meanFreePath)
|
||
|
{
|
||
|
// Keep in sync with kMinFogDistance
|
||
|
return rcp(max(0.05, meanFreePath));
|
||
|
}
|
||
|
|
||
|
#endif // UNITY_VOLUME_RENDERING_INCLUDED
|