Files
Fishing2/Assets/ThirdParty/LuxWater/Scripts/LuxWater_ProjectorRenderer.cs
2025-05-10 12:49:47 +08:00

372 lines
14 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.XR;
namespace LuxWater {
[RequireComponent(typeof(Camera))]
[ExecuteInEditMode]
public class LuxWater_ProjectorRenderer : MonoBehaviour {
public enum BufferResolution {
Full = 1,
Half = 2,
Quarter = 4,
Eighth = 8
};
[Space(8)]
public BufferResolution FoamBufferResolution = BufferResolution.Full;
public BufferResolution NormalBufferResolution = BufferResolution.Full;
[Space(2)]
[Header("Debug")]
[Space(4)]
public bool DebugFoamBuffer = false;
public bool DebugNormalBuffer = false;
public bool DebugStats = false;
private int drawnFoamProjectors = 0;
private int drawnNormalProjectors = 0;
private static CommandBuffer cb_Foam;
private static CommandBuffer cb_Normals;
private Camera cam;
private Transform camTransform;
private RenderTexture ProjectedFoam;
private RenderTexture ProjectedNormals;
private Texture2D defaultBump;
private Bounds tempBounds;
private int _LuxWater_FoamOverlayPID;
private int _LuxWater_NormalOverlayPID;
//private int _CameraDepthTexturePID;
private Plane[] frustumPlanes = new Plane[6];
private Material DebugMat;
private Material DebugNormalMat;
// Use this for initialization
void OnEnable () {
_LuxWater_FoamOverlayPID = Shader.PropertyToID("_LuxWater_FoamOverlay");
_LuxWater_NormalOverlayPID = Shader.PropertyToID("_LuxWater_NormalOverlay");
//_CameraDepthTexturePID = Shader.PropertyToID("_CameraDepthTexture");
cb_Foam = new CommandBuffer();
cb_Foam.name = "Lux Water: Foam Overlay Buffer";
cb_Normals = new CommandBuffer();
cb_Normals.name = "Lux Water: Normal Overlay Buffer";
cam = GetComponent<Camera>();
// Underwatermask uses AfterSkybox
cam.AddCommandBuffer(CameraEvent.BeforeSkybox, cb_Foam);
cam.AddCommandBuffer(CameraEvent.BeforeSkybox, cb_Normals);
}
void OnDisable() {
if(ProjectedFoam != null) {
DestroyImmediate(ProjectedFoam);
}
if(ProjectedNormals != null) {
DestroyImmediate(ProjectedNormals);
}
if(defaultBump != null) {
DestroyImmediate(defaultBump);
}
if(DebugMat != null) {
DestroyImmediate(DebugMat);
}
if(cb_Foam != null) {
// Checking for != null is not enough, so we check for its size as well
if(cb_Foam.sizeInBytes > 0) {
cb_Foam.Clear();
cb_Foam.Dispose();
}
}
if(cb_Normals != null) {
// Checking for != null is not enough, so we check for its size as well
if(cb_Normals.sizeInBytes > 0) {
cb_Normals.Clear();
cb_Normals.Dispose();
}
}
Shader.DisableKeyword("USINGWATERPROJECTORS");
}
// We have to use OnPreCull or OnPreRender as otherwise particlesystems will make unity crash
// void OnPreRender() {
// This is what is active in 1.08
void OnPreCull () {
#if UNITY_EDITOR
if(!Application.isPlaying) {
if(defaultBump == null) {
defaultBump = new Texture2D(1, 1, TextureFormat.RGBA32, false);
defaultBump.SetPixel(0, 0, new Color(0.5f, 0.5f, 1.0f, 0.5f).gamma);
defaultBump.Apply(false);
}
Shader.SetGlobalTexture(_LuxWater_NormalOverlayPID, defaultBump);
Shader.SetGlobalTexture(_LuxWater_FoamOverlayPID, Texture2D.blackTexture);
return;
}
#endif
cam = GetComponent<Camera>();
#if UNITY_EDITOR
if (UnityEditor.SceneView.currentDrawingSceneView != null && UnityEditor.SceneView.currentDrawingSceneView.camera == cam) {
// Shader.DisableKeyword("USINGWATERPROJECTORS");
return;
}
#endif
// Check if we have to do anything
var numFoamProjectors = LuxWater_Projector.FoamProjectors.Count;
var numNormalProjectors = LuxWater_Projector.NormalProjectors.Count;
// No registered projectors
if (numFoamProjectors + numNormalProjectors == 0) {
if(cb_Foam != null) {
cb_Foam.Clear();
}
if(cb_Normals != null) {
cb_Normals.Clear();
}
Shader.DisableKeyword("USINGWATERPROJECTORS");
return;
}
else {
Shader.EnableKeyword("USINGWATERPROJECTORS");
}
var projectionMatrix = cam.projectionMatrix;
var worldToCameraMatrix = cam.worldToCameraMatrix;
var worldToProjectionMatrix = projectionMatrix * worldToCameraMatrix;
var camPixelWidth = cam.pixelWidth;
var camPixelHeight = cam.pixelHeight;
//UnityEngine.Profiling.Profiler.BeginSample("Get Planes");
GeomUtil.CalculateFrustumPlanes(frustumPlanes, worldToProjectionMatrix);
//UnityEngine.Profiling.Profiler.EndSample();
// Foam Buffer
var rtWidth = Mathf.FloorToInt(camPixelWidth / (int)FoamBufferResolution);
var rtHeight = Mathf.FloorToInt(camPixelHeight / (int)FoamBufferResolution);
// XR support
var xrSinglePassInstanced = false;
var dim = TextureDimension.Tex2D;
if (cam.stereoEnabled && XRSettings.stereoRenderingMode == XRSettings.StereoRenderingMode.SinglePassInstanced) {
xrSinglePassInstanced = true;
dim = TextureDimension.Tex2DArray;
}
// RenderTexture.vrUsage?
// Check if buffer's rt has to be created/updated
if(!ProjectedFoam) {
ProjectedFoam = new RenderTexture(rtWidth, rtHeight, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear);
ProjectedFoam.dimension = dim;
if(xrSinglePassInstanced) {
ProjectedFoam.volumeDepth = 2;
}
//Shader.SetGlobalTexture(_LuxWater_FoamOverlayPID, ProjectedFoam);
}
else if (ProjectedFoam.width != rtWidth) {
DestroyImmediate(ProjectedFoam);
ProjectedFoam = new RenderTexture(rtWidth, rtHeight, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear);
ProjectedFoam.dimension = dim;
if(xrSinglePassInstanced) {
ProjectedFoam.volumeDepth = 2;
}
// We have to reassign the texture (prevented projectors from being updated after pause)
//Shader.SetGlobalTexture(_LuxWater_FoamOverlayPID, ProjectedFoam);
}
//GL.PushMatrix();
//GL.modelview = worldToCameraMatrix;
//GL.LoadProjectionMatrix(projectionMatrix);
// ////////////////////////////////////////////////////////////////////////
// Render Foam Buffer
RenderTargetIdentifier target;
if(xrSinglePassInstanced) {
target = new RenderTargetIdentifier(ProjectedFoam, 0, CubemapFace.Unknown, -1); // slice = -1 makes it draw into both layers!
}
else {
target = new RenderTargetIdentifier(ProjectedFoam);
}
cb_Foam.Clear();
cb_Foam.SetRenderTarget(target);
cb_Foam.ClearRenderTarget(true, true, new Color(0,0,0,0), 1.0f);
//Shader.SetGlobalTexture(_CameraDepthTexturePID, Texture2D.whiteTexture);
drawnFoamProjectors = 0;
// Adding the cb to the camera we do not need this.
// if (xrSinglePassInstanced) {
// cb_Foam.SetSinglePassStereo(SinglePassStereoMode.Instancing); // now it draws instanced bit only into the first layer
// cb_Foam.EnableShaderKeyword("STEREO_INSTANCING_ON");
// }
for(int i = 0; i < numFoamProjectors; i++) {
// Check renderer's bounds against frustum before calling DrawRenderer
var currentProjector = LuxWater_Projector.FoamProjectors[i];
tempBounds = currentProjector.m_Rend.bounds;
if (GeometryUtility.TestPlanesAABB(frustumPlanes, tempBounds)) {
cb_Foam.DrawRenderer(currentProjector.m_Rend, currentProjector.m_Mat);
drawnFoamProjectors ++;
}
}
//Graphics.ExecuteCommandBuffer(cb_Foam);
// We might have multiple Cameras (split screen) - so we have to assign the Rendertexture each time.
Shader.SetGlobalTexture(_LuxWater_FoamOverlayPID, ProjectedFoam);
// ////////////////////////////////////////////////////////////////////////
// Render Normal Buffer
rtWidth = Mathf.FloorToInt(camPixelWidth / (int)NormalBufferResolution);
rtHeight = Mathf.FloorToInt(camPixelHeight / (int)NormalBufferResolution);
// Check if buffer's rt has to be created/updated
if(!ProjectedNormals) {
ProjectedNormals = new RenderTexture(rtWidth, rtHeight, 0, RenderTextureFormat.ARGBHalf, RenderTextureReadWrite.Linear);
ProjectedNormals.name = "ProjectedNormals";
ProjectedNormals.dimension = dim;
if(xrSinglePassInstanced) {
ProjectedNormals.volumeDepth = 2;
}
//Shader.SetGlobalTexture(_LuxWater_NormalOverlayPID, ProjectedNormals);
}
else if (ProjectedNormals.width != rtWidth) {
DestroyImmediate(ProjectedNormals);
ProjectedNormals = new RenderTexture(rtWidth, rtHeight, 0, RenderTextureFormat.ARGBHalf, RenderTextureReadWrite.Linear);
ProjectedNormals.name = "ProjectedNormals";
ProjectedNormals.dimension = dim;
if(xrSinglePassInstanced) {
ProjectedNormals.volumeDepth = 2;
}
// We have to reassign the texture (prevented projectors from being updated after pause)
//Shader.SetGlobalTexture(_LuxWater_NormalOverlayPID, ProjectedNormals);
}
RenderTargetIdentifier targetNormals;
if(xrSinglePassInstanced) {
targetNormals = new RenderTargetIdentifier(ProjectedNormals, 0, CubemapFace.Unknown, -1); // slice = -1 makes it draw into both layers!
}
else {
targetNormals = new RenderTargetIdentifier(ProjectedNormals);
}
cb_Normals.Clear();
cb_Normals.SetRenderTarget(targetNormals);
// Regular ARGB buffer
// cb_Normals.ClearRenderTarget(true, true, new Color(0.5f,0.5f,1.0f,0.5f), 1.0f);
// ARGBHalf buffer
// cb_Normals.ClearRenderTarget(true, true, new Color(0.0f, 0.0f, 1.0f, 0.0f), 1.0f); // blue was 1.0 corrupting height!
cb_Normals.ClearRenderTarget(true, true, new Color(0.0f, 0.0f, 0.0f, 0.0f), 1.0f);
drawnNormalProjectors = 0;
for(int i = 0; i < numNormalProjectors; i++) {
// Check renderer's bounds against frustum before calling DrawRenderer
var currentProjector = LuxWater_Projector.NormalProjectors[i];
tempBounds = currentProjector.m_Rend.bounds;
if (GeometryUtility.TestPlanesAABB(frustumPlanes, tempBounds)) {
cb_Normals.DrawRenderer(currentProjector.m_Rend, currentProjector.m_Mat);
drawnNormalProjectors ++;
}
}
//Graphics.ExecuteCommandBuffer(cb_Normals);
// We might have multiple Cameras (split screen) - so we have to assign the Rendertexture each time.
Shader.SetGlobalTexture(_LuxWater_NormalOverlayPID, ProjectedNormals);
//GL.PopMatrix();
}
// Debug functions
void OnDrawGizmos() {
var tcam = GetComponent<Camera>();
var offset = 0;
var textureDrawWidth = (int)(tcam.aspect * 128.0f);
// Due to the alpha channels of the buffers we have to use a custom material/shader here
if (DebugMat == null) {
DebugMat = new Material(Shader.Find("Hidden/LuxWater_Debug"));
}
if (DebugNormalMat == null) {
DebugNormalMat = new Material(Shader.Find("Hidden/LuxWater_DebugNormals"));
}
if (DebugFoamBuffer) {
if(ProjectedFoam == null)
return;
GL.PushMatrix();
GL.LoadPixelMatrix(0, Screen.width, Screen.height, 0);
Graphics.DrawTexture(new Rect(offset, 0, textureDrawWidth, 128), ProjectedFoam, DebugMat);
GL.PopMatrix();
offset = textureDrawWidth;
}
if (DebugNormalBuffer) {
if(ProjectedNormals == null)
return;
GL.PushMatrix();
GL.LoadPixelMatrix(0, Screen.width, Screen.height, 0);
Graphics.DrawTexture(new Rect(offset, 0, textureDrawWidth, 128), ProjectedNormals, DebugNormalMat);
GL.PopMatrix();
}
}
void OnGUI() {
if(DebugStats) {
var NumberOfFoamProjectors = LuxWater_Projector.FoamProjectors.Count;
var NumberOfNormalProjectors = LuxWater_Projector.NormalProjectors.Count;
var Alignement = GUI.skin.label.alignment;
GUI.skin.label.alignment = TextAnchor.MiddleLeft;
GUI.Label(new Rect(10, 0, 300, 40), "Foam Projectors [Registered] " + NumberOfFoamProjectors + " [Drawn] " + drawnFoamProjectors);
GUI.Label(new Rect(10, 18, 300, 40), "Normal Projectors [Registered] " + NumberOfNormalProjectors + " [Drawn] " + drawnNormalProjectors );
GUI.skin.label.alignment = Alignement;
}
}
}
// Alloc free version to get the frustum planes
public static class GeomUtil {
private static System.Action<Plane[], Matrix4x4> _calculateFrustumPlanes_Imp;
public static void CalculateFrustumPlanes(Plane[] planes, Matrix4x4 worldToProjectMatrix)
{
//if (planes == null) throw new System.ArgumentNullException("planes");
//if (planes.Length < 6) throw new System.ArgumentException("Output array must be at least 6 in length.", "planes");
if (_calculateFrustumPlanes_Imp == null)
{
var meth = typeof(GeometryUtility).GetMethod("Internal_ExtractPlanes", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic, null, new System.Type[] { typeof(Plane[]), typeof(Matrix4x4) }, null);
if (meth == null) throw new System.Exception("Failed to reflect internal method. Your Unity version may not contain the presumed named method in GeometryUtility.");
_calculateFrustumPlanes_Imp = System.Delegate.CreateDelegate(typeof(System.Action<Plane[], Matrix4x4>), meth) as System.Action<Plane[], Matrix4x4>;
if(_calculateFrustumPlanes_Imp == null) throw new System.Exception("Failed to reflect internal method. Your Unity version may not contain the presumed named method in GeometryUtility.");
}
_calculateFrustumPlanes_Imp(planes, worldToProjectMatrix);
}
}
}