// Must be in sync with ShaderConfig.cs //#define VELOCITY_IN_GBUFFER using UnityEngine.Rendering; using UnityEngine.Experimental.Rendering; using System.Collections.Generic; using System; namespace UnityEngine.Experimental.ScriptableRenderLoop { [ExecuteInEditMode] // This HDRenderLoop assume linear lighting. Don't work with gamma. public class HDRenderLoop : ScriptableRenderLoop { private const string k_HDRenderLoopPath = "Assets/ScriptableRenderLoop/HDRenderLoop/HDRenderLoop.asset"; public class SkyParameters { public Cubemap skyHDRI; public float rotation; public float exposure; public float multiplier; } [SerializeField] private SkyParameters m_SkyParameters = new SkyParameters(); public SkyParameters skyParameters { get { return m_SkyParameters; } } public class DebugParameters { // Material Debugging public int debugViewMaterial = 0; // Rendering debugging public bool displayOpaqueObjects = true; public bool displayTransparentObjects = true; public bool useForwardRenderingOnly = false; // TODO: Currently there is no way to strip the extra forward shaders generated by the shaders compiler, so we can switch dynamically. public bool useDepthPrepass = false; public bool enableTonemap = true; public float exposure = 0; } private DebugParameters m_DebugParameters = new DebugParameters(); public DebugParameters debugParameters { get { return m_DebugParameters; } } #if UNITY_EDITOR [UnityEditor.MenuItem("Renderloop/CreateHDRenderLoop")] static void CreateHDRenderLoop() { var instance = ScriptableObject.CreateInstance(); UnityEditor.AssetDatabase.CreateAsset(instance, k_HDRenderLoopPath); } #endif public class GBufferManager { public const int MaxGbuffer = 8; public void SetBufferDescription(int index, string stringId, RenderTextureFormat inFormat, RenderTextureReadWrite inSRGBWrite) { IDs[index] = Shader.PropertyToID(stringId); RTIDs[index] = new RenderTargetIdentifier(IDs[index]); formats[index] = inFormat; sRGBWrites[index] = inSRGBWrite; } public void InitGBuffers(int width, int height, CommandBuffer cmd) { for (int index = 0; index < gbufferCount; index++) { /* RTs[index] = */ cmd.GetTemporaryRT(IDs[index], width, height, 0, FilterMode.Point, formats[index], sRGBWrites[index]); } } public RenderTargetIdentifier[] GetGBuffers(CommandBuffer cmd) { var colorMRTs = new RenderTargetIdentifier[gbufferCount]; for (int index = 0; index < gbufferCount; index++) { colorMRTs[index] = RTIDs[index]; } return colorMRTs; } /* public void BindBuffers(Material mat) { for (int index = 0; index < gbufferCount; index++) { mat.SetTexture(IDs[index], RTs[index]); } } */ public int gbufferCount { get; set; } int[] IDs = new int[MaxGbuffer]; RenderTargetIdentifier[] RTIDs = new RenderTargetIdentifier[MaxGbuffer]; RenderTextureFormat[] formats = new RenderTextureFormat[MaxGbuffer]; RenderTextureReadWrite[] sRGBWrites = new RenderTextureReadWrite[MaxGbuffer]; } public const int MaxLights = 32; public const int MaxShadows = 16; // Max shadow allowed on screen simultaneously - a point light is 6 shadows public const int MaxProbes = 32; [SerializeField] ShadowSettings m_ShadowSettings = ShadowSettings.Default; ShadowRenderPass m_ShadowPass; [SerializeField] TextureSettings m_TextureSettings = TextureSettings.Default; Material m_SkyboxMaterial; Material m_SkyHDRIMaterial; Material m_DeferredMaterial; Material m_FinalPassMaterial; // TODO: Find a way to automatically create/iterate through deferred material Lit.RenderLoop m_LitRenderLoop; // Debug Material m_DebugViewMaterialGBuffer; GBufferManager m_gbufferManager = new GBufferManager(); private int s_CameraColorBuffer; private int s_CameraDepthBuffer; private int s_VelocityBuffer; private int s_DistortionBuffer; private ComputeBuffer s_punctualLightList; private ComputeBuffer s_envLightList; private ComputeBuffer s_areaLightList; private ComputeBuffer s_punctualShadowList; private TextureCacheCubemap m_cubeReflTexArray; void OnEnable() { Rebuild(); } void OnValidate() { Rebuild(); } void ClearComputeBuffers() { if (s_punctualLightList != null) s_punctualLightList.Release(); if (s_areaLightList != null) s_areaLightList.Release(); if (s_punctualShadowList != null) s_punctualShadowList.Release(); if (s_envLightList != null) s_envLightList.Release(); } Material CreateEngineMaterial(string shaderPath) { var mat = new Material(Shader.Find(shaderPath) as Shader) { hideFlags = HideFlags.HideAndDontSave }; return mat; } public override void Rebuild() { ClearComputeBuffers(); s_CameraColorBuffer = Shader.PropertyToID("_CameraColorTexture"); s_CameraDepthBuffer = Shader.PropertyToID("_CameraDepthTexture"); s_punctualLightList = new ComputeBuffer(MaxLights, System.Runtime.InteropServices.Marshal.SizeOf(typeof(PunctualLightData))); s_areaLightList = new ComputeBuffer(MaxLights, System.Runtime.InteropServices.Marshal.SizeOf(typeof(AreaLightData))); s_envLightList = new ComputeBuffer(MaxLights, System.Runtime.InteropServices.Marshal.SizeOf(typeof(EnvLightData))); s_punctualShadowList = new ComputeBuffer(MaxShadows, System.Runtime.InteropServices.Marshal.SizeOf(typeof(PunctualShadowData))); // TODO: We need to have an API to send our sky information to Enlighten. For now use a workaround through skybox/cubemap material... m_SkyboxMaterial = CreateEngineMaterial("Skybox/Cubemap"); RenderSettings.skybox = m_SkyboxMaterial; // Setup this material as the default to be use in RenderSettings RenderSettings.ambientIntensity = 1.0f; // fix this to 1, this parameter should not exist! RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Skybox; // Force skybox for our HDRI RenderSettings.reflectionIntensity = 1.0f; m_SkyHDRIMaterial = CreateEngineMaterial("Hidden/HDRenderLoop/SkyHDRI"); m_DeferredMaterial = CreateEngineMaterial("Hidden/HDRenderLoop/Deferred"); m_FinalPassMaterial = CreateEngineMaterial("Hidden/HDRenderLoop/FinalPass"); // Debug m_DebugViewMaterialGBuffer = CreateEngineMaterial("Hidden/HDRenderLoop/DebugViewMaterialGBuffer"); m_ShadowPass = new ShadowRenderPass (m_ShadowSettings); m_cubeReflTexArray = new TextureCacheCubemap(); m_cubeReflTexArray.AllocTextureArray(32, (int)m_TextureSettings.reflectionCubemapSize, TextureFormat.BC6H, true); // Init Gbuffer description m_LitRenderLoop = new Lit.RenderLoop(); // Our object can be garbacge collected, so need to be allocate here m_gbufferManager.gbufferCount = m_LitRenderLoop.GetMaterialGBufferCount(); RenderTextureFormat[] RTFormat; RenderTextureReadWrite[] RTReadWrite; m_LitRenderLoop.GetMaterialGBufferDescription(out RTFormat, out RTReadWrite); for (int gbufferIndex = 0; gbufferIndex < m_gbufferManager.gbufferCount; ++gbufferIndex) { m_gbufferManager.SetBufferDescription(gbufferIndex, "_GBufferTexture" + gbufferIndex, RTFormat[gbufferIndex], RTReadWrite[gbufferIndex]); } #pragma warning disable 162 // warning CS0162: Unreachable code detected s_VelocityBuffer = Shader.PropertyToID("_VelocityTexture"); if (ShaderConfig.VelocityInGbuffer == 1) { // If velocity is in GBuffer then it is in the last RT. Assign a different name to it. m_gbufferManager.SetBufferDescription(m_gbufferManager.gbufferCount, "_VelocityTexture", Builtin.RenderLoop.GetVelocityBufferFormat(), Builtin.RenderLoop.GetVelocityBufferReadWrite()); m_gbufferManager.gbufferCount++; } #pragma warning restore 162 s_DistortionBuffer = Shader.PropertyToID("_DistortionTexture"); m_LitRenderLoop.Rebuild(); } void OnDisable() { m_LitRenderLoop.OnDisable(); s_punctualLightList.Release(); s_areaLightList.Release(); s_envLightList.Release(); s_punctualShadowList.Release(); if (m_SkyboxMaterial) DestroyImmediate(m_SkyboxMaterial); if (m_SkyHDRIMaterial) DestroyImmediate(m_SkyHDRIMaterial); if (m_DeferredMaterial) DestroyImmediate(m_DeferredMaterial); if (m_FinalPassMaterial) DestroyImmediate(m_FinalPassMaterial); m_cubeReflTexArray.Release(); } void InitAndClearBuffer(Camera camera, RenderLoop renderLoop) { // We clear only the depth buffer, no need to clear the various color buffer as we overwrite them. // Clear depth/stencil and init buffers { var cmd = new CommandBuffer(); cmd.name = "InitGBuffers and clear Depth/Stencil"; // Init buffer // With scriptable render loop we must allocate ourself depth and color buffer (We must be independent of backbuffer for now, hope to fix that later). // Also we manage ourself the HDR format, here allocating fp16 directly. // With scriptable render loop we can allocate temporary RT in a command buffer, they will not be release with ExecuteCommandBuffer // These temporary surface are release automatically at the end of the scriptable renderloop if not release explicitly int w = camera.pixelWidth; int h = camera.pixelHeight; cmd.GetTemporaryRT(s_CameraColorBuffer, w, h, 0, FilterMode.Point, RenderTextureFormat.ARGBHalf, RenderTextureReadWrite.Linear); cmd.GetTemporaryRT(s_CameraDepthBuffer, w, h, 24, FilterMode.Point, RenderTextureFormat.Depth); if (!debugParameters.useForwardRenderingOnly) { m_gbufferManager.InitGBuffers(w, h, cmd); } cmd.SetRenderTarget(new RenderTargetIdentifier(s_CameraColorBuffer), new RenderTargetIdentifier(s_CameraDepthBuffer)); cmd.ClearRenderTarget(true, false, new Color(0, 0, 0, 0)); renderLoop.ExecuteCommandBuffer(cmd); cmd.Dispose(); } // TEMP: As we are in development and have not all the setup pass we still clear the color in emissive buffer and gbuffer, but this will be removed later. // Clear HDR target { var cmd = new CommandBuffer(); cmd.name = "Clear HDR target"; cmd.SetRenderTarget(new RenderTargetIdentifier(s_CameraColorBuffer), new RenderTargetIdentifier(s_CameraDepthBuffer)); cmd.ClearRenderTarget(false, true, new Color(0, 0, 0, 0)); renderLoop.ExecuteCommandBuffer(cmd); cmd.Dispose(); } // Clear GBuffers { var cmd = new CommandBuffer(); cmd.name = "Clear GBuffer"; // Write into the Camera Depth buffer cmd.SetRenderTarget(m_gbufferManager.GetGBuffers(cmd), new RenderTargetIdentifier(s_CameraDepthBuffer)); // Clear everything // TODO: Clear is not required for color as we rewrite everything, will save performance. cmd.ClearRenderTarget(false, true, new Color(0, 0, 0, 0)); renderLoop.ExecuteCommandBuffer(cmd); cmd.Dispose(); } // END TEMP } void RenderOpaqueNoLightingRenderList(CullResults cull, Camera camera, RenderLoop renderLoop, string passName) { if (!debugParameters.displayOpaqueObjects) return; var settings = new DrawRendererSettings(cull, camera, new ShaderPassName(passName)) { rendererConfiguration = 0, sorting = { sortOptions = SortOptions.SortByMaterialThenMesh } }; settings.inputFilter.SetQueuesOpaque(); renderLoop.DrawRenderers(ref settings); } void RenderOpaqueRenderList(CullResults cull, Camera camera, RenderLoop renderLoop, string passName) { if (!debugParameters.displayOpaqueObjects) return; var settings = new DrawRendererSettings(cull, camera, new ShaderPassName(passName)) { rendererConfiguration = RendererConfiguration.PerObjectLightProbe | RendererConfiguration.PerObjectReflectionProbes | RendererConfiguration.PerObjectLightmaps | RendererConfiguration.PerObjectLightProbeProxyVolume, sorting = { sortOptions = SortOptions.SortByMaterialThenMesh } }; settings.inputFilter.SetQueuesOpaque(); renderLoop.DrawRenderers(ref settings); } void RenderTransparentNoLightingRenderList(CullResults cull, Camera camera, RenderLoop renderLoop, string passName) { if (!debugParameters.displayTransparentObjects) return; var settings = new DrawRendererSettings(cull, camera, new ShaderPassName(passName)) { rendererConfiguration = 0, sorting = { sortOptions = SortOptions.BackToFront } }; settings.inputFilter.SetQueuesTransparent(); renderLoop.DrawRenderers(ref settings); } void RenderTransparentRenderList(CullResults cull, Camera camera, RenderLoop renderLoop, string passName) { if (!debugParameters.displayTransparentObjects) return; var settings = new DrawRendererSettings(cull, camera, new ShaderPassName(passName)) { rendererConfiguration = RendererConfiguration.PerObjectLightProbe | RendererConfiguration.PerObjectReflectionProbes | RendererConfiguration.PerObjectLightmaps | RendererConfiguration.PerObjectLightProbeProxyVolume, sorting = { sortOptions = SortOptions.BackToFront } }; settings.inputFilter.SetQueuesTransparent(); renderLoop.DrawRenderers(ref settings); } void RenderDepthPrepass(CullResults cull, Camera camera, RenderLoop renderLoop) { // If we are forward only we will do a depth prepass // TODO: Depth prepass should be enabled based on light loop settings. LightLoop define if they need a depth prepass + forward only... if (!debugParameters.useDepthPrepass) return; // TODO: Must do opaque then alpha masked for performance! // TODO: front to back for opaque and by materal for opaque tested when we split in two var cmd = new CommandBuffer { name = "Depth Prepass" }; cmd.SetRenderTarget(new RenderTargetIdentifier(s_CameraDepthBuffer)); renderLoop.ExecuteCommandBuffer(cmd); cmd.Dispose(); RenderOpaqueNoLightingRenderList(cull, camera, renderLoop, "DepthOnly"); } void RenderGBuffer(CullResults cull, Camera camera, RenderLoop renderLoop) { if (debugParameters.useForwardRenderingOnly) { return ; } // setup GBuffer for rendering var cmd = new CommandBuffer { name = "GBuffer Pass" }; cmd.SetRenderTarget(m_gbufferManager.GetGBuffers(cmd), new RenderTargetIdentifier(s_CameraDepthBuffer)); renderLoop.ExecuteCommandBuffer(cmd); cmd.Dispose(); // render opaque objects into GBuffer RenderOpaqueRenderList(cull, camera, renderLoop, "GBuffer"); } // This pass is use in case of forward opaque and deferred rendering. We need to render forward objects before tile lighting pass void RenderForwardOpaqueDepth(CullResults cull, Camera camera, RenderLoop renderLoop) { // If we have render a depth prepass, no need for this pass if (debugParameters.useDepthPrepass) return; // TODO: Use the render queue index to only send the forward opaque! var cmd = new CommandBuffer { name = "Depth Prepass" }; cmd.SetRenderTarget(new RenderTargetIdentifier(s_CameraDepthBuffer)); renderLoop.ExecuteCommandBuffer(cmd); cmd.Dispose(); RenderOpaqueNoLightingRenderList(cull, camera, renderLoop, "DepthOnly"); } void RenderDebugViewMaterial(CullResults cull, Camera camera, RenderLoop renderLoop) { // Render Opaque forward { var cmd = new CommandBuffer { name = "DebugView Material Mode Pass" }; cmd.SetRenderTarget(new RenderTargetIdentifier(s_CameraColorBuffer), new RenderTargetIdentifier(s_CameraDepthBuffer)); cmd.ClearRenderTarget(true, true, new Color(0, 0, 0, 0)); renderLoop.ExecuteCommandBuffer(cmd); cmd.Dispose(); Shader.SetGlobalInt("_DebugViewMaterial", (int)debugParameters.debugViewMaterial); RenderOpaqueRenderList(cull, camera, renderLoop, "DebugViewMaterial"); } // Render GBuffer opaque if (!debugParameters.useForwardRenderingOnly) { Vector4 screenSize = ComputeScreenSize(camera); m_DebugViewMaterialGBuffer.SetVector("_ScreenSize", screenSize); m_DebugViewMaterialGBuffer.SetFloat("_DebugViewMaterial", (float)debugParameters.debugViewMaterial); // m_gbufferManager.BindBuffers(m_DeferredMaterial); // TODO: Bind depth textures var cmd = new CommandBuffer { name = "GBuffer Debug Pass" }; cmd.Blit(null, new RenderTargetIdentifier(s_CameraColorBuffer), m_DebugViewMaterialGBuffer, 0); renderLoop.ExecuteCommandBuffer(cmd); cmd.Dispose(); } // Render forward transparent { RenderTransparentRenderList(cull, camera, renderLoop, "DebugViewMaterial"); } // Last blit { var cmd = new CommandBuffer { name = "Blit DebugView Material Debug" }; cmd.Blit(new RenderTargetIdentifier(s_CameraColorBuffer), BuiltinRenderTextureType.CameraTarget); renderLoop.ExecuteCommandBuffer(cmd); cmd.Dispose(); } } Matrix4x4 GetViewProjectionMatrix(Camera camera) { // The actual projection matrix used in shaders is actually massaged a bit to work across all platforms // (different Z value ranges etc.) var gpuProj = GL.GetGPUProjectionMatrix(camera.projectionMatrix, false); var gpuVP = gpuProj * camera.worldToCameraMatrix; return gpuVP; } Vector4 ComputeScreenSize(Camera camera) { return new Vector4(camera.pixelWidth, camera.pixelHeight, 1.0f / camera.pixelWidth, 1.0f / camera.pixelHeight); } void RenderDeferredLighting(Camera camera, RenderLoop renderLoop) { if (debugParameters.useForwardRenderingOnly) { return ; } // Bind material data m_LitRenderLoop.Bind(); var invViewProj = GetViewProjectionMatrix(camera).inverse; m_DeferredMaterial.SetMatrix("_InvViewProjMatrix", invViewProj); var screenSize = ComputeScreenSize(camera); m_DeferredMaterial.SetVector("_ScreenSize", screenSize); // m_gbufferManager.BindBuffers(m_DeferredMaterial); // TODO: Bind depth textures var cmd = new CommandBuffer { name = "Deferred Ligthing Pass" }; cmd.Blit(null, new RenderTargetIdentifier(s_CameraColorBuffer), m_DeferredMaterial, 0); renderLoop.ExecuteCommandBuffer(cmd); cmd.Dispose(); } void RenderSky(Camera camera, RenderLoop renderLoop) { /* // Render sky into a cubemap - doesn't happen every frame, can be control // TODO: do a render to texture here // Downsample the cubemap and provide it to Enlighten // TODO: currently workaround is to set the cubemap in a Skybox/cubemap material //m_SkyboxMaterial.SetTexture(cubemap); // Render the sky itself Vector3[] vertData = new Vector3[4]; vertData[0] = new Vector3(-1.0f, -1.0f, 0.0f); vertData[1] = new Vector3(1.0f, -1.0f, 0.0f); vertData[2] = new Vector3(1.0f, 1.0f, 0.0f); vertData[3] = new Vector3(-1.0f, 1.0f, 0.0f); Vector3[] eyeVectorData = new Vector3[4]; // camera.worldToCameraMatrix, camera.projectionMatrix // Get view vector vased on the frustrum, i.e (invert transform frustrum get position etc...) eyeVectorData[0] = eyeVectorData[1] = eyeVectorData[2] = eyeVectorData[3] = // Write out the mesh var triangles = new int[4]; for (int i = 0; i < 4; i++) { triangles[i] = i; } Mesh mesh = new Mesh { vertices = vertData, normals = eyeVectorData, triangles = triangles }; m_SkyHDRIMaterial.SetTexture("_Cubemap", skyParameters.skyHDRI); m_SkyHDRIMaterial.SetVector("_SkyParam", new Vector4(skyParameters.exposure, skyParameters.multiplier, skyParameters.rotation, 0.0f)); var cmd = new CommandBuffer { name = "Skybox" }; cmd.DrawMesh(mesh, Matrix4x4.identity, m_SkyHDRIMaterial); renderloop.ExecuteCommandBuffer(cmd); cmd.Dispose(); */ } void RenderForward(CullResults cullResults, Camera camera, RenderLoop renderLoop) { // Bind material data m_LitRenderLoop.Bind(); var cmd = new CommandBuffer { name = "Forward Pass" }; cmd.SetRenderTarget(new RenderTargetIdentifier(s_CameraColorBuffer), new RenderTargetIdentifier(s_CameraDepthBuffer)); renderLoop.ExecuteCommandBuffer(cmd); cmd.Dispose(); if (debugParameters.useForwardRenderingOnly) { RenderOpaqueRenderList(cullResults, camera, renderLoop, "Forward"); } RenderTransparentRenderList(cullResults, camera, renderLoop, "Forward"); } void RenderForwardUnlit(CullResults cullResults, Camera camera, RenderLoop renderLoop) { // Bind material data m_LitRenderLoop.Bind(); var cmd = new CommandBuffer { name = "Forward Unlit Pass" }; cmd.SetRenderTarget(new RenderTargetIdentifier(s_CameraColorBuffer), new RenderTargetIdentifier(s_CameraDepthBuffer)); renderLoop.ExecuteCommandBuffer(cmd); cmd.Dispose(); RenderOpaqueNoLightingRenderList(cullResults, camera, renderLoop, "ForwardUnlit"); RenderTransparentNoLightingRenderList(cullResults, camera, renderLoop, "ForwardUnlit"); } void RenderVelocity(CullResults cullResults, Camera camera, RenderLoop renderLoop) { // warning CS0162: Unreachable code detected // warning CS0429: Unreachable expression code detected #pragma warning disable 162, 429 // If opaque velocity have been render during GBuffer no need to render it here if ((ShaderConfig.VelocityInGbuffer == 0) || debugParameters.useForwardRenderingOnly) return ; int w = camera.pixelWidth; int h = camera.pixelHeight; var cmd = new CommandBuffer { name = "Velocity Pass" }; cmd.GetTemporaryRT(s_VelocityBuffer, w, h, 0, FilterMode.Point, Builtin.RenderLoop.GetVelocityBufferFormat(), Builtin.RenderLoop.GetVelocityBufferReadWrite()); cmd.SetRenderTarget(new RenderTargetIdentifier(s_VelocityBuffer), new RenderTargetIdentifier(s_CameraDepthBuffer)); renderLoop.ExecuteCommandBuffer(cmd); cmd.Dispose(); RenderOpaqueNoLightingRenderList(cullResults, camera, renderLoop, "MotionVectors"); #pragma warning restore 162, 429 } void RenderDistortion(CullResults cullResults, Camera camera, RenderLoop renderLoop) { int w = camera.pixelWidth; int h = camera.pixelHeight; var cmd = new CommandBuffer { name = "Distortion Pass" }; cmd.GetTemporaryRT(s_DistortionBuffer, w, h, 0, FilterMode.Point, Builtin.RenderLoop.GetDistortionBufferFormat(), Builtin.RenderLoop.GetDistortionBufferReadWrite()); cmd.SetRenderTarget(new RenderTargetIdentifier(s_DistortionBuffer), new RenderTargetIdentifier(s_CameraDepthBuffer)); renderLoop.ExecuteCommandBuffer(cmd); cmd.Dispose(); // Only transparent object can render distortion vectors RenderTransparentNoLightingRenderList(cullResults, camera, renderLoop, "DistortionVectors"); } void FinalPass(RenderLoop renderLoop) { // Those could be tweakable for the neutral tonemapper, but in the case of the LookDev we don't need that const float blackIn = 0.02f; const float whiteIn = 10.0f; const float blackOut = 0.0f; const float whiteOut = 10.0f; const float whiteLevel = 5.3f; const float whiteClip = 10.0f; const float dialUnits = 20.0f; const float halfDialUnits = dialUnits * 0.5f; // converting from artist dial units to easy shader-lerps (0-1) var tonemapCoeff1 = new Vector4((blackIn * dialUnits) + 1.0f, (blackOut * halfDialUnits) + 1.0f, (whiteIn / dialUnits), (1.0f - (whiteOut / dialUnits))); var tonemapCoeff2 = new Vector4(0.0f, 0.0f, whiteLevel, whiteClip / halfDialUnits); m_FinalPassMaterial.SetVector("_ToneMapCoeffs1", tonemapCoeff1); m_FinalPassMaterial.SetVector("_ToneMapCoeffs2", tonemapCoeff2); m_FinalPassMaterial.SetFloat("_EnableToneMap", debugParameters.enableTonemap ? 1.0f : 0.0f); m_FinalPassMaterial.SetFloat("_Exposure", debugParameters.exposure); var cmd = new CommandBuffer { name = "FinalPass" }; // Resolve our HDR texture to CameraTarget. cmd.Blit(new RenderTargetIdentifier(s_CameraColorBuffer), BuiltinRenderTextureType.CameraTarget, m_FinalPassMaterial, 0); renderLoop.ExecuteCommandBuffer(cmd); cmd.Dispose(); } void NewFrame() { // update texture caches m_cubeReflTexArray.NewFrame(); } //--------------------------------------------------------------------------------------------------------------------------------------------------- void UpdatePunctualLights(VisibleLight[] visibleLights, ref ShadowOutput shadowOutput) { var pLights = new List(); var aLights = new List(); var shadows = new List(); for (int lightIndex = 0, numLights = Math.Min(visibleLights.Length, MaxLights); lightIndex < numLights; ++lightIndex) { var light = visibleLights[lightIndex]; // We only process light with additional data var additionalData = light.light.GetComponent(); if (additionalData == null) { Debug.LogWarning("Light entity detected without additional data, will not be taken into account " + light.light.name); continue; } if (light.lightType == LightType.Area) { // Skip area lights which are currently only used for baking. continue; } // Correct intensity calculation (different from Unity) var lightColorR = light.light.intensity * Mathf.GammaToLinearSpace(light.light.color.r); var lightColorG = light.light.intensity * Mathf.GammaToLinearSpace(light.light.color.g); var lightColorB = light.light.intensity * Mathf.GammaToLinearSpace(light.light.color.b); // Test whether we should treat this punctual light as an area light. // It's a temporary hack until the proper UI support is added. if (additionalData.treatAsAreaLight) { AreaLightData lightData = new AreaLightData(); // TODO: add AreaShapeType.Line support for small widths. lightData.shapeType = AreaShapeType.Rectangle; lightData.size = new Vector2(additionalData.areaLightLength, additionalData.areaLightWidth); lightData.twoSided = additionalData.isDoubleSided; lightData.positionWS = light.light.transform.position; lightData.forward = light.light.transform.forward; // Note: Light direction is oriented backward (-Z) lightData.up = light.light.transform.up; lightData.right = light.light.transform.right; lightData.color = new Vector3(lightColorR, lightColorG, lightColorB); lightData.diffuseScale = additionalData.affectDiffuse ? 1.0f : 0.0f; lightData.specularScale = additionalData.affectSpecular ? 1.0f : 0.0f; lightData.shadowDimmer = additionalData.shadowDimmer; lightData.invSqrAttenuationRadius = 1.0f / (light.range * light.range); aLights.Add(lightData); // TODO: shadows. } else { var l = new PunctualLightData(); if (light.lightType == LightType.Directional) { l.useDistanceAttenuation = 0.0f; // positionWS store Light direction for directional and is opposite to the forward direction l.positionWS = -light.light.transform.forward; l.invSqrAttenuationRadius = 0.0f; } else { l.useDistanceAttenuation = 1.0f; l.positionWS = light.light.transform.position; l.invSqrAttenuationRadius = 1.0f / (light.range * light.range); } l.color = new Vector3(lightColorR, lightColorG, lightColorB); l.forward = light.light.transform.forward; // Note: Light direction is oriented backward (-Z) l.up = light.light.transform.up; l.right = light.light.transform.right; if (light.lightType == LightType.Spot) { var spotAngle = light.spotAngle; var innerConePercent = additionalData.GetInnerSpotPercent01(); var cosSpotOuterHalfAngle = Mathf.Clamp(Mathf.Cos(spotAngle * 0.5f * Mathf.Deg2Rad), 0.0f, 1.0f); var cosSpotInnerHalfAngle = Mathf.Clamp(Mathf.Cos(spotAngle * 0.5f * innerConePercent * Mathf.Deg2Rad), 0.0f, 1.0f); // inner cone var val = Mathf.Max(0.001f, (cosSpotInnerHalfAngle - cosSpotOuterHalfAngle)); l.angleScale = 1.0f / val; l.angleOffset = -cosSpotOuterHalfAngle * l.angleScale; } else { // 1.0f, 2.0f are neutral value allowing GetAngleAnttenuation in shader code to return 1.0 l.angleScale = 1.0f; l.angleOffset = 2.0f; } l.diffuseScale = additionalData.affectDiffuse ? 1.0f : 0.0f; l.specularScale = additionalData.affectSpecular ? 1.0f : 0.0f; l.shadowDimmer = additionalData.shadowDimmer; l.IESIndex = -1; l.cookieIndex = -1; l.shadowIndex = -1; // Setup shadow data arrays bool hasShadows = shadowOutput.GetShadowSliceCountLightIndex(lightIndex) != 0; bool hasNotReachMaxLimit = shadows.Count + (light.lightType == LightType.Point ? 6 : 1) <= MaxShadows; if (hasShadows && hasNotReachMaxLimit) // Note < MaxShadows should be check at shadowOutput creation { // When we have a point light, we assumed that there is 6 consecutive PunctualShadowData l.shadowIndex = shadows.Count; for (int sliceIndex = 0; sliceIndex < shadowOutput.GetShadowSliceCountLightIndex(lightIndex); ++sliceIndex) { PunctualShadowData s = new PunctualShadowData(); int shadowSliceIndex = shadowOutput.GetShadowSliceIndex(lightIndex, sliceIndex); s.worldToShadow = shadowOutput.shadowSlices[shadowSliceIndex].shadowTransform.transpose; // Transpose for hlsl reading ? if (light.lightType == LightType.Spot) { s.shadowType = ShadowType.Spot; } else if (light.lightType == LightType.Point) { s.shadowType = ShadowType.Point; } else { s.shadowType = ShadowType.Directional; } s.bias = light.light.shadowBias; shadows.Add(s); } } pLights.Add(l); } } s_punctualLightList.SetData(pLights.ToArray()); s_areaLightList.SetData(aLights.ToArray()); s_punctualShadowList.SetData(shadows.ToArray()); Shader.SetGlobalBuffer("_PunctualLightList", s_punctualLightList); Shader.SetGlobalBuffer("_AreaLightList", s_areaLightList); Shader.SetGlobalInt("_PunctualLightCount", pLights.Count); Shader.SetGlobalInt("_AreaLightCount", aLights.Count); Shader.SetGlobalBuffer("_PunctualShadowList", s_punctualShadowList); } void UpdateReflectionProbes(VisibleReflectionProbe[] activeReflectionProbes) { var lights = new List(); for (int lightIndex = 0; lightIndex < Math.Min(activeReflectionProbes.Length, MaxProbes); lightIndex++) { var probe = activeReflectionProbes[lightIndex]; if (probe.texture == null) continue; var l = new EnvLightData(); // CAUTION: localToWorld is the transform for the widget of the reflection probe. i.e the world position of the point use to do the cubemap capture (mean it include the local offset) l.positionWS = probe.localToWorld.GetColumn(3); l.envShapeType = EnvShapeType.None; // TODO: Support sphere in the interface if (probe.boxProjection != 0) { l.envShapeType = EnvShapeType.Box; } // remove scale from the matrix (Scale in this matrix is use to scale the widget) l.right = probe.localToWorld.GetColumn(0); l.right.Normalize(); l.up = probe.localToWorld.GetColumn(1); l.up.Normalize(); l.forward = probe.localToWorld.GetColumn(2); l.forward.Normalize(); // Artists prefer to have blend distance inside the volume! // So we let the current UI but we assume blendDistance is an inside factor instead // Blend distance can't be larger than the max radius // probe.bounds.extents is BoxSize / 2 float maxBlendDist = Mathf.Min(probe.bounds.extents.x, Mathf.Min(probe.bounds.extents.y, probe.bounds.extents.z)); float blendDistance = Mathf.Min(maxBlendDist, probe.blendDistance); l.innerDistance = probe.bounds.extents - new Vector3(blendDistance, blendDistance, blendDistance); l.envIndex = m_cubeReflTexArray.FetchSlice(probe.texture); l.offsetLS = probe.center; // center is misnamed, it is the offset (in local space) from center of the bounding box to the cubemap capture point l.blendDistance = blendDistance; lights.Add(l); } s_envLightList.SetData(lights.ToArray()); Shader.SetGlobalBuffer("_EnvLightList", s_envLightList); Shader.SetGlobalInt("_EnvLightCount", lights.Count); Shader.SetGlobalTexture("_EnvTextures", m_cubeReflTexArray.GetTexCache()); } public override void Render(Camera[] cameras, RenderLoop renderLoop) { if (!m_LitRenderLoop.isInit) { m_LitRenderLoop.RenderInit(renderLoop); } // Do anything we need to do upon a new frame. NewFrame(); // Set Frame constant buffer // TODO... foreach (var camera in cameras) { // Set camera constant buffer // TODO... CullingParameters cullingParams; if (!CullResults.GetCullingParameters(camera, out cullingParams)) continue; m_ShadowPass.UpdateCullingParameters (ref cullingParams); var cullResults = CullResults.Cull(ref cullingParams, renderLoop); renderLoop.SetupCameraProperties(camera); InitAndClearBuffer(camera, renderLoop); RenderDepthPrepass(cullResults, camera, renderLoop); RenderGBuffer(cullResults, camera, renderLoop); // For tile lighting with forward opaque //RenderForwardOpaqueDepth(cullResults, camera, renderLoop); if (debugParameters.debugViewMaterial != 0) { RenderDebugViewMaterial(cullResults, camera, renderLoop); } else { ShadowOutput shadows; m_ShadowPass.Render(renderLoop, cullResults, out shadows); renderLoop.SetupCameraProperties(camera); // Need to recall SetupCameraProperties after m_ShadowPass.Render UpdatePunctualLights(cullResults.visibleLights, ref shadows); UpdateReflectionProbes(cullResults.visibleReflectionProbes); RenderDeferredLighting(camera, renderLoop); RenderSky(camera, renderLoop); RenderForward(cullResults, camera, renderLoop); // Note: We want to render forward opaque before RenderSky, then RenderTransparent - can only do that once we have material.SetPass feature... RenderForwardUnlit(cullResults, camera, renderLoop); RenderVelocity(cullResults, camera, renderLoop); // Note we may have to render velocity earlier if we do temporalAO, temporal volumetric etc... Mean we will not take into account forward opaque in case of deferred rendering ? // TODO: Check with VFX team. // Rendering distortion here have off course lot of artifact. // But resolving at each objects that write in distortion is not possible (need to sort transparent, render those that do not distort, then resolve, then etc...) // Instead we chose to apply distortion at the end after we cumulate distortion vector and desired blurriness. This // RenderDistortion(cullResults, camera, renderLoop); FinalPass(renderLoop); } renderLoop.Submit(); } // Post effects } #if UNITY_EDITOR public override UnityEditor.SupportedRenderingFeatures GetSupportedRenderingFeatures() { var features = new UnityEditor.SupportedRenderingFeatures { reflectionProbe = UnityEditor.SupportedRenderingFeatures.ReflectionProbe.Rotation }; return features; } #endif } }