using UnityEngine.Rendering; using UnityEngine.Experimental.Rendering; using System; using System.Collections.Generic; namespace UnityEngine.Experimental.ScriptableRenderLoop { [ExecuteInEditMode] public class FptlLighting : ScriptableRenderLoop { #if UNITY_EDITOR [UnityEditor.MenuItem("Renderloop/CreateRenderLoopFPTL")] static void CreateRenderLoopFPTL() { var instance = ScriptableObject.CreateInstance(); UnityEditor.AssetDatabase.CreateAsset(instance, "Assets/renderloopfptl.asset"); //AssetDatabase.CreateAsset(instance, "Assets/ScriptableRenderLoop/fptl/renderloopfptl.asset"); } #endif [SerializeField] ShadowSettings m_ShadowSettings = ShadowSettings.Default; ShadowRenderPass m_ShadowPass; [SerializeField] TextureSettings m_TextureSettings = TextureSettings.Default; public Shader deferredShader; public Shader deferredReflectionShader; public Shader finalPassShader; public Shader debugLightBoundsShader; public ComputeShader buildScreenAABBShader; public ComputeShader buildPerTileLightListShader; // FPTL public ComputeShader buildPerBigTileLightListShader; public ComputeShader buildPerVoxelLightListShader; // clustered private Material m_DeferredMaterial; private Material m_DeferredReflectionMaterial; private static int s_GBufferAlbedo; private static int s_GBufferSpecRough; private static int s_GBufferNormal; private static int s_GBufferEmission; private static int s_GBufferZ; private static int s_CameraTarget; private static int s_CameraDepthTexture; private static int s_GenAABBKernel; private static int s_GenListPerTileKernel; private static int s_GenListPerVoxelKernel; private static int s_ClearVoxelAtomicKernel; private static ComputeBuffer s_LightDataBuffer; private static ComputeBuffer s_ConvexBoundsBuffer; private static ComputeBuffer s_AABBBoundsBuffer; private static ComputeBuffer s_LightList; private static ComputeBuffer s_DirLightList; private static ComputeBuffer s_BigTileLightList; // used for pre-pass coarse culling on 64x64 tiles private static int s_GenListPerBigTileKernel; // clustered light list specific buffers and data begin public bool enableClustered = false; public bool disableFptlWhenClustered = false; // still useful on opaques public bool enableBigTilePrepass = true; public bool enableDrawLightBoundsDebug = false; public bool enableDrawTileDebug = false; const bool k_UseDepthBuffer = true;// // only has an impact when EnableClustered is true (requires a depth-prepass) const int k_Log2NumClusters = 6; // accepted range is from 0 to 6. NumClusters is 1< visibleLights) { var dirLightCount = 0; var lights = new List(); var worldToView = camera.worldToCameraMatrix; for (int nLight = 0; nLight < visibleLights.Count; nLight++) { var light = visibleLights[nLight]; if (light.lightType == LightType.Directional) { Debug.Assert(dirLightCount < MaxNumDirLights, "Too many directional lights."); var l = new DirectionalLight(); var lightToWorld = light.localToWorld; Vector3 lightDir = lightToWorld.GetColumn(2); // Z axis in world space // represents a left hand coordinate system in world space Vector3 vx = lightToWorld.GetColumn(0); // X axis in world space Vector3 vy = lightToWorld.GetColumn(1); // Y axis in world space var vz = lightDir; // Z axis in world space vx = worldToView.MultiplyVector(vx); vy = worldToView.MultiplyVector(vy); vz = worldToView.MultiplyVector(vz); l.shadowLightIndex = (light.light.shadows != LightShadows.None) ? (uint)nLight : 0xffffffff; l.lightAxisX = vx; l.lightAxisY = vy; l.lightAxisZ = vz; l.color.Set(light.finalColor.r, light.finalColor.g, light.finalColor.b); l.intensity = light.light.intensity; lights.Add(l); dirLightCount++; } } s_DirLightList.SetData(lights.ToArray()); return dirLightCount; } void UpdateShadowConstants(IList visibleLights, ref ShadowOutput shadow) { var nNumLightsIncludingTooMany = 0; var numLights = 0; var lightShadowIndex_LightParams = new Vector4[k_MaxLights]; var lightFalloffParams = new Vector4[k_MaxLights]; for (int nLight = 0; nLight < visibleLights.Count; nLight++) { nNumLightsIncludingTooMany++; if (nNumLightsIncludingTooMany > k_MaxLights) continue; var light = visibleLights[nLight]; var lightType = light.lightType; var position = light.light.transform.position; var lightDir = light.light.transform.forward.normalized; // Setup shadow data arrays var hasShadows = shadow.GetShadowSliceCountLightIndex(nLight) != 0; if (lightType == LightType.Directional) { lightShadowIndex_LightParams[numLights] = new Vector4(0, 0, 1, 1); lightFalloffParams[numLights] = new Vector4(0.0f, 0.0f, float.MaxValue, (float)lightType); if (hasShadows) { for (int s = 0; s < k_MaxDirectionalSplit; ++s) { m_DirShadowSplitSpheres[s] = shadow.directionalShadowSplitSphereSqr[s]; } } } else if (lightType == LightType.Point) { lightShadowIndex_LightParams[numLights] = new Vector4(0, 0, 1, 1); lightFalloffParams[numLights] = new Vector4(1.0f, 0.0f, light.range * light.range, (float)lightType); } else if (lightType == LightType.Spot) { lightShadowIndex_LightParams[numLights] = new Vector4(0, 0, 1, 1); lightFalloffParams[numLights] = new Vector4(1.0f, 0.0f, light.range * light.range, (float)lightType); } if (hasShadows) { // Enable shadows lightShadowIndex_LightParams[numLights].x = 1; for (int s = 0; s < shadow.GetShadowSliceCountLightIndex(nLight); ++s) { var shadowSliceIndex = shadow.GetShadowSliceIndex(nLight, s); m_MatWorldToShadow[numLights * k_MaxShadowmapPerLights + s] = shadow.shadowSlices[shadowSliceIndex].shadowTransform.transpose; } } numLights++; } // Warn if too many lights found if (nNumLightsIncludingTooMany > k_MaxLights) { if (nNumLightsIncludingTooMany > m_WarnedTooManyLights) { Debug.LogError("ERROR! Found " + nNumLightsIncludingTooMany + " runtime lights! Valve renderer supports up to " + k_MaxLights + " active runtime lights at a time!\nDisabling " + (nNumLightsIncludingTooMany - k_MaxLights) + " runtime light" + ((nNumLightsIncludingTooMany - k_MaxLights) > 1 ? "s" : "") + "!\n"); } m_WarnedTooManyLights = nNumLightsIncludingTooMany; } else { if (m_WarnedTooManyLights > 0) { m_WarnedTooManyLights = 0; Debug.Log("SUCCESS! Found " + nNumLightsIncludingTooMany + " runtime lights which is within the supported number of lights, " + k_MaxLights + ".\n\n"); } } // PCF 3x3 Shadows var flTexelEpsilonX = 1.0f / m_ShadowSettings.shadowAtlasWidth; var flTexelEpsilonY = 1.0f / m_ShadowSettings.shadowAtlasHeight; m_Shadow3X3PCFTerms[0] = new Vector4(20.0f / 267.0f, 33.0f / 267.0f, 55.0f / 267.0f, 0.0f); m_Shadow3X3PCFTerms[1] = new Vector4(flTexelEpsilonX, flTexelEpsilonY, -flTexelEpsilonX, -flTexelEpsilonY); m_Shadow3X3PCFTerms[2] = new Vector4(flTexelEpsilonX, flTexelEpsilonY, 0.0f, 0.0f); m_Shadow3X3PCFTerms[3] = new Vector4(-flTexelEpsilonX, -flTexelEpsilonY, 0.0f, 0.0f); } int GenerateSourceLightBuffers(Camera camera, CullResults inputs) { var probes = inputs.visibleReflectionProbes; //ReflectionProbe[] probes = Object.FindObjectsOfType(); var numModels = (int)LightDefinitions.NR_LIGHT_MODELS; var numVolTypes = (int)LightDefinitions.MAX_TYPES; var numEntries = new int[numModels,numVolTypes]; var offsets = new int[numModels,numVolTypes]; var numEntries2nd = new int[numModels,numVolTypes]; // first pass. Figure out how much we have of each and establish offsets foreach (var cl in inputs.visibleLights) { var volType = cl.lightType==LightType.Spot ? LightDefinitions.SPOT_LIGHT : (cl.lightType==LightType.Point ? LightDefinitions.SPHERE_LIGHT : -1); if(volType>=0) ++numEntries[LightDefinitions.DIRECT_LIGHT,volType]; } foreach (var rl in probes) { var volType = LightDefinitions.BOX_LIGHT; // always a box for now if(rl.texture!=null) ++numEntries[LightDefinitions.REFLECTION_LIGHT,volType]; } // add decals here too similar to the above // establish offsets for(var m=0; m 0.0f ? (si / cs) : FltMax; var cota = si > 0.0f ? (cs / si) : FltMax; //const float cotasa = l.GetCotanHalfSpotAngle(); // apply nonuniform scale to OBB of spot light var squeeze = true;//sa < 0.7f * 90.0f; // arb heuristic var fS = squeeze ? ta : si; bound.center = worldToView.MultiplyPoint(lightPos + ((0.5f * range) * lightDir)); // use mid point of the spot as the center of the bounding volume for building screen-space AABB for tiled lighting. light.lightAxisX = vx; light.lightAxisY = vy; light.lightAxisZ = vz; // scale axis to match box or base of pyramid bound.boxAxisX = (fS * range) * vx; bound.boxAxisY = (fS * range) * vy; bound.boxAxisZ = (0.5f * range) * vz; // generate bounding sphere radius var fAltDx = si; var fAltDy = cs; fAltDy = fAltDy - 0.5f; //if(fAltDy<0) fAltDy=-fAltDy; fAltDx *= range; fAltDy *= range; var altDist = Mathf.Sqrt(fAltDy * fAltDy + (isCircularSpot ? 1.0f : 2.0f) * fAltDx * fAltDx); bound.radius = altDist > (0.5f * range) ? altDist : (0.5f * range); // will always pick fAltDist bound.scaleXY = squeeze ? new Vector2(0.01f, 0.01f) : new Vector2(1.0f, 1.0f); // fill up ldata light.lightType = (uint)LightDefinitions.SPOT_LIGHT; light.lightPos = worldToView.MultiplyPoint(lightPos); light.radiusSq = range * range; light.penumbra = cs; light.cotan = cota; light.flags |= (isCircularSpot ? LightDefinitions.IS_CIRCULAR_SPOT_SHAPE : 0); light.flags |= (bHasCookie ? LightDefinitions.HAS_COOKIE_TEXTURE : 0); light.flags |= (bHasShadow ? LightDefinitions.HAS_SHADOW : 0); int i = LightDefinitions.DIRECT_LIGHT, j = LightDefinitions.SPOT_LIGHT; idxOut = numEntries2nd[i,j] + offsets[i,j]; ++numEntries2nd[i,j]; } else if (cl.lightType == LightType.Point) { if (bHasCookie) { light.sliceIndex = m_CubeCookieTexArray.FetchSlice(cl.light.cookie); } bound.center = worldToView.MultiplyPoint(lightPos); bound.boxAxisX.Set(range, 0, 0); bound.boxAxisY.Set(0, range, 0); bound.boxAxisZ.Set(0, 0, -range); // transform to camera space (becomes a left hand coordinate frame in Unity since Determinant(worldToView)<0) bound.scaleXY.Set(1.0f, 1.0f); bound.radius = range; // represents a left hand coordinate system in world space since det(worldToView)<0 var lightToView = worldToView * lightToWorld; Vector3 vx = lightToView.GetColumn(0); Vector3 vy = lightToView.GetColumn(1); Vector3 vz = lightToView.GetColumn(2); // fill up ldata light.lightType = (uint)LightDefinitions.SPHERE_LIGHT; light.lightPos = bound.center; light.radiusSq = range * range; light.lightAxisX = vx; light.lightAxisY = vy; light.lightAxisZ = vz; light.flags |= (bHasCookie ? LightDefinitions.HAS_COOKIE_TEXTURE : 0); light.flags |= (bHasShadow ? LightDefinitions.HAS_SHADOW : 0); int i = LightDefinitions.DIRECT_LIGHT, j = LightDefinitions.SPHERE_LIGHT; idxOut = numEntries2nd[i,j] + offsets[i,j]; ++numEntries2nd[i,j]; } else { //Assert(false); } // next light if (cl.lightType == LightType.Spot || cl.lightType == LightType.Point) { boundData[idxOut] = bound; lightData[idxOut] = light; } } var numLightsOut = offsets[LightDefinitions.DIRECT_LIGHT, numVolTypes-1] + numEntries[LightDefinitions.DIRECT_LIGHT, numVolTypes-1]; // probe.m_BlendDistance // Vector3f extents = 0.5*Abs(probe.m_BoxSize); // C center of rendered refl box <-- GetComponent (Transform).GetPosition() + m_BoxOffset; // cube map capture point: GetComponent (Transform).GetPosition() // shader parameter min and max are C+/-(extents+blendDistance) foreach (var rl in probes) { var cubemap = rl.texture; // always a box for now if (cubemap == null) continue; var bndData = new SFiniteLightBound(); var lgtData = new SFiniteLightData(); var idxOut = 0; lgtData.flags = 0; var bnds = rl.bounds; var boxOffset = rl.center; // reflection volume offset relative to cube map capture point var blendDistance = rl.blendDistance; float imp = rl.importance; var mat = rl.localToWorld; //Matrix4x4 mat = rl.transform.localToWorldMatrix; Vector3 cubeCapturePos = mat.GetColumn(3); // cube map capture position in world space // implicit in CalculateHDRDecodeValues() --> float ints = rl.intensity; var boxProj = (rl.boxProjection != 0); var decodeVals = rl.hdr; //Vector4 decodeVals = rl.CalculateHDRDecodeValues(); // C is reflection volume center in world space (NOT same as cube map capture point) var e = bnds.extents; // 0.5f * Vector3.Max(-boxSizes[p], boxSizes[p]); //Vector3 C = bnds.center; // P + boxOffset; var C = mat.MultiplyPoint(boxOffset); // same as commented out line above when rot is identity //Vector3 posForShaderParam = bnds.center - boxOffset; // gives same as rl.GetComponent().position; var posForShaderParam = cubeCapturePos; // same as commented out line above when rot is identity var combinedExtent = e + new Vector3(blendDistance, blendDistance, blendDistance); Vector3 vx = mat.GetColumn(0); Vector3 vy = mat.GetColumn(1); Vector3 vz = mat.GetColumn(2); // transform to camera space (becomes a left hand coordinate frame in Unity since Determinant(worldToView)<0) vx = worldToView.MultiplyVector(vx); vy = worldToView.MultiplyVector(vy); vz = worldToView.MultiplyVector(vz); var Cw = worldToView.MultiplyPoint(C); if (boxProj) lgtData.flags |= LightDefinitions.IS_BOX_PROJECTED; lgtData.lightPos = Cw; lgtData.lightAxisX = vx; lgtData.lightAxisY = vy; lgtData.lightAxisZ = vz; lgtData.localCubeCapturePoint = -boxOffset; lgtData.probeBlendDistance = blendDistance; lgtData.lightIntensity = decodeVals.x; lgtData.decodeExp = decodeVals.y; lgtData.sliceIndex = m_CubeReflTexArray.FetchSlice(cubemap); var delta = combinedExtent - e; lgtData.boxInnerDist = e; lgtData.boxInvRange.Set(1.0f / delta.x, 1.0f / delta.y, 1.0f / delta.z); bndData.center = Cw; bndData.boxAxisX = combinedExtent.x * vx; bndData.boxAxisY = combinedExtent.y * vy; bndData.boxAxisZ = combinedExtent.z * vz; bndData.scaleXY.Set(1.0f, 1.0f); bndData.radius = combinedExtent.magnitude; // fill up ldata lgtData.lightType = (uint)LightDefinitions.BOX_LIGHT; lgtData.lightModel = (uint)LightDefinitions.REFLECTION_LIGHT; int i = LightDefinitions.REFLECTION_LIGHT, j = LightDefinitions.BOX_LIGHT; idxOut = numEntries2nd[i,j] + offsets[i,j]; ++numEntries2nd[i,j]; boundData[idxOut] = bndData; lightData[idxOut] = lgtData; } var numProbesOut = offsets[LightDefinitions.REFLECTION_LIGHT, numVolTypes-1] + numEntries[LightDefinitions.REFLECTION_LIGHT, numVolTypes-1]; for(var m=0; m 0 && s_HeightOnRecord > 0) ReleaseResolutionDependentBuffers(); AllocResolutionDependentBuffers(curWidth, curHeight); // update recorded window resolution s_WidthOnRecord = curWidth; s_HeightOnRecord = curHeight; } } void ReleaseResolutionDependentBuffers() { if (s_LightList != null) s_LightList.Release(); if (enableClustered) { if (s_PerVoxelLightLists != null) s_PerVoxelLightLists.Release(); if (s_PerVoxelOffset != null) s_PerVoxelOffset.Release(); if (k_UseDepthBuffer && s_PerTileLogBaseTweak != null) s_PerTileLogBaseTweak.Release(); } if(enableBigTilePrepass) { if(s_BigTileLightList!=null) s_BigTileLightList.Release(); } } int NumLightIndicesPerClusteredTile() { return 8 * (1 << k_Log2NumClusters); // total footprint for all layers of the tile (measured in light index entries) } void AllocResolutionDependentBuffers(int width, int height) { var nrTilesX = (width + 15) / 16; var nrTilesY = (height + 15) / 16; var nrTiles = nrTilesX * nrTilesY; const int capacityUShortsPerTile = 32; const int dwordsPerTile = (capacityUShortsPerTile + 1) >> 1; // room for 31 lights and a nrLights value. s_LightList = new ComputeBuffer(LightDefinitions.NR_LIGHT_MODELS * dwordsPerTile * nrTiles, sizeof(uint)); // enough list memory for a 4k x 4k display if (enableClustered) { s_PerVoxelOffset = new ComputeBuffer(LightDefinitions.NR_LIGHT_MODELS * (1 << k_Log2NumClusters) * nrTiles, sizeof(uint)); s_PerVoxelLightLists = new ComputeBuffer(NumLightIndicesPerClusteredTile() * nrTiles, sizeof(uint)); if (k_UseDepthBuffer) { s_PerTileLogBaseTweak = new ComputeBuffer(nrTiles, sizeof(float)); } } if(enableBigTilePrepass) { var nrBigTilesX = (width + 63) / 64; var nrBigTilesY = (height + 63) / 64; var nrBigTiles = nrBigTilesX * nrBigTilesY; s_BigTileLightList = new ComputeBuffer(LightDefinitions.MAX_NR_BIGTILE_LIGHTS_PLUSONE * nrBigTiles, sizeof(uint)); } } void VoxelLightListGeneration(CommandBuffer cmd, Camera camera, int numLights, Matrix4x4 projscr, Matrix4x4 invProjscr) { // clear atomic offset index cmd.SetComputeBufferParam(buildPerVoxelLightListShader, s_ClearVoxelAtomicKernel, "g_LayeredSingleIdxBuffer", s_GlobalLightListAtomic); cmd.DispatchCompute(buildPerVoxelLightListShader, s_ClearVoxelAtomicKernel, 1, 1, 1); cmd.SetComputeIntParam(buildPerVoxelLightListShader, "g_iNrVisibLights", numLights); SetMatrixCS(cmd, buildPerVoxelLightListShader, "g_mScrProjection", projscr); SetMatrixCS(cmd, buildPerVoxelLightListShader, "g_mInvScrProjection", invProjscr); cmd.SetComputeIntParam(buildPerVoxelLightListShader, "g_iLog2NumClusters", k_Log2NumClusters); //Vector4 v2_near = invProjscr * new Vector4(0.0f, 0.0f, 0.0f, 1.0f); //Vector4 v2_far = invProjscr * new Vector4(0.0f, 0.0f, 1.0f, 1.0f); //float nearPlane2 = -(v2_near.z/v2_near.w); //float farPlane2 = -(v2_far.z/v2_far.w); var nearPlane = camera.nearClipPlane; var farPlane = camera.farClipPlane; cmd.SetComputeFloatParam(buildPerVoxelLightListShader, "g_fNearPlane", nearPlane); cmd.SetComputeFloatParam(buildPerVoxelLightListShader, "g_fFarPlane", farPlane); const float C = (float)(1 << k_Log2NumClusters); var geomSeries = (1.0 - Mathf.Pow(k_ClustLogBase, C)) / (1 - k_ClustLogBase); // geometric series: sum_k=0^{C-1} base^k m_ClustScale = (float)(geomSeries / (farPlane - nearPlane)); cmd.SetComputeFloatParam(buildPerVoxelLightListShader, "g_fClustScale", m_ClustScale); cmd.SetComputeFloatParam(buildPerVoxelLightListShader, "g_fClustBase", k_ClustLogBase); cmd.SetComputeTextureParam(buildPerVoxelLightListShader, s_GenListPerVoxelKernel, "g_depth_tex", new RenderTargetIdentifier(s_CameraDepthTexture)); cmd.SetComputeBufferParam(buildPerVoxelLightListShader, s_GenListPerVoxelKernel, "g_vLayeredLightList", s_PerVoxelLightLists); cmd.SetComputeBufferParam(buildPerVoxelLightListShader, s_GenListPerVoxelKernel, "g_LayeredOffset", s_PerVoxelOffset); cmd.SetComputeBufferParam(buildPerVoxelLightListShader, s_GenListPerVoxelKernel, "g_LayeredSingleIdxBuffer", s_GlobalLightListAtomic); if(enableBigTilePrepass) cmd.SetComputeBufferParam(buildPerVoxelLightListShader, s_GenListPerVoxelKernel, "g_vBigTileLightList", s_BigTileLightList); if (k_UseDepthBuffer) { cmd.SetComputeBufferParam(buildPerVoxelLightListShader, s_GenListPerVoxelKernel, "g_logBaseBuffer", s_PerTileLogBaseTweak); } var numTilesX = (camera.pixelWidth + 15) / 16; var numTilesY = (camera.pixelHeight + 15) / 16; cmd.DispatchCompute(buildPerVoxelLightListShader, s_GenListPerVoxelKernel, numTilesX, numTilesY, 1); } void PushGlobalParams(Camera camera, RenderLoop loop, Matrix4x4 viewToWorld, Matrix4x4 scrProj, Matrix4x4 incScrProj, int numDirLights) { var cmd = new CommandBuffer { name = "Push Global Parameters" }; cmd.SetGlobalFloat("g_widthRT", (float)camera.pixelWidth); cmd.SetGlobalFloat("g_heightRT", (float)camera.pixelHeight); cmd.SetGlobalMatrix("g_mViewToWorld", viewToWorld); cmd.SetGlobalMatrix("g_mWorldToView", viewToWorld.inverse); cmd.SetGlobalMatrix("g_mScrProjection", scrProj); cmd.SetGlobalMatrix("g_mInvScrProjection", incScrProj); cmd.SetGlobalBuffer("g_vLightData", s_LightDataBuffer); cmd.SetGlobalTexture("_spotCookieTextures", m_CookieTexArray.GetTexCache()); cmd.SetGlobalTexture("_pointCookieTextures", m_CubeCookieTexArray.GetTexCache()); cmd.SetGlobalTexture("_reflCubeTextures", m_CubeReflTexArray.GetTexCache()); var topCube = ReflectionProbe.GetDefaultCubemapIBL(); var defdecode = ReflectionProbe.CalculateHDRDecodeValuesForDefaultTexture(); cmd.SetGlobalTexture("_reflRootCubeTexture", topCube); cmd.SetGlobalFloat("_reflRootHdrDecodeMult", defdecode.x); cmd.SetGlobalFloat("_reflRootHdrDecodeExp", defdecode.y); if(enableBigTilePrepass) cmd.SetGlobalBuffer("g_vBigTileLightList", s_BigTileLightList); if (enableClustered) { cmd.SetGlobalFloat("g_fClustScale", m_ClustScale); cmd.SetGlobalFloat("g_fClustBase", k_ClustLogBase); cmd.SetGlobalFloat("g_fNearPlane", camera.nearClipPlane); cmd.SetGlobalFloat("g_fFarPlane", camera.farClipPlane); cmd.SetGlobalFloat("g_iLog2NumClusters", k_Log2NumClusters); cmd.SetGlobalFloat("g_isLogBaseBufferEnabled", k_UseDepthBuffer ? 1 : 0); cmd.SetGlobalBuffer("g_vLayeredOffsetsBuffer", s_PerVoxelOffset); if (k_UseDepthBuffer) { cmd.SetGlobalBuffer("g_logBaseBuffer", s_PerTileLogBaseTweak); } } cmd.SetGlobalFloat("g_nNumDirLights", numDirLights); cmd.SetGlobalBuffer("g_dirLightData", s_DirLightList); // Shadow constants cmd.SetGlobalMatrixArray("g_matWorldToShadow", m_MatWorldToShadow); cmd.SetGlobalVectorArray("g_vDirShadowSplitSpheres", m_DirShadowSplitSpheres); cmd.SetGlobalVector("g_vShadow3x3PCFTerms0", m_Shadow3X3PCFTerms[0]); cmd.SetGlobalVector("g_vShadow3x3PCFTerms1", m_Shadow3X3PCFTerms[1]); cmd.SetGlobalVector("g_vShadow3x3PCFTerms2", m_Shadow3X3PCFTerms[2]); cmd.SetGlobalVector("g_vShadow3x3PCFTerms3", m_Shadow3X3PCFTerms[3]); loop.ExecuteCommandBuffer(cmd); cmd.Dispose(); } } }