This commit is contained in:
CortexCore
2025-03-14 21:04:19 +08:00
parent ff8670c453
commit 757ffe79ee
1282 changed files with 104378 additions and 3 deletions

View File

@@ -0,0 +1,73 @@
#if GRIFFIN
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace Pinwheel.Griffin.Rendering
{
public static class GBillboardUtilities
{
private static Dictionary<BillboardAsset, Mesh> billboardMeshes;
private static Dictionary<BillboardAsset, Mesh> BillboardMeshes
{
get
{
if (billboardMeshes == null)
{
billboardMeshes = new Dictionary<BillboardAsset, Mesh>();
}
return billboardMeshes;
}
}
public static Mesh GetMesh(BillboardAsset billboard)
{
if (!BillboardMeshes.ContainsKey(billboard))
{
BillboardMeshes.Add(billboard, null);
}
if (BillboardMeshes[billboard] == null)
{
Mesh m = CreateMesh(billboard);
BillboardMeshes[billboard] = m;
}
return BillboardMeshes[billboard];
}
private static Mesh CreateMesh(BillboardAsset billboard)
{
Mesh m = new Mesh();
Vector2[] uvs = billboard.GetVertices();
Vector3[] vertices = new Vector3[billboard.vertexCount];
for (int i = 0; i < vertices.Length; ++i)
{
vertices[i] = new Vector3(
(uvs[i].x - 0.5f) * billboard.width,
uvs[i].y * billboard.height + billboard.bottom,
0);
}
ushort[] tris = billboard.GetIndices();
int[] trisInt = new int[tris.Length];
for (int i = 0; i < trisInt.Length; ++i)
{
trisInt[i] = tris[i];
}
m.vertices = vertices;
m.uv = uvs;
m.triangles = trisInt;
m.name = billboard.name;
return m;
}
public static void CleanUp()
{
foreach (Mesh m in BillboardMeshes.Values)
{
GUtilities.DestroyObject(m);
}
BillboardMeshes.Clear();
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e3bf1b19b259c514e84f8bf8edf83cbc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,67 @@
#if GRIFFIN
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Jobs;
using Unity.Burst;
namespace Pinwheel.Griffin.Rendering
{
#if GRIFFIN_BURST
[BurstCompile(CompileSynchronously = true)]
#endif
public struct GBuildInstancedBatchJob : IJob
{
[ReadOnly]
public NativeArray<GGrassInstance> instances;
[WriteOnly]
public NativeArray<Vector3Int> batchMetadata; //x: prototypeIndex, y: startIndex, z: length
public int maxLength;
public void Execute()
{
int startIndex = 0;
int prototypeIndex = int.MinValue;
Vector3Int metadata = new Vector3Int(instances[0].prototypeIndex, startIndex, 0);
int batchCount = 0;
int length = instances.Length;
for (int i = 0; i < length; ++i)
{
prototypeIndex = instances[i].prototypeIndex;
if (prototypeIndex != metadata.x)
{
metadata.z = i - metadata.y;
batchCount += 1;
batchMetadata[batchCount] = metadata;
metadata.x = prototypeIndex;
metadata.y = i;
metadata.z = 0;
}
else if (i - metadata.y + 1 > maxLength)
{
metadata.z = maxLength;
batchCount += 1;
batchMetadata[batchCount] = metadata;
metadata.y = i;
metadata.z = 0;
}
else if (i == length - 1)
{
metadata.z = i - metadata.y + 1;
batchCount += 1;
batchMetadata[batchCount] = metadata;
}
}
metadata.Set(-1, -1, batchCount);
batchMetadata[0] = metadata;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: acea7ee9e78d0af4e9c7e5fbfa9dce69
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,62 @@
#if GRIFFIN
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Unity.Jobs;
using Unity.Collections;
using Unity.Burst;
namespace Pinwheel.Griffin.Rendering
{
#if GRIFFIN_BURST
[BurstCompile(CompileSynchronously = true)]
#endif
public struct GCalculateGrassTransformJob : IJobParallelFor
{
[ReadOnly]
public NativeArray<GGrassInstance> instances;
[ReadOnly]
public NativeArray<float> prototypePivotOffset;
[ReadOnly]
public NativeArray<Vector3> prototypeSize;
[WriteOnly]
public NativeArray<Matrix4x4> transforms;
public Vector3 terrainSize;
public Vector3 terrainPos;
public void Execute(int i)
{
GGrassInstance grass = instances[i];
float pivotOffset;
Vector3 size;
if (grass.prototypeIndex < 0 || grass.prototypeIndex >= prototypePivotOffset.Length)
{
pivotOffset = 0;
size = Vector3.one;
}
else
{
pivotOffset = prototypePivotOffset[grass.prototypeIndex];
size = prototypeSize[grass.prototypeIndex];
}
Vector3 worldPos = new Vector3(
grass.position.x * terrainSize.x + terrainPos.x,
grass.position.y * terrainSize.y + terrainPos.y + pivotOffset,
grass.position.z * terrainSize.z + terrainPos.z);
Vector3 worldScale = new Vector3(
grass.scale.x * size.x,
grass.scale.y * size.y,
grass.scale.z * size.z);
Matrix4x4 matrix = Matrix4x4.TRS(worldPos, grass.rotation, worldScale);
transforms[i] = matrix;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1110bbb1c37eb2e4dbf8e5bfda3400be
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
#if GRIFFIN
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace Pinwheel.Griffin.Rendering
{
public struct GCellCullingParams
{
public Vector3 boundCenter;
public Vector3 boundSize;
public int instanceCount;
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cb0decc1cc19c2f48a3b8528dbea10d3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,138 @@
#if GRIFFIN
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Unity.Jobs;
using Unity.Collections;
using Unity.Burst;
namespace Pinwheel.Griffin.Rendering
{
#if GRIFFIN_BURST
[BurstCompile(CompileSynchronously = true)]
#endif
public struct GCullAndCalculateTreeTransformJob : IJobParallelFor
{
[ReadOnly]
public NativeArray<GTreeInstance> instances;
[ReadOnly]
public NativeArray<float> prototypePivotOffset;
[ReadOnly]
public NativeArray<Quaternion> prototypeBaseRotation;
[ReadOnly]
public NativeArray<Vector3> prototypeBaseScale;
[ReadOnly]
public NativeArray<BoundingSphere> prototypeBounds;
[ReadOnly]
public NativeArray<bool> prototypeWillDoFrustumTest;
[ReadOnly]
public NativeArray<Plane> frustum;
public NativeArray<int> prototypeIndices;
[WriteOnly]
public NativeArray<Matrix4x4> transforms;
[WriteOnly]
public NativeArray<byte> cullResult;
public Vector3 cullBoxMin;
public Vector3 cullBoxMax;
public byte flagCulled;
public byte flagVisible;
public byte flagBillboard;
public Vector3 terrainPos;
public Vector3 terrainSize;
public Vector3 cameraPos;
public float treeDistance;
public float billboardStart;
public float cullVolumeBias;
public void Execute(int index)
{
GTreeInstance tree = instances[index];
if (tree.prototypeIndex < 0 || tree.prototypeIndex >= prototypePivotOffset.Length)
{
cullResult[index] = flagCulled;
return;
}
if (tree.position.x < cullBoxMin.x || tree.position.x > cullBoxMax.x ||
tree.position.y < cullBoxMin.y || tree.position.y > cullBoxMax.y ||
tree.position.z < cullBoxMin.z || tree.position.z > cullBoxMax.z)
{
cullResult[index] = flagCulled;
return;
}
float pivotOffset = prototypePivotOffset[tree.prototypeIndex];
Vector3 worldPos = new Vector3(
tree.position.x * terrainSize.x + terrainPos.x,
tree.position.y * terrainSize.y + terrainPos.y + pivotOffset,
tree.position.z * terrainSize.z + terrainPos.z);
float sqrDistance = Vector3.SqrMagnitude(worldPos - cameraPos);
float sqrTreeDistance = treeDistance * treeDistance;
if (sqrDistance > sqrTreeDistance)
{
cullResult[index] = flagCulled;
return;
}
Vector3 baseScale = prototypeBaseScale[tree.prototypeIndex];
Vector3 worldScale = new Vector3(
tree.scale.x * baseScale.x,
tree.scale.y * baseScale.y,
tree.scale.z * baseScale.z);
bool testFrustum = prototypeWillDoFrustumTest[tree.prototypeIndex];
if (testFrustum)
{
BoundingSphere b = prototypeBounds[tree.prototypeIndex];
b.position = worldPos;
b.radius *= Mathf.Max(worldScale.x, Mathf.Max(worldScale.y, worldScale.z));
b.radius += cullVolumeBias;
if (!DoFrustumTest(frustum, b))
{
cullResult[index] = flagCulled;
return;
}
}
float sqrBillboardStart = billboardStart * billboardStart;
if (sqrDistance >= sqrBillboardStart)
{
cullResult[index] = flagBillboard;
}
else
{
cullResult[index] = flagVisible;
}
if (prototypeIndices[index] < 0)
{
Quaternion baseRotation = prototypeBaseRotation[tree.prototypeIndex];
Quaternion worldRotation = tree.rotation * baseRotation;
Matrix4x4 matrix = Matrix4x4.TRS(worldPos, worldRotation, worldScale);
transforms[index] = matrix;
prototypeIndices[index] = tree.prototypeIndex;
}
}
private bool DoFrustumTest(NativeArray<Plane> frustum, BoundingSphere bounds)
{
float d = 0;
for (int i = 0; i < 6; ++i)
{
d = frustum[i].GetDistanceToPoint(bounds.position);
if (d < -bounds.radius)
{
return false;
}
}
return true;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fddf2b3749fae7244ba21feba47fc595
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,64 @@
#if GRIFFIN
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Unity.Jobs;
using Unity.Burst;
using Unity.Collections;
namespace Pinwheel.Griffin.Rendering
{
#if GRIFFIN_BURST
[BurstCompile(CompileSynchronously = true)]
#endif
public struct GCullCellJob : IJobParallelFor
{
[ReadOnly]
public NativeArray<GCellCullingParams> cullParams;
[ReadOnly]
public NativeArray<Plane> frustum;
public Matrix4x4 normalizeToWorldMatrix;
public int visibleValue;
public int culledValue;
[WriteOnly]
public NativeArray<int> cullResults;
public void Execute(int index)
{
GCellCullingParams param = cullParams[index];
if (param.instanceCount == 0)
{
cullResults[index] = culledValue;
return;
}
BoundingSphere b = new BoundingSphere();
b.position = normalizeToWorldMatrix.MultiplyPoint(param.boundCenter);
b.radius = normalizeToWorldMatrix.MultiplyVector(param.boundSize).x;
if (!DoFrustumTest(frustum, b))
{
cullResults[index] = culledValue;
}
else
{
cullResults[index] = visibleValue;
}
}
private bool DoFrustumTest(NativeArray<Plane> frustum, BoundingSphere bounds)
{
float d = 0;
for (int i = 0; i < 6; ++i)
{
d = frustum[i].GetDistanceToPoint(bounds.position);
if (d < -bounds.radius)
{
return false;
}
}
return true;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 253a86558e40f194781e257e714032d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
#if GRIFFIN
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace Pinwheel.Griffin.Rendering
{
public static class GFrustumUtilities
{
private static Vector3[] corners = new Vector3[4];
public static void Calculate(Camera cam, Plane[] planes, float zFar)
{
GeometryUtility.CalculateFrustumPlanes(cam, planes);
cam.CalculateFrustumCorners(GCommon.UnitRect, zFar, Camera.MonoOrStereoscopicEye.Mono, corners);
planes[5].Set3Points(
cam.transform.TransformPoint(corners[0]),
cam.transform.TransformPoint(corners[1]),
cam.transform.TransformPoint(corners[2]));
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b88f92cc59acf6948bfe150a5369d579
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,94 @@
#if GRIFFIN
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace Pinwheel.Griffin.Rendering
{
public static class GGrassMaterialProvider
{
public const string FADE_KW = "FADE";
public static Material GetMaterial(bool isInteractiveGrassEnabled, bool isBillboardEnabled)
{
Material mat;
if (isInteractiveGrassEnabled)
{
mat = GetInteractiveGrassMaterial();
}
else
{
mat = GetNonInteractiveGrassMaterial(isBillboardEnabled);
}
if (mat != null)
{
mat.EnableKeyword(FADE_KW);
}
return mat;
}
private static Material GetInteractiveGrassMaterial()
{
GRenderPipelineType pipeline = GCommon.CurrentRenderPipeline;
if (pipeline == GRenderPipelineType.Builtin)
{
return GRuntimeSettings.Instance.foliageRendering.grassInteractiveMaterial;
}
else if (pipeline == GRenderPipelineType.Universal)
{
return GRuntimeSettings.Instance.foliageRendering.urpGrassInteractiveMaterial;
}
else
{
return null;
}
}
private static Material GetNonInteractiveGrassMaterial(bool isBillboardEnabled)
{
if (isBillboardEnabled)
{
return GetNonInteractiveBillboardGrassMaterial();
}
else
{
return GetNonInteractiveNonBillboardGrassMaterial();
}
}
private static Material GetNonInteractiveNonBillboardGrassMaterial()
{
GRenderPipelineType pipeline = GCommon.CurrentRenderPipeline;
if (pipeline == GRenderPipelineType.Builtin)
{
return GRuntimeSettings.Instance.foliageRendering.grassMaterial;
}
else if (pipeline == GRenderPipelineType.Universal)
{
return GRuntimeSettings.Instance.foliageRendering.urpGrassMaterial;
}
else
{
return null;
}
}
private static Material GetNonInteractiveBillboardGrassMaterial()
{
GRenderPipelineType pipeline = GCommon.CurrentRenderPipeline;
if (pipeline == GRenderPipelineType.Builtin)
{
return GRuntimeSettings.Instance.foliageRendering.grassBillboardMaterial;
}
else if (pipeline == GRenderPipelineType.Universal)
{
return GRuntimeSettings.Instance.foliageRendering.urpGrassBillboardMaterial;
}
else
{
return null;
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9f2be55c77b2e0947a2096f0d2369ef7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,14 @@
#if GRIFFIN
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
namespace Pinwheel.Griffin.Rendering
{
internal class GGrassPatchData
{
internal GInstancedBatch[] instancedBatches;
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d8875b6227e50924cbf6acfb4716d1ca
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,32 @@
#if GRIFFIN
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using Unity.Collections;
namespace Pinwheel.Griffin.Rendering
{
internal class GGrassPatchNativeData : IDisposable
{
internal NativeArray<GGrassInstance> instances;
internal NativeArray<Matrix4x4> trs;
internal NativeArray<Vector3Int> metadata;
public GGrassPatchNativeData(List<GGrassInstance> grasses)
{
grasses.Sort((g0, g1) => { return g0.prototypeIndex.CompareTo(g1.prototypeIndex); });
instances = new NativeArray<GGrassInstance>(grasses.ToArray(), Allocator.TempJob);
trs = new NativeArray<Matrix4x4>(grasses.Count, Allocator.TempJob);
metadata = new NativeArray<Vector3Int>(grasses.Count + 1, Allocator.TempJob);
}
public void Dispose()
{
GNativeArrayUtilities.Dispose(instances);
GNativeArrayUtilities.Dispose(trs);
GNativeArrayUtilities.Dispose(metadata);
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b7e4e48d2e2be864bb222e58505db2da
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,533 @@
#if GRIFFIN
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine.Rendering;
namespace Pinwheel.Griffin.Rendering
{
public class GGrassRenderer
{
public delegate void ConfiguringMaterialHandler(GStylizedTerrain terrain, int prototypeIndex, MaterialPropertyBlock propertyBlock);
public static event ConfiguringMaterialHandler ConfiguringMaterial;
private GStylizedTerrain terrain;
private Camera camera;
private Plane[] frustum;
private int[] cellCullResults;
private int[] cellCulledFrameCount;
private float[] cellSqrDistanceToCam;
private Bounds[] cellWorldBounds;
private List<int> visibleCells;
private List<int> cellToProcess;
private const int CULLED = 0;
private const int VISIBLE = 1;
private const int FRAME_COUNT_TO_UNLOAD_CELL = 100;
private Matrix4x4 normalizedToLocalMatrix;
private Matrix4x4 localToWorldMatrix;
private Matrix4x4 normalizedToWorldMatrix;
private float grassDistance;
private Vector3 terrainPosition;
private Vector3 terrainSize;
private float cullBias;
private GGrassPatch[] cells;
private GGrassPatchData[] cellsData;
private GGrassPatchNativeData[] cellsNativeData;
private List<GGrassPrototype> prototypes;
private MaterialPropertyBlock[] propertyBlocks;
private IGGrassMaterialConfigurator materialConfigurator;
private Mesh[] baseMeshes;
private Material[] materials;
private const int BATCH_MAX_INSTANCE_COUNT = 1023;
private bool willIgnoreCellLimit;
public GGrassRenderer(GStylizedTerrain terrain)
{
this.terrain = terrain;
RecalculateCellBounds();
}
private void RecalculateCellBounds()
{
GGrassPatch[] cells = terrain.TerrainData.Foliage.GrassPatches;
for (int i = 0; i < cells.Length; ++i)
{
cells[i].RecalculateBounds();
}
}
public void Render(Camera cam)
{
try
{
if (GRuntimeSettings.Instance.isEditingGeometry)
return;
if (!CheckSystemInstancingAvailable())
return;
InitFrame(cam);
if (CullTerrain())
return;
CullCells();
CalculateAndCacheTransforms();
BuildBatches();
ConfigureMaterial();
Submit();
}
catch (GSkipFrameException)
{ }
CleanUpFrame();
}
private bool CheckSystemInstancingAvailable()
{
return SystemInfo.supportsInstancing;
}
private void InitFrame(Camera cam)
{
if (cullBias != GRuntimeSettings.Instance.renderingDefault.grassCullBias)
{
ClearAllCells();
}
cullBias = GRuntimeSettings.Instance.renderingDefault.grassCullBias;
terrainPosition = terrain.transform.position;
terrainSize = terrain.TerrainData.Geometry.Size;
grassDistance = terrain.TerrainData.Rendering.GrassDistance;
cells = terrain.TerrainData.Foliage.GrassPatches;
willIgnoreCellLimit =
cellsData == null ||
GRuntimeSettings.Instance.isEditingFoliage;
if (cellsData == null || cellsData.Length != cells.Length)
{
cellsData = new GGrassPatchData[cells.Length];
}
for (int i = 0; i < cellsData.Length; ++i)
{
if (cellsData[i] == null)
{
cellsData[i] = new GGrassPatchData();
}
}
if (cellsNativeData == null || cellsNativeData.Length != cells.Length)
{
cellsNativeData = new GGrassPatchNativeData[cells.Length];
}
if (visibleCells == null)
{
visibleCells = new List<int>();
}
if (cellToProcess == null)
{
cellToProcess = new List<int>();
}
if (terrain.TerrainData.Foliage.Grasses != null)
{
prototypes = terrain.TerrainData.Foliage.Grasses.Prototypes;
if (propertyBlocks == null || propertyBlocks.Length != prototypes.Count)
{
propertyBlocks = new MaterialPropertyBlock[prototypes.Count];
}
}
else
{
prototypes = new List<GGrassPrototype>();
}
if (materialConfigurator == null)
{
materialConfigurator = System.Activator.CreateInstance<GSimpleGrassMaterialConfigurator>();
}
normalizedToLocalMatrix = Matrix4x4.Scale(terrainSize);
localToWorldMatrix = terrain.transform.localToWorldMatrix;
normalizedToWorldMatrix = localToWorldMatrix * normalizedToLocalMatrix;
camera = cam;
if (frustum == null)
{
frustum = new Plane[6];
}
GFrustumUtilities.Calculate(camera, frustum, grassDistance);
if (cellCullResults == null || cellCullResults.Length != cells.Length)
{
cellCullResults = new int[cells.Length];
}
if (cellCulledFrameCount == null || cellCulledFrameCount.Length != cells.Length)
{
cellCulledFrameCount = new int[cells.Length];
}
if (cellSqrDistanceToCam == null || cellSqrDistanceToCam.Length != cells.Length)
{
cellSqrDistanceToCam = new float[cells.Length];
}
if (cellWorldBounds == null || cellWorldBounds.Length != cells.Length)
{
cellWorldBounds = new Bounds[cells.Length];
for (int i = 0; i < cells.Length; ++i)
{
cellWorldBounds[i] = new Bounds()
{
center = normalizedToWorldMatrix.MultiplyPoint(cells[i].bounds.center),
size = normalizedToWorldMatrix.MultiplyVector(cells[i].bounds.size) + Vector3.one * cullBias
};
}
}
GUtilities.EnsureArrayLength(ref baseMeshes, prototypes.Count);
GUtilities.EnsureArrayLength(ref materials, prototypes.Count);
for (int i = 0; i < prototypes.Count; ++i)
{
baseMeshes[i] = prototypes[i].GetBaseMesh();
materials[i] =
prototypes[i].Shape == GGrassShape.DetailObject ?
prototypes[i].DetailMaterial :
GGrassMaterialProvider.GetMaterial(terrain.TerrainData.Foliage.EnableInteractiveGrass, prototypes[i].IsBillboard);
}
}
/// <summary>
///
/// </summary>
/// <returns>True if the terrain is culled</returns>
private bool CullTerrain()
{
bool prototypeCountTest = prototypes.Count > 0;
if (!prototypeCountTest)
return true;
bool nonZeroDistanceTest = terrain.TerrainData.Rendering.GrassDistance > 0;
if (!nonZeroDistanceTest)
return true;
bool frustumTest = GeometryUtility.TestPlanesAABB(frustum, terrain.Bounds);
if (!frustumTest)
return true;
return false;
}
private void CullCells()
{
visibleCells.Clear();
cellToProcess.Clear();
Vector3 camWorldPos = camera.transform.position;
for (int i = 0; i < cells.Length; ++i)
{
if (cells[i].InstanceCount == 0)
{
cellCullResults[i] = CULLED;
}
else
{
int cullResult = GeometryUtility.TestPlanesAABB(frustum, cellWorldBounds[i]) ? VISIBLE : CULLED;
cellCullResults[i] = cullResult;
if (cullResult == VISIBLE)
{
visibleCells.Add(i);
}
}
if (cellCullResults[i] == CULLED)
{
cellCulledFrameCount[i] += 1;
}
else
{
cellCulledFrameCount[i] = 0;
}
cellSqrDistanceToCam[i] = Vector3.SqrMagnitude(cellWorldBounds[i].center - camWorldPos);
}
for (int i = 0; i < visibleCells.Count; ++i)
{
int cellIndex = visibleCells[i];
if (cellsData[cellIndex].instancedBatches == null)
{
cellToProcess.Add(cellIndex);
if (cellToProcess.Count == GRuntimeSettings.Instance.renderingDefault.grassCellToProcessPerFrame &&
!willIgnoreCellLimit)
{
break;
}
}
}
}
private void CalculateAndCacheTransforms()
{
if (cellToProcess.Count == 0)
return;
bool willSkipFrame = false;
try
{
NativeArray<float> prototypePivotOffset = new NativeArray<float>(prototypes.Count, Allocator.TempJob);
NativeArray<Vector3> prototypeSize = new NativeArray<Vector3>(prototypes.Count, Allocator.TempJob);
for (int i = 0; i < prototypes.Count; ++i)
{
prototypePivotOffset[i] = prototypes[i].pivotOffset;
prototypeSize[i] = prototypes[i].size;
}
JobHandle[] handles = new JobHandle[cellToProcess.Count];
for (int i = 0; i < cellToProcess.Count; ++i)
{
int cellIndex = cellToProcess[i];
GGrassPatch cell = cells[cellIndex];
GGrassPatchNativeData nativeData = new GGrassPatchNativeData(cell.Instances);
cellsNativeData[cellIndex] = nativeData;
GCalculateGrassTransformJob job = new GCalculateGrassTransformJob()
{
instances = nativeData.instances,
transforms = nativeData.trs,
prototypePivotOffset = prototypePivotOffset,
prototypeSize = prototypeSize,
terrainSize = terrainSize,
terrainPos = terrainPosition
};
//handles[i] = job.Schedule();
handles[i] = job.Schedule(nativeData.instances.Length, 100);
}
GJobUtilities.CompleteAll(handles);
prototypePivotOffset.Dispose();
prototypeSize.Dispose();
}
catch (System.InvalidOperationException)
{
willSkipFrame = true;
}
catch (System.Exception e)
{
Debug.LogException(e);
}
if (willSkipFrame)
{
throw new GSkipFrameException();
}
}
private void BuildBatches()
{
JobHandle[] handles = new JobHandle[cellToProcess.Count];
for (int i = 0; i < cellToProcess.Count; ++i)
{
int cellIndex = cellToProcess[i];
GGrassPatchNativeData nativeData = cellsNativeData[cellIndex];
//GGrassPatch cell = cellToProcess[i];
GBuildInstancedBatchJob job = new GBuildInstancedBatchJob()
{
instances = nativeData.instances,
batchMetadata = nativeData.metadata,
maxLength = BATCH_MAX_INSTANCE_COUNT
};
handles[i] = job.Schedule();
}
GJobUtilities.CompleteAll(handles);
for (int i = 0; i < cellToProcess.Count; ++i)
{
int cellIndex = cellToProcess[i];
CreateInstancedBatches(cellIndex);
}
}
private void CreateInstancedBatches(int cellIndex)
{
GGrassPatchNativeData nativeData = cellsNativeData[cellIndex];
NativeArray<Matrix4x4> trs = nativeData.trs;
NativeArray<Vector3Int> metadata = nativeData.metadata;
GInstancedBatch[] batches = new GInstancedBatch[metadata[0].z];
for (int i = 0; i < batches.Length; ++i)
{
int prototypeIndex = metadata[i + 1].x;
int startIndex = metadata[i + 1].y;
int length = metadata[i + 1].z;
int indexLimit = startIndex + length;
GInstancedBatch batch = new GInstancedBatch(BATCH_MAX_INSTANCE_COUNT);
batch.prototypeIndex = prototypeIndex;
for (int j = startIndex; j < indexLimit; ++j)
{
batch.AddTransform(trs[j]);
}
batches[i] = batch;
}
cellsData[cellIndex].instancedBatches = batches;
}
private void ConfigureMaterial()
{
for (int i = 0; i < prototypes.Count; ++i)
{
if (propertyBlocks[i] == null)
propertyBlocks[i] = new MaterialPropertyBlock();
propertyBlocks[i].Clear();
materialConfigurator.Configure(terrain, i, propertyBlocks[i]);
if (ConfiguringMaterial != null)
{
ConfiguringMaterial.Invoke(terrain, i, propertyBlocks[i]);
}
}
}
private void Submit()
{
for (int i = 0; i < visibleCells.Count; ++i)
{
int cellIndex = visibleCells[i];
Submit(cellIndex);
}
}
private void Submit(int cellIndex)
{
GGrassPatchData data = cellsData[cellIndex];
if (data == null)
return;
GInstancedBatch[] batches = data.instancedBatches;
if (batches == null)
return;
for (int i = 0; i < batches.Length; ++i)
{
GInstancedBatch b = batches[i];
if (b.prototypeIndex >= prototypes.Count)
continue;
GGrassPrototype proto = prototypes[b.prototypeIndex];
MaterialPropertyBlock propertyBlock = propertyBlocks[b.prototypeIndex];
Mesh baseMesh = baseMeshes[b.prototypeIndex];
if (baseMesh == null)
continue;
Material material = materials[b.prototypeIndex];
if (material == null || !material.enableInstancing)
continue;
Graphics.DrawMeshInstanced(
baseMesh,
0,
material,
b.transforms,
b.instanceCount,
propertyBlock,
proto.shadowCastingMode,
proto.receiveShadow,
proto.layer,
camera,
LightProbeUsage.BlendProbes);
}
}
private void CleanUpFrame()
{
if (cellsNativeData != null)
{
for (int i = 0; i < cellsNativeData.Length; ++i)
{
if (cellsNativeData[i] != null)
{
cellsNativeData[i].Dispose();
cellsNativeData[i] = null;
}
}
}
UnloadInactiveCells();
}
private void UnloadInactiveCells()
{
float sqrRenderDistance = grassDistance * grassDistance;
for (int i = 0; i < cells.Length; ++i)
{
if (cellCulledFrameCount[i] >= FRAME_COUNT_TO_UNLOAD_CELL &&
cellSqrDistanceToCam[i] >= sqrRenderDistance)
{
ClearCellData(i);
}
}
}
internal void ClearCellData(int index)
{
if (cellsData != null)
{
cellsData[index] = null;
}
}
private void CalculateCellWorldBounds(int index)
{
if (cellWorldBounds != null)
{
cellWorldBounds[index] = new Bounds()
{
center = normalizedToWorldMatrix.MultiplyPoint(cells[index].bounds.center),
size = normalizedToWorldMatrix.MultiplyVector(cells[index].bounds.size)
};
}
}
internal void ClearAllCells()
{
cells = null;
cellsData = null;
cellWorldBounds = null;
RecalculateCellBounds();
}
internal void CleanUp()
{
ClearAllCells();
}
internal void OnCellChanged(int index)
{
ClearCellData(index);
CalculateCellWorldBounds(index);
}
internal void OnPatchGridSizeChanged()
{
ClearAllCells();
}
internal void OnPrototypeGroupChanged()
{
ClearAllCells();
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8c1c78d363aaf404d97038e7df1c7572
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,33 @@
#if GRIFFIN
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.Rendering;
namespace Pinwheel.Griffin.Rendering
{
internal class GInstancedBatch
{
internal Matrix4x4[] transforms;
internal int instanceCount;
internal int prototypeIndex;
public GInstancedBatch(int maxInstanceCount)
{
transforms = new Matrix4x4[maxInstanceCount];
instanceCount = 0;
}
public void AddTransform(Matrix4x4 m)
{
transforms[instanceCount] = m;
instanceCount += 1;
}
public void ClearTransforms()
{
instanceCount = 0;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6f83b55574e3ce1469bf74b72430553c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,20 @@
#if GRIFFIN
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Unity.Jobs;
namespace Pinwheel.Griffin.Rendering
{
public static class GJobUtilities
{
public static void CompleteAll(JobHandle[] handles)
{
for (int i = 0; i < handles.Length; ++i)
{
handles[i].Complete();
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 21642d52c4f639d4dbc61de384710337
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,19 @@
#if GRIFFIN
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
namespace Pinwheel.Griffin.Rendering
{
public class GRenderingVisualization
{
public Vector3 boxMin;
public Vector3 boxMax;
public Color terrainBoundsColor;
public Matrix4x4[] trs;
public byte[] cullResult;
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 75e7203b548498b479e1dc40802db128
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,52 @@
#if GRIFFIN
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Pinwheel.Griffin;
namespace Pinwheel.Griffin.Rendering
{
public class GSimpleGrassMaterialConfigurator : IGGrassMaterialConfigurator
{
private static readonly int COLOR = Shader.PropertyToID("_Color");
private static readonly int MAIN_TEX = Shader.PropertyToID("_MainTex");
private static readonly int NOISE_TEX = Shader.PropertyToID("_NoiseTex");
private static readonly int WIND = Shader.PropertyToID("_Wind");
private static readonly int BEND_FACTOR = Shader.PropertyToID("_BendFactor");
private static readonly int VECTOR_FIELD = Shader.PropertyToID("_VectorField");
private static readonly int WORLD_TO_NORMALIZED = Shader.PropertyToID("_WorldToNormalized");
private static readonly int FADE_MIN_DISTANCE = Shader.PropertyToID("_FadeMinDistance");
private static readonly int FADE_MAX_DISTANCE = Shader.PropertyToID("_FadeMaxDistance");
public void Configure(GStylizedTerrain terrain, int prototypeIndex, MaterialPropertyBlock propertyBlock)
{
GGrassPrototype proto = terrain.TerrainData.Foliage.Grasses.Prototypes[prototypeIndex];
propertyBlock.SetTexture(NOISE_TEX, GRuntimeSettings.Instance.foliageRendering.windNoiseTexture);
IEnumerator<GWindZone> windZone = GWindZone.ActiveWindZones.GetEnumerator();
if (windZone.MoveNext())
{
GWindZone w = windZone.Current;
propertyBlock.SetVector(WIND, w.GetWindParams());
}
propertyBlock.SetColor(COLOR, proto.Color);
if (proto.Shape != GGrassShape.DetailObject && proto.Texture != null)
propertyBlock.SetTexture(MAIN_TEX, proto.Texture);
propertyBlock.SetFloat(BEND_FACTOR, proto.BendFactor);
if (terrain.TerrainData.Foliage.EnableInteractiveGrass)
{
propertyBlock.SetTexture(VECTOR_FIELD, terrain.GetGrassVectorFieldRenderTexture());
propertyBlock.SetMatrix(WORLD_TO_NORMALIZED, terrain.GetWorldToNormalizedMatrix());
}
float fadeMaxDistance = terrain.TerrainData.Rendering.GrassDistance;
float fadeMinDistance = Mathf.Clamp(terrain.TerrainData.Rendering.GrassFadeStart, 0f, 0.99f) * fadeMaxDistance;
propertyBlock.SetFloat(FADE_MIN_DISTANCE, fadeMinDistance);
propertyBlock.SetFloat(FADE_MAX_DISTANCE, fadeMaxDistance);
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 47710649461da2949a2bfb30d05e70a9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,13 @@
#if GRIFFIN
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
namespace Pinwheel.Griffin.Rendering
{
public class GSkipFrameException : Exception
{
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 222c87f944ae70540a26b7ae6cb0e20d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,35 @@
#if GRIFFIN
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using Unity.Collections;
namespace Pinwheel.Griffin.Rendering
{
internal class GTreeNativeData : IDisposable
{
internal NativeArray<GTreeInstance> instances;
internal NativeArray<int> prototypeIndices;
internal NativeArray<Matrix4x4> trs;
internal NativeArray<byte> cullResults;
public GTreeNativeData(List<GTreeInstance> trees)
{
instances = new NativeArray<GTreeInstance>(trees.ToArray(), Allocator.Persistent);
prototypeIndices = new NativeArray<int>(trees.Count, Allocator.Persistent);
GUtilities.Fill(prototypeIndices, -1);
trs = new NativeArray<Matrix4x4>(trees.Count, Allocator.Persistent);
cullResults = new NativeArray<byte>(trees.Count, Allocator.Persistent);
}
public void Dispose()
{
GNativeArrayUtilities.Dispose(instances);
GNativeArrayUtilities.Dispose(prototypeIndices);
GNativeArrayUtilities.Dispose(trs);
GNativeArrayUtilities.Dispose(cullResults);
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 044a58944776b0a42b889fdca06ca71d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,629 @@
#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<GStylizedTerrain, GRenderingVisualization> 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<GTreePrototype> 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<Plane> frustumPlanes;
private NativeArray<float> prototypePivotOffset;
private NativeArray<Quaternion> prototypeBaseRotation;
private NativeArray<Vector3> prototypeBaseScale;
private NativeArray<BoundingSphere> prototypeBounds;
private NativeArray<bool> 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<GTreePrototype>();
}
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;
}
}
/// <summary>
///
/// </summary>
/// <returns>True if the terrain is culled</returns>
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<Plane>(frustum, Allocator.TempJob);
prototypePivotOffset = new NativeArray<float>(prototypes.Count, Allocator.TempJob);
prototypeBaseRotation = new NativeArray<Quaternion>(prototypes.Count, Allocator.TempJob);
prototypeBaseScale = new NativeArray<Vector3>(prototypes.Count, Allocator.TempJob);
prototypeBounds = new NativeArray<BoundingSphere>(prototypes.Count, Allocator.TempJob);
prototypeWillDoFrustumTest = new NativeArray<bool>(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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8e103efa80d697e4bb7dadbb56db1bb2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,13 @@
#if GRIFFIN
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace Pinwheel.Griffin.Rendering
{
public interface IGGrassMaterialConfigurator
{
void Configure(GStylizedTerrain terrain, int prototypeIndex, MaterialPropertyBlock propertyBlock);
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cb9c323a4aad2d4469e14d941215b1ce
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: