using System; using UnityEngine.Rendering; namespace UnityEngine.Experimental.Rendering.LightweightPipeline { public struct ShadowSliceData { public Matrix4x4 shadowTransform; public int offsetX; public int offsetY; public int resolution; public void Clear() { shadowTransform = Matrix4x4.identity; offsetX = offsetY = 0; resolution = 1024; } } public class LightweightShadowUtils { public static bool ExtractDirectionalLightMatrix(ref CullResults cullResults, ref ShadowData shadowData, int shadowLightIndex, int cascadeIndex, int shadowResolution, float shadowNearPlane, out Vector4 cascadeSplitDistance, out ShadowSliceData shadowSliceData, out Matrix4x4 viewMatrix, out Matrix4x4 projMatrix) { ShadowSplitData splitData; bool success = cullResults.ComputeDirectionalShadowMatricesAndCullingPrimitives(shadowLightIndex, cascadeIndex, shadowData.directionalLightCascadeCount, shadowData.directionalLightCascades, shadowResolution, shadowNearPlane, out viewMatrix, out projMatrix, out splitData); cascadeSplitDistance = splitData.cullingSphere; shadowSliceData.offsetX = (cascadeIndex % 2) * shadowResolution; shadowSliceData.offsetY = (cascadeIndex / 2) * shadowResolution; shadowSliceData.resolution = shadowResolution; shadowSliceData.shadowTransform = GetShadowTransform(projMatrix, viewMatrix); // If we have shadow cascades baked into the atlas we bake cascade transform // in each shadow matrix to save shader ALU and L/S if (shadowData.directionalLightCascadeCount > 1) ApplySliceTransform(ref shadowSliceData, shadowData.directionalShadowAtlasWidth, shadowData.directionalShadowAtlasHeight); return success; } public static bool ExtractSpotLightMatrix(ref CullResults cullResults, ref ShadowData shadowData, int shadowLightIndex, out Matrix4x4 shadowMatrix, out Matrix4x4 viewMatrix, out Matrix4x4 projMatrix) { ShadowSplitData splitData; bool success = cullResults.ComputeSpotShadowMatricesAndCullingPrimitives(shadowLightIndex, out viewMatrix, out projMatrix, out splitData); shadowMatrix = GetShadowTransform(projMatrix, viewMatrix); return success; } public static void RenderShadowSlice(CommandBuffer cmd, ref ScriptableRenderContext context, ref ShadowSliceData shadowSliceData, ref DrawShadowsSettings settings, Matrix4x4 proj, Matrix4x4 view) { cmd.SetViewport(new Rect(shadowSliceData.offsetX, shadowSliceData.offsetY, shadowSliceData.resolution, shadowSliceData.resolution)); cmd.EnableScissorRect(new Rect(shadowSliceData.offsetX + 4, shadowSliceData.offsetY + 4, shadowSliceData.resolution - 8, shadowSliceData.resolution - 8)); cmd.SetViewProjectionMatrices(view, proj); context.ExecuteCommandBuffer(cmd); cmd.Clear(); context.DrawShadows(ref settings); cmd.DisableScissorRect(); context.ExecuteCommandBuffer(cmd); cmd.Clear(); } public static int GetMaxTileResolutionInAtlas(int atlasWidth, int atlasHeight, int tileCount) { int resolution = Mathf.Min(atlasWidth, atlasHeight); if (tileCount > Mathf.Log(resolution)) { Debug.LogError( String.Format( "Cannot fit {0} tiles into current shadowmap atlas of size ({1}, {2}). ShadowMap Resolution set to zero.", tileCount, atlasWidth, atlasHeight)); return 0; } int currentTileCount = atlasWidth / resolution * atlasHeight / resolution; while (currentTileCount < tileCount) { resolution = resolution >> 1; currentTileCount = atlasWidth / resolution * atlasHeight / resolution; } return resolution; } public static void ApplySliceTransform(ref ShadowSliceData shadowSliceData, int atlasWidth, int atlasHeight) { Matrix4x4 sliceTransform = Matrix4x4.identity; float oneOverAtlasWidth = 1.0f / atlasWidth; float oneOverAtlasHeight = 1.0f / atlasHeight; sliceTransform.m00 = shadowSliceData.resolution * oneOverAtlasWidth; sliceTransform.m11 = shadowSliceData.resolution * oneOverAtlasHeight; sliceTransform.m03 = shadowSliceData.offsetX * oneOverAtlasWidth; sliceTransform.m13 = shadowSliceData.offsetY * oneOverAtlasHeight; // Apply shadow slice scale and offset shadowSliceData.shadowTransform = sliceTransform * shadowSliceData.shadowTransform; } public static void SetupShadowCasterConstants(CommandBuffer cmd, ref VisibleLight visibleLight, Matrix4x4 proj, float cascadeResolution) { Light light = visibleLight.light; float bias = 0.0f; float normalBias = 0.0f; // Use same kernel radius as built-in pipeline so we can achieve same bias results // with the default light bias parameters. const float kernelRadius = 3.65f; if (visibleLight.lightType == LightType.Directional) { // Scale bias by cascade's world space depth range. // Directional shadow lights have orthogonal projection. // proj.m22 = -2 / (far - near) since the projection's depth range is [-1.0, 1.0] // In order to be correct we should multiply bias by 0.5 but this introducing aliasing along cascades more visible. float sign = (SystemInfo.usesReversedZBuffer) ? 1.0f : -1.0f; bias = light.shadowBias * proj.m22 * sign; // Currently only square POT cascades resolutions are used. // We scale normalBias double frustumWidth = 2.0 / (double)proj.m00; double frustumHeight = 2.0 / (double)proj.m11; float texelSizeX = (float)(frustumWidth / (double)cascadeResolution); float texelSizeY = (float)(frustumHeight / (double)cascadeResolution); float texelSize = Mathf.Max(texelSizeX, texelSizeY); // Since we are applying normal bias on caster side we want an inset normal offset // thus we use a negative normal bias. normalBias = -light.shadowNormalBias * texelSize * kernelRadius; } else if (visibleLight.lightType == LightType.Spot) { float sign = (SystemInfo.usesReversedZBuffer) ? -1.0f : 1.0f; bias = light.shadowBias * sign; normalBias = 0.0f; } else { Debug.LogWarning("Only spot and directional shadow casters are supported in lightweight pipeline"); } Vector3 lightDirection = -visibleLight.localToWorld.GetColumn(2); cmd.SetGlobalVector("_ShadowBias", new Vector4(bias, normalBias, 0.0f, 0.0f)); cmd.SetGlobalVector("_LightDirection", new Vector4(lightDirection.x, lightDirection.y, lightDirection.z, 0.0f)); } static Matrix4x4 GetShadowTransform(Matrix4x4 proj, Matrix4x4 view) { // Currently CullResults ComputeDirectionalShadowMatricesAndCullingPrimitives doesn't // apply z reversal to projection matrix. We need to do it manually here. if (SystemInfo.usesReversedZBuffer) { proj.m20 = -proj.m20; proj.m21 = -proj.m21; proj.m22 = -proj.m22; proj.m23 = -proj.m23; } Matrix4x4 worldToShadow = proj * view; var textureScaleAndBias = Matrix4x4.identity; textureScaleAndBias.m00 = 0.5f; textureScaleAndBias.m11 = 0.5f; textureScaleAndBias.m22 = 0.5f; textureScaleAndBias.m03 = 0.5f; textureScaleAndBias.m23 = 0.5f; textureScaleAndBias.m13 = 0.5f; // Apply texture scale and offset to save a MAD in shader. return textureScaleAndBias * worldToShadow; } } }