218 lines
9.5 KiB
C#
218 lines
9.5 KiB
C#
//Stylized Water 2: Underwater Rendering extension
|
|
//Staggart Creations (http://staggart.xyz)
|
|
//Copyright protected under Unity Asset Store EULA
|
|
|
|
using System;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
#if URP
|
|
using UnityEngine.Rendering.Universal;
|
|
|
|
namespace StylizedWater2.UnderwaterRendering
|
|
{
|
|
#if UNITY_2021_1_OR_NEWER
|
|
[DisallowMultipleRendererFeature]
|
|
#endif
|
|
public class UnderwaterRenderFeature : ScriptableRendererFeature
|
|
{
|
|
//Shared resources, ensures they're included in a build when the render feature is in use
|
|
public UnderwaterResources resources;
|
|
|
|
[Serializable]
|
|
public class Settings
|
|
{
|
|
public bool allowBlur = true;
|
|
public bool allowDistortion = true;
|
|
|
|
public enum DistortionMode
|
|
{
|
|
[InspectorName("Screen-space (Fastest)")]
|
|
ScreenSpace,
|
|
[InspectorName("Camera-space (Nicest)")]
|
|
CameraSpace
|
|
}
|
|
[Tooltip("Screen-space mode is faster, but distortion will appear to move with the camera\n\n" +
|
|
"Camera-space mode looks better, but requires more calculations")]
|
|
public DistortionMode distortionMode = DistortionMode.CameraSpace;
|
|
|
|
[Tooltip("Limit caustics only to parts of a surface where sun light hits it")]
|
|
public bool directionalCaustics;
|
|
[Tooltip("(Requires Unity 2020.2+) Use the depth normals texture created from the Depth Normals pre-pass." +
|
|
"\n\nThis can negatively impact performance if the game isn't already optimized for draw calls!" +
|
|
"\n\nIf disabled, normals will be reconstructed from the depth texture")]
|
|
public bool accurateDirectionalCaustics = false;
|
|
|
|
[Tooltip("Attempts to create a glass-like appearance by refracting the scene geometry behind the water line. Note this does not refract the water surface behind it")]
|
|
public bool waterlineRefraction = true;
|
|
}
|
|
public Settings settings = new Settings();
|
|
|
|
private UnderwaterMaskPass maskPass;
|
|
private UnderwaterLinePass waterLinePass;
|
|
private UnderwaterShadingPass shadingPass;
|
|
private DistortionSpherePass distortionSpherePass;
|
|
private UnderwaterPost postProcessingPass;
|
|
|
|
public UnderwaterRenderer.KeywordStates keywordStates;
|
|
|
|
private void Reset() //Note: editor-only
|
|
{
|
|
if (!resources) resources = UnderwaterResources.Find();
|
|
|
|
//Recommended fastest settings
|
|
#if UNITY_IOS || UNITY_TVOS || UNITY_ANDROID || UNITY_SWITCH
|
|
settings.directionalCaustics = false;
|
|
settings.accurateDirectionalCaustics = false;
|
|
settings.allowBlur = false;
|
|
settings.allowDistortion = false;
|
|
settings.distortionMode = Settings.DistortionMode.ScreenSpace;
|
|
settings.waterlineRefraction = false;
|
|
#endif
|
|
}
|
|
|
|
public override void Create()
|
|
{
|
|
#if UNITY_EDITOR
|
|
if (!resources) resources = UnderwaterResources.Find();
|
|
#endif
|
|
|
|
maskPass = new UnderwaterMaskPass(this);
|
|
maskPass.renderPassEvent = RenderPassEvent.BeforeRenderingTransparents;
|
|
|
|
shadingPass = new UnderwaterShadingPass(this);
|
|
shadingPass.renderPassEvent = RenderPassEvent.BeforeRenderingTransparents;
|
|
|
|
distortionSpherePass = new DistortionSpherePass(resources);
|
|
distortionSpherePass.renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
|
|
|
|
postProcessingPass = new UnderwaterPost(this);
|
|
postProcessingPass.renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
|
|
|
|
waterLinePass = new UnderwaterLinePass(this);
|
|
waterLinePass.renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
maskPass.Dispose();
|
|
shadingPass.Dispose();
|
|
distortionSpherePass.Dispose();
|
|
postProcessingPass.Dispose();
|
|
}
|
|
|
|
private bool cameraIntersecting;
|
|
private bool cameraSubmerged;
|
|
|
|
private bool RequiresPostProcessingPass(UnderwaterRenderer renderer)
|
|
{
|
|
return (renderer.enableBlur && settings.allowBlur) || (renderer.enableDistortion && settings.allowDistortion);
|
|
}
|
|
|
|
private bool InvalidRenderingContext(CameraData cameraData)
|
|
{
|
|
#if SWS_DEV
|
|
//Debug.Log($"Name:{cameraData.camera.name} Type:{cameraData.cameraType} Enabled:{cameraData.camera.enabled}");
|
|
#endif
|
|
|
|
//Likely a planar reflections camera or otherwise
|
|
if (cameraData.camera.cameraType != CameraType.SceneView && cameraData.camera.enabled == false) return true;
|
|
|
|
//Camera stacking and depth-based post processing is essentially non-functional.
|
|
//All effects render twice to the screen, causing double brightness. Next to fog causing overlay objects to appear transparent
|
|
//- Best option is to not render anything for overlay cameras
|
|
//- Reflection probes do not capture the water line correctly
|
|
//- Preview cameras end up rendering the effect into asset thumbnails
|
|
if (cameraData.renderType == CameraRenderType.Overlay || cameraData.camera.cameraType == CameraType.Reflection || cameraData.camera.cameraType == CameraType.Preview) return true;
|
|
|
|
#if UNITY_EDITOR
|
|
//Skip if post-processing is disabled in scene-view
|
|
if (cameraData.camera.cameraType == CameraType.SceneView && UnityEditor.SceneView.lastActiveSceneView && !UnityEditor.SceneView.lastActiveSceneView.sceneViewState.showImageEffects) return true;
|
|
|
|
#endif
|
|
|
|
//Skip hidden or off-screen cameras.
|
|
if (cameraData.camera.cameraType == CameraType.Game && cameraData.camera.hideFlags != HideFlags.None) return true;
|
|
|
|
#if UNITY_EDITOR
|
|
//Skip rendering if editing a prefab
|
|
#if UNITY_2021_2_OR_NEWER
|
|
if (UnityEditor.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage() != null) return true;
|
|
#else
|
|
if (UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage() != null) return true;
|
|
#endif
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
private int _FullySubmerged = Shader.PropertyToID("_FullySubmerged");
|
|
private int _UnderwaterRenderingEnabled = Shader.PropertyToID("_UnderwaterRenderingEnabled");
|
|
|
|
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
|
|
{
|
|
if (!UnderwaterRenderer.EnableRendering) return;
|
|
|
|
if (UnderwaterRenderer.Instance == null) return;
|
|
if (UnderwaterRenderer.Instance.waterMaterial == null) return;
|
|
|
|
if (InvalidRenderingContext(renderingData.cameraData)) return;
|
|
|
|
//Only render if the bottom of the screen touches the water or is below the water level
|
|
cameraIntersecting = UnderwaterRenderer.Instance.CameraIntersectingWater(renderingData.cameraData.camera);
|
|
|
|
if (cameraIntersecting)
|
|
{
|
|
Shader.SetGlobalInt(_UnderwaterRenderingEnabled, 1);
|
|
|
|
keywordStates = UnderwaterRenderer.Instance.materialKeywordStates;
|
|
cameraSubmerged = UnderwaterRenderer.Instance.CameraSubmerged(renderingData.cameraData.camera);
|
|
|
|
//Once submerged, the pass stops executing. At which point the water mask buffer will be left entirely filled
|
|
if (!cameraSubmerged)
|
|
{
|
|
maskPass.Setup(settings, renderer);
|
|
}
|
|
|
|
//Note: Previously was assigning Texture2D.redTexture as the water mask, but this breaks in VR since the texture isn't a texture array
|
|
//Instead return a full white value in the shader function
|
|
Shader.SetGlobalInt(_FullySubmerged, cameraSubmerged ? 1 : 0);
|
|
|
|
shadingPass.Setup(settings, renderer);
|
|
|
|
if (RequiresPostProcessingPass(UnderwaterRenderer.Instance))
|
|
{
|
|
if (UnderwaterRenderer.Instance.enableDistortion && settings.allowDistortion && settings.distortionMode == UnderwaterRenderFeature.Settings.DistortionMode.CameraSpace)
|
|
{
|
|
renderer.EnqueuePass(distortionSpherePass);
|
|
}
|
|
|
|
postProcessingPass.Setup(settings, renderer);
|
|
}
|
|
|
|
//No need to render this if the water line won't be visible
|
|
if (!cameraSubmerged)
|
|
{
|
|
renderer.EnqueuePass(waterLinePass);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Shader.SetGlobalInt(_UnderwaterRenderingEnabled, 0);
|
|
}
|
|
}
|
|
|
|
public static Material CreateMaterial(string profilerTag, Shader shader)
|
|
{
|
|
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
|
if (!shader)
|
|
{
|
|
//Debug.LogError("[" + profilerTag + "] Shader could not be found, ensure all files are imported");
|
|
return null;
|
|
}
|
|
#endif
|
|
|
|
return CoreUtils.CreateEngineMaterial(shader);
|
|
}
|
|
}
|
|
}
|
|
#endif |