Unity中无GC Alloc的CalculateFrustumPlanes
如果你需要在逻辑层做一些预先的剔除操作,可能需要从MainCamera构建视锥体,然后进行简易相交测试,这时候在unity里面用到的函数接口是CalculateFrustumPlanes:
1 namespace UnityEngine 2 { 3 // 摘要: 4 // Utility class for common geometric functions. 5 public sealed class GeometryUtility 6 { 7 public GeometryUtility(); 8 9 // 摘要: 10 // Calculates frustum planes. 11 public static Plane[] CalculateFrustumPlanes(Camera camera); 12 // 13 // 摘要: 14 // Calculates frustum planes. 15 public static Plane[] CalculateFrustumPlanes(Matrix4x4 worldToProjectionMatrix); 16 // 17 // 摘要: 18 // Returns true if bounds are inside the plane array. 19 public static bool TestPlanesAABB(Plane[] planes, Bounds bounds); 20 } 21 }
然而它的主要问题是有gc alloc,每次调用都会自己new一个Plane数组,这很明显是不科学的,然而unity迟迟未修复此问题。
下面提供的函数在C#层中重新实现了这个接口,同时没有gcalloc,然而由于在C#中实现的原因,其效率比引擎提供的C++版本慢一倍。C#版本实测一次调用在0.01毫秒左右。所以使用哪一个版本,根据实际需求来是最好的。
C#版本的实现:
1 public static class GeometryUtilityUser 2 { 3 /** 4 * @warning OutPlanes must be new Plane[6] 5 * Plane Position : 6 * Left 7 * Right 8 * Bottom 9 * Top 10 * Near 11 * Far 12 */ 13 enum EPlaneSide 14 { 15 Left, 16 Right, 17 Bottom, 18 Top, 19 Near, 20 Far 21 } 22 23 static float[] RootVector = new float[4]; 24 static float[] ComVector = new float[4]; 25 26 public static void CalculateFrustumPlanes(Camera InCamera, ref Plane[] OutPlanes) 27 { 28 Matrix4x4 projectionMatrix = InCamera.projectionMatrix; 29 Matrix4x4 worldToCameraMatrix = InCamera.worldToCameraMatrix; 30 Matrix4x4 worldToProjectionMatrix = projectionMatrix * worldToCameraMatrix; 31 32 RootVector[0] = worldToProjectionMatrix[3, 0]; 33 RootVector[1] = worldToProjectionMatrix[3, 1]; 34 RootVector[2] = worldToProjectionMatrix[3, 2]; 35 RootVector[3] = worldToProjectionMatrix[3, 3]; 36 37 ComVector[0] = worldToProjectionMatrix[0, 0]; 38 ComVector[1] = worldToProjectionMatrix[0, 1]; 39 ComVector[2] = worldToProjectionMatrix[0, 2]; 40 ComVector[3] = worldToProjectionMatrix[0, 3]; 41 42 CalcPlane(ref OutPlanes[(int)EPlaneSide.Left], ComVector[0] + RootVector[0], ComVector[1] + RootVector[1], ComVector[2] + RootVector[2], ComVector[3] + RootVector[3]); 43 CalcPlane(ref OutPlanes[(int)EPlaneSide.Right], -ComVector[0] + RootVector[0], -ComVector[1] + RootVector[1], -ComVector[2] + RootVector[2], -ComVector[3] + RootVector[3]); 44 45 ComVector[0] = worldToProjectionMatrix[1, 0]; 46 ComVector[1] = worldToProjectionMatrix[1, 1]; 47 ComVector[2] = worldToProjectionMatrix[1, 2]; 48 ComVector[3] = worldToProjectionMatrix[1, 3]; 49 50 CalcPlane(ref OutPlanes[(int)EPlaneSide.Bottom], ComVector[0] + RootVector[0], ComVector[1] + RootVector[1], ComVector[2] + RootVector[2], ComVector[3] + RootVector[3]); 51 CalcPlane(ref OutPlanes[(int)EPlaneSide.Top], -ComVector[0] + RootVector[0], -ComVector[1] + RootVector[1], -ComVector[2] + RootVector[2], -ComVector[3] + RootVector[3]); 52 53 ComVector[0] = worldToProjectionMatrix[2, 0]; 54 ComVector[1] = worldToProjectionMatrix[2, 1]; 55 ComVector[2] = worldToProjectionMatrix[2, 2]; 56 ComVector[3] = worldToProjectionMatrix[2, 3]; 57 58 CalcPlane(ref OutPlanes[(int)EPlaneSide.Near], ComVector[0] + RootVector[0], ComVector[1] + RootVector[1], ComVector[2] + RootVector[2], ComVector[3] + RootVector[3]); 59 CalcPlane(ref OutPlanes[(int)EPlaneSide.Far], -ComVector[0] + RootVector[0], -ComVector[1] + RootVector[1], -ComVector[2] + RootVector[2], -ComVector[3] + RootVector[3]); 60 61 } 62 63 static void CalcPlane(ref Plane InPlane, float InA, float InB, float InC, float InDistance) 64 { 65 Vector3 Normal = new Vector3(InA, InB, InC); 66 67 float InverseMagnitude = 1.0f / (float)System.Math.Sqrt(Normal.x * Normal.x + Normal.y * Normal.y + Normal.z * Normal.z); 68 69 InPlane.normal = new Vector3(Normal.x * InverseMagnitude, Normal.y * InverseMagnitude, Normal.z * InverseMagnitude); 70 71 InPlane.distance = InDistance * InverseMagnitude; 72 } 73 }
下面的代码可用于验证其正确性:
1 private Plane[] CalcFrustum(Camera InCamera) 2 { 3 GeometryUtilityUser.CalculateFrustumPlanes(InCamera, ref CachedPlanes); 4 #if UNITY_EDITOR && false 5 Plane[] SysPlanes = GeometryUtility.CalculateFrustumPlanes(InCamera); 6 for (int i = 0; i < SysPlanes.Length; ++i ) 7 { 8 if( !IsEqual(SysPlanes[i], CachedPlanes[i]) ) 9 { 10 DebugHelper.Assert(false, "Internal error in CalcFrustum"); 11 } 12 } 13 #endif 14 return CachedPlanes; 15 } 16 private static bool IsEqual(Plane InFirst, Plane InSecond) 17 { 18 return IsEqual(InFirst.normal, InSecond.normal) && 19 IsEqual(InFirst.distance, InSecond.distance); 20 } 21 private static bool IsEqual(Vector3 InFirst, Vector3 InSecond) 22 { 23 return IsEqual(InFirst.x, InSecond.x) && 24 IsEqual(InFirst.y, InSecond.y) && 25 IsEqual(InFirst.y, InSecond.y); 26 } 27 private static bool IsEqual(float InFirst, float InSecond) 28 { 29 return System.Math.Abs(InFirst - InSecond) < 0.001f; 30 } 31 private Plane[] CachedPlanes = new Plane[6];