// 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(); } } } }