您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
421 行
15 KiB
421 行
15 KiB
using UnityEngine;
|
|
using System.Collections;
|
|
using UnityEngine.Rendering;
|
|
using System.Collections.Generic;
|
|
using System;
|
|
|
|
namespace UnityEngine.ScriptableRenderLoop
|
|
{
|
|
[System.Serializable]
|
|
public struct ShadowSettings
|
|
{
|
|
public bool enabled;
|
|
public int shadowAtlasWidth;
|
|
public int shadowAtlasHeight;
|
|
|
|
public float maxShadowDistance;
|
|
public int directionalLightCascadeCount;
|
|
public Vector3 directionalLightCascades;
|
|
|
|
|
|
public static ShadowSettings Default
|
|
{
|
|
get
|
|
{
|
|
ShadowSettings settings;
|
|
settings.enabled = true;
|
|
settings.shadowAtlasHeight = settings.shadowAtlasWidth = 4096;
|
|
settings.directionalLightCascadeCount = 1;
|
|
settings.directionalLightCascades = new Vector3 (0.05F, 0.2F, 0.3F);
|
|
settings.directionalLightCascadeCount = 4;
|
|
settings.maxShadowDistance = 1000.0F;
|
|
return settings;
|
|
}
|
|
}
|
|
}
|
|
|
|
public struct InputShadowLightData
|
|
{
|
|
public int lightIndex;
|
|
public int shadowResolution;
|
|
}
|
|
|
|
public struct ShadowLight
|
|
{
|
|
public int shadowSliceIndex;
|
|
public int shadowSliceCount;
|
|
}
|
|
|
|
public struct ShadowSliceData
|
|
{
|
|
public Matrix4x4 shadowTransform;
|
|
public int atlasX;
|
|
public int atlasY;
|
|
public int shadowResolution;
|
|
}
|
|
|
|
public struct ShadowOutput
|
|
{
|
|
public ShadowSliceData[] shadowSlices;
|
|
public ShadowLight[] shadowLights;
|
|
public Vector4[] directionalShadowSplitSphereSqr;
|
|
|
|
public int GetShadowSliceCountLightIndex(int lightIndex)
|
|
{
|
|
return shadowLights[lightIndex].shadowSliceCount;
|
|
}
|
|
|
|
public int GetShadowSliceIndex(int lightIndex, int sliceIndex)
|
|
{
|
|
if (sliceIndex >= shadowLights[lightIndex].shadowSliceCount)
|
|
throw new System.IndexOutOfRangeException ();
|
|
|
|
return shadowLights[lightIndex].shadowSliceIndex + sliceIndex;
|
|
}
|
|
}
|
|
|
|
public struct ShadowRenderPass : IDisposable
|
|
{
|
|
ShadowSettings m_Settings;
|
|
|
|
[NonSerialized]
|
|
bool m_bFailedToPackLastTime;
|
|
int m_ShadowTexName;
|
|
const int kDepthBuffer = 24;
|
|
|
|
|
|
public ShadowRenderPass(ShadowSettings settings)
|
|
{
|
|
m_Settings = settings;
|
|
m_bFailedToPackLastTime = false;
|
|
m_ShadowTexName = Shader.PropertyToID("g_tShadowBuffer");
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
|
|
}
|
|
|
|
struct AtlasEntry
|
|
{
|
|
public AtlasEntry(int splitIndex, int lightIndex)
|
|
{
|
|
this.splitIndex = splitIndex;
|
|
this.lightIndex = lightIndex;
|
|
}
|
|
|
|
public int splitIndex;
|
|
public int lightIndex;
|
|
}
|
|
|
|
int CalculateNumShadowSplits(int index, ActiveLight[] lights)
|
|
{
|
|
LightType lightType = lights [index].lightType;
|
|
if (lightType == LightType.Spot)
|
|
return 1;
|
|
|
|
if (lightType == LightType.Directional)
|
|
return m_Settings.directionalLightCascadeCount;
|
|
|
|
return 6;
|
|
}
|
|
|
|
static public void ClearPackedShadows(ActiveLight[] lights, out ShadowOutput packedShadows)
|
|
{
|
|
packedShadows.directionalShadowSplitSphereSqr = null;
|
|
packedShadows.shadowSlices = null;
|
|
packedShadows.shadowLights = new ShadowLight[lights.Length];
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------------------------------------------------------------------
|
|
bool AutoPackLightsIntoShadowTexture(List<InputShadowLightData> shadowLights, ActiveLight[] lights, out ShadowOutput packedShadows)
|
|
{
|
|
List<int> shadowIndices = new List<int>();
|
|
|
|
//@TODO: Disallow multiple directional lights
|
|
|
|
for (int i = 0; i < shadowLights.Count; i++)
|
|
{
|
|
shadowIndices.Add (shadowLights[i].lightIndex);
|
|
}
|
|
|
|
// World's stupidest sheet packer:
|
|
// 1. Sort all lights from largest to smallest
|
|
// 2. In a left->right, top->bottom pattern, fill quads until you reach the edge of the texture
|
|
// 3. Move position to x=0, y=bottomOfFirstTextureInThisRow
|
|
// 4. Goto 2.
|
|
// Yes, this will produce holes as the quads shrink, but it's good enough for now. I'll work on this more later to fill the gaps.
|
|
|
|
// Sort all lights from largest to smallest
|
|
shadowIndices.Sort(
|
|
delegate( int l1, int l2 )
|
|
{
|
|
int nCompare = 0;
|
|
// Sort shadow-casting lights by shadow resolution
|
|
nCompare = shadowLights[l1].shadowResolution.CompareTo(shadowLights[l2].shadowResolution); // Sort by shadow size
|
|
|
|
if ( nCompare == 0 ) // Same, so sort by range to stabilize sort results
|
|
nCompare = lights[l1].range.CompareTo( lights[l2].range ); // Sort by shadow size
|
|
|
|
if ( nCompare == 0 ) // Still same, so sort by instance ID to stabilize sort results
|
|
nCompare = lights[l1].light.GetInstanceID().CompareTo( lights[l2].light.GetInstanceID() );
|
|
|
|
return nCompare;
|
|
}
|
|
);
|
|
|
|
// Start filling lights into texture
|
|
List<AtlasEntry> requestedPages = new List<AtlasEntry>();
|
|
packedShadows.shadowLights = new ShadowLight[lights.Length];
|
|
for (int i = 0; i != shadowIndices.Count;i++)
|
|
{
|
|
int numShadowSplits = CalculateNumShadowSplits(shadowIndices[i], lights);
|
|
|
|
packedShadows.shadowLights[shadowIndices [i]].shadowSliceCount = numShadowSplits;
|
|
packedShadows.shadowLights[shadowIndices [i]].shadowSliceIndex = requestedPages.Count;
|
|
|
|
for (int s = 0; s < numShadowSplits; s++)
|
|
requestedPages.Add (new AtlasEntry (requestedPages.Count, shadowIndices[i]));
|
|
}
|
|
|
|
int nCurrentX = 0;
|
|
int nCurrentY = -1;
|
|
int nNextY = 0;
|
|
|
|
packedShadows.shadowSlices = new ShadowSliceData[requestedPages.Count];
|
|
packedShadows.directionalShadowSplitSphereSqr = new Vector4[4];
|
|
|
|
foreach (AtlasEntry entry in requestedPages)
|
|
{
|
|
int shadowResolution = shadowLights[entry.lightIndex].shadowResolution;
|
|
|
|
// Check if first texture is too wide
|
|
if ( nCurrentY == -1 )
|
|
{
|
|
if ( ( shadowResolution > m_Settings.shadowAtlasWidth ) || ( shadowResolution > m_Settings.shadowAtlasHeight ) )
|
|
{
|
|
Debug.LogError( "ERROR! Shadow packer ran out of space in the " + m_Settings.shadowAtlasWidth + "x" + m_Settings.shadowAtlasHeight + " texture!\n\n" );
|
|
m_bFailedToPackLastTime = true;
|
|
ClearPackedShadows (lights, out packedShadows);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Goto next scanline
|
|
if ( ( nCurrentY == -1 ) || ( ( nCurrentX + shadowResolution ) > m_Settings.shadowAtlasWidth ) )
|
|
{
|
|
nCurrentX = 0;
|
|
nCurrentY = nNextY;
|
|
nNextY += shadowResolution;
|
|
}
|
|
|
|
// Check if we've run out of space
|
|
if ( ( nCurrentY + shadowResolution ) > m_Settings.shadowAtlasHeight )
|
|
{
|
|
Debug.LogError( "ERROR! Shadow packer ran out of space in the " + m_Settings.shadowAtlasWidth + "x" + m_Settings.shadowAtlasHeight + " texture!\n\n" );
|
|
m_bFailedToPackLastTime = true;
|
|
ClearPackedShadows (lights, out packedShadows);
|
|
return false;
|
|
}
|
|
|
|
// Save location to light
|
|
packedShadows.shadowSlices[entry.splitIndex].atlasX = nCurrentX;
|
|
packedShadows.shadowSlices[entry.splitIndex].atlasY = nCurrentY;
|
|
packedShadows.shadowSlices[entry.splitIndex].shadowResolution = shadowResolution;
|
|
|
|
// Move ahead
|
|
nCurrentX += shadowResolution;
|
|
|
|
//Debug.Log( "Sheet packer: " + vl.m_cachedLight.name + " ( " + vl.m_shadowX + ", " + vl.m_shadowY + " ) " + vl.m_shadowResolution + "\n\n" );
|
|
}
|
|
|
|
if ( m_bFailedToPackLastTime )
|
|
{
|
|
m_bFailedToPackLastTime = false;
|
|
Debug.Log( "SUCCESS! Shadow packer can now fit all lights into the " + m_Settings.shadowAtlasWidth + "x" + m_Settings.shadowAtlasHeight + " texture!\n\n" );
|
|
}
|
|
|
|
return requestedPages.Count != 0;
|
|
}
|
|
|
|
static List<InputShadowLightData> GetInputShadowLightData(CullResults cullResults)
|
|
{
|
|
var shadowCasters = new List<InputShadowLightData> ();
|
|
ActiveLight[] lights = cullResults.culledLights;
|
|
int directionalLightCount = 0;
|
|
for (int i = 0; i < lights.Length; i++)
|
|
{
|
|
//@TODO: ignore baked. move this logic to c++...
|
|
if (lights[i].light.shadows != LightShadows.None)
|
|
{
|
|
|
|
// Only a single directional shadow casting light is supported
|
|
if (lights [i].lightType == LightType.Directional)
|
|
{
|
|
directionalLightCount++;
|
|
if (directionalLightCount != 1)
|
|
continue;
|
|
}
|
|
|
|
AdditionalLightData additionalLight = lights [i].light.GetComponent<AdditionalLightData> ();
|
|
|
|
InputShadowLightData light;
|
|
light.lightIndex = i;
|
|
light.shadowResolution = AdditionalLightData.GetShadowResolution(additionalLight);
|
|
|
|
shadowCasters.Add (light);
|
|
}
|
|
}
|
|
return shadowCasters;
|
|
}
|
|
|
|
public void UpdateCullingParameters(ref CullingParameters parameters)
|
|
{
|
|
parameters.shadowDistance = Mathf.Min (m_Settings.maxShadowDistance, parameters.shadowDistance);
|
|
}
|
|
|
|
public void Render(RenderLoop loop, CullResults cullResults, out ShadowOutput packedShadows)
|
|
{
|
|
if (!m_Settings.enabled)
|
|
{
|
|
ClearPackedShadows(cullResults.culledLights, out packedShadows);
|
|
}
|
|
|
|
// Pack all shadow quads into the texture
|
|
if ( !AutoPackLightsIntoShadowTexture(GetInputShadowLightData(cullResults), cullResults.culledLights, out packedShadows) )
|
|
{
|
|
// No shadowing lights found, so skip all rendering
|
|
return;
|
|
}
|
|
|
|
RenderPackedShadows (loop, cullResults, ref packedShadows);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------------------------------------------------------------------
|
|
// Render shadows
|
|
//---------------------------------------------------------------------------------------------------------------------------------------------------
|
|
void RenderPackedShadows(RenderLoop loop, CullResults cullResults, ref ShadowOutput packedShadows)
|
|
{
|
|
var setRenderTargetCommandBuffer = new CommandBuffer ();
|
|
|
|
setRenderTargetCommandBuffer.GetTemporaryRT (m_ShadowTexName, m_Settings.shadowAtlasWidth, m_Settings.shadowAtlasHeight, kDepthBuffer, FilterMode.Bilinear, RenderTextureFormat.Shadowmap, RenderTextureReadWrite.Linear);
|
|
setRenderTargetCommandBuffer.SetRenderTarget (new RenderTargetIdentifier (m_ShadowTexName));
|
|
|
|
setRenderTargetCommandBuffer.ClearRenderTarget (true, true, Color.green);
|
|
loop.ExecuteCommandBuffer (setRenderTargetCommandBuffer);
|
|
setRenderTargetCommandBuffer.Dispose ();
|
|
|
|
ActiveLight[] activeLights = cullResults.culledLights;
|
|
var shadowSlices = packedShadows.shadowSlices;
|
|
|
|
// Render each light's shadow buffer into a subrect of the shared depth texture
|
|
for ( int lightIndex = 0; lightIndex < packedShadows.shadowLights.Length; lightIndex++ )
|
|
{
|
|
int shadowSliceCount = packedShadows.shadowLights[lightIndex].shadowSliceCount;
|
|
if (shadowSliceCount == 0)
|
|
continue;
|
|
|
|
Profiler.BeginSample ("Shadows.GetShadowCasterBounds");
|
|
Bounds bounds;
|
|
if (!cullResults.GetShadowCasterBounds (lightIndex, out bounds))
|
|
{
|
|
Profiler.EndSample ();
|
|
return;
|
|
}
|
|
Profiler.EndSample ();
|
|
|
|
Profiler.BeginSample ("Shadows.DrawShadows");
|
|
|
|
Matrix4x4 proj;
|
|
Matrix4x4 view;
|
|
|
|
LightType lightType = activeLights[lightIndex].lightType;
|
|
Vector3 lightDirection = activeLights[lightIndex].light.transform.forward;
|
|
var shadowNearClip = activeLights[lightIndex].light.shadowNearPlane;
|
|
|
|
int shadowSliceIndex = packedShadows.GetShadowSliceIndex (lightIndex, 0);
|
|
|
|
if (lightType == LightType.Spot)
|
|
{
|
|
DrawShadowsSettings settings = new DrawShadowsSettings(cullResults, lightIndex);
|
|
bool needRendering = cullResults.ComputeSpotShadowsMatricesAndCullingPrimitives(lightIndex, out view, out proj, out settings.splitData);
|
|
SetupShadowSplitMatrices(ref packedShadows.shadowSlices[shadowSliceIndex], proj, view);
|
|
if (needRendering)
|
|
RenderShadowSplit(ref shadowSlices[shadowSliceIndex], lightDirection, proj, view, ref loop, settings);
|
|
}
|
|
else if (lightType == LightType.Directional)
|
|
{
|
|
Vector3 splitRatio = m_Settings.directionalLightCascades;
|
|
|
|
for (int s = 0; s < 4; ++s)
|
|
packedShadows.directionalShadowSplitSphereSqr[s] = new Vector4 (0, 0, 0, float.NegativeInfinity);
|
|
|
|
for (int s = 0; s < shadowSliceCount; ++s, shadowSliceIndex++)
|
|
{
|
|
var settings = new DrawShadowsSettings(cullResults, lightIndex);
|
|
var shadowResolution = shadowSlices [shadowSliceIndex].shadowResolution;
|
|
bool needRendering = cullResults.ComputeDirectionalShadowMatricesAndCullingPrimitives(lightIndex, s, shadowSliceCount, splitRatio, shadowResolution, shadowNearClip, out view, out proj, out settings.splitData);
|
|
|
|
packedShadows.directionalShadowSplitSphereSqr[s] = settings.splitData.cullingSphere;
|
|
packedShadows.directionalShadowSplitSphereSqr[s].w *= packedShadows.directionalShadowSplitSphereSqr[s].w;
|
|
|
|
SetupShadowSplitMatrices(ref shadowSlices[shadowSliceIndex], proj, view);
|
|
if (needRendering)
|
|
RenderShadowSplit(ref shadowSlices[shadowSliceIndex], lightDirection, proj, view, ref loop, settings);
|
|
}
|
|
}
|
|
else if (lightType == LightType.Point)
|
|
{
|
|
for(int s = 0; s < shadowSliceCount; ++s, shadowSliceIndex++)
|
|
{
|
|
DrawShadowsSettings settings = new DrawShadowsSettings(cullResults, lightIndex);
|
|
bool needRendering = cullResults.ComputePointShadowsMatricesAndCullingPrimitives(lightIndex, (CubemapFace)s, 2.0f, out view, out proj, out settings.splitData);
|
|
|
|
SetupShadowSplitMatrices(ref shadowSlices[shadowSliceIndex], proj, view);
|
|
if (needRendering)
|
|
RenderShadowSplit(ref shadowSlices[shadowSliceIndex], lightDirection, proj, view, ref loop, settings);
|
|
}
|
|
}
|
|
Profiler.EndSample ();
|
|
}
|
|
}
|
|
|
|
|
|
private void SetupShadowSplitMatrices(ref ShadowSliceData lightData, Matrix4x4 proj, Matrix4x4 view)
|
|
{
|
|
Matrix4x4 matScaleBias = Matrix4x4.identity;
|
|
matScaleBias.m00 = 0.5f;
|
|
matScaleBias.m11 = 0.5f;
|
|
matScaleBias.m22 = 0.5f;
|
|
matScaleBias.m03 = 0.5f;
|
|
matScaleBias.m13 = 0.5f;
|
|
matScaleBias.m23 = 0.5f;
|
|
|
|
Matrix4x4 matTile = Matrix4x4.identity;
|
|
matTile.m00 = (float)lightData.shadowResolution / (float)m_Settings.shadowAtlasWidth;
|
|
matTile.m11 = (float)lightData.shadowResolution / (float)m_Settings.shadowAtlasHeight;
|
|
matTile.m03 = (float)lightData.atlasX / (float)m_Settings.shadowAtlasWidth;
|
|
matTile.m13 = (float)lightData.atlasY / (float)m_Settings.shadowAtlasHeight;
|
|
lightData.shadowTransform = matTile * matScaleBias * proj * view;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------------------------------------------------------------------
|
|
private void RenderShadowSplit(ref ShadowSliceData slice, Vector3 lightDirection, Matrix4x4 proj, Matrix4x4 view, ref RenderLoop loop, DrawShadowsSettings settings)
|
|
{
|
|
var commandBuffer = new CommandBuffer();
|
|
commandBuffer.name = "ShadowSetup";
|
|
|
|
// Set viewport / matrices etc
|
|
commandBuffer.SetViewport(new Rect(slice.atlasX, slice.atlasY, slice.shadowResolution, slice.shadowResolution));
|
|
//commandBuffer.ClearRenderTarget (true, true, Color.green);
|
|
commandBuffer.SetGlobalVector("g_vLightDirWs", new Vector4(lightDirection.x, lightDirection.y, lightDirection.z));
|
|
commandBuffer.SetProjectionAndViewMatrices(proj, view);
|
|
// commandBuffer.SetGlobalDepthBias (1.0F, 1.0F);
|
|
loop.ExecuteCommandBuffer(commandBuffer);
|
|
commandBuffer.Dispose();
|
|
|
|
// Render
|
|
loop.DrawShadows(ref settings);
|
|
}
|
|
}
|
|
}
|