using UnityEngine.Rendering; using System; using System.Collections.Generic; namespace UnityEngine.Experimental.Rendering.Fptl { class ShadowSetup : IDisposable { // shadow related stuff const int k_MaxShadowDataSlots = 64; const int k_MaxPayloadSlotsPerShadowData = 4; ShadowmapBase[] m_Shadowmaps; ShadowManager m_ShadowMgr; static ComputeBuffer s_ShadowDataBuffer; static ComputeBuffer s_ShadowPayloadBuffer; public ShadowSetup(ShadowInitParameters shadowInit, ShadowSettings shadowSettings, out IShadowManager shadowManager) { s_ShadowDataBuffer = new ComputeBuffer(k_MaxShadowDataSlots, System.Runtime.InteropServices.Marshal.SizeOf(typeof(ShadowData))); s_ShadowPayloadBuffer = new ComputeBuffer(k_MaxShadowDataSlots * k_MaxPayloadSlotsPerShadowData, System.Runtime.InteropServices.Marshal.SizeOf(typeof(ShadowPayload))); ShadowAtlas.AtlasInit atlasInit; atlasInit.baseInit.width = (uint)shadowInit.shadowAtlasWidth; atlasInit.baseInit.height = (uint)shadowInit.shadowAtlasHeight; atlasInit.baseInit.slices = 1; atlasInit.baseInit.shadowmapBits = 32; atlasInit.baseInit.shadowmapFormat = RenderTextureFormat.Shadowmap; atlasInit.baseInit.samplerState = SamplerState.Default(); atlasInit.baseInit.comparisonSamplerState = ComparisonSamplerState.Default(); atlasInit.baseInit.clearColor = new Vector4(0.0f, 0.0f, 0.0f, 0.0f); atlasInit.baseInit.maxPayloadCount = 0; atlasInit.baseInit.shadowSupport = ShadowmapBase.ShadowSupport.Directional | ShadowmapBase.ShadowSupport.Point | ShadowmapBase.ShadowSupport.Spot; atlasInit.shaderKeyword = null; m_Shadowmaps = new ShadowmapBase[] { new ShadowAtlas(ref atlasInit) }; ShadowContext.SyncDel syncer = (ShadowContext sc) => { // update buffers uint offset, count; ShadowData[] sds; sc.GetShadowDatas(out sds, out offset, out count); Debug.Assert(offset == 0); s_ShadowDataBuffer.SetData(sds); // unfortunately we can't pass an offset or count to this function ShadowPayload[] payloads; sc.GetPayloads(out payloads, out offset, out count); Debug.Assert(offset == 0); s_ShadowPayloadBuffer.SetData(payloads); }; // binding code. This needs to be in sync with ShadowContext.hlsl ShadowContext.BindDel binder = (ShadowContext sc, CommandBuffer cb, ComputeShader computeShader, int computeKernel) => { // bind buffers cb.SetGlobalBuffer("_ShadowDatasExp", s_ShadowDataBuffer); cb.SetGlobalBuffer("_ShadowPayloads", s_ShadowPayloadBuffer); // bind textures uint offset, count; RenderTargetIdentifier[] tex; sc.GetTex2DArrays(out tex, out offset, out count); cb.SetGlobalTexture("_ShadowmapExp_PCF", tex[0]); // TODO: Currently samplers are hard coded in ShadowContext.hlsl, so we can't really set them here }; ShadowContext.CtxtInit scInit; scInit.storage.maxShadowDataSlots = k_MaxShadowDataSlots; scInit.storage.maxPayloadSlots = k_MaxShadowDataSlots * k_MaxPayloadSlotsPerShadowData; scInit.storage.maxTex2DArraySlots = 1; scInit.storage.maxTexCubeArraySlots = 0; scInit.storage.maxComparisonSamplerSlots = 1; scInit.storage.maxSamplerSlots = 0; scInit.dataSyncer = syncer; scInit.resourceBinder = binder; m_ShadowMgr = new ShadowManager(shadowSettings, ref scInit, m_Shadowmaps); // set global overrides - these need to match the override specified in ShadowDispatch.hlsl m_ShadowMgr.SetGlobalShadowOverride( GPUShadowType.Point , ShadowAlgorithm.PCF, ShadowVariant.V1, ShadowPrecision.High, true ); m_ShadowMgr.SetGlobalShadowOverride( GPUShadowType.Spot , ShadowAlgorithm.PCF, ShadowVariant.V1, ShadowPrecision.High, true ); m_ShadowMgr.SetGlobalShadowOverride( GPUShadowType.Directional , ShadowAlgorithm.PCF, ShadowVariant.V1, ShadowPrecision.High, true ); shadowManager = m_ShadowMgr; } public void Dispose() { if (m_Shadowmaps != null) { (m_Shadowmaps[0] as ShadowAtlas).Dispose(); m_Shadowmaps = null; } m_ShadowMgr = null; if( s_ShadowDataBuffer != null ) s_ShadowDataBuffer.Release(); if( s_ShadowPayloadBuffer != null ) s_ShadowPayloadBuffer.Release(); } } public class FptlLightingInstance : RenderPipeline { private readonly FptlLighting m_Owner; public FptlLightingInstance(FptlLighting owner) { m_Owner = owner; if (m_Owner != null) m_Owner.Build(); } public override void Dispose() { base.Dispose(); if (m_Owner != null) m_Owner.Cleanup(); } public override void Render(ScriptableRenderContext renderContext, Camera[] cameras) { base.Render(renderContext, cameras); m_Owner.Render(renderContext, cameras); } } [ExecuteInEditMode] public class FptlLighting : RenderPipelineAsset { #if UNITY_EDITOR [UnityEditor.MenuItem("RenderPipeline/Create FPTLRenderPipeline")] static void CreateRenderLoopFPTL() { var instance = ScriptableObject.CreateInstance(); UnityEditor.AssetDatabase.CreateAsset(instance, "Assets/ScriptableRenderPipeline/fptl/FPTLRenderPipeline.asset"); //AssetDatabase.CreateAsset(instance, "Assets/ScriptableRenderPipeline/fptl/FPTLRenderPipeline.asset"); } #endif protected override IRenderPipeline InternalCreatePipeline() { return new FptlLightingInstance(this); } [SerializeField] ShadowSettings m_ShadowSettings = new ShadowSettings(); ShadowSetup m_ShadowSetup; IShadowManager m_ShadowMgr; FrameId m_FrameId = new FrameId(); List m_ShadowRequests = new List(); Dictionary m_ShadowIndices = new Dictionary(); void InitShadowSystem(ShadowSettings shadowSettings) { m_ShadowSetup = new ShadowSetup(new ShadowInitParameters(), shadowSettings, out m_ShadowMgr); } void DeinitShadowSystem() { if (m_ShadowSetup != null) { m_ShadowSetup.Dispose(); m_ShadowSetup = null; m_ShadowMgr = null; } } [SerializeField] TextureSettings m_TextureSettings = new TextureSettings(); 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; public bool enableReflectionProbeDebug = false; const bool k_UseDepthBuffer = true;// // only has an impact when EnableClustered is true (requires a depth-prepass) const bool k_UseAsyncCompute = true;// should not use on mobile const int k_Log2NumClusters = 6; // accepted range is from 0 to 6. NumClusters is 1< visibleLights, Dictionary shadowIndices) { var dirLightCount = 0; var lights = new List(); var worldToView = WorldToCamera(camera); 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); int shadowIdx; l.shadowLightIndex = shadowIndices.TryGetValue((int)nLight, out shadowIdx) ? (uint)shadowIdx : 0x80000000; 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); return dirLightCount; } int GenerateSourceLightBuffers(Camera camera, CullResults inputs) { // 0. deal with shadows { m_FrameId.frameCount++; // get the indices for all lights that want to have shadows m_ShadowRequests.Clear(); m_ShadowRequests.Capacity = inputs.visibleLights.Count; int lcnt = inputs.visibleLights.Count; for (int i = 0; i < lcnt; ++i) { VisibleLight vl = inputs.visibleLights[i]; AdditionalShadowData asd = vl.light.GetComponent(); if (vl.light.shadows != LightShadows.None && asd != null && asd.shadowDimmer > 0.0f) m_ShadowRequests.Add(i); } // pass this list to a routine that assigns shadows based on some heuristic uint shadowRequestCount = (uint)m_ShadowRequests.Count; int[] shadowRequests = m_ShadowRequests.ToArray(); int[] shadowDataIndices; m_ShadowMgr.ProcessShadowRequests(m_FrameId, inputs, camera, false, inputs.visibleLights, ref shadowRequestCount, shadowRequests, out shadowDataIndices); // update the visibleLights with the shadow information m_ShadowIndices.Clear(); for (uint i = 0; i < shadowRequestCount; i++) { m_ShadowIndices.Add(shadowRequests[i], shadowDataIndices[i]); } } 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 < numModels; m++) { offsets[m, 0] = m == 0 ? 0 : (numEntries[m - 1, numVolTypes - 1] + offsets[m - 1, numVolTypes - 1]); for (var v = 1; v < numVolTypes; v++) offsets[m, v] = numEntries[m, v - 1] + offsets[m, v - 1]; } var numLights = inputs.visibleLights.Count; var numProbes = probes.Count; var numVolumes = numLights + numProbes; var lightData = new SFiniteLightData[numVolumes]; var boundData = new SFiniteLightBound[numVolumes]; var worldToView = WorldToCamera(camera); bool isNegDeterminant = Vector3.Dot(worldToView.GetColumn(0), Vector3.Cross(worldToView.GetColumn(1), worldToView.GetColumn(2))) < 0.0f; // 3x3 Determinant. uint shadowLightIndex = 0; foreach (var cl in inputs.visibleLights) { var range = cl.range; var lightToWorld = cl.localToWorld; Vector3 lightPos = lightToWorld.GetColumn(3); var bound = new SFiniteLightBound(); var light = new SFiniteLightData(); bound.boxAxisX.Set(1, 0, 0); bound.boxAxisY.Set(0, 1, 0); bound.boxAxisZ.Set(0, 0, 1); bound.scaleXY.Set(1.0f, 1.0f); bound.radius = range; light.flags = 0; light.recipRange = 1.0f / range; light.color.Set(cl.finalColor.r, cl.finalColor.g, cl.finalColor.b); light.sliceIndex = 0; light.lightModel = (uint)LightDefinitions.DIRECT_LIGHT; int shadowIdx; light.shadowLightIndex = m_ShadowIndices.TryGetValue( (int) shadowLightIndex, out shadowIdx ) ? (uint) shadowIdx : 0x80000000; shadowLightIndex++; var bHasCookie = cl.light.cookie != null; var bHasShadow = cl.light.shadows != LightShadows.None; var idxOut = 0; if (cl.lightType == LightType.Spot) { var isCircularSpot = !bHasCookie; if (!isCircularSpot) // square spots always have cookie { light.sliceIndex = m_CookieTexArray.FetchSlice(cl.light.cookie); } 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 // 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); const float pi = 3.1415926535897932384626433832795f; const float degToRad = (float)(pi / 180.0); var sa = cl.light.spotAngle; var cs = Mathf.Cos(0.5f * sa * degToRad); var si = Mathf.Sin(0.5f * sa * degToRad); var ta = cs > 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, isNegDeterminant ? (-range) : 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; } } int numLightsOut = 0; for(int v=0; v 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 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; } int numProbesOut = 0; for(int v=0; v cameras) { foreach (var camera in cameras) { ScriptableCullingParameters cullingParams; if (!CullResults.GetCullingParameters(camera, out cullingParams)) continue; m_ShadowMgr.UpdateCullingParameters( ref cullingParams ); CullResults.Cull(ref cullingParams, renderContext, ref m_CullResults); ExecuteRenderLoop(camera, m_CullResults, renderContext); } renderContext.Submit(); } void FinalPass(ScriptableRenderContext loop) { var cmd = CommandBufferPool.Get("FinalPass"); cmd.Blit(s_CameraTarget, BuiltinRenderTextureType.CameraTarget, m_BlitMaterial, 0); loop.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } void ExecuteRenderLoop(Camera camera, CullResults cullResults, ScriptableRenderContext loop) { var w = camera.pixelWidth; var h = camera.pixelHeight; ResizeIfNecessary(w, h); // do anything we need to do upon a new frame. NewFrame(); // generate g-buffer before shadows to leverage async compute // forward opaques just write to depth. loop.SetupCameraProperties(camera); RenderGBuffer(cullResults, camera, loop); DepthOnlyForForwardOpaques(cullResults, camera, loop); CopyDepthAfterGBuffer(loop); // camera to screen matrix (and it's inverse) var proj = CameraProjection(camera); var temp = new Matrix4x4(); temp.SetRow(0, new Vector4(0.5f * w, 0.0f, 0.0f, 0.5f * w)); temp.SetRow(1, new Vector4(0.0f, 0.5f * h, 0.0f, 0.5f * h)); temp.SetRow(2, new Vector4(0.0f, 0.0f, 0.5f, 0.5f)); temp.SetRow(3, new Vector4(0.0f, 0.0f, 0.0f, 1.0f)); var projscr = temp * proj; var invProjscr = projscr.inverse; // build per tile light lists var numLights = GenerateSourceLightBuffers(camera, cullResults); GPUFence postLightListFence; if (k_UseAsyncCompute) { CommandBuffer cmdPreShadows = CommandBufferPool.Get(); GPUFence preShadowsFence = cmdPreShadows.CreateGPUFence(); loop.ExecuteCommandBuffer(cmdPreShadows); CommandBufferPool.Release(cmdPreShadows); postLightListFence = BuildPerTileLightListsAsync(camera, loop, numLights, projscr, invProjscr, preShadowsFence); } else { BuildPerTileLightLists(camera, loop, numLights, projscr, invProjscr); } CommandBuffer cmdShadow = CommandBufferPool.Get(); m_ShadowMgr.RenderShadows( m_FrameId, loop, cmdShadow, cullResults, cullResults.visibleLights ); m_ShadowMgr.SyncData(); m_ShadowMgr.BindResources( cmdShadow, null, 0 ); loop.ExecuteCommandBuffer(cmdShadow); CommandBufferPool.Release(cmdShadow); // Push all global params var numDirLights = UpdateDirectionalLights(camera, cullResults.visibleLights, m_ShadowIndices); if (k_UseAsyncCompute) { PushGlobalParamsWithFence(camera, loop, CameraToWorld(camera), projscr, invProjscr, numDirLights, postLightListFence); } else { PushGlobalParams(camera, loop, CameraToWorld(camera), projscr, invProjscr, numDirLights); } // do deferred lighting DoTiledDeferredLighting(camera, loop, numLights, numDirLights); // render opaques using tiled forward RenderForward(cullResults, camera, loop, true); // opaques only (requires a depth pre-pass) // render the backdrop/canvas m_SkyboxHelper.Draw(loop, camera); // transparencies atm. requires clustered until we get traditional forward if (enableClustered) RenderForward(cullResults, camera, loop, false); // debug views. if (enableDrawLightBoundsDebug) DrawLightBoundsDebug(loop, cullResults.visibleLights.Count); // present frame buffer. FinalPass(loop); // bind depth surface for editor grid/gizmo/selection rendering if (camera.cameraType == CameraType.SceneView) { var cmd = CommandBufferPool.Get(); cmd.SetRenderTarget(BuiltinRenderTextureType.CameraTarget, new RenderTargetIdentifier(s_CameraDepthTexture)); loop.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } loop.Submit(); } void DrawLightBoundsDebug(ScriptableRenderContext loop, int numLights) { var cmd = CommandBufferPool.Get("DrawLightBoundsDebug"); m_DebugLightBoundsMaterial.SetBuffer("g_data", s_ConvexBoundsBuffer); cmd.DrawProcedural(Matrix4x4.identity, m_DebugLightBoundsMaterial, 0, MeshTopology.Triangles, 12 * 3 * numLights); loop.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } void NewFrame() { // update texture caches m_CookieTexArray.NewFrame(); m_CubeCookieTexArray.NewFrame(); m_CubeReflTexArray.NewFrame(); } void RenderShadowMaps(CullResults cullResults, ScriptableRenderContext loop) { } void ResizeIfNecessary(int curWidth, int curHeight) { if (curWidth != s_WidthOnRecord || curHeight != s_HeightOnRecord || s_LightList == null || (s_BigTileLightList == null && enableBigTilePrepass) || (s_PerVoxelLightLists == null && enableClustered)) { if (s_WidthOnRecord > 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) { var tileSizeClust = LightDefinitions.TILE_SIZE_CLUSTERED; var nrTilesClustX = (width + (tileSizeClust - 1)) / tileSizeClust; var nrTilesClustY = (height + (tileSizeClust - 1)) / tileSizeClust; var nrTilesClust = nrTilesClustX * nrTilesClustY; s_PerVoxelOffset = new ComputeBuffer(LightDefinitions.NR_LIGHT_MODELS * (1 << k_Log2NumClusters) * nrTilesClust, sizeof(uint)); s_PerVoxelLightLists = new ComputeBuffer(NumLightIndicesPerClusteredTile() * nrTilesClust, sizeof(uint)); if (k_UseDepthBuffer) { s_PerTileLogBaseTweak = new ComputeBuffer(nrTilesClust, 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); bool isOrthographic = camera.orthographic; cmd.SetComputeIntParam(buildPerVoxelLightListShader, "g_isOrthographic", isOrthographic ? 1 : 0); cmd.SetComputeIntParam(buildPerVoxelLightListShader, "g_iNrVisibLights", numLights); cmd.SetComputeMatrixParam(buildPerVoxelLightListShader, "g_mScrProjection", projscr); cmd.SetComputeMatrixParam(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 tileSizeClust = LightDefinitions.TILE_SIZE_CLUSTERED; var nrTilesClustX = (camera.pixelWidth + (tileSizeClust - 1)) / tileSizeClust; var nrTilesClustY = (camera.pixelHeight + (tileSizeClust - 1)) / tileSizeClust; cmd.DispatchCompute(buildPerVoxelLightListShader, s_GenListPerVoxelKernel, nrTilesClustX, nrTilesClustY, 1); } void BuildPerTileLightLists(Camera camera, ScriptableRenderContext loop, int numLights, Matrix4x4 projscr, Matrix4x4 invProjscr) { var cmd = CommandBufferPool.Get("Build light list"); BuildPerTileLightListsCommon(camera, loop, numLights, projscr, invProjscr, cmd); loop.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } GPUFence BuildPerTileLightListsAsync(Camera camera, ScriptableRenderContext loop, int numLights, Matrix4x4 projscr, Matrix4x4 invProjscr, GPUFence startFence) { var cmd = CommandBufferPool.Get("Build light list"); cmd.WaitOnGPUFence(startFence); BuildPerTileLightListsCommon(camera, loop, numLights, projscr, invProjscr, cmd); GPUFence completeFence = cmd.CreateGPUFence(); loop.ExecuteCommandBufferAsync(cmd, ComputeQueueType.Default); CommandBufferPool.Release(cmd); return completeFence; } void BuildPerTileLightListsCommon(Camera camera, ScriptableRenderContext loop, int numLights, Matrix4x4 projscr, Matrix4x4 invProjscr, CommandBuffer cmd) { var w = camera.pixelWidth; var h = camera.pixelHeight; var numTilesX = (w + 15) / 16; var numTilesY = (h + 15) / 16; var numBigTilesX = (w + 63) / 64; var numBigTilesY = (h + 63) / 64; bool isOrthographic = camera.orthographic; // generate screen-space AABBs (used for both fptl and clustered). if (numLights != 0) { var proj = CameraProjection(camera); var temp = new Matrix4x4(); temp.SetRow(0, new Vector4(1.0f, 0.0f, 0.0f, 0.0f)); temp.SetRow(1, new Vector4(0.0f, 1.0f, 0.0f, 0.0f)); temp.SetRow(2, new Vector4(0.0f, 0.0f, 0.5f, 0.5f)); temp.SetRow(3, new Vector4(0.0f, 0.0f, 0.0f, 1.0f)); var projh = temp * proj; var invProjh = projh.inverse; cmd.SetComputeIntParam(buildScreenAABBShader, "g_isOrthographic", isOrthographic ? 1 : 0); cmd.SetComputeIntParam(buildScreenAABBShader, "g_iNrVisibLights", numLights); cmd.SetComputeMatrixParam(buildScreenAABBShader, "g_mProjection", projh); cmd.SetComputeMatrixParam(buildScreenAABBShader, "g_mInvProjection", invProjh); cmd.SetComputeBufferParam(buildScreenAABBShader, s_GenAABBKernel, "g_vBoundsBuffer", s_AABBBoundsBuffer); cmd.DispatchCompute(buildScreenAABBShader, s_GenAABBKernel, (numLights + 7) / 8, 1, 1); } // enable coarse 2D pass on 64x64 tiles (used for both fptl and clustered). if (enableBigTilePrepass) { cmd.SetComputeIntParam(buildPerBigTileLightListShader, "g_isOrthographic", isOrthographic ? 1 : 0); cmd.SetComputeIntParams(buildPerBigTileLightListShader, "g_viDimensions", new int[2] { w, h }); cmd.SetComputeIntParam(buildPerBigTileLightListShader, "g_iNrVisibLights", numLights); cmd.SetComputeMatrixParam(buildPerBigTileLightListShader, "g_mScrProjection", projscr); cmd.SetComputeMatrixParam(buildPerBigTileLightListShader, "g_mInvScrProjection", invProjscr); cmd.SetComputeFloatParam(buildPerBigTileLightListShader, "g_fNearPlane", camera.nearClipPlane); cmd.SetComputeFloatParam(buildPerBigTileLightListShader, "g_fFarPlane", camera.farClipPlane); cmd.SetComputeBufferParam(buildPerBigTileLightListShader, s_GenListPerBigTileKernel, "g_vLightList", s_BigTileLightList); cmd.DispatchCompute(buildPerBigTileLightListShader, s_GenListPerBigTileKernel, numBigTilesX, numBigTilesY, 1); } if (usingFptl) // optimized for opaques only { cmd.SetComputeIntParam(buildPerTileLightListShader, "g_isOrthographic", isOrthographic ? 1 : 0); cmd.SetComputeIntParams(buildPerTileLightListShader, "g_viDimensions", new int[2] { w, h }); cmd.SetComputeIntParam(buildPerTileLightListShader, "g_iNrVisibLights", numLights); cmd.SetComputeMatrixParam(buildPerTileLightListShader, "g_mScrProjection", projscr); cmd.SetComputeMatrixParam(buildPerTileLightListShader, "g_mInvScrProjection", invProjscr); cmd.SetComputeTextureParam(buildPerTileLightListShader, s_GenListPerTileKernel, "g_depth_tex", new RenderTargetIdentifier(s_CameraDepthTexture)); cmd.SetComputeBufferParam(buildPerTileLightListShader, s_GenListPerTileKernel, "g_vLightList", s_LightList); if (enableBigTilePrepass) cmd.SetComputeBufferParam(buildPerTileLightListShader, s_GenListPerTileKernel, "g_vBigTileLightList", s_BigTileLightList); cmd.DispatchCompute(buildPerTileLightListShader, s_GenListPerTileKernel, numTilesX, numTilesY, 1); } if (enableClustered) // works for transparencies too. { VoxelLightListGeneration(cmd, camera, numLights, projscr, invProjscr); } } void PushGlobalParams(Camera camera, ScriptableRenderContext loop, Matrix4x4 viewToWorld, Matrix4x4 scrProj, Matrix4x4 incScrProj, int numDirLights) { var cmd = CommandBufferPool.Get("Push Global Parameters"); PushGlobalParamsCommon(camera, loop, viewToWorld, scrProj, incScrProj, numDirLights, cmd); CommandBufferPool.Release(cmd); } void PushGlobalParamsWithFence(Camera camera, ScriptableRenderContext loop, Matrix4x4 viewToWorld, Matrix4x4 scrProj, Matrix4x4 incScrProj, int numDirLights, GPUFence startFence) { var cmd = CommandBufferPool.Get("Push Global Parameters"); cmd.WaitOnGPUFence(startFence); PushGlobalParamsCommon(camera, loop, viewToWorld, scrProj, incScrProj, numDirLights, cmd); CommandBufferPool.Release(cmd); } void PushGlobalParamsCommon(Camera camera, ScriptableRenderContext loop, Matrix4x4 viewToWorld, Matrix4x4 scrProj, Matrix4x4 incScrProj, int numDirLights, CommandBuffer cmd) { bool isOrthographic = camera.orthographic; cmd.SetGlobalFloat("g_isOrthographic", (float)(isOrthographic ? 1 : 0)); 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.defaultTexture; var defdecode = ReflectionProbe.defaultTextureHDRDecodeValues; 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); } } }