// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
using UnityEngine.Rendering.Universal;
using WaveHarmonic.Crest.Internal;
#if !UNITY_2023_2_OR_NEWER
using GraphicsFormatUsage = UnityEngine.Experimental.Rendering.FormatUsage;
#endif
namespace WaveHarmonic.Crest
{
///
/// General purpose helpers which, at the moment, do not warrant a seperate file.
///
static partial class Helpers
{
// Adapted from:
// Packages/com.unity.render-pipelines.universal/Runtime/UniversalRenderer.cs
#if UNITY_SWITCH || UNITY_ANDROID || UNITY_EMBEDDED_LINUX || UNITY_QNX
internal const GraphicsFormat k_DepthStencilFormat = GraphicsFormat.D24_UNorm_S8_UInt;
internal const int k_DepthBufferBits = 24;
internal const DepthBits k_DepthBits = DepthBits.Depth24;
#else
internal const GraphicsFormat k_DepthStencilFormat = GraphicsFormat.D32_SFloat_S8_UInt;
internal const int k_DepthBufferBits = 32;
internal const DepthBits k_DepthBits = DepthBits.Depth32;
#endif
public static class ShaderIDs
{
public static readonly int s_MainTexture = Shader.PropertyToID("_Utility_MainTexture");
}
static Mesh s_Plane;
///
/// Plane geometry
///
public static Mesh PlaneMesh
{
get
{
if (s_Plane) return s_Plane;
return s_Plane = Resources.GetBuiltinResource("New-Plane.fbx");
}
}
static Mesh s_Quad;
///
/// Quad geometry
///
public static Mesh QuadMesh
{
get
{
if (s_Quad) return s_Quad;
return s_Quad = Resources.GetBuiltinResource("Quad.fbx");
}
}
static Mesh s_SphereMesh;
///
/// Sphere geometry
///
public static Mesh SphereMesh
{
get
{
if (s_SphereMesh) return s_SphereMesh;
return s_SphereMesh = Resources.GetBuiltinResource("New-Sphere.fbx");
}
}
internal static int SiblingIndexComparison(int x, int y) => x.CompareTo(y);
///
/// Comparer that always returns less or greater, never equal, to get work around unique key constraint
///
internal static int DuplicateComparison(int x, int y)
{
var result = x.CompareTo(y);
// If non-zero, use result, otherwise return greater (never equal)
return result != 0 ? result : 1;
}
static Vector2Int CalculateResolution(Vector2 resolution, int maximum)
{
// Enforce maximum and scale to maintain aspect ratio.
var largest = Mathf.Max(resolution.x, resolution.y);
if (largest > maximum)
{
var scale = maximum / largest;
resolution *= scale;
}
return new(Mathf.CeilToInt(resolution.x), Mathf.CeilToInt(resolution.y));
}
internal static Vector2Int CalculateResolutionFromTexelSize(Vector2 worldSize, float texelSize, int maximum)
{
return CalculateResolution(new(worldSize.x / texelSize, worldSize.y / texelSize), maximum);
}
internal static Vector2Int CalculateResolutionFromTexelDensity(Vector2 worldSize, float texelDensity, int maximum)
{
return CalculateResolution(new(Mathf.RoundToInt(texelDensity * worldSize.x), Mathf.RoundToInt(texelDensity * worldSize.y)), maximum);
}
///
/// Rotates an XZ size and returns an XZ size which encapsulates it.
///
public static Vector2 RotateAndEncapsulateXZ(Vector2 size, float angle)
{
angle = Mathf.PingPong(angle, 90f);
var c = Mathf.Cos(angle * Mathf.Deg2Rad);
var s = Mathf.Sin(angle * Mathf.Deg2Rad);
return new
(
size.x * c + size.y * s,
size.y * c + size.x * s
);
}
public static BindingFlags s_AnyMethod = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance |
BindingFlags.Static;
public static T GetCustomAttribute(System.Type type) where T : System.Attribute
{
return (T)System.Attribute.GetCustomAttribute(type, typeof(T));
}
public static WaitForEndOfFrame WaitForEndOfFrame { get; } = new();
// Taken from:
// https://github.com/Unity-Technologies/Graphics/blob/871df5563d88e1ba778c82a43f39c9afc95368e6/Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl#L1149-L1152
// Z buffer to linear 0..1 depth (0 at camera position, 1 at far plane).
// Does NOT work with orthographic projections.
// Does NOT correctly handle oblique view frustums.
public static float NonLinearToLinear01Depth(float depth, Vector4 zBufferParameters)
{
return 1.0f / (zBufferParameters.x * depth + zBufferParameters.y);
}
// Taken from:
// https://github.com/Unity-Technologies/Graphics/blob/871df5563d88e1ba778c82a43f39c9afc95368e6/Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl#L1154-L1161
// Z buffer to linear depth.
// Does NOT correctly handle oblique view frustums.
// Does NOT work with orthographic projection.
public static float NonLinearToLinearEyeDepth(float depth, Vector4 zBufferParameters)
{
return 1.0f / (zBufferParameters.z * depth + zBufferParameters.w);
}
// Taken from:
// https://www.cyanilux.com/tutorials/depth/#depth-output
public static float LinearDepthToNonLinear(float depth, Vector4 zBufferParameters)
{
return (1.0f - depth * zBufferParameters.y) / (depth * zBufferParameters.x);
}
// Taken from:
// https://www.cyanilux.com/tutorials/depth/#depth-output
public static float EyeDepthToNonLinear(float depth, Vector4 zBufferParameters)
{
return (1.0f - depth * zBufferParameters.w) / (depth * zBufferParameters.z);
}
public static Vector4 GetZBufferParameters(Camera camera)
{
// Taken and modified from:
// https://github.com/Unity-Technologies/Graphics/blob/871df5563d88e1ba778c82a43f39c9afc95368e6/Packages/com.unity.render-pipelines.universal/Runtime/ScriptableRenderer.cs#L303-L327
var near = camera.nearClipPlane;
var far = camera.farClipPlane;
var inverseNear = Mathf.Approximately(near, 0.0f) ? 0.0f : 1.0f / near;
var inverseFar = Mathf.Approximately(far, 0.0f) ? 0.0f : 1.0f / far;
// From http://www.humus.name/temp/Linearize%20depth.txt
// But as depth component textures on OpenGL always return in 0..1 range (as in D3D), we have to use
// the same constants for both D3D and OpenGL here.
// OpenGL would be this:
// zc0 = (1.0 - far / near) / 2.0;
// zc1 = (1.0 + far / near) / 2.0;
// D3D is this:
var zc0 = 1.0f - far * inverseNear;
var zc1 = far * inverseNear;
var zBufferParameters = new Vector4(zc0, zc1, zc0 * inverseFar, zc1 * inverseFar);
if (SystemInfo.usesReversedZBuffer)
{
zBufferParameters.y += zBufferParameters.x;
zBufferParameters.x = -zBufferParameters.x;
zBufferParameters.w += zBufferParameters.z;
zBufferParameters.z = -zBufferParameters.z;
}
return zBufferParameters;
}
///
/// Uses PrefabUtility.InstantiatePrefab in edit mode, otherwise uses GameObject.Instantiate.
///
public static GameObject InstantiatePrefab(GameObject prefab)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
// Previously we always used this in the editor, including play mode. But it was
// reported to have failed (null return) when Asset Bundles were used in play mode.
return (GameObject)UnityEditor.PrefabUtility.InstantiatePrefab(prefab);
}
else
#endif
{
return Object.Instantiate(prefab);
}
}
// Taken from Unity
// https://docs.unity3d.com/2022.2/Documentation/Manual/BestPracticeUnderstandingPerformanceInUnity5.html
public static bool StartsWithNoAlloc(this string a, string b)
{
var aLen = a.Length;
var bLen = b.Length;
var ap = 0; var bp = 0;
while (ap < aLen && bp < bLen && a[ap] == b[bp])
{
ap++;
bp++;
}
return bp == bLen;
}
public static void ReadRenderTexturePixels(ref RenderTexture rt, ref Texture2D texture, int slice = -1)
{
var source = rt;
var copy = slice > -1 && rt.volumeDepth > 1;
if (copy)
{
var descriptor = rt.descriptor;
descriptor.volumeDepth = 1;
source = RenderTexture.GetTemporary(descriptor);
Graphics.CopyTexture(rt, slice, 0, source, 0, 0);
}
var previous = RenderTexture.active;
RenderTexture.active = source;
texture.ReadPixels(new(0, 0, texture.width, texture.height), 0, 0, false);
texture.Apply();
RenderTexture.active = previous;
if (copy)
{
RenderTexture.active = null;
RenderTexture.ReleaseTemporary(source);
}
}
// Reads a single pixel.
public static void ReadRenderTexturePixel(ref RenderTexture rt, ref Texture2D texture, int x, int y, int slice = 0)
{
var descriptor = rt.descriptor;
descriptor.width = 1;
descriptor.height = 1;
descriptor.volumeDepth = 1;
var source = RenderTexture.GetTemporary(descriptor);
Graphics.CopyTexture(rt, slice, 0, x, y, 1, 1, source, 0, 0, 0, 0);
var previous = RenderTexture.active;
RenderTexture.active = source;
texture.ReadPixels(new(0, 0, texture.width, texture.height), 0, 0, false);
texture.Apply();
RenderTexture.active = previous;
RenderTexture.ReleaseTemporary(source);
}
public static void Blit(RenderTexture source, RenderTexture target)
{
var active = RenderTexture.active;
Graphics.Blit(source, target);
RenderTexture.active = active;
}
public static float ConvertDepthBufferValueToDistance(Camera camera, float depth)
{
float zBufferParamsX; float zBufferParamsY;
if (SystemInfo.usesReversedZBuffer)
{
zBufferParamsY = 1f;
zBufferParamsX = camera.farClipPlane / camera.nearClipPlane - 1f;
}
else
{
zBufferParamsY = camera.farClipPlane / camera.nearClipPlane;
zBufferParamsX = 1f - zBufferParamsY;
}
return 1.0f / (zBufferParamsX / camera.farClipPlane * depth + zBufferParamsY / camera.farClipPlane);
}
#if UNITY_EDITOR
public static bool IsPreviewOfGameCamera(Camera camera)
{
// StartsWith has GC allocations. It is only used in the editor.
return camera.cameraType == CameraType.Preview && camera.name == "Preview Camera";
}
#endif
public static bool IsMSAAEnabled(Camera camera)
{
#if d_UnityHDRP
if (RenderPipelineHelper.IsHighDefinition)
{
var hdCamera = HDCamera.GetOrCreate(camera);
// Scene view camera does appear to support MSAA unlike other RPs.
// Querying frame settings on the camera will give the correct results - overriden or not.
return hdCamera.msaaSamples != MSAASamples.None;
}
#endif
var isMSAA = camera.allowMSAA;
#if d_UnityURP
if (RenderPipelineHelper.IsUniversal)
{
// MSAA will be the same for every camera if XR rendering.
isMSAA = isMSAA || Rendering.EnabledXR;
}
#endif
#if UNITY_EDITOR
// Game View Preview ignores allowMSAA.
isMSAA = isMSAA || IsPreviewOfGameCamera(camera);
// Scene view doesn't support MSAA.
isMSAA = isMSAA && camera.cameraType != CameraType.SceneView;
#endif
#if d_UnityURP
if (RenderPipelineHelper.IsUniversal)
{
// Keep this check last so it overrides everything else.
isMSAA = isMSAA && camera.GetUniversalAdditionalCameraData().scriptableRenderer.supportedRenderingFeatures.msaa;
}
#endif
// QualitySettings.antiAliasing can be zero.
return (isMSAA ? QualitySettings.antiAliasing : 1) > 1;
}
public static bool IsIntelGPU()
{
// Works for Windows and MacOS. Grabbed from Unity Graphics repository:
// https://github.com/Unity-Technologies/Graphics/blob/68b0d42c/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.PostProcess.cs#L198-L199
return SystemInfo.graphicsDeviceName.ToLowerInvariant().Contains("intel");
}
public static bool MaskIncludesLayer(int mask, int layer)
{
// Taken from:
// http://answers.unity.com/answers/1332280/view.html
return mask == (mask | (1 << layer));
}
// R16G16B16A16_SFloat appears to be the most compatible format.
// https://docs.unity3d.com/Manual/class-TextureImporterOverride.html#texture-compression-support-platforms
// https://learn.microsoft.com/en-us/windows/win32/direct3d12/typed-unordered-access-view-loads#supported-formats-and-api-calls
static readonly GraphicsFormat s_FallbackGraphicsFormat = GraphicsFormat.R16G16B16A16_SFloat;
static bool SupportsRandomWriteOnRenderTextureFormat(GraphicsFormat format)
{
var rtFormat = GraphicsFormatUtility.GetRenderTextureFormat(format);
return System.Enum.IsDefined(typeof(RenderTextureFormat), rtFormat)
&& SystemInfo.SupportsRandomWriteOnRenderTextureFormat(rtFormat);
}
internal static GraphicsFormat GetCompatibleTextureFormat(GraphicsFormat format, GraphicsFormatUsage usage, string label, bool randomWrite = false)
{
var result = SystemInfo.GetCompatibleFormat(format, usage);
var useFallback = result == GraphicsFormat.None;
#if CREST_DEBUG_LOG_FORMAT_CHANGES
if (useFallback)
{
Debug.Log($"Crest: The graphics device does not support the render texture format {format}. Will attempt to use fallback. ({label})");
}
else if (result != format)
{
Debug.Log($"Crest: Using render texture format {result} instead of {format}. ({label})");
}
#endif
// NOTE: Disabling for now. RenderTextureFormat is a subset of GraphicsFormat and
// there is not always an equivalent.
// if (!useFallback && randomWrite && !SupportsRandomWriteOnRenderTextureFormat(result))
// {
// Debug.Log($"Crest: The graphics device does not support the render texture format {result} with random read/write. Will attempt to use fallback.");
// useFallback = true;
// }
// Check if fallback is compatible before using it.
if (useFallback && format == s_FallbackGraphicsFormat)
{
Debug.Log($"Crest: Fallback {s_FallbackGraphicsFormat} is not supported on this device. Please inform us. ({label})");
useFallback = false;
}
if (useFallback)
{
result = s_FallbackGraphicsFormat;
}
return result;
}
public static void SetGlobalKeyword(string keyword, bool enabled)
{
if (enabled)
{
Shader.EnableKeyword(keyword);
}
else
{
Shader.DisableKeyword(keyword);
}
}
public static void RenderTargetIdentifierXR(ref RenderTexture texture, ref RenderTargetIdentifier target)
{
target = new
(
texture,
mipLevel: 0,
CubemapFace.Unknown,
depthSlice: -1 // Bind all XR slices.
);
}
public static RenderTargetIdentifier RenderTargetIdentifierXR(int id) => new
(
id,
mipLevel: 0,
CubemapFace.Unknown,
depthSlice: -1 // Bind all XR slices.
);
///
/// Creates an RT reference and adds it to the RTI. Native object behind RT is not created so you can change its
/// properties before being used.
///
public static void CreateRenderTargetTextureReference(ref RenderTexture texture, ref RenderTargetIdentifier target)
{
// Do not overwrite reference or it will create reference leak.
if (texture == null)
{
// Dummy values. We are only creating an RT reference, not an RT native object. RT should be configured
// properly before using or calling Create.
texture = new(0, 0, 0);
}
// Always call this in case of recompilation as RTI will lose its reference to the RT.
RenderTargetIdentifierXR(ref texture, ref target);
}
///
/// Creates an RT with an RTD if it does not exist or assigns RTD to RT (RT should be released first). This
/// prevents reference leaks.
///
///
/// Afterwards call Create if
/// necessary or let Unity handle
/// it.
///
public static void SafeCreateRenderTexture(ref RenderTexture texture, RenderTextureDescriptor descriptor)
{
// Do not overwrite reference or it will create reference leak.
if (texture == null)
{
texture = new(descriptor);
}
else
{
if (texture.IsCreated())
{
texture.Release();
}
texture.descriptor = descriptor;
}
}
public static void SafeCreateRenderTexture(string name, ref RenderTexture texture, RenderTextureDescriptor descriptor)
{
// Do not overwrite reference or it will create reference leak.
if (texture == null)
{
texture = new(descriptor);
texture.name = name;
}
else
{
if (texture.IsCreated())
{
texture.Release();
}
texture.descriptor = descriptor;
}
texture.Create();
}
public static void ClearRenderTexture(RenderTexture texture, Color clear, bool depth = true, bool color = true)
{
var active = RenderTexture.active;
// Using RenderTexture.active will not write to all slices.
Graphics.SetRenderTarget(texture, 0, CubemapFace.Unknown, -1);
// TODO: Do we need to disable GL.sRGBWrite as it is linear to linear.
GL.Clear(depth, color, clear);
// Graphics.SetRenderTarget can be equivalent to setting RenderTexture.active:
// https://docs.unity3d.com/ScriptReference/Graphics.SetRenderTarget.html
// Restore previous active texture or it can incur a warning when releasing:
// Releasing render texture that is set to be RenderTexture.active!
RenderTexture.active = active;
}
public static void VerticallyFlipRenderTexture(RenderTexture target, bool force = false)
{
if (!force && !SystemInfo.graphicsUVStartsAtTop) return;
var temporary = RenderTexture.GetTemporary(target.descriptor);
Graphics.Blit(target, temporary, new Vector2(1, -1), new Vector2(0, 1));
Graphics.Blit(temporary, target);
RenderTexture.ReleaseTemporary(temporary);
}
public static bool RenderTargetTextureNeedsUpdating(RenderTexture texture, RenderTextureDescriptor descriptor)
{
return
descriptor.width != texture.width ||
descriptor.height != texture.height ||
descriptor.volumeDepth != texture.volumeDepth ||
descriptor.useDynamicScale != texture.useDynamicScale;
}
public static bool RenderTextureNeedsUpdating(RenderTexture t1, RenderTexture t2)
{
return
t1.width != t2.width ||
t1.height != t2.height ||
t1.volumeDepth != t2.volumeDepth ||
t1.graphicsFormat != t2.graphicsFormat;
}
public static bool RenderTextureNeedsUpdating(RenderTextureDescriptor t1, RenderTextureDescriptor t2)
{
return
t1.width != t2.width ||
t1.height != t2.height ||
t1.volumeDepth != t2.volumeDepth ||
t1.graphicsFormat != t2.graphicsFormat;
}
public static int CalculateMipMapCount(int maximumDimension)
{
return Mathf.FloorToInt(Mathf.Log(maximumDimension, 2f));
}
///
/// Uses Destroy in play mode or DestroyImmediate in edit mode.
///
public static void Destroy(Object @object, bool undo = false)
{
#if UNITY_EDITOR
// We must use DestroyImmediate in edit mode. As it apparently has an overhead, use recommended Destroy in
// play mode. DestroyImmediate is generally recommended in edit mode by Unity:
// https://docs.unity3d.com/ScriptReference/Object.DestroyImmediate.html
if (!Application.isPlaying)
{
if (undo)
{
UnityEditor.Undo.DestroyObjectImmediate(@object);
}
else
{
Object.DestroyImmediate(@object);
}
}
else
#endif
{
Object.Destroy(@object);
}
}
static readonly Matrix4x4 s_ScaleMatrix = Matrix4x4.Scale(new(1f, 1f, -1f));
// Borrowed from SRP code:
// https://github.com/Unity-Technologies/Graphics/blob/7d292932bec3b4257a4defaf698fc7d77e2027f5/com.unity.render-pipelines.high-definition/Runtime/Core/Utilities/GeometryUtils.cs#L181-L184
public static Matrix4x4 CalculateWorldToCameraMatrixRHS(Vector3 position, Quaternion rotation)
{
return s_ScaleMatrix * Matrix4x4.TRS(position, rotation, Vector3.one).inverse;
}
///
/// Blit using full screen triangle. Supports more features than CommandBuffer.Blit like the RenderPipeline tag
/// in sub-shaders. Never use for data.
///
public static void Blit(CommandBuffer buffer, RenderTargetIdentifier target, Material material, int pass = -1, MaterialPropertyBlock properties = null)
{
CoreUtils.SetRenderTarget(buffer, target);
buffer.DrawProcedural
(
Matrix4x4.identity,
material,
pass,
MeshTopology.Triangles,
vertexCount: 3,
instanceCount: 1,
properties
);
}
///
/// Blit using full screen triangle. Supports more features than CommandBuffer.Blit like the RenderPipeline tag
/// in sub-shaders. Never use for fullscreen effects.
///
public static void Blit(CommandBuffer buffer, RenderTexture target, Material material, int pass = -1, int depthSlice = -1, MaterialPropertyBlock properties = null)
{
buffer.SetRenderTarget(target, mipLevel: 0, CubemapFace.Unknown, depthSlice);
buffer.DrawProcedural
(
Matrix4x4.identity,
material,
pass,
MeshTopology.Triangles,
vertexCount: 3,
instanceCount: 1,
properties
);
}
// Fixes a bug with Unity, as we should not have to do this ourselves.
// Only required under the following conditions:
// Camera > URP Dynamic Resolution = checked
// URP Asset > Anti Aliasing (MSAA) = unchecked
// URP Asset > Upscale Filter = Spatial-Temporal Post-Processing
[System.Diagnostics.Conditional("d_UnityURP")]
public static void ScaleViewport(Camera camera, CommandBuffer buffer, RTHandle handle)
{
// Only applies to URP.
if (!RenderPipelineHelper.IsUniversal) return;
// Causes problems if we continue when this is checked.
if (camera.allowDynamicResolution) return;
var size = handle.GetScaledSize(handle.rtHandleProperties.currentViewportSize);
if (size == Vector2Int.zero) return;
buffer.SetViewport(new(0f, 0f, size.x, size.y));
}
public static void SetShaderVector(Material material, int nameID, Vector4 value, bool global = false)
{
if (global)
{
Shader.SetGlobalVector(nameID, value);
}
else
{
material.SetVector(nameID, value);
}
}
public static void SetShaderInteger(Material material, int nameID, int value, bool global = false)
{
if (global)
{
Shader.SetGlobalInteger(nameID, value);
}
else
{
material.SetInteger(nameID, value);
}
}
public static void SetShaderFloat(Material material, int nameID, float value, bool global = false)
{
if (global)
{
Shader.SetGlobalFloat(nameID, value);
}
else
{
material.SetFloat(nameID, value);
}
}
public static bool GetGlobalBoolean(int id)
{
return Shader.GetGlobalInteger(id) == 1;
}
public static void SetGlobalBoolean(int id, bool value)
{
Shader.SetGlobalInteger(id, value ? 1 : 0);
}
#if d_UnityURP
static readonly List s_RenderFeatureActiveStates = new();
static readonly FieldInfo s_RenderDataListField = typeof(UniversalRenderPipelineAsset)
.GetField("m_RendererDataList", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly FieldInfo s_DefaultRendererIndex = typeof(UniversalRenderPipelineAsset)
.GetField("m_DefaultRendererIndex", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly FieldInfo s_RendererIndex = typeof(UniversalAdditionalCameraData)
.GetField("m_RendererIndex", BindingFlags.NonPublic | BindingFlags.Instance);
internal static ScriptableRendererData[] UniversalRendererData(UniversalRenderPipelineAsset asset) =>
(ScriptableRendererData[])s_RenderDataListField.GetValue(asset);
internal static int GetRendererIndex(Camera camera)
{
var rendererIndex = (int)s_RendererIndex.GetValue(camera.GetUniversalAdditionalCameraData());
if (rendererIndex < 0)
{
rendererIndex = (int)s_DefaultRendererIndex.GetValue(UniversalRenderPipeline.asset);
}
return rendererIndex;
}
internal static bool IsSSAOEnabled(Camera camera)
{
// Get this every time as it could change.
var renderers = (ScriptableRendererData[])s_RenderDataListField.GetValue(UniversalRenderPipeline.asset);
var rendererIndex = GetRendererIndex(camera);
foreach (var feature in renderers[rendererIndex].rendererFeatures)
{
if (feature.GetType().Name == "ScreenSpaceAmbientOcclusion")
{
return feature.isActive;
}
}
return false;
}
internal static void RenderCameraWithoutCustomPasses(Camera camera)
{
// Get this every time as it could change.
var renderers = (ScriptableRendererData[])s_RenderDataListField.GetValue(UniversalRenderPipeline.asset);
var rendererIndex = GetRendererIndex(camera);
foreach (var feature in renderers[rendererIndex].rendererFeatures)
{
// Null exception reported here. Might be null due to missing render features
if (feature == null) continue;
s_RenderFeatureActiveStates.Add(feature.isActive);
feature.SetActive(false);
}
camera.Render();
var index = 0;
foreach (var feature in renderers[rendererIndex].rendererFeatures)
{
if (feature == null) continue;
feature.SetActive(s_RenderFeatureActiveStates[index++]);
}
s_RenderFeatureActiveStates.Clear();
}
static readonly UniversalRenderPipeline.SingleCameraRequest s_RenderSingleCameraRequest = new();
#endif
static readonly UnityEngine.Rendering.RenderPipeline.StandardRequest s_RenderStandardRequest = new();
public static void RenderCamera(Camera camera, ScriptableRenderContext context, int slice)
{
#if d_UnityURP
if (RenderPipelineHelper.IsUniversal)
{
#if UNITY_6000_0_OR_NEWER
// SingleCameraRequest does not render the full camera stack, thus should exclude
// overlays which is likely desirable. Alternative approach worth investigating:
// https://docs.unity3d.com/6000.0/Documentation/Manual/urp/User-Render-Requests.html
// Setting destination silences a warning if Opaque Texture enabled.
s_RenderSingleCameraRequest.destination = camera.targetTexture;
s_RenderSingleCameraRequest.slice = slice;
UnityEngine.Rendering.RenderPipeline.SubmitRenderRequest(camera, s_RenderSingleCameraRequest);
#else
#pragma warning disable CS0618 // Type or member is obsolete
UniversalRenderPipeline.RenderSingleCamera(context, camera);
#pragma warning restore CS0618 // Type or member is obsolete
#endif
return;
}
#endif
#if d_UnityHDRP
if (RenderPipelineHelper.IsHighDefinition)
{
camera.SubmitRenderRequest(s_RenderStandardRequest);
return;
}
#endif
camera.Render();
}
}
// Undo
static partial class Helpers
{
public static class Undo
{
static class Symbols
{
public const string k_UnityEditor = "UNITY_EDITOR";
}
[System.Diagnostics.Conditional(Symbols.k_UnityEditor)]
public static void RecordObject(Object @object, string label)
{
#if UNITY_EDITOR
UnityEditor.Undo.RecordObject(@object, label);
#endif
}
[System.Diagnostics.Conditional(Symbols.k_UnityEditor)]
public static void SetSiblingIndex(Transform transform, int index, string label)
{
#if UNITY_EDITOR
UnityEditor.Undo.SetSiblingIndex(transform, index, label);
#endif
}
[System.Diagnostics.Conditional(Symbols.k_UnityEditor)]
public static void RegisterCreatedObjectUndo(Object @object, string label)
{
#if UNITY_EDITOR
UnityEditor.Undo.RegisterCreatedObjectUndo(@object, label);
#endif
}
}
}
// Terrain
#if d_Unity_Terrain
static partial class Helpers
{
static readonly List s_Terrains = new();
internal static Terrain GetTerrainAtPosition(Vector2 position)
{
Terrain.GetActiveTerrains(s_Terrains);
foreach (var terrain in s_Terrains)
{
if (terrain.terrainData == null)
{
continue;
}
var rect = new Rect(terrain.transform.position.XZ(), terrain.terrainData.size.XZ());
// Return the first one.
if (rect.Contains(position))
{
return terrain;
}
}
return null;
}
}
#endif // d_Unity_Terrain
namespace Internal
{
static class Extensions
{
// Swizzle
public static Vector2 XZ(this Vector3 v) => new(v.x, v.z);
public static Vector2 XY(this Vector4 v) => new(v.x, v.y);
public static Vector2 ZW(this Vector4 v) => new(v.z, v.w);
public static Vector3 XYZ(this Vector4 v) => new(v.x, v.y, v.z);
public static Vector3 XNZ(this Vector2 v, float n = 0f) => new(v.x, n, v.y);
public static Vector3 XNZ(this Vector3 v, float n = 0f) => new(v.x, n, v.z);
public static Vector3 XNN(this Vector3 v, float n = 0f) => new(v.x, n, n);
public static Vector3 NNZ(this Vector3 v, float n = 0f) => new(n, n, v.z);
public static Vector3 NYN(this Vector3 v, float n = 0f) => new(n, v.y, n);
public static Vector4 XYZN(this Vector3 v, float n = 0f) => new(v.x, v.y, v.z, n);
public static Vector4 XYNN(this Vector2 v, float n = 0f) => new(v.x, v.y, n, n);
public static Vector4 XYNN(this Vector2 v, Vector2 n) => new(v.x, v.y, n.x, n.y);
public static Vector4 NNZW(this Vector2 v, float n = 0f) => new(n, n, v.x, v.y);
public static Vector4 XNZW(this Vector4 v, float n) => new(v.x, n, v.z, v.w);
public static float Maximum(this Vector3 v) => Mathf.Max(Mathf.Max(v.x, v.y), v.z);
public static Vector2 Absolute(this Vector2 v) => new
(
Mathf.Abs(v.x),
Mathf.Abs(v.y)
);
public static Color Clamped01(this Color c) => new
(
Mathf.Clamp01(c.r),
Mathf.Clamp01(c.g),
Mathf.Clamp01(c.b),
Mathf.Clamp01(c.a)
);
public static void SetKeyword(this Material material, string keyword, bool enabled)
{
if (enabled)
{
material.EnableKeyword(keyword);
}
else
{
material.DisableKeyword(keyword);
}
}
public static void SetKeyword(this ComputeShader shader, string keyword, bool enabled)
{
if (enabled)
{
shader.EnableKeyword(keyword);
}
else
{
shader.DisableKeyword(keyword);
}
}
public static void SetShaderKeyword(this CommandBuffer buffer, string keyword, bool enabled)
{
if (enabled)
{
buffer.EnableShaderKeyword(keyword);
}
else
{
buffer.DisableShaderKeyword(keyword);
}
}
static readonly Vector3[] s_BoundsPoints = new Vector3[8];
public static Bounds Bounds(this Transform transform)
{
var bounds = new Bounds();
bounds.center = transform.position;
var f = new Vector3(0.0f, 0.0f, 0.5f);
var u = new Vector3(0.0f, 0.5f, 0.0f);
var r = new Vector3(0.5f, 0.0f, 0.0f);
bounds.Encapsulate(transform.TransformPoint(f + u + r));
bounds.Encapsulate(transform.TransformPoint(-f + u + r));
bounds.Encapsulate(transform.TransformPoint(f + -u + r));
bounds.Encapsulate(transform.TransformPoint(f + u + -r));
bounds.Encapsulate(transform.TransformPoint(-f + -u + r));
bounds.Encapsulate(transform.TransformPoint(f + -u + -r));
bounds.Encapsulate(transform.TransformPoint(-f + u + -r));
bounds.Encapsulate(transform.TransformPoint(-f + -u + -r));
return bounds;
}
///
/// Applys the transform to local bounds similar to Renderer.
///
/// The transform to apply to bounds.
/// Local bounds to transform.
/// Bounds with transform applied.
public static Bounds TransformBounds(this Transform transform, Bounds bounds)
{
s_BoundsPoints[0] = bounds.min;
s_BoundsPoints[1] = bounds.max;
s_BoundsPoints[2] = new(bounds.min.x, bounds.min.y, bounds.max.z);
s_BoundsPoints[3] = new(bounds.min.x, bounds.max.y, bounds.min.z);
s_BoundsPoints[4] = new(bounds.max.x, bounds.min.y, bounds.min.z);
s_BoundsPoints[5] = new(bounds.min.x, bounds.max.y, bounds.max.z);
s_BoundsPoints[6] = new(bounds.max.x, bounds.min.y, bounds.max.z);
s_BoundsPoints[7] = new(bounds.max.x, bounds.max.y, bounds.min.z);
return GeometryUtility.CalculateBounds(s_BoundsPoints, transform.localToWorldMatrix);
}
public static bool IntersectsXZ(this Bounds a, Bounds b)
{
return a.min.x <= b.max.x && a.max.x >= b.min.x &&
a.min.z <= b.max.z && a.max.z >= b.min.z;
}
public static Rect RectXZ(this Bounds bounds)
{
return Rect.MinMaxRect(bounds.min.x, bounds.min.z, bounds.max.x, bounds.max.z);
}
public static Rect RectXZ(this Transform transform)
{
var scale = transform.lossyScale.XZ();
scale = Helpers.RotateAndEncapsulateXZ(scale, transform.rotation.eulerAngles.y);
return new(transform.position.XZ() - scale * 0.5f, scale);
}
public static Vector2 RotationXZ(this Transform transform)
{
return new Vector2(transform.localToWorldMatrix.m20, transform.localToWorldMatrix.m00).normalized;
}
public static Color MaybeLinear(this Color color)
{
return QualitySettings.activeColorSpace == ColorSpace.Linear ? color.linear : color;
}
public static Color MaybeGamma(this Color color)
{
return QualitySettings.activeColorSpace == ColorSpace.Linear ? color : color.gamma;
}
public static Color FinalColor(this Light light)
{
var linear = GraphicsSettings.lightsUseLinearIntensity;
var color = linear ? light.color.linear : light.color;
color *= light.intensity;
if (linear && light.useColorTemperature) color *= Mathf.CorrelatedColorTemperatureToRGB(light.colorTemperature);
if (!linear) color = color.MaybeLinear();
return linear ? color.MaybeGamma() : color;
}
///
/// Sets the msaaSamples property to the highest supported MSAA level in the settings.
///
public static void SetMSAASamples(this ref RenderTextureDescriptor descriptor, Camera camera)
{
// QualitySettings.antiAliasing is zero when disabled which is invalid for msaaSamples.
// We need to set this first as GetRenderTextureSupportedMSAASampleCount uses it:
// https://docs.unity3d.com/ScriptReference/SystemInfo.GetRenderTextureSupportedMSAASampleCount.html
descriptor.msaaSamples = Helpers.IsMSAAEnabled(camera) ? Mathf.Max(QualitySettings.antiAliasing, 1) : 1;
descriptor.msaaSamples = SystemInfo.GetRenderTextureSupportedMSAASampleCount(descriptor);
}
// RT descriptor from texture base class.
internal static RenderTextureDescriptor GetDescriptor(this Texture texture)
{
if (texture is RenderTexture rt)
{
return rt.descriptor;
}
var descriptor = new RenderTextureDescriptor(0, 0)
{
width = texture.width,
height = texture.height,
graphicsFormat = texture.graphicsFormat,
dimension = texture.dimension,
volumeDepth = 1,
msaaSamples = 1,
useMipMap = false,
enableRandomWrite = true,
};
return descriptor;
}
public static bool GetBoolean(this Material material, int id)
{
return (material.HasInteger(id) ? material.GetInteger(id) : material.GetInt(id)) != 0;
}
public static void SetBoolean(this Material material, int id, bool value)
{
if (material.HasInteger(id))
{
material.SetInteger(id, value ? 1 : 0);
}
else
{
material.SetInt(id, value ? 1 : 0);
}
}
public static void SetGlobalBoolean(this CommandBuffer buffer, int id, bool value)
{
buffer.SetGlobalInteger(id, value ? 1 : 0);
}
public static bool IsEmpty(this UnityEngine.Events.UnityEvent @event)
{
return @event.GetPersistentEventCount() == 0;
}
public static bool IsEmpty(this UnityEngine.Events.UnityEvent @event)
{
return @event.GetPersistentEventCount() == 0;
}
}
}
namespace Utility
{
///
/// Puts together a hash from given data values. More deterministic than
/// System.HashCode across recompiles.
///
static class Hash
{
public static int CreateHash() => 0x19384567;
public static void AddFloat(float value, ref int hash)
{
// Appears to be deterministic across recompiles.
hash ^= value.GetHashCode();
}
public static void AddInt(int value, ref int hash)
{
hash ^= value;
}
public static void AddBool(bool value, ref int hash)
{
hash ^= value ? 0x74659374 : 0x62649035;
}
public static void AddObject(object value, ref int hash)
{
// Will be the index of this object instance
hash ^= value.GetHashCode();
}
public static void AddObject(T value, ref int hash) where T : struct
{
// Will be the index of this object instance
hash ^= value.GetHashCode();
}
}
}
}