354 lines
15 KiB
C#
354 lines
15 KiB
C#
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
|
|
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
|
|
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
|
|
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
|
|
|
|
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
|
#define DEBUG_AVAILABLE
|
|
#endif
|
|
|
|
#if URP
|
|
using System;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
using UnityEngine.Rendering.RenderGraphModule;
|
|
using UnityEngine.Rendering.RenderGraphModule.Util;
|
|
using UnityEngine.Rendering.Universal;
|
|
using UnityEngine.Serialization;
|
|
|
|
namespace StylizedWater3
|
|
{
|
|
[DisallowMultipleRendererFeature("Stylized Water 3")]
|
|
public partial class StylizedWaterRenderFeature : ScriptableRendererFeature
|
|
{
|
|
[Tooltip("Render all transparent materials NOT on the \"Water\" layer into a screen-space texture, and use that for refraction rendering in the water." +
|
|
"\n\n" +
|
|
"If enabled, transparent materials can be submerged and refracted correctly")]
|
|
public bool transparencyRefraction;
|
|
|
|
public static StylizedWaterRenderFeature GetDefault()
|
|
{
|
|
return (StylizedWaterRenderFeature)PipelineUtilities.GetRenderFeature<StylizedWaterRenderFeature>();
|
|
}
|
|
|
|
[Serializable]
|
|
public class ScreenSpaceReflectionSettings
|
|
{
|
|
[FormerlySerializedAs("enable")]
|
|
[Tooltip("Allow SSR to be rendered in water materials that have it enabled." +
|
|
"\n\nDisable as a global performance scaling measure")]
|
|
public bool allow = true;
|
|
|
|
[FormerlySerializedAs("reflectSkybox")]
|
|
[Tooltip("Only enable when Reflection Probes cannot be used in a realtime lighting setup. If enabled, SSR will also reflects the skybox color and geometry in front of the water." +
|
|
"\n\nIdeally disabled, so that Reflection Probes can be relied on for a 1:1 accurate skybox reflection.")]
|
|
public bool reflectEverything = false;
|
|
}
|
|
public ScreenSpaceReflectionSettings screenSpaceReflectionSettings = new ScreenSpaceReflectionSettings();
|
|
|
|
[FormerlySerializedAs("directionalCaustics")]
|
|
[Tooltip("Pass on the main directional light's projection onto the water shader. Allows caustics to project along its direction (rather than top-down)." +
|
|
"\n\nThis shading operation is relative expensive, as it involves analyzing the depth texture at 4 different points")]
|
|
public bool allowDirectionalCaustics;
|
|
|
|
public HeightPrePass.Settings heightPrePassSettings = new HeightPrePass.Settings();
|
|
#if SWS_DEV
|
|
public TerrainHeightPrePass.Settings terrainHeightPrePassSettings = new TerrainHeightPrePass.Settings();
|
|
#endif
|
|
|
|
private SetupConstants constantsSetup;
|
|
private HeightPrePass heightPrePass;
|
|
private HeightQuerySystem.RenderPass heightQueryPass;
|
|
#if SWS_DEV
|
|
private TerrainHeightPrePass terrainHeightPrePass;
|
|
private DistanceFieldPass distanceFieldPass;
|
|
private RenderTransparentTexture transparentTexturePass;
|
|
#endif
|
|
|
|
#if DEBUG_AVAILABLE
|
|
private DebugInspectorPass debugInspectorPass;
|
|
#endif
|
|
|
|
[SerializeField]
|
|
public ComputeShader heightReadbackCS;
|
|
[SerializeField]
|
|
public Shader heightProcessingShader;
|
|
|
|
/// <summary>
|
|
/// Set this to true from a render pass if it requires the displacement pre-pass, despite it being disabled in the render feature settings.
|
|
/// </summary>
|
|
public static bool RequireHeightPrePass;
|
|
|
|
protected bool WillExecuteHeightPrePass => RequireHeightPrePass || heightPrePassSettings.enable || HeightQuerySystem.RequiresHeightPrepass;
|
|
|
|
private void OnValidate()
|
|
{
|
|
VerifyReferences();
|
|
}
|
|
|
|
private void Reset()
|
|
{
|
|
VerifyReferences();
|
|
}
|
|
|
|
public void VerifyReferences()
|
|
{
|
|
if (!heightReadbackCS)
|
|
{
|
|
#if UNITY_EDITOR
|
|
//HeightSampler.cs
|
|
string assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath("768e0c28dfdbc6b429fd59518fa03f2d");
|
|
|
|
ComputeShader cs = UnityEditor.AssetDatabase.LoadAssetAtPath<ComputeShader>(assetPath);
|
|
|
|
if (cs)
|
|
{
|
|
heightReadbackCS = cs;
|
|
}
|
|
#endif
|
|
}
|
|
if(!heightProcessingShader) heightProcessingShader = Shader.Find(ShaderParams.ShaderNames.HeightProcessor);
|
|
|
|
#if SWS_DEV
|
|
if(!terrainHeightPrePassSettings.terrainHeightVisualizationShader) terrainHeightPrePassSettings.terrainHeightVisualizationShader = Shader.Find(ShaderParams.ShaderNames.TerrainHeight);
|
|
#endif
|
|
|
|
VerifyUnderwaterRendering();
|
|
}
|
|
|
|
public class DebugData : ContextItem
|
|
{
|
|
public TextureHandle currentHandle;
|
|
|
|
public override void Reset()
|
|
{
|
|
currentHandle = TextureHandle.nullHandle;
|
|
}
|
|
}
|
|
|
|
public override void Create()
|
|
{
|
|
GraphicsDeviceType currentGraphicsAPI = SystemInfo.graphicsDeviceType;
|
|
//https://issuetracker.unity3d.com/issues/crash-on-gfxdeviced3d12base-drawbufferscommon-when-adding-specific-custom-render-pass-feature-to-renderer
|
|
if (currentGraphicsAPI == GraphicsDeviceType.Direct3D12 || currentGraphicsAPI == GraphicsDeviceType.XboxOneD3D12)
|
|
{
|
|
//Using the "BeforeRendering" point causes a fatal crash when using DX12 when allocating a RT
|
|
defaultInjectionPoint = RenderPassEvent.BeforeRenderingShadows;
|
|
}
|
|
|
|
constantsSetup = new SetupConstants
|
|
{
|
|
renderPassEvent = defaultInjectionPoint
|
|
};
|
|
|
|
heightPrePass = new HeightPrePass
|
|
{
|
|
renderPassEvent = defaultInjectionPoint
|
|
};
|
|
|
|
heightQueryPass = new HeightQuerySystem.RenderPass()
|
|
{
|
|
renderPassEvent = defaultInjectionPoint
|
|
};
|
|
|
|
#if SWS_DEV
|
|
terrainHeightPrePass = new TerrainHeightPrePass()
|
|
{
|
|
renderPassEvent = defaultInjectionPoint
|
|
};
|
|
|
|
if (transparencyRefraction)
|
|
{
|
|
transparentTexturePass = new RenderTransparentTexture();
|
|
transparentTexturePass.renderPassEvent = RenderPassEvent.BeforeRenderingTransparents;
|
|
}
|
|
#endif
|
|
|
|
CreateFlowMapPass();
|
|
CreateDynamicEffectsPasses();
|
|
CreateUnderwaterRenderingPasses();
|
|
|
|
#if DEBUG_AVAILABLE
|
|
debugInspectorPass = new DebugInspectorPass();
|
|
debugInspectorPass.renderPassEvent = RenderPassEvent.AfterRendering;
|
|
#endif
|
|
}
|
|
|
|
//Note: Actually prefer to render before transparents, but this creates a recursive RenderSingleCamera call
|
|
//Restoring the view/projection to that of the camera also breaks VR. Required functions are internal URP code
|
|
|
|
//In some cases, if no pre-passes render (depth, shadows, etc) then the projection does not get reset when rendering the opaque objects pass. Hence, things must render as early as possible.
|
|
private static RenderPassEvent defaultInjectionPoint = RenderPassEvent.BeforeRendering;
|
|
|
|
//Dynamic Effects
|
|
partial void CreateDynamicEffectsPasses();
|
|
partial void AddDynamicEffectsPasses(ScriptableRenderer renderer, ref RenderingData renderingData);
|
|
partial void DisposeDynamicEffectsPasses();
|
|
|
|
partial void VerifyUnderwaterRendering();
|
|
partial void CreateUnderwaterRenderingPasses();
|
|
partial void AddUnderwaterRenderingPasses(ScriptableRenderer renderer, ref RenderingData renderingData);
|
|
partial void DisposeUnderwaterRenderingPasses();
|
|
|
|
partial void CreateFlowMapPass();
|
|
partial void AddFlowMapPass(ScriptableRenderer renderer, ref RenderingData renderingData);
|
|
partial void DisposeFlowMapPass();
|
|
|
|
private bool IsInvalidContext(CameraType cameraType, CameraRenderType cameraRenderType)
|
|
{
|
|
//Skip for any special use camera's (except scene view camera)
|
|
if (cameraType != CameraType.SceneView && (cameraType == CameraType.Preview || hideFlags != HideFlags.None))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//Skip overlay cameras
|
|
if (cameraRenderType == CameraRenderType.Overlay)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private bool isMainCamera(Camera camera)
|
|
{
|
|
//Skip for any special use camera's (except scene view camera)
|
|
return (camera.cameraType == CameraType.SceneView || camera.CompareTag("MainCamera"));
|
|
}
|
|
|
|
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
|
|
{
|
|
var currentCam = renderingData.cameraData.camera;
|
|
|
|
if(IsInvalidContext(currentCam.cameraType, renderingData.cameraData.renderType)) return;
|
|
|
|
constantsSetup.Setup(this);
|
|
renderer.EnqueuePass(constantsSetup);
|
|
|
|
#if SWS_DEV
|
|
if (terrainHeightPrePassSettings.enable)
|
|
{
|
|
terrainHeightPrePass.Setup(terrainHeightPrePassSettings);
|
|
renderer.EnqueuePass(terrainHeightPrePass);
|
|
}
|
|
|
|
if (transparencyRefraction)
|
|
{
|
|
renderer.EnqueuePass(transparentTexturePass);
|
|
}
|
|
#endif
|
|
|
|
//Do not execute for reflection probe captures
|
|
if (currentCam.cameraType != CameraType.Reflection)
|
|
{
|
|
AddFlowMapPass(renderer, ref renderingData);
|
|
AddDynamicEffectsPasses(renderer, ref renderingData);
|
|
|
|
//Do not execute for the scene-view camera in play-mode. Even if the tab is not active, it would render around it instead of the main camera
|
|
var skipHeightPrePass = Application.isPlaying && heightPrePassSettings.disableInSceneView && currentCam.cameraType == CameraType.SceneView;
|
|
|
|
if (WillExecuteHeightPrePass && skipHeightPrePass == false)
|
|
{
|
|
heightPrePass.Setup(heightPrePassSettings);
|
|
renderer.EnqueuePass(heightPrePass);
|
|
|
|
if (HeightQuerySystem.QueryCount > 0)
|
|
{
|
|
heightQueryPass.Setup(this, heightReadbackCS);
|
|
renderer.EnqueuePass(heightQueryPass);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Shader.SetGlobalInt(HeightPrePass._WaterHeightPrePassAvailable, 0);
|
|
}
|
|
}
|
|
|
|
AddUnderwaterRenderingPasses(renderer, ref renderingData);
|
|
|
|
#if DEBUG_AVAILABLE
|
|
if (RenderTargetDebugger.InspectedProperty > 0)
|
|
{
|
|
renderer.EnqueuePass(debugInspectorPass);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
constantsSetup.Dispose();
|
|
heightPrePass.Dispose();
|
|
heightQueryPass.Dispose();
|
|
#if SWS_DEV
|
|
terrainHeightPrePass.Dispose();
|
|
#endif
|
|
|
|
DisposeFlowMapPass();
|
|
DisposeDynamicEffectsPasses();
|
|
DisposeUnderwaterRenderingPasses();
|
|
}
|
|
|
|
#if DEBUG_AVAILABLE
|
|
private class DebugInspectorPass : ScriptableRenderPass
|
|
{
|
|
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
|
|
{
|
|
DebugData debugData = frameData.Get<DebugData>();
|
|
|
|
//Whichever pass's render target PropertyID matches the selected one in the inspector window get assigned as the 'currentHandle'
|
|
if (debugData.currentHandle.IsValid())
|
|
{
|
|
var destinationDesc = renderGraph.GetTextureDesc(debugData.currentHandle);
|
|
destinationDesc.clearBuffer = false;
|
|
|
|
RenderTextureDescriptor rtDsc = new RenderTextureDescriptor
|
|
{
|
|
width = destinationDesc.width,
|
|
height = destinationDesc.height,
|
|
//If you're seeing an error here you are not using a compatible Unity version!
|
|
graphicsFormat = destinationDesc.colorFormat,
|
|
#if UNITY_6000_1_OR_NEWER
|
|
colorFormat = RenderTextureFormat.Default,
|
|
#endif
|
|
volumeDepth = 1,
|
|
dimension = destinationDesc.dimension,
|
|
useMipMap = destinationDesc.useMipMap,
|
|
msaaSamples = 1
|
|
};
|
|
|
|
TextureDesc textureDesc = debugData.currentHandle.GetDescriptor(renderGraph);
|
|
RenderingUtils.ReAllocateHandleIfNeeded(ref RenderTargetDebugger.CurrentRT, rtDsc, textureDesc.filterMode, textureDesc.wrapMode, textureDesc.anisoLevel, textureDesc.mipMapBias, textureDesc.name);
|
|
|
|
TextureHandle destination = renderGraph.ImportTexture(RenderTargetDebugger.CurrentRT);
|
|
|
|
if (destination.IsValid() == false)
|
|
{
|
|
throw new Exception("Failed to generate debugger texture");
|
|
}
|
|
else
|
|
{
|
|
var cameraData = frameData.Get<UniversalCameraData>();
|
|
RenderTargetDebugger.CurrentCameraName = cameraData.camera.name;
|
|
|
|
//Copy TextureHandle into persistent RT
|
|
renderGraph.AddCopyPass(debugData.currentHandle, destination, passName: "Water Debug");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RenderTargetDebugger.CurrentRT = null;
|
|
RenderTargetDebugger.CurrentCameraName = string.Empty;
|
|
}
|
|
}
|
|
|
|
#pragma warning disable CS0672
|
|
#pragma warning disable CS0618
|
|
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { }
|
|
#pragma warning restore CS0672
|
|
#pragma warning restore CS0618
|
|
}
|
|
#endif //DEBUG_AVAILABLE
|
|
}
|
|
}
|
|
#endif |