GitHub
8 年前
当前提交
5444da2d
共有 38 个文件被更改,包括 2448 次插入 和 20 次删除
-
2Assets/ScriptableRenderPipeline/HDRenderPipeline/HDRenderPipeline.asset
-
2Assets/ScriptableRenderPipeline/HDRenderPipeline/HDRenderPipeline.asset.meta
-
2Assets/ScriptableRenderPipeline/HDRenderPipeline/HDRenderPipeline.cs
-
2Assets/ScriptableRenderPipeline/HDRenderPipeline/HDRenderPipeline.cs.meta
-
1Assets/ScriptableRenderPipeline/HDRenderPipeline/Lighting/LightLoop.cs
-
3Assets/ScriptableRenderPipeline/HDRenderPipeline/Lighting/Lighting.hlsl
-
201Assets/ScriptableRenderPipeline/HDRenderPipeline/Lighting/TilePass/TilePass.cs
-
7Assets/ScriptableRenderPipeline/HDRenderPipeline/Lighting/TilePass/TilePass.hlsl
-
17Assets/ScriptableRenderPipeline/HDRenderPipeline/Lighting/TilePass/TilePassLoop.hlsl
-
2Assets/ScriptableRenderPipeline/HDRenderPipeline/Lighting/TilePass/TilePassProducer.asset.meta
-
23Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Lit.hlsl
-
2Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow.meta
-
135Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow/Shadow.hlsl
-
1Assets/ScriptableRenderPipeline/ShaderLibrary/API/D3D11.hlsl
-
1Assets/ScriptableRenderPipeline/ShaderLibrary/API/Metal.hlsl
-
1Assets/ScriptableRenderPipeline/ShaderLibrary/API/PSSL.hlsl
-
692Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow/Shadow.cs
-
12Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow/Shadow.cs.meta
-
188Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow/ShadowAlgorithms.hlsl
-
9Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow/ShadowAlgorithms.hlsl.meta
-
11Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow/ShadowAlgorithmsCustom.hlsl
-
9Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow/ShadowAlgorithmsCustom.hlsl.meta
-
354Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow/ShadowBase.cs
-
71Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow/ShadowBase.cs.hlsl
-
9Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow/ShadowBase.cs.hlsl.meta
-
12Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow/ShadowBase.cs.meta
-
29Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow/ShadowContext.hlsl
-
9Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow/ShadowContext.hlsl.meta
-
52Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow/ShadowDispatch.hlsl
-
9Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow/ShadowDispatch.hlsl.meta
-
86Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow/ShadowSampling.hlsl
-
9Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow/ShadowSampling.hlsl.meta
-
81Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow/ShadowTexFetch.hlsl
-
9Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow/ShadowTexFetch.hlsl.meta
-
88Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow/ShadowUtilities.cs
-
12Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow/ShadowUtilities.cs.meta
-
303Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow/VectorArray.cs
-
12Assets/ScriptableRenderPipeline/HDRenderPipeline/Shadow/VectorArray.cs.meta
|
|||
// TODO: |
|||
// - How to support a Gather sampling with such abstraction ? |
|||
// - What's belong to shadow and what's belong to renderloop ? (shadowmap size depends on the usage of atlas or not) |
|||
// - Is PunctualShadowData fixed or customizable ? Who is the owner ? Should it be pass to GetPunctualShadowAttenuation ? Sure it should... |
|||
// - Could be return by GetShadowTextureCoordinate() and pass to GetPunctualShadowAttenuation(). But in this case, who control the atlas application ? |
|||
// TODO: |
|||
// Caution: formula doesn't work as we are texture atlas... |
|||
// if (max3(abs(NDC.x), abs(NDC.y), 1.0 - texCoordXYZ.z) <= 1.0) return 1.0; |
|||
#ifndef SHADOW_HLSL |
|||
#define SHADOW_HLSL |
|||
// |
|||
// Shadow master include header. |
|||
// |
|||
// There are four relevant files for shadows. |
|||
// First ShadowContext.hlsl must declare the specific ShadowContext struct and the loader that goes along with it. |
|||
// ShadowContext loading and resource setup from C# must be in sync. |
|||
// |
|||
// Second there are two headers for shadow algorithms, whose signatures must match any of the Get...Attenuation function prototypes. |
|||
// The first header contains engine defaults, whereas the second header is empty by default. All project specific custom shadow algorithms should go in there or leave empty. |
|||
// |
|||
// Last there's a dispatcher include. By default the Get...Attenuation functions are rerouted to their default implementations. This can be overridden for each |
|||
// shadow type in the dispatcher source. For each overridden shadow type a specific define must be defined to prevent falling back to the default functions. |
|||
// |
|||
|
|||
|
|||
//#define SHADOWS_USE_SHADOWCTXT |
|||
|
|||
#ifdef SHADOWS_USE_SHADOWCTXT |
|||
#define SHADOW_SUPPORTS_DYNAMIC_INDEXING 0 // only on >= sm 5.1 |
|||
|
|||
// TODO: Remove this once we've moved over to the new system. Also delete the undef at the bottom again. |
|||
#define ShadowData ShadowDataExp |
|||
|
|||
#include "ShadowBase.cs.hlsl" // ShadowData definition, auto generated (don't modify) |
|||
#include "ShadowTexFetch.hlsl" // Resource sampling definitions (don't modify) |
|||
|
|||
#define SHADOWCONTEXT_DECLARE( _Tex2DArraySlots, _TexCubeArraySlots, _SamplerCompSlots, _SamplerSlots ) \ |
|||
\ |
|||
struct ShadowContext \ |
|||
{ \ |
|||
StructuredBuffer<ShadowData> shadowDatas; \ |
|||
StructuredBuffer<int4> payloads; \ |
|||
Texture2DArray tex2DArray[_Tex2DArraySlots]; \ |
|||
TextureCubeArray texCubeArray[_TexCubeArraySlots]; \ |
|||
SamplerComparisonState compSamplers[_SamplerCompSlots]; \ |
|||
SamplerState samplers[_SamplerSlots]; \ |
|||
}; \ |
|||
\ |
|||
SHADOW_DEFINE_SAMPLING_FUNCS( _Tex2DArraySlots, _TexCubeArraySlots, _SamplerCompSlots, _SamplerSlots ) |
|||
|
|||
// Shadow context definition and initialization, i.e. resource binding (project header, must be kept in sync with C# runtime) |
|||
#include "ShadowContext.hlsl" |
|||
|
|||
// helper function to extract shadowmap data from the ShadowData struct |
|||
void unpackShadowmapId( uint shadowmapId, out uint texIdx, out uint sampIdx, out float slice ) |
|||
{ |
|||
texIdx = (shadowmapId >> 24) & 0xff; |
|||
sampIdx = (shadowmapId >> 16) & 0xff; |
|||
slice = (float)(shadowmapId & 0xffff); |
|||
} |
|||
void unpackShadowmapId( uint shadowmapId, out uint texIdx, out uint sampIdx ) |
|||
{ |
|||
texIdx = (shadowmapId >> 24) & 0xff; |
|||
sampIdx = (shadowmapId >> 16) & 0xff; |
|||
} |
|||
void unpackShadowmapId( uint shadowmapId, out float slice ) |
|||
{ |
|||
slice = (float)(shadowmapId & 0xffff); |
|||
} |
|||
|
|||
|
|||
|
|||
// shadow sampling prototypes |
|||
float GetPunctualShadowAttenuation( ShadowContext shadowContext, float3 positionWS, int shadowDataIndex, float3 L ); |
|||
float GetPunctualShadowAttenuation( ShadowContext shadowContext, float3 positionWS, int shadowDataIndex, float3 L, float2 unPositionSS ); |
|||
// shadow sampling prototypes with screenspace info |
|||
float GetDirectionalShadowAttenuation( ShadowContext shadowContext, float3 positionWS, int shadowDataIndex, float3 L ); |
|||
float GetDirectionalShadowAttenuation( ShadowContext shadowContext, float3 positionWS, int shadowDataIndex, float3 L, float2 unPositionSS ); |
|||
|
|||
|
|||
// wedge in the actual shadow sampling algorithms |
|||
#include "ShadowSampling.hlsl" // sampling patterns |
|||
#include "ShadowAlgorithms.hlsl" // engine default algorithms (don't modify) |
|||
#include "ShadowAlgorithmsCustom.hlsl" // project specific custom algorithms (project can modify this) |
|||
|
|||
|
|||
// default dispatchers for the individual shadow types (with and without screenspace support) |
|||
// point/spot light shadows |
|||
float GetPunctualShadowAttenuationDefault( ShadowContext shadowContext, float3 positionWS, int shadowDataIndex, float3 L ) |
|||
{ |
|||
return EvalShadow_PunctualDepth(shadowContext, positionWS, shadowDataIndex, L); |
|||
} |
|||
float GetPunctualShadowAttenuationDefault( ShadowContext shadowContext, float3 positionWS, int shadowDataIndex, float3 L, float2 unPositionSS ) |
|||
{ |
|||
return GetPunctualShadowAttenuationDefault( shadowContext, positionWS, shadowDataIndex, L ); |
|||
} |
|||
// directional light shadows |
|||
float GetDirectionalShadowAttenuationDefault( ShadowContext shadowContext, float3 positionWS, int shadowDataIndex, float3 L ) |
|||
{ |
|||
return EvalShadow_CascadedDepth( shadowContext, positionWS, shadowDataIndex, L ); |
|||
} |
|||
float GetDirectionalShadowAttenuationDefault( ShadowContext shadowContext, float3 positionWS, int shadowDataIndex, float3 L, float2 unPositionSS ) |
|||
{ |
|||
return GetDirectionalShadowAttenuationDefault( shadowContext, positionWS, shadowDataIndex, L ); |
|||
} |
|||
|
|||
// include project specific shadow dispatcher. If this file is not empty, it MUST define which default shadows it's overriding |
|||
#include "ShadowDispatch.hlsl" |
|||
|
|||
// if shadow dispatch is empty we'll fall back to default shadow sampling implementations |
|||
#ifndef SHADOW_DISPATCH_USE_CUSTOM_PUNCTUAL |
|||
float GetPunctualShadowAttenuation( ShadowContext shadowContext, float3 positionWS, int shadowDataIndex, float3 L ) |
|||
{ |
|||
return GetPunctualShadowAttenuationDefault( shadowContext, positionWS, shadowDataIndex, L ); |
|||
} |
|||
float GetPunctualShadowAttenuation( ShadowContext shadowContext, float3 positionWS, int shadowDataIndex, float3 L, float2 unPositionSS ) |
|||
{ |
|||
return GetPunctualShadowAttenuationDefault( shadowContext, positionWS, shadowDataIndex, L, unPositionSS ); |
|||
} |
|||
#endif |
|||
#ifndef SHADOW_DISPATCH_USE_CUSTOM_DIRECTIONAL |
|||
float GetDirectionalShadowAttenuation( ShadowContext shadowContext, float3 positionWS, int shadowDataIndex, float3 L ) |
|||
{ |
|||
return GetDirectionalShadowAttenuationDefault( shadowContext, positionWS, shadowDataIndex, L ); |
|||
} |
|||
float GetDirectionalShadowAttenuation( ShadowContext shadowContext, float3 positionWS, int shadowDataIndex, float3 L, float2 unPositionSS ) |
|||
{ |
|||
return GetDirectionalShadowAttenuationDefault( shadowContext, positionWS, shadowDataIndex, L, unPositionSS ); |
|||
} |
|||
#endif |
|||
#undef ShadowData // TODO: Remove this once we've moved over to the new system. Also delete the define at the top again. |
|||
|
|||
#endif // SHADOWS_USE_SHADOWCTXT |
|||
|
|||
#endif // SHADOW_HLSL |
|
|||
using UnityEngine.Rendering; |
|||
using System; |
|||
|
|||
|
|||
namespace UnityEngine.Experimental.Rendering.HDPipeline |
|||
{ |
|||
// temporary namespace
|
|||
namespace ShadowExp |
|||
{ |
|||
|
|||
using ShadowRequestVector = VectorArray<ShadowmapBase.ShadowRequest>; |
|||
using ShadowDataVector = VectorArray<ShadowData>; |
|||
using ShadowPayloadVector = VectorArray<ShadowPayload>; |
|||
using ShadowIndicesVector = VectorArray<int>; |
|||
|
|||
// Standard shadow map atlas implementation using one large shadow map
|
|||
public class ShadowAtlas : ShadowmapBase, IDisposable |
|||
{ |
|||
public const uint k_MaxCascadesInShader = 4; |
|||
|
|||
protected readonly RenderTexture m_Shadowmap; |
|||
protected readonly RenderTargetIdentifier m_ShadowmapId; |
|||
protected readonly int m_TempDepthId; |
|||
protected VectorArray<CachedEntry> m_EntryCache = new VectorArray<CachedEntry>( 0, true ); |
|||
protected uint m_ActiveEntriesCount; |
|||
protected FrameId m_FrameId; |
|||
protected string m_ShaderKeyword; |
|||
protected int m_CascadeCount; |
|||
protected Vector3 m_CascadeRatios; |
|||
protected uint m_TexSlot; |
|||
protected uint m_SampSlot; |
|||
protected uint[] m_TmpWidths = new uint[ShadowmapBase.ShadowRequest.k_MaxFaceCount]; |
|||
protected uint[] m_TmpHeights = new uint[ShadowmapBase.ShadowRequest.k_MaxFaceCount]; |
|||
protected Vector4[] m_TmpSplits = new Vector4[k_MaxCascadesInShader]; |
|||
|
|||
protected struct Key |
|||
{ |
|||
public int id; |
|||
public uint faceIdx; |
|||
public int visibleIdx; |
|||
public uint shadowDataIdx; |
|||
} |
|||
|
|||
protected struct Data |
|||
{ |
|||
public FrameId frameId; |
|||
public int contentHash; |
|||
public uint slice; |
|||
public Rect viewport; |
|||
public Matrix4x4 view; |
|||
public Matrix4x4 proj; |
|||
public Vector4 lightDir; |
|||
public ShadowSplitData splitData; |
|||
|
|||
public bool IsValid() { return viewport.width > 0 && viewport.height > 0; } |
|||
} |
|||
protected struct CachedEntry : IComparable<CachedEntry> |
|||
{ |
|||
public Key key; |
|||
public Data current; |
|||
public Data previous; |
|||
|
|||
public int CompareTo( CachedEntry other ) |
|||
{ |
|||
if (current.viewport.height != other.current.viewport.height) |
|||
return current.viewport.height > other.current.viewport.height ? -1 : 1; |
|||
if (current.viewport.width != other.current.viewport.width) |
|||
return current.viewport.width > other.current.viewport.width ? -1 : 1; |
|||
if( key.id != other.key.id ) |
|||
return key.id < other.key.id ? -1 : 1; |
|||
|
|||
return key.faceIdx != other.key.faceIdx ? (key.faceIdx < other.key.faceIdx ? -1 : 1) : 0; |
|||
} |
|||
}; |
|||
|
|||
public struct AtlasInit |
|||
{ |
|||
public BaseInit baseInit; // the base class's initializer
|
|||
public string shaderKeyword; // the global shader keyword to use when rendering the shadowmap
|
|||
public int cascadeCount; // the number of cascades to use (these are global in ShadowSettings for now for some reason)
|
|||
public Vector3 cascadeRatios; // cascade split ratios
|
|||
} |
|||
|
|||
|
|||
public ShadowAtlas( ref AtlasInit init ) : base( ref init.baseInit ) |
|||
{ |
|||
m_Shadowmap = new RenderTexture( (int) m_Width, (int) m_Height, (int) m_ShadowmapBits, m_ShadowmapFormat, RenderTextureReadWrite.Linear ); |
|||
m_Shadowmap.dimension = TextureDimension.Tex2DArray; |
|||
m_Shadowmap.volumeDepth = (int) m_Slices; |
|||
m_ShadowmapId = new RenderTargetIdentifier( m_Shadowmap ); |
|||
|
|||
if( !IsNativeDepth() ) |
|||
{ |
|||
m_TempDepthId = Shader.PropertyToID( "Temporary Shadowmap Depth" ); |
|||
} |
|||
|
|||
Initialize( init ); |
|||
} |
|||
|
|||
public void Initialize( AtlasInit init ) |
|||
{ |
|||
m_ShaderKeyword = init.shaderKeyword; |
|||
m_CascadeCount = init.cascadeCount; |
|||
m_CascadeRatios = init.cascadeRatios; |
|||
} |
|||
|
|||
override public void ReserveSlots( ShadowContextStorage sc ) |
|||
{ |
|||
m_TexSlot = sc.RequestTex2DArraySlot(); |
|||
m_SampSlot = IsNativeDepth() ? sc.RequestSamplerSlot( m_CompSamplerState ) : sc.RequestSamplerSlot( m_SamplerState ); |
|||
} |
|||
|
|||
override public void Fill( ShadowContextStorage cs ) |
|||
{ |
|||
cs.SetTex2DArraySlot( m_TexSlot, m_ShadowmapId ); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
// TODO: clean up resources if necessary
|
|||
} |
|||
|
|||
override public bool Reserve( FrameId frameId, ref ShadowData shadowData, ShadowRequest sr, uint width, uint height, ref VectorArray<ShadowData> entries, ref VectorArray<ShadowPayload> payload, VisibleLight[] lights ) |
|||
{ |
|||
for( uint i = 0, cnt = sr.facecount; i < cnt; ++i ) |
|||
{ |
|||
m_TmpWidths[i] = width; |
|||
m_TmpHeights[i] = height; |
|||
} |
|||
return Reserve( frameId, ref shadowData, sr, m_TmpWidths, m_TmpHeights, ref entries, ref payload, lights ); |
|||
} |
|||
|
|||
override public bool Reserve( FrameId frameId, ref ShadowData shadowData, ShadowRequest sr, uint[] widths, uint[] heights, ref VectorArray<ShadowData> entries, ref VectorArray<ShadowPayload> payload, VisibleLight[] lights ) |
|||
{ |
|||
ShadowData sd = shadowData; |
|||
ShadowData dummy = new ShadowData(); |
|||
|
|||
if( sr.shadowType != GPUShadowType.Point && sr.shadowType != GPUShadowType.Spot && sr.shadowType != GPUShadowType.Directional ) |
|||
return false; |
|||
|
|||
if( sr.shadowType == GPUShadowType.Directional ) |
|||
{ |
|||
for( uint i = 0; i < k_MaxCascadesInShader; ++i ) |
|||
m_TmpSplits[i].Set( 0.0f, 0.0f, 0.0f, float.NegativeInfinity ); |
|||
} |
|||
|
|||
Key key; |
|||
key.id = sr.instanceId; |
|||
key.faceIdx = 0; |
|||
key.visibleIdx = (int)sr.index; |
|||
key.shadowDataIdx = entries.Count(); |
|||
|
|||
uint originalEntryCount = entries.Count(); |
|||
uint originalPayloadCount = payload.Count(); |
|||
uint originalActiveEntries = m_ActiveEntriesCount; |
|||
|
|||
uint facecnt = sr.facecount; |
|||
uint facemask = sr.facemask; |
|||
uint bit = 1; |
|||
int resIdx = 0; |
|||
|
|||
entries.Reserve( 6 ); |
|||
|
|||
float nearPlaneOffset = QualitySettings.shadowNearPlaneOffset; |
|||
|
|||
while ( facecnt > 0 ) |
|||
{ |
|||
if( (bit & facemask) != 0 ) |
|||
{ |
|||
uint width = widths[resIdx]; |
|||
uint height = heights[resIdx]; |
|||
uint ceIdx; |
|||
if( !Alloc( frameId, key, width, height, out ceIdx, payload ) ) |
|||
{ |
|||
entries.Purge( entries.Count() - originalEntryCount ); |
|||
payload.Purge( payload.Count() - originalPayloadCount ); |
|||
uint added = m_ActiveEntriesCount - originalActiveEntries; |
|||
for( uint i = originalActiveEntries; i < m_ActiveEntriesCount; ++i ) |
|||
m_EntryCache.Swap( i, m_EntryCache.Count()-i-1 ); |
|||
m_EntryCache.Purge( added, Free ); |
|||
m_ActiveEntriesCount = originalActiveEntries; |
|||
return false; |
|||
} |
|||
|
|||
// read
|
|||
CachedEntry ce = m_EntryCache[ceIdx]; |
|||
// modify
|
|||
Matrix4x4 vp; |
|||
if( sr.shadowType == GPUShadowType.Point ) |
|||
vp = ShadowUtils.ExtractPointLightMatrix( lights[sr.index], key.faceIdx, 2.0f, out ce.current.view, out ce.current.proj, out ce.current.lightDir, out ce.current.splitData, m_CullResults, (int) sr.index ); |
|||
else if( sr.shadowType == GPUShadowType.Spot ) |
|||
vp = ShadowUtils.ExtractSpotLightMatrix( lights[sr.index], out ce.current.view, out ce.current.proj, out ce.current.lightDir, out ce.current.splitData ); |
|||
else if( sr.shadowType == GPUShadowType.Directional ) |
|||
{ |
|||
vp = ShadowUtils.ExtractDirectionalLightMatrix( lights[sr.index], key.faceIdx, m_CascadeCount, m_CascadeRatios, nearPlaneOffset, width, height, out ce.current.view, out ce.current.proj, out ce.current.lightDir, out ce.current.splitData, m_CullResults, (int) sr.index ); |
|||
m_TmpSplits[key.faceIdx] = ce.current.splitData.cullingSphere; |
|||
m_TmpSplits[key.faceIdx].w *= ce.current.splitData.cullingSphere.w; |
|||
} |
|||
else |
|||
vp = Matrix4x4.identity; // should never happen, though
|
|||
// write :(
|
|||
m_EntryCache[ceIdx] = ce; |
|||
|
|||
sd.worldToShadow = vp.transpose; // apparently we need to transpose matrices that are sent to HLSL
|
|||
sd.scaleOffset = new Vector4( ce.current.viewport.width * m_WidthRcp, ce.current.viewport.height * m_HeightRcp, ce.current.viewport.x, ce.current.viewport.y ); |
|||
sd.texelSizeRcp = new Vector2( m_WidthRcp, m_HeightRcp ); |
|||
sd.PackShadowmapId( m_TexSlot, m_SampSlot, ce.current.slice ); |
|||
sd.shadowType = sr.shadowType; |
|||
sd.payloadOffset = payload.Count(); |
|||
entries.AddUnchecked(sd); |
|||
|
|||
resIdx++; |
|||
facecnt--; |
|||
key.shadowDataIdx++; |
|||
} |
|||
else |
|||
{ |
|||
// we push a dummy face in, otherwise we'd need a mapping from face index to shadowData in the shader as well
|
|||
entries.AddUnchecked( dummy ); |
|||
} |
|||
key.faceIdx++; |
|||
bit <<= 1; |
|||
} |
|||
|
|||
if (sr.shadowType == GPUShadowType.Directional) |
|||
{ |
|||
ShadowPayload sp = new ShadowPayload(); |
|||
payload.Reserve( k_MaxCascadesInShader ); |
|||
for( uint i = 0; i < k_MaxCascadesInShader; i++ ) |
|||
{ |
|||
sp.Set( m_TmpSplits[i] ); |
|||
payload.AddUnchecked( sp ); |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
override public bool ReserveFinalize( FrameId frameId, ref VectorArray<ShadowData> entries, ref VectorArray<ShadowPayload> payload ) |
|||
{ |
|||
if( Layout() ) |
|||
{ |
|||
// patch up the shadow data contents with the result of the layouting step
|
|||
for( uint i = 0; i < m_ActiveEntriesCount; ++i ) |
|||
{ |
|||
CachedEntry ce = m_EntryCache[i]; |
|||
|
|||
ShadowData sd = entries[ce.key.shadowDataIdx]; |
|||
// update the shadow data with the actual result of the layouting step
|
|||
sd.scaleOffset = new Vector4( ce.current.viewport.width * m_WidthRcp, ce.current.viewport.height * m_HeightRcp, ce.current.viewport.x * m_WidthRcp, ce.current.viewport.y * m_HeightRcp ); |
|||
sd.PackShadowmapId( m_TexSlot, m_SampSlot, ce.current.slice ); |
|||
// write back the correct results
|
|||
entries[ce.key.shadowDataIdx] = sd; |
|||
} |
|||
m_EntryCache.Purge(m_EntryCache.Count() - m_ActiveEntriesCount, (CachedEntry entry) => { Free(entry); }); |
|||
return true; |
|||
} |
|||
m_ActiveEntriesCount = 0; |
|||
m_EntryCache.Reset( (CachedEntry entry) => { Free(entry); }); |
|||
return false; |
|||
} |
|||
|
|||
virtual protected void PreUpdate( FrameId frameId, CommandBuffer cb, uint rendertargetSlice ) |
|||
{ |
|||
cb.SetRenderTarget( m_ShadowmapId, 0, (CubemapFace) 0, (int) rendertargetSlice ); |
|||
if( !IsNativeDepth() ) |
|||
{ |
|||
cb.GetTemporaryRT(m_TempDepthId, (int)m_Width, (int)m_Height, (int)m_ShadowmapBits, FilterMode.Bilinear, RenderTextureFormat.Shadowmap, RenderTextureReadWrite.Default); |
|||
cb.SetRenderTarget( new RenderTargetIdentifier( m_TempDepthId ) ); |
|||
} |
|||
cb.ClearRenderTarget( true, !IsNativeDepth(), m_ClearColor ); |
|||
} |
|||
|
|||
override public void Update( FrameId frameId, ScriptableRenderContext renderContext, CullResults cullResults, VisibleLight[] lights ) |
|||
{ |
|||
var profilingSample = new Utilities.ProfilingSample("Shadowmap" + m_TexSlot, renderContext); |
|||
|
|||
if (!string.IsNullOrEmpty( m_ShaderKeyword ) ) |
|||
{ |
|||
var cb = new CommandBuffer(); |
|||
cb.name = "Shadowmap.EnableShadowKeyword"; |
|||
cb.EnableShaderKeyword(m_ShaderKeyword); |
|||
renderContext.ExecuteCommandBuffer( cb ); |
|||
cb.Dispose(); |
|||
} |
|||
|
|||
// loop for generating each individual shadowmap
|
|||
uint curSlice = uint.MaxValue; |
|||
Bounds bounds; |
|||
DrawShadowsSettings dss = new DrawShadowsSettings( cullResults, 0 ); |
|||
for( uint i = 0; i < m_ActiveEntriesCount; ++i ) |
|||
{ |
|||
if( !cullResults.GetShadowCasterBounds( m_EntryCache[i].key.visibleIdx, out bounds ) ) |
|||
continue; |
|||
|
|||
var cb = new CommandBuffer(); |
|||
uint entrySlice = m_EntryCache[i].current.slice; |
|||
if( entrySlice != curSlice ) |
|||
{ |
|||
Debug.Assert( curSlice == uint.MaxValue || entrySlice >= curSlice, "Entries in the entry cache are not ordered in slice order." ); |
|||
cb.name = "Shadowmap.Update.Slice" + entrySlice; |
|||
|
|||
if( curSlice != uint.MaxValue ) |
|||
{ |
|||
PostUpdate( frameId, cb, curSlice ); |
|||
} |
|||
curSlice = entrySlice; |
|||
PreUpdate( frameId, cb, curSlice ); |
|||
} |
|||
|
|||
cb.name = "Shadowmap.Update - slice: " + curSlice + ", vp.x: " + m_EntryCache[i].current.viewport.x + ", vp.y: " + m_EntryCache[i].current.viewport.y + ", vp.w: " + m_EntryCache[i].current.viewport.width + ", vp.h: " + m_EntryCache[i].current.viewport.height; |
|||
cb.SetViewport( m_EntryCache[i].current.viewport ); |
|||
cb.SetViewProjectionMatrices( m_EntryCache[i].current.view, m_EntryCache[i].current.proj ); |
|||
cb.SetGlobalVector( "g_vLightDirWs", m_EntryCache[i].current.lightDir ); |
|||
renderContext.ExecuteCommandBuffer( cb ); |
|||
cb.Dispose(); |
|||
|
|||
dss.lightIndex = m_EntryCache[i].key.visibleIdx; |
|||
dss.splitData = m_EntryCache[i].current.splitData; |
|||
renderContext.DrawShadows( ref dss ); // <- if this was a call on the commandbuffer we would get away with using just once commandbuffer for the entire shadowmap, instead of one per face
|
|||
} |
|||
|
|||
// post update
|
|||
if( !string.IsNullOrEmpty( m_ShaderKeyword ) ) |
|||
{ |
|||
var cb = new CommandBuffer(); |
|||
cb.name = "Shadowmap.DisableShaderKeyword"; |
|||
cb.DisableShaderKeyword( m_ShaderKeyword ); |
|||
renderContext.ExecuteCommandBuffer( cb ); |
|||
cb.Dispose(); |
|||
} |
|||
|
|||
m_ActiveEntriesCount = 0; |
|||
|
|||
profilingSample.Dispose(); |
|||
} |
|||
|
|||
virtual protected void PostUpdate( FrameId frameId, CommandBuffer cb, uint rendertargetSlice ) |
|||
{ |
|||
if( !IsNativeDepth() ) |
|||
cb.ReleaseTemporaryRT( m_TempDepthId ); |
|||
} |
|||
|
|||
protected bool Alloc( FrameId frameId, Key key, uint width, uint height, out uint cachedEntryIdx, VectorArray<ShadowPayload> payload ) |
|||
{ |
|||
CachedEntry ce = new CachedEntry(); |
|||
ce.key = key; |
|||
ce.current.frameId = frameId; |
|||
ce.current.contentHash = -1; |
|||
ce.current.slice = 0; |
|||
ce.current.viewport = new Rect( 0, 0, width, height ); |
|||
|
|||
uint idx; |
|||
if ( m_EntryCache.FindFirst( out idx, ref key, (ref Key k, ref CachedEntry entry) => { return k.id == entry.key.id && k.faceIdx == entry.key.faceIdx; } ) ) |
|||
{ |
|||
if( m_EntryCache[idx].current.viewport.width == width && m_EntryCache[idx].current.viewport.height == height ) |
|||
{ |
|||
ce.previous = m_EntryCache[idx].current; |
|||
m_EntryCache[idx] = ce; |
|||
cachedEntryIdx = m_ActiveEntriesCount; |
|||
m_EntryCache.SwapUnchecked( m_ActiveEntriesCount++, idx ); |
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
m_EntryCache.SwapUnchecked( idx, m_EntryCache.Count()-1 ); |
|||
m_EntryCache.Purge( 1, Free ); |
|||
} |
|||
} |
|||
|
|||
idx = m_EntryCache.Count(); |
|||
m_EntryCache.Add( ce ); |
|||
cachedEntryIdx = m_ActiveEntriesCount; |
|||
m_EntryCache.SwapUnchecked( m_ActiveEntriesCount++, idx ); |
|||
return true; |
|||
} |
|||
|
|||
protected bool Layout() |
|||
{ |
|||
VectorArray<CachedEntry> tmp = m_EntryCache.Subrange( 0, m_ActiveEntriesCount ); |
|||
tmp.Sort(); |
|||
|
|||
float curx = 0, cury = 0, curh = 0, xmax = m_Width, ymax = m_Height; |
|||
uint curslice = 0; |
|||
|
|||
for (uint i = 0; i < m_ActiveEntriesCount; ++i) |
|||
{ |
|||
// shadow atlas layouting
|
|||
CachedEntry ce = m_EntryCache[i]; |
|||
Rect vp = ce.current.viewport; |
|||
|
|||
if( curx + vp.width > xmax ) |
|||
{ |
|||
curx = 0; |
|||
cury += curh; |
|||
} |
|||
if( curx + vp.width > xmax || cury + curh > ymax ) |
|||
{ |
|||
curslice++; |
|||
curx = 0; |
|||
cury = 0; |
|||
} |
|||
if( curx + vp.width > xmax || cury + curh > ymax || curslice == m_Slices ) |
|||
{ |
|||
Debug.LogError( "ERROR! Shadow atlasing failed." ); |
|||
return false; |
|||
} |
|||
vp.x = curx; |
|||
vp.y = cury; |
|||
ce.current.viewport = vp; |
|||
ce.current.slice = curslice; |
|||
m_EntryCache[i] = ce; |
|||
curx += vp.width; |
|||
curh = curh >= vp.height ? curh : vp.height; |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
protected void Free( CachedEntry ce ) |
|||
{ |
|||
// Nothing to do for this implementation here, as the atlas is reconstructed each frame, instead of keeping state across frames
|
|||
} |
|||
} |
|||
|
|||
// -------------------------------------------------------------------------------------------------------------------------------------------------
|
|||
//
|
|||
// ShadowManager
|
|||
//
|
|||
// -------------------------------------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
|
|||
// Standard shadow manager
|
|||
public class ShadowManager : ShadowManagerBase |
|||
{ |
|||
protected class ShadowContextAccess : ShadowContext |
|||
{ |
|||
public ShadowContextAccess( ref ShadowContext.CtxtInit initializer ) : base( ref initializer ) { } |
|||
// unfortunately ref returns are only a C# 7.0 feature
|
|||
public VectorArray<ShadowData> shadowDatas { get { return m_ShadowDatas; } set { m_ShadowDatas = value; } } |
|||
public VectorArray<ShadowPayload> payloads { get { return m_Payloads; } set { m_Payloads = value; } } |
|||
} |
|||
|
|||
private const int k_MaxShadowmapPerType = 4; |
|||
private ShadowSettings m_ShadowSettings; |
|||
private ShadowmapBase[] m_Shadowmaps; |
|||
private ShadowmapBase[,] m_ShadowmapsPerType = new ShadowmapBase[(int)GPUShadowType.MAX, k_MaxShadowmapPerType]; |
|||
private ShadowContextAccess m_ShadowCtxt; |
|||
private int[,] m_MaxShadows = new int[(int)GPUShadowType.MAX,2]; |
|||
// The following vectors are just temporary helpers to avoid reallocation each frame. Contents are not stable.
|
|||
private VectorArray<long> m_TmpSortKeys = new VectorArray<long>( 0, false ); |
|||
private ShadowRequestVector m_TmpRequests = new ShadowRequestVector( 0, false ); |
|||
// The following vector holds data that are returned to the caller so it can be sent to GPU memory in some form. Contents are stable in between calls to ProcessShadowRequests.
|
|||
private ShadowIndicesVector m_ShadowIndices = new ShadowIndicesVector( 0, false ); |
|||
|
|||
public ShadowManager( ShadowSettings shadowSettings, ref ShadowContext.CtxtInit ctxtInitializer, ShadowmapBase[] shadowmaps ) |
|||
{ |
|||
m_ShadowSettings = shadowSettings; |
|||
m_ShadowCtxt = new ShadowContextAccess( ref ctxtInitializer ); |
|||
|
|||
Debug.Assert( shadowmaps != null && shadowmaps.Length > 0 ); |
|||
m_Shadowmaps = shadowmaps; |
|||
foreach( var sm in shadowmaps ) |
|||
{ |
|||
sm.ReserveSlots( m_ShadowCtxt ); |
|||
ShadowmapBase.ShadowSupport smsupport = sm.QueryShadowSupport(); |
|||
for( int i = 0, bit = 1; i < (int) GPUShadowType.MAX; ++i, bit <<= 1 ) |
|||
{ |
|||
if( ((int)smsupport & bit) == 0 ) |
|||
continue; |
|||
|
|||
for( int idx = 0; i < k_MaxShadowmapPerType; ++idx ) |
|||
{ |
|||
if( m_ShadowmapsPerType[i,idx] == null ) |
|||
{ |
|||
m_ShadowmapsPerType[i,idx] = sm; |
|||
break; |
|||
} |
|||
} |
|||
Debug.Assert( m_ShadowmapsPerType[i,k_MaxShadowmapPerType-1] == null || m_ShadowmapsPerType[i,k_MaxShadowmapPerType-1] == sm, |
|||
"Only up to " + k_MaxShadowmapPerType + " are allowed per light type. If more are needed then increase ShadowManager.k_MaxShadowmapPerType" ); |
|||
} |
|||
} |
|||
|
|||
m_MaxShadows[(int)GPUShadowType.Point ,0] = m_MaxShadows[(int)GPUShadowType.Point ,1] = 4; |
|||
m_MaxShadows[(int)GPUShadowType.Spot ,0] = m_MaxShadows[(int)GPUShadowType.Spot ,1] = 8; |
|||
m_MaxShadows[(int)GPUShadowType.Directional,0] = m_MaxShadows[(int)GPUShadowType.Directional ,1] = 1; |
|||
} |
|||
|
|||
public override void ProcessShadowRequests( FrameId frameId, CullResults cullResults, Camera camera, VisibleLight[] lights, ref uint shadowRequestsCount, int[] shadowRequests, out int[] shadowDataIndices ) |
|||
{ |
|||
shadowDataIndices = null; |
|||
|
|||
// TODO:
|
|||
// Cached the cullResults here so we don't need to pass them around.
|
|||
// Allocate needs to pass them to the shadowmaps, as the ShadowUtil functions calculating view/proj matrices need them to call into C++ land.
|
|||
// Ideally we can get rid of that at some point, then we wouldn't need to cache them here, anymore.
|
|||
foreach( var sm in m_Shadowmaps ) |
|||
{ |
|||
sm.Assign( cullResults ); |
|||
} |
|||
|
|||
if( shadowRequestsCount == 0 || lights == null || shadowRequests == null ) |
|||
{ |
|||
shadowRequestsCount = 0; |
|||
return; |
|||
} |
|||
|
|||
// first sort the shadow casters according to some priority
|
|||
PrioritizeShadowCasters( camera, lights, shadowRequestsCount, shadowRequests ); |
|||
|
|||
// next prune them based on some logic
|
|||
VectorArray<int> requestedShadows = new VectorArray<int>( shadowRequests, 0, shadowRequestsCount, false ); |
|||
m_TmpRequests.Reset( shadowRequestsCount ); |
|||
uint totalGranted; |
|||
PruneShadowCasters( camera, lights, ref requestedShadows, ref m_TmpRequests, out totalGranted ); |
|||
|
|||
// if there are no shadow casters at this point -> bail
|
|||
if( totalGranted == 0 ) |
|||
{ |
|||
shadowRequestsCount = 0; |
|||
return; |
|||
} |
|||
|
|||
// TODO: Now would be a good time to kick off the culling jobs for the granted requests - but there's no way to control that at the moment.
|
|||
|
|||
// finally go over the lights deemed shadow casters and try to fit them into the shadow map
|
|||
// shadowmap allocation must succeed at this point.
|
|||
m_ShadowCtxt.ClearData(); |
|||
ShadowDataVector shadowVector = m_ShadowCtxt.shadowDatas; |
|||
ShadowPayloadVector payloadVector = m_ShadowCtxt.payloads; |
|||
m_ShadowIndices.Reset( m_TmpRequests.Count() ); |
|||
AllocateShadows( frameId, lights, totalGranted, ref m_TmpRequests, ref m_ShadowIndices, ref shadowVector, ref payloadVector ); |
|||
Debug.Assert( m_TmpRequests.Count() == m_ShadowIndices.Count() ); |
|||
m_ShadowCtxt.shadowDatas = shadowVector; |
|||
m_ShadowCtxt.payloads = payloadVector; |
|||
|
|||
// and set the output parameters
|
|||
uint offset; |
|||
shadowDataIndices = m_ShadowIndices.AsArray( out offset, out shadowRequestsCount ); |
|||
} |
|||
|
|||
|
|||
protected override void PrioritizeShadowCasters( Camera camera, VisibleLight[] lights, uint shadowRequestsCount, int[] shadowRequests ) |
|||
{ |
|||
// this function simply looks at the projected area on the screen, ignoring all light types and shapes
|
|||
m_TmpSortKeys.Reset( shadowRequestsCount ); |
|||
|
|||
for( int i = 0; i < shadowRequestsCount; ++i ) |
|||
{ |
|||
int vlidx = shadowRequests[i]; |
|||
VisibleLight vl = lights[vlidx]; |
|||
Light l = vl.light; |
|||
|
|||
// use the screen rect as a measure of importance
|
|||
float area = vl.screenRect.width * vl.screenRect.height; |
|||
long val = ShadowUtils.Asint( area ); |
|||
val <<= 32; |
|||
val |= (long) vlidx; |
|||
m_TmpSortKeys.AddUnchecked( val ); |
|||
} |
|||
m_TmpSortKeys.Sort(); |
|||
m_TmpSortKeys.ExtractTo( shadowRequests, 0, out shadowRequestsCount, delegate(long key) { return (int) (key & 0xffffffff); } ); |
|||
} |
|||
|
|||
protected override void PruneShadowCasters( Camera camera, VisibleLight[] lights, ref VectorArray<int> shadowRequests, ref ShadowRequestVector requestsGranted, out uint totalRequestCount ) |
|||
{ |
|||
Debug.Assert( shadowRequests.Count() > 0 ); |
|||
// at this point the array is sorted in order of some importance determined by the prioritize function
|
|||
requestsGranted.Reserve( shadowRequests.Count() ); |
|||
totalRequestCount = 0; |
|||
|
|||
ShadowmapBase.ShadowRequest sreq = new ShadowmapBase.ShadowRequest(); |
|||
uint totalSlots = ResetMaxShadows(); |
|||
// there's a 1:1 mapping between the index in the shadowRequests array and the element in requestsGranted at the same index.
|
|||
// if the prune function skips requests it must make sure that the array is still compact
|
|||
m_TmpSortKeys.Reset( shadowRequests.Count() ); |
|||
for( uint i = 0, count = shadowRequests.Count(); i < count && totalSlots > 0; ++i ) |
|||
{ |
|||
int requestIdx = shadowRequests[i]; |
|||
VisibleLight vl = lights[requestIdx]; |
|||
bool add = false; |
|||
int facecount = 0; |
|||
GPUShadowType shadowType = GPUShadowType.Point; |
|||
|
|||
switch( vl.lightType ) |
|||
{ |
|||
case LightType.Directional : add = m_MaxShadows[(int)GPUShadowType.Directional , 0]-- >= 0; shadowType = GPUShadowType.Directional; facecount = m_ShadowSettings.directionalLightCascadeCount; break; |
|||
case LightType.Point : add = m_MaxShadows[(int)GPUShadowType.Point , 0]-- >= 0; shadowType = GPUShadowType.Point ; facecount = 6; break; |
|||
case LightType.Spot : add = m_MaxShadows[(int)GPUShadowType.Spot , 0]-- >= 0; shadowType = GPUShadowType.Spot ; facecount = 1; break; |
|||
} |
|||
|
|||
if( add ) |
|||
{ |
|||
sreq.instanceId = vl.light.GetInstanceID(); |
|||
sreq.index = (uint) requestIdx; |
|||
sreq.facemask = (uint) (1 << facecount) - 1; |
|||
sreq.shadowType = shadowType; |
|||
totalRequestCount += (uint) facecount; |
|||
requestsGranted.AddUnchecked( sreq ); |
|||
totalSlots--; |
|||
} |
|||
else |
|||
m_TmpSortKeys.AddUnchecked( requestIdx ); |
|||
} |
|||
// make sure that shadowRequests contains all light indices that are going to cast a shadow first, then the rest
|
|||
shadowRequests.Reset(); |
|||
requestsGranted.ExtractTo( ref shadowRequests, (ShadowmapBase.ShadowRequest request) => { return (int) request.index; } ); |
|||
m_TmpSortKeys.ExtractTo( ref shadowRequests, (long idx) => { return (int) idx; } ); |
|||
} |
|||
|
|||
protected override void AllocateShadows( FrameId frameId, VisibleLight[] lights, uint totalGranted, ref ShadowRequestVector grantedRequests, ref ShadowIndicesVector shadowIndices, ref ShadowDataVector shadowDatas, ref ShadowPayloadVector shadowmapPayload ) |
|||
{ |
|||
ShadowData sd = new ShadowData(); |
|||
shadowDatas.Reserve( totalGranted ); |
|||
shadowIndices.Reserve( grantedRequests.Count() ); |
|||
for( uint i = 0, cnt = grantedRequests.Count(); i < cnt; ++i ) |
|||
{ |
|||
VisibleLight vl = lights[grantedRequests[i].index]; |
|||
Light l = vl.light; |
|||
AdditionalLightData ald = l.GetComponent<AdditionalLightData>(); |
|||
|
|||
// set light specific values that are not related to the shadowmap
|
|||
GPUShadowType shadowtype; |
|||
ShadowUtils.MapLightType( ald.archetype, vl.lightType, out sd.lightType, out shadowtype ); |
|||
sd.bias = l.shadowBias; |
|||
sd.quality = 0; |
|||
|
|||
shadowIndices.AddUnchecked( (int) shadowDatas.Count() ); |
|||
|
|||
int smidx = 0; |
|||
while( smidx < k_MaxShadowmapPerType ) |
|||
{ |
|||
if( m_ShadowmapsPerType[(int)shadowtype,smidx].Reserve( frameId, ref sd, grantedRequests[i], (uint) ald.shadowResolution, (uint) ald.shadowResolution, ref shadowDatas, ref shadowmapPayload, lights ) ) |
|||
break; |
|||
smidx++; |
|||
} |
|||
if( smidx == k_MaxShadowmapPerType ) |
|||
throw new ArgumentException("The requested shadows do not fit into any shadowmap."); |
|||
} |
|||
|
|||
// final step for shadowmaps that only gather data during the previous loop and do the actual allocation once they have all the data.
|
|||
foreach( var sm in m_Shadowmaps ) |
|||
{ |
|||
if( !sm.ReserveFinalize( frameId, ref shadowDatas, ref shadowmapPayload ) ) |
|||
throw new ArgumentException( "Shadow allocation failed in the ReserveFinalize step." ); |
|||
} |
|||
} |
|||
|
|||
public override void RenderShadows( FrameId frameId, ScriptableRenderContext renderContext, CullResults cullResults, VisibleLight[] lights ) |
|||
{ |
|||
using (new Utilities.ProfilingSample("Render Shadows Exp", renderContext)) |
|||
{ |
|||
foreach( var sm in m_Shadowmaps ) |
|||
{ |
|||
sm.Update( frameId, renderContext, cullResults, lights ); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public override void SyncData() |
|||
{ |
|||
m_ShadowCtxt.SyncData(); |
|||
} |
|||
|
|||
public override void BindResources( ScriptableRenderContext renderContext ) |
|||
{ |
|||
foreach( var sm in m_Shadowmaps ) |
|||
{ |
|||
sm.Fill( m_ShadowCtxt ); |
|||
} |
|||
CommandBuffer cb = new CommandBuffer(); // <- can we just keep this around or does this have to be newed every frame?
|
|||
cb.name = "Bind resources to GPU"; |
|||
m_ShadowCtxt.BindResources( cb ); |
|||
renderContext.ExecuteCommandBuffer( cb ); |
|||
cb.Dispose(); |
|||
} |
|||
|
|||
// resets the shadow slot counters and returns the sum of all slots
|
|||
private uint ResetMaxShadows() |
|||
{ |
|||
int total = 0; |
|||
for( int i = 0; i < (int) GPUShadowType.MAX; ++i ) |
|||
{ |
|||
m_MaxShadows[i,0] = m_MaxShadows[i,1]; |
|||
total += m_MaxShadows[i,1]; |
|||
} |
|||
return total > 0 ? (uint) total : 0; |
|||
} |
|||
} |
|||
|
|||
} // end of temporary namespace ShadowExp
|
|||
} // end of namespace UnityEngine.Experimental.ScriptableRenderLoop
|
|
|||
fileFormatVersion: 2 |
|||
guid: 6931cfbd757a1864f8822b5399719960 |
|||
timeCreated: 1485511432 |
|||
licenseType: Pro |
|||
MonoImporter: |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
// Various shadow algorithms |
|||
// There are two variants provided, one takes the texture and sampler explicitly so they can be statically passed in. |
|||
// The variant without resource parameters dynamically accesses the texture when sampling. |
|||
|
|||
// function called by spot, point and directional eval routines to calculate shadow coordinates |
|||
float3 EvalShadow_GetTexcoords( ShadowData sd, float3 positionWS ) |
|||
{ |
|||
float4 posCS = mul( float4( positionWS, 1.0 ), sd.worldToShadow ); |
|||
// apply a bias |
|||
posCS.z -= sd.bias; |
|||
float3 posNDC = posCS.xyz / posCS.w; |
|||
// calc TCs |
|||
float3 posTC = posNDC * 0.5 + 0.5; |
|||
posTC.xy = posTC.xy * sd.scaleOffset.xy + sd.scaleOffset.zw; |
|||
#if UNITY_REVERSED_Z |
|||
posTC.z = 1.0 - posTC.z; |
|||
#endif |
|||
return posTC; |
|||
} |
|||
|
|||
// |
|||
// Point shadows |
|||
// |
|||
float EvalShadow_PointDepth( ShadowContext shadowContext, float3 positionWS, int index, float3 L ) |
|||
{ |
|||
// load the right shadow data for the current face |
|||
int faceIndex = 0; |
|||
GetCubeFaceID( L, faceIndex ); |
|||
ShadowData sd = shadowContext.shadowDatas[index + faceIndex]; |
|||
// get shadowmap texcoords |
|||
float3 posTC = EvalShadow_GetTexcoords( sd, positionWS ); |
|||
// sample the texture |
|||
uint texIdx, sampIdx; |
|||
float slice; |
|||
unpackShadowmapId( sd.id, texIdx, sampIdx, slice ); |
|||
return SampleShadow_PCF_1tap( shadowContext, posTC, slice, texIdx, sampIdx ); |
|||
} |
|||
|
|||
float EvalShadow_PointDepth( ShadowContext shadowContext, Texture2DArray tex, SamplerComparisonState compSamp, float3 positionWS, int index, float3 L ) |
|||
{ |
|||
// load the right shadow data for the current face |
|||
int faceIndex = 0; |
|||
GetCubeFaceID( L, faceIndex ); |
|||
ShadowData sd = shadowContext.shadowDatas[index + faceIndex]; |
|||
// get shadowmap texcoords |
|||
float3 posTC = EvalShadow_GetTexcoords( sd, positionWS ); |
|||
// sample the texture |
|||
float slice; |
|||
unpackShadowmapId( sd.id, slice ); |
|||
return SampleShadow_PCF_1tap( shadowContext, posTC, slice, tex, compSamp ); |
|||
} |
|||
|
|||
|
|||
// |
|||
// Spot shadows |
|||
// |
|||
float EvalShadow_SpotDepth( ShadowContext shadowContext, float3 positionWS, int index, float3 L ) |
|||
{ |
|||
// load the right shadow data for the current face |
|||
ShadowData sd = shadowContext.shadowDatas[index]; |
|||
// get shadowmap texcoords |
|||
float3 posTC = EvalShadow_GetTexcoords( sd, positionWS ); |
|||
// sample the texture |
|||
uint texIdx, sampIdx; |
|||
float slice; |
|||
unpackShadowmapId( sd.id, texIdx, sampIdx, slice ); |
|||
return SampleShadow_PCF_1tap( shadowContext, posTC, slice, texIdx, sampIdx ); |
|||
} |
|||
|
|||
float EvalShadow_SpotDepth( ShadowContext shadowContext, Texture2DArray tex, SamplerComparisonState compSamp, float3 positionWS, int index, float3 L ) |
|||
{ |
|||
// load the right shadow data for the current face |
|||
ShadowData sd = shadowContext.shadowDatas[index]; |
|||
// get shadowmap texcoords |
|||
float3 posTC = EvalShadow_GetTexcoords( sd, positionWS ); |
|||
// sample the texture |
|||
float slice; |
|||
unpackShadowmapId( sd.id, slice ); |
|||
return SampleShadow_PCF_1tap( shadowContext, posTC, slice, tex, compSamp ); |
|||
} |
|||
|
|||
// |
|||
// Punctual shadows for Point and Spot |
|||
// |
|||
float EvalShadow_PunctualDepth( ShadowContext shadowContext, float3 positionWS, int index, float3 L ) |
|||
{ |
|||
// load the right shadow data for the current face |
|||
int faceIndex = 0; |
|||
|
|||
[branch] |
|||
if( shadowContext.shadowDatas[index].shadowType == GPUSHADOWTYPE_POINT ) |
|||
GetCubeFaceID( L, faceIndex ); |
|||
|
|||
ShadowData sd = shadowContext.shadowDatas[index + faceIndex]; |
|||
// get shadowmap texcoords |
|||
float3 posTC = EvalShadow_GetTexcoords( sd, positionWS ); |
|||
// sample the texture |
|||
uint texIdx, sampIdx; |
|||
float slice; |
|||
unpackShadowmapId( sd.id, texIdx, sampIdx, slice ); |
|||
return SampleShadow_PCF_1tap( shadowContext, posTC, slice, texIdx, sampIdx ); |
|||
} |
|||
|
|||
float EvalShadow_PunctualDepth( ShadowContext shadowContext, Texture2DArray tex, SamplerComparisonState compSamp, float3 positionWS, int index, float3 L ) |
|||
{ |
|||
// load the right shadow data for the current face |
|||
int faceIndex = 0; |
|||
|
|||
[branch] |
|||
if( shadowContext.shadowDatas[index].shadowType == GPUSHADOWTYPE_POINT ) |
|||
GetCubeFaceID( L, faceIndex ); |
|||
|
|||
ShadowData sd = shadowContext.shadowDatas[index + faceIndex]; |
|||
// get shadowmap texcoords |
|||
float3 posTC = EvalShadow_GetTexcoords( sd, positionWS ); |
|||
// sample the texture |
|||
float slice; |
|||
unpackShadowmapId( sd.id, slice ); |
|||
return SampleShadow_PCF_1tap( shadowContext, posTC, slice, tex, compSamp ); |
|||
} |
|||
|
|||
// |
|||
// Directional shadows (cascaded shadow map) |
|||
// |
|||
uint EvalShadow_GetSplitSphereIndexForDirshadows( float3 positionWS, float4 dirShadowSplitSpheres[4] ) |
|||
{ |
|||
float3 fromCenter0 = positionWS.xyz - dirShadowSplitSpheres[0].xyz; |
|||
float3 fromCenter1 = positionWS.xyz - dirShadowSplitSpheres[1].xyz; |
|||
float3 fromCenter2 = positionWS.xyz - dirShadowSplitSpheres[2].xyz; |
|||
float3 fromCenter3 = positionWS.xyz - dirShadowSplitSpheres[3].xyz; |
|||
float4 distances2 = float4(dot(fromCenter0, fromCenter0), dot(fromCenter1, fromCenter1), dot(fromCenter2, fromCenter2), dot(fromCenter3, fromCenter3)); |
|||
|
|||
float4 dirShadowSplitSphereSqRadii; |
|||
dirShadowSplitSphereSqRadii.x = dirShadowSplitSpheres[0].w; |
|||
dirShadowSplitSphereSqRadii.y = dirShadowSplitSpheres[1].w; |
|||
dirShadowSplitSphereSqRadii.z = dirShadowSplitSpheres[2].w; |
|||
dirShadowSplitSphereSqRadii.w = dirShadowSplitSpheres[3].w; |
|||
|
|||
float4 weights = float4( distances2 < dirShadowSplitSphereSqRadii ); |
|||
weights.yzw = saturate( weights.yzw - weights.xyz ); |
|||
|
|||
return uint( 4.0 - dot( weights, float4(4.0, 3.0, 2.0, 1.0 ) ) ); |
|||
} |
|||
|
|||
void EvalShadow_LoadSplitSpheres( ShadowContext shadowContext, int index, out float4 splitSpheres[4] ) |
|||
{ |
|||
uint offset = GetPayloadOffset( shadowContext.shadowDatas[index] ); |
|||
|
|||
splitSpheres[0] = asfloat( shadowContext.payloads[offset + 0] ); |
|||
splitSpheres[1] = asfloat( shadowContext.payloads[offset + 1] ); |
|||
splitSpheres[2] = asfloat( shadowContext.payloads[offset + 2] ); |
|||
splitSpheres[3] = asfloat( shadowContext.payloads[offset + 3] ); |
|||
} |
|||
|
|||
float EvalShadow_CascadedDepth( ShadowContext shadowContext, float3 positionWS, int index, float3 L ) |
|||
{ |
|||
// load the right shadow data for the current face |
|||
float4 dirShadowSplitSpheres[4]; |
|||
EvalShadow_LoadSplitSpheres( shadowContext, index, dirShadowSplitSpheres ); |
|||
uint shadowSplitIndex = EvalShadow_GetSplitSphereIndexForDirshadows( positionWS, dirShadowSplitSpheres ); |
|||
ShadowData sd = shadowContext.shadowDatas[index + shadowSplitIndex]; |
|||
// get shadowmap texcoords |
|||
float3 posTC = EvalShadow_GetTexcoords( sd, positionWS ); |
|||
|
|||
// sample the texture |
|||
uint texIdx, sampIdx; |
|||
float slice; |
|||
unpackShadowmapId( sd.id, texIdx, sampIdx, slice ); |
|||
|
|||
return SampleShadow_PCF_9tap_Adaptive( shadowContext, sd.texelSizeRcp, posTC, slice, texIdx, sampIdx ); |
|||
} |
|||
|
|||
float EvalShadow_CascadedDepth( ShadowContext shadowContext, Texture2DArray tex, SamplerComparisonState compSamp, float3 positionWS, int index, float3 L ) |
|||
{ |
|||
// load the right shadow data for the current face |
|||
float4 dirShadowSplitSpheres[4]; |
|||
EvalShadow_LoadSplitSpheres( shadowContext, index, dirShadowSplitSpheres ); |
|||
uint shadowSplitIndex = EvalShadow_GetSplitSphereIndexForDirshadows( positionWS, dirShadowSplitSpheres ); |
|||
ShadowData sd = shadowContext.shadowDatas[index + shadowSplitIndex]; |
|||
// get shadowmap texcoords |
|||
float3 posTC = EvalShadow_GetTexcoords( sd, positionWS ); |
|||
// sample the texture |
|||
float slice; |
|||
unpackShadowmapId(sd.id, slice); |
|||
|
|||
return SampleShadow_PCF_9tap_Adaptive( shadowContext, sd.texelSizeRcp, posTC, slice, tex, compSamp ); |
|||
} |
|||
|
|
|||
fileFormatVersion: 2 |
|||
guid: 9900a5b8191991b4d9e2c37413d85dc3 |
|||
timeCreated: 1485511902 |
|||
licenseType: Pro |
|||
ShaderImporter: |
|||
defaultTextures: [] |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
// This file is empty on purpose. Projects can put their custom shadow algorithms in here so they get automatically included by Shadow.hlsl. |
|||
|
|||
float EvalShadow_CascadedMomentum( ShadowContext shadowContext, float3 positionWS, int shadowDataIndex, float3 L ) |
|||
{ |
|||
return 1.0; |
|||
} |
|||
|
|||
float EvalShadow_CascadedMomentum( ShadowContext shadowContext, float3 positionWS, int shadowDataIndex, float3 L, float2 unPositionSS ) |
|||
{ |
|||
return 1.0; |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 1163a3730c1812748b5cc5a7ecfbaf0c |
|||
timeCreated: 1485511901 |
|||
licenseType: Pro |
|||
ShaderImporter: |
|||
defaultTextures: [] |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using UnityEngine.Rendering; |
|||
using System; |
|||
|
|||
namespace UnityEngine.Experimental.Rendering.HDPipeline |
|||
{ |
|||
[GenerateHLSL] |
|||
public enum GPUShadowType |
|||
{ |
|||
Point, |
|||
Spot, |
|||
Directional, |
|||
MAX, |
|||
Unknown = MAX, |
|||
All = Point | Spot | Directional |
|||
}; |
|||
|
|||
|
|||
[GenerateHLSL] |
|||
public enum GPUShadowSampling |
|||
{ |
|||
PCF_1tap, |
|||
PCF_9Taps_Adaptive, |
|||
VSM_1tap, |
|||
MSM_1tap |
|||
}; |
|||
|
|||
namespace ShadowExp // temporary namespace until everything can be merged into the HDPipeline
|
|||
{ |
|||
|
|||
|
|||
// This is the struct passed into shaders
|
|||
[GenerateHLSL] |
|||
public struct ShadowData |
|||
{ |
|||
// shadow texture related params (need to be set by ShadowmapBase and derivatives)
|
|||
public Matrix4x4 worldToShadow; // to light space matrix
|
|||
public Vector4 scaleOffset; // scale and offset of shadowmap in atlas
|
|||
public Vector2 texelSizeRcp; // reciprocal of the shadowmap's texel size in x and y
|
|||
public uint id; // packed texture id, sampler id and slice idx
|
|||
public GPUShadowType shadowType; // determines the shadow algorithm, i.e. which map to sample and how to interpret the data
|
|||
public uint payloadOffset; // if this shadow type requires additional data it can be fetched from a global Buffer<uint> at payloadOffset.
|
|||
|
|||
// light related params (need to be set via ShadowMgr and derivatives)
|
|||
public GPULightType lightType; // the light type
|
|||
public float bias; // bias setting
|
|||
public float quality; // some quality parameters
|
|||
|
|||
public void PackShadowmapId( uint texIdx, uint sampIdx, uint slice ) |
|||
{ |
|||
Debug.Assert( texIdx <= 0xff ); |
|||
Debug.Assert( sampIdx <= 0xff ); |
|||
Debug.Assert( slice <= 0xffff ); |
|||
id = texIdx << 24 | sampIdx << 16 | slice; |
|||
} |
|||
}; |
|||
|
|||
public struct FrameId |
|||
{ |
|||
public int frameCount; |
|||
public float deltaT; |
|||
} |
|||
|
|||
// -------------- Begin temporary structs that need to be replaced at some point ---------------
|
|||
public struct SamplerState |
|||
{ |
|||
// TODO: this should either contain the description for a sampler, or be replaced by a struct that does
|
|||
public static bool operator ==( SamplerState lhs, SamplerState rhs ) { return false; } |
|||
public static bool operator !=( SamplerState lhs, SamplerState rhs ) { return true; } |
|||
public override bool Equals( object obj ) { return (obj is SamplerState) && (SamplerState) obj == this; } |
|||
public override int GetHashCode() { /* TODO: implement this at some point */ throw new NotImplementedException(); } |
|||
} |
|||
|
|||
public struct ComparisonSamplerState |
|||
{ |
|||
// TODO: this should either contain the description for a comparison sampler, or be replaced by a struct that does
|
|||
public static bool operator ==(ComparisonSamplerState lhs, ComparisonSamplerState rhs) { return false; } |
|||
public static bool operator !=(ComparisonSamplerState lhs, ComparisonSamplerState rhs) { return true; } |
|||
public override bool Equals( object obj ) { return (obj is ComparisonSamplerState) && (ComparisonSamplerState) obj == this; } |
|||
public override int GetHashCode() { /* TODO: implement this at some point */ throw new NotImplementedException(); } |
|||
} |
|||
// -------------- End temporary structs that need to be replaced at some point ---------------
|
|||
|
|||
public struct ShadowPayload |
|||
{ |
|||
public int p0; |
|||
public int p1; |
|||
public int p2; |
|||
public int p3; |
|||
|
|||
public void Set( float v0, float v1, float v2, float v3 ) |
|||
{ |
|||
p0 = ShadowUtils.Asint( v0 ); |
|||
p1 = ShadowUtils.Asint( v1 ); |
|||
p2 = ShadowUtils.Asint( v2 ); |
|||
p3 = ShadowUtils.Asint( v3 ); |
|||
} |
|||
public void Set( Vector4 v ) { Set( v.x, v.y, v.z, v.w ); } |
|||
} |
|||
|
|||
// Class holding resource information that needs to be synchronized with shaders.
|
|||
public class ShadowContextStorage |
|||
{ |
|||
public struct Init |
|||
{ |
|||
public uint maxShadowDataSlots; |
|||
public uint maxPayloadSlots; |
|||
public uint maxTex2DArraySlots; |
|||
public uint maxTexCubeArraySlots; |
|||
public uint maxComparisonSamplerSlots; |
|||
public uint maxSamplerSlots; |
|||
} |
|||
protected ShadowContextStorage( ref Init initializer ) |
|||
{ |
|||
m_ShadowDatas.Reserve( initializer.maxShadowDataSlots ); |
|||
m_Payloads.Reserve( initializer.maxPayloadSlots ); |
|||
m_Tex2DArray.Reserve( initializer.maxTex2DArraySlots ); |
|||
m_TexCubeArray.Reserve( initializer.maxTexCubeArraySlots ); |
|||
m_CompSamplers.Reserve( initializer.maxComparisonSamplerSlots ); |
|||
m_Samplers.Reserve( initializer.maxSamplerSlots ); |
|||
} |
|||
// query functions to be used by the shadowmap
|
|||
public uint RequestTex2DArraySlot() { return m_Tex2DArray.Add( new RenderTargetIdentifier() ); } |
|||
public uint RequestTexCubeArraySlot() { return m_TexCubeArray.Add( new RenderTargetIdentifier() ); } |
|||
public uint RequestSamplerSlot( SamplerState ss ) |
|||
{ |
|||
uint idx; |
|||
if( m_Samplers.FindFirst( out idx, ref ss ) ) |
|||
return idx; |
|||
idx = m_Samplers.Count(); |
|||
m_Samplers.Add( ss ); |
|||
return idx; |
|||
} |
|||
public uint RequestSamplerSlot( ComparisonSamplerState css ) |
|||
{ |
|||
uint idx; |
|||
if( m_CompSamplers.FindFirst( out idx, ref css ) ) |
|||
return idx; |
|||
idx = m_CompSamplers.Count(); |
|||
m_CompSamplers.Add( css ); |
|||
return idx; |
|||
} |
|||
// setters called each frame on the shadowmap
|
|||
public void SetTex2DArraySlot( uint slot, RenderTargetIdentifier val ) { m_Tex2DArray[slot] = val; } |
|||
public void SetTexCubeArraySlot( uint slot, RenderTargetIdentifier val ) { m_TexCubeArray[slot] = val; } |
|||
|
|||
protected VectorArray<ShadowData> m_ShadowDatas = new VectorArray<ShadowData>( 0, false ); |
|||
protected VectorArray<ShadowPayload> m_Payloads = new VectorArray<ShadowPayload>( 0, false ); |
|||
protected VectorArray<RenderTargetIdentifier> m_Tex2DArray = new VectorArray<RenderTargetIdentifier>( 0, true ); |
|||
protected VectorArray<RenderTargetIdentifier> m_TexCubeArray = new VectorArray<RenderTargetIdentifier>( 0, true ); |
|||
protected VectorArray<ComparisonSamplerState> m_CompSamplers = new VectorArray<ComparisonSamplerState>( 0, true ); |
|||
protected VectorArray<SamplerState> m_Samplers = new VectorArray<SamplerState>( 0, true ); |
|||
} |
|||
|
|||
// Class providing hooks to do the actual synchronization
|
|||
public class ShadowContext : ShadowContextStorage |
|||
{ |
|||
public delegate void SyncDel( ShadowContext sc ); |
|||
public delegate void BindDel( ShadowContext sc, CommandBuffer cb ); |
|||
public struct CtxtInit |
|||
{ |
|||
public Init storage; |
|||
public SyncDel dataSyncer; |
|||
public BindDel resourceBinder; |
|||
} |
|||
public ShadowContext( ref CtxtInit initializer ) : base( ref initializer.storage ) |
|||
{ |
|||
Debug.Assert( initializer.dataSyncer != null && initializer.resourceBinder != null ); |
|||
m_DataSyncerDel = initializer.dataSyncer; |
|||
m_ResourceBinderDel = initializer.resourceBinder; |
|||
} |
|||
public void ClearData() { m_ShadowDatas.Reset(); m_Payloads.Reset(); } |
|||
// delegate that takes care of syncing data to the GPU
|
|||
public void SyncData() { m_DataSyncerDel( this ); } |
|||
// delegate that takes care of binding textures, buffers and samplers to shaders just before rendering
|
|||
public void BindResources( CommandBuffer cb ) { m_ResourceBinderDel( this, cb ); } |
|||
|
|||
// the following functions are to be used by the bind and sync delegates
|
|||
public void GetShadowDatas( out ShadowData[] shadowDatas, out uint offset, out uint count ) { shadowDatas = m_ShadowDatas.AsArray( out offset, out count ); } |
|||
public void GetPayloads( out ShadowPayload[] payloads, out uint offset, out uint count ) { payloads = m_Payloads.AsArray( out offset, out count ); } |
|||
public void GetTex2DArrays( out RenderTargetIdentifier[] tex2DArrays, out uint offset, out uint count ) { tex2DArrays = m_Tex2DArray.AsArray( out offset, out count ); } |
|||
public void GetTexCubeArrays( out RenderTargetIdentifier[] texCubeArrays, out uint offset, out uint count ) { texCubeArrays = m_TexCubeArray.AsArray( out offset, out count ); } |
|||
public void GetComparisonSamplerArrays( out ComparisonSamplerState[] compSamplers, out uint offset, out uint count ) { compSamplers = m_CompSamplers.AsArray( out offset, out count ); } |
|||
public void GetSamplerArrays( out SamplerState[] samplerArrays, out uint offset, out uint count ) { samplerArrays = m_Samplers.AsArray( out offset, out count ); } |
|||
|
|||
private SyncDel m_DataSyncerDel; |
|||
private BindDel m_ResourceBinderDel; |
|||
} |
|||
|
|||
// Abstract base class for handling shadow maps.
|
|||
// Specific implementations managing atlases and the likes should inherit from this
|
|||
abstract public class ShadowmapBase |
|||
{ |
|||
[Flags] |
|||
public enum ShadowSupport |
|||
{ |
|||
Point = 1 << GPUShadowType.Point, |
|||
Spot = 1 << GPUShadowType.Spot, |
|||
Directional = 1 << GPUShadowType.Directional |
|||
} |
|||
public struct ShadowRequest |
|||
{ |
|||
private const byte k_IndexBits = 24; |
|||
private const byte k_FaceBits = 32 - k_IndexBits; |
|||
private const uint k_MaxIndex = (1 << k_IndexBits) - 1; |
|||
private const byte k_MaxFace = (1 << k_FaceBits) - 1; |
|||
public const int k_MaxFaceCount = k_FaceBits; |
|||
|
|||
// combined face mask and visible light index
|
|||
private uint m_MaskIndex; |
|||
// instance Id for this light
|
|||
public int instanceId { get; set; } |
|||
// shadow type of this light
|
|||
public GPUShadowType shadowType { get; set; } |
|||
// index into the visible lights array
|
|||
public uint index |
|||
{ |
|||
get { return m_MaskIndex & k_MaxIndex; } |
|||
set { m_MaskIndex = value & k_MaxIndex; } |
|||
} |
|||
// mask of which faces are requested:
|
|||
// - for spotlights the value is always 1
|
|||
// - for point lights the bit positions map to the faces as listed in the CubemapFace enum
|
|||
// - for directional lights the bit positions map to the individual cascades
|
|||
public uint facemask |
|||
{ |
|||
get { return (m_MaskIndex >> k_IndexBits) & k_MaxFace; } |
|||
set { m_MaskIndex = (m_MaskIndex & k_MaxIndex) | (value << k_IndexBits); } |
|||
} |
|||
public uint facecount |
|||
{ |
|||
get |
|||
{ |
|||
uint fc = facemask; |
|||
uint count = 0; |
|||
while (fc != 0) |
|||
{ |
|||
count += fc & 1; |
|||
fc >>= 1; |
|||
} |
|||
return count; |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
protected readonly uint m_Width; |
|||
protected readonly uint m_Height; |
|||
protected readonly uint m_Slices; |
|||
protected readonly uint m_ShadowmapBits; |
|||
protected readonly RenderTextureFormat m_ShadowmapFormat; |
|||
protected readonly SamplerState m_SamplerState; |
|||
protected readonly ComparisonSamplerState m_CompSamplerState; |
|||
protected readonly Vector4 m_ClearColor; |
|||
protected readonly float m_WidthRcp; |
|||
protected readonly float m_HeightRcp; |
|||
protected readonly uint m_MaxPayloadCount; |
|||
protected readonly ShadowSupport m_ShadowSupport; |
|||
protected uint m_ShadowId; |
|||
protected CullResults m_CullResults; // TODO: Temporary, due to CullResults dependency in ShadowUtils' matrix extraction code. Remove this member once that dependency is gone.
|
|||
|
|||
public struct BaseInit |
|||
{ |
|||
public uint width; // width of the shadowmap
|
|||
public uint height; // height of the shadowmap
|
|||
public uint slices; // slices for the shadowmap
|
|||
public uint shadowmapBits; // bit depth for native shadowmaps, or bitdepth for the temporary shadowmap if the shadowmapFormat is not native
|
|||
public RenderTextureFormat shadowmapFormat; // texture format of the shadowmap
|
|||
public SamplerState samplerState; // the desired sampler state for non-native shadowmaps
|
|||
public ComparisonSamplerState comparisonSamplerState; // the desired sampler state for native shadowmaps doing depth comparisons as well
|
|||
public Vector4 clearColor; // the clear color used for non-native shadowmaps
|
|||
public uint maxPayloadCount; // how many ints will be pushed into the payload buffer for each invocation of Reserve
|
|||
public ShadowSupport shadowSupport; // bitmask of all shadow types that this shadowmap supports
|
|||
}; |
|||
|
|||
protected ShadowmapBase( ref BaseInit initializer ) |
|||
{ |
|||
m_Width = initializer.width; |
|||
m_Height = initializer.height; |
|||
m_Slices = initializer.slices; |
|||
m_ShadowmapBits = initializer.shadowmapBits; |
|||
m_ShadowmapFormat = initializer.shadowmapFormat; |
|||
m_SamplerState = initializer.samplerState; |
|||
m_CompSamplerState = initializer.comparisonSamplerState; |
|||
m_ClearColor = initializer.clearColor; |
|||
m_WidthRcp = 1.0f / initializer.width; |
|||
m_HeightRcp = 1.0f / initializer.height; |
|||
m_MaxPayloadCount = initializer.maxPayloadCount; |
|||
m_ShadowSupport = initializer.shadowSupport; |
|||
m_ShadowId = 0; |
|||
|
|||
if( IsNativeDepth() && m_Slices > 1 ) |
|||
{ |
|||
// TODO: Right now when using any of the SetRendertarget functions we ultimately end up in RenderTextureD3D11.cpp
|
|||
// SetRenderTargetD3D11Internal. This function sets the correct slice only for RTVs, whereas depth textures only
|
|||
// support one DSV. So there's currently no way to have individual DSVs per slice to render into (ignoring going through a geometry shader and selecting the slice there).
|
|||
Debug.LogError( "Unity does not allow direct rendering into specific depth slices, yet. Defaulting back to one array slice." ); |
|||
m_Slices = 1; |
|||
} |
|||
} |
|||
|
|||
protected bool IsNativeDepth() |
|||
{ |
|||
return m_ShadowmapFormat == RenderTextureFormat.Shadowmap || m_ShadowmapFormat == RenderTextureFormat.Depth; |
|||
} |
|||
|
|||
|
|||
public ShadowSupport QueryShadowSupport() { return m_ShadowSupport; } |
|||
public uint GetMaxPayload() { return m_MaxPayloadCount; } |
|||
public void AssignId( uint shadowId ) { m_ShadowId = shadowId; } |
|||
public void Assign( CullResults cullResults ) { m_CullResults = cullResults; } // TODO: Remove when m_CullResults is removed again
|
|||
abstract public bool Reserve( FrameId frameId, ref ShadowData shadowData, ShadowRequest sr, uint width, uint height, ref VectorArray<ShadowData> entries, ref VectorArray<ShadowPayload> payloads, VisibleLight[] lights ); |
|||
abstract public bool Reserve( FrameId frameId, ref ShadowData shadowData, ShadowRequest sr, uint[] widths, uint[] heights, ref VectorArray<ShadowData> entries, ref VectorArray<ShadowPayload> payloads, VisibleLight[] lights ); |
|||
abstract public bool ReserveFinalize( FrameId frameId, ref VectorArray<ShadowData> entries, ref VectorArray<ShadowPayload> payloads ); |
|||
abstract public void Update( FrameId frameId, ScriptableRenderContext renderContext, CullResults cullResults, VisibleLight[] lights ); |
|||
abstract public void ReserveSlots( ShadowContextStorage sc ); |
|||
abstract public void Fill( ShadowContextStorage cs ); |
|||
} |
|||
|
|||
interface IShadowManager |
|||
{ |
|||
// Warning: The shadowRequests array and shadowRequestsCount are modified by this function.
|
|||
// When called the array contains the indices of lights requesting shadows,
|
|||
// upon returning the array contains up to shadowRequestsCount valid shadow caster indices,
|
|||
// whereas [shadowRequestsCount;originalRequestsCount) will hold all indices for lights that wanted to cast a shadow but got rejected.
|
|||
// shadowDataIndices contains the offset into the shadowDatas array only for each shadow casting light, e.g. lights[shadowRequests[i]].shadowDataOffset = shadowDataIndices[i];
|
|||
// shadowDatas contains shadowmap related basic parameters that can be passed to the shader.
|
|||
// shadowPayloads contains implementation specific data that is accessed from the shader by indexing into an Buffer<int> using ShadowData.ShadowmapData.payloadOffset.
|
|||
// This is the equivalent of a void pointer in the shader and there needs to be loader code that knows how to interpret the data.
|
|||
// If there are no valid shadow casters all output arrays will be null, otherwise they will contain valid data that can be passed to shaders.
|
|||
void ProcessShadowRequests( FrameId frameId, CullResults cullResults, Camera camera, VisibleLight[] lights, ref uint shadowRequestsCount, int[] shadowRequests, out int[] shadowDataIndices ); |
|||
// Renders all shadows for lights the were deemed shadow casters after the last call to ProcessShadowRequests
|
|||
void RenderShadows( FrameId frameId, ScriptableRenderContext renderContext, CullResults cullResults, VisibleLight[] lights ); |
|||
// Synchronize data with GPU buffers
|
|||
void SyncData(); |
|||
// Binds resources to shader stages just before rendering the lighting pass
|
|||
void BindResources( ScriptableRenderContext renderContext ); |
|||
} |
|||
|
|||
abstract public class ShadowManagerBase : IShadowManager |
|||
{ |
|||
public abstract void ProcessShadowRequests( FrameId frameId, CullResults cullResults, Camera camera, VisibleLight[] lights, ref uint shadowRequestsCount, int[] shadowRequests, out int[] shadowDataIndices ); |
|||
public abstract void RenderShadows( FrameId frameId, ScriptableRenderContext renderContext, CullResults cullResults, VisibleLight[] lights ); |
|||
public abstract void SyncData(); |
|||
public abstract void BindResources( ScriptableRenderContext renderContext ); |
|||
// sort the shadow requests in descending priority - may only modify shadowRequests
|
|||
protected abstract void PrioritizeShadowCasters( Camera camera, VisibleLight[] lights, uint shadowRequestsCount, int[] shadowRequests ); |
|||
// prune the shadow requests - may modify shadowRequests and shadowsCountshadowRequestsCount
|
|||
protected abstract void PruneShadowCasters( Camera camera, VisibleLight[] lights, ref VectorArray<int> shadowRequests, ref VectorArray<ShadowmapBase.ShadowRequest> requestsGranted, out uint totalRequestCount ); |
|||
// allocate the shadow requests in the shadow map, only is called if shadowsCount > 0 - may modify shadowRequests and shadowsCount
|
|||
protected abstract void AllocateShadows( FrameId frameId, VisibleLight[] lights, uint totalGranted, ref VectorArray<ShadowmapBase.ShadowRequest> grantedRequests, ref VectorArray<int> shadowIndices, ref VectorArray<ShadowData> shadowmapDatas, ref VectorArray<ShadowPayload> shadowmapPayload ); |
|||
} |
|||
|
|||
} // end of namespace ShadowExp
|
|||
} // end of namespace UnityEngine.Experimental.ScriptableRenderLoop
|
|
|||
// |
|||
// This file was automatically generated from Assets/ScriptableRenderLoop/HDRenderPipeline/Shadow/ShadowBase.cs. Please don't edit by hand. |
|||
// |
|||
|
|||
#ifndef SHADOWBASE_CS_HLSL |
|||
#define SHADOWBASE_CS_HLSL |
|||
// |
|||
// UnityEngine.Experimental.Rendering.HDPipeline.GPUShadowType: static fields |
|||
// |
|||
#define GPUSHADOWTYPE_POINT (0) |
|||
#define GPUSHADOWTYPE_SPOT (1) |
|||
#define GPUSHADOWTYPE_DIRECTIONAL (2) |
|||
#define GPUSHADOWTYPE_MAX (3) |
|||
|
|||
// Generated from UnityEngine.Experimental.Rendering.HDPipeline.ShadowExp.ShadowData |
|||
// PackingRules = Exact |
|||
struct ShadowData |
|||
{ |
|||
float4x4 worldToShadow; |
|||
float4 scaleOffset; |
|||
float2 texelSizeRcp; |
|||
uint id; |
|||
int shadowType; |
|||
uint payloadOffset; |
|||
int lightType; |
|||
float bias; |
|||
float quality; |
|||
}; |
|||
|
|||
// |
|||
// Accessors for UnityEngine.Experimental.Rendering.HDPipeline.ShadowExp.ShadowData |
|||
// |
|||
float4x4 GetWorldToShadow(ShadowData value) |
|||
{ |
|||
return value.worldToShadow; |
|||
} |
|||
float4 GetScaleOffset(ShadowData value) |
|||
{ |
|||
return value.scaleOffset; |
|||
} |
|||
float2 GetTexelSizeRcp(ShadowData value) |
|||
{ |
|||
return value.texelSizeRcp; |
|||
} |
|||
uint GetId(ShadowData value) |
|||
{ |
|||
return value.id; |
|||
} |
|||
int GetShadowType(ShadowData value) |
|||
{ |
|||
return value.shadowType; |
|||
} |
|||
uint GetPayloadOffset(ShadowData value) |
|||
{ |
|||
return value.payloadOffset; |
|||
} |
|||
int GetLightType(ShadowData value) |
|||
{ |
|||
return value.lightType; |
|||
} |
|||
float GetBias(ShadowData value) |
|||
{ |
|||
return value.bias; |
|||
} |
|||
float GetQuality(ShadowData value) |
|||
{ |
|||
return value.quality; |
|||
} |
|||
|
|||
|
|||
#endif |
|
|||
fileFormatVersion: 2 |
|||
guid: 68d59da52ffb57240bdf73300a2736bf |
|||
timeCreated: 1485788693 |
|||
licenseType: Pro |
|||
ShaderImporter: |
|||
defaultTextures: [] |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 0998db889743f994b8ea4257f9ab33fa |
|||
timeCreated: 1485511431 |
|||
licenseType: Pro |
|||
MonoImporter: |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
|
|||
// This can be custom for each project and needs to be in sync with the ShadowMgr |
|||
|
|||
#define SHADOWCONTEXT_MAX_TEX2DARRAY 2 |
|||
#define SHADOWCONTEXT_MAX_TEXCUBEARRAY 1 |
|||
#define SHADOWCONTEXT_MAX_SAMPLER 1 |
|||
#define SHADOWCONTEXT_MAX_COMPSAMPLER 2 |
|||
|
|||
SHADOWCONTEXT_DECLARE( SHADOWCONTEXT_MAX_TEX2DARRAY, SHADOWCONTEXT_MAX_TEXCUBEARRAY, SHADOWCONTEXT_MAX_COMPSAMPLER, SHADOWCONTEXT_MAX_SAMPLER ); |
|||
|
|||
StructuredBuffer<ShadowData> _ShadowDatasExp; |
|||
StructuredBuffer<int4> _ShadowPayloads; |
|||
TEXTURE2D_ARRAY(_ShadowmapExp_Dir); |
|||
SAMPLER2D_SHADOW(sampler_ShadowmapExp_Dir); |
|||
TEXTURE2D_ARRAY(_ShadowmapExp_PointSpot); |
|||
SAMPLER2D_SHADOW(sampler_ShadowmapExp_PointSpot); |
|||
|
|||
ShadowContext InitShadowContext() |
|||
{ |
|||
ShadowContext sc; |
|||
sc.shadowDatas = _ShadowDatasExp; |
|||
sc.payloads = _ShadowPayloads; |
|||
sc.tex2DArray[0] = _ShadowmapExp_Dir; |
|||
sc.tex2DArray[1] = _ShadowmapExp_PointSpot; |
|||
sc.compSamplers[0] = sampler_ShadowmapExp_Dir; |
|||
sc.compSamplers[1] = sampler_ShadowmapExp_PointSpot; |
|||
return sc; |
|||
} |
|||
|
|
|||
fileFormatVersion: 2 |
|||
guid: 4091aef5166b0624db069f6dbfd0673a |
|||
timeCreated: 1485511901 |
|||
licenseType: Pro |
|||
ShaderImporter: |
|||
defaultTextures: [] |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
// This file is empty by default. |
|||
// Project specific file to override the default shadow sampling routines. |
|||
// We need to define which dispatchers we're overriding, otherwise the compiler will pick default implementations which will lead to compile errors. |
|||
// Check Shadow.hlsl right below where this header is included for the individual defines. |
|||
|
|||
// example of overriding directional lights |
|||
//#define SHADOW_DISPATCH_USE_CUSTOM_DIRECTIONAL |
|||
#ifdef SHADOW_DISPATCH_USE_CUSTOM_DIRECTIONAL |
|||
float GetDirectionalShadowAttenuation( ShadowContext shadowContext, float3 positionWS, int shadowDataIndex, float3 L ) |
|||
{ |
|||
Texture2DArray tex = shadowContext.tex2DArray[0]; |
|||
SamplerComparisonState compSamp = shadowContext.compSamplers[0]; |
|||
|
|||
return EvalShadow_CascadedDepth( shadowContext, tex, compSamp, positionWS, shadowDataIndex, L ); |
|||
|
|||
//return EvalShadow_CascadedMomentum( shadowContext, positionWS, shadowDataIndex, L ); |
|||
} |
|||
|
|||
float GetDirectionalShadowAttenuation( ShadowContext shadowContext, float3 positionWS, int shadowDataIndex, float3 L, float2 unPositionSS ) |
|||
{ |
|||
Texture2DArray tex = shadowContext.tex2DArray[0]; |
|||
SamplerComparisonState compSamp = shadowContext.compSamplers[0]; |
|||
|
|||
return EvalShadow_CascadedDepth( shadowContext, tex, compSamp, positionWS, shadowDataIndex, L ); |
|||
|
|||
//return EvalShadow_CascadedMomentum( shadowContext, positionWS, shadowDataIndex, L ); |
|||
} |
|||
#endif |
|||
|
|||
|
|||
|
|||
// This is an example of how to override the default dynamic resource dispatcher |
|||
// by hardcoding the resources used and calling the shadow sampling routines that take an explicit texture and sampler. |
|||
// It is the responsibility of the author to make sure that ShadowContext.hlsl binds the correct texture to the right slot, |
|||
// and that on the C# side the shadowContext bindDelegate binds the correct resource to the correct texture id. |
|||
//#define SHADOW_DISPATCH_USE_CUSTOM_PUNCTUAL |
|||
#ifdef SHADOW_DISPATCH_USE_CUSTOM_PUNCTUAL |
|||
float GetPunctualShadowAttenuation( ShadowContext shadowContext, float3 positionWS, int shadowDataIndex, float3 L ) |
|||
{ |
|||
Texture2DArray tex = shadowContext.tex2DArray[1]; |
|||
SamplerComparisonState compSamp = shadowContext.compSamplers[1]; |
|||
|
|||
return EvalShadow_PunctualDepth( shadowContext, tex, compSamp, positionWS, shadowDataIndex, L ); |
|||
} |
|||
float GetPunctualShadowAttenuation( ShadowContext shadowContext, float3 positionWS, int shadowDataIndex, float3 L, float2 unPositionSS ) |
|||
{ |
|||
Texture2DArray tex = shadowContext.tex2DArray[1]; |
|||
SamplerComparisonState compSamp = shadowContext.compSamplers[1]; |
|||
|
|||
return EvalShadow_PunctualDepth( shadowContext, tex, compSamp, positionWS, shadowDataIndex, L ); |
|||
} |
|||
#endif |
|
|||
fileFormatVersion: 2 |
|||
guid: 1c09a112d6337c7468843f1d64d7795f |
|||
timeCreated: 1485511901 |
|||
licenseType: Pro |
|||
ShaderImporter: |
|||
defaultTextures: [] |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
// Various shadow sampling logic. |
|||
// Again two versions, one for dynamic resource indexing, one for static resource access. |
|||
|
|||
// |
|||
// 1 tap PCF sampling |
|||
// |
|||
float SampleShadow_PCF_1tap( ShadowContext shadowContext, float3 tcs, uint slice, uint texIdx, uint sampIdx ) |
|||
{ |
|||
// sample the texture |
|||
return SampleCompShadow_T2DA( shadowContext, texIdx, sampIdx, tcs, slice ).x; |
|||
} |
|||
|
|||
float SampleShadow_PCF_1tap( ShadowContext shadowContext, float3 tcs, uint slice, Texture2DArray tex, SamplerComparisonState compSamp ) |
|||
{ |
|||
// sample the texture |
|||
return SAMPLE_TEXTURE2D_ARRAY_SHADOW( tex, compSamp, tcs, slice ); |
|||
} |
|||
|
|||
// |
|||
// 9 tap adaptive PCF sampling |
|||
// |
|||
float SampleShadow_PCF_9tap_Adaptive( ShadowContext shadowContext, float2 texelSizeRcp, float3 tcs, uint slice, uint texIdx, uint sampIdx ) |
|||
{ |
|||
// Terms0 are weights for the individual samples, the other terms are offsets in texel space |
|||
float4 vShadow3x3PCFTerms0 = float4( 20.0f / 267.0f, 33.0f / 267.0f, 55.0f / 267.0f, 0.0f ); |
|||
float4 vShadow3x3PCFTerms1 = float4( texelSizeRcp.x, texelSizeRcp.y, -texelSizeRcp.x, -texelSizeRcp.y ); |
|||
float4 vShadow3x3PCFTerms2 = float4( texelSizeRcp.x, texelSizeRcp.y, 0.0f, 0.0f ); |
|||
float4 vShadow3x3PCFTerms3 = float4(-texelSizeRcp.x, -texelSizeRcp.y, 0.0f, 0.0f ); |
|||
|
|||
float4 v20Taps; |
|||
v20Taps.x = SampleCompShadow_T2DA( shadowContext, texIdx, sampIdx, float3( tcs.xy + vShadow3x3PCFTerms1.xy, tcs.z ), slice ).x; // 1 1 |
|||
v20Taps.y = SampleCompShadow_T2DA( shadowContext, texIdx, sampIdx, float3( tcs.xy + vShadow3x3PCFTerms1.zy, tcs.z ), slice ).x; // -1 1 |
|||
v20Taps.z = SampleCompShadow_T2DA( shadowContext, texIdx, sampIdx, float3( tcs.xy + vShadow3x3PCFTerms1.xw, tcs.z ), slice ).x; // 1 -1 |
|||
v20Taps.w = SampleCompShadow_T2DA( shadowContext, texIdx, sampIdx, float3( tcs.xy + vShadow3x3PCFTerms1.zw, tcs.z ), slice ).x; // -1 -1 |
|||
float flSum = dot( v20Taps.xyzw, float4( 0.25, 0.25, 0.25, 0.25 ) ); |
|||
// fully in light or shadow? -> bail |
|||
if( ( flSum == 0.0 ) || ( flSum == 1.0 ) ) |
|||
return flSum; |
|||
|
|||
// we're in a transition area, do 5 more taps |
|||
flSum *= vShadow3x3PCFTerms0.x * 4.0; |
|||
|
|||
float4 v33Taps; |
|||
v33Taps.x = SampleCompShadow_T2DA( shadowContext, texIdx, sampIdx, float3( tcs.xy + vShadow3x3PCFTerms2.xz, tcs.z ), slice ).x; // 1 0 |
|||
v33Taps.y = SampleCompShadow_T2DA( shadowContext, texIdx, sampIdx, float3( tcs.xy + vShadow3x3PCFTerms3.xz, tcs.z ), slice ).x; // -1 0 |
|||
v33Taps.z = SampleCompShadow_T2DA( shadowContext, texIdx, sampIdx, float3( tcs.xy + vShadow3x3PCFTerms3.zy, tcs.z ), slice ).x; // 0 -1 |
|||
v33Taps.w = SampleCompShadow_T2DA( shadowContext, texIdx, sampIdx, float3( tcs.xy + vShadow3x3PCFTerms2.zy, tcs.z ), slice ).x; // 0 1 |
|||
flSum += dot( v33Taps.xyzw, vShadow3x3PCFTerms0.yyyy ); |
|||
|
|||
flSum += SampleCompShadow_T2DA( shadowContext, texIdx, sampIdx, tcs, slice ).x * vShadow3x3PCFTerms0.z; |
|||
|
|||
return flSum; |
|||
} |
|||
|
|||
float SampleShadow_PCF_9tap_Adaptive(ShadowContext shadowContext, float2 texelSizeRcp, float3 tcs, uint slice, Texture2DArray tex, SamplerComparisonState compSamp ) |
|||
{ |
|||
// Terms0 are weights for the individual samples, the other terms are offsets in texel space |
|||
float4 vShadow3x3PCFTerms0 = float4(20.0f / 267.0f, 33.0f / 267.0f, 55.0f / 267.0f, 0.0f); |
|||
float4 vShadow3x3PCFTerms1 = float4( texelSizeRcp.x, texelSizeRcp.y, -texelSizeRcp.x, -texelSizeRcp.y); |
|||
float4 vShadow3x3PCFTerms2 = float4( texelSizeRcp.x, texelSizeRcp.y, 0.0f, 0.0f); |
|||
float4 vShadow3x3PCFTerms3 = float4(-texelSizeRcp.x, -texelSizeRcp.y, 0.0f, 0.0f); |
|||
|
|||
float4 v20Taps; |
|||
v20Taps.x = SAMPLE_TEXTURE2D_ARRAY_SHADOW( tex, compSamp, float3( tcs.xy + vShadow3x3PCFTerms1.xy, tcs.z ), slice ).x; // 1 1 |
|||
v20Taps.y = SAMPLE_TEXTURE2D_ARRAY_SHADOW( tex, compSamp, float3( tcs.xy + vShadow3x3PCFTerms1.zy, tcs.z ), slice ).x; // -1 1 |
|||
v20Taps.z = SAMPLE_TEXTURE2D_ARRAY_SHADOW( tex, compSamp, float3( tcs.xy + vShadow3x3PCFTerms1.xw, tcs.z ), slice ).x; // 1 -1 |
|||
v20Taps.w = SAMPLE_TEXTURE2D_ARRAY_SHADOW( tex, compSamp, float3( tcs.xy + vShadow3x3PCFTerms1.zw, tcs.z ), slice ).x; // -1 -1 |
|||
float flSum = dot( v20Taps.xyzw, float4( 0.25, 0.25, 0.25, 0.25 ) ); |
|||
// fully in light or shadow? -> bail |
|||
if( ( flSum == 0.0 ) || ( flSum == 1.0 ) ) |
|||
return flSum; |
|||
|
|||
// we're in a transition area, do 5 more taps |
|||
flSum *= vShadow3x3PCFTerms0.x * 4.0; |
|||
|
|||
float4 v33Taps; |
|||
v33Taps.x = SAMPLE_TEXTURE2D_ARRAY_SHADOW( tex, compSamp, float3( tcs.xy + vShadow3x3PCFTerms2.xz, tcs.z ), slice ).x; // 1 0 |
|||
v33Taps.y = SAMPLE_TEXTURE2D_ARRAY_SHADOW( tex, compSamp, float3( tcs.xy + vShadow3x3PCFTerms3.xz, tcs.z ), slice ).x; // -1 0 |
|||
v33Taps.z = SAMPLE_TEXTURE2D_ARRAY_SHADOW( tex, compSamp, float3( tcs.xy + vShadow3x3PCFTerms3.zy, tcs.z ), slice ).x; // 0 -1 |
|||
v33Taps.w = SAMPLE_TEXTURE2D_ARRAY_SHADOW( tex, compSamp, float3( tcs.xy + vShadow3x3PCFTerms2.zy, tcs.z ), slice ).x; // 0 1 |
|||
flSum += dot( v33Taps.xyzw, vShadow3x3PCFTerms0.yyyy ); |
|||
|
|||
flSum += SAMPLE_TEXTURE2D_ARRAY_SHADOW( tex, compSamp, tcs, slice ).x * vShadow3x3PCFTerms0.z; |
|||
|
|||
return flSum; |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 32c86a80860fd014ba44f8b7a7914a5c |
|||
timeCreated: 1487253249 |
|||
licenseType: Pro |
|||
ShaderImporter: |
|||
defaultTextures: [] |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
|
|||
|
|||
// shadow lookup routines when dynamic array access is possible |
|||
#if SHADOW_SUPPORTS_DYNAMIC_INDEXING != 0 |
|||
|
|||
// Shader model >= 5.1 |
|||
# define SHADOW_DEFINE_SAMPLING_FUNCS( _Tex2DArraySlots, _TexCubeArraySlots ) \ |
|||
float4 SampleCompShadow_T2DA( ShadowContext ctxt, uint texIdx, uint sampIdx, float3 tcs, float slice ) { return SAMPLE_TEXTURE2D_ARRAY_SHADOW( ctxt.tex2DArray[texIdx], ctxt.compSamplers[sampIdx], tcs, slice ); } \ |
|||
float4 SampleShadow_T2DA( ShadowContext ctxt, uint texIdx, uint sampIdx, float2 tcs, float slice, float lod = 0.0 ) { return SAMPLE_TEXTURE2D_ARRAY_LOD( ctxt.tex2DArray[texIdx], ctxt.samplers[sampIdx], tcs, slice, lod ); } \ |
|||
float4 SampleCompShadow_TCA( ShadowContext ctxt, uint texIdx, uint sampIdx, float4 tcs, float cubeIdx ) { return SAMPLE_TEXTURECUBE_ARRAY_SHADOW( ctxt.texCubeArray[texIdx], ctxt.compSamplers[sampIdx], tcs, cubeIdx );} \ |
|||
float4 SampleShadow_TCA( ShadowContext ctxt, uint texIdx, uint sampIdx, float3 tcs, float cubeIdx, float lod = 0.0 ) { return SAMPLE_TEXTURECUBE_ARRAY_LOD( ctxt.texCubeArray[texIdx], ctxt.samplers[sampIdx], tcs, cubeIdx, lod ); } |
|||
|
|||
|
|||
#else // helper macros if dynamic indexing does not work |
|||
|
|||
|
|||
// Sampler and texture combinations are static. No shadowmap will ever be sampled with two different samplers. |
|||
// Once shadowmaps and samplers are fixed consider writing custom dispatchers directly accessing textures and samplers. |
|||
# define SHADOW_DEFINE_SAMPLING_FUNCS( _Tex2DArraySlots, _TexCubeArraySlots, _SamplerCompSlots, _SamplerSlots ) \ |
|||
\ |
|||
float4 SampleCompShadow_T2DA( ShadowContext ctxt, uint texIdx, uint sampIdx, float3 tcs, float slice ) \ |
|||
{ \ |
|||
[unroll] for( uint i = 0; i < _Tex2DArraySlots; i++ ) \ |
|||
{ \ |
|||
[unroll] for( uint j = 0; j < _SamplerCompSlots; j++ ) \ |
|||
{ \ |
|||
[branch] if( i == texIdx && j == sampIdx ) \ |
|||
{ \ |
|||
return SAMPLE_TEXTURE2D_ARRAY_SHADOW( ctxt.tex2DArray[i], ctxt.compSamplers[j], tcs, slice ); \ |
|||
} \ |
|||
} \ |
|||
} \ |
|||
return 1.0; \ |
|||
} \ |
|||
\ |
|||
float4 SampleShadow_T2DA( ShadowContext ctxt, uint texIdx, uint sampIdx, float2 tcs, float slice, float lod = 0.0 ) \ |
|||
{ \ |
|||
[unroll] for( uint i = 0; i < _Tex2DArraySlots; i++ ) \ |
|||
{ \ |
|||
[unroll] for( uint j = 0; j < _SamplerSlots; j++ ) \ |
|||
{ \ |
|||
[branch] if( i == texIdx && j == sampIdx ) \ |
|||
{ \ |
|||
return SAMPLE_TEXTURE2D_ARRAY_LOD( ctxt.tex2DArray[i], ctxt.samplers[j], tcs, slice, lod ); \ |
|||
} \ |
|||
} \ |
|||
} \ |
|||
return 1.0; \ |
|||
} \ |
|||
\ |
|||
float4 SampleCompShadow_TCA( ShadowContext ctxt, uint texIdx, uint sampIdx, float4 tcs, float cubeIdx ) \ |
|||
{ \ |
|||
[unroll] for( uint i = 0; i < _TexCubeArraySlots; i++ ) \ |
|||
{ \ |
|||
[unroll] for( uint j = 0; j < _SamplerCompSlots; j++ ) \ |
|||
{ \ |
|||
[branch] if( i == texIdx && j == sampIdx ) \ |
|||
{ \ |
|||
return SAMPLE_TEXTURECUBE_ARRAY_SHADOW( ctxt.texCubeArray[i], ctxt.compSamplers[j], tcs, cubeIdx ); \ |
|||
} \ |
|||
} \ |
|||
} \ |
|||
return 1.0; \ |
|||
} \ |
|||
\ |
|||
float4 SampleShadow_TCA( ShadowContext ctxt, uint texIdx, uint sampIdx, float3 tcs, float cubeIdx, float lod = 0.0 ) \ |
|||
{ \ |
|||
[unroll] for( uint i = 0; i < _TexCubeArraySlots; i++ ) \ |
|||
{ \ |
|||
[unroll] for( uint j = 0; j < _SamplerSlots; j++ ) \ |
|||
{ \ |
|||
[branch] if( i == texIdx && j == sampIdx ) \ |
|||
{ \ |
|||
return SAMPLE_TEXTURECUBE_ARRAY_LOD( ctxt.texCubeArray[i], ctxt.samplers[j], tcs, cubeIdx, lod ); \ |
|||
} \ |
|||
} \ |
|||
} \ |
|||
return 1.0; \ |
|||
} |
|||
|
|||
#endif |
|
|||
fileFormatVersion: 2 |
|||
guid: 2ac0d1cc115e6ec42b5c47d2ee13f139 |
|||
timeCreated: 1485873843 |
|||
licenseType: Pro |
|||
ShaderImporter: |
|||
defaultTextures: [] |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
|
|||
namespace UnityEngine.Experimental.Rendering.HDPipeline |
|||
{ |
|||
public class ShadowUtils |
|||
{ |
|||
public static Matrix4x4 ExtractSpotLightMatrix( VisibleLight vl, out Matrix4x4 view, out Matrix4x4 proj, out Vector4 lightDir, out ShadowSplitData splitData ) |
|||
{ |
|||
splitData = new ShadowSplitData(); |
|||
splitData.cullingSphere.Set( 0.0f, 0.0f, 0.0f, float.NegativeInfinity ); |
|||
splitData.cullingPlaneCount = 0; |
|||
// get lightDir
|
|||
lightDir = vl.light.transform.forward; |
|||
// calculate view
|
|||
Matrix4x4 scaleMatrix = Matrix4x4.identity; |
|||
scaleMatrix.m22 = -1.0f; |
|||
view = scaleMatrix * vl.localToWorld.inverse; |
|||
// following code is from SharedLightData::GetNearPlaneMinBound
|
|||
float percentageBound = 0.01f * vl.light.range; |
|||
float fixedBound = 0.1f; |
|||
float nearmin = fixedBound <= percentageBound ? fixedBound : percentageBound; |
|||
// calculate projection
|
|||
float zfar = vl.range; |
|||
float znear = vl.light.shadowNearPlane >= nearmin ? vl.light.shadowNearPlane : nearmin; |
|||
float fov = vl.spotAngle; |
|||
proj = Matrix4x4.Perspective(fov, 1.0f, znear, zfar); |
|||
// and the compound
|
|||
return proj * view; |
|||
} |
|||
|
|||
public static Matrix4x4 ExtractPointLightMatrix( VisibleLight vl, uint faceIdx, float fovBias, out Matrix4x4 view, out Matrix4x4 proj, out Vector4 lightDir, out ShadowSplitData splitData, CullResults cullResults, int lightIndex ) |
|||
{ |
|||
Debug.Assert( faceIdx <= (uint) CubemapFace.NegativeZ, "Tried to extract cubemap face " + faceIdx + "." ); |
|||
|
|||
splitData = new ShadowSplitData(); |
|||
splitData.cullingSphere.Set(0.0f, 0.0f, 0.0f, float.NegativeInfinity); |
|||
splitData.cullingPlaneCount = 0; |
|||
// get lightDir
|
|||
lightDir = vl.light.transform.forward; |
|||
// TODO: At some point this logic should be moved to C#, then the parameters cullResults and lightIndex can be removed as well
|
|||
cullResults.ComputePointShadowMatricesAndCullingPrimitives( lightIndex, (CubemapFace) faceIdx, fovBias, out view, out proj, out splitData ); |
|||
// and the compound
|
|||
return proj * view; |
|||
} |
|||
|
|||
public static Matrix4x4 ExtractDirectionalLightMatrix( VisibleLight vl, uint cascadeIdx, int cascadeCount, Vector3 splitRatio, float nearPlaneOffset, uint width, uint height, out Matrix4x4 view, out Matrix4x4 proj, out Vector4 lightDir, out ShadowSplitData splitData, CullResults cullResults, int lightIndex ) |
|||
{ |
|||
Debug.Assert( width == height, "Currently the cascaded shadow mapping code requires square cascades." ); |
|||
splitData = new ShadowSplitData(); |
|||
splitData.cullingSphere.Set(0.0f, 0.0f, 0.0f, float.NegativeInfinity); |
|||
splitData.cullingPlaneCount = 0; |
|||
// get lightDir
|
|||
lightDir = vl.light.transform.forward; |
|||
// TODO: At some point this logic should be moved to C#, then the parameters cullResults and lightIndex can be removed as well
|
|||
// For directional lights shadow data is extracted from the cullResults, so that needs to be somehow provided here.
|
|||
// Check ScriptableShadowsUtility.cpp ComputeDirectionalShadowMatricesAndCullingPrimitives(...) for details.
|
|||
cullResults.ComputeDirectionalShadowMatricesAndCullingPrimitives( lightIndex, (int) cascadeIdx, cascadeCount, splitRatio, (int) width, nearPlaneOffset, out view, out proj, out splitData ); |
|||
// and the compound
|
|||
return proj * view; |
|||
} |
|||
|
|||
public static bool MapLightType( LightArchetype la, LightType lt, out GPULightType gputype, out GPUShadowType shadowtype ) |
|||
{ |
|||
switch( la ) |
|||
{ |
|||
case LightArchetype.Punctual : return MapLightType( lt, out gputype, out shadowtype ); |
|||
case LightArchetype.Rectangle : gputype = GPULightType.Rectangle; shadowtype = GPUShadowType.Unknown; return true; |
|||
case LightArchetype.Line : gputype = GPULightType.Line; shadowtype = GPUShadowType.Unknown; return true; |
|||
default : gputype = GPULightType.Spot; shadowtype = GPUShadowType.Unknown; return false; // <- probably not what you want
|
|||
} |
|||
|
|||
} |
|||
public static bool MapLightType( LightType lt, out GPULightType gputype, out GPUShadowType shadowtype ) |
|||
{ |
|||
switch( lt ) |
|||
{ |
|||
case LightType.Spot : gputype = GPULightType.Spot; shadowtype = GPUShadowType.Spot; return true; |
|||
case LightType.Directional : gputype = GPULightType.Directional; shadowtype = GPUShadowType.Directional; return true; |
|||
case LightType.Point : gputype = GPULightType.Point; shadowtype = GPUShadowType.Point; return true; |
|||
default: |
|||
case LightType.Area : gputype = GPULightType.Rectangle; shadowtype = GPUShadowType.Unknown; return false; // area lights by themselves can't be mapped to any GPU type
|
|||
} |
|||
} |
|||
|
|||
public static float Asfloat( uint val ) { return System.BitConverter.ToSingle( System.BitConverter.GetBytes( val ), 0 ); } |
|||
public static int Asint( float val ) { return System.BitConverter.ToInt32( System.BitConverter.GetBytes( val ), 0 ); } |
|||
public static uint Asuint( float val ) { return System.BitConverter.ToUInt32( System.BitConverter.GetBytes( val ), 0 ); } |
|||
} |
|||
} // end of namespace UnityEngine.Experimental.ScriptableRenderLoop
|
|
|||
fileFormatVersion: 2 |
|||
guid: 99dfd76ad655ff441aef9bb0015a1f05 |
|||
timeCreated: 1485511433 |
|||
licenseType: Pro |
|||
MonoImporter: |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
|
|||
namespace UnityEngine.Experimental |
|||
{ |
|||
public struct VectorArray<T> |
|||
{ |
|||
public const uint k_InvalidIdx = uint.MaxValue; |
|||
private T[] m_array; // backing storage
|
|||
private uint m_offset; // offset into backing storage
|
|||
private uint m_count; // active slots, <= m_array.Length
|
|||
private bool m_clearToDefault; // if true, freed up slots will be default initialized
|
|||
|
|||
public VectorArray( uint capacity, bool clearToDefault ) |
|||
{ |
|||
m_array = new T[capacity]; |
|||
m_offset = 0; |
|||
m_count = capacity; |
|||
m_clearToDefault = clearToDefault; |
|||
} |
|||
|
|||
public VectorArray( T[] array, uint offset, uint count, bool clearToDefault ) |
|||
{ |
|||
m_array = array; |
|||
m_offset = offset; |
|||
m_count = count; |
|||
m_clearToDefault = clearToDefault; |
|||
} |
|||
|
|||
public VectorArray( ref VectorArray<T> vec, uint offset, uint count ) |
|||
{ |
|||
m_array = vec.m_array; |
|||
m_offset = vec.m_offset + offset; |
|||
m_count = count; |
|||
m_clearToDefault = vec.m_clearToDefault; |
|||
} |
|||
|
|||
// Reset fill count, but keep storage
|
|||
public void Reset() |
|||
{ |
|||
if( m_clearToDefault ) |
|||
{ |
|||
for( uint i = 0; i < m_count; ++i ) |
|||
this[i] = default(T); |
|||
} |
|||
m_count = 0; |
|||
} |
|||
|
|||
// Reset fill count and reserve enough storage
|
|||
public void Reset( uint capacity ) |
|||
{ |
|||
if( m_array.Length < (m_offset+capacity) ) |
|||
{ |
|||
m_array = new T[capacity]; |
|||
m_offset = 0; |
|||
m_count = 0; |
|||
} |
|||
else |
|||
Reset(); |
|||
} |
|||
|
|||
// Reserve additional storage
|
|||
public void Reserve(uint capacity) |
|||
{ |
|||
if (m_offset + m_count + capacity <= m_array.Length) |
|||
return; |
|||
|
|||
if (m_count == 0) |
|||
{ |
|||
m_array = new T[capacity]; |
|||
} |
|||
else |
|||
{ |
|||
T[] tmp = new T[m_count + capacity]; |
|||
Array.Copy(m_array, m_offset, tmp, 0, m_count); |
|||
m_array = tmp; |
|||
} |
|||
m_offset = 0; |
|||
} |
|||
|
|||
// Resize array, either by cutting off at the end, or adding potentially default initialized slots
|
|||
// Resize will modify the count member as well, whereas reserve won't.
|
|||
public void Resize( uint size ) |
|||
{ |
|||
if( size > m_count ) |
|||
{ |
|||
uint residue = size - m_count; |
|||
Reserve( residue ); |
|||
} |
|||
else if( m_clearToDefault ) |
|||
{ |
|||
for( uint i = size; i < m_count; ++i ) |
|||
this[i] = default( T ); |
|||
} |
|||
m_count = size; |
|||
} |
|||
|
|||
public delegate void Cleanup( T obj ); |
|||
// Same as Resize(), with an additional delegate to do any cleanup on the potentially freed slots
|
|||
public void Resize( uint size, Cleanup cleanupDelegate ) |
|||
{ |
|||
for( uint i = size; i < m_count; ++i ) |
|||
cleanupDelegate( this[i] ); |
|||
|
|||
Resize( size ); |
|||
} |
|||
|
|||
// Same as Reset(), with an additional delegate to do any cleanup on the potentially freed slots
|
|||
public void Reset( Cleanup cleanupDelegate ) |
|||
{ |
|||
for( uint i = 0; i < m_count; ++i ) |
|||
cleanupDelegate( this[i] ); |
|||
|
|||
Reset(); |
|||
} |
|||
|
|||
// Same as Reset( uint capacity ), with an additional delegate to do any cleanup on the potentially freed slots
|
|||
public void Reset( uint capacity, Cleanup cleanupDelegate ) |
|||
{ |
|||
for( uint i = 0; i < m_count; ++i ) |
|||
cleanupDelegate( this[i] ); |
|||
|
|||
Reset( capacity ); |
|||
} |
|||
|
|||
// Add obj and reallocate if necessary. Returns the index where the object was added.
|
|||
public uint Add(T obj) |
|||
{ |
|||
Reserve( 1 ); |
|||
uint idx = m_count; |
|||
this[idx] = obj; |
|||
m_count++; |
|||
return idx; |
|||
} |
|||
|
|||
// Add multiple objects and reallocate if necessary. Returns the index where the first object was added.
|
|||
public uint Add( T[] objs, uint count ) |
|||
{ |
|||
Reserve( count ); |
|||
return AddUnchecked( objs, count ); |
|||
} |
|||
|
|||
public uint Add( ref VectorArray<T> vec ) |
|||
{ |
|||
Reserve( vec.Count() ); |
|||
return AddUnchecked( ref vec ); |
|||
} |
|||
|
|||
// Add an object without doing size checks. Make sure to call Reserve( capacity ) before using this.
|
|||
public uint AddUnchecked(T obj) |
|||
{ |
|||
uint idx = m_count; |
|||
this[idx] = obj; |
|||
m_count++; |
|||
return idx; |
|||
} |
|||
|
|||
// Add multiple objects without doing size checks. Make sure to call Reserve( capacity ) before using this.
|
|||
public uint AddUnchecked( T[] objs, uint count ) |
|||
{ |
|||
uint idx = m_count; |
|||
Array.Copy( objs, 0, m_array, m_offset + idx, count ); |
|||
m_count += count; |
|||
return idx; |
|||
} |
|||
|
|||
public uint AddUnchecked( ref VectorArray<T> vec ) |
|||
{ |
|||
uint cnt = vec.Count(); |
|||
uint idx = m_count; |
|||
Array.Copy( vec.m_array, vec.m_offset, m_array, m_offset + idx, cnt ); |
|||
m_count += cnt; |
|||
return idx; |
|||
} |
|||
|
|||
// Purge count number of elements from the end of the array.
|
|||
public void Purge( uint count ) |
|||
{ |
|||
Resize( count > m_count ? 0 : (m_count - count) ); |
|||
} |
|||
|
|||
// Same as Purge with an additional cleanup delegate.
|
|||
public void Purge( uint count, Cleanup cleanupDelegate ) |
|||
{ |
|||
Resize( count > m_count ? 0 : (m_count - count), cleanupDelegate ); |
|||
} |
|||
|
|||
// Copies the active elements to the destination. destination.Length must be large enough to hold all values.
|
|||
public void CopyTo(T[] destination, int destinationStart, out uint count) |
|||
{ |
|||
count = m_count; |
|||
Array.Copy(m_array, m_offset, destination, destinationStart, count); |
|||
} |
|||
|
|||
// Swaps two elements in the array doing bounds checks.
|
|||
public void Swap( uint first, uint second ) |
|||
{ |
|||
if( first >= m_count || second >= m_count ) |
|||
throw new System.ArgumentException( "Swap indices are out of range." ); |
|||
|
|||
SwapUnchecked( first, second ); |
|||
} |
|||
|
|||
// Swaps two elements without any checks.
|
|||
public void SwapUnchecked( uint first, uint second ) |
|||
{ |
|||
T tmp = this[first]; |
|||
this[first] = this[second]; |
|||
this[second] = tmp; |
|||
} |
|||
|
|||
// Extracts information from objects contained in the array and stores them in the destination array.
|
|||
// Conversion is performed by the extractor delegate for each object.
|
|||
// The destination array must be large enough to hold all values.
|
|||
// The output parameter count will contain the number of actual objects written out to the destination array.
|
|||
public delegate U Extractor<U>(T obj); |
|||
public void ExtractTo<U>( U[] destination, int destinationStart, out uint count, Extractor<U> extractorDelegate ) |
|||
{ |
|||
if (destination.Length < m_count) |
|||
throw new System.ArgumentException("Destination array is too small for source array."); |
|||
|
|||
count = m_count; |
|||
for( uint i = 0; i < m_count; ++i ) |
|||
destination[destinationStart+i] = extractorDelegate( this[i] ); |
|||
} |
|||
public void ExtractTo<U>( ref VectorArray<U> destination, Extractor<U> extractorDelegate ) |
|||
{ |
|||
destination.Reserve( m_count ); |
|||
for( uint i = 0; i < m_count; ++i ) |
|||
destination.AddUnchecked( extractorDelegate( this[i] ) ); |
|||
} |
|||
|
|||
// Cast to array. The output parameter will contain the array offset to valid members and the number of active elements.
|
|||
// Accessing array elements outside of this interval will lead to undefined behavior.
|
|||
public T[] AsArray( out uint offset, out uint count ) |
|||
{ |
|||
offset = m_offset; |
|||
count = m_count; |
|||
return m_array; |
|||
} |
|||
|
|||
// Array access. No bounds checking here, make sure you know what you're doing.
|
|||
public T this[uint index] |
|||
{ |
|||
get { return m_array[m_offset+index]; } |
|||
set { m_array[m_offset+index] = value; } |
|||
} |
|||
|
|||
// Returns the number of active elements in the vector.
|
|||
public uint Count() |
|||
{ |
|||
return m_count; |
|||
} |
|||
|
|||
// Returns the total capacity of accessible backing storage
|
|||
public uint CapacityTotal() |
|||
{ |
|||
return (uint) m_array.Length - m_offset; |
|||
} |
|||
|
|||
// Return the number of slots that can still be added before reallocation of the backing storage becomes necessary.
|
|||
public uint CapacityAvailable() |
|||
{ |
|||
return (uint) m_array.Length - m_offset - m_count; |
|||
} |
|||
|
|||
// Default sort the active array content.
|
|||
public void Sort() |
|||
{ |
|||
Debug.Assert( m_count <= int.MaxValue && m_offset <= int.MaxValue ); |
|||
Array.Sort( m_array, (int) m_offset, (int)m_count ); |
|||
} |
|||
// Returns true if the element matches the designator according to the comparator. idx will hold the index to the first matched object in the array.
|
|||
public delegate bool Comparator<U>( ref U designator, ref T obj ); |
|||
public bool FindFirst<U>( out uint idx, ref U designator, Comparator<U> compareDelegate ) |
|||
{ |
|||
for( idx = 0; idx < m_count; ++idx ) |
|||
{ |
|||
T obj = this[idx]; |
|||
if( compareDelegate( ref designator, ref obj ) ) |
|||
return true; |
|||
} |
|||
idx = k_InvalidIdx; |
|||
return false; |
|||
} |
|||
// Returns true if the element matches the designator using the types native compare method. idx will hold the index to the first matched object in the array.
|
|||
public bool FindFirst( out uint idx, ref T designator ) |
|||
{ |
|||
for( idx = 0; idx < m_count; ++idx ) |
|||
{ |
|||
if( this[idx].Equals( designator ) ) |
|||
return true; |
|||
} |
|||
idx = k_InvalidIdx; |
|||
return false; |
|||
} |
|||
|
|||
// Returns a vector representing a subrange. Contents are shared, not duplicated.
|
|||
public VectorArray<T> Subrange( uint offset, uint count ) |
|||
{ |
|||
return new VectorArray<T>( m_array, m_offset + offset, count, m_clearToDefault ); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: f5c1ec8ec44960d4bbf72eafaf47d8fd |
|||
timeCreated: 1485511435 |
|||
licenseType: Pro |
|||
MonoImporter: |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
撰写
预览
正在加载...
取消
保存
Reference in new issue