Files
CartoonRPG/Assets/StixGames/GrassShader/Scripts/GrassRenderer.cs
Caleb Sandford deQuincey 02c4b5ab66 Adding in new packages
2025-06-05 22:59:18 +01:00

297 lines
9.9 KiB
C#

using System;
using StixGames.NatureCore;
using StixGames.NatureCore.Utility;
using UnityEngine;
using UnityEngine.Rendering;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace StixGames.GrassShader
{
[ExecuteInEditMode]
[RequireComponent(typeof(NatureMeshFilter))]
[AddComponentMenu("Stix Games/General/Grass Renderer", 1)]
public class GrassRenderer : MonoBehaviour
{
public Material Material;
[Tooltip("If the mesh has multiple materials, this can be used to select the target material index.")]
public int MaterialIndex = 0;
[LayerField] public int Layer;
public ShadowCastingMode ShadowCastingMode = ShadowCastingMode.On;
public bool ReceiveShadows = true;
[Space] public bool AutoSetFloorColor = false;
[Tooltip("If true, the renderer will not render meshes that are not visible by the main camera.")]
public bool UseMainCameraFrustumCulling = true;
private NatureMeshFilter _natureMeshFilter;
private MaterialPropertyBlock propertyBlock = null;
private Shader currentGrassShader = null;
private Plane[] planes = new Plane[6];
private static bool hasInitializedGlobalVariables;
private static readonly int GrassTime = Shader.PropertyToID("_GrassTime");
private static readonly int GlobalTargetDensityMultiplier =
Shader.PropertyToID("_GlobalTargetDensityMultiplier");
public NatureMeshFilter NatureMeshFilter
{
get
{
return _natureMeshFilter != null
? _natureMeshFilter
: (_natureMeshFilter = GetComponent<NatureMeshFilter>());
}
}
private void Awake()
{
SetupGlobalVariables();
}
private void SetupGlobalVariables()
{
if (hasInitializedGlobalVariables)
{
return;
}
hasInitializedGlobalVariables = true;
if (Shader.GetGlobalFloat(GlobalTargetDensityMultiplier) == 0.0f)
{
Shader.SetGlobalFloat(GlobalTargetDensityMultiplier, 1);
}
}
private void Start()
{
if (AutoSetFloorColor)
{
var renderer = GetComponent<Renderer>();
var terrain = GetComponent<Terrain>();
UpdateFloorColor(renderer, terrain);
}
}
private void OnEnable()
{
#if UNITY_EDITOR
#if UNITY_2019_2_OR_NEWER
SceneView.duringSceneGui -= EditorGrassTimeUpdate;
SceneView.duringSceneGui += EditorGrassTimeUpdate;
#else
SceneView.onSceneGUIDelegate -= EditorGrassTimeUpdate;
SceneView.onSceneGUIDelegate += EditorGrassTimeUpdate;
#endif
#endif
}
private void OnDisable()
{
#if UNITY_EDITOR
#if UNITY_2019_2_OR_NEWER
SceneView.duringSceneGui -= EditorGrassTimeUpdate;
#else
SceneView.onSceneGUIDelegate -= EditorGrassTimeUpdate;
#endif
#endif
}
#if UNITY_EDITOR
public void EditorGrassTimeUpdate(SceneView view)
{
UpdateGrassTime();
SceneView.RepaintAll();
#if UNITY_2019_2_OR_NEWER
EditorApplication.QueuePlayerLoopUpdate();
#endif
}
#endif
/// <summary>
/// Set a material property block that will be used for rendering.
/// </summary>
/// <param name="propertyBlock"></param>
public void SetPropertyBlock(MaterialPropertyBlock propertyBlock)
{
this.propertyBlock = propertyBlock;
}
private void LateUpdate()
{
if (Material == null)
{
return;
}
if (currentGrassShader != Material.shader)
{
if (!GrassUtility.IsGrassMaterial(Material))
{
throw new InvalidOperationException("GrassMaterial does not use the DX11 Grass Shader");
}
currentGrassShader = Material.shader;
}
#if UNITY_EDITOR
SetupGlobalVariables();
#endif
var renderer = GetComponent<Renderer>();
var terrain = GetComponent<Terrain>();
if (Application.isEditor && !Application.isPlaying && AutoSetFloorColor)
{
UpdateFloorColor(renderer, terrain);
}
if (Material.shader.isSupported)
{
UpdateGrassTime();
var maxGrassHeight = GrassUtility.GetMaxGrassHeight(Material);
Vector3 localBoundingBoxExpansion;
if (renderer != null)
{
// The mesh (and bounding box) will be scaled accoring to the local scale
localBoundingBoxExpansion = new Vector3(maxGrassHeight / transform.localScale.x,
maxGrassHeight / transform.localScale.y, maxGrassHeight / transform.localScale.z);
}
else
{
// Terrain is never scaled
localBoundingBoxExpansion = new Vector3(maxGrassHeight, maxGrassHeight, maxGrassHeight);
}
#if UNITY_EDITOR
var mainCamera = Application.isPlaying ? Camera.main : SceneView.lastActiveSceneView?.camera;
#else
var mainCamera = Camera.main;
#endif
// Calculate a new frustum that
if (UseMainCameraFrustumCulling && mainCamera != null)
{
var farDistance = Mathf.Min(mainCamera.farClipPlane,
GrassUtility.GetGrassFadeEnd(Material) + maxGrassHeight);
var modifiedProjectionMatrix =
Matrix4x4.Perspective(mainCamera.fieldOfView, mainCamera.aspect, mainCamera.nearClipPlane,
farDistance);
var modifiedVP = modifiedProjectionMatrix * mainCamera.worldToCameraMatrix;
GeometryUtility.CalculateFrustumPlanes(modifiedVP, planes);
}
var meshes = NatureMeshFilter.GetMeshes();
foreach (var mesh in meshes)
{
// Update bounds with grass height
var original = mesh.bounds;
var modified = original;
modified.Expand(localBoundingBoxExpansion);
mesh.bounds = modified;
try
{
var localToWorldMatrix = renderer != null
? transform.localToWorldMatrix
: Matrix4x4.Translate(transform.position);
if (UseMainCameraFrustumCulling && mainCamera != null)
{
var meshWorldBounds = TransformBounds(localToWorldMatrix, modified);
// Additional frustum culling to only render meshes where the grass could be visible
if (!GeometryUtility.TestPlanesAABB(planes, meshWorldBounds))
{
continue;
}
}
// Draw mesh
Graphics.DrawMesh(mesh, localToWorldMatrix, Material, Layer, null, MaterialIndex, propertyBlock,
ShadowCastingMode, ReceiveShadows);
}
finally
{
// Restore bounds
mesh.bounds = original;
}
}
}
}
/// <summary>
/// Transform a bounding box with a matrix. The new bounds completely encapsulate the new bounding box.
/// </summary>
/// <param name="transformationMatrix"></param>
/// <param name="mesh"></param>
/// <returns></returns>
private static Bounds TransformBounds(Matrix4x4 transformationMatrix, Bounds bounds)
{
var center = bounds.center;
var extent = bounds.extents;
var newCenter = transformationMatrix.MultiplyPoint3x4(center);
var absRotationMatrix = AbsRotationMatrix(transformationMatrix);
var newExtents = absRotationMatrix.MultiplyVector(extent);
return new Bounds(
newCenter,
newExtents * 2
);
}
private static Matrix4x4 AbsRotationMatrix(Matrix4x4 m)
{
return new Matrix4x4(
new Vector4(Mathf.Abs(m.m00), Mathf.Abs(m.m01), Mathf.Abs(m.m02), 0),
new Vector4(Mathf.Abs(m.m10), Mathf.Abs(m.m11), Mathf.Abs(m.m12), 0),
new Vector4(Mathf.Abs(m.m20), Mathf.Abs(m.m21), Mathf.Abs(m.m22), 0),
new Vector4(0, 0, 0, 1)
);
}
private static void UpdateGrassTime()
{
Shader.SetGlobalVector(GrassTime,
new Vector4(Time.time / 20, Time.time, Time.time * 2, Time.time * 3));
}
private void UpdateFloorColor(Renderer renderer, Terrain terrain)
{
if (renderer != null)
{
var floorMaterial = renderer.sharedMaterial;
var baseColor = floorMaterial.color;
var colorTexture = floorMaterial.mainTexture;
var offset = floorMaterial.mainTextureOffset;
var scale = floorMaterial.mainTextureScale;
GrassUtility.SetFloorColor(Material, baseColor);
GrassUtility.SetFloorColorTexture(Material, colorTexture, offset, scale);
return;
}
if (terrain != null)
{
//TODO: Add terrain support for this
Debug.LogWarning("Auto setting floor color isn't supported for terrain yet.");
}
}
}
}