297 lines
9.9 KiB
C#
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.");
|
|
}
|
|
}
|
|
}
|
|
} |