#if GRIFFIN using UnityEngine; using System.Collections; using System.Collections.Generic; using Unity.Collections; using Unity.Jobs; using UnityEngine.Rendering; namespace Pinwheel.Griffin.Rendering { public class GTreeRenderer { public struct PrototypeCache { public bool validation; public bool canDrawInstanced; public bool canDrawBillboardInstanced; public int subMeshCount; public Vector4[] billboardImageTexcoords; public Mesh billboardMesh; //global cached, disposed in GRuntimeSettings } public static Dictionary vis; private GStylizedTerrain terrain; private GFoliage foliage; private Camera camera; private Plane[] frustum; private Vector3[] nearFrustumCorners; private Vector3[] farFrustumCorners; private Vector3 cullBoxMin; private Vector3 cullBoxMax; private const byte CULLED = 0; private const byte VISIBLE = 1; private const byte BILLBOARD = 2; private Matrix4x4 normalizedToLocalMatrix; private Matrix4x4 localToWorldMatrix; private Matrix4x4 normalizedToWorldMatrix; private float treeDistance; private float billboardStart; private float cullVolumeBias; private Vector3 terrainPosition; private Vector3 terrainSize; private List prototypes; private PrototypeCache[] prototypeCache; private bool enableInstancing; private Matrix4x4[] batchContainer; private int batchInstanceCount; private Matrix4x4[] billboardBatchContainer; private int billboardBatchInstanceCount; private int[] instancePrototypeIndices; private Matrix4x4[] instanceTransforms; private byte[] instanceCullResults; private const int BATCH_MAX_INSTANCE_COUNT = 500; private bool isWarningLogged; private const string BILLBOARD_IMAGE_TEXCOORDS = "_ImageTexcoords"; private const string BILLBOARD_IMAGE_COUNT = "_ImageCount"; private GTreeNativeData nativeData; private NativeArray frustumPlanes; private NativeArray prototypePivotOffset; private NativeArray prototypeBaseRotation; private NativeArray prototypeBaseScale; private NativeArray prototypeBounds; private NativeArray prototypeWillDoFrustumTest; public GTreeRenderer(GStylizedTerrain terrain) { this.terrain = terrain; } public void Render(Camera cam) { try { if (GRuntimeSettings.Instance.isEditingGeometry) return; InitFrame(cam); if (CullTerrain()) return; CalculateQuickInstancesCullBox(); CreateCommonJobData(); CullAndCalculateTreeTransform(); CopyInstanceNativeData(); if (enableInstancing) { DrawInstanced(); } else { Draw(); } } catch (GSkipFrameException) { } CleanUpFrame(); } private void InitFrame(Camera cam) { foliage = terrain.TerrainData.Foliage; terrainPosition = terrain.transform.position; terrainSize = terrain.TerrainData.Geometry.Size; treeDistance = terrain.TerrainData.Rendering.TreeDistance; billboardStart = terrain.TerrainData.Rendering.BillboardStart; cullVolumeBias = GRuntimeSettings.Instance.renderingDefault.treeCullBias; if (terrain.TerrainData.Foliage.Trees != null) { prototypes = terrain.TerrainData.Foliage.Trees.Prototypes; } else { prototypes = new List(); } if (prototypeCache == null || prototypeCache.Length!=prototypes.Count) { prototypeCache = new PrototypeCache[prototypes.Count]; } for (int i = 0; i < prototypes.Count; ++i) { GTreePrototype p = prototypes[i]; PrototypeCache cache = prototypeCache[i]; bool valid = prototypes[i].IsValid; cache.validation = valid; if (valid) { cache.subMeshCount = p.sharedMesh.subMeshCount; cache.canDrawInstanced = IsInstancingEnabledForAllMaterials(p); cache.canDrawBillboardInstanced = p.billboard != null && p.billboard.material != null && p.billboard.material.enableInstancing; } if (p.billboard != null) { cache.billboardMesh = GBillboardUtilities.GetMesh(p.billboard); } if (p.billboard != null && p.billboard.material != null) { if (cache.billboardImageTexcoords == null || cache.billboardImageTexcoords.Length != p.billboard.imageCount) { cache.billboardImageTexcoords = p.billboard.GetImageTexCoords(); } Material mat = p.billboard.material; mat.SetVectorArray(BILLBOARD_IMAGE_TEXCOORDS, cache.billboardImageTexcoords); mat.SetInt(BILLBOARD_IMAGE_COUNT, p.billboard.imageCount); } prototypeCache[i] = cache; } enableInstancing = terrain.TerrainData.Rendering.EnableInstancing && SystemInfo.supportsInstancing; normalizedToLocalMatrix = Matrix4x4.Scale(terrainSize); localToWorldMatrix = terrain.transform.localToWorldMatrix; normalizedToWorldMatrix = localToWorldMatrix * normalizedToLocalMatrix; camera = cam; if (frustum == null) { frustum = new Plane[6]; } GFrustumUtilities.Calculate(camera, frustum, treeDistance); if (nearFrustumCorners == null) { nearFrustumCorners = new Vector3[4]; } if (farFrustumCorners == null) { farFrustumCorners = new Vector3[4]; } if (batchContainer == null) { batchContainer = new Matrix4x4[BATCH_MAX_INSTANCE_COUNT]; } if (billboardBatchContainer == null) { billboardBatchContainer = new Matrix4x4[BATCH_MAX_INSTANCE_COUNT]; } if (!isWarningLogged) { for (int i = 0; i < prototypes.Count; ++i) { if (!prototypes[i].IsValid) { string msg = string.Format( "Tree prototye {0}: " + "The prototype is not valid, make sure you've assigned a prefab with correct mesh and materials setup.", i); Debug.LogWarning(msg); } if (enableInstancing && prototypes[i].IsValid) { if (!IsInstancingEnabledForAllMaterials(prototypes[i])) { string msg = string.Format( "Tree prototype {0} ({1}): " + "Instancing need to be enabled for all materials for the renderer to work at its best. " + "Otherwise it will fallback to non-instanced for this prototype.", i, prototypes[i].Prefab.name); Debug.LogWarning(msg); } if (prototypes[i].billboard != null && prototypes[i].billboard.material != null && prototypes[i].billboard.material.enableInstancing == false) { string msg = string.Format( "Tree prototype {0} ({1}): " + "Instancing need to be enabled for billboard material for the renderer to work at its best. " + "Otherwise it will fallback to non-instanced for this prototype when render billboards.", i, prototypes[i].Prefab.name); Debug.LogWarning(msg); } } } isWarningLogged = true; } } /// /// /// /// True if the terrain is culled private bool CullTerrain() { bool prototypeCountTest = prototypes.Count > 0; if (!prototypeCountTest) return true; bool nonZeroDistanceTest = terrain.TerrainData.Rendering.TreeDistance > 0; if (!nonZeroDistanceTest) return true; bool instanceCountTest = terrain.TerrainData.Foliage.TreeInstances.Count > 0; if (!instanceCountTest) return true; bool frustumTest = GeometryUtility.TestPlanesAABB(frustum, terrain.Bounds); if (!frustumTest) return true; return false; } private void CalculateQuickInstancesCullBox() { camera.CalculateFrustumCorners(GCommon.UnitRect, camera.nearClipPlane, Camera.MonoOrStereoscopicEye.Mono, nearFrustumCorners); camera.CalculateFrustumCorners(GCommon.UnitRect, treeDistance, Camera.MonoOrStereoscopicEye.Mono, farFrustumCorners); for (int i = 0; i < 4; ++i) { nearFrustumCorners[i] = camera.transform.TransformPoint(nearFrustumCorners[i]); farFrustumCorners[i] = camera.transform.TransformPoint(farFrustumCorners[i]); } cullBoxMin = Vector3.zero; cullBoxMax = Vector3.zero; cullBoxMin.x = Mathf.Min( nearFrustumCorners[0].x, nearFrustumCorners[1].x, nearFrustumCorners[2].x, nearFrustumCorners[3].x, farFrustumCorners[0].x, farFrustumCorners[1].x, farFrustumCorners[2].x, farFrustumCorners[3].x); cullBoxMin.y = Mathf.Min( nearFrustumCorners[0].y, nearFrustumCorners[1].y, nearFrustumCorners[2].y, nearFrustumCorners[3].y, farFrustumCorners[0].y, farFrustumCorners[1].y, farFrustumCorners[2].y, farFrustumCorners[3].y); cullBoxMin.z = Mathf.Min( nearFrustumCorners[0].z, nearFrustumCorners[1].z, nearFrustumCorners[2].z, nearFrustumCorners[3].z, farFrustumCorners[0].z, farFrustumCorners[1].z, farFrustumCorners[2].z, farFrustumCorners[3].z); cullBoxMax.x = Mathf.Max( nearFrustumCorners[0].x, nearFrustumCorners[1].x, nearFrustumCorners[2].x, nearFrustumCorners[3].x, farFrustumCorners[0].x, farFrustumCorners[1].x, farFrustumCorners[2].x, farFrustumCorners[3].x); cullBoxMax.y = Mathf.Max( nearFrustumCorners[0].y, nearFrustumCorners[1].y, nearFrustumCorners[2].y, nearFrustumCorners[3].y, farFrustumCorners[0].y, farFrustumCorners[1].y, farFrustumCorners[2].y, farFrustumCorners[3].y); cullBoxMax.z = Mathf.Max( nearFrustumCorners[0].z, nearFrustumCorners[1].z, nearFrustumCorners[2].z, nearFrustumCorners[3].z, farFrustumCorners[0].z, farFrustumCorners[1].z, farFrustumCorners[2].z, farFrustumCorners[3].z); cullBoxMin -= Vector3.one * cullVolumeBias; cullBoxMax += Vector3.one * cullVolumeBias; cullBoxMin = terrain.WorldPointToNormalized(cullBoxMin); cullBoxMax = terrain.WorldPointToNormalized(cullBoxMax); } private void CreateCommonJobData() { frustumPlanes = new NativeArray(frustum, Allocator.TempJob); prototypePivotOffset = new NativeArray(prototypes.Count, Allocator.TempJob); prototypeBaseRotation = new NativeArray(prototypes.Count, Allocator.TempJob); prototypeBaseScale = new NativeArray(prototypes.Count, Allocator.TempJob); prototypeBounds = new NativeArray(prototypes.Count, Allocator.TempJob); prototypeWillDoFrustumTest = new NativeArray(prototypes.Count, Allocator.TempJob); } private void CullAndCalculateTreeTransform() { if (nativeData == null) { nativeData = new GTreeNativeData(terrain.TerrainData.Foliage.TreeInstances); } bool willSkipFrame = false; try { for (int i = 0; i < prototypes.Count; ++i) { prototypePivotOffset[i] = prototypes[i].PivotOffset; prototypeBaseRotation[i] = prototypes[i].BaseRotation; prototypeBaseScale[i] = prototypes[i].BaseScale; prototypeBounds[i] = prototypes[i].GetBoundingSphere(); prototypeWillDoFrustumTest[i] = IsInstancingEnabledForAllMaterials(prototypes[i]); } GCullAndCalculateTreeTransformJob job = new GCullAndCalculateTreeTransformJob() { instances = nativeData.instances, prototypeIndices = nativeData.prototypeIndices, transforms = nativeData.trs, prototypePivotOffset = prototypePivotOffset, prototypeBaseRotation = prototypeBaseRotation, prototypeBaseScale = prototypeBaseScale, cullResult = nativeData.cullResults, cullBoxMin = cullBoxMin, cullBoxMax = cullBoxMax, flagCulled = CULLED, flagVisible = VISIBLE, flagBillboard = BILLBOARD, terrainPos = terrainPosition, terrainSize = terrainSize, cameraPos = camera.transform.position, treeDistance = treeDistance, billboardStart = billboardStart, cullVolumeBias = cullVolumeBias, prototypeBounds = prototypeBounds, prototypeWillDoFrustumTest = prototypeWillDoFrustumTest, frustum = frustumPlanes }; JobHandle handle = job.Schedule(nativeData.instances.Length, 100); handle.Complete(); } catch (System.InvalidOperationException) { foliage.TreeAllChanged(); willSkipFrame = true; } catch (System.Exception e) { Debug.LogException(e); } prototypePivotOffset.Dispose(); prototypeBaseRotation.Dispose(); prototypeBaseScale.Dispose(); prototypeBounds.Dispose(); prototypeWillDoFrustumTest.Dispose(); frustumPlanes.Dispose(); if (willSkipFrame) { throw new GSkipFrameException(); } } private bool IsInstancingEnabledForAllMaterials(GTreePrototype p) { if (!enableInstancing) return false; if (p.SharedMaterials == null || p.SharedMaterials.Length == 0) return false; for (int i = 0; i < p.SharedMaterials.Length; ++i) { if (!p.SharedMaterials[i].enableInstancing) { return false; } } return true; } private void CopyInstanceNativeData() { if (instancePrototypeIndices == null || instancePrototypeIndices.Length != nativeData.prototypeIndices.Length) { instancePrototypeIndices = new int[nativeData.prototypeIndices.Length]; } if (instanceTransforms == null || instanceTransforms.Length != nativeData.trs.Length) { instanceTransforms = new Matrix4x4[nativeData.trs.Length]; } if (instanceCullResults == null || instanceCullResults.Length != nativeData.cullResults.Length) { instanceCullResults = new byte[nativeData.cullResults.Length]; } nativeData.prototypeIndices.CopyTo(instancePrototypeIndices); nativeData.trs.CopyTo(instanceTransforms); nativeData.cullResults.CopyTo(instanceCullResults); } private void Draw() { GTreePrototype proto; Mesh mesh; Material[] materials; Material billboardMaterial; int protoIndex = 0; int submeshCount = 0; int drawCount = 0; Matrix4x4 trs; int count = instancePrototypeIndices.Length; for (int i = 0; i < count; ++i) { if (instanceCullResults[i] == CULLED) continue; protoIndex = instancePrototypeIndices[i]; if (prototypeCache[protoIndex].validation == false) continue; proto = prototypes[protoIndex]; trs = instanceTransforms[i]; if (instanceCullResults[i] == VISIBLE) { mesh = proto.sharedMesh; materials = proto.sharedMaterials; submeshCount = prototypeCache[protoIndex].subMeshCount; drawCount = Mathf.Min(materials.Length, submeshCount); for (int d = 0; d < drawCount; ++d) { Graphics.DrawMesh( mesh, trs, materials[d], proto.layer, camera, d, null, proto.shadowCastingMode, proto.receiveShadow, null, LightProbeUsage.BlendProbes, null); } } else { if (proto.billboard == null) continue; mesh = prototypeCache[protoIndex].billboardMesh; billboardMaterial = proto.billboard.material; Graphics.DrawMesh( mesh, trs, billboardMaterial, proto.layer, camera, 0, null, proto.billboardShadowCastingMode, proto.BillboardReceiveShadow, null, LightProbeUsage.BlendProbes, null); } } } private void DrawInstanced() { for (int i = 0; i < prototypes.Count; ++i) { if (prototypeCache[i].validation == false) continue; DrawInstanced(i); } } private void DrawInstanced(int prototypeIndex) { GTreePrototype proto = prototypes[prototypeIndex]; Mesh mesh = proto.sharedMesh; Material[] materials = proto.sharedMaterials; int submeshCount = prototypeCache[prototypeIndex].subMeshCount; int drawCount = Mathf.Min(submeshCount, materials.Length); Mesh billboardMesh = null; Material billboardMaterial = null; BillboardAsset billboard = proto.billboard; if (billboard != null) { billboardMesh = GBillboardUtilities.GetMesh(billboard); billboardMaterial = billboard.material; } bool canDrawInstanced = prototypeCache[prototypeIndex].canDrawInstanced; bool canDrawBillboardInstanced = prototypeCache[prototypeIndex].canDrawBillboardInstanced; batchInstanceCount = 0; billboardBatchInstanceCount = 0; int count = instancePrototypeIndices.Length; for (int i = 0; i <= count; ++i) { if (i == count || batchInstanceCount == BATCH_MAX_INSTANCE_COUNT) { if (canDrawInstanced) { for (int d = 0; d < drawCount; ++d) { Graphics.DrawMeshInstanced( mesh, d, materials[d], batchContainer, batchInstanceCount, null, proto.shadowCastingMode, proto.receiveShadow, proto.layer, camera, LightProbeUsage.BlendProbes); } batchInstanceCount = 0; } } if (i == count || billboardBatchInstanceCount == BATCH_MAX_INSTANCE_COUNT) { if (billboard != null && canDrawBillboardInstanced) { Graphics.DrawMeshInstanced( billboardMesh, 0, billboardMaterial, billboardBatchContainer, billboardBatchInstanceCount, null, proto.billboardShadowCastingMode, proto.BillboardReceiveShadow, proto.layer, camera, LightProbeUsage.BlendProbes); billboardBatchInstanceCount = 0; } } if (i == count) break; if (instanceCullResults[i] == CULLED) continue; if (instancePrototypeIndices[i] != prototypeIndex) continue; if (instanceCullResults[i] == VISIBLE) { if (canDrawInstanced) { batchContainer[batchInstanceCount] = instanceTransforms[i]; batchInstanceCount += 1; } else { for (int d = 0; d < drawCount; ++d) { Graphics.DrawMesh( mesh, instanceTransforms[i], materials[d], proto.layer, camera, d, null, proto.shadowCastingMode, proto.receiveShadow, null, LightProbeUsage.BlendProbes, null); } } } else if (instanceCullResults[i] == BILLBOARD && billboard != null) { if (canDrawBillboardInstanced) { billboardBatchContainer[billboardBatchInstanceCount] = instanceTransforms[i]; billboardBatchInstanceCount += 1; } else { Graphics.DrawMesh( billboardMesh, instanceTransforms[i], billboardMaterial, proto.layer, camera, 0, null, proto.billboardShadowCastingMode, proto.BillboardReceiveShadow, null, LightProbeUsage.BlendProbes, null); } } } } private void CleanUpFrame() { GNativeArrayUtilities.Dispose(frustumPlanes); GNativeArrayUtilities.Dispose(prototypePivotOffset); GNativeArrayUtilities.Dispose(prototypeBaseRotation); GNativeArrayUtilities.Dispose(prototypeBaseScale); GNativeArrayUtilities.Dispose(prototypeBounds); GNativeArrayUtilities.Dispose(prototypeWillDoFrustumTest); } internal void ResetTrees() { if (nativeData != null) { nativeData.Dispose(); nativeData = null; } } internal void CleanUp() { ResetTrees(); } } } #endif