您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
499 行
19 KiB
499 行
19 KiB
using System;
|
|
using UnityEngine.Rendering;
|
|
|
|
namespace UnityEngine.Experimental.Rendering.HDPipeline
|
|
{
|
|
|
|
[GenerateHLSL]
|
|
public struct VolumeProperties
|
|
{
|
|
public Vector3 scattering; // [0, 1], prefer sRGB
|
|
public float extinction; // [0, 1], prefer sRGB
|
|
|
|
public static VolumeProperties GetNeutralVolumeProperties()
|
|
{
|
|
VolumeProperties properties = new VolumeProperties();
|
|
|
|
properties.scattering = Vector3.zero;
|
|
properties.extinction = 0;
|
|
|
|
return properties;
|
|
}
|
|
} // struct VolumeProperties
|
|
|
|
[Serializable]
|
|
public class VolumeParameters
|
|
{
|
|
public Bounds bounds; // Position and dimensions in meters
|
|
public Color albedo; // Single scattering albedo [0, 1]
|
|
public float meanFreePath; // In meters [1, inf]. Should be chromatic - this is an optimization!
|
|
|
|
public VolumeParameters()
|
|
{
|
|
bounds = new Bounds(Vector3.zero, Vector3.positiveInfinity);
|
|
albedo = new Color(0.5f, 0.5f, 0.5f);
|
|
meanFreePath = 10.0f;
|
|
}
|
|
|
|
public bool IsVolumeUnbounded()
|
|
{
|
|
return bounds.size.x == float.PositiveInfinity &&
|
|
bounds.size.y == float.PositiveInfinity &&
|
|
bounds.size.z == float.PositiveInfinity;
|
|
}
|
|
|
|
public Vector3 GetAbsorptionCoefficient()
|
|
{
|
|
float extinction = GetExtinctionCoefficient();
|
|
Vector3 scattering = GetScatteringCoefficient();
|
|
|
|
return Vector3.Max(new Vector3(extinction, extinction, extinction) - scattering, Vector3.zero);
|
|
}
|
|
|
|
public Vector3 GetScatteringCoefficient()
|
|
{
|
|
float extinction = GetExtinctionCoefficient();
|
|
|
|
return new Vector3(albedo.r * extinction, albedo.g * extinction, albedo.b * extinction);
|
|
}
|
|
|
|
public float GetExtinctionCoefficient()
|
|
{
|
|
return 1.0f / meanFreePath;
|
|
}
|
|
|
|
public void Constrain()
|
|
{
|
|
bounds.size = Vector3.Max(bounds.size, Vector3.zero);
|
|
|
|
albedo.r = Mathf.Clamp01(albedo.r);
|
|
albedo.g = Mathf.Clamp01(albedo.g);
|
|
albedo.b = Mathf.Clamp01(albedo.b);
|
|
|
|
meanFreePath = Mathf.Max(meanFreePath, 1.0f);
|
|
}
|
|
|
|
public VolumeProperties GetProperties()
|
|
{
|
|
VolumeProperties properties = new VolumeProperties();
|
|
|
|
properties.scattering = GetScatteringCoefficient();
|
|
properties.extinction = GetExtinctionCoefficient();
|
|
|
|
return properties;
|
|
}
|
|
} // class VolumeParameters
|
|
|
|
public partial class HDRenderPipeline : RenderPipeline
|
|
{
|
|
public enum VolumetricLightingPreset
|
|
{
|
|
Off,
|
|
Normal,
|
|
Ultra,
|
|
Count
|
|
};
|
|
|
|
VolumetricLightingPreset m_VolumetricLightingPreset
|
|
{ get { return (VolumetricLightingPreset)Math.Min(ShaderConfig.s_VolumetricLightingPreset, (int)VolumetricLightingPreset.Count); } }
|
|
|
|
ComputeShader m_VolumetricLightingCS { get { return m_Asset.renderPipelineResources.volumetricLightingCS; } }
|
|
|
|
float m_VBufferNearPlane = 0.5f; // Distance in meters; dynamic modifications not handled by reprojection
|
|
float m_VBufferFarPlane = 64.0f; // Distance in meters; dynamic modifications not handled by reprojection
|
|
const int k_VBufferCount = 3; // 0 and 1 - history (prev) and feedback (next), 2 - integral (curr)
|
|
|
|
RenderTexture[] m_VBufferLighting = null;
|
|
RenderTargetIdentifier[] m_VBufferLightingRT = null;
|
|
|
|
int m_ViewCount = 0;
|
|
int[] m_ViewIdArray = new int[8]; // TODO: account for the CameraType
|
|
|
|
int ViewOffsetFromViewId(int viewId)
|
|
{
|
|
int viewOffset = -1;
|
|
|
|
Debug.Assert(m_ViewCount == 0 || m_ViewIdArray != null);
|
|
|
|
for (int i = 0; i < m_ViewCount; i++)
|
|
{
|
|
if (m_ViewIdArray[i] == viewId)
|
|
{
|
|
viewOffset = i;
|
|
}
|
|
}
|
|
|
|
return viewOffset;
|
|
}
|
|
|
|
public static int ComputeVBufferTileSize(VolumetricLightingPreset preset)
|
|
{
|
|
switch (preset)
|
|
{
|
|
case VolumetricLightingPreset.Normal:
|
|
return 8;
|
|
case VolumetricLightingPreset.Ultra:
|
|
return 4;
|
|
case VolumetricLightingPreset.Off:
|
|
return 0;
|
|
default:
|
|
Debug.Assert(false, "Encountered an unexpected VolumetricLightingPreset.");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public static int ComputeVBufferSliceCount(VolumetricLightingPreset preset)
|
|
{
|
|
switch (preset)
|
|
{
|
|
case VolumetricLightingPreset.Normal:
|
|
return 128;
|
|
case VolumetricLightingPreset.Ultra:
|
|
return 256;
|
|
case VolumetricLightingPreset.Off:
|
|
return 0;
|
|
default:
|
|
Debug.Assert(false, "Encountered an unexpected VolumetricLightingPreset.");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Since a single voxel corresponds to a tile (e.g. 8x8) of pixels,
|
|
// the VBuffer can potentially extend past the boundaries of the viewport.
|
|
// The function returns the fraction of the {width, height} of the VBuffer visible on screen.
|
|
Vector2 ComputeVBufferResolutionAndScale(float screenWidth, float screenHeight,
|
|
ref int w, ref int h, ref int d)
|
|
{
|
|
int t = ComputeVBufferTileSize(m_VolumetricLightingPreset);
|
|
|
|
// Ceil(ScreenSize / TileSize).
|
|
w = ((int)screenWidth + t - 1) / t;
|
|
h = ((int)screenHeight + t - 1) / t;
|
|
d = ComputeVBufferSliceCount(m_VolumetricLightingPreset);
|
|
|
|
return new Vector2(screenWidth / (w * t), screenHeight / (h * t));
|
|
}
|
|
|
|
void ResizeVBuffer(int viewId, int screenWidth, int screenHeight)
|
|
{
|
|
int viewOffset = ViewOffsetFromViewId(viewId);
|
|
|
|
if (viewOffset >= 0)
|
|
{
|
|
// Found, check resolution.
|
|
int w = 0, h = 0, d = 0;
|
|
ComputeVBufferResolutionAndScale(screenWidth, screenHeight, ref w, ref h, ref d);
|
|
|
|
Debug.Assert(m_VBufferLighting != null);
|
|
Debug.Assert(m_VBufferLighting.Length >= (viewOffset + 1) * k_VBufferCount);
|
|
Debug.Assert(m_VBufferLighting[viewOffset * k_VBufferCount] != null);
|
|
|
|
if (w == m_VBufferLighting[viewOffset * k_VBufferCount].width &&
|
|
h == m_VBufferLighting[viewOffset * k_VBufferCount].height &&
|
|
d == m_VBufferLighting[viewOffset * k_VBufferCount].volumeDepth)
|
|
{
|
|
// Everything matches, nothing to do here.
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Otherwise, we have to recreate the VBuffer.
|
|
CreateVBuffer(viewId, screenWidth, screenHeight);
|
|
}
|
|
|
|
void CreateVBuffer(int viewId, int screenWidth, int screenHeight)
|
|
{
|
|
// Clean up first.
|
|
DestroyVBuffer(viewId);
|
|
|
|
int viewOffset = ViewOffsetFromViewId(viewId);
|
|
|
|
if (viewOffset < 0)
|
|
{
|
|
// Not found. Push back.
|
|
viewOffset = m_ViewCount++;
|
|
Debug.Assert(viewOffset < 8);
|
|
m_ViewIdArray[viewOffset] = viewId;
|
|
|
|
if (m_VBufferLighting == null)
|
|
{
|
|
// Lazy initialize.
|
|
m_VBufferLighting = new RenderTexture[k_VBufferCount];
|
|
m_VBufferLightingRT = new RenderTargetIdentifier[k_VBufferCount];
|
|
}
|
|
else if (m_VBufferLighting.Length < m_ViewCount * k_VBufferCount)
|
|
{
|
|
// Grow by reallocation and copy.
|
|
RenderTexture[] newArray = new RenderTexture[m_ViewCount * k_VBufferCount];
|
|
RenderTargetIdentifier[] newArrayRT = new RenderTargetIdentifier[m_ViewCount * k_VBufferCount];
|
|
|
|
for (int i = 0, n = m_VBufferLighting.Length; i < n; i++)
|
|
{
|
|
newArray[i] = m_VBufferLighting[i];
|
|
newArrayRT[i] = m_VBufferLightingRT[i];
|
|
}
|
|
|
|
// Reassign and release memory.
|
|
m_VBufferLighting = newArray;
|
|
m_VBufferLightingRT = newArrayRT;
|
|
}
|
|
}
|
|
|
|
Debug.Assert(m_VBufferLighting != null);
|
|
|
|
int w = 0, h = 0, d = 0;
|
|
ComputeVBufferResolutionAndScale(screenWidth, screenHeight, ref w, ref h, ref d);
|
|
|
|
for (int i = viewOffset * k_VBufferCount,
|
|
n = viewOffset * k_VBufferCount + k_VBufferCount; i < n; i++)
|
|
{
|
|
m_VBufferLighting[i] = new RenderTexture(w, h, 0, RenderTextureFormat.ARGBHalf, RenderTextureReadWrite.Linear);
|
|
m_VBufferLighting[i].filterMode = FilterMode.Trilinear; // Custom
|
|
m_VBufferLighting[i].dimension = TextureDimension.Tex3D; // TODO: request the thick 3D tiling layout
|
|
m_VBufferLighting[i].volumeDepth = d;
|
|
m_VBufferLighting[i].enableRandomWrite = true;
|
|
m_VBufferLighting[i].Create();
|
|
|
|
m_VBufferLightingRT[i] = new RenderTargetIdentifier(m_VBufferLighting[i]);
|
|
}
|
|
}
|
|
|
|
void DestroyVBuffer(int viewId)
|
|
{
|
|
int viewOffset = ViewOffsetFromViewId(viewId);
|
|
|
|
if (viewOffset < 0)
|
|
{
|
|
// Not found.
|
|
return;
|
|
}
|
|
|
|
int lastOffset = m_ViewCount - 1;
|
|
Debug.Assert(lastOffset >= 0);
|
|
|
|
if (m_VBufferLighting != null)
|
|
{
|
|
Debug.Assert(m_VBufferLighting.Length >= m_ViewCount * k_VBufferCount);
|
|
|
|
for (int i = 0; i < k_VBufferCount; i++)
|
|
{
|
|
int viewBuffer = viewOffset * k_VBufferCount + i;
|
|
int lastBuffer = lastOffset * k_VBufferCount + i;
|
|
|
|
// Release the memory.
|
|
if (m_VBufferLighting[viewBuffer] != null)
|
|
{
|
|
m_VBufferLighting[viewBuffer].Release();
|
|
}
|
|
|
|
// Swap with the last element.
|
|
m_VBufferLighting[viewBuffer] = m_VBufferLighting[lastBuffer];
|
|
m_VBufferLightingRT[viewBuffer] = m_VBufferLightingRT[lastBuffer];
|
|
}
|
|
}
|
|
|
|
// Swap with the last element and shrink the array.
|
|
m_ViewIdArray[viewOffset] = m_ViewIdArray[lastOffset];
|
|
m_ViewCount--;
|
|
}
|
|
|
|
// Uses a logarithmic depth encoding.
|
|
// Near plane: depth = 0; far plane: depth = 1.
|
|
// x = n, y = log2(f/n), z = 1/n, w = 1/log2(f/n).
|
|
public static Vector4 ComputeLogarithmicDepthEncodingParams(float nearPlane, float farPlane)
|
|
{
|
|
Vector4 depthParams = new Vector4();
|
|
|
|
float n = nearPlane;
|
|
float f = farPlane;
|
|
|
|
depthParams.x = n;
|
|
depthParams.y = Mathf.Log(f / n, 2);
|
|
depthParams.z = 1.0f / depthParams.x;
|
|
depthParams.w = 1.0f / depthParams.y;
|
|
|
|
return depthParams;
|
|
}
|
|
|
|
// Returns NULL if a global fog component does not exist, or is not enabled.
|
|
public static HomogeneousFog GetGlobalFogComponent()
|
|
{
|
|
HomogeneousFog globalFogComponent = null;
|
|
|
|
HomogeneousFog[] fogComponents = Object.FindObjectsOfType(typeof(HomogeneousFog)) as HomogeneousFog[];
|
|
|
|
foreach (HomogeneousFog fogComponent in fogComponents)
|
|
{
|
|
if (fogComponent.enabled && fogComponent.volumeParameters.IsVolumeUnbounded())
|
|
{
|
|
globalFogComponent = fogComponent;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return globalFogComponent;
|
|
}
|
|
|
|
RenderTargetIdentifier GetVBufferLightingHistory(int viewOffset) // From the previous frame
|
|
{
|
|
return m_VBufferLightingRT[viewOffset * k_VBufferCount + ((Time.renderedFrameCount + 0) & 1)]; // Does not work in the Scene view
|
|
}
|
|
|
|
RenderTargetIdentifier GetVBufferLightingFeedback(int viewOffset) // For the next frame
|
|
{
|
|
return m_VBufferLightingRT[viewOffset * k_VBufferCount + ((Time.renderedFrameCount + 1) & 1)]; // Does not work in the Scene view
|
|
}
|
|
|
|
RenderTargetIdentifier GetVBufferLightingIntegral(int viewOffset) // Of the current frame
|
|
{
|
|
return m_VBufferLightingRT[viewOffset * k_VBufferCount + 2];
|
|
}
|
|
|
|
public void SetVolumetricLightingData(HDCamera camera, CommandBuffer cmd)
|
|
{
|
|
HomogeneousFog globalFogComponent = GetGlobalFogComponent();
|
|
|
|
// TODO: may want to cache these results somewhere.
|
|
VolumeProperties globalFogProperties = (globalFogComponent != null) ? globalFogComponent.volumeParameters.GetProperties()
|
|
: VolumeProperties.GetNeutralVolumeProperties();
|
|
|
|
cmd.SetGlobalVector(HDShaderIDs._GlobalFog_Scattering, globalFogProperties.scattering);
|
|
cmd.SetGlobalFloat( HDShaderIDs._GlobalFog_Extinction, globalFogProperties.extinction);
|
|
|
|
int w = 0, h = 0, d = 0;
|
|
Vector2 scale = ComputeVBufferResolutionAndScale(camera.screenSize.x, camera.screenSize.y, ref w, ref h, ref d);
|
|
|
|
int viewId = camera.camera.GetInstanceID();
|
|
int viewOffset = ViewOffsetFromViewId(viewId);
|
|
|
|
Debug.Assert(viewOffset >= 0 && viewOffset < 8);
|
|
|
|
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));
|
|
cmd.SetGlobalTexture(HDShaderIDs._VBufferLighting, GetVBufferLightingIntegral(viewOffset));
|
|
}
|
|
|
|
// Ref: https://en.wikipedia.org/wiki/Close-packing_of_equal_spheres
|
|
// The returned {x, y} coordinates (and all spheres) are all within the (-0.5, 0.5)^2 range.
|
|
// The pattern has been rotated by 15 degrees to maximize the resolution along X and Y:
|
|
// https://www.desmos.com/calculator/kcpfvltz7c
|
|
Vector2[] GetHexagonalClosePackedSpheres7()
|
|
{
|
|
Vector2[] coords = new Vector2[7];
|
|
|
|
float r = 0.17054068870105443882f;
|
|
float d = 2 * r;
|
|
float s = r * Mathf.Sqrt(3);
|
|
|
|
// Try to keep the weighted average as close to the center (0.5) as possible.
|
|
// (7)(5) ( )( ) ( )( ) ( )( ) ( )( ) ( )(o) ( )(x) (o)(x) (x)(x)
|
|
// (2)(1)(3) ( )(o)( ) (o)(x)( ) (x)(x)(o) (x)(x)(x) (x)(x)(x) (x)(x)(x) (x)(x)(x) (x)(x)(x)
|
|
// (4)(6) ( )( ) ( )( ) ( )( ) (o)( ) (x)( ) (x)(o) (x)(x) (x)(x)
|
|
coords[0] = new Vector2( 0, 0);
|
|
coords[1] = new Vector2(-d, 0);
|
|
coords[2] = new Vector2( d, 0);
|
|
coords[3] = new Vector2(-r, -s);
|
|
coords[4] = new Vector2( r, s);
|
|
coords[5] = new Vector2( r, -s);
|
|
coords[6] = new Vector2(-r, s);
|
|
|
|
// Rotate the sampling pattern by 15 degrees.
|
|
const float cos15 = 0.96592582628906828675f;
|
|
const float sin15 = 0.25881904510252076235f;
|
|
|
|
for (int i = 0; i < 7; i++)
|
|
{
|
|
Vector2 coord = coords[i];
|
|
|
|
coords[i].x = coord.x * cos15 - coord.y * sin15;
|
|
coords[i].y = coord.x * sin15 + coord.y * cos15;
|
|
}
|
|
|
|
return coords;
|
|
}
|
|
|
|
void VolumetricLightingPass(HDCamera camera, CommandBuffer cmd)
|
|
{
|
|
if (m_VolumetricLightingPreset == VolumetricLightingPreset.Off) return;
|
|
|
|
using (new ProfilingSample(cmd, "Volumetric Lighting"))
|
|
{
|
|
int viewId = camera.camera.GetInstanceID(); // Warning: different views can use the same camera
|
|
int viewOffset = ViewOffsetFromViewId(viewId);
|
|
|
|
Debug.Assert(viewOffset >= 0 && viewOffset < 8);
|
|
|
|
if (GetGlobalFogComponent() == null)
|
|
{
|
|
// Clear the render target instead of running the shader.
|
|
// CoreUtils.SetRenderTarget(cmd, GetVBufferLightingIntegral(viewOffset), ClearFlag.Color, CoreUtils.clearColorAllBlack);
|
|
// return;
|
|
|
|
// Clearing 3D textures does not seem to work!
|
|
// Use the workaround by running the full shader with no volume.
|
|
}
|
|
|
|
bool enableClustered = m_FrameSettings.lightLoopSettings.enableTileAndCluster;
|
|
bool enableReprojection = Application.isPlaying && camera.camera.cameraType == CameraType.Game;
|
|
|
|
int kernel;
|
|
|
|
if (enableReprojection)
|
|
{
|
|
// Only available in the Play Mode because all the frame counters in the Edit Mode are broken.
|
|
kernel = m_VolumetricLightingCS.FindKernel(enableClustered ? "VolumetricLightingClusteredReproj"
|
|
: "VolumetricLightingAllLightsReproj");
|
|
}
|
|
else
|
|
{
|
|
kernel = m_VolumetricLightingCS.FindKernel(enableClustered ? "VolumetricLightingClustered"
|
|
: "VolumetricLightingAllLights");
|
|
|
|
}
|
|
|
|
int w = 0, h = 0, d = 0;
|
|
Vector2 scale = ComputeVBufferResolutionAndScale(camera.screenSize.x, camera.screenSize.y, ref w, ref h, ref d);
|
|
float vFoV = camera.camera.fieldOfView * Mathf.Deg2Rad;
|
|
|
|
// Compose the matrix which allows us to compute the world space view direction.
|
|
// Compute it using the scaled resolution to account for the visible area of the VBuffer.
|
|
Vector4 scaledRes = new Vector4(w * scale.x, h * scale.y, 1.0f / (w * scale.x), 1.0f / (h * scale.y));
|
|
Matrix4x4 transform = HDUtils.ComputePixelCoordToWorldSpaceViewDirectionMatrix(vFoV, scaledRes, camera.viewMatrix, false);
|
|
|
|
camera.SetupComputeShader(m_VolumetricLightingCS, cmd);
|
|
|
|
Vector2[] xySeq = GetHexagonalClosePackedSpheres7();
|
|
|
|
// This is a sequence of 7 equidistant numbers from 1/14 to 13/14.
|
|
// Each of them is the centroid of the interval of length 2/14.
|
|
// They've been rearranged in a sequence of pairs {small, large}, s.t. (small + large) = 1.
|
|
// That way, the running average position is close to 0.5.
|
|
// | 6 | 2 | 4 | 1 | 5 | 3 | 7 |
|
|
// | | | | o | | | |
|
|
// | | o | | x | | | |
|
|
// | | x | | x | | o | |
|
|
// | | x | o | x | | x | |
|
|
// | | x | x | x | o | x | |
|
|
// | o | x | x | x | x | x | |
|
|
// | x | x | x | x | x | x | o |
|
|
// | x | x | x | x | x | x | x |
|
|
float[] zSeq = {7.0f/14.0f, 3.0f/14.0f, 11.0f/14.0f, 5.0f/14.0f, 9.0f/14.0f, 1.0f/14.0f, 13.0f/14.0f};
|
|
|
|
int rfc = Time.renderedFrameCount;
|
|
int sampleIndex = rfc % 7;
|
|
Vector4 offset = new Vector4(xySeq[sampleIndex].x, xySeq[sampleIndex].y, zSeq[sampleIndex], rfc);
|
|
|
|
// TODO: set 'm_VolumetricLightingPreset'.
|
|
cmd.SetComputeVectorParam( m_VolumetricLightingCS, HDShaderIDs._VBufferSampleOffset, offset);
|
|
cmd.SetComputeMatrixParam( m_VolumetricLightingCS, HDShaderIDs._VBufferCoordToViewDirWS, transform);
|
|
cmd.SetComputeTextureParam(m_VolumetricLightingCS, kernel, HDShaderIDs._VBufferLightingHistory, GetVBufferLightingHistory(viewOffset)); // Read
|
|
cmd.SetComputeTextureParam(m_VolumetricLightingCS, kernel, HDShaderIDs._VBufferLightingFeedback, GetVBufferLightingFeedback(viewOffset)); // Write
|
|
cmd.SetComputeTextureParam(m_VolumetricLightingCS, kernel, HDShaderIDs._VBufferLightingIntegral, GetVBufferLightingIntegral(viewOffset)); // Write
|
|
|
|
// The shader defines GROUP_SIZE_1D = 16.
|
|
cmd.DispatchCompute(m_VolumetricLightingCS, kernel, (w + 15) / 16, (h + 15) / 16, 1);
|
|
}
|
|
}
|
|
} // class HDRenderPipeline
|
|
} // namespace UnityEngine.Experimental.Rendering.HDPipeline
|