浏览代码

Merge pull request #901 from EvgeniiG/master

Add support of the ambient probe to volumetric lighting
/main
GitHub 6 年前
当前提交
6bcaecf3
共有 9 个文件被更改,包括 253 次插入29 次删除
  1. 12
      ScriptableRenderPipeline/Core/CoreRP/ShaderLibrary/CommonLighting.hlsl
  2. 6
      ScriptableRenderPipeline/HDRenderPipeline/HDRP/Camera/HDCamera.cs
  3. 2
      ScriptableRenderPipeline/HDRenderPipeline/HDRP/HDStringConstants.cs
  4. 44
      ScriptableRenderPipeline/HDRenderPipeline/HDRP/Lighting/Volumetrics/Resources/VolumetricLighting.compute
  5. 12
      ScriptableRenderPipeline/HDRenderPipeline/HDRP/Lighting/Volumetrics/VBuffer.hlsl
  6. 48
      ScriptableRenderPipeline/HDRenderPipeline/HDRP/Lighting/Volumetrics/VolumetricLighting.cs
  7. 1
      ScriptableRenderPipeline/HDRenderPipeline/HDRP/ShaderVariables.hlsl
  8. 146
      ScriptableRenderPipeline/HDRenderPipeline/HDRP/Lighting/SphericalHarmonics.cs
  9. 11
      ScriptableRenderPipeline/HDRenderPipeline/HDRP/Lighting/SphericalHarmonics.cs.meta

12
ScriptableRenderPipeline/Core/CoreRP/ShaderLibrary/CommonLighting.hlsl


// texelArea = 4.0 / (resolution * resolution).
// Ref: http://bpeers.com/blog/?itemid=1017
// This version is less accurate, but much faster than this one:
// http://www.rorydriscoll.com/2012/01/15/cubemap-texel-solid-angle/
real ComputeCubemapTexelSolidAngle(real3 L, real texelArea)
{
// Stretch 'L' by (1/d) so that it points at a side of a [-1, 1]^2 cube.

// dw = dA * cosTheta / (dist * dist), cosTheta = 1.0 / dist,
// where 'dA' is the area of the cube map texel.
return texelArea * invDist * invDist * invDist;
}
// Only makes sense for Monte-Carlo integration.
// Normalize by dividing by the total weight (or the number of samples) in the end.
// Integrate[6*(u^2+v^2+1)^(-3/2), {u,-1,1},{v,-1,1}] = 4 * Pi
// Ref: "Stupid Spherical Harmonics Tricks", p. 9.
real ComputeCubemapTexelSolidAngle(real2 uv)
{
float u = uv.x, v = uv.y;
return pow(1 + u * u + v * v, -1.5);
}
//-----------------------------------------------------------------------------

6
ScriptableRenderPipeline/HDRenderPipeline/HDRP/Camera/HDCamera.cs


}
// Warning: different views can use the same camera!
public int GetViewID()
public long GetViewID()
int viewID = camera.GetInstanceID();
long viewID = camera.GetInstanceID();
// Make it positive.
viewID += (-(long)int.MinValue) + 1;
Debug.Assert(viewID > 0);
return viewID;
}

2
ScriptableRenderPipeline/HDRenderPipeline/HDRP/HDStringConstants.cs


public static readonly int _Source4 = Shader.PropertyToID("_Source4");
public static readonly int _Result1 = Shader.PropertyToID("_Result1");
public static readonly int _AmbientProbeCoeffs = Shader.PropertyToID("_AmbientProbeCoeffs");
public static readonly int _CornetteShanksConstant = Shader.PropertyToID("_CornetteShanksConstant");
public static readonly int _VBufferResolution = Shader.PropertyToID("_VBufferResolution");
public static readonly int _VBufferScaleAndSliceCount = Shader.PropertyToID("_VBufferScaleAndSliceCount");
public static readonly int _VBufferDepthEncodingParams = Shader.PropertyToID("_VBufferDepthEncodingParams");

44
ScriptableRenderPipeline/HDRenderPipeline/HDRP/Lighting/Volumetrics/Resources/VolumetricLighting.compute


