2021-09-09 20:42:29 -04:00

381 lines
17 KiB
HLSL

#ifndef UNITY_ATMOSPHERIC_SCATTERING_INCLUDED
#define UNITY_ATMOSPHERIC_SCATTERING_INCLUDED
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/VolumeRendering.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Filtering.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/GeometricTools.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/AtmosphericScattering/AtmosphericScattering.cs.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/VolumetricLighting/VBuffer.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Sky/PhysicallyBasedSky/PhysicallyBasedSkyCommon.hlsl"
#ifdef DEBUG_DISPLAY
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugDisplay.hlsl"
#endif
TEXTURE3D(_VBufferLighting);
float3 ExpLerp(float3 A, float3 B, float t, float x, float y)
{
// Remap t: (exp(10 k t) - 1) / (exp(10 k) - 1) = exp(x t) y - y.
t = exp(x * t) * y - y;
// Perform linear interpolation using the new value of t.
return lerp(A, B, t);
}
float3 GetFogColor(float3 V, float fragDist)
{
float3 color = _FogColor.rgb;
if (_FogColorMode == FOGCOLORMODE_SKY_COLOR)
{
// Based on Uncharted 4 "Mip Sky Fog" trick: http://advances.realtimerendering.com/other/2016/naughty_dog/NaughtyDog_TechArt_Final.pdf
float mipLevel = (1.0 - _MipFogMaxMip * saturate((fragDist - _MipFogNear) / (_MipFogFar - _MipFogNear))) * (ENVCONSTANTS_CONVOLUTION_MIP_COUNT - 1);
// For the atmospheric scattering, we use the GGX convoluted version of the cubemap. That matches the of the idnex 0
color *= SampleSkyTexture(-V, mipLevel, 0).rgb; // '_FogColor' is the tint
}
return color;
}
// All units in meters!
// Assumes that there is NO sky occlusion along the ray AT ALL.
// We evaluate atmospheric scattering for the sky and other celestial bodies
// during the sky pass. The opaque atmospheric scattering pass applies atmospheric
// scattering to all other opaque geometry.
void EvaluatePbrAtmosphere(float3 worldSpaceCameraPos, float3 V, float distAlongRay, bool renderSunDisk,
out float3 skyColor, out float3 skyOpacity)
{
skyColor = skyOpacity = 0;
const float R = _PlanetaryRadius;
const float2 n = float2(_AirDensityFalloff, _AerosolDensityFalloff);
const float2 H = float2(_AirScaleHeight, _AerosolScaleHeight);
// TODO: Not sure it's possible to precompute cam rel pos since variables
// in the two constant buffers may be set at a different frequency?
const float3 O = worldSpaceCameraPos - _PlanetCenterPosition.xyz;
const float tFrag = abs(distAlongRay); // Clear the "hit ground" flag
float3 N; float r; // These params correspond to the entry point
float tEntry = IntersectAtmosphere(O, V, N, r).x;
float tExit = IntersectAtmosphere(O, V, N, r).y;
float NdotV = dot(N, V);
float cosChi = -NdotV;
float cosHor = ComputeCosineOfHorizonAngle(r);
bool rayIntersectsAtmosphere = (tEntry >= 0);
bool lookAboveHorizon = (cosChi >= cosHor);
// Our precomputed tables only contain information above ground.
// Being on or below ground still counts as outside.
// If it's outside the atmosphere, we only need one texture look-up.
bool hitGround = distAlongRay < 0;
bool rayEndsInsideAtmosphere = (tFrag < tExit) && !hitGround;
if (rayIntersectsAtmosphere)
{
float2 Z = R * n;
float r0 = r, cosChi0 = cosChi;
float r1 = 0, cosChi1 = 0;
float3 N1 = 0;
if (tFrag < tExit)
{
float3 P1 = O + tFrag * -V;
r1 = length(P1);
N1 = P1 * rcp(r1);
cosChi1 = dot(P1, -V) * rcp(r1);
// Potential swap.
cosChi0 = (cosChi1 >= 0) ? cosChi0 : -cosChi0;
}
float2 ch0, ch1 = 0;
{
float2 z0 = r0 * n;
ch0.x = RescaledChapmanFunction(z0.x, Z.x, cosChi0);
ch0.y = RescaledChapmanFunction(z0.y, Z.y, cosChi0);
}
if (tFrag < tExit)
{
float2 z1 = r1 * n;
ch1.x = ChapmanUpperApprox(z1.x, abs(cosChi1)) * exp(Z.x - z1.x);
ch1.y = ChapmanUpperApprox(z1.y, abs(cosChi1)) * exp(Z.y - z1.y);
}
// We may have swapped X and Y.
float2 ch = abs(ch0 - ch1);
float3 optDepth = ch.x * H.x * _AirSeaLevelExtinction.xyz
+ ch.y * H.y * _AerosolSeaLevelExtinction;
skyOpacity = 1 - TransmittanceFromOpticalDepth(optDepth); // from 'tEntry' to 'tFrag'
for (uint i = 0; i < _DirectionalLightCount; i++)
{
DirectionalLightData light = _DirectionalLightDatas[i];
// Use scalar or integer cores (more efficient).
bool interactsWithSky = asint(light.distanceFromCamera) >= 0;
if (!interactsWithSky) continue;
float3 L = -light.forward.xyz;
// The sun disk hack causes some issues when applied to nearby geometry, so don't do that.
if (renderSunDisk && asint(light.angularDiameter) != 0 && light.distanceFromCamera <= tFrag)
{
float c = dot(L, -V);
if (-0.99999 < c && c < 0.99999)
{
float alpha = 0.5 * light.angularDiameter;
float beta = acos(c);
float gamma = min(alpha, beta);
// Make sure that if (beta = Pi), no rotation is performed.
gamma *= (PI - beta) * rcp(PI - gamma);
// Perform a shortest arc rotation.
float3 A = normalize(cross(L, -V));
float3x3 R = RotationFromAxisAngle(A, sin(gamma), cos(gamma));
// Rotate the light direction.
L = mul(R, L);
}
}
// TODO: solve in spherical coords?
float height = r - R;
float NdotL = dot(N, L);
float3 projL = L - N * NdotL;
float3 projV = V - N * NdotV;
float phiL = acos(clamp(dot(projL, projV) * rsqrt(max(dot(projL, projL) * dot(projV, projV), FLT_EPS)), -1, 1));
TexCoord4D tc = ConvertPositionAndOrientationToTexCoords(height, NdotV, NdotL, phiL);
float3 radiance = 0; // from 'tEntry' to 'tExit'
// Single scattering does not contain the phase function.
float LdotV = dot(L, V);
// Air.
radiance += lerp(SAMPLE_TEXTURE3D_LOD(_AirSingleScatteringTexture, s_linear_clamp_sampler, float3(tc.u, tc.v, tc.w0), 0).rgb,
SAMPLE_TEXTURE3D_LOD(_AirSingleScatteringTexture, s_linear_clamp_sampler, float3(tc.u, tc.v, tc.w1), 0).rgb,
tc.a) * AirPhase(LdotV);
// Aerosols.
// TODO: since aerosols are in a separate texture,
// they could use a different max height value for improved precision.
radiance += lerp(SAMPLE_TEXTURE3D_LOD(_AerosolSingleScatteringTexture, s_linear_clamp_sampler, float3(tc.u, tc.v, tc.w0), 0).rgb,
SAMPLE_TEXTURE3D_LOD(_AerosolSingleScatteringTexture, s_linear_clamp_sampler, float3(tc.u, tc.v, tc.w1), 0).rgb,
tc.a) * AerosolPhase(LdotV);
// MS.
radiance += lerp(SAMPLE_TEXTURE3D_LOD(_MultipleScatteringTexture, s_linear_clamp_sampler, float3(tc.u, tc.v, tc.w0), 0).rgb,
SAMPLE_TEXTURE3D_LOD(_MultipleScatteringTexture, s_linear_clamp_sampler, float3(tc.u, tc.v, tc.w1), 0).rgb,
tc.a);
if (rayEndsInsideAtmosphere)
{
float3 radiance1 = 0; // from 'tFrag' to 'tExit'
// TODO: solve in spherical coords?
float height1 = r1 - R;
float NdotV1 = -cosChi1;
float NdotL1 = dot(N1, L);
float3 projL1 = L - N1 * NdotL1;
float3 projV1 = V - N1 * NdotV1;
float phiL1 = acos(clamp(dot(projL1, projV1) * rsqrt(max(dot(projL1, projL1) * dot(projV1, projV1), FLT_EPS)), -1, 1));
tc = ConvertPositionAndOrientationToTexCoords(height1, NdotV1, NdotL1, phiL1);
// Single scattering does not contain the phase function.
// Air.
radiance1 += lerp(SAMPLE_TEXTURE3D_LOD(_AirSingleScatteringTexture, s_linear_clamp_sampler, float3(tc.u, tc.v, tc.w0), 0).rgb,
SAMPLE_TEXTURE3D_LOD(_AirSingleScatteringTexture, s_linear_clamp_sampler, float3(tc.u, tc.v, tc.w1), 0).rgb,
tc.a) * AirPhase(LdotV);
// Aerosols.
// TODO: since aerosols are in a separate texture,
// they could use a different max height value for improved precision.
radiance1 += lerp(SAMPLE_TEXTURE3D_LOD(_AerosolSingleScatteringTexture, s_linear_clamp_sampler, float3(tc.u, tc.v, tc.w0), 0).rgb,
SAMPLE_TEXTURE3D_LOD(_AerosolSingleScatteringTexture, s_linear_clamp_sampler, float3(tc.u, tc.v, tc.w1), 0).rgb,
tc.a) * AerosolPhase(LdotV);
// MS.
radiance1 += lerp(SAMPLE_TEXTURE3D_LOD(_MultipleScatteringTexture, s_linear_clamp_sampler, float3(tc.u, tc.v, tc.w0), 0).rgb,
SAMPLE_TEXTURE3D_LOD(_MultipleScatteringTexture, s_linear_clamp_sampler, float3(tc.u, tc.v, tc.w1), 0).rgb,
tc.a);
// L(tEntry, tFrag) = L(tEntry, tExit) - T(tEntry, tFrag) * L(tFrag, tExit)
radiance = max(0, radiance - (1 - skyOpacity) * radiance1);
}
radiance *= light.color.rgb; // Globally scale the intensity
skyColor += radiance;
}
skyColor = Desaturate(skyColor, _ColorSaturation);
skyOpacity = Desaturate(skyOpacity, _AlphaSaturation) * _AlphaMultiplier;
float horAngle = acos(cosHor);
float chiAngle = acos(cosChi);
// [start, end] -> [0, 1] : (x - start) / (end - start) = x * rcpLength - (start * rcpLength)
// TEMPLATE_3_REAL(Remap01, x, rcpLength, startTimesRcpLength, return saturate(x * rcpLength - startTimesRcpLength))
float start = horAngle;
float end = 0;
float rcpLen = rcp(end - start);
float nrmAngle = Remap01(chiAngle, rcpLen, start * rcpLen);
// float angle = saturate((0.5 * PI) - acos(cosChi) * rcp(0.5 * PI));
skyColor *= ExpLerp(_HorizonTint.rgb, _ZenithTint.rgb, nrmAngle, _HorizonZenithShiftPower, _HorizonZenithShiftScale);
}
}
float3 GetViewForwardDir1(float4x4 viewMatrix)
{
return -viewMatrix[2].xyz;
}
void EvaluateAtmosphericScattering(PositionInputs posInput, float3 V, out float3 color, out float3 opacity)
{
color = opacity = 0;
#ifdef DEBUG_DISPLAY
// Don't sample atmospheric scattering when lighting debug more are enabled so fog is not visible
if (_DebugLightingMode >= DEBUGLIGHTINGMODE_DIFFUSE_LIGHTING && _DebugLightingMode <= DEBUGLIGHTINGMODE_EMISSIVE_LIGHTING)
return;
if (_DebugShadowMapMode == SHADOWMAPDEBUGMODE_SINGLE_SHADOW || _DebugLightingMode == DEBUGLIGHTINGMODE_LUX_METER || _DebugLightingMode == DEBUGLIGHTINGMODE_LUMINANCE_METER)
return;
#endif
// TODO: do not recompute this, but rather pass it directly.
// Note1: remember the hacked value of 'posInput.positionWS'.
// Note2: we do not adjust it anymore to account for the distance to the planet. This can lead to wrong results (since the planet does not write depth).
float fogFragDist = distance(posInput.positionWS, GetCurrentViewPosition());
if (_FogEnabled)
{
float4 volFog = float4(0.0, 0.0, 0.0, 0.0);
float expFogStart = 0.0f;
if (_EnableVolumetricFog != 0)
{
bool doBiquadraticReconstruction = _VolumetricFilteringEnabled == 0; // Only if filtering is disabled.
float4 value = SampleVBuffer(TEXTURE3D_ARGS(_VBufferLighting, s_linear_clamp_sampler),
posInput.positionNDC,
fogFragDist,
_VBufferViewportSize,
_VBufferLightingViewportScale.xyz,
_VBufferLightingViewportLimit.xyz,
_VBufferDistanceEncodingParams,
_VBufferDistanceDecodingParams,
true, doBiquadraticReconstruction, false);
// TODO: add some slowly animated noise (dither?) to the reconstructed value.
// TODO: re-enable tone mapping after implementing pre-exposure.
volFog = DelinearizeRGBA(float4(/*FastTonemapInvert*/(value.rgb), value.a));
expFogStart = _VBufferLastSliceDist;
}
// TODO: if 'posInput.linearDepth' is computed using 'posInput.positionWS',
// and the latter resides on the far plane, the computation will be numerically unstable.
float distDelta = fogFragDist - expFogStart;
if ((distDelta > 0))
{
// Apply the distant (fallback) fog.
float3 positionWS = GetCurrentViewPosition() - V * expFogStart;
float startHeight = positionWS.y;
float cosZenith = -V.y;
// For both homogeneous and exponential media,
// Integrate[Transmittance[x] * Scattering[x], {x, 0, t}] = Albedo * Opacity[t].
// Note that pulling the incoming radiance (which is affected by the fog) out of the
// integral is wrong, as it means that shadow rays are not volumetrically shadowed.
// This will result in fog looking overly bright.
float3 volAlbedo = _HeightFogBaseScattering.xyz / _HeightFogBaseExtinction;
float odFallback = OpticalDepthHeightFog(_HeightFogBaseExtinction, _HeightFogBaseHeight,
_HeightFogExponents, cosZenith, startHeight, distDelta);
float trFallback = TransmittanceFromOpticalDepth(odFallback);
float trCamera = 1 - volFog.a;
volFog.rgb += trCamera * GetFogColor(V, fogFragDist) * GetCurrentExposureMultiplier() * volAlbedo * (1 - trFallback);
volFog.a = 1 - (trCamera * trFallback);
}
color = volFog.rgb; // Already pre-exposed
opacity = volFog.a;
}
// Sky pass already applies atmospheric scattering to the far plane.
// This pass only handles geometry.
if (_PBRFogEnabled && (posInput.deviceDepth != UNITY_RAW_FAR_CLIP_VALUE))
{
float3 skyColor = 0, skyOpacity = 0;
// Convert it to distance along the ray. Doesn't work with tilt shift, etc.
float tFrag = posInput.linearDepth * rcp(dot(-V, GetViewForwardDir1(UNITY_MATRIX_V)));
EvaluatePbrAtmosphere(_WorldSpaceCameraPos.xyz, V, tFrag, false, skyColor, skyOpacity);
skyColor *= _IntensityMultiplier * GetCurrentExposureMultiplier();
// Rendering of fog and atmospheric scattering cannot really be decoupled.
#if 0
// The best workaround is to deep composite them.
float3 fogOD = OpticalDepthFromOpacity(fogOpacity);
float3 fogRatio;
fogRatio.r = (fogOpacity.r >= FLT_EPS) ? (fogOD.r * rcp(fogOpacity.r)) : 1;
fogRatio.g = (fogOpacity.g >= FLT_EPS) ? (fogOD.g * rcp(fogOpacity.g)) : 1;
fogRatio.b = (fogOpacity.b >= FLT_EPS) ? (fogOD.b * rcp(fogOpacity.b)) : 1;
float3 skyRatio;
skyRatio.r = (skyOpacity.r >= FLT_EPS) ? (skyOD.r * rcp(skyOpacity.r)) : 1;
skyRatio.g = (skyOpacity.g >= FLT_EPS) ? (skyOD.g * rcp(skyOpacity.g)) : 1;
skyRatio.b = (skyOpacity.b >= FLT_EPS) ? (skyOD.b * rcp(skyOpacity.b)) : 1;
float3 logFogColor = fogRatio * fogColor;
float3 logSkyColor = skyRatio * skyColor;
float3 logCompositeColor = logFogColor + logSkyColor;
float3 compositeOD = fogOD + skyOD;
opacity = OpacityFromOpticalDepth(compositeOD);
float3 rcpCompositeRatio;
rcpCompositeRatio.r = (opacity.r >= FLT_EPS) ? (opacity.r * rcp(compositeOD.r)) : 1;
rcpCompositeRatio.g = (opacity.g >= FLT_EPS) ? (opacity.g * rcp(compositeOD.g)) : 1;
rcpCompositeRatio.b = (opacity.b >= FLT_EPS) ? (opacity.b * rcp(compositeOD.b)) : 1;
color = rcpCompositeRatio * logCompositeColor;
#else
// Deep compositing assumes that the fog spans the same range as the atmosphere.
// Our fog is short range, so deep compositing gives surprising results.
// Using the "shallow" over operator is more appropriate in our context.
// We could do something more clever with deep compositing, but this would
// probably be a waste in terms of perf.
CompositeOver(color, opacity, skyColor, skyOpacity, color, opacity);
#endif
}
}
#endif