init
This commit is contained in:
commit
f5c1616018
679 changed files with 188502 additions and 0 deletions
284
Assets/Pixel3D/Scripts/GrassInstancer.cs
Normal file
284
Assets/Pixel3D/Scripts/GrassInstancer.cs
Normal file
|
@ -0,0 +1,284 @@
|
|||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using static System.Runtime.InteropServices.Marshal;
|
||||
|
||||
[RequireComponent(typeof(MeshGenerator))]
|
||||
public class GrassInstancer : MonoBehaviour
|
||||
{
|
||||
[SerializeField] LayerMask _grassLayer;
|
||||
[SerializeField] Mesh _grassMesh;
|
||||
[SerializeField] Material _material;
|
||||
[SerializeField] float _density = 10;
|
||||
[SerializeField] int _seed = 42;
|
||||
|
||||
MeshGenerator _meshGenerator;
|
||||
RenderParams _renderParams;
|
||||
|
||||
GraphicsBuffer.IndirectDrawIndexedArgs[] commandData;
|
||||
|
||||
struct GrassData
|
||||
{
|
||||
public Vector3 position;
|
||||
public Vector2 colorTexUV;
|
||||
}
|
||||
|
||||
struct GrassChunk
|
||||
{
|
||||
public Vector2Int sampleCount;
|
||||
public GraphicsBuffer commandBuffer;
|
||||
public ComputeBuffer grassBuffer;
|
||||
public Bounds bounds;
|
||||
public Material material;
|
||||
public Vector2 uvMin;
|
||||
public Vector2 uvMax;
|
||||
}
|
||||
|
||||
GrassChunk[] grassChunks;
|
||||
[SerializeField] int chunkCount = 1;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
Setup();
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
FreeChunks();
|
||||
}
|
||||
|
||||
void Setup()
|
||||
{
|
||||
_meshGenerator = GetComponent<MeshGenerator>();
|
||||
if (_meshGenerator == null)
|
||||
Debug.LogError("MeshGenerator is null");
|
||||
|
||||
var meshRenderer = GetComponent<MeshRenderer>();
|
||||
if (meshRenderer == null)
|
||||
Debug.LogError("MeshRenderer is null");
|
||||
|
||||
if (_material == null)
|
||||
Debug.LogError("Material is null");
|
||||
|
||||
if (_grassMesh == null)
|
||||
Debug.LogError("Grass mesh is null");
|
||||
}
|
||||
|
||||
// Initialize the chunks
|
||||
public void InitChunks()
|
||||
{
|
||||
// Ensure the chunks are freed
|
||||
FreeChunks();
|
||||
|
||||
// Create the chunks
|
||||
grassChunks = new GrassChunk[chunkCount * chunkCount];
|
||||
for (int y = 0; y < chunkCount; y++)
|
||||
{
|
||||
for (int x = 0; x < chunkCount; x++)
|
||||
{
|
||||
var chunkIndex = y * chunkCount + x;
|
||||
var bounds = GetChunkBounds(x, y);
|
||||
var uvMin = new Vector2(x, y) / chunkCount;
|
||||
var uvMax = uvMin + Vector2.one / chunkCount;
|
||||
var size = new Vector2(bounds.size.x, bounds.size.z);
|
||||
var sampleCount = new Vector2Int((int)(_density * size.x), (int)(_density * size.y));
|
||||
grassChunks[chunkIndex] = new GrassChunk
|
||||
{
|
||||
commandBuffer = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments, 1, GraphicsBuffer.IndirectDrawIndexedArgs.size),
|
||||
grassBuffer = new ComputeBuffer(sampleCount.x * sampleCount.y, SizeOf<GrassData>()),
|
||||
bounds = bounds,
|
||||
sampleCount = sampleCount,
|
||||
material = _material,
|
||||
uvMin = uvMin,
|
||||
uvMax = uvMax,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Setup a chunk
|
||||
void SetupChunk(GrassChunk chunk)
|
||||
{
|
||||
if (chunk.sampleCount.x <= 0 || chunk.sampleCount.y <= 0)
|
||||
return;
|
||||
|
||||
// Initialize the matrices
|
||||
var grassData = new GrassData[chunk.sampleCount.x * chunk.sampleCount.y];
|
||||
var meshSize = new Vector2(_meshGenerator.size.x, _meshGenerator.size.z);
|
||||
|
||||
// Loop through each grass
|
||||
var index = 0;
|
||||
for (int y = 0; y < chunk.sampleCount.y; y++)
|
||||
{
|
||||
for (int x = 0; x < chunk.sampleCount.x; x++)
|
||||
{
|
||||
// Calculate the position and uvs with random offset
|
||||
var randomOffset = Random.insideUnitCircle / 2;
|
||||
var chunkUV = (new Vector2(x, y) + Vector2.one / 2 + randomOffset) / chunk.sampleCount;
|
||||
var worldUV = new Vector2(
|
||||
Remap(chunkUV.x, 0, 1, chunk.uvMin.x, chunk.uvMax.x),
|
||||
Remap(chunkUV.y, 0, 1, chunk.uvMin.y, chunk.uvMax.y)
|
||||
);
|
||||
var position2D = (worldUV - Vector2.one / 2) * meshSize;
|
||||
var height = _meshGenerator.GetMeshHeight(worldUV);
|
||||
var position = transform.TransformPoint(new Vector3(position2D.x, height, position2D.y));
|
||||
|
||||
// Set the grass data
|
||||
grassData[index] = new GrassData
|
||||
{
|
||||
position = position,
|
||||
colorTexUV = worldUV
|
||||
};
|
||||
|
||||
// Increment the index
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
chunk.grassBuffer.SetData(grassData);
|
||||
commandData = new GraphicsBuffer.IndirectDrawIndexedArgs[1];
|
||||
commandData[0] = new GraphicsBuffer.IndirectDrawIndexedArgs
|
||||
{
|
||||
indexCountPerInstance = _grassMesh.GetIndexCount(0),
|
||||
instanceCount = (uint)(chunk.sampleCount.x * chunk.sampleCount.y),
|
||||
startIndex = _grassMesh.GetIndexStart(0),
|
||||
baseVertexIndex = _grassMesh.GetBaseVertex(0),
|
||||
startInstance = 0,
|
||||
};
|
||||
chunk.commandBuffer.SetData(commandData);
|
||||
}
|
||||
|
||||
// Free the chunks
|
||||
void FreeChunks()
|
||||
{
|
||||
if (grassChunks == null)
|
||||
return;
|
||||
|
||||
foreach (var chunk in grassChunks)
|
||||
{
|
||||
chunk.commandBuffer?.Release();
|
||||
chunk.grassBuffer?.Release();
|
||||
}
|
||||
grassChunks = null;
|
||||
}
|
||||
|
||||
void OnDrawGizmos()
|
||||
{
|
||||
if (grassChunks == null)
|
||||
return;
|
||||
|
||||
foreach (var chunk in grassChunks)
|
||||
{
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawWireCube(chunk.bounds.center, chunk.bounds.size);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the bounds of a chunk
|
||||
Bounds GetChunkBounds(int x, int y)
|
||||
{
|
||||
var size = new Vector2(_meshGenerator.size.x, _meshGenerator.size.z);
|
||||
var chunkSize = size / chunkCount;
|
||||
var chunkPosition = new Vector2(x, y) * chunkSize - size / 2;
|
||||
var chunkBounds = new Bounds();
|
||||
var min = new Vector3(chunkPosition.x, 0, chunkPosition.y);
|
||||
var max = new Vector3(chunkPosition.x + chunkSize.x, _meshGenerator.size.y, chunkPosition.y + chunkSize.y);
|
||||
chunkBounds.SetMinMax(
|
||||
transform.TransformPoint(min),
|
||||
transform.TransformPoint(max)
|
||||
);
|
||||
return chunkBounds;
|
||||
}
|
||||
|
||||
// Remap a value from one range to another
|
||||
float Remap(float value, float low1, float high1, float low2, float high2)
|
||||
{
|
||||
return low2 + (value - low1) * (high2 - low2) / (high1 - low1);
|
||||
}
|
||||
|
||||
public void Generate()
|
||||
{
|
||||
// Set the seed
|
||||
Random.InitState(_seed);
|
||||
|
||||
// Set the constant render params
|
||||
var block = new MaterialPropertyBlock();
|
||||
block.SetTexture("_ColorTex", _meshGenerator.colorTexture);
|
||||
block.SetFloat("_MeshHeight", _grassMesh.bounds.size.y);
|
||||
_renderParams = new RenderParams(_material)
|
||||
{
|
||||
layer = (int)Mathf.Log(_grassLayer.value, 2),
|
||||
worldBounds = new Bounds(Vector3.zero, 10000 * Vector3.one),
|
||||
matProps = block,
|
||||
receiveShadows = true,
|
||||
};
|
||||
|
||||
// Generate the chunks
|
||||
InitChunks();
|
||||
foreach (var chunk in grassChunks)
|
||||
SetupChunk(chunk);
|
||||
}
|
||||
|
||||
// Update the rotation of the grass
|
||||
void UpdateRotation()
|
||||
{
|
||||
var target = Camera.main.transform.position;
|
||||
var rotation = Quaternion.LookRotation(transform.position - target, Vector3.up);
|
||||
var quaternion = new Vector4(rotation.x, rotation.y, rotation.z, rotation.w);
|
||||
_renderParams.matProps.SetVector("_Rotation", quaternion);
|
||||
}
|
||||
|
||||
void RenderChunks()
|
||||
{
|
||||
// Update the rotation
|
||||
UpdateRotation();
|
||||
|
||||
// Render the chunks
|
||||
foreach (var chunk in grassChunks)
|
||||
{
|
||||
// Frustum culling check for the chunk
|
||||
var frustumPlanes = GeometryUtility.CalculateFrustumPlanes(Camera.main);
|
||||
if (!GeometryUtility.TestPlanesAABB(frustumPlanes, chunk.bounds))
|
||||
continue;
|
||||
|
||||
// Render the chunk
|
||||
_renderParams.matProps.SetBuffer("_GrassData", chunk.grassBuffer);
|
||||
Graphics.RenderMeshIndirect(_renderParams, _grassMesh, chunk.commandBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
// Validation checks
|
||||
if (_meshGenerator == null)
|
||||
Setup();
|
||||
|
||||
if (!_meshGenerator.IsMeshGenerated())
|
||||
_meshGenerator.GenerateTerrainMesh();
|
||||
|
||||
if (transform.lossyScale != Vector3.one)
|
||||
Debug.LogWarning("GrassInstancer does not support scaling");
|
||||
|
||||
if (grassChunks == null || grassChunks.Length == 0)
|
||||
Generate();
|
||||
|
||||
// Render the chunks
|
||||
RenderChunks();
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// Custom editor which adds a button to generate the terrain
|
||||
[CustomEditor(typeof(GrassInstancer))]
|
||||
public class GrassInstancerEditor : Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
var script = (GrassInstancer)target;
|
||||
DrawDefaultInspector();
|
||||
GUILayout.Space(10);
|
||||
// GUILayout.Label("Sample Count: " + script.GetSampleCount());
|
||||
if (GUILayout.Button("Generate Grass"))
|
||||
script.Generate();
|
||||
}
|
||||
}
|
||||
#endif
|
Loading…
Add table
Add a link
Reference in a new issue