namespace UnityEngine.Experimental.Rendering { public class ShadowUtilsConstants { // Matches ScriptableShadowsUtility.cpp public enum CubemapEdge { kCubeEdgePX_PY = 0, kCubeEdgePX_NY, kCubeEdgePX_PZ, kCubeEdgePX_NZ, kCubeEdgeNX_PY, kCubeEdgeNX_NY, kCubeEdgeNX_PZ, kCubeEdgeNX_NZ, kCubeEdgePY_PZ, kCubeEdgePY_NZ, kCubeEdgeNY_PZ, kCubeEdgeNY_NZ, kCubeEdge_Count }; public static readonly CubemapEdge[,] kCubemapEdgesPerFace = new CubemapEdge[6, 4] { { CubemapEdge.kCubeEdgePX_PY, CubemapEdge.kCubeEdgePX_NY, CubemapEdge.kCubeEdgePX_PZ, CubemapEdge.kCubeEdgePX_NZ }, // PX { CubemapEdge.kCubeEdgeNX_PY, CubemapEdge.kCubeEdgeNX_NY, CubemapEdge.kCubeEdgeNX_PZ, CubemapEdge.kCubeEdgeNX_NZ }, // NX { CubemapEdge.kCubeEdgePX_PY, CubemapEdge.kCubeEdgeNX_PY, CubemapEdge.kCubeEdgePY_PZ, CubemapEdge.kCubeEdgePY_NZ }, // PY { CubemapEdge.kCubeEdgePX_NY, CubemapEdge.kCubeEdgeNX_NY, CubemapEdge.kCubeEdgeNY_PZ, CubemapEdge.kCubeEdgeNY_NZ }, // NY { CubemapEdge.kCubeEdgePX_PZ, CubemapEdge.kCubeEdgeNX_PZ, CubemapEdge.kCubeEdgePY_PZ, CubemapEdge.kCubeEdgeNY_PZ }, // PZ { CubemapEdge.kCubeEdgePX_NZ, CubemapEdge.kCubeEdgeNX_NZ, CubemapEdge.kCubeEdgePY_NZ, CubemapEdge.kCubeEdgeNY_NZ } // NZ }; const float oneOverSqr2 = 0.70710678118654752440084436210485f; public static readonly Vector3[] kCubemapEdgeDirections = new Vector3[(int)CubemapEdge.kCubeEdge_Count] { new Vector3(oneOverSqr2, oneOverSqr2, 0), new Vector3(oneOverSqr2, -oneOverSqr2, 0), new Vector3(oneOverSqr2, 0, oneOverSqr2), new Vector3(oneOverSqr2, 0, -oneOverSqr2), new Vector3(-oneOverSqr2, oneOverSqr2, 0), new Vector3(-oneOverSqr2, -oneOverSqr2, 0), new Vector3(-oneOverSqr2, 0, oneOverSqr2), new Vector3(-oneOverSqr2, 0, -oneOverSqr2), new Vector3(0, oneOverSqr2, oneOverSqr2), new Vector3(0, oneOverSqr2, -oneOverSqr2), new Vector3(0, -oneOverSqr2, oneOverSqr2), new Vector3(0, -oneOverSqr2, -oneOverSqr2) }; // Cubemap faces with flipped z coordinate. // These matrices do NOT match what we have in Skybox.cpp. // The C++ runtime flips y as well and requires patching up // the culling state. Using these matrices keeps the winding // order, but may need some special treatment if rendering // into an actual cubemap. public static readonly Matrix4x4[] kCubemapFaces = new Matrix4x4[] { new Matrix4x4( // pos X new Vector4(0.0f, 0.0f, -1.0f, 0.0f), new Vector4(0.0f, 1.0f, 0.0f, 0.0f), new Vector4(-1.0f, 0.0f, 0.0f, 0.0f), new Vector4(0.0f, 0.0f, 0.0f, 1.0f)), new Matrix4x4( // neg x new Vector4(0.0f, 0.0f, 1.0f, 0.0f), new Vector4(0.0f, 1.0f, 0.0f, 0.0f), new Vector4(1.0f, 0.0f, 0.0f, 0.0f), new Vector4(0.0f, 0.0f, 0.0f, 1.0f)), new Matrix4x4( // pos y new Vector4(1.0f, 0.0f, 0.0f, 0.0f), new Vector4(0.0f, 0.0f, -1.0f, 0.0f), new Vector4(0.0f, -1.0f, 0.0f, 0.0f), new Vector4(0.0f, 0.0f, 0.0f, 1.0f)), new Matrix4x4( // neg y new Vector4(1.0f, 0.0f, 0.0f, 0.0f), new Vector4(0.0f, 0.0f, 1.0f, 0.0f), new Vector4(0.0f, 1.0f, 0.0f, 0.0f), new Vector4(0.0f, 0.0f, 0.0f, 1.0f)), new Matrix4x4( // pos z new Vector4(1.0f, 0.0f, 0.0f, 0.0f), new Vector4(0.0f, 1.0f, 0.0f, 0.0f), new Vector4(0.0f, 0.0f, -1.0f, 0.0f), new Vector4(0.0f, 0.0f, 0.0f, 1.0f)), new Matrix4x4( // neg z new Vector4(-1.0f, 0.0f, 0.0f, 0.0f), new Vector4(0.0f, 1.0f, 0.0f, 0.0f), new Vector4(0.0f, 0.0f, 1.0f, 0.0f), new Vector4(0.0f, 0.0f, 0.0f, 1.0f)) }; } public class ShadowUtils { public static void InvertView(ref Matrix4x4 view, out Matrix4x4 invview) { invview = Matrix4x4.zero; invview.m00 = view.m00; invview.m01 = view.m10; invview.m02 = view.m20; invview.m10 = view.m01; invview.m11 = view.m11; invview.m12 = view.m21; invview.m20 = view.m02; invview.m21 = view.m12; invview.m22 = view.m22; invview.m33 = 1.0f; invview.m03 = -(invview.m00 * view.m03 + invview.m01 * view.m13 + invview.m02 * view.m23); invview.m13 = -(invview.m10 * view.m03 + invview.m11 * view.m13 + invview.m12 * view.m23); invview.m23 = -(invview.m20 * view.m03 + invview.m21 * view.m13 + invview.m22 * view.m23); } public static void InvertOrthographic(ref Matrix4x4 proj, ref Matrix4x4 view, out Matrix4x4 vpinv) { Matrix4x4 invview; InvertView(ref view, out invview); Matrix4x4 invproj = Matrix4x4.zero; invproj.m00 = 1.0f / proj.m00; invproj.m11 = 1.0f / proj.m11; invproj.m22 = 1.0f / proj.m22; invproj.m33 = 1.0f; invproj.m03 = proj.m03 * invproj.m00; invproj.m13 = proj.m13 * invproj.m11; invproj.m23 = -proj.m23 * invproj.m22; vpinv = invview * invproj; } public static void InvertPerspective(ref Matrix4x4 proj, ref Matrix4x4 view, out Matrix4x4 vpinv) { Matrix4x4 invview; InvertView(ref view, out invview); Matrix4x4 invproj = Matrix4x4.zero; invproj.m00 = 1.0f / proj.m00; invproj.m03 = proj.m02 * invproj.m00; invproj.m11 = 1.0f / proj.m11; invproj.m13 = proj.m12 * invproj.m11; invproj.m22 = 0.0f; invproj.m23 = -1.0f; invproj.m33 = proj.m22 / proj.m23; invproj.m32 = invproj.m33 / proj.m22; vpinv = invview * invproj; } public static Matrix4x4 ExtractSpotLightMatrix(VisibleLight vl, float guardAngle, out Matrix4x4 view, out Matrix4x4 proj, out Matrix4x4 deviceProj, out Matrix4x4 vpinverse, out Vector4 lightDir, out ShadowSplitData splitData) { splitData = new ShadowSplitData(); splitData.cullingSphere.Set(0.0f, 0.0f, 0.0f, float.NegativeInfinity); splitData.cullingPlaneCount = 0; // get lightDir lightDir = vl.light.transform.forward; // calculate view Matrix4x4 scaleMatrix = Matrix4x4.identity; scaleMatrix.m22 = -1.0f; view = scaleMatrix * vl.localToWorld.inverse; // following code is from SharedLightData::GetNearPlaneMinBound float percentageBound = 0.01f * vl.light.range; float fixedBound = 0.1f; float nearmin = fixedBound <= percentageBound ? fixedBound : percentageBound; // calculate projection float zfar = vl.range; float znear = vl.light.shadowNearPlane >= nearmin ? vl.light.shadowNearPlane : nearmin; float fov = vl.spotAngle + guardAngle; proj = Matrix4x4.Perspective(fov, 1.0f, znear, zfar); // and the compound (deviceProj will potentially inverse-Z) deviceProj = GL.GetGPUProjectionMatrix(proj, false); InvertPerspective(ref deviceProj, ref view, out vpinverse); return deviceProj * view; } public static Matrix4x4 ExtractPointLightMatrix(VisibleLight vl, uint faceIdx, float guardAngle, out Matrix4x4 view, out Matrix4x4 proj, out Matrix4x4 deviceProj, out Matrix4x4 vpinverse, out Vector4 lightDir, out ShadowSplitData splitData) { if (faceIdx > (uint)CubemapFace.NegativeZ) Debug.LogError("Tried to extract cubemap face " + faceIdx + "."); splitData = new ShadowSplitData(); splitData.cullingSphere.Set(0.0f, 0.0f, 0.0f, float.NegativeInfinity); splitData.cullingPlaneCount = 4; // get lightDir lightDir = vl.light.transform.forward; // calculate the view matrices Vector3 lpos = vl.light.transform.position; view = ShadowUtilsConstants.kCubemapFaces[faceIdx]; Vector3 inverted_viewpos = ShadowUtilsConstants.kCubemapFaces[faceIdx].MultiplyPoint(-lpos); view.SetColumn(3, new Vector4(inverted_viewpos.x, inverted_viewpos.y, inverted_viewpos.z, 1.0f)); for (int i = 0; i < 4; ++i) { ShadowUtilsConstants.CubemapEdge cubemapEdge = ShadowUtilsConstants.kCubemapEdgesPerFace[faceIdx, i]; Vector3 cullingPlaneDirection = ShadowUtilsConstants.kCubemapEdgeDirections[(int)cubemapEdge]; splitData.SetCullingPlane(i, new Plane(cullingPlaneDirection, lpos)); } // following code is from SharedLightData::GetNearPlaneMinBound float percentageBound = 0.01f * vl.light.range; float fixedBound = 0.1f; float nearmin = fixedBound <= percentageBound ? fixedBound : percentageBound; // calculate projection float farPlane = vl.range; float nearPlane = vl.light.shadowNearPlane >= nearmin ? vl.light.shadowNearPlane : nearmin; proj = Matrix4x4.Perspective(90.0f + guardAngle, 1.0f, nearPlane, farPlane); // and the compound (deviceProj will potentially inverse-Z) deviceProj = GL.GetGPUProjectionMatrix(proj, false); InvertPerspective(ref deviceProj, ref view, out vpinverse); return deviceProj * view; } public static Matrix4x4 ExtractDirectionalLightMatrix(VisibleLight vl, uint cascadeIdx, int cascadeCount, float[] splitRatio, float nearPlaneOffset, uint width, uint height, out Matrix4x4 view, out Matrix4x4 proj, out Matrix4x4 deviceProj, out Matrix4x4 vpinverse, out Vector4 lightDir, out ShadowSplitData splitData, CullResults cullResults, int lightIndex) { Debug.Assert(width == height, "Currently the cascaded shadow mapping code requires square cascades."); splitData = new ShadowSplitData(); splitData.cullingSphere.Set(0.0f, 0.0f, 0.0f, float.NegativeInfinity); splitData.cullingPlaneCount = 0; // get lightDir lightDir = vl.light.transform.forward; // TODO: At some point this logic should be moved to C#, then the parameters cullResults and lightIndex can be removed as well // For directional lights shadow data is extracted from the cullResults, so that needs to be somehow provided here. // Check ScriptableShadowsUtility.cpp ComputeDirectionalShadowMatricesAndCullingPrimitives(...) for details. Vector3 ratios = new Vector3(); for (int i = 0, cnt = splitRatio.Length < 3 ? splitRatio.Length : 3; i < cnt; i++) ratios[i] = splitRatio[i]; cullResults.ComputeDirectionalShadowMatricesAndCullingPrimitives(lightIndex, (int)cascadeIdx, cascadeCount, ratios, (int)width, nearPlaneOffset, out view, out proj, out splitData); // and the compound (deviceProj will potentially inverse-Z) deviceProj = GL.GetGPUProjectionMatrix(proj, false); InvertOrthographic(ref deviceProj, ref view, out vpinverse); return deviceProj * view; } public static float CalcGuardAnglePerspective(float angleInDeg, float resolution, float filterWidth, float normalBiasMax, float guardAngleMaxInDeg) { float angleInRad = angleInDeg * 0.5f * Mathf.Deg2Rad; float res = 2.0f / resolution; float texelSize = Mathf.Cos(angleInRad) * res; float beta = normalBiasMax * texelSize * 1.4142135623730950488016887242097f; float guardAngle = Mathf.Atan(beta); texelSize = Mathf.Tan(angleInRad + guardAngle) * res; guardAngle = Mathf.Atan((resolution + Mathf.Ceil(filterWidth)) * texelSize * 0.5f) * 2.0f * Mathf.Rad2Deg - angleInDeg; guardAngle *= 2.0f; return guardAngle < guardAngleMaxInDeg ? guardAngle : guardAngleMaxInDeg; } public static GPUShadowAlgorithm Pack(ShadowAlgorithm algo, ShadowVariant vari, ShadowPrecision prec) { int precshift = ShadowConstants.Bits.k_ShadowVariant + ShadowConstants.Bits.k_ShadowAlgorithm; int algoshift = ShadowConstants.Bits.k_ShadowVariant; return (GPUShadowAlgorithm)((int)prec << precshift | ((int)algo << algoshift) | (int)vari); } public static ShadowAlgorithm ExtractAlgorithm(GPUShadowAlgorithm gpuAlgo) { return (ShadowAlgorithm)(ShadowConstants.Masks.k_ShadowAlgorithm & ((int)gpuAlgo >> ShadowConstants.Bits.k_ShadowVariant)); } public static ShadowVariant ExtractVariant(GPUShadowAlgorithm gpuAlgo) { return (ShadowVariant)(ShadowConstants.Masks.k_ShadowVariant & (int)gpuAlgo); } public static ShadowPrecision ExtractPrecision(GPUShadowAlgorithm gpuAlgo) { return (ShadowPrecision)(ShadowConstants.Masks.k_ShadowPrecision & ((int)gpuAlgo >> (ShadowConstants.Bits.k_ShadowVariant + ShadowConstants.Bits.k_ShadowAlgorithm))); } public static void Unpack(GPUShadowAlgorithm gpuAlgo, out ShadowAlgorithm algo, out ShadowVariant vari, out ShadowPrecision prec) { algo = ExtractAlgorithm(gpuAlgo); vari = ExtractVariant(gpuAlgo); prec = ExtractPrecision(gpuAlgo); } public static GPUShadowAlgorithm ClearPrecision(GPUShadowAlgorithm gpuAlgo) { var algo = ExtractAlgorithm(gpuAlgo); var vari = ExtractVariant(gpuAlgo); return Pack(algo, vari, ShadowPrecision.Low); } public static float Asfloat(uint val) { unsafe { return *((float*)&val); } } public static float Asfloat(int val) { unsafe { return *((float*)&val); } } public static int Asint(float val) { unsafe { return *((int*)&val); } } public static uint Asuint(float val) { unsafe { return *((uint*)&val); } } } } // end of namespace UnityEngine.Experimental.ScriptableRenderLoop