grass-rendering-prototype/Assets/Scripts/GrassField.cs

164 lines
6.6 KiB
C#

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using UnityEngine;
using UnityEngine.Serialization;
using Random = UnityEngine.Random;
public class GrassField : MonoBehaviour {
public int size = 100;
public int numChunks = 5;
public GrassChunk chunkPrefab;
[Header("LOD 0")]
public Material grassMaterialLOD0;
public int bladesPerMeterLOD0 = 100;
public bool enableShadowsLOD0 = true;
[Header("LOD 1")]
public Material grassMaterialLOD1;
public int bladesPerMeterLOD1 = 50;
public bool enableShadowsLOD1 = true;
[Header("LOD 2")]
public Material grassMaterialLOD2;
public int bladesPerMeterLOD2 = 10;
public bool enableShadowsLOD2 = false;
private struct BladeData {
public Vector3 rootPosition;
public Vector3 tipOffset;
}
// ReSharper disable Unity.PerformanceAnalysis
public void GenerateGrassField() {
Debug.Log("Generating grass field... ");
var bladesLOD0 = GenerateBladesRandom(size * size * bladesPerMeterLOD0);
var bladesLOD1 = GenerateBladesRandom(size * size * bladesPerMeterLOD1);
var bladesLOD2 = GenerateBladesRandom(size * size * bladesPerMeterLOD2);
CreateChunks(bladesLOD0, bladesLOD1, bladesLOD2);
Debug.Log("Generating grass field done");
}
private BladeData[] GenerateBladesRandom(int num) {
var blades = new BladeData[num];
for (int i = 0; i < num; i++) {
blades[i].rootPosition = new Vector3(
Random.Range(0f, size), Random.Range(-0.25f, 0f), Random.Range(0f, size)
);
blades[i].tipOffset = new Vector3(Random.Range(-.8f, .8f), Random.Range(0.7f, 1f), Random.Range(-.8f, .8f));
}
return blades;
}
private BladeData[] GenerateBladesEven() {
var blades = new BladeData[size * size * 100];
for (int x = 0; x < size * 10; x++)
for (int y = 0; y < size * 10; y++) {
var i = x + y * size * 10;
blades[i].rootPosition = new Vector3(x/10.0f, 0.0f, y/10.0f);
blades[i].tipOffset = new Vector3(0.01f, 1.0f, 0.01f);
}
return blades;
}
private void CreateChunks(BladeData[] bladesLOD0, BladeData[] bladesLOD1, BladeData[] bladesLOD2) {
GameObject chunks;
// If a child called "Chunks" exists, destroy it -> children are also destroyed
var chunksTransform = gameObject.transform.Find("Chunks");
if (chunksTransform != null) {
// Destroy(obj) does not work in edit mode
DestroyImmediate(chunksTransform.gameObject);
}
// create GameObject "Chunks" as a container for all GrassChunk objects
chunks = new GameObject("Chunks");
chunks.transform.SetParent(this.transform, false);
var chunkSize = ((float)size) / numChunks;
// bounds of the chunk (relative to chunk) - do not consider y position
var chunkBounds = new Bounds(
new Vector3(0.5f * chunkSize, 0.0f, 0.5f * chunkSize),
new Vector3(chunkSize, 10000f, chunkSize)
);
for (int x = 0; x < numChunks; x++)
for (int y = 0; y < numChunks; y++) {
var chunkPos = new Vector3(x, 0.0f, y) * chunkSize;
var meshLOD0 = GenerateChunkMesh(chunkPos, chunkBounds, bladesLOD0, "grass_chunk_" + x + "_" + y + "_lod0");
var meshLOD1 = GenerateChunkMesh(chunkPos, chunkBounds, bladesLOD1, "grass_chunk_" + x + "_" + y + "_lod1");
var meshLOD2 = GenerateChunkMesh(chunkPos, chunkBounds, bladesLOD2, "grass_chunk_" + x + "_" + y + "_lod2");
var chunk = Instantiate(chunkPrefab, chunks.transform);
chunk.name = "Chunk [" + x + ", " + y +"]";
chunk.transform.localPosition = chunkPos;
chunk.MaterialLOD0 = grassMaterialLOD0;
chunk.MaterialLOD1 = grassMaterialLOD1;
chunk.MaterialLOD2 = grassMaterialLOD2;
chunk.MeshLOD0 = meshLOD0;
chunk.MeshLOD1 = meshLOD1;
chunk.MeshLOD2 = meshLOD2;
chunk.EnableShadowsLOD0 = enableShadowsLOD0;
chunk.EnableShadowsLOD1 = enableShadowsLOD1;
chunk.EnableShadowsLOD2 = enableShadowsLOD2;
}
}
private Mesh GenerateChunkMesh(Vector3 chunkPos, Bounds chunkBounds, BladeData[] blades, string meshName) {
List<Vector3> rootPositions = new(); // vertices
List<Vector3> tipOffsets = new(); // uv0
List<int> indices = new();
foreach (var blade in blades) {
// position relative to chunk
var localRootPos = blade.rootPosition - chunkPos;
if (chunkBounds.Contains(localRootPos)) {
rootPositions.Add(localRootPos);
tipOffsets.Add(blade.tipOffset);
indices.Add(rootPositions.Count - 1);
}
}
var mesh = new Mesh();
mesh.name = meshName;
if (rootPositions.Count > 65536) {
// 16 bit (default) supports 65536 vertices, 32 bit supports 4 billion
mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
Debug.LogWarning(meshName + " has more than 65536 grass blades. Mesh.indexFormat set to UInt32.");
}
mesh.SetVertices(rootPositions);
mesh.SetUVs(0, tipOffsets); // Vector3 UVs
// AABB gets calculated automatically (considers all root positions)
mesh.SetIndices(indices, MeshTopology.Points, 0);
// update the AABB to match the actual grass (not just the root positions)
// use mesh.bounds rather than meshRenderer.localBounds,
// because the latter gets overwritten automatically
var previousBounds = mesh.bounds;
const float width = 1f; // a blade should never bend sideways farther than this
const float height = 1f; // a blade should not be higher than this
mesh.bounds = new Bounds(
previousBounds.center + new Vector3(0f, height * 0.5f, 0f),
previousBounds.size + new Vector3(2f*width, height, 2f*width)
);
return mesh;
}
private void OnDrawGizmos() {
Gizmos.color = new Color(0f, 0.2f, 1f, 1f);
var chunkSize = ((float)size) / numChunks;
var bounds = new Bounds(
new Vector3(0.5f * chunkSize, 0.0f, 0.5f * chunkSize),
new Vector3(chunkSize, 0f, chunkSize)
);
for (int x = 0; x < numChunks; x++)
for (int y = 0; y < numChunks; y++) {
var chunkPos = new Vector3(x, 0.0f, y) * chunkSize;
Gizmos.DrawWireCube(bounds.center + chunkPos + transform.position, bounds.size);
}
}
}