#if (SHADEROPTIONS_VOLUMETRIC_LIGHTING_PRESET == 1)
// E.g. for 1080p: (1920/8)x(1080/8)x(128) = 4,147,200 voxels
#define VBUFFER_TILE_SIZE 8
#define VBUFFER_SLICE_COUNT 128
#define VBUFFER_SLICE_COUNT 64
#define VBUFFER_SLICE_COUNT 256
#define VBUFFER_SLICE_COUNT 128
#define SUPPORT_ASYMMETRY 1 // Support asymmetric phase functions
#define SUPPORT_ASYMMETRY 1 // Support asymmetric phase functions
#define SUPPORT_PUNCTUAL_LIGHTS 1 // Punctual lights contribute to fog lighting
#define GROUP_SIZE_1D 16
#define GROUP_SIZE_2D (GROUP_SIZE_1D * GROUP_SIZE_1D)

CBUFFER_START(UnityVolumetricLighting)
float4 _VBufferSampleOffset; // {x, y, z}, w = rendered frame count
float4x4 _VBufferCoordToViewDirWS; // Actually just 3x3, but Unity can only set 4x4
float _CornetteShanksConstant; // CornetteShanksPhasePartConstant(_GlobalFog_Asymmetry)
CBUFFER_END
//--------------------------------------------------------------------------------------------------

lighting.radianceComplete += phase * intensity * color;
}
}
#if (SUPPORT_PUNCTUAL_LIGHTS == 0)
return lighting;
#endif
#ifdef LIGHTLOOP_TILE_PASS
// Loop over 1 or 2 light clusters.

float t0 = ray.strataDirInvViewZ * z0; // Convert view space Z to distance along the stratified ray
float de = rcp(VBUFFER_SLICE_COUNT); // Log-encoded distance between slices
// The contribution of the ambient probe does not depend on the position,
// only on the direction and the length of the interval.
// SampleSH9() evaluates the 3-band SH in a given direction.
float3 probeInScatteredRadiance = SampleSH9(_AmbientProbeCoeffs, ray.centerDirWS);
float3 totalRadiance = 0;
float opticalDepth = 0;

#else
);
#endif
#if (SUPPORT_ASYMMETRY == 0)
lighting.radianceComplete = lighting.radianceNoPhase;
#endif
#if ENABLE_REPROJECTION
// Reproject the history at 'centerWS'.
float2 reprojPosNDC = ComputeNormalizedDeviceCoordinates(centerWS, _PrevViewProjMatrix);

// to prevent ghosting.
_VBufferLightingFeedback[uint3(posInput.positionSS, slice)] = float4(blendedRadiance, dt);
#if SUPPORT_ASYMMETRY
// TODO: how to ensure we do not divide by 0?
float3 phaseCurrFrame = lighting.radianceComplete / lighting.radianceNoPhase;
// Use max() to prevent division by 0.
float3 phaseCurrFrame = lighting.radianceComplete * rcp(max(lighting.radianceNoPhase, FLT_MIN));
#else
#endif // SUPPORT_ASYMMETRY
#else // ENABLE_REPROJECTION
#if SUPPORT_ASYMMETRY
#else // SUPPORT_ASYMMETRY
float3 blendedRadiance = lighting.radianceNoPhase;
#endif // SUPPORT_ASYMMETRY
#endif // ENABLE_REPROJECTION
// Compute the transmittance from the camera to 't0'.

float phase = CornetteShanksPhasePartConstant(asymmetry);
float phase = _CornetteShanksConstant;
// Integrate the contribution of the probe over the interval.
totalRadiance += (transmittance * phase) * scattering * blendedRadiance;
float3 probeRadiance = probeInScatteredRadiance * TransmittanceIntegralHomogeneousMedium(extinction, dt);
totalRadiance += transmittance * scattering * (phase * blendedRadiance + probeRadiance);
// Compute the optical depth up to the center of the interval.
opticalDepth += 0.5 * extinction * dt;

12
ScriptableRenderPipeline/HDRenderPipeline/HDRP/Lighting/Volumetrics/VBuffer.hlsl


[branch] if (clampToEdge || isInBounds)
{
#if 0
// We could ignore non-linearity at the cost of accuracy.
// TODO: visually test this option (especially in motion).
#if 1
// Ignore non-linearity (for performance reasons) at the cost of accuracy.
// The results are exact for a stationary camera, but can potentially cause some judder in motion.
float w = d;
#else
// Adjust the texture coordinate for HW trilinear sampling.

float z = linearDepth;
float d = EncodeLogarithmicDepthGeneralized(z, VBufferDepthEncodingParams);
#if 0
// Ignore non-linearity (for performance reasons) at the cost of accuracy.
// The results are exact for a stationary camera, but can potentially cause some judder in motion.
float w = d;
#else
#endif
float2 weights[2], offsets[2];
BiquadraticFilter(1 - fc, weights, offsets); // Inverse-translate the filter centered around 0.5

48
ScriptableRenderPipeline/HDRenderPipeline/HDRP/Lighting/Volumetrics/VolumetricLighting.cs


}
class VBuffer
{
public int viewID = -1; // -1 is invalid; positive for Game Views, 0 otherwise
public long viewID = -1; // -1 is invalid; positive for Game Views, 0 otherwise
public RenderTexture[] lightingRTEX = null;
public RenderTargetIdentifier[] lightingRTID = null;

return lightingRTID[1 + ((Time.renderedFrameCount + 1) & 1)];
}
public void Create(int viewID, int w, int h, int d)
public void Create(long viewID, int w, int h, int d)
{
Debug.Assert(viewID >= 0);
Debug.Assert(w > 0 && h > 0 && d > 0);

{
for (int i = 0, n = this.lightingRTEX.Length; i < n; i++)
{
this.lightingRTEX[i].Release();
if (this.lightingRTEX[i] != null)
{
this.lightingRTEX[i].Release();
}
}
}

if (preset == VolumetricLightingPreset.Off) return;
m_VolumetricLightingCS = asset.renderPipelineResources.volumetricLightingCS;
m_VBuffers = new List<VBuffer>(1);
m_VBuffers = new List<VBuffer>(0);
}
public void Cleanup()

{
if (preset == VolumetricLightingPreset.Off) return;
int viewID = camera.GetViewID();
long viewID = camera.GetViewID();
Debug.Assert(viewID >= 0);

vBuffer.Create(viewID, w, h, d);
}
VBuffer FindVBuffer(int viewID)
VBuffer FindVBuffer(long viewID)
{
Debug.Assert(viewID >= 0);

for (int i = 0; i < n; i++)
{
if (viewID == m_VBuffers[i].viewID)
// Check whether domain reload killed it...
if (viewID == m_VBuffers[i].viewID && m_VBuffers[i].lightingRTEX != null && m_VBuffers[i].lightingRTEX[0] != null)
{
vBuffer = m_VBuffers[i];
}

switch (preset)
{
case VolumetricLightingPreset.Normal:
return 128;
return 64;
return 256;
return 128;
case VolumetricLightingPreset.Off:
return 0;
default:

return depthParams;
}
void SetPreconvolvedAmbientLightProbe(CommandBuffer cmd, float asymmetry)
{
SphericalHarmonicsL2 probeSH = SphericalHarmonicMath.UndoCosineRescaling(RenderSettings.ambientProbe);
ZonalHarmonicsL2 phaseZH = ZonalHarmonicsL2.GetCornetteShanksPhaseFunction(asymmetry);
SphericalHarmonicsL2 finalSH = SphericalHarmonicMath.PremultiplyCoefficients(SphericalHarmonicMath.Convolve(probeSH, phaseZH));
cmd.SetGlobalVectorArray(HDShaderIDs._AmbientProbeCoeffs, SphericalHarmonicMath.PackCoefficients(finalSH));
}
float CornetteShanksPhasePartConstant(float asymmetry)
{
float g = asymmetry;
return (1.0f / (4.0f * Mathf.PI)) * 1.5f * (1.0f - g * g) / (2.0f + g * g);
}
public void PushGlobalParams(HDCamera camera, CommandBuffer cmd)
{
if (preset == VolumetricLightingPreset.Off) return;

VolumeProperties globalFogProperties = (globalFogComponent != null) ? globalFogComponent.volumeParameters.GetProperties()
: VolumeProperties.GetNeutralVolumeProperties();
float asymmetry = globalFogComponent != null ? globalFogComponent.volumeParameters.asymmetry : 0;
cmd.SetGlobalFloat( HDShaderIDs._GlobalFog_Asymmetry, globalFogComponent != null ? globalFogComponent.volumeParameters.asymmetry : 0);
cmd.SetGlobalFloat( HDShaderIDs._GlobalFog_Asymmetry, asymmetry);
int w = 0, h = 0, d = 0;
Vector2 scale = ComputeVBufferResolutionAndScale(preset, (int)camera.screenSize.x, (int)camera.screenSize.y, ref w, ref h, ref d);

SetPreconvolvedAmbientLightProbe(cmd, asymmetry);
cmd.SetGlobalVector( HDShaderIDs._VBufferResolution, new Vector4(w, h, 1.0f / w, 1.0f / h));
cmd.SetGlobalVector( HDShaderIDs._VBufferScaleAndSliceCount, new Vector4(scale.x, scale.y, d, 1.0f / d));
cmd.SetGlobalVector( HDShaderIDs._VBufferDepthEncodingParams, ComputeLogarithmicDepthEncodingParams(m_VBufferNearPlane, m_VBufferFarPlane, k_LogScale));

VBuffer vBuffer = FindVBuffer(camera.GetViewID());
Debug.Assert(vBuffer != null);
if (HomogeneousFog.GetGlobalFogComponent() == null)
HomogeneousFog globalFogComponent = HomogeneousFog.GetGlobalFogComponent();
float asymmetry = globalFogComponent != null ? globalFogComponent.volumeParameters.asymmetry : 0;
if (globalFogComponent == null)
{
// Clear the render target instead of running the shader.
// CoreUtils.SetRenderTarget(cmd, GetVBufferLightingIntegral(viewOffset), ClearFlag.Color, CoreUtils.clearColorAllBlack);

Vector4 offset = new Vector4(xySeq[sampleIndex].x, xySeq[sampleIndex].y, zSeq[sampleIndex], rfc);
// TODO: set 'm_VolumetricLightingPreset'.
cmd.SetComputeFloatParam( m_VolumetricLightingCS, HDShaderIDs._CornetteShanksConstant, CornetteShanksPhasePartConstant(asymmetry));
cmd.SetComputeVectorParam( m_VolumetricLightingCS, HDShaderIDs._VBufferSampleOffset, offset);
cmd.SetComputeMatrixParam( m_VolumetricLightingCS, HDShaderIDs._VBufferCoordToViewDirWS, transform);
cmd.SetComputeTextureParam(m_VolumetricLightingCS, kernel, HDShaderIDs._VBufferLightingIntegral, vBuffer.GetLightingIntegralBuffer()); // Write

1
ScriptableRenderPipeline/HDRenderPipeline/HDRP/ShaderVariables.hlsl


float2 _TaaFrameRotation; // {x = sin(_TaaFrameIndex * PI/2), y = cos(_TaaFrameIndex * PI/2), z = unused}
uint _TaaFrameIndex; // [0, 7]
// Volumetric lighting.
float4 _AmbientProbeCoeffs[7]; // 3 bands of SH, packed, rescaled and convolved with the phase function
float _GlobalFog_Asymmetry;
float3 _GlobalFog_Scattering;
float _GlobalFog_Extinction;

146
ScriptableRenderPipeline/HDRenderPipeline/HDRP/Lighting/SphericalHarmonics.cs


using System;
using UnityEngine;
using UnityEngine.Rendering;
public struct ZonalHarmonicsL2
{
public float[] coeffs; // Must have the size of 3
public static ZonalHarmonicsL2 GetHenyeyGreensteinPhaseFunction(float asymmetry)
{
float g = asymmetry;
var zh = new ZonalHarmonicsL2();
zh.coeffs = new float[3];
zh.coeffs[0] = 0.5f * Mathf.Sqrt(1.0f / Mathf.PI);
zh.coeffs[1] = 0.5f * Mathf.Sqrt(3.0f / Mathf.PI) * g;
zh.coeffs[2] = 0.5f * Mathf.Sqrt(5.0f / Mathf.PI) * g * g;
return zh;
}
public static ZonalHarmonicsL2 GetCornetteShanksPhaseFunction(float asymmetry)
{
float g = asymmetry;
var zh = new ZonalHarmonicsL2();
zh.coeffs = new float[3];
zh.coeffs[0] = 0.282095f;
zh.coeffs[1] = 0.293162f * g * (4.0f + (g * g)) / (2.0f + (g * g));
zh.coeffs[2] = (0.126157f + 1.44179f * (g * g) + 0.324403f * (g * g) * (g * g)) / (2.0f + (g * g));
return zh;
}
}
public class SphericalHarmonicMath
{
// Ref: "Stupid Spherical Harmonics Tricks", p. 6.
public static SphericalHarmonicsL2 Convolve(SphericalHarmonicsL2 sh, ZonalHarmonicsL2 zh)
{
for (int l = 0; l <= 2; l++)
{
float n = Mathf.Sqrt((4.0f * Mathf.PI) / (2 * l + 1));
float k = zh.coeffs[l];
float p = n * k;
for (int m = -l; m <= l; m++)
{
int i = l * (l + 1) + m;
for (int c = 0; c < 3; c++)
{
sh[c, i] *= p;
}
}
}
return sh;
}
// Undoes coefficient rescaling due to the convolution with the clamped cosine kernel
// to obtain the canonical values of SH.
public static SphericalHarmonicsL2 UndoCosineRescaling(SphericalHarmonicsL2 sh)
{
const float c0 = 0.28209479177387814347f; // 1/2 * sqrt(1/Pi)
const float c1 = 0.32573500793527994772f; // 1/3 * sqrt(3/Pi)
const float c2 = 0.27313710764801976764f; // 1/8 * sqrt(15/Pi)
const float c3 = 0.07884789131313000151f; // 1/16 * sqrt(5/Pi)
const float c4 = 0.13656855382400988382f; // 1/16 * sqrt(15/Pi)
// Compute the inverse of SphericalHarmonicsL2::kNormalizationConstants.
// See SetSHEMapConstants() in "Stupid Spherical Harmonics Tricks".
float[] invNormConsts = { 1/c0, -1/c1, 1/c1, -1/c1, 1/c2, -1/c2, 1/c3, -1/c2, 1/c4 };
for (int c = 0; c < 3; c++)
{
for (int i = 0; i < 9; i++)
{
sh[c, i] *= invNormConsts[i];
}
}
return sh;
}
// Premultiplies the SH with the polynomial coefficients of SH basis functions,
// which avoids using any constants during SH evaluation.
// The resulting evaluation takes the form:
// (c_0 - c_6) + c_1 y + c_2 z + c_3 x + c_4 x y + c_5 y z + c_6 (3 z^2) + c_7 x z + c_8 (x^2 - y^2)
public static SphericalHarmonicsL2 PremultiplyCoefficients(SphericalHarmonicsL2 sh)
{
const float k0 = 0.28209479177387814347f; // {0, 0} : 1/2 * sqrt(1/Pi)
const float k1 = 0.48860251190291992159f; // {1, 0} : 1/2 * sqrt(3/Pi)
const float k2 = 1.09254843059207907054f; // {2,-2} : 1/2 * sqrt(15/Pi)
const float k3 = 0.31539156525252000603f; // {2, 0} : 1/4 * sqrt(5/Pi)
const float k4 = 0.54627421529603953527f; // {2, 2} : 1/4 * sqrt(15/Pi)
float[] ks = { k0, -k1, k1, -k1, k2, -k2, k3, -k2, k4 };
for (int c = 0; c < 3; c++)
{
for (int i = 0; i < 9; i++)
{
sh[c, i] *= ks[i];
}
}
return sh;
}
// Packs coefficients so that we can use Peter-Pike Sloan's shader code.
// Does not perform premultiplication with coefficients of SH basis functions.
// See SetSHEMapConstants() in "Stupid Spherical Harmonics Tricks".
public static Vector4[] PackCoefficients(SphericalHarmonicsL2 sh)
{
Vector4[] coeffs = new Vector4[7];
// Constant + linear
for (int c = 0; c < 3; c++)
{
coeffs[c].x = sh[c, 3];
coeffs[c].y = sh[c, 1];
coeffs[c].z = sh[c, 2];
coeffs[c].w = sh[c, 0] - sh[c, 6];
}
// Quadratic (4/5)
for (int c = 0; c < 3; c++)
{
coeffs[3 + c].x = sh[c, 4];
coeffs[3 + c].y = sh[c, 5];
coeffs[3 + c].z = sh[c, 6] * 3.0f;
coeffs[3 + c].w = sh[c, 7];
}
// Quadratic (5)
coeffs[6].x = sh[0, 8];
coeffs[6].y = sh[1, 8];
coeffs[6].z = sh[2, 8];
coeffs[6].w = 1.0f;
return coeffs;
}
}

11
ScriptableRenderPipeline/HDRenderPipeline/HDRP/Lighting/SphericalHarmonics.cs.meta


fileFormatVersion: 2
guid: 0ae2a6cdcae867941b675151d66a41d9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
正在加载...
取消
保存