备份CatanBuilding瘦身独立工程
This commit is contained in:
139
LocalPackages/StylizedWater2/Runtime/AlignToWaves.cs
Normal file
139
LocalPackages/StylizedWater2/Runtime/AlignToWaves.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
//Stylized Water 2
|
||||
//Staggart Creations (http://staggart.xyz)
|
||||
//Copyright protected under Unity Asset Store EULA
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace StylizedWater2
|
||||
{
|
||||
[ExecuteInEditMode]
|
||||
[AddComponentMenu("Stylized Water 2/Align Transform To Waves")]
|
||||
public class AlignToWaves : MonoBehaviour
|
||||
{
|
||||
[Tooltip("This reference is required to grab the wave distance and height values")]
|
||||
public WaterObject waterObject;
|
||||
[Tooltip("Automatically find the Water Object below of above the Transform's position. This is slower than assigning a specific Water Object directly.")]
|
||||
public bool autoFind;
|
||||
[Tooltip("Only enable if the material's wave parameters are being changed in realtime, this has some performance overhead.\n\nIn edit-mode, the wave parameters are always fetched, so changes are directly visible")]
|
||||
public bool dynamicMaterial;
|
||||
|
||||
public enum WaterLevelSource
|
||||
{
|
||||
FixedValue,
|
||||
WaterObject
|
||||
}
|
||||
[Tooltip("Configure what should be used to set the base water level. Relative wave height is added to this value")]
|
||||
public WaterLevelSource waterLevelSource = WaterLevelSource.WaterObject;
|
||||
public float waterLevel;
|
||||
[Tooltip("You can assign a child mesh object here. When assigned, the sample points will rotate/scale with the transform, instead of transform the component is attached to.")]
|
||||
public Transform childTransform;
|
||||
|
||||
public float heightOffset;
|
||||
[Min(0)]
|
||||
[Tooltip("Controls how strongly the transform should rotate to align with the wave curvature")]
|
||||
public float rollAmount = 0.1f;
|
||||
|
||||
public List<Vector3> samples = new List<Vector3>();
|
||||
|
||||
private Vector3 normal;
|
||||
private float height;
|
||||
private float m_waterLevel = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Global toggle to disable the animations. This is used to temporarily disable all instances when editing a prefab, or sample positions in the editor
|
||||
/// </summary>
|
||||
public static bool Disable;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public static bool EnableInEditor
|
||||
{
|
||||
get { return UnityEditor.EditorPrefs.GetBool("SWS2_BUOYANCY_EDITOR_ENABLED", true); }
|
||||
set { UnityEditor.EditorPrefs.SetBool("SWS2_BUOYANCY_EDITOR_ENABLED", value); }
|
||||
}
|
||||
#endif
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnEnable()
|
||||
{
|
||||
UnityEditor.EditorApplication.update += FixedUpdate;
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
//Auto-assign water object if there is only one
|
||||
if (waterObject == null && WaterObject.Instances.Count > 0)
|
||||
{
|
||||
waterObject = WaterObject.Instances[0];
|
||||
UnityEditor.EditorUtility.SetDirty(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
UnityEditor.EditorApplication.update -= FixedUpdate;
|
||||
}
|
||||
#endif
|
||||
|
||||
public void FixedUpdate()
|
||||
{
|
||||
if (!this || !this.enabled || Disable) return;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!EnableInEditor && Application.isPlaying == false) return;
|
||||
#endif
|
||||
|
||||
if(autoFind) waterObject = WaterObject.Find(this.transform.position, false);
|
||||
|
||||
if (!waterObject || !waterObject.material) return;
|
||||
|
||||
m_waterLevel = waterObject && waterLevelSource == WaterLevelSource.WaterObject? waterObject.transform.position.y : waterLevel;
|
||||
|
||||
normal = Vector3.up;
|
||||
height = 0f;
|
||||
if (samples.Count == 0)
|
||||
{
|
||||
height = Buoyancy.SampleWaves(this.transform.position, waterObject.material, m_waterLevel, rollAmount, dynamicMaterial, out normal);
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector3 avgNormal = Vector3.zero;
|
||||
for (int i = 0; i < samples.Count; i++)
|
||||
{
|
||||
height += Buoyancy.SampleWaves(ConvertToWorldSpace(samples[i]), waterObject.material, m_waterLevel, rollAmount, dynamicMaterial, out normal);
|
||||
avgNormal += normal;
|
||||
}
|
||||
|
||||
height /= samples.Count;
|
||||
normal = (avgNormal / samples.Count).normalized;
|
||||
}
|
||||
|
||||
height += heightOffset;
|
||||
|
||||
ApplyTransform();
|
||||
}
|
||||
|
||||
private void ApplyTransform()
|
||||
{
|
||||
if(rollAmount > 0) this.transform.up = normal;
|
||||
|
||||
var position = this.transform.position;
|
||||
this.transform.position = new Vector3(position.x, height, position.z);
|
||||
}
|
||||
|
||||
public Vector3 ConvertToWorldSpace(Vector3 position)
|
||||
{
|
||||
if (childTransform) return childTransform.TransformPoint(position);
|
||||
|
||||
return this.transform.TransformPoint(position);
|
||||
}
|
||||
|
||||
public Vector3 ConvertToLocalSpace(Vector3 position)
|
||||
{
|
||||
if (childTransform) return childTransform.InverseTransformPoint(position);
|
||||
|
||||
return this.transform.InverseTransformPoint(position);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
11
LocalPackages/StylizedWater2/Runtime/AlignToWaves.cs.meta
Normal file
11
LocalPackages/StylizedWater2/Runtime/AlignToWaves.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75215afeb99f1fc48aacad291a415f32
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 148978298399363526, guid: 0000000000000000d000000000000000, type: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
297
LocalPackages/StylizedWater2/Runtime/Buoyancy.cs
Normal file
297
LocalPackages/StylizedWater2/Runtime/Buoyancy.cs
Normal file
@@ -0,0 +1,297 @@
|
||||
//Stylized Water 2
|
||||
//Staggart Creations (http://staggart.xyz)
|
||||
//Copyright protected under Unity Asset Store EULA
|
||||
|
||||
//#undef MATHEMATICS
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
|
||||
#if MATHEMATICS
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using Vector4 = Unity.Mathematics.float4;
|
||||
using Vector3 = Unity.Mathematics.float3;
|
||||
using Vector2 = Unity.Mathematics.float2;
|
||||
#endif
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace StylizedWater2
|
||||
{
|
||||
public static partial class Buoyancy
|
||||
{
|
||||
private static WaveParameters waveParameters = new WaveParameters();
|
||||
private static Material lastMaterial;
|
||||
|
||||
private static readonly int TimeParametersID = Shader.PropertyToID("_TimeParameters");
|
||||
|
||||
private static void GetMaterialParameters(Material mat)
|
||||
{
|
||||
waveParameters.Update(mat);
|
||||
}
|
||||
|
||||
//Returns the same value as _TimeParameters.x
|
||||
private static float _TimeParameters
|
||||
{
|
||||
get
|
||||
{
|
||||
if (WaterObject.CustomTime >= 0) return WaterObject.CustomTime;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
return Application.isPlaying ? Time.time : Shader.GetGlobalVector(TimeParametersID).x;
|
||||
#else
|
||||
return Time.time;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Set the static 'WaterObject.CustomTime' parameter instead.", false)]
|
||||
public static void SetCustomTime(float value)
|
||||
{
|
||||
WaterObject.CustomTime = value;
|
||||
}
|
||||
|
||||
private static Vector4 sine;
|
||||
private static Vector4 cosine;
|
||||
private static Vector4 dotABCD;
|
||||
private static Vector4 AB;
|
||||
private static Vector4 CD;
|
||||
private static Vector4 direction1;
|
||||
private static Vector4 direction2;
|
||||
private static Vector4 TIME;
|
||||
private static Vector2 planarPosition;
|
||||
|
||||
private static Vector4 amp = new Vector4(0.3f, 0.35f, 0.25f, 0.25f);
|
||||
private static Vector4 freq = new Vector4(1.3f, 1.35f, 1.25f, 1.25f);
|
||||
private static Vector4 speed = new Vector4(1.2f, 1.375f, 1.1f, 1);
|
||||
private static Vector4 dir1 = new Vector4(0.3f, 0.85f, 0.85f, 0.25f);
|
||||
private static Vector4 dir2 = new Vector4(0.1f, 0.9f, -0.5f, -0.5f);
|
||||
private static Vector4 steepness = new Vector4(12f,12f,12f,12f);
|
||||
|
||||
//Real frequency value per wave layer
|
||||
private static Vector4 frequency;
|
||||
//Output
|
||||
private static Vector3 offsets;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a position in world-space, where a ray cast from the origin in the direction hits the (flat) water level height
|
||||
/// </summary>
|
||||
/// <param name="origin"></param>
|
||||
/// <param name="direction"></param>
|
||||
/// <param name="waterLevel">Water level height in world-space</param>
|
||||
/// <returns></returns>
|
||||
public static Vector3 FindWaterLevelIntersection(Vector3 origin, Vector3 direction, float waterLevel)
|
||||
{
|
||||
#if MATHEMATICS
|
||||
float upDot = dot(direction, UnityEngine.Vector3.up);
|
||||
float angle = (Mathf.Acos(upDot) * 180f) / Mathf.PI;
|
||||
|
||||
float depth = waterLevel - origin.y;
|
||||
//Distance from origin to water level along direction
|
||||
float hypotenuse = depth / cos(Mathf.Deg2Rad * angle);
|
||||
|
||||
return origin + (direction * hypotenuse);
|
||||
#else
|
||||
return Vector3.zero;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Faux-raycast against the water surface
|
||||
/// </summary>
|
||||
/// <param name="waterObject">Water object component, used to get the water material and level (height)</param>
|
||||
/// <param name="origin">Ray origin</param>
|
||||
/// <param name="direction">Ray direction</param>
|
||||
/// <param name="dynamicMaterial">If true, the material's wave parameters will be re-fetched with every function call</param>
|
||||
/// <param name="hit">Reference to a RaycastHit, hit point and normal will be set</param>
|
||||
public static void Raycast(WaterObject waterObject, Vector3 origin, Vector3 direction, bool dynamicMaterial, out RaycastHit hit)
|
||||
{
|
||||
Raycast(waterObject.material, waterObject.transform.position.y, origin, direction, dynamicMaterial, out hit);
|
||||
}
|
||||
|
||||
private static RaycastHit hit = new RaycastHit();
|
||||
/// <summary>
|
||||
/// Faux-raycast against the water surface
|
||||
/// </summary>
|
||||
/// <param name="waterMat">Material using StylizedWater2 shader</param>
|
||||
/// <param name="waterLevel">Height of the reference water plane.</param>
|
||||
/// <param name="origin">Ray origin</param>
|
||||
/// <param name="direction">Ray direction</param>
|
||||
/// <param name="dynamicMaterial">If true, the material's wave parameters will be re-fetched with every function call</param>
|
||||
/// <param name="hit">Reference to a RaycastHit, hit point and normal will be set</param>
|
||||
public static void Raycast(Material waterMat, float waterLevel, Vector3 origin, Vector3 direction, bool dynamicMaterial, out RaycastHit hit)
|
||||
{
|
||||
Vector3 samplePos = FindWaterLevelIntersection(origin, direction, waterLevel);
|
||||
|
||||
float waveHeight = SampleWaves(samplePos, waterMat, waterLevel, 1f, dynamicMaterial, out var normal);
|
||||
samplePos.y = waveHeight;
|
||||
|
||||
hit = Buoyancy.hit;
|
||||
hit.normal = normal;
|
||||
hit.point = samplePos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a position in world-space, returns the wave height and normal
|
||||
/// </summary>
|
||||
/// <param name="position">Sample position in world-space</param>
|
||||
/// <param name="waterObject">Water object component, used to get the water material and level (height)</param>
|
||||
/// <param name="rollStrength">Multiplier for the the normal strength</param>
|
||||
/// <param name="dynamicMaterial">If true, the material's wave parameters will be re-fetched with every function call</param>
|
||||
/// <param name="normal">Output upwards normal vector, perpendicular to the wave</param>
|
||||
/// <returns>Wave height, in world-space.</returns>
|
||||
public static float SampleWaves(UnityEngine.Vector3 position, WaterObject waterObject, float rollStrength, bool dynamicMaterial, out UnityEngine.Vector3 normal)
|
||||
{
|
||||
return SampleWaves(position, waterObject.material, waterObject.transform.position.y, rollStrength, dynamicMaterial, out normal);
|
||||
}
|
||||
|
||||
private static void RecalculateParameters()
|
||||
{
|
||||
#if MATHEMATICS
|
||||
direction1 = dir1 * waveParameters.direction;
|
||||
direction2 = dir2 * waveParameters.direction;
|
||||
|
||||
frequency = freq * (1-waveParameters.distance) * 3f;
|
||||
|
||||
AB.x = steepness.x * waveParameters.steepness * direction1.x * amp.x;
|
||||
AB.y = steepness.x * waveParameters.steepness * direction1.y * amp.x;
|
||||
AB.z = steepness.x * waveParameters.steepness * direction1.z * amp.y;
|
||||
AB.w = steepness.x * waveParameters.steepness * direction1.w * amp.y;
|
||||
|
||||
CD.x = steepness.z * waveParameters.steepness * direction2.x * amp.z;
|
||||
CD.y = steepness.z * waveParameters.steepness * direction2.y * amp.z;
|
||||
CD.z = steepness.w * waveParameters.steepness * direction2.z * amp.w;
|
||||
CD.w = steepness.w * waveParameters.steepness * direction2.w * amp.w;
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void SampleWaves(UnityEngine.Vector3 position, Material waterMat, float waterLevel, float rollStrength, bool dynamicMaterial, out UnityEngine.Vector3 offset, out UnityEngine.Vector3 normal)
|
||||
{
|
||||
Profiler.BeginSample("Buoyancy sampling");
|
||||
|
||||
#if MATHEMATICS
|
||||
//If not desired to re-fetch the material properties every call, at least fetch them if the input material changed (since this is a static function)
|
||||
//In edit-mode, always do this as materials are most likely modified then
|
||||
if(!dynamicMaterial && Application.isPlaying)
|
||||
{
|
||||
//Fetch the material's wave parameters, so the exact calculations can be mirrored
|
||||
if (lastMaterial == null || lastMaterial.Equals(waterMat) == false)
|
||||
{
|
||||
#if SWS_DEV
|
||||
Debug.Log("SampleWaves: water material changed, re-fetching parameters");
|
||||
#endif
|
||||
|
||||
GetMaterialParameters(waterMat);
|
||||
|
||||
lastMaterial = waterMat;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GetMaterialParameters(waterMat);
|
||||
}
|
||||
|
||||
TIME = (_TimeParameters * -waveParameters.animationSpeed * waveParameters.speed * speed);
|
||||
|
||||
RecalculateParameters();
|
||||
|
||||
offsets = Vector3.zero;
|
||||
|
||||
planarPosition.x = position.x - WaterObject.PositionOffset.x;
|
||||
planarPosition.y = position.z - WaterObject.PositionOffset.z;
|
||||
|
||||
for (int i = 0; i <= waveParameters.count; i++)
|
||||
{
|
||||
var t = 1f+((float)i / (float)waveParameters.count);
|
||||
|
||||
frequency *= t;
|
||||
|
||||
#if MATHEMATICS
|
||||
dotABCD.x = dot(direction1.xy, planarPosition) * frequency.x;
|
||||
dotABCD.y = dot(direction1.zw, planarPosition) * frequency.y;
|
||||
dotABCD.z = dot(direction2.xy, planarPosition) * frequency.z;
|
||||
dotABCD.w = dot(direction2.zw, planarPosition) * frequency.w;
|
||||
#endif
|
||||
|
||||
sine.x = sin(dotABCD.x + TIME.x);
|
||||
sine.y = sin(dotABCD.y + TIME.y);
|
||||
sine.z = sin(dotABCD.z + TIME.z);
|
||||
sine.w = sin(dotABCD.w + TIME.w);
|
||||
|
||||
cosine.x = cos(dotABCD.x + TIME.x);
|
||||
cosine.y = cos(dotABCD.y + TIME.y);
|
||||
cosine.z = cos(dotABCD.z + TIME.z);
|
||||
cosine.w = cos(dotABCD.w + TIME.w);
|
||||
|
||||
offsets.x += dot(cosine, new Vector4(AB.x, AB.z, CD.x, CD.z));
|
||||
offsets.y += dot(sine, amp);
|
||||
offsets.z += dot(cosine, new Vector4(AB.y, AB.w, CD.y, CD.w));
|
||||
}
|
||||
|
||||
rollStrength *= lerp(0.001f, 0.1f, waveParameters.steepness);
|
||||
|
||||
normal.x = -offsets.x * rollStrength * waveParameters.height;
|
||||
normal.y = 2f;
|
||||
normal.z = -offsets.z * rollStrength * waveParameters.height;
|
||||
|
||||
normal = normalize(normal);
|
||||
|
||||
//Average height
|
||||
offsets.y /= waveParameters.count;
|
||||
offsets.y = (offsets.y* waveParameters.height) + waterLevel;
|
||||
|
||||
offset = offsets;
|
||||
#else
|
||||
offset = Vector3.zero;
|
||||
normal = Vector3.zero;
|
||||
#endif
|
||||
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
private static UnityEngine.Vector3 m_offset;
|
||||
/// <summary>
|
||||
/// Given a position in world-space, returns the wave height and normal
|
||||
/// </summary>
|
||||
/// <param name="position">Sample position in world-space</param>
|
||||
/// <param name="waterMat">Material using StylizedWater2 shader</param>
|
||||
/// <param name="waterLevel">Height of the reference water plane.</param>
|
||||
/// <param name="rollStrength">Multiplier for the the normal strength</param>
|
||||
/// <param name="dynamicMaterial">If true, the material's wave parameters will be re-fetched with every function call</param>
|
||||
/// <param name="normal">Output upwards normal vector, perpendicular to the wave</param>
|
||||
/// <returns>Wave height, in world-space.</returns>
|
||||
public static float SampleWaves(UnityEngine.Vector3 position, Material waterMat, float waterLevel, float rollStrength, bool dynamicMaterial, out UnityEngine.Vector3 normal)
|
||||
{
|
||||
SampleWaves(position, waterMat, waterLevel, rollStrength, dynamicMaterial, out m_offset, out normal);
|
||||
|
||||
return m_offset.y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the position is below the maximum possible wave height. Can be used as a fast broad-phase check, before actually using the more expensive SampleWaves function
|
||||
/// </summary>
|
||||
/// <param name="position"></param>
|
||||
/// <param name="waterObject"></param>
|
||||
/// <returns></returns>
|
||||
public static bool CanTouchWater(Vector3 position, WaterObject waterObject)
|
||||
{
|
||||
if (!waterObject) return false;
|
||||
|
||||
return position.y < (waterObject.transform.position.y + WaveParameters.GetMaxWaveHeight(waterObject.material));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the position is below the maximum possible wave height. Can be used as a fast broad-phase check, before actually using the more expensive SampleWaves function
|
||||
/// </summary>
|
||||
public static bool CanTouchWater(Vector3 position, Material waterMaterial, float waterLevel)
|
||||
{
|
||||
return position.y < (waterLevel + WaveParameters.GetMaxWaveHeight(waterMaterial));
|
||||
}
|
||||
}
|
||||
}
|
||||
11
LocalPackages/StylizedWater2/Runtime/Buoyancy.cs.meta
Normal file
11
LocalPackages/StylizedWater2/Runtime/Buoyancy.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7991692bea09cb4f9aae01639d1d1e6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
649
LocalPackages/StylizedWater2/Runtime/PipelineUtilities.cs
Normal file
649
LocalPackages/StylizedWater2/Runtime/PipelineUtilities.cs
Normal file
@@ -0,0 +1,649 @@
|
||||
//Stylized Water 2
|
||||
//Staggart Creations (http://staggart.xyz)
|
||||
//Copyright protected under Unity Asset Store EULA
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using Debug = UnityEngine.Debug;
|
||||
#if URP
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
#if !UNITY_2021_2_OR_NEWER
|
||||
using UniversalRendererData = UnityEngine.Rendering.Universal.ForwardRendererData;
|
||||
#endif
|
||||
|
||||
using ScriptableRendererFeature = UnityEngine.Rendering.Universal.ScriptableRendererFeature;
|
||||
#endif
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace StylizedWater2
|
||||
{
|
||||
//Stay awesome Unity, locking everything behind internal UI code just makes things convoluted.
|
||||
public static class PipelineUtilities
|
||||
{
|
||||
private const string renderDataListFieldName = "m_RendererDataList";
|
||||
private const string renderFeaturesListFieldName = "m_RendererFeatures";
|
||||
private const string defaultRendererIndexFieldName = "m_DefaultRendererIndex";
|
||||
|
||||
#if URP
|
||||
public static ScriptableRendererData[] GetRenderDataList(UniversalRenderPipelineAsset asset)
|
||||
{
|
||||
FieldInfo renderDataListField = typeof(UniversalRenderPipelineAsset).GetField(renderDataListFieldName, BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
if (renderDataListField != null)
|
||||
{
|
||||
return (ScriptableRendererData[])renderDataListField.GetValue(asset);
|
||||
}
|
||||
|
||||
throw new Exception("Reflection failed on field \"m_RendererDataList\" from class \"UniversalRenderPipelineAsset\". URP API likely changed");
|
||||
}
|
||||
|
||||
public static void RefreshRendererList()
|
||||
{
|
||||
if (UniversalRenderPipeline.asset == null)
|
||||
{
|
||||
Debug.LogError("No pipeline is active, do not display UI that uses this function if it isn't!");
|
||||
}
|
||||
|
||||
ScriptableRendererData[] m_rendererDataList = GetRenderDataList(UniversalRenderPipeline.asset);
|
||||
|
||||
//Display names
|
||||
_rendererDisplayList = new GUIContent[m_rendererDataList.Length+1];
|
||||
|
||||
int defaultIndex = GetDefaultRendererIndex(UniversalRenderPipeline.asset);
|
||||
_rendererDisplayList[0] = new GUIContent($"Default ({(m_rendererDataList[defaultIndex].name)})");
|
||||
|
||||
for (int i = 1; i < _rendererDisplayList.Length; i++)
|
||||
{
|
||||
if (m_rendererDataList[i - 1] != null)
|
||||
{
|
||||
_rendererDisplayList[i] = new GUIContent($"{(i - 1).ToString()}: {(m_rendererDataList[i - 1]).name}");
|
||||
}
|
||||
else
|
||||
{
|
||||
_rendererDisplayList[i] = new GUIContent("(Missing)");
|
||||
}
|
||||
}
|
||||
|
||||
//Indices
|
||||
_rendererIndexList = new int[m_rendererDataList.Length+1];
|
||||
for (int i = 0; i < _rendererIndexList.Length; i++)
|
||||
{
|
||||
_rendererIndexList[i] = i-1;
|
||||
}
|
||||
}
|
||||
|
||||
private static GUIContent[] _rendererDisplayList;
|
||||
public static GUIContent[] rendererDisplayList
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_rendererDisplayList == null) RefreshRendererList();
|
||||
return _rendererDisplayList;
|
||||
}
|
||||
}
|
||||
|
||||
private static int[] _rendererIndexList;
|
||||
public static int[] rendererIndexList
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_rendererIndexList == null) RefreshRendererList();
|
||||
return _rendererIndexList;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a renderer index, validates if there is actually a renderer at the index. Otherwise returns the index of the default renderer.
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <returns></returns>
|
||||
public static int ValidateRenderer(int index)
|
||||
{
|
||||
if (UniversalRenderPipeline.asset)
|
||||
{
|
||||
int defaultRendererIndex = GetDefaultRendererIndex(UniversalRenderPipeline.asset);
|
||||
ScriptableRendererData[] m_rendererDataList = GetRenderDataList(UniversalRenderPipeline.asset);
|
||||
|
||||
//-1 is used to indicate the default renderer
|
||||
if (index == -1) index = defaultRendererIndex;
|
||||
|
||||
//Check if any renderer exists at the current index
|
||||
if (!(index < m_rendererDataList.Length && m_rendererDataList[index] != null))
|
||||
{
|
||||
Debug.LogWarning($"Renderer at <b>index {index.ToString()}</b> is missing, falling back to Default Renderer. <b>{m_rendererDataList[defaultRendererIndex].name}</b>", UniversalRenderPipeline.asset);
|
||||
return defaultRendererIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Valid
|
||||
return index;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("No Universal Render Pipeline is currently active.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a ForwardRenderer has been assigned to the pipeline asset
|
||||
/// </summary>
|
||||
/// <param name="renderer"></param>
|
||||
public static bool IsRendererAdded(ScriptableRendererData renderer)
|
||||
{
|
||||
if (UniversalRenderPipeline.asset)
|
||||
{
|
||||
ScriptableRendererData[] m_rendererDataList = GetRenderDataList(UniversalRenderPipeline.asset);
|
||||
bool isPresent = false;
|
||||
|
||||
for (int i = 0; i < m_rendererDataList.Length; i++)
|
||||
{
|
||||
if (m_rendererDataList[i] == renderer) isPresent = true;
|
||||
}
|
||||
|
||||
return isPresent;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("No Universal Render Pipeline is currently active.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a ForwardRenderer to the pipeline asset in use
|
||||
/// </summary>
|
||||
/// <param name="renderer"></param>
|
||||
private static int AddRendererToPipeline(ScriptableRendererData renderer)
|
||||
{
|
||||
if (renderer == null) return -1;
|
||||
|
||||
if (UniversalRenderPipeline.asset)
|
||||
{
|
||||
ScriptableRendererData[] m_rendererDataList = GetRenderDataList(UniversalRenderPipeline.asset);
|
||||
List<ScriptableRendererData> rendererDataList = new List<ScriptableRendererData>();
|
||||
|
||||
for (int i = 0; i < m_rendererDataList.Length; i++)
|
||||
{
|
||||
rendererDataList.Add(m_rendererDataList[i]);
|
||||
}
|
||||
|
||||
rendererDataList.Add(renderer);
|
||||
int index = rendererDataList.Count-1;
|
||||
|
||||
typeof(UniversalRenderPipelineAsset).GetField(renderDataListFieldName, BindingFlags.NonPublic | BindingFlags.Instance).SetValue(UniversalRenderPipeline.asset, rendererDataList.ToArray());
|
||||
|
||||
#if UNITY_EDITOR
|
||||
EditorUtility.SetDirty(UniversalRenderPipeline.asset);
|
||||
#endif
|
||||
|
||||
RefreshRendererList();
|
||||
|
||||
return index;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("No Universal Render Pipeline is currently active.");
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static int GetDefaultRendererIndex(UniversalRenderPipelineAsset asset)
|
||||
{
|
||||
FieldInfo fieldInfo = typeof(UniversalRenderPipelineAsset).GetField(defaultRendererIndexFieldName, BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
if (fieldInfo == null) {throw new Exception($"Reflection failed on the field named \"{defaultRendererIndexFieldName}\". It may have changed in the current Unity version");}
|
||||
|
||||
return (int)fieldInfo.GetValue(asset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the renderer from the current pipeline asset that's marked as default
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static ScriptableRendererData GetDefaultRenderer(UniversalRenderPipelineAsset asset = null)
|
||||
{
|
||||
if (asset == null) asset = UniversalRenderPipeline.asset;
|
||||
|
||||
if (asset)
|
||||
{
|
||||
ScriptableRendererData[] rendererDataList = GetRenderDataList(asset);
|
||||
int defaultRendererIndex = GetDefaultRendererIndex(asset);
|
||||
|
||||
return rendererDataList[defaultRendererIndex];
|
||||
}
|
||||
|
||||
throw new Exception("No Universal Render Pipeline is currently active.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Editor only! Checks if the given render feature is missing on any renderers. Displays a pop up if that is the case, with the option to add it
|
||||
/// </summary>
|
||||
/// <param name="name">Descriptive name for the render feature</param>
|
||||
/// <typeparam name="T">Render feature type</typeparam>
|
||||
[Conditional("UNITY_EDITOR")]
|
||||
public static void ValidateRenderFeatureSetup<T>(string name)
|
||||
{
|
||||
if (Application.isPlaying == false)
|
||||
{
|
||||
if (RenderFeatureMissing<T>(out ScriptableRendererData[] renderers))
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
string[] rendererNames = new string[renderers.Length];
|
||||
for (int i = 0; i < rendererNames.Length; i++)
|
||||
{
|
||||
rendererNames[i] = "• " + renderers[i].name;
|
||||
}
|
||||
|
||||
if (EditorUtility.DisplayDialog($"Stylized Water 2",
|
||||
$"The {name} render feature hasn't been added to the following renderers:\n\n" +
|
||||
System.String.Join(System.Environment.NewLine, rendererNames) +
|
||||
$"\n\nThis is required for rendering to take effect", "Setup", "Ignore"))
|
||||
{
|
||||
SetupRenderFeature<T>(name:$"Stylized Water 2: {name}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the given render feature from the default renderer
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static ScriptableRendererFeature GetRenderFeature<T>(ScriptableRendererData renderer = null)
|
||||
{
|
||||
if(renderer == null) renderer = GetDefaultRenderer();
|
||||
|
||||
foreach (ScriptableRendererFeature feature in renderer.rendererFeatures)
|
||||
{
|
||||
if (feature && feature.GetType() == typeof(T)) return feature;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a ScriptableRendererFeature is added to the default renderer
|
||||
/// </summary>
|
||||
/// <param name="addIfMissing"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static bool RenderFeatureAdded<T>(ScriptableRendererData renderer = null)
|
||||
{
|
||||
if(renderer == null) renderer = GetDefaultRenderer();
|
||||
|
||||
foreach (ScriptableRendererFeature feature in renderer.rendererFeatures)
|
||||
{
|
||||
if(feature == null) continue;
|
||||
|
||||
if (feature.GetType() == typeof(T))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given render feature is missing on any configured renderers
|
||||
/// </summary>
|
||||
/// <param name="renderers"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static bool RenderFeatureMissing<T>(out ScriptableRendererData[] renderers)
|
||||
{
|
||||
List<ScriptableRendererData> unconfigured = new List<ScriptableRendererData>();
|
||||
|
||||
foreach (var asset in GraphicsSettings.allConfiguredRenderPipelines)
|
||||
{
|
||||
ScriptableRendererData renderer = GetDefaultRenderer((UniversalRenderPipelineAsset)asset);
|
||||
|
||||
if(RenderFeatureAdded<T>(renderer) == false)
|
||||
{
|
||||
unconfigured.Add(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
renderers = unconfigured.ToArray();
|
||||
|
||||
return unconfigured.Count > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a render feature of a given type to all default renderers
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public static void SetupRenderFeature<T>(string name = "")
|
||||
{
|
||||
foreach (var asset in GraphicsSettings.allConfiguredRenderPipelines)
|
||||
{
|
||||
ScriptableRendererData renderer = GetDefaultRenderer((UniversalRenderPipelineAsset)asset);
|
||||
|
||||
if(RenderFeatureAdded<T>(renderer) == false) AddRenderFeature<T>(renderer, name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a ScriptableRendererFeature to the renderer (default is none is supplied)
|
||||
/// </summary>
|
||||
/// <param name="renderer"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public static ScriptableRendererFeature AddRenderFeature<T>(ScriptableRendererData renderer = null, string name = "")
|
||||
{
|
||||
if (renderer == null) renderer = GetDefaultRenderer();
|
||||
|
||||
ScriptableRendererFeature feature = (ScriptableRendererFeature)ScriptableRendererFeature.CreateInstance(typeof(T).ToString());
|
||||
feature.name = name == string.Empty ? typeof(T).ToString() : name;
|
||||
|
||||
//Add component https://github.com/Unity-Technologies/Graphics/blob/d0473769091ff202422ad13b7b764c7b6a7ef0be/com.unity.render-pipelines.universal/Editor/ScriptableRendererDataEditor.cs#L180
|
||||
#if UNITY_EDITOR
|
||||
AssetDatabase.AddObjectToAsset(feature, renderer);
|
||||
AssetDatabase.TryGetGUIDAndLocalFileIdentifier(feature, out var guid, out long localId);
|
||||
#endif
|
||||
|
||||
//Get feature list
|
||||
FieldInfo renderFeaturesInfo = typeof(ScriptableRendererData).GetField(renderFeaturesListFieldName, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
List<ScriptableRendererFeature> m_RendererFeatures = (List<ScriptableRendererFeature>)renderFeaturesInfo.GetValue(renderer);
|
||||
|
||||
//Modify and set list
|
||||
m_RendererFeatures.Add(feature);
|
||||
renderFeaturesInfo.SetValue(renderer, m_RendererFeatures);
|
||||
|
||||
//Onvalidate will call ValidateRendererFeatures and update m_RendererPassMap
|
||||
MethodInfo validateInfo = typeof(ScriptableRendererData).GetMethod("OnValidate", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
validateInfo.Invoke(renderer, null);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
EditorUtility.SetDirty(renderer);
|
||||
AssetDatabase.SaveAssets();
|
||||
#endif
|
||||
|
||||
Debug.Log("<b>" + feature.name + "</b> was added to the <i>" + renderer.name + "</i> renderer");
|
||||
|
||||
return feature;
|
||||
}
|
||||
|
||||
public static bool IsRenderFeatureEnabled<T>(ScriptableRendererData forwardRenderer = null, bool autoEnable = false)
|
||||
{
|
||||
if (!UniversalRenderPipeline.asset) return true;
|
||||
|
||||
#if UNITY_2020_1_OR_NEWER //Older version doesn't have the isActive property
|
||||
if (forwardRenderer == null) forwardRenderer = GetDefaultRenderer();
|
||||
|
||||
FieldInfo renderFeaturesInfo = typeof(ScriptableRendererData).GetField(renderFeaturesListFieldName, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
List<ScriptableRendererFeature> m_RendererFeatures = (List<ScriptableRendererFeature>)renderFeaturesInfo.GetValue(forwardRenderer);
|
||||
|
||||
foreach (ScriptableRendererFeature feature in m_RendererFeatures)
|
||||
{
|
||||
if (feature && feature.GetType() == typeof(T))
|
||||
{
|
||||
if (feature.isActive == false && autoEnable)
|
||||
{
|
||||
feature.SetActive(true);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.EditorUtility.SetDirty(forwardRenderer);
|
||||
#endif
|
||||
}
|
||||
|
||||
return feature.isActive;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
//Fallback, if it is not even in the list
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void ToggleRenderFeature<T>(bool state)
|
||||
{
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
ScriptableRendererData forwardRenderer = GetDefaultRenderer();
|
||||
|
||||
foreach (ScriptableRendererFeature feature in forwardRenderer.rendererFeatures)
|
||||
{
|
||||
if (feature && feature.GetType() == typeof(T)) feature.SetActive(state);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.EditorUtility.SetDirty(forwardRenderer);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void CreateAndAssignNewRenderer(out int index, out string path)
|
||||
{
|
||||
ScriptableRendererData defaultRenderer = GetDefaultRenderer();
|
||||
|
||||
path = string.Empty;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
//Save next to default renderer
|
||||
path = AssetDatabase.GetAssetPath(defaultRenderer);
|
||||
path = path.Replace(defaultRenderer.name + ".asset", string.Empty);
|
||||
#endif
|
||||
|
||||
ScriptableRendererData r = CreateEmptyRenderer("Planar Reflections Renderer", path);
|
||||
#if UNITY_EDITOR
|
||||
path = AssetDatabase.GetAssetPath(r);
|
||||
#endif
|
||||
|
||||
index = AddRendererToPipeline(r);
|
||||
|
||||
//Debug.Log("Created new renderer with index " + index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an empty renderer, without any render features, but otherwise suitable for camera rendering
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public static UniversalRendererData CreateEmptyRenderer(string name = "", string folder = "")
|
||||
{
|
||||
ScriptableRendererData defaultRenderer = GetDefaultRenderer();
|
||||
|
||||
UniversalRendererData rendererData = ScriptableObject.CreateInstance<UniversalRendererData>();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
//Save asset to disk, and load
|
||||
if (folder != string.Empty)
|
||||
{
|
||||
string path = $"{folder}{name}.asset";
|
||||
AssetDatabase.CreateAsset(rendererData, path);
|
||||
|
||||
AssetDatabase.ImportAsset(path);
|
||||
|
||||
rendererData = AssetDatabase.LoadAssetAtPath<UniversalRendererData>(path);
|
||||
}
|
||||
#endif
|
||||
|
||||
UniversalRendererData r = (UniversalRendererData)defaultRenderer;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
//Copy all fields. This should include the shader references, and post processing + XR data. Failing to do so results in nullrefs on these resources when using the renderer.
|
||||
EditorUtility.CopySerialized(r, rendererData);
|
||||
#endif
|
||||
|
||||
//After copying, apply these unique changes
|
||||
rendererData.name = name; //Name must match file name
|
||||
rendererData.rendererFeatures.Clear();
|
||||
|
||||
/* CopySerialized function accounts for any public fields
|
||||
rendererData.shaders = r.shaders;
|
||||
rendererData.postProcessData = r.postProcessData;
|
||||
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
rendererData.debugShaders = r.debugShaders;
|
||||
rendererData.xrSystemData = r.xrSystemData;
|
||||
#endif
|
||||
*/
|
||||
|
||||
return rendererData;
|
||||
}
|
||||
|
||||
public static void RemoveRendererFromPipeline(ScriptableRendererData renderer)
|
||||
{
|
||||
if (renderer == null) return;
|
||||
|
||||
if (UniversalRenderPipeline.asset)
|
||||
{
|
||||
BindingFlags bindings = BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
|
||||
ScriptableRendererData[] m_rendererDataList = GetRenderDataList(UniversalRenderPipeline.asset);
|
||||
List<ScriptableRendererData> rendererDataList = new List<ScriptableRendererData>(m_rendererDataList);
|
||||
|
||||
if (rendererDataList.Contains(renderer))
|
||||
{
|
||||
rendererDataList.Remove(renderer);
|
||||
|
||||
typeof(UniversalRenderPipelineAsset).GetField(renderDataListFieldName, bindings).SetValue(UniversalRenderPipeline.asset, rendererDataList.ToArray());
|
||||
|
||||
#if UNITY_EDITOR
|
||||
EditorUtility.SetDirty(UniversalRenderPipeline.asset);
|
||||
AssetDatabase.SaveAssets();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("No Universal Render Pipeline is currently active.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the renderer index of the related forward renderer
|
||||
/// </summary>
|
||||
/// <param name="camData"></param>
|
||||
/// <param name="renderer"></param>
|
||||
public static void AssignRendererToCamera(UniversalAdditionalCameraData camData, ScriptableRendererData renderer)
|
||||
{
|
||||
if (UniversalRenderPipeline.asset)
|
||||
{
|
||||
if (renderer)
|
||||
{
|
||||
ScriptableRendererData[] rendererDataList = GetRenderDataList(UniversalRenderPipeline.asset);
|
||||
|
||||
for (int i = 0; i < rendererDataList.Length; i++)
|
||||
{
|
||||
if (rendererDataList[i] == renderer) camData.SetRenderer(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("No Universal Render Pipeline is currently active.");
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsDepthTextureOptionDisabledAnywhere()
|
||||
{
|
||||
bool state = false;
|
||||
|
||||
for (int i = 0; i < GraphicsSettings.allConfiguredRenderPipelines.Length; i++)
|
||||
{
|
||||
if(GraphicsSettings.allConfiguredRenderPipelines[i].GetType() != typeof(UniversalRenderPipelineAsset)) continue;
|
||||
|
||||
UniversalRenderPipelineAsset pipeline = (UniversalRenderPipelineAsset)GraphicsSettings.allConfiguredRenderPipelines[i];
|
||||
|
||||
state |= (pipeline.supportsCameraDepthTexture == false);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
public static void SetDepthTextureOnAllAssets(bool state)
|
||||
{
|
||||
for (int i = 0; i < GraphicsSettings.allConfiguredRenderPipelines.Length; i++)
|
||||
{
|
||||
if(GraphicsSettings.allConfiguredRenderPipelines[i].GetType() != typeof(UniversalRenderPipelineAsset)) continue;
|
||||
|
||||
UniversalRenderPipelineAsset pipeline = (UniversalRenderPipelineAsset)GraphicsSettings.allConfiguredRenderPipelines[i];
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if(pipeline.supportsCameraDepthTexture != state) EditorUtility.SetDirty(pipeline);
|
||||
#endif
|
||||
|
||||
pipeline.supportsCameraDepthTexture = state;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsOpaqueTextureOptionDisabledAnywhere()
|
||||
{
|
||||
bool state = false;
|
||||
|
||||
for (int i = 0; i < GraphicsSettings.allConfiguredRenderPipelines.Length; i++)
|
||||
{
|
||||
if(GraphicsSettings.allConfiguredRenderPipelines[i].GetType() != typeof(UniversalRenderPipelineAsset)) continue;
|
||||
|
||||
UniversalRenderPipelineAsset pipeline = (UniversalRenderPipelineAsset)GraphicsSettings.allConfiguredRenderPipelines[i];
|
||||
|
||||
if (pipeline.supportsCameraOpaqueTexture == false) return true;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
public static void SetOpaqueTextureOnAllAssets(bool state)
|
||||
{
|
||||
for (int i = 0; i < GraphicsSettings.allConfiguredRenderPipelines.Length; i++)
|
||||
{
|
||||
if(GraphicsSettings.allConfiguredRenderPipelines[i].GetType() != typeof(UniversalRenderPipelineAsset)) continue;
|
||||
|
||||
UniversalRenderPipelineAsset pipeline = (UniversalRenderPipelineAsset)GraphicsSettings.allConfiguredRenderPipelines[i];
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if(pipeline.supportsCameraOpaqueTexture != state) EditorUtility.SetDirty(pipeline);
|
||||
#endif
|
||||
|
||||
pipeline.supportsCameraOpaqueTexture = state;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TransparentShadowsEnabled()
|
||||
{
|
||||
if (!UniversalRenderPipeline.asset) return false;
|
||||
|
||||
UniversalRendererData main = (UniversalRendererData)GetDefaultRenderer();
|
||||
|
||||
return main ? main.shadowTransparentReceive : false;
|
||||
}
|
||||
|
||||
public static bool IsDepthAfterTransparents()
|
||||
{
|
||||
#if UNITY_2022_2_OR_NEWER
|
||||
if (!UniversalRenderPipeline.asset) return false;
|
||||
|
||||
UniversalRendererData main = (UniversalRendererData)GetDefaultRenderer();
|
||||
|
||||
return main.copyDepthMode == CopyDepthMode.AfterTransparents;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool VREnabled()
|
||||
{
|
||||
#if UNITY_2023_2_OR_NEWER
|
||||
return XRSRPSettings.enabled;
|
||||
#else
|
||||
return XRGraphics.enabled;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 004552901932ac14d889d7b07cc104f2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
3
LocalPackages/StylizedWater2/Runtime/Rendering.meta
Normal file
3
LocalPackages/StylizedWater2/Runtime/Rendering.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7fa4596443764ff6967bf4a4fedfe497
|
||||
timeCreated: 1701077187
|
||||
@@ -0,0 +1,181 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
#if URP
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
namespace StylizedWater2
|
||||
{
|
||||
public class DisplacementPrePass : ScriptableRenderPass
|
||||
{
|
||||
private const string profilerTag = "Water Displacement Prepass";
|
||||
private static readonly ProfilingSampler profilerSampler = new ProfilingSampler(profilerTag);
|
||||
|
||||
public const string KEYWORD = "WATER_DISPLACEMENT_PASS";
|
||||
|
||||
[Serializable]
|
||||
public class Settings
|
||||
{
|
||||
public bool enable;
|
||||
|
||||
public float range = 500f;
|
||||
|
||||
[Range(0.1f, 4f)]
|
||||
public float cellSize = 0.25f;
|
||||
}
|
||||
|
||||
//Render pass
|
||||
FilteringSettings m_FilteringSettings;
|
||||
RenderStateBlock m_RenderStateBlock;
|
||||
private readonly List<ShaderTagId> m_ShaderTagIdList = new List<ShaderTagId>()
|
||||
{
|
||||
new ShaderTagId("DepthOnly")
|
||||
};
|
||||
|
||||
public DisplacementPrePass()
|
||||
{
|
||||
m_FilteringSettings = new FilteringSettings(RenderQueueRange.all, LayerMask.GetMask("Water"));
|
||||
m_RenderStateBlock = new RenderStateBlock(RenderStateMask.Nothing);
|
||||
}
|
||||
|
||||
private static readonly Quaternion viewRotation = Quaternion.Euler(new Vector3(90f, 0f, 0f));
|
||||
private static readonly Vector3 viewScale = new Vector3(1, 1, -1);
|
||||
private static Rect viewportRect;
|
||||
|
||||
private const string BufferName = "_WaterDisplacementBuffer";
|
||||
private static readonly int _WaterDisplacementBuffer = Shader.PropertyToID(BufferName);
|
||||
private const string CoordsName = "_WaterDisplacementCoords";
|
||||
private static readonly int _WaterDisplacementCoords = Shader.PropertyToID(CoordsName);
|
||||
|
||||
private RTHandle renderTarget;
|
||||
private static Vector3 centerPosition;
|
||||
private static Vector4 rendererCoords;
|
||||
|
||||
private static Matrix4x4 projection { set; get; }
|
||||
private static Matrix4x4 view { set; get; }
|
||||
|
||||
private int resolution;
|
||||
private int m_resolution;
|
||||
private float orthoSize;
|
||||
private Settings settings;
|
||||
|
||||
public void Setup(Settings settings)
|
||||
{
|
||||
this.settings = settings;
|
||||
|
||||
resolution = Mathf.CeilToInt(settings.range / settings.cellSize);
|
||||
resolution = Mathf.Clamp(resolution, 16, 2048);
|
||||
orthoSize = 0.5f * settings.range;
|
||||
}
|
||||
|
||||
//Important to snap the projection to the nearest texel. Otherwise pixel swimming is introduced when moving, due to bilinear filtering
|
||||
private static Vector3 StabilizeProjection(Vector3 pos, float texelSize)
|
||||
{
|
||||
float Snap(float coord, float cellSize) => Mathf.FloorToInt(coord / cellSize) * (cellSize) + (cellSize * 0.5f);
|
||||
|
||||
return new Vector3(Snap(pos.x, texelSize), Snap(pos.y, texelSize), Snap(pos.z, texelSize));
|
||||
}
|
||||
|
||||
private void SetupProjection(CommandBuffer cmd, Camera camera)
|
||||
{
|
||||
centerPosition = camera.transform.position;
|
||||
centerPosition += camera.transform.forward * settings.range * 0.5f;
|
||||
|
||||
centerPosition = StabilizeProjection(centerPosition, (settings.range) / resolution);
|
||||
|
||||
//var frustumHeight = 2.0f * renderRange * Mathf.Tan(camera.fieldOfView * 0.5f * Mathf.Deg2Rad); //Still clips, plus doesn't support orthographc
|
||||
var frustumHeight = settings.range;
|
||||
centerPosition += (Vector3.up * frustumHeight * 0.5f);
|
||||
|
||||
projection = Matrix4x4.Ortho(-orthoSize, orthoSize, -orthoSize, orthoSize, 0.03f, frustumHeight * 2f);
|
||||
|
||||
view = Matrix4x4.TRS(centerPosition, viewRotation, viewScale).inverse;
|
||||
|
||||
cmd.SetViewProjectionMatrices(view, projection);
|
||||
//RenderingUtils.SetViewAndProjectionMatrices(cmd, view, projection, true);
|
||||
|
||||
viewportRect.width = resolution;
|
||||
viewportRect.height = resolution;
|
||||
cmd.SetViewport(viewportRect);
|
||||
|
||||
cmd.SetGlobalMatrix("UNITY_MATRIX_V", view);
|
||||
|
||||
//Position/scale of projection. Converted to a UV in the shader
|
||||
rendererCoords.x = centerPosition.x - orthoSize;
|
||||
rendererCoords.y = centerPosition.z - orthoSize;
|
||||
rendererCoords.z = settings.range;
|
||||
rendererCoords.w = 1f; //Enable in shader
|
||||
|
||||
cmd.SetGlobalVector(_WaterDisplacementCoords, rendererCoords);
|
||||
}
|
||||
|
||||
public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
|
||||
{
|
||||
if (resolution != m_resolution || renderTarget == null)
|
||||
{
|
||||
RTHandles.Release(renderTarget);
|
||||
|
||||
renderTarget = RTHandles.Alloc(resolution, resolution, 1, DepthBits.None,
|
||||
UnityEngine.Experimental.Rendering.GraphicsFormat.R16_SFloat,
|
||||
filterMode: FilterMode.Bilinear,
|
||||
wrapMode: TextureWrapMode.Clamp,
|
||||
useMipMap: false,
|
||||
name: BufferName);
|
||||
}
|
||||
m_resolution = resolution;
|
||||
|
||||
cmd.SetGlobalTexture(_WaterDisplacementBuffer, renderTarget);
|
||||
|
||||
cmd.EnableShaderKeyword(KEYWORD);
|
||||
|
||||
ConfigureTarget(renderTarget);
|
||||
ConfigureClear(ClearFlag.Color, Color.clear);
|
||||
}
|
||||
|
||||
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
|
||||
{
|
||||
CommandBuffer cmd = CommandBufferPool.Get();
|
||||
|
||||
DrawingSettings drawingSettings = CreateDrawingSettings(m_ShaderTagIdList, ref renderingData, SortingCriteria.RenderQueue | SortingCriteria.SortingLayer | SortingCriteria.CommonTransparent);
|
||||
drawingSettings.perObjectData = PerObjectData.None;
|
||||
|
||||
using (new ProfilingScope(cmd, profilerSampler))
|
||||
{
|
||||
ref CameraData cameraData = ref renderingData.cameraData;
|
||||
|
||||
SetupProjection(cmd, cameraData.camera);
|
||||
|
||||
//Execute current commands first
|
||||
context.ExecuteCommandBuffer(cmd);
|
||||
cmd.Clear();
|
||||
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
rendererListParams.cullingResults = renderingData.cullResults;
|
||||
rendererListParams.drawSettings = drawingSettings;
|
||||
rendererListParams.filteringSettings = m_FilteringSettings;
|
||||
rendererList = context.CreateRendererList(ref rendererListParams);
|
||||
|
||||
cmd.DrawRendererList(rendererList);
|
||||
#else
|
||||
context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref m_FilteringSettings, ref m_RenderStateBlock);
|
||||
#endif
|
||||
|
||||
//Restore
|
||||
//Disabled, because this pass renders before the camera is initialized anyway
|
||||
//cmd.SetViewProjectionMatrices(cameraData.camera.worldToCameraMatrix, cameraData.camera.projectionMatrix);
|
||||
}
|
||||
|
||||
context.ExecuteCommandBuffer(cmd);
|
||||
CommandBufferPool.Release(cmd);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Shader.SetGlobalVector(_WaterDisplacementCoords, Vector4.zero);
|
||||
RTHandles.Release(renderTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c08ec7d2563d4bea89aa3509298f8bf4
|
||||
timeCreated: 1701077223
|
||||
@@ -0,0 +1,575 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
#if URP
|
||||
using UnityEngine.Rendering.Universal;
|
||||
#endif
|
||||
|
||||
namespace StylizedWater2
|
||||
{
|
||||
[ExecuteInEditMode]
|
||||
[AddComponentMenu("Stylized Water 2/Planar Reflection Renderer")]
|
||||
[HelpURL("https://staggart.xyz/unity/stylized-water-2/sws-2-docs/?section=planar-reflections")]
|
||||
public class PlanarReflectionRenderer : MonoBehaviour
|
||||
{
|
||||
#if URP
|
||||
public static List<PlanarReflectionRenderer> Instances = new List<PlanarReflectionRenderer>();
|
||||
public Dictionary<Camera, Camera> reflectionCameras = new Dictionary<Camera, Camera>();
|
||||
|
||||
//Rendering
|
||||
[Tooltip("If enabled, the reflection plane will be based on this transform's up vector (green arrow).\n\nOtherwise the world's upwards direction is assumed")]
|
||||
public bool rotatable = false;
|
||||
[Tooltip("Set the layers that should be rendered into the reflection. The \"Water\" layer is always excluded")]
|
||||
public LayerMask cullingMask = -1;
|
||||
[Tooltip("The renderer used by the reflection camera. It's recommend to create a separate renderer, so any custom render features aren't executed for the reflection")]
|
||||
public int rendererIndex = -1;
|
||||
|
||||
[Min(0f)]
|
||||
public float offset = 0.05f;
|
||||
[Tooltip("When disabled, the skybox reflection comes from a Reflection Probe. This has the benefit of being omni-directional rather than flat/planar. Enabled this to render the skybox into the planar reflection anyway." +
|
||||
"\n\nNote that enabling this will override Screen Space Reflections completely!")]
|
||||
public bool includeSkybox;
|
||||
[Tooltip("Render Unity's default fog in the reflection. Note that this doesn't strictly work correctly on large triangles, as it is incompatible with oblique camera projections.")]
|
||||
public bool enableFog;
|
||||
|
||||
//Quality
|
||||
public bool renderShadows;
|
||||
[Tooltip("Objects beyond this range aren't rendered into the reflection. Note that this may causes popping for large/tall objects.")]
|
||||
public float renderRange = 500f;
|
||||
[Range(0.25f, 1f)]
|
||||
[Tooltip("A multiplier for the rendering resolution, based on the current screen resolution. The render scale, as configured in the pipeline settings is multiplied over this.")]
|
||||
public float renderScale = 0.75f;
|
||||
|
||||
[Range(0, 4)]
|
||||
[Tooltip("Do not render LOD objects lower than this value. Example: With a value of 1, LOD0 for LOD Groups will not be used")]
|
||||
public int maximumLODLevel = 0;
|
||||
|
||||
[SerializeField]
|
||||
public List<WaterObject> waterObjects = new List<WaterObject>();
|
||||
[Tooltip("If enabled, the center of the rendering bounds (that wraps around the water objects) moves with the Transform position" +
|
||||
"\n\nYou must however ensure you are only moving on the XZ axis")]
|
||||
public bool moveWithTransform;
|
||||
[HideInInspector]
|
||||
public Bounds bounds = new Bounds();
|
||||
|
||||
private float m_renderScale = 1f;
|
||||
private float m_renderRange;
|
||||
|
||||
/// <summary>
|
||||
/// Reflections will only render if this is true. Value can be set through the static SetQuality function
|
||||
/// </summary>
|
||||
public static bool AllowReflections { get; private set; } = true;
|
||||
|
||||
private static readonly int _PlanarReflectionsEnabledID = Shader.PropertyToID("_PlanarReflectionsEnabled");
|
||||
private static readonly int _PlanarReflectionID = Shader.PropertyToID("_PlanarReflection");
|
||||
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
private UniversalRenderPipeline.SingleCameraRequest requestData;
|
||||
#endif
|
||||
|
||||
[NonSerialized]
|
||||
public bool isRendering;
|
||||
|
||||
private Camera m_reflectionCamera;
|
||||
private static UniversalAdditionalCameraData m_cameraData;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
this.gameObject.name = "Planar Reflection Renderer";
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
InitializeValues();
|
||||
|
||||
Instances.Add(this);
|
||||
EnableReflections();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
Instances.Remove(this);
|
||||
DisableReflections();
|
||||
}
|
||||
|
||||
public void InitializeValues()
|
||||
{
|
||||
m_renderScale = renderScale;
|
||||
m_renderRange = renderRange;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns all Water Objects in the WaterObject.Instances list and enables reflection for them
|
||||
/// </summary>
|
||||
public void ApplyToAllWaterInstances()
|
||||
{
|
||||
waterObjects = new List<WaterObject>(WaterObject.Instances);
|
||||
RecalculateBounds();
|
||||
EnableMaterialReflectionSampling();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggle reflections or set the render scale for all reflection renderers. This can be tied into performance scaling or graphics settings in menus
|
||||
/// </summary>
|
||||
/// <param name="enableReflections">Toggles rendering of reflections, and toggles it on all the assigned water objects</param>
|
||||
/// <param name="renderScale">A multiplier for the current screen resolution. Note that the render scale configured in URP is also taken into account</param>
|
||||
/// <param name="renderRange">Objects beyond this range aren't rendered into the reflection</param>
|
||||
public static void SetQuality(bool enableReflections, float renderScale = -1f, float renderRange = -1f, int maxLodLevel = -1)
|
||||
{
|
||||
AllowReflections = enableReflections;
|
||||
|
||||
foreach (PlanarReflectionRenderer renderer in Instances)
|
||||
{
|
||||
if (renderScale > 0) renderer.renderScale = renderScale;
|
||||
if (renderRange > 0) renderer.renderRange = renderRange;
|
||||
if (maxLodLevel >= 0) renderer.maximumLODLevel = maxLodLevel;
|
||||
renderer.InitializeValues();
|
||||
|
||||
if (enableReflections) renderer.EnableReflections();
|
||||
if (!enableReflections) renderer.DisableReflections();
|
||||
}
|
||||
}
|
||||
|
||||
public void EnableReflections()
|
||||
{
|
||||
if (!AllowReflections || PipelineUtilities.VREnabled()) return;
|
||||
|
||||
RenderPipelineManager.beginCameraRendering += OnWillRenderCamera;
|
||||
ToggleMaterialReflectionSampling(true);
|
||||
}
|
||||
|
||||
public void DisableReflections()
|
||||
{
|
||||
RenderPipelineManager.beginCameraRendering -= OnWillRenderCamera;
|
||||
ToggleMaterialReflectionSampling(false);
|
||||
|
||||
//Clear cameras
|
||||
foreach (var kvp in reflectionCameras)
|
||||
{
|
||||
if (kvp.Value == null) continue;
|
||||
|
||||
if (kvp.Value)
|
||||
{
|
||||
RenderTexture.ReleaseTemporary(kvp.Value.targetTexture);
|
||||
DestroyImmediate(kvp.Value.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
reflectionCameras.Clear();
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
Gizmos.color = bounds.size.y > 0.01f ? Color.yellow : Color.white;
|
||||
Gizmos.DrawWireCube(bounds.center, bounds.size);
|
||||
}
|
||||
|
||||
public Bounds CalculateBounds()
|
||||
{
|
||||
Bounds m_bounds = new Bounds(Vector3.zero, Vector3.zero);
|
||||
|
||||
if (waterObjects == null) return m_bounds;
|
||||
if (waterObjects.Count == 0) return m_bounds;
|
||||
|
||||
Vector3 minSum = Vector3.one * Mathf.Infinity;
|
||||
Vector3 maxSum = Vector3.one * Mathf.NegativeInfinity;
|
||||
|
||||
for (int i = 0; i < waterObjects.Count; i++)
|
||||
{
|
||||
if (!waterObjects[i]) continue;
|
||||
|
||||
minSum = Vector3.Min(waterObjects[i].meshRenderer.bounds.min, minSum);
|
||||
maxSum = Vector3.Max(waterObjects[i].meshRenderer.bounds.max, maxSum);
|
||||
}
|
||||
|
||||
m_bounds.SetMinMax(minSum, maxSum);
|
||||
|
||||
//Flatten to center
|
||||
m_bounds.size = new Vector3(m_bounds.size.x, 0f, m_bounds.size.z);
|
||||
|
||||
return m_bounds;
|
||||
}
|
||||
|
||||
public void RecalculateBounds()
|
||||
{
|
||||
bounds = CalculateBounds();
|
||||
}
|
||||
|
||||
public static bool InvalidContext(Camera camera)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
//Avoid the "Screen position outside of frustrum" error
|
||||
if (camera.orthographic && Vector3.Dot(Vector3.up, camera.transform.up) > 0.9999f) return true;
|
||||
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
//Causes an internal error in URP's rendering code in the CopyColorPass
|
||||
if (camera.cameraType == CameraType.SceneView && UnityEditor.SceneView.lastActiveSceneView && UnityEditor.SceneView.lastActiveSceneView.isUsingSceneFiltering) return true;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//Skip for any special use camera's (except scene view camera)
|
||||
//Note: Scene camera still rendering even if window not focused!
|
||||
return (camera.cameraType != CameraType.SceneView && (camera.cameraType == CameraType.Reflection || camera.cameraType == CameraType.Preview || camera.hideFlags != HideFlags.None));
|
||||
}
|
||||
|
||||
private void OnWillRenderCamera(ScriptableRenderContext context, Camera camera)
|
||||
{
|
||||
if (InvalidContext(camera))
|
||||
{
|
||||
isRendering = false;
|
||||
return;
|
||||
}
|
||||
|
||||
isRendering = WaterObjectsVisible(camera);
|
||||
|
||||
if (isRendering == false) return;
|
||||
|
||||
|
||||
if (moveWithTransform) bounds.center = this.transform.position;
|
||||
|
||||
m_cameraData = camera.GetComponent<UniversalAdditionalCameraData>();
|
||||
if (m_cameraData && m_cameraData.renderType == CameraRenderType.Overlay) return;
|
||||
|
||||
reflectionCameras.TryGetValue(camera, out m_reflectionCamera);
|
||||
if (m_reflectionCamera == null) CreateReflectionCamera(camera);
|
||||
|
||||
//It's possible it is destroyed at this point when disabling reflections
|
||||
if (!m_reflectionCamera) return;
|
||||
|
||||
UnityEngine.Profiling.Profiler.BeginSample("Planar Water Reflections", camera);
|
||||
|
||||
//Render scale changed
|
||||
if (Math.Abs(renderScale - m_renderScale) > 0.02f)
|
||||
{
|
||||
RenderTexture.ReleaseTemporary(m_reflectionCamera.targetTexture);
|
||||
CreateRenderTexture(m_reflectionCamera, camera);
|
||||
|
||||
m_renderScale = renderScale;
|
||||
}
|
||||
|
||||
UpdateWaterProperties(m_reflectionCamera);
|
||||
|
||||
UpdateCameraProperties(camera, m_reflectionCamera);
|
||||
UpdatePerspective(camera, m_reflectionCamera);
|
||||
|
||||
bool fogEnabled = RenderSettings.fog && !enableFog;
|
||||
//Fog is based on clip-space z-distance and doesn't work with oblique projections
|
||||
if (fogEnabled) RenderSettings.fog = false;
|
||||
int maxLODLevel = QualitySettings.maximumLODLevel;
|
||||
QualitySettings.maximumLODLevel = maximumLODLevel;
|
||||
GL.invertCulling = true;
|
||||
|
||||
#pragma warning disable 0618
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
/*
|
||||
requestData = new UniversalRenderPipeline.SingleCameraRequest();
|
||||
requestData.destination = reflectionCamera.targetTexture;
|
||||
requestData.slice = -1;
|
||||
|
||||
//Throws the 'Recursive rendering is not supported in SRP (are you calling Camera.Render from within a render pipeline?).' error.
|
||||
if (RenderPipeline.SupportsRenderRequest(m_reflectionCamera, requestData))
|
||||
{
|
||||
RenderPipeline.SubmitRenderRequest(m_reflectionCamera, requestData);
|
||||
}
|
||||
*/
|
||||
|
||||
//Instead, Unity will whine about using an obsolete API.
|
||||
UniversalRenderPipeline.RenderSingleCamera(context, m_reflectionCamera);
|
||||
|
||||
//So now what?
|
||||
#else
|
||||
UniversalRenderPipeline.RenderSingleCamera(context, m_reflectionCamera);
|
||||
#endif
|
||||
#pragma warning restore 0618
|
||||
|
||||
if (fogEnabled) RenderSettings.fog = true;
|
||||
QualitySettings.maximumLODLevel = maxLODLevel;
|
||||
GL.invertCulling = false;
|
||||
|
||||
UnityEngine.Profiling.Profiler.EndSample();
|
||||
}
|
||||
|
||||
private float GetRenderScale()
|
||||
{
|
||||
return Mathf.Clamp(renderScale * UniversalRenderPipeline.asset.renderScale, 0.25f, 1f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should the renderer index be changed at runtime, this function must be called to update any reflection cameras
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
public void SetRendererIndex(int index)
|
||||
{
|
||||
index = PipelineUtilities.ValidateRenderer(index);
|
||||
|
||||
foreach (var kvp in reflectionCameras)
|
||||
{
|
||||
if (kvp.Value == null) continue;
|
||||
|
||||
m_cameraData = kvp.Value.GetComponent<UniversalAdditionalCameraData>();
|
||||
m_cameraData.SetRenderer(index);
|
||||
}
|
||||
}
|
||||
|
||||
public void ToggleShadows(bool state)
|
||||
{
|
||||
foreach (var kvp in reflectionCameras)
|
||||
{
|
||||
if (kvp.Value == null) continue;
|
||||
|
||||
m_cameraData = kvp.Value.GetComponent<UniversalAdditionalCameraData>();
|
||||
m_cameraData.renderShadows = state;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add the WaterObject, and recalculates the rendering bounds.
|
||||
/// </summary>
|
||||
/// <param name="waterObject"></param>
|
||||
public void AddWaterObject(WaterObject waterObject)
|
||||
{
|
||||
ToggleMaterialReflectionSampling(waterObject, true);
|
||||
waterObjects.Add(waterObject);
|
||||
|
||||
RecalculateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the WaterObject, and recalculates the rendering bounds.
|
||||
/// </summary>
|
||||
/// <param name="waterObject"></param>
|
||||
public void RemoveWaterObject(WaterObject waterObject)
|
||||
{
|
||||
ToggleMaterialReflectionSampling(waterObject, false);
|
||||
waterObjects.Remove(waterObject);
|
||||
|
||||
RecalculateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables planar reflections on the MeshRenderers of the assigned water objects
|
||||
/// </summary>
|
||||
public void EnableMaterialReflectionSampling()
|
||||
{
|
||||
ToggleMaterialReflectionSampling(AllowReflections);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the sampling of the planar reflections texture in the water shader.
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
public void ToggleMaterialReflectionSampling(bool state)
|
||||
{
|
||||
if (waterObjects == null) return;
|
||||
|
||||
for (int i = 0; i < waterObjects.Count; i++)
|
||||
{
|
||||
if (waterObjects[i] == null) continue;
|
||||
|
||||
ToggleMaterialReflectionSampling(waterObjects[i], state);
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleMaterialReflectionSampling(WaterObject waterObject, bool state)
|
||||
{
|
||||
waterObject.props.SetFloat(_PlanarReflectionsEnabledID, state ? 1f : 0f);
|
||||
waterObject.ApplyInstancedProperties();
|
||||
}
|
||||
|
||||
private void CreateReflectionCamera(Camera source)
|
||||
{
|
||||
//Object creation
|
||||
GameObject go = new GameObject($"{source.name} Planar Reflection");
|
||||
go.hideFlags = HideFlags.DontSave | HideFlags.HideInHierarchy;
|
||||
|
||||
Camera newCamera = go.AddComponent<Camera>();
|
||||
newCamera.hideFlags = HideFlags.DontSave;
|
||||
//For the scene-view camera this also copies unwanted properties. Such as the camera type and background color!
|
||||
newCamera.CopyFrom(source);
|
||||
|
||||
//Always exclude water layer
|
||||
newCamera.cullingMask = ~(1 << 4) & cullingMask;
|
||||
//Must always be set to Game, otherwise shadows render anyway
|
||||
newCamera.cameraType = CameraType.Game;
|
||||
newCamera.depth = source.depth-1f;
|
||||
newCamera.rect = new Rect(0,0,1,1);
|
||||
newCamera.enabled = false;
|
||||
newCamera.clearFlags = includeSkybox ? CameraClearFlags.Skybox : CameraClearFlags.Depth;
|
||||
//Required to maintain the alpha channel for the scene view
|
||||
newCamera.backgroundColor = Color.clear;
|
||||
newCamera.useOcclusionCulling = false;
|
||||
|
||||
//Component required for the UniversalRenderPipeline.RenderSingleCamera call
|
||||
UniversalAdditionalCameraData data = newCamera.gameObject.AddComponent<UniversalAdditionalCameraData>();
|
||||
data.requiresDepthTexture = false;
|
||||
data.requiresColorTexture = false;
|
||||
data.renderShadows = renderShadows;
|
||||
|
||||
rendererIndex = PipelineUtilities.ValidateRenderer(rendererIndex);
|
||||
data.SetRenderer(rendererIndex);
|
||||
|
||||
CreateRenderTexture(newCamera, source);
|
||||
|
||||
reflectionCameras[source] = newCamera;
|
||||
}
|
||||
|
||||
private void CreateRenderTexture(Camera targetCamera, Camera source)
|
||||
{
|
||||
//Note: Do not use RenderTextureFormat.Default or HDR, as these may be without an alpha channel on some platforms
|
||||
RenderTextureFormat colorFormat = UniversalRenderPipeline.asset.supportsHDR && SystemInfo.SupportsRenderTextureFormat(RenderTextureFormat.ARGBHalf) ? RenderTextureFormat.ARGBHalf : RenderTextureFormat.ARGB32;
|
||||
|
||||
float scale = GetRenderScale();
|
||||
|
||||
RenderTextureDescriptor rtDsc = new RenderTextureDescriptor(
|
||||
(int)((float)source.scaledPixelWidth * scale),
|
||||
(int)((float)source.scaledPixelHeight * scale),
|
||||
colorFormat);
|
||||
|
||||
rtDsc.depthBufferBits = 16;
|
||||
|
||||
targetCamera.targetTexture = RenderTexture.GetTemporary(rtDsc);
|
||||
targetCamera.targetTexture.name = $"{source.name}_Reflection {rtDsc.width}x{rtDsc.height}";
|
||||
}
|
||||
|
||||
private static readonly Plane[] frustrumPlanes = new Plane[6];
|
||||
|
||||
public bool WaterObjectsVisible(Camera targetCamera)
|
||||
{
|
||||
GeometryUtility.CalculateFrustumPlanes(targetCamera.projectionMatrix * targetCamera.worldToCameraMatrix, frustrumPlanes);
|
||||
|
||||
return GeometryUtility.TestPlanesAABB(frustrumPlanes, bounds);
|
||||
}
|
||||
|
||||
//Assigns the render target of the current reflection camera
|
||||
private void UpdateWaterProperties(Camera cam)
|
||||
{
|
||||
for (int i = 0; i < waterObjects.Count; i++)
|
||||
{
|
||||
if (waterObjects[i] == null) continue;
|
||||
|
||||
waterObjects[i].props.SetTexture(_PlanarReflectionID, cam.targetTexture);
|
||||
waterObjects[i].ApplyInstancedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector4 reflectionPlane;
|
||||
private static Matrix4x4 reflectionBase;
|
||||
private static Vector3 oldCamPos;
|
||||
|
||||
private static Matrix4x4 worldToCamera;
|
||||
private static Matrix4x4 viewMatrix;
|
||||
private static Matrix4x4 projectionMatrix;
|
||||
private static Vector4 clipPlane;
|
||||
private static readonly float[] layerCullDistances = new float[32];
|
||||
|
||||
private void UpdateCameraProperties(Camera source, Camera reflectionCam)
|
||||
{
|
||||
reflectionCam.fieldOfView = source.fieldOfView;
|
||||
reflectionCam.orthographic = source.orthographic;
|
||||
reflectionCam.orthographicSize = source.orthographicSize;
|
||||
reflectionCam.useOcclusionCulling = source.useOcclusionCulling;
|
||||
}
|
||||
|
||||
private void UpdatePerspective(Camera source, Camera reflectionCam)
|
||||
{
|
||||
if (!source || !reflectionCam) return;
|
||||
|
||||
Vector3 normal = rotatable ? this.transform.up : Vector3.up;
|
||||
|
||||
Vector3 position = bounds.center + (normal * offset);
|
||||
|
||||
var d = -Vector3.Dot(normal, position);
|
||||
reflectionPlane = new Vector4(normal.x, normal.y, normal.z, d);
|
||||
|
||||
reflectionBase = Matrix4x4.identity;
|
||||
reflectionBase *= Matrix4x4.Scale(new Vector3(1, -1, 1));
|
||||
|
||||
// View
|
||||
CalculateReflectionMatrix(ref reflectionBase, reflectionPlane);
|
||||
oldCamPos = source.transform.position - new Vector3(0, position.y * 2, 0);
|
||||
reflectionCam.transform.forward = Vector3.Scale(source.transform.forward, new Vector3(1, -1, 1));
|
||||
|
||||
worldToCamera = source.worldToCameraMatrix;
|
||||
viewMatrix = worldToCamera * reflectionBase;
|
||||
|
||||
//Reflect position
|
||||
oldCamPos.y = -oldCamPos.y;
|
||||
reflectionCam.transform.position = oldCamPos;
|
||||
|
||||
clipPlane = CameraSpacePlane(reflectionCam.worldToCameraMatrix, position - normal * 0.1f, normal, 1.0f);
|
||||
projectionMatrix = source.CalculateObliqueMatrix(clipPlane);
|
||||
|
||||
//Settings
|
||||
reflectionCam.cullingMask = ~(1 << 4) & cullingMask;;
|
||||
m_reflectionCamera.clearFlags = includeSkybox ? CameraClearFlags.Skybox : CameraClearFlags.Depth;
|
||||
|
||||
#if !UNITY_2023_3_OR_NEWER
|
||||
//Only re-apply on value change
|
||||
if (m_renderRange != renderRange)
|
||||
{
|
||||
m_renderRange = renderRange;
|
||||
|
||||
for (int i = 0; i < layerCullDistances.Length; i++)
|
||||
{
|
||||
layerCullDistances[i] = renderRange;
|
||||
}
|
||||
}
|
||||
reflectionCam.layerCullDistances = layerCullDistances;
|
||||
reflectionCam.layerCullSpherical = true;
|
||||
#endif
|
||||
|
||||
reflectionCam.projectionMatrix = projectionMatrix;
|
||||
reflectionCam.worldToCameraMatrix = viewMatrix;
|
||||
}
|
||||
|
||||
// Calculates reflection matrix around the given plane
|
||||
private void CalculateReflectionMatrix(ref Matrix4x4 reflectionMat, Vector4 plane)
|
||||
{
|
||||
reflectionMat.m00 = (1F - 2F * plane[0] * plane[0]);
|
||||
reflectionMat.m01 = (-2F * plane[0] * plane[1]);
|
||||
reflectionMat.m02 = (-2F * plane[0] * plane[2]);
|
||||
reflectionMat.m03 = (-2F * plane[3] * plane[0]);
|
||||
|
||||
reflectionMat.m10 = (-2F * plane[1] * plane[0]);
|
||||
reflectionMat.m11 = (1F - 2F * plane[1] * plane[1]);
|
||||
reflectionMat.m12 = (-2F * plane[1] * plane[2]);
|
||||
reflectionMat.m13 = (-2F * plane[3] * plane[1]);
|
||||
|
||||
reflectionMat.m20 = (-2F * plane[2] * plane[0]);
|
||||
reflectionMat.m21 = (-2F * plane[2] * plane[1]);
|
||||
reflectionMat.m22 = (1F - 2F * plane[2] * plane[2]);
|
||||
reflectionMat.m23 = (-2F * plane[3] * plane[2]);
|
||||
|
||||
reflectionMat.m30 = 0F;
|
||||
reflectionMat.m31 = 0F;
|
||||
reflectionMat.m32 = 0F;
|
||||
reflectionMat.m33 = 1F;
|
||||
}
|
||||
|
||||
// Given position/normal of the plane, calculates plane in camera space.
|
||||
private Vector4 CameraSpacePlane(Matrix4x4 worldToCameraMatrix, Vector3 pos, Vector3 normal, float sideSign)
|
||||
{
|
||||
var offsetPos = pos + normal * offset;
|
||||
var cameraPosition = worldToCameraMatrix.MultiplyPoint(offsetPos);
|
||||
var cameraNormal = worldToCameraMatrix.MultiplyVector(normal).normalized * sideSign;
|
||||
return new Vector4(cameraNormal.x, cameraNormal.y, cameraNormal.z,
|
||||
-Vector3.Dot(cameraPosition, cameraNormal));
|
||||
}
|
||||
|
||||
public RenderTexture TryGetReflectionTexture(Camera targetCamera)
|
||||
{
|
||||
if (targetCamera)
|
||||
{
|
||||
reflectionCameras.TryGetValue(targetCamera, out m_reflectionCamera);
|
||||
if (m_reflectionCamera)
|
||||
{
|
||||
return m_reflectionCamera.targetTexture;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 569d0b097a6f78843bff90729cbe4ef1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: -1426863774865177168, guid: 0000000000000000d000000000000000, type: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
110
LocalPackages/StylizedWater2/Runtime/Rendering/SetupConstants.cs
Normal file
110
LocalPackages/StylizedWater2/Runtime/Rendering/SetupConstants.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
#if URP
|
||||
using UnityEngine.Rendering.Universal;
|
||||
using UnityEngine.Rendering.Universal.Internal;
|
||||
|
||||
namespace StylizedWater2
|
||||
{
|
||||
public class SetupConstants : ScriptableRenderPass
|
||||
{
|
||||
private static readonly int _EnableDirectionalCaustics = Shader.PropertyToID("_EnableDirectionalCaustics");
|
||||
private static readonly int CausticsProjection = Shader.PropertyToID("CausticsProjection");
|
||||
private static readonly int _WaterSSREnabled = Shader.PropertyToID("_WaterSSREnabled");
|
||||
private static readonly int _WaterDisplacementPrePassAvailable = Shader.PropertyToID("_WaterDisplacementPrePassAvailable");
|
||||
|
||||
private bool m_directionalCaustics;
|
||||
|
||||
private static VisibleLight mainLight;
|
||||
private Matrix4x4 causticsProjection;
|
||||
|
||||
public SetupConstants()
|
||||
{
|
||||
//Force a unit scale, otherwise affects the projection tiling of the caustics
|
||||
causticsProjection = Matrix4x4.Scale(Vector3.one);
|
||||
}
|
||||
|
||||
private StylizedWaterRenderFeature settings;
|
||||
|
||||
public void Setup(StylizedWaterRenderFeature renderFeature)
|
||||
{
|
||||
this.settings = renderFeature;
|
||||
m_directionalCaustics = settings.directionalCaustics;
|
||||
}
|
||||
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
private ScriptableRenderPassInput requirements;
|
||||
#endif
|
||||
|
||||
public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
|
||||
{
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
//Inform the render pipeline which pre-passes are required
|
||||
requirements = ScriptableRenderPassInput.None;
|
||||
|
||||
//Only when using advanced shading, so don't forcibly enable
|
||||
//if(m_directionalCaustics) requirements = ScriptableRenderPassInput.Depth;
|
||||
|
||||
if (settings.screenSpaceReflectionSettings.enable)
|
||||
{
|
||||
requirements |= ScriptableRenderPassInput.Color | ScriptableRenderPassInput.Depth;
|
||||
}
|
||||
|
||||
if(settings.displacementPrePassSettings.enable) cmd.EnableShaderKeyword(DisplacementPrePass.KEYWORD);
|
||||
else cmd.DisableShaderKeyword(DisplacementPrePass.KEYWORD);
|
||||
|
||||
cmd.SetGlobalInt(_WaterSSREnabled, settings.screenSpaceReflectionSettings.enable ? 1 : 0);
|
||||
cmd.SetGlobalInt(_WaterDisplacementPrePassAvailable, settings.displacementPrePassSettings.enable ? 1 : 0);
|
||||
|
||||
ConfigureInput(requirements);
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
|
||||
{
|
||||
CommandBuffer cmd = CommandBufferPool.Get();
|
||||
|
||||
if (m_directionalCaustics)
|
||||
{
|
||||
//When no lights are visible, main light will be set to -1.
|
||||
if (renderingData.lightData.mainLightIndex > -1)
|
||||
{
|
||||
mainLight = renderingData.lightData.visibleLights[renderingData.lightData.mainLightIndex];
|
||||
|
||||
if (mainLight.lightType == LightType.Directional)
|
||||
{
|
||||
causticsProjection = Matrix4x4.Rotate(mainLight.light.transform.rotation);
|
||||
|
||||
cmd.SetGlobalMatrix(CausticsProjection, causticsProjection.inverse);
|
||||
}
|
||||
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
//Sets up the required View- -> Clip-space matrices
|
||||
NormalReconstruction.SetupProperties(cmd, renderingData.cameraData);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
m_directionalCaustics = false;
|
||||
}
|
||||
}
|
||||
|
||||
cmd.SetGlobalInt(_EnableDirectionalCaustics, m_directionalCaustics ? 1 : 0);
|
||||
|
||||
context.ExecuteCommandBuffer(cmd);
|
||||
CommandBufferPool.Release(cmd);
|
||||
}
|
||||
|
||||
public override void OnCameraCleanup(CommandBuffer cmd)
|
||||
{
|
||||
cmd.SetGlobalInt(_EnableDirectionalCaustics, 0);
|
||||
cmd.SetGlobalInt(_WaterSSREnabled, 0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Shader.SetGlobalInt(_WaterDisplacementPrePassAvailable, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fb6834bcc3854bbca9142a4bc547b968
|
||||
timeCreated: 1701078225
|
||||
@@ -0,0 +1,65 @@
|
||||
#if URP
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
namespace StylizedWater2
|
||||
{
|
||||
#if UNITY_2021_1_OR_NEWER
|
||||
[DisallowMultipleRendererFeature("Stylized Water 2")]
|
||||
#endif
|
||||
public class StylizedWaterRenderFeature : ScriptableRendererFeature
|
||||
{
|
||||
public static StylizedWaterRenderFeature GetDefault()
|
||||
{
|
||||
return (StylizedWaterRenderFeature)PipelineUtilities.GetRenderFeature<StylizedWaterRenderFeature>();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ScreenSpaceReflectionSettings
|
||||
{
|
||||
public bool enable;
|
||||
}
|
||||
public ScreenSpaceReflectionSettings screenSpaceReflectionSettings = new ScreenSpaceReflectionSettings();
|
||||
|
||||
[Tooltip("Project caustics from the main directional light.")]
|
||||
public bool directionalCaustics;
|
||||
|
||||
public DisplacementPrePass.Settings displacementPrePassSettings = new DisplacementPrePass.Settings();
|
||||
|
||||
private SetupConstants constantsSetup;
|
||||
private DisplacementPrePass displacementPass;
|
||||
|
||||
public override void Create()
|
||||
{
|
||||
constantsSetup = new SetupConstants
|
||||
{
|
||||
renderPassEvent = RenderPassEvent.BeforeRendering
|
||||
};
|
||||
|
||||
displacementPass = new DisplacementPrePass
|
||||
{
|
||||
renderPassEvent = RenderPassEvent.BeforeRendering
|
||||
};
|
||||
}
|
||||
|
||||
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
|
||||
{
|
||||
constantsSetup.Setup(this);
|
||||
renderer.EnqueuePass(constantsSetup);
|
||||
|
||||
if (displacementPrePassSettings.enable)
|
||||
{
|
||||
displacementPass.Setup(displacementPrePassSettings);
|
||||
renderer.EnqueuePass(displacementPass);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
displacementPass.Dispose();
|
||||
constantsSetup.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2086e3e143e14a2abcd3b0dbc77a10ac
|
||||
timeCreated: 1701077140
|
||||
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using StylizedWater2;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace NWH.DWP2.WaterData
|
||||
{
|
||||
#if NWH_DWP2
|
||||
public class StylizedWaterDataProvider : WaterDataProvider
|
||||
{
|
||||
[Tooltip("This reference is required to grab the wave distance and height values")]
|
||||
public Material waterMat;
|
||||
public enum WaterLevelSource
|
||||
{
|
||||
Value,
|
||||
Mesh
|
||||
}
|
||||
[Tooltip("Configure what should be used to set the base water level. Relative wave height is added to this value")]
|
||||
public WaterLevelSource waterLevelSource = WaterLevelSource.Value;
|
||||
[Tooltip("This reference is required to get the base water height. Relative wave height is added to this")]
|
||||
public MeshRenderer waterPlane;
|
||||
public float waterLevel;
|
||||
|
||||
[Tooltip("Enable if the wave settings are being changed at runtime. Incurs some overhead")]
|
||||
public bool dynamicMaterial;
|
||||
|
||||
private float m_waterLevel = 0f;
|
||||
private Vector3[] _normals;
|
||||
private int _prevArraySize;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
MeshRenderer r = GetComponent<MeshRenderer>();
|
||||
|
||||
if (r)
|
||||
{
|
||||
waterPlane = r;
|
||||
waterMat = r.sharedMaterial;
|
||||
}
|
||||
|
||||
waterLevel = this.transform.position.y;
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
if (!waterMat && waterPlane) waterMat = waterPlane.sharedMaterial;
|
||||
}
|
||||
|
||||
public override bool SupportsWaterHeightQueries()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool SupportsWaterNormalQueries()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool SupportsWaterFlowQueries()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void GetWaterHeights(NWH.DWP2.WaterObjects.WaterObject waterObject, ref Vector3[] points, ref float[] waterHeights)
|
||||
{
|
||||
var n = points.Length;
|
||||
|
||||
m_waterLevel = waterPlane && waterLevelSource == WaterLevelSource.Mesh ? waterPlane.transform.position.y : waterLevel;
|
||||
|
||||
// Resize array if data size changed
|
||||
if (n != _prevArraySize)
|
||||
{
|
||||
_normals = new Vector3[n];
|
||||
waterHeights = new float[n];
|
||||
|
||||
_prevArraySize = n;
|
||||
}
|
||||
|
||||
for (int i = 0; i < points.Length; i++)
|
||||
{
|
||||
waterHeights[i] = Buoyancy.SampleWaves(points[i], waterMat, m_waterLevel, 0f, dynamicMaterial, out _normals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public override void GetWaterNormals(NWH.DWP2.WaterObjects.WaterObject waterObject, ref Vector3[] points, ref Vector3[] waterNormals)
|
||||
{
|
||||
waterNormals = _normals; // Already queried in GetWaterHeights
|
||||
}
|
||||
|
||||
public override float GetWaterHeightSingle(NWH.DWP2.WaterObjects.WaterObject waterObject, Vector3 point)
|
||||
{
|
||||
return Buoyancy.SampleWaves(point, waterMat, m_waterLevel, 0f, dynamicMaterial, out _);
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomEditor(typeof(StylizedWaterDataProvider))]
|
||||
public class StylizedWaterDataProviderInspector : Editor
|
||||
{
|
||||
SerializedProperty waterMat;
|
||||
SerializedProperty dynamicMaterial;
|
||||
SerializedProperty waterLevelSource;
|
||||
SerializedProperty waterPlane;
|
||||
SerializedProperty waterLevel;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
waterMat = serializedObject.FindProperty("waterMat");
|
||||
dynamicMaterial = serializedObject.FindProperty("dynamicMaterial");
|
||||
waterLevelSource = serializedObject.FindProperty("waterLevelSource");
|
||||
waterPlane = serializedObject.FindProperty("waterPlane");
|
||||
waterLevel = serializedObject.FindProperty("waterLevel");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
EditorGUILayout.PropertyField(waterMat);
|
||||
|
||||
if (waterMat.objectReferenceValue == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("A water material must be assigned!", MessageType.Error);
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(dynamicMaterial);
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
EditorGUILayout.PrefixLabel("Water level source");
|
||||
waterLevelSource.intValue = GUILayout.Toolbar(waterLevelSource.intValue, new GUIContent[] { new GUIContent("Fixed Value"), new GUIContent("Mesh Object") });
|
||||
}
|
||||
|
||||
if (waterLevelSource.intValue == (int)StylizedWaterDataProvider.WaterLevelSource.Value) EditorGUILayout.PropertyField(waterLevel);
|
||||
if (waterLevelSource.intValue == (int)StylizedWaterDataProvider.WaterLevelSource.Mesh) EditorGUILayout.PropertyField(waterPlane);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f6878d8c33f6d147918f802eec4ddbf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 63c76fd48f9ad734da6fed8533f240a1, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
219
LocalPackages/StylizedWater2/Runtime/WaterGrid.cs
Normal file
219
LocalPackages/StylizedWater2/Runtime/WaterGrid.cs
Normal file
@@ -0,0 +1,219 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace StylizedWater2
|
||||
{
|
||||
[ExecuteInEditMode]
|
||||
[AddComponentMenu("Stylized Water 2/Water Grid")]
|
||||
public class WaterGrid : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Material used on the tile meshes")]
|
||||
public Material material;
|
||||
|
||||
[Tooltip("When not in play-mode, the water will follow the scene-view camera position.")]
|
||||
public bool followSceneCamera = false;
|
||||
[Tooltip("If enabled, the object with the \"MainCamera\" tag will be assigned as the follow target when entering play mode")]
|
||||
public bool autoAssignCamera;
|
||||
[Tooltip("The grid will follow this Transform's position on the XZ axis. Ideally set to the camera's transform.")]
|
||||
public Transform followTarget;
|
||||
|
||||
[Tooltip("Scale of the entire grid in the length and width")]
|
||||
public float scale = 500f;
|
||||
[Range(0.15f, 10f)]
|
||||
[Tooltip("Distance between vertices, rather higher than lower")]
|
||||
public float vertexDistance = 2f;
|
||||
[Min(1)]
|
||||
public int rowsColumns = 4;
|
||||
|
||||
[HideInInspector]
|
||||
public int m_rowsColumns = 4;
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
private Mesh mesh;
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
private List<WaterObject> objects = new List<WaterObject>();
|
||||
|
||||
[NonSerialized]
|
||||
private float tileSize;
|
||||
[NonSerialized]
|
||||
private WaterObject m_waterObject = null;
|
||||
[NonSerialized]
|
||||
private Transform actualFollowTarget;
|
||||
[NonSerialized]
|
||||
private Vector3 targetPosition;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public static bool DisplayGrid = true;
|
||||
public static bool DisplayWireframe;
|
||||
#endif
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
Recreate();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (autoAssignCamera) followTarget = Camera.main ? Camera.main.transform : followTarget;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.SceneView.duringSceneGui += OnSceneGUI;
|
||||
#endif
|
||||
m_rowsColumns = rowsColumns;
|
||||
|
||||
//Mesh is serialized with the scene, if component is used as a prefab, regenerate it
|
||||
if (mesh == null)
|
||||
{
|
||||
RecreateMesh();
|
||||
ReassignMesh();
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnDisable()
|
||||
{
|
||||
UnityEditor.SceneView.duringSceneGui -= OnSceneGUI;
|
||||
}
|
||||
#endif
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (Application.isPlaying) actualFollowTarget = followTarget;
|
||||
|
||||
if (actualFollowTarget)
|
||||
{
|
||||
targetPosition = actualFollowTarget.transform.position;
|
||||
|
||||
targetPosition = SnapToGrid(targetPosition, vertexDistance);
|
||||
targetPosition.y = this.transform.position.y;
|
||||
this.transform.position = targetPosition;
|
||||
}
|
||||
}
|
||||
|
||||
public void Recreate()
|
||||
{
|
||||
RecreateMesh();
|
||||
|
||||
bool requireRecreate = (m_rowsColumns != rowsColumns) || objects.Count < (rowsColumns * rowsColumns);
|
||||
if (requireRecreate) m_rowsColumns = rowsColumns;
|
||||
|
||||
//Only destroy/recreate objects if grid subdivision has changed
|
||||
if (requireRecreate && objects.Count > 0)
|
||||
{
|
||||
foreach (WaterObject obj in objects)
|
||||
{
|
||||
if (obj) DestroyImmediate(obj.gameObject);
|
||||
}
|
||||
objects.Clear();
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
for (int x = 0; x < rowsColumns; x++)
|
||||
{
|
||||
for (int z = 0; z < rowsColumns; z++)
|
||||
{
|
||||
if (requireRecreate)
|
||||
{
|
||||
m_waterObject = WaterObject.New(material, mesh);
|
||||
objects.Add(m_waterObject);
|
||||
|
||||
m_waterObject.transform.parent = this.transform;
|
||||
m_waterObject.name = "WaterTile_x" + x + "z" + z;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_waterObject = objects[index];
|
||||
m_waterObject.AssignMesh(mesh);
|
||||
m_waterObject.AssignMaterial(material);
|
||||
}
|
||||
|
||||
m_waterObject.transform.localPosition = GridLocalCenterPosition(x, z);
|
||||
m_waterObject.transform.localScale = Vector3.one;
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RecreateMesh()
|
||||
{
|
||||
rowsColumns = Mathf.Max(rowsColumns, 1);
|
||||
tileSize = Mathf.Max(1f, scale / rowsColumns);
|
||||
|
||||
mesh = WaterMesh.Create(WaterMesh.Shape.Rectangle, tileSize, vertexDistance, tileSize);
|
||||
}
|
||||
|
||||
private void ReassignMesh()
|
||||
{
|
||||
foreach (WaterObject obj in objects)
|
||||
{
|
||||
obj.AssignMesh(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector3 GridLocalCenterPosition(int x, int z)
|
||||
{
|
||||
return new Vector3(x * tileSize - ((tileSize * (rowsColumns)) * 0.5f) + (tileSize * 0.5f), 0f,
|
||||
z * tileSize - ((tileSize * (rowsColumns)) * 0.5f) + (tileSize * 0.5f));
|
||||
}
|
||||
|
||||
public static Vector3 SnapToGrid(Vector3 position, float cellSize)
|
||||
{
|
||||
return new Vector3(SnapToGrid(position.x, cellSize), SnapToGrid(position.y, cellSize), SnapToGrid(position.z, cellSize));
|
||||
}
|
||||
|
||||
private static float SnapToGrid(float position, float cellSize)
|
||||
{
|
||||
return Mathf.FloorToInt(position / cellSize) * (cellSize) + (cellSize * 0.5f);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
if (DisplayWireframe)
|
||||
{
|
||||
Gizmos.color = new Color(0, 0, 0, 0.5f);
|
||||
|
||||
foreach (WaterObject waterObject in objects)
|
||||
{
|
||||
if(waterObject.meshFilter.sharedMesh) Gizmos.DrawWireMesh(waterObject.meshFilter.sharedMesh, waterObject.transform.position);
|
||||
}
|
||||
}
|
||||
|
||||
if (DisplayGrid)
|
||||
{
|
||||
Gizmos.color = new Color(1f, 0.25f, 0.25f, 0.5f);
|
||||
Gizmos.matrix = this.transform.localToWorldMatrix;
|
||||
|
||||
for (int x = 0; x < rowsColumns; x++)
|
||||
{
|
||||
for (int z = 0; z < rowsColumns; z++)
|
||||
{
|
||||
Vector3 pos = GridLocalCenterPosition(x, z);
|
||||
|
||||
Gizmos.DrawWireCube(pos, new Vector3(tileSize, 0f, tileSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSceneGUI(UnityEditor.SceneView sceneView)
|
||||
{
|
||||
if (followSceneCamera)
|
||||
{
|
||||
actualFollowTarget = sceneView.camera.transform;
|
||||
Update();
|
||||
}
|
||||
else
|
||||
{
|
||||
actualFollowTarget = null;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
13
LocalPackages/StylizedWater2/Runtime/WaterGrid.cs.meta
Normal file
13
LocalPackages/StylizedWater2/Runtime/WaterGrid.cs.meta
Normal file
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6265ced51cdf0b94bb311b507ea5c6b6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- material: {fileID: 2100000, guid: fbb04271505a76f40b984e38071e86f3, type: 2}
|
||||
- followTarget: {instanceID: 0}
|
||||
executionOrder: 0
|
||||
icon: {fileID: 5243786984396574768, guid: 0000000000000000d000000000000000, type: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
241
LocalPackages/StylizedWater2/Runtime/WaterMesh.cs
Normal file
241
LocalPackages/StylizedWater2/Runtime/WaterMesh.cs
Normal file
@@ -0,0 +1,241 @@
|
||||
//Stylized Water 2
|
||||
//Staggart Creations (http://staggart.xyz)
|
||||
//Copyright protected under Unity Asset Store EULA
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
using Random = System.Random;
|
||||
|
||||
namespace StylizedWater2
|
||||
{
|
||||
[Serializable]
|
||||
public class WaterMesh
|
||||
{
|
||||
public enum Shape
|
||||
{
|
||||
Rectangle,
|
||||
Disk
|
||||
}
|
||||
public Shape shape;
|
||||
|
||||
[FormerlySerializedAs("size")]
|
||||
[Range(10, 1000)]
|
||||
public float scale = 100f;
|
||||
[Tooltip("Distance between vertices")]
|
||||
[Range(0.15f, 10f)]
|
||||
public float vertexDistance = 1f;
|
||||
|
||||
public float UVTiling = 1f;
|
||||
[Tooltip("Shifts the vertices in a random direction. Definitely use this when using flat shading")]
|
||||
[Range(0f, 1f)]
|
||||
public float noise;
|
||||
[Min(0)]
|
||||
[Tooltip("The surface is normally flat, yet vertex displacement on the GPU such as waves can give the surface artificial height." +
|
||||
"\n\nThis can cause a Mesh Renderer to be prematurely culled, despite still actually being visible." +
|
||||
"\n\nThis value adds an artificial amount of height to the generate mesh's bounds, to avoid this from happening.")]
|
||||
public float boundsPadding = 4f;
|
||||
|
||||
/// <summary>
|
||||
/// Generated output mesh. Empty by default, use the Rebuild() function to generate one from the current settings.
|
||||
/// </summary>
|
||||
public Mesh mesh;
|
||||
|
||||
public Mesh Rebuild()
|
||||
{
|
||||
switch (shape)
|
||||
{
|
||||
case Shape.Rectangle: mesh = CreatePlane();
|
||||
break;
|
||||
case Shape.Disk: mesh = CreateCircle();
|
||||
break;
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
public static Mesh Create(Shape shape, float size, float vertexDistance, float uvTiling = 1f, float noise = 0f)
|
||||
{
|
||||
WaterMesh waterMesh = new WaterMesh();
|
||||
waterMesh.shape = shape;
|
||||
waterMesh.scale = size;
|
||||
waterMesh.vertexDistance = vertexDistance;
|
||||
waterMesh.UVTiling = uvTiling;
|
||||
waterMesh.noise = noise;
|
||||
|
||||
return waterMesh.Rebuild();
|
||||
}
|
||||
|
||||
// Get the index of point number 'x' in circle number 'c'
|
||||
private int GetPointIndex(int c, int x)
|
||||
{
|
||||
if (c < 0) return 0;
|
||||
|
||||
x = x % ((c + 1) * 6);
|
||||
|
||||
return (3 * c * (c + 1) + x + 1);
|
||||
}
|
||||
|
||||
private Mesh CreateCircle()
|
||||
{
|
||||
Mesh m = new Mesh();
|
||||
m.name = "WaterDisk";
|
||||
|
||||
int subdivisions = Mathf.FloorToInt(scale / vertexDistance);
|
||||
|
||||
float distance = 1f / subdivisions;
|
||||
|
||||
var vertices = new List<Vector3>();
|
||||
var uvs = new List<Vector2>();
|
||||
var uvs2 = new List<Vector2>();
|
||||
vertices.Add(Vector3.zero); //Center
|
||||
var tris = new List<int>();
|
||||
|
||||
// First pass => build vertices
|
||||
for (int loop = 0; loop < subdivisions; loop++)
|
||||
{
|
||||
float angleStep = (Mathf.PI * 2f) / ((loop + 1) * 6);
|
||||
for (int point = 0; point < (loop + 1) * 6; ++point)
|
||||
{
|
||||
Vector3 vPos = new Vector3(
|
||||
Mathf.Sin(angleStep * point) ,
|
||||
0f,
|
||||
Mathf.Cos(angleStep * point));
|
||||
|
||||
UnityEngine.Random.InitState(loop + point);
|
||||
vPos.x += UnityEngine.Random.Range(-noise * 0.01f, noise * 0.01f);
|
||||
vPos.z -= UnityEngine.Random.Range(noise * 0.01f, -noise * 0.01f);
|
||||
|
||||
vertices.Add(vPos * (scale * 0.5f) * distance * (loop + 1));
|
||||
}
|
||||
}
|
||||
|
||||
//Planar mapping
|
||||
for (int i = 0; i < vertices.Count; i++)
|
||||
{
|
||||
uvs.Add(new Vector2(0.5f + (vertices[i].x) * UVTiling,0.5f + (vertices[i].z) * UVTiling));
|
||||
//Lightmap UV's
|
||||
uvs2.Add(new Vector2(0.5f + (vertices[i].x / scale),0.5f + (vertices[i].z / scale)));
|
||||
}
|
||||
|
||||
// Second pass => connect vertices into triangles
|
||||
for (int circ = 0; circ < subdivisions; ++circ)
|
||||
{
|
||||
for (int point = 0, other = 0; point < (circ + 1) * 6; ++point)
|
||||
{
|
||||
if (point % (circ + 1) != 0)
|
||||
{
|
||||
// Create 2 triangles
|
||||
tris.Add(GetPointIndex(circ - 1, other + 1));
|
||||
tris.Add(GetPointIndex(circ - 1, other));
|
||||
tris.Add(GetPointIndex(circ, point));
|
||||
|
||||
tris.Add(GetPointIndex(circ, point));
|
||||
tris.Add(GetPointIndex(circ, point + 1));
|
||||
tris.Add(GetPointIndex(circ - 1, other + 1));
|
||||
++other;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create 1 inverse triangle
|
||||
tris.Add(GetPointIndex(circ, point));
|
||||
tris.Add(GetPointIndex(circ, point + 1));
|
||||
tris.Add(GetPointIndex(circ - 1, other));
|
||||
// Do not move to the next point in the smaller circle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the mesh
|
||||
|
||||
m.SetVertices(vertices);
|
||||
m.SetTriangles(tris, 0);
|
||||
m.RecalculateNormals();
|
||||
m.RecalculateTangents();
|
||||
m.SetUVs(0, uvs);
|
||||
m.SetUVs(1, uvs2);
|
||||
m.colors = new Color[vertices.Count];
|
||||
|
||||
m.bounds = new Bounds(Vector3.zero, new Vector3(scale, boundsPadding, scale));
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
private Mesh CreatePlane()
|
||||
{
|
||||
Mesh m = new Mesh();
|
||||
m.name = "WaterPlane";
|
||||
|
||||
scale = Mathf.Max(1f, scale);
|
||||
int subdivisions = Mathf.FloorToInt(scale / vertexDistance);
|
||||
|
||||
int xCount = subdivisions + 1;
|
||||
int zCount = subdivisions + 1;
|
||||
int numTriangles = subdivisions * subdivisions * 6;
|
||||
int numVertices = xCount * zCount;
|
||||
|
||||
Vector3[] vertices = new Vector3[numVertices];
|
||||
Vector2[] uvs = new Vector2[numVertices];
|
||||
Vector2[] uvs2 = new Vector2[numVertices];
|
||||
int[] triangles = new int[numTriangles];
|
||||
Vector4[] tangents = new Vector4[numVertices];
|
||||
Vector3[] normals = new Vector3[numVertices];
|
||||
Vector4 tangent = new Vector4(1f, 0f, 0f, -1f);
|
||||
|
||||
int index = 0;
|
||||
float scaleX = scale / subdivisions;
|
||||
float scaleY = scale / subdivisions;
|
||||
|
||||
float noiseScale = vertexDistance * 0.5f;
|
||||
|
||||
for (int z = 0; z < zCount; z++)
|
||||
{
|
||||
for (int x = 0; x < xCount; x++)
|
||||
{
|
||||
vertices[index] = new Vector3(x * scaleX - (scale * 0.5f), 0f, z * scaleY - (scale * 0.5f));
|
||||
|
||||
UnityEngine.Random.InitState(index);
|
||||
vertices[index].x += UnityEngine.Random.Range(-noise * noiseScale, noise * noiseScale);
|
||||
vertices[index].z -= UnityEngine.Random.Range(noise * noiseScale, -noise * noiseScale);
|
||||
|
||||
tangents[index] = tangent;
|
||||
uvs[index] = new Vector2(0.5f + (vertices[index].x) * UVTiling, 0.5f + (vertices[index].z) * UVTiling);
|
||||
//Lightmap UV's
|
||||
uvs2[index] = new Vector2(0.5f + vertices[index].x / scale, 0.5f + vertices[index].z / scale);
|
||||
normals[index] = Vector3.up;
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
index = 0;
|
||||
for (int z = 0; z < subdivisions; z++)
|
||||
{
|
||||
for (int x = 0; x < subdivisions; x++)
|
||||
{
|
||||
triangles[index] = (z * xCount) + x;
|
||||
triangles[index + 1] = ((z + 1) * xCount) + x;
|
||||
triangles[index + 2] = (z * xCount) + x + 1;
|
||||
|
||||
triangles[index + 3] = ((z + 1) * xCount) + x;
|
||||
triangles[index + 4] = ((z + 1) * xCount) + x + 1;
|
||||
triangles[index + 5] = (z * xCount) + x + 1;
|
||||
index += 6;
|
||||
}
|
||||
}
|
||||
|
||||
m.vertices = vertices;
|
||||
m.triangles = triangles;
|
||||
m.uv = uvs;
|
||||
m.uv2 = uvs2;
|
||||
m.tangents = tangents;
|
||||
m.normals = normals;
|
||||
m.colors = new Color[vertices.Length];
|
||||
m.bounds = new Bounds(Vector3.zero, new Vector3(scale, boundsPadding, scale));
|
||||
|
||||
return m;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
LocalPackages/StylizedWater2/Runtime/WaterMesh.cs.meta
Normal file
11
LocalPackages/StylizedWater2/Runtime/WaterMesh.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f4d8d3a5060ac547a2664e9e46d2c4a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
203
LocalPackages/StylizedWater2/Runtime/WaterObject.cs
Normal file
203
LocalPackages/StylizedWater2/Runtime/WaterObject.cs
Normal file
@@ -0,0 +1,203 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace StylizedWater2
|
||||
{
|
||||
/// <summary>
|
||||
/// Attached to every mesh using the Stylized Water 2 shader
|
||||
/// Provides a generic way of identifying water objects and accessing their properties
|
||||
/// </summary>
|
||||
[ExecuteInEditMode]
|
||||
[AddComponentMenu("Stylized Water 2/Water Object")]
|
||||
[DisallowMultipleComponent]
|
||||
public class WaterObject : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Collection of all available WaterObject instances. Instances (un)register themselves in the OnEnable/OnDisable functions.
|
||||
/// </summary>
|
||||
public static readonly List<WaterObject> Instances = new List<WaterObject>();
|
||||
|
||||
public Material material;
|
||||
public MeshFilter meshFilter;
|
||||
public MeshRenderer meshRenderer;
|
||||
|
||||
private static Vector3 s_PositionOffset;
|
||||
private static readonly int _WaterPositionOffset = Shader.PropertyToID("_WaterPositionOffset");
|
||||
/// <summary>
|
||||
/// For use with floating-point origin systems. In the shader, the world-position (used for UV coordinates) will be offset by this value.
|
||||
/// Buoyancy calculations will also be offset to stay in sync.
|
||||
/// </summary>
|
||||
public static Vector3 PositionOffset
|
||||
{
|
||||
set
|
||||
{
|
||||
s_PositionOffset = value;
|
||||
Shader.SetGlobalVector(_WaterPositionOffset, s_PositionOffset);
|
||||
}
|
||||
internal get => s_PositionOffset;
|
||||
}
|
||||
|
||||
private static float m_customTimeValue = -1f;
|
||||
private static readonly int CustomTimeID = Shader.PropertyToID("_CustomTime");
|
||||
|
||||
/// <summary>
|
||||
/// Pass in any time value, any kind of animations will use this as a time index, including wave animations (and thus buoyancy calculations as well).
|
||||
/// This is typically used for network synchronized waves or cutscenes.
|
||||
/// To revert to using normal time, pass in a value lower than 0.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public static float CustomTime
|
||||
{
|
||||
set
|
||||
{
|
||||
m_customTimeValue = value;
|
||||
Shader.SetGlobalFloat(CustomTimeID, m_customTimeValue);
|
||||
}
|
||||
internal get => m_customTimeValue;
|
||||
}
|
||||
|
||||
private MaterialPropertyBlock _props;
|
||||
public MaterialPropertyBlock props
|
||||
{
|
||||
get
|
||||
{
|
||||
//Fetch when required, execution order makes it unreliable otherwise
|
||||
if (_props == null)
|
||||
{
|
||||
CreatePropertyBlock(meshRenderer);
|
||||
}
|
||||
return _props;
|
||||
}
|
||||
private set => _props = value;
|
||||
}
|
||||
|
||||
private void CreatePropertyBlock(Renderer sourceRenderer)
|
||||
{
|
||||
_props = new MaterialPropertyBlock();
|
||||
sourceRenderer.GetPropertyBlock(_props);
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
meshRenderer = GetComponent<MeshRenderer>();
|
||||
CreatePropertyBlock(meshRenderer);
|
||||
meshFilter = GetComponent<MeshFilter>();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
Instances.Add(this);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
Instances.Remove(this);
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
if (!meshRenderer) meshRenderer = GetComponent<MeshRenderer>();
|
||||
if (!meshFilter) meshFilter = GetComponent<MeshFilter>();
|
||||
FetchWaterMaterial();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grabs the material from the attached Mesh Renderer
|
||||
/// </summary>
|
||||
public Material FetchWaterMaterial()
|
||||
{
|
||||
if (meshRenderer)
|
||||
{
|
||||
material = meshRenderer.sharedMaterial;
|
||||
return material;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies to changes made to the Material Property Blocks ('props' property)
|
||||
/// </summary>
|
||||
public void ApplyInstancedProperties()
|
||||
{
|
||||
if(props != null) meshRenderer.SetPropertyBlock(props);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the position is below the maximum possible wave height. Can be used as a fast broad-phase check, before actually using the more expensive SampleWaves function
|
||||
/// </summary>
|
||||
/// <param name="position"></param>
|
||||
public bool CanTouch(Vector3 position)
|
||||
{
|
||||
return Buoyancy.CanTouchWater(position, this);
|
||||
}
|
||||
|
||||
public void AssignMesh(Mesh mesh)
|
||||
{
|
||||
if (meshFilter) meshFilter.sharedMesh = mesh;
|
||||
}
|
||||
|
||||
public void AssignMaterial(Material newMaterial)
|
||||
{
|
||||
if (meshRenderer) meshRenderer.sharedMaterial = newMaterial;
|
||||
material = newMaterial;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GameObject with a MeshFilter, MeshRenderer and WaterObject component
|
||||
/// </summary>
|
||||
/// <param name="waterMaterial">If assigned, this material is automatically added to the MeshRenderer</param>
|
||||
/// <returns></returns>
|
||||
public static WaterObject New(Material waterMaterial = null, Mesh mesh = null)
|
||||
{
|
||||
GameObject go = new GameObject("Water Object", typeof(MeshFilter), typeof(MeshRenderer), typeof(WaterObject));
|
||||
go.layer = LayerMask.NameToLayer("Water");
|
||||
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.Undo.RegisterCreatedObjectUndo(go, "Created Water Object");
|
||||
#endif
|
||||
|
||||
WaterObject waterObject = go.GetComponent<WaterObject>();
|
||||
|
||||
waterObject.meshRenderer = waterObject.gameObject.GetComponent<MeshRenderer>();
|
||||
waterObject.meshFilter = waterObject.gameObject.GetComponent<MeshFilter>();
|
||||
|
||||
waterObject.meshFilter.sharedMesh = mesh;
|
||||
waterObject.meshRenderer.sharedMaterial = waterMaterial;
|
||||
waterObject.meshRenderer.shadowCastingMode = ShadowCastingMode.Off;
|
||||
waterObject.material = waterMaterial;
|
||||
|
||||
return waterObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to find the WaterObject above or below the position. Checks against the bounds of ALL Water Object meshes by raycasting on the XZ plane
|
||||
/// </summary>
|
||||
/// <param name="position">Position in world-space (height is not relevant)</param>
|
||||
/// <param name="rotationSupport">Unless this is true, water rotated on the Y-axis will yield incorrect results (but is faster)</param>
|
||||
/// <returns></returns>
|
||||
public static WaterObject Find(Vector3 position, bool rotationSupport)
|
||||
{
|
||||
Ray ray = new Ray(position + (Vector3.up * 1000f), Vector3.down);
|
||||
|
||||
foreach (WaterObject obj in Instances)
|
||||
{
|
||||
if (rotationSupport)
|
||||
{
|
||||
//Local space
|
||||
ray.origin = obj.transform.InverseTransformPoint(ray.origin);
|
||||
if (obj.meshFilter.sharedMesh.bounds.IntersectRay(ray)) return obj;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Axis-aligned bounds
|
||||
if (obj.meshRenderer.bounds.IntersectRay(ray)) return obj;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
LocalPackages/StylizedWater2/Runtime/WaterObject.cs.meta
Normal file
11
LocalPackages/StylizedWater2/Runtime/WaterObject.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52480f6baf9b7b0428c75481cb3d5a6d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 83548767ebe1791409159648329da97d, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
85
LocalPackages/StylizedWater2/Runtime/WaveParameters.cs
Normal file
85
LocalPackages/StylizedWater2/Runtime/WaveParameters.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace StylizedWater2
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class to retrieve and store a water material's wave settings
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class WaveParameters
|
||||
{
|
||||
private const string WavesKeyword = "_WAVES";
|
||||
|
||||
private static int _Direction = Shader.PropertyToID("_Direction");
|
||||
private static int _Speed = Shader.PropertyToID("_Speed"
|
||||
);
|
||||
private static int _WaveDistance = Shader.PropertyToID("_WaveDistance");
|
||||
private static int _WaveSpeed = Shader.PropertyToID("_WaveSpeed");
|
||||
private static int _WaveHeight = Shader.PropertyToID("_WaveHeight");
|
||||
private static int _WaveSteepness = Shader.PropertyToID("_WaveSteepness");
|
||||
private static int _WaveCount = Shader.PropertyToID("_WaveCount");
|
||||
private static int _WaveDirection = Shader.PropertyToID("_WaveDirection");
|
||||
|
||||
public Vector2 animationDirection;
|
||||
public float animationSpeed;
|
||||
|
||||
public int count;
|
||||
public float distance;
|
||||
public float speed;
|
||||
public float height;
|
||||
public float steepness;
|
||||
public Vector4 direction;
|
||||
|
||||
public static bool WavesEnabled(Material waterMat)
|
||||
{
|
||||
if (!waterMat) return false;
|
||||
|
||||
return waterMat.IsKeywordEnabled(WavesKeyword);
|
||||
}
|
||||
|
||||
public static float GetMaxWaveHeight(Material mat)
|
||||
{
|
||||
return mat.GetFloat(_WaveHeight);
|
||||
}
|
||||
|
||||
public void Update(Material waterMat)
|
||||
{
|
||||
animationDirection = waterMat.GetVector(_Direction);
|
||||
animationSpeed = waterMat.GetFloat(_Speed);
|
||||
|
||||
speed = waterMat.GetFloat(_WaveSpeed);
|
||||
distance = waterMat.GetFloat(_WaveDistance);
|
||||
steepness = waterMat.GetFloat(_WaveSteepness);
|
||||
height = waterMat.GetFloat(_WaveHeight);
|
||||
count = waterMat.GetInt(_WaveCount);
|
||||
direction = waterMat.GetVector(_WaveDirection);
|
||||
}
|
||||
|
||||
public void SetAsGlobal()
|
||||
{
|
||||
Shader.SetGlobalVector(_Direction, animationDirection);
|
||||
Shader.SetGlobalFloat(_Speed, animationSpeed);
|
||||
|
||||
Shader.SetGlobalFloat(_WaveSpeed, speed);
|
||||
Shader.SetGlobalFloat(_WaveDistance, distance);
|
||||
Shader.SetGlobalFloat(_WaveSteepness, steepness);
|
||||
Shader.SetGlobalFloat(_WaveHeight, height);
|
||||
Shader.SetGlobalFloat(_WaveCount, count);
|
||||
Shader.SetGlobalVector(_WaveDirection, direction);
|
||||
}
|
||||
|
||||
public void Apply(Material mat)
|
||||
{
|
||||
mat.SetVector(_Direction, animationDirection);
|
||||
mat.SetFloat(_Speed, animationSpeed);
|
||||
|
||||
mat.SetFloat(_WaveSpeed, speed);
|
||||
mat.SetFloat(_WaveDistance, distance);
|
||||
mat.SetFloat(_WaveSteepness, steepness);
|
||||
mat.SetFloat(_WaveHeight, height);
|
||||
mat.SetInt(_WaveCount, count);
|
||||
mat.SetVector(_WaveDirection, direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 99230946af6b43d59e5588b131421f2d
|
||||
timeCreated: 1616150513
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "sc.stylizedwater2.runtime",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:15fc0a57446b3144c949da3e2b9737a9",
|
||||
"GUID:df380645f10b7bc4b97d4f5eb6303d95",
|
||||
"GUID:d8b63aba1907145bea998dd612889d6b",
|
||||
"GUID:75ecb28acc33857438e533566abcb3be",
|
||||
"GUID:f06555f75b070af458a003d92f9efb00",
|
||||
"GUID:9cb5eaf8df574e829047543e7b48b611"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.unity.mathematics",
|
||||
"expression": "1.1.0",
|
||||
"define": "MATHEMATICS"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.render-pipelines.universal",
|
||||
"expression": "10.3.2",
|
||||
"define": "URP"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.visualeffectgraph",
|
||||
"expression": "10.3.2",
|
||||
"define": "VFX_GRAPH"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.splines",
|
||||
"expression": "2.0.0",
|
||||
"define": "SPLINES"
|
||||
}
|
||||
],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 93226a8cd37f67d4a996a525146f6f09
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user