(Update) Clean Projet

This commit is contained in:
2026-06-24 14:38:51 +02:00
parent 48588ccfed
commit ef01552268
2629 changed files with 1523 additions and 430219 deletions
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e8749e501856428a81e8994b0d49aa82
timeCreated: 1728748280
@@ -0,0 +1,391 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using System;
using Unity.Mathematics;
using UnityEngine;
namespace StylizedWater3
{
/// <summary>
/// Samples the water height at 4 points around the Transform, and snaps the Y-position to the average.
/// From these 4 points a normal direction can also be derived, which is used to orient the transform's rotation
/// - This script is a prime example of how the Height Query System may be used.
/// </summary>
[ExecuteInEditMode]
[AddComponentMenu("Stylized Water 3/Align Transform To Water")]
public class AlignToWater : MonoBehaviour
{
//Because there two completely different methods of sampling the water's height, this Interface class provides a way to specify which is to be used.
//In the case of the CPU-method, it contains everything else needed for such a query (eg. water level and material)
public HeightQuerySystem.Interface heightInterface = new HeightQuerySystem.Interface();
public Vector2 surfaceSize = new Vector2(0.5f, 0.5f);
[Tooltip("Assign an optional transform to follow on the XZ axis.\n\nThis may be used if you want to use this component to read the water height at another transform's position.")]
public Transform followTarget;
public float heightOffset;
[Range(0f, 8f)]
[Tooltip("Controls how strongly the transform should rotate to align with the wave curvature")]
public float rollAmount = 0.1f;
[Tooltip("Add a Y-axis rotation to the transform. Note that this technically results in an incorrect alignment.")]
[Range(0f, 360f)]
public float rotation = 0f;
[Tooltip("Smoothly blend towards the newly calculated position and rotation. May be used to combat jittering")]
[Min(0f)]
public float smoothing = 0.1f;
private Vector3 normal;
private float height;
public enum HeightValue
{
Average,
Maximum
}
public HeightValue heightValue;
//A sampler defines:
// • An array of world-space input positions to sample the height at.
// • A float array (of equal size) that stores the output height values.
// Samplers must always be initialized by specifying how many sample points are needed.
private HeightQuerySystem.Sampler heightSampler;
//A request ask the Height Query System to return the water height at every one of the sampler's positions from the GPU
//It contains a callback event that needs to be subscribed to, to let you know when the request is completed
private HeightQuerySystem.AsyncRequest heightRequest;
#pragma warning disable 108,114 //New keyword
public Rigidbody rigidbody;
#pragma warning restore 108,114
private Vector3 m_targetNormal;
private float m_targetHeight;
#if UNITY_EDITOR
public static bool EnableInEditor
{
get { return UnityEditor.EditorPrefs.GetBool("SW3_BUOYANCY_EDITOR_ENABLED", true); }
set { UnityEditor.EditorPrefs.SetBool("SW3_BUOYANCY_EDITOR_ENABLED", value); }
}
#endif
private void Start()
{
rigidbody = GetComponent<Rigidbody>();
if (rigidbody && rigidbody.useGravity)
{
Debug.LogWarning($"[Align Transform To Water] Disabled gravity on RigidBody \"{rigidbody.name}\". Otherwise its position can't be set without it struggling back");
rigidbody.useGravity = false;
}
}
private void OnEnable()
{
#if UNITY_EDITOR
if(Application.isPlaying == false) UnityEditor.SceneView.duringSceneGui += DuringSceneViewUpdate;
#endif
//Sampler uses 4 sampling points, one for each corner of the rectangle/plane
//In the context of a physics-based buoyancy system a sampler will use more than 4 sampling points
heightSampler = new HeightQuerySystem.Sampler();
heightSampler.SetSampleCount(4);
//Only when using the GPU-readback method does the HeightQuerySystem come into play
if (heightInterface.method == HeightQuerySystem.Interface.Method.GPU)
{
//Create a new request with the sampler created above.
//Do this just once! It'll keep going until you call "Dispose()" on it.
//Using the object's hashcode ensures the request gets a unique ID
heightRequest = new HeightQuerySystem.AsyncRequest(this.GetHashCode(), heightSampler, this.name);
//Issue the request so the system starts populating the "heightSampler" with data
heightRequest.Issue();
//True by default. This checks if the returned water height values are valid. If not, the value remains the same as last frame
//This avoids objects that are not above any water surface, or fall outside the camera frustum, from falling down to -1000 heights
heightRequest.invalidateMisses = true;
//You may also call .Withdraw() if you wish to only temporarily remove the request from the system. Then call .Issue() again when needed.
//This is also necessary when looking to call heightSampler.SetSampleCount() again.
//Doing so avoids reallocating its resources but only temporarily takes it out of the running.
//Subscribe to the event that indicates that data was retrieved from the GPU
//Important to note that this may be a few (rendering) frames later than when the request was issued (at least 1)
heightRequest.onCompleted += OnHeightRequestComplete;
}
prevHeight = this.transform.position.y;
height = prevHeight;
}
#if UNITY_EDITOR
void DuringSceneViewUpdate(UnityEditor.SceneView sceneView)
{
FixedUpdate();
}
#endif
private void OnHeightRequestComplete()
{
//Debug.Log("Height request returned");
//Use the data. It pre-presents the water geometry's height value at each of the requested sample positions
float xNeg = heightSampler.heightValues[0];
float xPos = heightSampler.heightValues[1];
float zNeg = heightSampler.heightValues[2];
float zPos = heightSampler.heightValues[3];
float newHeight = 0f;
if (heightValue == HeightValue.Average)
{
newHeight = xNeg + xPos + zNeg + zPos;
newHeight /= 4f;
}
if (heightValue == HeightValue.Maximum)
{
newHeight = Mathf.Max(Mathf.Max(xNeg, xPos), Mathf.Max(zNeg, zPos));
}
newHeight += heightOffset;
if (float.IsNaN(newHeight))
{
#if SWS_DEV
Debug.LogError("Height is NaN");
#endif
//May occur during the first run
newHeight = 0f;
}
if (heightInterface.method == HeightQuerySystem.Interface.Method.GPU)
{
//If all the samples were taking at a point where no water was visible, the height values would be -1000f.
//Avoid setting an invalid height to prevent objects from sinking way down, instead keep them at the last valid height
if (HeightQuerySystem.EqualsVoid(newHeight))
{
UpdateSamplePositions();
return;
}
}
height = newHeight;
//Using 4 samples in a plus-shape pattern, a normal can be derived from the height differences
normal = HeightQuerySystem.DeriveNormal(
xNeg, xPos,
zNeg, zPos,
rollAmount);
//Note: In a physics-based buoyancy system, calculating the normal will not be necessary. The varying upward forces will naturally align an object to the water.
//Reading back the water surface normals directly will not be possible, since no such information is available (requires a deferred rendering water system)
//Only updating the sampling positions here, for the next query. There would be no point in doing so for frames where the height request may not be returned
UpdateSamplePositions();
}
private void Reset()
{
//Auto-assign water object if there is only one
if (heightInterface.waterObject == null && WaterObject.Instances.Count > 0)
{
heightInterface.waterObject = WaterObject.Instances[0];
#if UNITY_EDITOR
UnityEditor.EditorUtility.SetDirty(this);
#endif
}
AutoCalculateSurfaceSizeFromMeshes();
}
private void OnDisable()
{
#if UNITY_EDITOR
if(Application.isPlaying == false) UnityEditor.SceneView.duringSceneGui -= DuringSceneViewUpdate;
#endif
if (heightRequest != null)
{
//Unsubscribe
heightRequest.onCompleted -= OnHeightRequestComplete;
//Important! Remove the request from the system so that it de-allocates the memory it uses.
heightRequest.Dispose();
heightRequest = null;
}
//Also dispose the height sampler. This stores two arrays that need to be de-allocated
//Note: When using the GPU method, disposing 'heightRequest' will already do this!
heightSampler.Dispose();
heightSampler = null;
}
public void FixedUpdate()
{
if (!this.enabled) return;
#if UNITY_EDITOR
if (!EnableInEditor && Application.isPlaying == false) return;
#endif
//When using the CPU (wave pattern replication) method
if (heightInterface.method == HeightQuerySystem.Interface.Method.CPU)
{
//The water object contains the material, and may be used to define a water level
heightInterface.GetWaterObject(this.transform.position);
//A reference to a Water Object is required, which in turn contains a material
if (heightInterface.HasMissingReferences()) return;
//This function reproduces the water shader's wave vertex animation.
Gerstner.ComputeHeight(heightSampler, heightInterface);
//At this point the 'heightSampler' has new height values, so can go right ahead and use them
OnHeightRequestComplete();
ApplyTransform();
}
else
{
ApplyTransform();
}
}
//Translates the corners of the bounds to the 4 sampling points (plus-shaped pattern)
//From local-space to world-space.
private void UpdateSamplePositions()
{
//Note: Should the number of sample positions change in realtime, that creates an issue where resources have to be destroyed and recreated. Avoid doing so!
//Should it be needed, do so by:
//• heightRequest.Withdraw(); //Remove the active request for data
//• heightSampler.SetSampleCount(count). //This will automatically reallocate the arrays if the count was changed.
//• heightRequest.Issue(); //Re-issue the request with the new number of samples
// ┤
heightSampler.SetSamplePosition(0, ConvertToWorldSpace(new Vector3(-surfaceSize.x * 0.5f, 0, 0)));
// ├
heightSampler.SetSamplePosition(1, ConvertToWorldSpace(new Vector3(surfaceSize.x * 0.5f, 0, 0)));
// ┬
heightSampler.SetSamplePosition(2, ConvertToWorldSpace(new Vector3(0, 0, -surfaceSize.y * 0.5f)));
// ┴
heightSampler.SetSamplePosition(3, ConvertToWorldSpace(new Vector3(0, 0, surfaceSize.y * 0.5f)));
}
private Vector3 prevNormal = new Vector3(0, 1, 0);
private float prevHeight;
private void ApplyTransform()
{
//Smooth transition to new normal of this frame, particularly reduces jittering on rigid bodies
if (smoothing > 0)
{
m_targetHeight = Mathf.Lerp(prevHeight, height, Time.deltaTime / smoothing);
prevHeight = m_targetHeight;
m_targetNormal = Vector3.Lerp(prevNormal, normal, Time.smoothDeltaTime / smoothing);
prevNormal = m_targetNormal;
}
else
{
prevHeight = height;
prevNormal = normal;
m_targetHeight = height;
m_targetNormal = normal;
}
var position = this.transform.position;
if (followTarget)
{
position.x = followTarget.position.x;
position.z = followTarget.position.z;
}
position.y = m_targetHeight;
//Setting the normal of a transform directly overrides any and all external rotations
//If the component is attached an object with a mesh, move the mesh into a child object and rotate that instead
var newRotation = Quaternion.FromToRotation(Vector3.up, m_targetNormal);
newRotation *= quaternion.RotateY(rotation * Mathf.Deg2Rad);
if (Application.isPlaying && rigidbody)
{
if (rollAmount > 0)
{
rigidbody.Move(position, newRotation);
}
else
{
rigidbody.MovePosition(position);
}
}
else
{
if (rollAmount > 0)
{
this.transform.SetPositionAndRotation(position, newRotation);
}
else
{
this.transform.position = position;
}
}
}
private Vector3 ConvertToWorldSpace(Vector3 position)
{
return this.transform.TransformPoint(position);
}
/// <summary>
/// Automatically grab a starting point for the area size, based on the attached mesh(es)
/// </summary>
public void AutoCalculateSurfaceSizeFromMeshes()
{
MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>();
int meshCount = meshFilters.Length;
if (meshCount > 0)
{
surfaceSize.x = 0f;
surfaceSize.y = 0f;
Bounds bounds = new Bounds();
Vector3 minSum = Vector3.one * Mathf.Infinity;
Vector3 maxSum = Vector3.one * Mathf.NegativeInfinity;
for (int i = 0; i < meshCount; i++)
{
minSum = Vector3.Min(minSum, meshFilters[i].sharedMesh.bounds.min);
maxSum = Vector3.Max(maxSum, meshFilters[i].sharedMesh.bounds.max);
}
bounds.SetMinMax(minSum, maxSum);
surfaceSize.x = bounds.size.x;
surfaceSize.y = bounds.size.z;
}
}
private void OnDrawGizmosSelected()
{
if (heightSampler != null && heightSampler.IsCreated())
{
foreach (Vector3 p in heightSampler.positions)
{
Gizmos.DrawWireCube(p - (Vector3.up * heightOffset), Vector3.one * 0.2f);
}
}
Gizmos.DrawLine(this.transform.position, this.transform.position + (m_targetNormal * 2f));
Gizmos.matrix = this.transform.localToWorldMatrix;
Gizmos.DrawWireCube(Vector3.zero - (Vector3.up * heightOffset), new Vector3(surfaceSize.x, 0f, surfaceSize.y));
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 95144392e6eede14cafa2d339e5c1ee9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 148978298399363526, guid: 0000000000000000d000000000000000, type: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/Components/AlignToWater.cs
uploadId: 895866
@@ -0,0 +1,259 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
namespace StylizedWater3
{
[ExecuteAlways]
[AddComponentMenu("")] //Hide, only to be used with the prefab
public class OceanFollowBehaviour : MonoBehaviour
{
public static OceanFollowBehaviour Instance;
const int LODCount = 7;
private static readonly float[] gridSizes = new[]
{
0.5f, //LOD0
01f, //LOD1
02f, //LOD2
04f, //LOD3
08f, //LOD4
16f, //LOD5
32f, //LOD6
32f //LOD7
};
private const float EXPECTED_MAX_WAVE_HEIGHT = 15f;
public Material material;
[Space]
[Tooltip("Enable to executing the camera following behaviour when outside of Play mode")]
public bool enableInEditMode = true;
[Tooltip("Assign a specific transform to follow on the XZ axis." +
"\n\nIf left empty, the camera currently rendering will be targeted.")]
public Transform followTarget;
[Space]
public static bool ShowWireFrame = true;
[Serializable]
public class LOD
{
public List<GameObject> gameObjects = new List<GameObject>();
public float gridSize = 1f;
}
[SerializeField]
//[HideInInspector]
private LOD[] lods;
//To ensure that each LOD renders behind the one before it, move each one down a tiny bit
private const float heightOffset = 0.01f;
private void Reset()
{
//BuildLODs();
}
#if SWS_DEV
[ContextMenu("Build LODs")]
#endif
//Populates the LOD list and sets the gridsize for each
void BuildLODs()
{
lods = new LOD[LODCount+1];
for (int i = 0; i < lods.Length; i++)
{
lods[i] = new LOD();
lods[i].gridSize = gridSizes[i];
}
MeshRenderer[] childs = this.gameObject.GetComponentsInChildren<MeshRenderer>(true);
for (int i = 0; i < childs.Length; i++)
{
childs[i].gameObject.layer = WaterObject.WaterLayer;
string objName = childs[i].name;
int lodIndex = 0;
if(objName.EndsWith("0")) lodIndex = 0;
if(objName.EndsWith("1")) lodIndex = 1;
if(objName.EndsWith("2")) lodIndex = 2;
if(objName.EndsWith("3")) lodIndex = 3;
if(objName.EndsWith("4")) lodIndex = 4;
if(objName.EndsWith("5")) lodIndex = 5;
if(objName.EndsWith("6")) lodIndex = 6;
if(objName.EndsWith("7")) lodIndex = 7;
#if UNITY_EDITOR
childs[i].scaleInLightmap = Mathf.Lerp(1, 0.01f, (float)lodIndex / LODCount);
#endif
lods[lodIndex].gameObjects.Add(childs[i].gameObject);
}
}
private Vector3 targetPosition;
private void SetPosition(Transform target)
{
float height = this.transform.position.y;
for (int i = 0; i < lods.Length; i++)
{
for (int j = 0; j < lods[i].gameObjects.Count; j++)
{
targetPosition = WaterGrid.SnapToGrid(target.position, this.transform.lossyScale.x * lods[i].gridSize);
//targetPosition = target.position; //No snapping
//Progressively lower the height of each LOD a small amount, this helps ensure transparency sorting will be correct.
targetPosition.y = height - (heightOffset * i);
#if UNITY_EDITOR || DEVELOPMENT_BUILD
if (lods[i].gameObjects[j] == false) throw new Exception("[Ocean] A child GameObject was deleted, these should not be touched!");
#endif
lods[i].gameObjects[j].transform.position = targetPosition;
}
}
}
public void ApplyMaterial()
{
for (int i = 0; i < lods.Length; i++)
{
foreach (var lod in lods[i].gameObjects)
{
MeshRenderer r = lod.GetComponent<MeshRenderer>();
if (r)
{
r.sharedMaterial = material;
PadBounds(r);
}
}
}
}
//Pad the bounds so that meshes don't get unintentionally culled when using high waves
private void PadBounds()
{
for (int i = 0; i < lods.Length; i++)
{
foreach (var lod in lods[i].gameObjects)
{
MeshRenderer r = lod.GetComponent<MeshRenderer>();
if (r)
{
r.sharedMaterial = material;
PadBounds(r);
}
}
}
}
private void PadBounds(MeshRenderer m_renderer)
{
Bounds bounds = m_renderer.localBounds;
bounds.Expand(Vector3.up * (EXPECTED_MAX_WAVE_HEIGHT * 0.5f));
m_renderer.localBounds = bounds;
}
private void OnEnable()
{
Instance = this;
PadBounds();
#if URP
RenderPipelineManager.beginCameraRendering += OnCameraRender;
#endif
}
private void LateUpdate()
{
if (followTarget)
{
SetPosition(followTarget);
}
}
#if URP
private void OnCameraRender(ScriptableRenderContext context, Camera targetCamera)
{
if (targetCamera.cameraType == CameraType.Preview) return;
//Component set up to follow a specific target
if (followTarget != null) return;
#if UNITY_EDITOR
//Skip if disabled in scene-view
if (targetCamera.cameraType == CameraType.SceneView && (enableInEditMode == false || Application.isPlaying)) return;
#endif
SetPosition(targetCamera.transform);
}
#endif
private void OnDisable()
{
Instance = null;
#if URP
RenderPipelineManager.beginCameraRendering -= OnCameraRender;
#endif
}
private void OnDrawGizmosSelected()
{
if (!ShowWireFrame) return;
MeshFilter[] meshes = GetComponentsInChildren<MeshFilter>();
Gizmos.color = new Color(0,0,0,0.25f);
for (int i = 0; i < meshes.Length; i++)
{
Gizmos.matrix = meshes[i].transform.localToWorldMatrix;
Gizmos.DrawWireMesh(meshes[i].sharedMesh);
}
/*
Gizmos.color = new Color(1,1,0,0.25f);
Gizmos.matrix = Matrix4x4.identity;
for (int i = 0; i < lods.Length; i++)
{
foreach (var lod in lods[i].gameObjects)
{
MeshRenderer r = lod.GetComponent<MeshRenderer>();
Gizmos.DrawWireCube(r.bounds.center, r.bounds.size);
}
}
*/
}
public bool InvalidSetup()
{
if(lods == null) return true;
if(lods.Length == 0) return true;
for (int i = 0; i < lods.Length; i++)
{
foreach (var lod in lods[i].gameObjects)
{
if(!lod.gameObject) return true;
}
}
return false;
}
}
}
@@ -0,0 +1,20 @@
fileFormatVersion: 2
guid: 59d166d8583159b44ae4a88bf823b554
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- material: {fileID: 2100000, guid: d9bee4d86b9f55b46a4288109fb2b181, type: 2}
- followTarget: {instanceID: 0}
executionOrder: 0
icon: {fileID: -7633815037825259808, guid: 0000000000000000d000000000000000, type: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/Components/OceanFollowBehaviour.cs
uploadId: 895866
@@ -0,0 +1,124 @@
using System;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace StylizedWater3
{
/// <summary>
/// Emulates the particle system "Rate over Distance" emission behaviour, but with accurate support for RigidBody's
/// </summary>
[ExecuteInEditMode]
[AddComponentMenu("Effects/Particle Trail Emitter")]
public class ParticleTrailEmitter : MonoBehaviour
{
#pragma warning disable 108,114 //New keyword
[Tooltip("The Emission module on this particle system should have its Rate Over Distance value set 0.")]
public ParticleSystem particleSystem;
private ParticleSystem.EmissionModule emissionModule;
#pragma warning restore 108,114
[Tooltip("If this particle system is parented under a RigidBody, then assign it here for correct positional tracking")]
public Rigidbody rigidBody;
[Space]
public float spawnRatePerUnit = 1f;
private float distanceAccumulation = 0f;
private Vector3 previousPosition;
void Reset()
{
particleSystem = GetComponent<ParticleSystem>();
if (particleSystem)
{
emissionModule = particleSystem.emission;
if (emissionModule.rateOverDistance.constant > 0f)
{
ParticleSystem.MinMaxCurve rateOverDistance = emissionModule.rateOverDistance;
rateOverDistance.constant = 0f;
emissionModule.rateOverDistance = rateOverDistance;
Debug.LogWarning($"The Rate Over Distance has been set to 0 on the particle system \"{particleSystem.name}\". This is because the Particle Trail Emitter component will be responsible for emission");
}
}
rigidBody = GetComponentInParent<Rigidbody>();
}
private void Start()
{
previousPosition = this.transform.position;
if (particleSystem)
{
ParticleSystem.MainModule main = particleSystem.main;
main.playOnAwake = false;
}
}
private float GetDistance()
{
float distanceThisFrame = 0;
if(rigidBody)
{
//Distance = speed * deltaTime
float movementSpeed = Mathf.Max(rigidBody.linearVelocity.magnitude, rigidBody.angularVelocity.magnitude);
distanceThisFrame = movementSpeed * Time.deltaTime;
}
else
{
distanceThisFrame = Vector3.Distance(transform.position, previousPosition);
previousPosition = this.transform.position;
}
return distanceThisFrame;
}
public void FixedUpdate()
{
if (!particleSystem || !enabled) return;
emissionModule = particleSystem.emission;
if (emissionModule.enabled == false) return;
float distance = GetDistance();
//Teleportation safeguard
if (distance > 50f) return;
distanceAccumulation += distance;
var particlesToEmit = Mathf.CeilToInt(distanceAccumulation * spawnRatePerUnit);
if (particlesToEmit > 0)
{
particleSystem.Emit(particlesToEmit);
distanceAccumulation -= particlesToEmit / spawnRatePerUnit;
}
}
}
#if UNITY_EDITOR
[CustomEditor(typeof(ParticleTrailEmitter))]
class ParticleTrailEmitterInspector : Editor
{
private ParticleTrailEmitter component;
private void OnEnable()
{
component = (ParticleTrailEmitter)target;
}
private void OnSceneGUI()
{
component.FixedUpdate();
}
}
#endif
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 6febe12fcf8d2e149842da43eb923880
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/Components/ParticleTrailEmitter.cs
uploadId: 895866
@@ -0,0 +1,165 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace StylizedWater3
{
[ExecuteAlways]
[AddComponentMenu("Stylized Water 3/Water Custom Time")]
public class SetCustomWaterTime : MonoBehaviour
{
public enum Mode
{
None,
Interval,
Time,
EditorTime,
Speed,
[InspectorName("System Time (UTC)")]
SystemTime,
Custom
}
public Mode mode = Mode.Custom;
//Parameters for different modes
[Min(0.02f)]
public float interval = 0.2f;
public float speed = 0f;
[Min(0f)]
public float customTime = 0f;
private float elapsedTime;
private DateTime _startTime;
private void OnEnable()
{
RenderPipelineManager.beginContextRendering += OnBeginFrame;
_startTime = new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month, DateTime.UtcNow.Day, 0, 0, 0);
}
private void OnBeginFrame(ScriptableRenderContext context, List<Camera> cams)
{
SetTime();
}
private void SetTime()
{
#if UNITY_EDITOR
if (mode == Mode.EditorTime)
{
WaterObject.CustomTime = (float)UnityEditor.EditorApplication.timeSinceStartup;
return;
}
#endif
if (mode == Mode.None)
{
ResetTime();
return;
}
if (mode == Mode.Interval)
{
elapsedTime += Time.deltaTime;
if (elapsedTime >= interval)
{
elapsedTime = 0;
WaterObject.CustomTime = Time.time;
}
}
else if (mode == Mode.Time)
{
WaterObject.CustomTime = Time.time;
}
else if (mode == Mode.Speed)
{
elapsedTime += Time.deltaTime * speed;
WaterObject.CustomTime = elapsedTime;
}
else if (mode == Mode.SystemTime)
{
WaterObject.CustomTime = (float)(DateTime.UtcNow - _startTime).TotalMilliseconds * 0.001f;
}
else if (mode == Mode.Custom)
{
WaterObject.CustomTime = customTime;
}
}
private void ResetTime()
{
elapsedTime = 0f;
//Revert to using normal time
WaterObject.CustomTime = -1;
}
private void OnDisable()
{
RenderPipelineManager.beginContextRendering -= OnBeginFrame;
ResetTime();
}
}
#if UNITY_EDITOR
[CustomEditor(typeof(SetCustomWaterTime))]
public class SetCustomWaterTimeEditor : Editor
{
private SerializedProperty mode;
private SerializedProperty interval;
private SerializedProperty speed;
private SerializedProperty customTime;
private void OnEnable()
{
mode = serializedObject.FindProperty("mode");
interval = serializedObject.FindProperty("interval");
speed = serializedObject.FindProperty("speed");
customTime = serializedObject.FindProperty("customTime");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(mode);
if (mode.intValue == (int)SetCustomWaterTime.Mode.Interval)
{
EditorGUILayout.PropertyField(interval);
}
else if (mode.intValue == (int)SetCustomWaterTime.Mode.Speed)
{
EditorGUILayout.PropertyField(speed);
}
else if (mode.intValue == (int)SetCustomWaterTime.Mode.Custom)
{
EditorGUILayout.PropertyField(customTime);
}
if(WaterObject.CustomTime > 0 && mode.intValue != (int)SetCustomWaterTime.Mode.Custom) EditorGUILayout.HelpBox($"Time: {WaterObject.CustomTime}", MessageType.None);
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
}
}
}
#endif
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 33cfa047dbc543ea9a51492ad11fce6e
timeCreated: 1719917866
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/Components/SetCustomWaterTime.cs
uploadId: 895866
@@ -0,0 +1,22 @@
using System;
using UnityEngine;
namespace StylizedWater3
{
[ExecuteAlways]
[AddComponentMenu("Stylized Water 3/Global Wave Origin Offset")]
public class SetGlobalWaveOriginOffset : MonoBehaviour
{
private readonly int _GlobalWaveOriginOffset = Shader.PropertyToID("_GlobalWaveOriginOffset");
private void Update()
{
Shader.SetGlobalVector(_GlobalWaveOriginOffset, this.transform.position);
}
private void OnDisable()
{
Shader.SetGlobalVector(_GlobalWaveOriginOffset, Vector3.zero);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 3290db5725a149f09da1024eb11ed2e1
timeCreated: 1747397807
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/Components/SetGlobalWaveOriginOffset.cs
uploadId: 895866
@@ -0,0 +1,28 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using UnityEngine;
namespace StylizedWater3
{
[ExecuteAlways]
[AddComponentMenu("Stylized Water 3/Water Position Offset")]
public class SetWaterPositionOffset : MonoBehaviour
{
public bool negate;
private void Update()
{
//Note: in a floating origin system, apply the value after offsetting all the transforms!
//Otherwise the water geometry gets shifted in one frame, and this offset is applied the next. This induces a jitter.
StylizedWater3.WaterObject.PositionOffset = negate ? -this.transform.position : this.transform.position;
}
private void OnDisable()
{
StylizedWater3.WaterObject.PositionOffset = Vector3.zero;
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: ff38353f302f4942bdccd7cb32b19d37
timeCreated: 1728977343
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/Components/SetWaterPositionOffset.cs
uploadId: 895866
@@ -0,0 +1,238 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using System;
using System.Collections.Generic;
using UnityEngine;
namespace StylizedWater3
{
[ExecuteInEditMode]
[AddComponentMenu("Stylized Water 3/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(0)]
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 CalculateTileSize()
{
rowsColumns = Mathf.Max(rowsColumns, 0);
float m_scale = scale * this.transform.lossyScale.x;
tileSize = Mathf.Max(1f, m_scale / rowsColumns);
}
private void RecreateMesh()
{
CalculateTileSize();
float m_vertexDistance = vertexDistance * this.transform.lossyScale.x;
//Value should never be larger than an individual tile
m_vertexDistance = Mathf.Min(m_vertexDistance, tileSize);
mesh = WaterMesh.Create(WaterMesh.Shape.Rectangle, tileSize, m_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.matrix = this.transform.localToWorldMatrix;
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.localPosition);
}
}
if (DisplayGrid)
{
if (tileSize <= 0) CalculateTileSize();
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
}
}
@@ -0,0 +1,20 @@
fileFormatVersion: 2
guid: bfcfc08716cec9c4ea8d75126995f02e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- material: {fileID: 2100000, guid: e8333e151973f1c4188ff534979c823b, type: 2}
- followTarget: {instanceID: 0}
executionOrder: 0
icon: {fileID: 5243786984396574768, guid: 0000000000000000d000000000000000, type: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/Components/WaterGrid.cs
uploadId: 895866
@@ -0,0 +1,205 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
namespace StylizedWater3
{
/// <summary>
/// Attached to every mesh using the Stylized Water 3 shader
/// Provides a generic way of identifying water objects and accessing their properties
/// </summary>
[ExecuteInEditMode]
[AddComponentMenu("Stylized Water 3/Water Object")]
[DisallowMultipleComponent]
public class WaterObject : MonoBehaviour
{
/// <summary>
/// The default Water layer index in Unity
/// </summary>
public const int WaterLayer = 4;
/// <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 height sampling as well).
/// This is typically used for network synchronized waves or cutscenes.
/// To revert to using normal <see cref="Time.time"/>, pass in a value lower than <c>0</c>.
/// </summary>
/// <param name="value"></param>
public static float CustomTime
{
set
{
m_customTimeValue = value;
Shader.SetGlobalFloat(CustomTimeID, m_customTimeValue);
}
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);
}
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;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 079d48f7ea5dc054a8b2cd9151030ee5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 22661e393f063dd4085f7fe57377fbc7, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/Components/WaterObject.cs
uploadId: 895866
@@ -0,0 +1,126 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
namespace StylizedWater3
{
public class Extension
{
private static Extension[] catalogue = new Extension[]
{
new Extension(ID.DynamicEffects, "Dynamic Effects", "Enables advanced effects to be projected onto the water surface. Such as boat wakes, ripples and shoreline waves.", 299321),
new Extension(ID.UnderwaterRendering, "Underwater Rendering", "Extends the shader with underwater rendering, by seamlessly blending the water with post processing effects.", 322081),
//new Extension(ID.Flowmaps, "Flowmaps", "Adds directional flow to water surfaces", 0),
//new Extension(ID.Physics, "Physics", "Buoyancy physics to make Rigidbodies float in a natural way", 0),
};
protected Extension(){}
protected Extension(ID id, string name, string description, int assetStoreID)
{
this.id = id;
this.name = name;
this.description = description;
this.assetStoreID = assetStoreID;
}
public enum ID
{
Unknown,
DynamicEffects,
UnderwaterRendering,
Flowmaps,
Physics,
Simulations,
OceanWaves
}
public ID id = ID.Unknown;
public string name;
public string description;
public int assetStoreID;
public Texture2D icon;
public string version;
public string minBaseVersion;
public static Extension[] installed;
public static Extension[] available;
public void CreateIcon(string data)
{
byte[] bytes = System.Convert.FromBase64String(data);
icon = new Texture2D(32, 32, TextureFormat.RGBA32, false, false);
icon.LoadImage(bytes, true);
}
#if UNITY_EDITOR
//[UnityEditor.Callbacks.DidReloadScripts]
[InitializeOnLoadMethod]
private static void GetInstalled()
{
var allTypes = new List<System.Type>();
var assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
Type[] types = assembly.GetTypes();
foreach (Type type in types)
{
if (type.IsAbstract) continue;
if (type.IsSubclassOf(typeof(Extension)))
allTypes.Add(type);
}
}
installed = new Extension[allTypes.Count];
for (int i = 0; i < allTypes.Count; i++)
{
installed[i] = Activator.CreateInstance(allTypes[i]) as Extension;
//installed[i] = Convert.ChangeType(typeof(Extension), allTypes[i]) as Extension;
//Debug.Log($"Found installed extension: {allTypes[i]}");
}
for (int i = 0; i < installed.Length; i++)
{
installed[i].Load();
}
List<Extension> notInstalledList = new List<Extension>();
for (int i = 0; i < catalogue.Length; i++)
{
//Get installed
Extension extension = Get(catalogue[i].id);
//Not installed
if (extension == null)
{
notInstalledList.Add(catalogue[i]);
}
}
available = notInstalledList.ToArray();
//Debug.Log($"{available.Length} extensions available");
}
#endif
public virtual void Load(){}
protected static Extension Get(ID id)
{
return installed.FirstOrDefault(e => e.id == id);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 1dc546b3144144d2a881f1fb82d02801
timeCreated: 1721041274
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/Extension.cs
uploadId: 895866
@@ -0,0 +1,192 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using Unity.Mathematics;
using UnityEngine;
namespace StylizedWater3
{
public static class Gerstner
{
private const float TWO_PI = Mathf.PI * 2f;
private const float GRAVITY = 9.8f;
private const float MAX_AMPLITUDE = 5.0f;
private static readonly int TimeParametersID = Shader.PropertyToID("_TimeParameters");
//Returns the same value as _TimeParameters.x
private static float _TimeParameters
{
get
{
if (WaterObject.CustomTime > 0) return WaterObject.CustomTime;
#if UNITY_EDITOR
float time = Application.isPlaying ? Time.time : Time.realtimeSinceStartup;
#else
float time = Time.time;
#endif
return time;
}
}
public static void ComputeHeight(HeightQuerySystem.Sampler sampler, HeightQuerySystem.Interface heightInterface)
{
ComputeHeight(sampler, heightInterface.waveProfile, heightInterface.GetWaterLevel(), heightInterface.waterObject.material);
}
public static void ComputeHeight(HeightQuerySystem.Sampler sampler, WaveProfile profile, float waterLevel, Material waterMaterial)
{
if (waterMaterial.IsKeywordEnabled(ShaderParams.Keywords.Waves) == false)
{
return;
}
//Get the material's wave-related parameters as these greatly influence the wave pattern
Vector4 m_dir = waterMaterial.GetVector(ShaderParams.Properties._Direction);
float2 direction = new float2(m_dir.x, m_dir.y);
float speed = waterMaterial.GetFloat(ShaderParams.Properties._Speed) * waterMaterial.GetFloat(ShaderParams.Properties._WaveSpeed);
float frequency = waterMaterial.GetFloat(ShaderParams.Properties._WaveFrequency);
int layerCount = waterMaterial.GetInt(ShaderParams.Properties._WaveMaxLayers);
float waveHeight = waterMaterial.GetFloat(ShaderParams.Properties._WaveHeight);
float3 scale = new float3(1f, waveHeight, 1f);
ComputeHeight(profile, sampler, waterLevel, speed, frequency, direction, scale, layerCount);
}
public static void ComputeHeight(WaveProfile profile, HeightQuerySystem.Sampler sampler, float waterLevel, in float speed, in float frequency, in float2 baseDirection, float3 scale, in int count)
{
int layerCount = profile.layers.Length - 1;
int m_count = min(count, layerCount);
for (int i = 0; i < sampler.positions.Length; i++)
{
//Account for world position offset
float2 worldPosition = new float2(sampler.positions[i].x + WaterObject.PositionOffset.x, sampler.positions[i].z + WaterObject.PositionOffset.z);
sampler.heightValues[i] = ComputeHeight(profile, waterLevel, worldPosition, frequency, speed * _TimeParameters, baseDirection, scale, m_count);
}
}
//Keep in sync with the HLSL function in the 'Gerstner' shader library
public static float ComputeHeight(WaveProfile profile, float waterLevel, float2 position, in float frequency, in float time, in float2 baseDirection, in float3 scale, int count)
{
float3 offset = 0f;
//float3 tangent = new float3(1,0,0);
//float3 bitangent = new float3(0,0,1);
//float normalStrength = 1f;
uint waveCount = 0;
for (uint i = 0; i <= count; i++)
{
WaveProfile.WaveParameters parameters = profile.CreateWaveParameters(profile.layers[i], (float)i / (float)count, 1f / profile.averageSteepness);
if (parameters.enabled > 0)
{
waveCount += 1;
float w = TWO_PI / (parameters.waveLength * frequency);
float freq = sqrt(GRAVITY * w);
//As amplitude scales down, so should the steepness
float ampRCP = (parameters.amplitude/MAX_AMPLITUDE);
//Both divide and scale by amplitude
float steepness = (parameters.steepness / parameters.amplitude) * ampRCP;
//Rotation already pre-converted into radians
float2 direction = new float2(sin(parameters.direction), cos(parameters.direction)) * baseDirection;
//Radial mode
if (parameters.mode == 1)
{
position -= parameters.origin;
direction += (position - parameters.origin);
direction = normalize(direction);
}
float dir = dot(direction, position - (parameters.origin * parameters.mode));
float t = dir * w + (freq * -time);
float proximalSine = sin(t); //Y
float lateralSine = cos(t); //XZ
//Relative XYZ offsets
offset.x += direction.x * parameters.amplitude * lateralSine * steepness;
offset.y += proximalSine * parameters.amplitude;
offset.z += direction.y * parameters.amplitude * lateralSine * steepness;
/*
tangent += new float3(
-direction.x * direction.x * (steepness * proximalSine),
offset.x,
-direction.x * direction.y * (steepness * proximalSine)
);
bitangent += new float3(
-direction.x * direction.y * (steepness * proximalSine),
offset.z,
-direction.y * -direction.y * (steepness * proximalSine)
);
*/
}
}
waveCount = max(waveCount, 1);
//tangent = lerp(new float3(1,0,0), tangent, normalStrength / waveCount);
//bitangent = lerp(new float3(0,0,1), bitangent, normalStrength / waveCount);
offset *= scale;
return waterLevel + offset.y;
}
#region Maths
//Mirroring the syntax of HLSL
private static float sqrt(float value)
{
return math.sqrt(value);
}
private static float sin(float value)
{
return math.sin(value);
}
private static float cos(float value)
{
return math.cos(value);
}
private static float2 normalize(float2 value)
{
return math.normalize(value);
}
private static float dot(float2 a, float2 b)
{
return math.dot(a, b);
}
private static uint max(uint a, uint b)
{
return math.max(a, b);
}
private static int min(int a, int b)
{
return math.min(a, b);
}
private static float3 lerp(float3 a, float3 b, float t)
{
return math.lerp(a, b, t);
}
#endregion
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: d403f579c84d487bae99b05854b6f2cf
timeCreated: 1719908608
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/Gerstner.cs
uploadId: 895866
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cd635c635f0b43fb8a6a4adedc8ba9d7
timeCreated: 1718013480
@@ -0,0 +1,267 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using System;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.Rendering;
using Unity.Mathematics;
namespace StylizedWater3
{
public static partial class HeightQuerySystem
{
public class Query
{
/// <summary>
/// Maximum allowed number of sample positions allowed within a single height query
/// A value too high may result in decreased performance, as the data payload needed to be retrieved from the GPU becomes too large.
/// </summary>
public const int MAX_SIZE = 128;
//Input
private NativeArray<float3> samplePositions;
public readonly GraphicsBuffer inputPositionBuffer;
//Output
public NativeArray<float> outputOffsets;
public readonly GraphicsBuffer outputOffsetsBuffer;
private int currentBufferIndex = 0;
//Need to keep a buffer alive so it can be used the next frame
private readonly NativeArray<float>[] readbackBuffers = new NativeArray<float>[2];
//Every request is issued with an ID
public readonly Dictionary<int, AsyncRequest> requests = new Dictionary<int, AsyncRequest>();
//Index of the last request.
public int sampleCount;
private bool hasPendingRequest;
public List<int> availableIndices;
public Query()
{
RecreateIndexPool();
//CPU input
samplePositions = new NativeArray<float3>(MAX_SIZE, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
//GPU input
inputPositionBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, MAX_SIZE, 3 * sizeof(float));
inputPositionBuffer.name = "Water Height Query: Sample Positions";
//GPU output
outputOffsetsBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, MAX_SIZE, sizeof(float));
inputPositionBuffer.name = "Water Height Query: Sampled Heights";
}
public int GetNextAvailableIndex()
{
var index = availableIndices[0];
//Remove as it is now no longer available
availableIndices.Remove(index);
return index;
}
public void ReleaseIndex(int index)
{
availableIndices.Add(index);
//Sorting optimizes the array occupancy
availableIndices.Sort();
}
private void RecreateIndexPool()
{
//Populate pool of available indices
availableIndices = new List<int>();
for (int i = 0; i < MAX_SIZE; i++)
{
availableIndices.Add(i);
}
}
//Combine all the positions from the requests into one list
private void PopulateSampleList()
{
//Copy all the sample positions in the queue to the summed array
foreach (KeyValuePair<int, AsyncRequest> request in requests)
{
for (int i = 0; i < request.Value.indices.Count; i++)
{
int index = request.Value.indices[i];
//NOTE: Setting items of a NativeArray is slow
samplePositions[index] = request.Value.sampler.positions[i];
}
}
//Note: unused indices are not initialized.
//Compute shader is configured not to sample them. Doing so otherwise causes VRAM corruption on Metal.
}
//Dispatch the compute shader. The "offsets" array will be populated based on the current GPU buffer contents.
public void Dispatch(ComputeCommandBuffer cmd, ComputeShader cs, int kernel)
{
Profiler.BeginSample($"{PROFILER_PREFIX} Setup and dispatch");
PopulateSampleList();
cmd.SetBufferData(inputPositionBuffer, samplePositions);
cmd.SetComputeIntParam(cs, "sampleCount", sampleCount);
cmd.SetComputeBufferParam(cs, kernel, "positions", inputPositionBuffer);
//Output
cmd.SetComputeBufferParam(cs, kernel, "offsets", outputOffsetsBuffer);
cmd.DispatchCompute(cs, kernel, RenderPass.THREAD_GROUPS, 1, 1);
Profiler.EndSample();
}
void ValidateNativeBuffer(ref NativeArray<float> buffer)
{
if (!buffer.IsCreated || buffer.Length != MAX_SIZE)
{
if (buffer.IsCreated) buffer.Dispose();
buffer = new NativeArray<float>(MAX_SIZE, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
}
}
public void Readback(UnsafeCommandBuffer cmd)
{
//Array was disposed of after readback request. Forced to recreate it
//https://forum.unity.com/threads/asyncgpureadback-requestintonativearray-causes-invalidoperationexception-on-nativearray.1011955
//AllocateReadbackBuffer();
//Query may have been disposed, but an async readback was still pending...
if (hasPendingRequest)
{
return;
}
hasPendingRequest = true;
//After the readback request is complete Unity will dispose of this array automatically. Possibly when 'GetData' is called.
//Hence a swap-buffer method is employed
ValidateNativeBuffer(ref readbackBuffers[0]);
ValidateNativeBuffer(ref readbackBuffers[1]);
NativeArray<float> nextBuffer = readbackBuffers[NextBufferIndex()];
#if UNITY_EDITOR
//Unity dev: Remove when bug is fixed
AtomicSafetyHandle ash = NativeArrayUnsafeUtility.GetAtomicSafetyHandle(nextBuffer);
AtomicSafetyHandle.CheckReadAndThrow(ash);
AtomicSafetyHandle.CheckDeallocateAndThrow(ash);
#endif
cmd.RequestAsyncReadbackIntoNativeArray(ref nextBuffer, outputOffsetsBuffer, MAX_SIZE * outputOffsetsBuffer.stride, 0, OnCompleteReadback);
}
private void SwapCurrentBuffer()
{
currentBufferIndex = (currentBufferIndex + 1) % 2;
}
int NextBufferIndex()
{
//return 0;
return (currentBufferIndex + 1) % 2;
}
private void OnCompleteReadback(AsyncGPUReadbackRequest asyncGPUReadbackRequest)
{
if (asyncGPUReadbackRequest.hasError)
{
throw new Exception("Error reading GPU water height with AsyncGPUReadbackRequest.");
}
Profiler.BeginSample($"{PROFILER_PREFIX} Readback data");
outputOffsets = asyncGPUReadbackRequest.GetData<float>();
foreach (KeyValuePair<int, AsyncRequest> m_request in requests)
{
AsyncRequest request = m_request.Value;
int queryLength = request.indices.Count;
for (int i = 0; i < queryLength; i++)
{
//List of indices this request occupies in the query
int index = request.indices[i];
var waterHeight = outputOffsets[index];
//Height value equals a void do not assign it
if (request.invalidateMisses && EqualsVoid(waterHeight))
{
//Debug.Log($"Height request for {request.label} at index {index} was invalidated (value={waterHeight}).");
continue;
}
request.sampler.heightValues[i] = waterHeight;
}
//Issue a callback event for the external scripts that issued the request
request.InvokeCallback();
}
outputOffsets.Dispose();
SwapCurrentBuffer();
hasPendingRequest = false;
Profiler.EndSample();
}
public void Clear()
{
foreach (KeyValuePair<int, AsyncRequest> request in requests)
{
request.Value.sampler.Dispose();
}
requests.Clear();
RecreateIndexPool();
}
public void Dispose()
{
Clear();
//Remove itself from the list of queries
queries.Remove(this);
QueryCount--;
//Wait before we freeing the resources
if (hasPendingRequest) AsyncGPUReadback.WaitAllRequests();
samplePositions.Dispose();
inputPositionBuffer.Dispose();
//Dispose any allocated arrays
outputOffsetsBuffer.Dispose();
if (readbackBuffers[0].IsCreated)
readbackBuffers[0].Dispose();
if (readbackBuffers[1].IsCreated)
readbackBuffers[1].Dispose();
//If the query is being disposed, whilst no readback request was pending then this array would not be allocated
if(outputOffsets.IsCreated) outputOffsets.Dispose();
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: c49a6fa4f6da4e81b30d4703ef16ca15
timeCreated: 1718013507
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/HeightQueries/HeightQuerySystem.Query.cs
uploadId: 895866
@@ -0,0 +1,142 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.Universal;
namespace StylizedWater3
{
public static partial class HeightQuerySystem
{
public class RenderPass : ScriptableRenderPass
{
public const int THREAD_GROUPS = 64; //Value must be mirrored in compute shader
private const string PROFILER_PREFIX = "[GPU] Water Height Query:";
private static readonly ProfilingSampler computeProfilerSampler = new ProfilingSampler( $"{PROFILER_PREFIX} Dispatch");
private static readonly ProfilingSampler readbackAsyncProfilerSampler = new ProfilingSampler( $"{PROFILER_PREFIX} Readback Async");
private ComputeShader cs;
private int kernel = -1;
public void Setup(StylizedWaterRenderFeature renderFeature, ComputeShader heightReadbackCs)
{
if (!heightReadbackCs)
{
Debug.LogError("[Stylized Water 3] Height query render pass initialized with an empty compute shader reference. Was it deleted from the project? Or not referenced on the render feature?" +
" This may happen when deleting the Library folder, creating a race condition where the Compute shader isn't yet imported. You should not add render features in play mode!.", renderFeature);
return;
}
this.cs = heightReadbackCs;
this.kernel = cs.FindKernel("SampleOffsets");
}
private class PassData
{
// Compute shader.
public ComputeShader cs;
public int kernel;
// Buffer handles for the compute buffers.
public GraphicsBuffer[] input;
public GraphicsBuffer[] output;
}
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameContext)
{
if (kernel < 0) return;
HeightPrePass.FrameData heightPrePassData = frameContext.Get<HeightPrePass.FrameData>();
bool cull = HeightQuerySystem.QueryCount == 0;
using(var builder = renderGraph.AddComputePass($"Water Height Query Sampling", out PassData passData))
{
passData.cs = this.cs;
passData.kernel = this.kernel;
passData.input = new GraphicsBuffer[HeightQuerySystem.QueryCount];
passData.output = new GraphicsBuffer[HeightQuerySystem.QueryCount];
for (int i = 0; i < HeightQuerySystem.QueryCount; i++)
{
passData.input[i] = HeightQuerySystem.queries[i].inputPositionBuffer;
BufferHandle inputBufferHandle = renderGraph.ImportBuffer(passData.input[i]);
builder.UseBuffer(inputBufferHandle);
passData.output[i] = HeightQuerySystem.queries[i].outputOffsetsBuffer;
BufferHandle outputBufferHandle = renderGraph.ImportBuffer(passData.output[i]);
builder.UseBuffer(outputBufferHandle);
}
//Input
builder.UseTexture(heightPrePassData._WaterHeightBuffer);
builder.AllowPassCulling(cull);
builder.SetRenderFunc((PassData data, ComputeGraphContext cgContext) => ExecuteSampling(data, cgContext));
}
using(var builder = renderGraph.AddUnsafePass("Water Height Query: Async Readback", out PassData passData))
{
//WebGPU and Nintendo Switch would not support this
builder.EnableAsyncCompute(SystemInfo.supportsAsyncCompute);
builder.AllowPassCulling(cull);
builder.SetRenderFunc((PassData data, UnsafeGraphContext cgContext) => ExecuteReadback(data, cgContext));
}
}
private void ExecuteSampling(PassData data, ComputeGraphContext context)
{
#if UNITY_EDITOR
if (HeightQuerySystem.DISABLE_IN_EDIT_MODE && Application.isPlaying == false) return;
#endif
var cmd = context.cmd;
using (new ProfilingScope(cmd, computeProfilerSampler))
{
foreach (var q in HeightQuerySystem.queries)
{
q.Dispatch(cmd, data.cs, data.kernel);
}
}
}
//Pass using "cmd.RequestAsyncReadbackIntoNativeArray"
private void ExecuteReadback(PassData data, UnsafeGraphContext context)
{
#if UNITY_EDITOR
if (HeightQuerySystem.DISABLE_IN_EDIT_MODE && Application.isPlaying == false) return;
#endif
var cmd = context.cmd;
using (new ProfilingScope(cmd, readbackAsyncProfilerSampler))
{
foreach (var q in HeightQuerySystem.queries)
{
q.Readback(cmd);
}
}
}
#if !UNITY_6000_4_OR_NEWER
#pragma warning disable CS0672
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { }
#pragma warning restore CS0672
#endif
public void Dispose()
{
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: e53401f9bd904222bee47b59d0342925
timeCreated: 1718013618
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/HeightQueries/HeightQuerySystem.Rendering.cs
uploadId: 895866
@@ -0,0 +1,80 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using System;
using System.Collections.Generic;
namespace StylizedWater3
{
public partial class HeightQuerySystem
{
/// <summary>
/// Issues the data from a <see cref="HeightQuerySystem.Sampler"/> to an asynchronous GPU readback request.
/// </summary>
public class AsyncRequest
{
public delegate void OnRequestCompleted();
/// <summary>
/// Callback fired whenever a height query was successfully returned from the GPU.
/// </summary>
public event OnRequestCompleted onCompleted;
//GUID
public readonly int hashCode;
//Identifier, mainly for debugging
public string label;
public Sampler sampler;
/// <summary>
/// If the sampling position falls outside the camera frustum, or is not above a water surface, it will hit a void. A default value of -1000 is then used.
/// Use this option to specify if the (invalid) value should be kept or not. If not, the value will represent that of the last successful hit.
/// </summary>
public bool invalidateMisses = true;
//The indices this request occupies in the array
//TODO: Allow a request to span over multiple queries. This will work around the limit of 128 sample points.
//A request would then reference multiple sets of indices, one per query
public readonly List<int> indices = new List<int>();
public int SampleCount => indices.Count;
public AsyncRequest(int hashCode, Sampler sampler, string label = "")
{
this.hashCode = hashCode;
this.sampler = sampler;
this.label = label;
}
public void Issue()
{
if (IsSupported() == false)
{
throw new System.ComponentModel.WarningException(HeightQuerySystem.UNSUPPORTED_MESSAGE);
}
AddRequest(this);
}
public void Withdraw()
{
WithdrawRequest(this);
}
public void Dispose()
{
Withdraw();
sampler.Dispose();
}
internal void InvokeCallback()
{
onCompleted?.Invoke();
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 0fef246c1fe84870b35f921448c136e3
timeCreated: 1718111030
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/HeightQueries/HeightQuerySystem.Request.cs
uploadId: 895866
@@ -0,0 +1,104 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using System;
using Unity.Collections;
using Unity.Mathematics;
using UnityEngine;
using Object = UnityEngine.Object;
namespace StylizedWater3
{
public static partial class HeightQuerySystem
{
/// <summary>
/// Holds a list of sampling positions, and the returned water height values relative to them
/// </summary>
public class Sampler
{
/// <summary>
/// Input sample positions in world-space
/// </summary>
public NativeArray<float3> positions;
/// <summary>
/// Output height values at each sampling <see cref="positions"/>
/// </summary>
public NativeArray<float> heightValues;
private int currentSampleCount = 0;
/// <summary>
/// Checks if the sampler has been initialized
/// </summary>
/// <returns></returns>
public bool IsCreated()
{
return currentSampleCount > 0;
}
public int SampleCount => currentSampleCount;
/// <summary>
/// Initialize the sampler with a number of sampling points. This allocates the memory required.
/// </summary>
/// <param name="sampleCount">Number of positions to sample at. For best performance, this number should be conservative!</param>
/// <param name="cpu">Specify if this sampler is used with the CPU-height query method. If so, no limit is imposed on the sample count</param>
/// <exception cref="Exception"></exception>
public void SetSampleCount(int sampleCount, bool cpu = false)
{
if (cpu == false && sampleCount > Query.MAX_SIZE)
{
Dispose();
throw new Exception($"The number of sample positions ({sampleCount}) exceeds the maximum capacity ({Query.MAX_SIZE}) of a single sampler." +
$" Decrease the number of input positions, or issue multiple smaller requests");
}
//Changed
if (currentSampleCount != sampleCount)
{
#if SWS_DEV
if(currentSampleCount > 0) Debug.Log($"Sampler count changed from {currentSampleCount} to {sampleCount}");
#endif
if (positions.IsCreated) positions.Dispose();
//Input data
positions = new NativeArray<float3>(sampleCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
if (heightValues.IsCreated) heightValues.Dispose();
//Output data
heightValues = new NativeArray<float>(sampleCount, Allocator.Persistent);
}
currentSampleCount = sampleCount;
}
[Obsolete("Use SetSampleCount() instead. Method was renamed for clarity")]
public void Initialize(int sampleCount, bool cpu = false)
{
SetSampleCount(sampleCount, cpu);
}
public void SetSamplePosition(int index, float3 value)
{
if (index > currentSampleCount)
{
throw new Exception($"Index out of range. This sampler was initialized with {currentSampleCount} number of samples. Dispose() and SetSampleCount() the sampler to increase the number of samples!");
}
positions[index] = value;
}
public void Dispose()
{
if (positions.IsCreated) positions.Dispose();
if (heightValues.IsCreated) heightValues.Dispose();
currentSampleCount = 0;
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: d5b78379f8de42328f0f5b50b094f050
timeCreated: 1728760960
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/HeightQueries/HeightQuerySystem.Sampler.cs
uploadId: 895866
@@ -0,0 +1,275 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using System;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Profiling;
using Unity.Mathematics;
namespace StylizedWater3
{
public static partial class HeightQuerySystem
{
private const string PROFILER_PREFIX = "[GPU] Water Height Query:";
public static bool DISABLE_IN_EDIT_MODE
{
#if UNITY_EDITOR
get { return UnityEditor.EditorPrefs.GetBool("SW3_HeightQuerySystem_EditMode", false); }
set { UnityEditor.EditorPrefs.SetBool("SW3_HeightQuerySystem_EditMode", value); }
#else
get { return false; }
#endif
}
/// <summary>
/// Reports if the current device/platform supports Compute Shaders
/// </summary>
/// <returns></returns>
public static bool IsSupported()
{
#if UNITY_WEBGL && !UNITY_6000_1_OR_NEWER
return false;
#else
return SystemInfo.supportsComputeShaders;
#endif
}
internal const string UNSUPPORTED_MESSAGE = "[Stylized Water 3] Compute shaders are reportedly not supported on this platform. The GPU Height readback technique relies on this, so is not supported either. " +
"If you are using any \"Align To Water\" components, or custom buoyancy physics using the API, switch all of them to the \"CPU\" method.";
/// <summary>
/// Verifies if the returned height value is valid. If not, the sampling position fell outside the camera frustum or was not above any water surface
/// If false, do not incorporate this value in any processing!
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static bool EqualsVoid(float value)
{
return value <= HeightPrePass.VOID_THRESHOLD;
}
/// <summary>
/// Given 4 height values (each representing the points of a +sign) a normal vector can be derived
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <param name="down"></param>
/// <param name="up"></param>
/// <param name="strength"></param>
/// <returns></returns>
public static Vector3 DeriveNormal(float left, float right, float down, float up, float strength = 1f)
{
float xDelta = (left - right) * strength;
float zDelta = (down - up) * strength;
return Vector3.Normalize(new Vector3(xDelta, 1.0f, zDelta));
}
/// <summary>
/// A generic front-end to determine the method used to sampling the water height
/// </summary>
[Serializable]
public class Interface
{
public enum Method
{
[InspectorName("GPU (Async height readback)")]
GPU,
[InspectorName("CPU (Wave pattern replication)")]
CPU
}
[Tooltip("Two completely different methods can be used to reproduce the wave height." +
"\n\n" +
"[GPU] Requires the \"Height Pre-pass\" feature to be enabled on the render feature. This queues up height samples and processes them in a compute shader, the result will be read back from the GPU asynchronously. " +
"\n\n" +
"Height values will represent the water surface height as it literally appears in the world, including any and all displacement effects. Slowest method, but ultimately more flexible." +
"\n\n" +
"[CPU] Given a water level, and water material, the same wave pattern can be 1:1 replicated through script. Does not include displacement effects and supports flat water geometry only. Fastest method")]
public Method method = Method.GPU;
[Tooltip("This reference is required to grab the wave distance and height values")]
public WaterObject waterObject;
[Tooltip("Try to find the Water Object below or above the Transform's position. This is slower than assigning a specific Water Object directly!")]
public bool autoFind = true;
public WaveProfile waveProfile;
public enum WaterLevelSource
{
FixedValue,
[InspectorName("Water Object Y-position")]
WaterObject,
Transform,
Ocean
}
[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;
[Tooltip("This transform's Y-position is used as the base water level, this value is important and required for correct rendering. As such, underwater rendering does not work with rivers or other non-flat water")]
public Transform waterLevelTransform;
public float waterLevel;
/// <summary>
/// Based on the current configuration, retrieve the water level height value
/// </summary>
/// <returns></returns>
public float GetWaterLevel()
{
if (waterLevelSource == WaterLevelSource.WaterObject && waterObject) return waterObject.transform.position.y;
if (waterLevelSource == WaterLevelSource.Transform && waterLevelTransform) return waterLevelTransform.position.y;
if (waterLevelSource == WaterLevelSource.Ocean && OceanFollowBehaviour.Instance)
{
//Store it, so that it is always valid even when the singleton hasn't loaded yet
waterLevel = OceanFollowBehaviour.Instance.transform.position.y;
return waterLevel;
}
return waterLevel;
}
public bool IsRiverMaterial()
{
return waterObject.material.IsKeywordEnabled(ShaderParams.Keywords.River);
}
public WaterObject GetWaterObject(Vector3 worldPosition)
{
if (autoFind) waterObject = WaterObject.Find(worldPosition, false);
return waterObject;
}
public bool HasMissingReferences()
{
return (waterObject && waterObject.material && waveProfile) == false;
}
}
/// <summary>
/// List of queries being submitted the next frame
/// </summary>
public static readonly List<Query> queries = new List<Query>();
public static int QueryCount { get; private set; }
/// <summary>
/// If there are any height queries present, the displacement pre-pass must execute to ensure data is being returned
/// </summary>
public static bool RequiresHeightPrepass => QueryCount > 0;
private static void AddRequest(AsyncRequest request)
{
//Find the next available query with enough space for the positions
//-If not, create a new query
Profiler.BeginSample($"{PROFILER_PREFIX} Add Request");
foreach (Query q in queries)
{
if (q.requests.ContainsKey(request.hashCode))
{
throw new Exception($"A request with the ID {request.hashCode} has already been issued. Use the \"onReadbackCompleted\" callback to receive the data." +
$"Use the DisposeRequest function only when you are sure you no longer need the data.");
}
}
int queryIndex = queries.Count;
int sampleCount = request.sampler.SampleCount;
if (sampleCount == 0)
{
return;
}
void CreateNewQuery()
{
queries.Add(new Query());
QueryCount++;
queryIndex++;
}
//Initial query needs to be created
if (queryIndex == 0)
{
CreateNewQuery();
}
int occupiedIndices = HeightQuerySystem.Query.MAX_SIZE - queries[queryIndex - 1].availableIndices.Count;
//Not enough space in the latest query for this many samples
if (occupiedIndices + sampleCount >= HeightQuerySystem.Query.MAX_SIZE)
{
CreateNewQuery();
//Debug.Log($"Query #{queryIndex-1} contains {occupiedIndices}, requires {occupiedIndices + sampleCount}. Created a new query (#{queryIndex}).");
}
var query = queries[queryIndex-1];
//Assign the indices available in the query to the request
for (int i = 0; i < sampleCount; i++)
{
request.indices.Add(query.GetNextAvailableIndex());
}
query.sampleCount += sampleCount;
query.requests.Add(request.hashCode, request);
Profiler.EndSample();
}
private static void WithdrawRequest(AsyncRequest request)
{
Profiler.BeginSample($"{PROFILER_PREFIX} Withdraw Request");
for (int i = 0; i < queries.Count; i++)
{
var query = queries[i];
if (query.requests.TryGetValue(request.hashCode, out _))
{
//Remove request from query
query.requests.Remove(request.hashCode);
int indexCount = request.indices.Count;
//Return the occupied indices to the pool
for (int j = 0; j < indexCount; j++)
{
query.ReleaseIndex(request.indices[j]);
}
//Update the current sample count
query.sampleCount -= indexCount;
//Clear the list of occupied indices, these will be repopulate should the request be issued again
request.indices.Clear();
//If the query is now completely empty, yeet it
if (query.requests.Count == 0)
{
//Debug.Log($"Query #{i} is now empty and was disposed");
query.Dispose();
}
}
}
Profiler.EndSample();
}
/// <summary>
/// Clears all queries from the system
/// </summary>
//Need to force a clean start when entering/exiting play mode. Otherwise certain arrays will get de-allocated.
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
public static void Clear()
{
for (int i = 0; i < queries.Count; i++)
{
queries[i].Dispose();
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 4a62da4d17a748dc840316692da46106
timeCreated: 1717752156
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/HeightQueries/HeightQuerySystem.cs
uploadId: 895866
@@ -0,0 +1,871 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
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 StylizedWater3
{
//Stay awesome Unity, locking everything behind internal UI code just makes things convoluted.
public static class PipelineUtilities
{
#if URP
private const string renderDataListFieldName = "m_RendererDataList";
private const string renderFeaturesListFieldName = "m_RendererFeatures";
private const string defaultRendererIndexFieldName = "m_DefaultRendererIndex";
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 \"{renderDataListFieldName}\" 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 3",
$"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 3: {name}");
}
#endif
}
}
}
/// <summary>
/// Retrieves the given render feature from the given renderer
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static ScriptableRendererFeature GetRenderFeature<T>(ScriptableRendererData renderer)
{
if (renderer == null) renderer = GetDefaultRenderer();
foreach (ScriptableRendererFeature feature in renderer.rendererFeatures)
{
if (feature && feature.GetType() == typeof(T)) return feature;
}
return null;
}
/// <summary>
/// Retrieves the given render feature from the first renderer that contains it
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static ScriptableRendererFeature GetRenderFeature<T>()
{
if (!UniversalRenderPipeline.asset) return null;
ScriptableRendererData[] rendererDataList = GetRenderDataList(UniversalRenderPipeline.asset);
for (int i = 0; i < rendererDataList.Length; i++)
{
foreach (ScriptableRendererFeature feature in rendererDataList[i].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.Distinct().ToArray();
return renderers.Length > 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 List<ScriptableRendererData> SetupRenderFeature<T>(string name = "")
{
List<ScriptableRendererData> renderers = new List<ScriptableRendererData>();
foreach (var asset in GraphicsSettings.allConfiguredRenderPipelines)
{
ScriptableRendererData renderer = GetDefaultRenderer((UniversalRenderPipelineAsset)asset);
if (RenderFeatureAdded<T>(renderer) == false)
{
AddRenderFeature<T>(renderer, name);
renderers.Add(renderer);
}
}
return renderers;
}
/// <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;
//Call the Reset method, otherwise done when added through the GUI
if (Application.isPlaying)
{
MethodInfo resetMethod = (feature.GetType()).GetMethod("Reset", BindingFlags.NonPublic | BindingFlags.Instance);
if (resetMethod != null) resetMethod.Invoke(feature, null);
}
//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 (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;
}
}
//Fallback, if it is not even in the list
return true;
}
public static void ToggleRenderFeature<T>(bool state)
{
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
}
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>
/// Checks if a ForwardRenderer has been assigned to the pipeline asset, if not it is added
/// </summary>
/// <param name="pass"></param>
public static void ValidatePipelineRenderers(ScriptableRendererData pass)
{
if (pass == null)
{
Debug.LogError("Pass is null");
return;
}
BindingFlags bindings = System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance;
ScriptableRendererData[] m_rendererDataList = (ScriptableRendererData[])typeof(UniversalRenderPipelineAsset).GetField(renderDataListFieldName, bindings).GetValue(UniversalRenderPipeline.asset);
bool isPresent = false;
for (int i = 0; i < m_rendererDataList.Length; i++)
{
if (m_rendererDataList[i] == pass) isPresent = true;
}
if (!isPresent)
{
AddRendererToPipeline(pass);
}
else
{
#if SWS_DEV
Debug.Log($"The {pass.name} ScriptableRendererFeature is already assigned to the pipeline asset");
#endif
}
}
/// <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(out List<UniversalRenderPipelineAsset> renderers)
{
bool state = false;
renderers = new List<UniversalRenderPipelineAsset>();
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);
if (pipeline.supportsCameraDepthTexture == false)
{
renderers.Add(pipeline);
}
}
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(out List<UniversalRenderPipelineAsset> renderers)
{
bool state = false;
renderers = new List<UniversalRenderPipelineAsset>();
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.supportsCameraOpaqueTexture == false);
if (pipeline.supportsCameraOpaqueTexture == false)
{
renderers.Add(pipeline);
}
}
return state;
}
public static bool IsOpaqueDownSampled()
{
return UniversalRenderPipeline.asset.opaqueDownsampling != Downsampling.None;
}
public static bool IsOpaqueDownSampled(out List<UniversalRenderPipelineAsset> renderers)
{
bool state = false;
renderers = new List<UniversalRenderPipelineAsset>();
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.opaqueDownsampling != Downsampling.None);
if (pipeline.opaqueDownsampling != Downsampling.None)
{
renderers.Add(pipeline);
}
}
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 void DisableOpaqueDownsampling(List<UniversalRenderPipelineAsset> renderers = null)
{
if (renderers == null) IsOpaqueDownSampled(out renderers);
for (int i = 0; i < renderers.Count; i++)
{
if (renderers[i].GetType() != typeof(UniversalRenderPipelineAsset)) continue;
UniversalRenderPipelineAsset pipeline = renderers[i];
#if UNITY_EDITOR
if (pipeline.opaqueDownsampling != Downsampling.None) EditorUtility.SetDirty(pipeline);
#endif
FieldInfo field = typeof(UniversalRenderPipelineAsset).GetField("m_OpaqueDownsampling", BindingFlags.NonPublic | BindingFlags.Instance);
if (field != null)
{
field.SetValue(pipeline, Downsampling.None);
}
else
{
Debug.LogWarning("Could not find field 'm_OpaqueDownsampling' via reflection.");
}
}
}
public static bool IsDecalRenderFeatureSetup()
{
ScriptableRendererData defaultRenderer = GetDefaultRenderer();
FieldInfo renderFeaturesInfo = typeof(ScriptableRendererData).GetField(renderFeaturesListFieldName, BindingFlags.Instance | BindingFlags.NonPublic);
List<ScriptableRendererFeature> m_RendererFeatures = (List<ScriptableRendererFeature>)renderFeaturesInfo.GetValue(defaultRenderer);
foreach (ScriptableRendererFeature feature in m_RendererFeatures)
{
if (feature && feature.GetType().ToString() == "UnityEngine.Rendering.Universal.DecalRendererFeature")
{
return feature.isActive;
}
}
//Fallback, if it is not even in the list
return false;
}
public static LightCookieFormat GetDefaultLightCookieFormat()
{
if (!UniversalRenderPipeline.asset) return LightCookieFormat.GrayscaleLow;
FieldInfo m_AdditionalLightsCookieFormat = typeof(UniversalRenderPipelineAsset).GetField("m_AdditionalLightsCookieFormat", BindingFlags.Instance | BindingFlags.NonPublic);
LightCookieFormat lightsCookieFormat = (LightCookieFormat)m_AdditionalLightsCookieFormat.GetValue(UniversalRenderPipeline.asset);
return lightsCookieFormat;
}
public static bool TransparentShadowsEnabled()
{
if (!UniversalRenderPipeline.asset) return false;
UniversalRendererData main = (UniversalRendererData)GetDefaultRenderer();
return main ? main.shadowTransparentReceive : false;
}
public static bool IsDepthAfterTransparents(out List<UniversalRendererData> renderers)
{
bool state = false;
renderers = new List<UniversalRendererData>();
for (int i = 0; i < GraphicsSettings.allConfiguredRenderPipelines.Length; i++)
{
if (GraphicsSettings.allConfiguredRenderPipelines[i].GetType() != typeof(UniversalRenderPipelineAsset)) continue;
UniversalRenderPipelineAsset pipeline = (UniversalRenderPipelineAsset)GraphicsSettings.allConfiguredRenderPipelines[i];
ScriptableRendererData[] rendererDataList = GetRenderDataList(pipeline);
for (int j = 0; j < rendererDataList.Length; j++)
{
UniversalRendererData renderer = (UniversalRendererData)rendererDataList[j];
//Exception, this never renders the water itself
if (renderer.name == "Planar Reflections Renderer") continue;
//Does not render transparents or no water?
if (renderer.transparentLayerMask == 0 ||
renderer.transparentLayerMask != (renderer.transparentLayerMask | (1 << 4))
)
{
Debug.Log($"Skipped {renderer.name}");
continue;
}
if (renderer.copyDepthMode == CopyDepthMode.AfterTransparents)
{
renderers.Add(renderer);
state = true;
}
}
}
//Renderers may be present of multiple pipeline assets, so remove the duplicates
renderers = renderers.Distinct().ToList();
return state;
}
public static bool IsDepthAfterTransparents()
{
bool state = false;
UniversalRendererData renderer = (UniversalRendererData)GetDefaultRenderer(UniversalRenderPipeline.asset);
if (renderer.copyDepthMode == CopyDepthMode.AfterTransparents)
{
state = true;
}
return state;
}
public static bool VREnabled()
{
return XRSRPSettings.enabled;
}
public static bool RenderGraphEnabled()
{
#if !UNITY_6000_3_OR_NEWER || (URP_COMPATIBILITY_MODE && UNITY_6000_3_OR_NEWER)
RenderGraphSettings settings = UnityEngine.Rendering.GraphicsSettings.GetRenderPipelineSettings<RenderGraphSettings>();
return settings != null ? settings.enableRenderCompatibilityMode == false : false;
#else
return true;
#endif
}
public static void SetRenderGraphCompatibilityMode(bool state)
{
#if !UNITY_6000_3_OR_NEWER || (URP_COMPATIBILITY_MODE && UNITY_6000_3_OR_NEWER)
UnityEngine.Rendering.Universal.RenderGraphSettings settings = UnityEngine.Rendering.GraphicsSettings.GetRenderPipelineSettings<UnityEngine.Rendering.Universal.RenderGraphSettings>();
settings.enableRenderCompatibilityMode = false;
#endif
}
#endif
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 3b99364911c19644f8afb7f5ed757403
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/PipelineUtilities.cs
uploadId: 895866
@@ -0,0 +1,74 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Rendering;
namespace StylizedWater3
{
public static class RenderTargetDebugger
{
public class RenderTarget
{
public string name;
public int order = 1000;
public string description = string.Empty;
public string textureName;
public int propertyID;
}
public static List<RenderTarget> renderTargets = new List<RenderTarget>();
//For dropdown menus
public static string[] renderTargetNames;
public static int InspectedProperty = -1;
public static RTHandle CurrentRT;
public static string CurrentCameraName;
public static void Initialize()
{
renderTargets.Clear();
var assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
Type[] types = assembly.GetTypes();
foreach (Type type in types)
{
if (type.IsAbstract || type.IsInterface) continue;
if (type.IsSubclassOf(typeof(RenderTarget)))
{
//Debug.Log($"Found {type}");
RenderTarget rt = Activator.CreateInstance(type) as RenderTarget;
renderTargets.Add(rt);
}
}
}
renderTargets = renderTargets.OrderBy(o => o.order).ToList();
renderTargetNames = new string[renderTargets.Count];
for (int i = 0; i < renderTargetNames.Length; i++)
{
renderTargetNames[i] = renderTargets[i].name;
}
}
public static void Cleanup()
{
InspectedProperty = -1;
CurrentRT?.Release();
CurrentCameraName = string.Empty;
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: eedd31f6ba91478bac642c9d755e8768
timeCreated: 1721025395
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/RenderTargetDebugger.cs
uploadId: 895866
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 24dc3dfc15f65a94c8312a1f9e4fad7e
timeCreated: 1701077187
@@ -0,0 +1,233 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
#if URP
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.Universal;
namespace StylizedWater3
{
public class HeightPrePass : ScriptableRenderPass
{
private const string profilerTag = "Water Height Prepass";
private static readonly ProfilingSampler profilerSampler = new ProfilingSampler(profilerTag);
/// <summary>
/// Using this as a value comparison in shader code to determine if not water is being hit
/// </summary>
public const float VOID_THRESHOLD = -1000f;
private static readonly Color targetClearColor = new Color(VOID_THRESHOLD, 0, 0, 0);
[Serializable]
public class Settings
{
public bool enable = true;
public float range = 500f;
[Range(1f, 8f)]
public int cellsPerUnit = 4;
public int maxResolution = 4096;
[Tooltip("[When in Play mode] Skips processing the height prepass for the scene-view camera. This helps keep the rendering centered around the main camera when in Play mode.")]
public bool disableInSceneView = true;
/// <summary>
/// Returns the enabled state, either from the settings or if forced because it is required by other functionality
/// </summary>
public bool isEnabled => enable || StylizedWaterRenderFeature.RequireHeightPrePass || HeightQuerySystem.RequiresHeightPrepass;
}
//Render pass
FilteringSettings m_FilteringSettings;
RenderStateBlock m_RenderStateBlock;
private readonly List<ShaderTagId> m_ShaderTagIdList = new List<ShaderTagId>()
{
new (ShaderParams.LightModes.WaterHeight)
};
public HeightPrePass()
{
m_FilteringSettings = new FilteringSettings(RenderQueueRange.all, LayerMask.GetMask("Water"));
m_RenderStateBlock = new RenderStateBlock(RenderStateMask.Nothing);
}
public const string BufferName = "_WaterHeightBuffer";
public static readonly int _WaterHeightBuffer = Shader.PropertyToID(BufferName);
private const string CoordsName = "_WaterHeightCoords";
private static readonly int _WaterHeightCoords = Shader.PropertyToID(CoordsName);
public static readonly int _WaterHeightPrePassAvailable = Shader.PropertyToID("_WaterHeightPrePassAvailable");
private int resolution;
private int m_resolution;
private Settings settings;
private RendererListParams rendererListParams;
private RendererList rendererList;
public sealed class RenderTargetDebugContext : RenderTargetDebugger.RenderTarget
{
public RenderTargetDebugContext()
{
this.name = "Height Buffer";
this.description = "Water geometry height (red channel). Height displacement from effects (green channel)." +
"\n\nUsed by: Water decals, GPU-based buoyancy";
this.textureName = BufferName;
this.propertyID = _WaterHeightBuffer;
this.order = 0;
}
}
public void Setup(Settings settings)
{
this.settings = settings;
resolution = PlanarProjection.CalculateResolution(settings.range, settings.cellsPerUnit, 16, settings.maxResolution);
}
public class FrameData : ContextItem
{
public TextureHandle _WaterHeightBuffer;
public override void Reset()
{
_WaterHeightBuffer = TextureHandle.nullHandle;
}
}
private class PassData
{
public RendererListHandle rendererListHandle;
public TextureHandle renderTarget;
public PlanarProjection planarProjection;
public Vector4 rendererCoords;
}
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameContext)
{
var renderingData = frameContext.Get<UniversalRenderingData>();
var cameraData = frameContext.Get<UniversalCameraData>();
var lightData = frameContext.Get<UniversalLightData>();
DrawingSettings drawingSettings = CreateDrawingSettings(m_ShaderTagIdList, renderingData, cameraData, lightData, SortingCriteria.RenderQueue | SortingCriteria.SortingLayer | SortingCriteria.CommonTransparent);
drawingSettings.perObjectData = PerObjectData.None;
rendererListParams.cullingResults = renderingData.cullResults;
rendererListParams.drawSettings = drawingSettings;
rendererListParams.filteringSettings = m_FilteringSettings;
using(var builder = renderGraph.AddRasterRenderPass<PassData>("Water Height Pre-pass", out var passData))
{
//Render target
RenderTextureDescriptor renderTargetDescriptor = new RenderTextureDescriptor(resolution, resolution, GraphicsFormat.R16G16_SFloat, 0);
passData.renderTarget = UniversalRenderer.CreateRenderGraphTexture(renderGraph, renderTargetDescriptor, BufferName + cameraData.camera.name, true, FilterMode.Bilinear, TextureWrapMode.Clamp);
//Store render target in RG, so it can be retrieved in other passes
FrameData frameData = frameContext.GetOrCreate<FrameData>();
frameData._WaterHeightBuffer = passData.renderTarget;
//Mark the texture as readable
//builder.UseTexture(passData.renderTarget, AccessFlags.ReadWrite);
#if UNITY_EDITOR || DEVELOPMENT_BUILD
if (RenderTargetDebugger.InspectedProperty == _WaterHeightBuffer)
{
StylizedWaterRenderFeature.DebugData debugData = frameContext.Get<StylizedWaterRenderFeature.DebugData>();
debugData.currentHandle = passData.renderTarget;
}
#endif
passData.rendererListHandle = renderGraph.CreateRendererList(rendererListParams);
passData.planarProjection = new PlanarProjection
{
center = cameraData.camera.transform.position,
scale = settings.range,
offset = cameraData.camera.transform.forward * ((settings.range * 0.5f) - 5),
resolution = resolution
};
passData.planarProjection.Recalculate();
passData.planarProjection.SetUV(ref passData.rendererCoords);
//Set render target and bind to global property
builder.SetRenderAttachment(passData.renderTarget, 0, AccessFlags.Write);
//builder.CreateTransientTexture(passData.renderTarget);
builder.SetGlobalTextureAfterPass(passData.renderTarget, _WaterHeightBuffer);
builder.UseRendererList(passData.rendererListHandle);
builder.AllowGlobalStateModification(true);
builder.AllowPassCulling(false);
builder.SetRenderFunc((PassData data, RasterGraphContext context) =>
{
Execute(context, data);
});
}
}
private readonly int _WorldSpaceCameraPos = Shader.PropertyToID("_WorldSpaceCameraPos");
private void Execute(RasterGraphContext context, PassData data)
{
var cmd = context.cmd;
using (new ProfilingScope(cmd, profilerSampler))
{
cmd.ClearRenderTarget(true, true, targetClearColor);
cmd.SetGlobalInt(_WaterHeightPrePassAvailable, 1);
cmd.EnableShaderKeyword(ShaderParams.Keywords.WaterHeightPass);
cmd.SetViewProjectionMatrices(data.planarProjection.view, data.planarProjection.projection);
//RenderingUtils.SetViewAndProjectionMatrices(cmd, data.planarProjection.view, data.planarProjection.projection, true);
cmd.SetViewport(data.planarProjection.viewportRect);
//Is this still needed?
//cmd.SetGlobalMatrix("UNITY_MATRIX_V", data.view);
cmd.SetGlobalVector(_WaterHeightCoords, data.rendererCoords);
//Bug? During this pass the camera position sent to shaderland is that of the scene-view camera (if the tab is open)
//Possibly the value from the previous frame/camera. This breaks distance-based effects such as waves.
//Force an updated value
cmd.SetGlobalVector(_WorldSpaceCameraPos, data.planarProjection.center);
cmd.DrawRendererList(data.rendererListHandle);
//Reset (breaks VR!)
//cmd.SetViewProjectionMatrices(data.ViewMatrix, data.GPUProjectionMatrix);
}
}
public override void OnCameraCleanup(CommandBuffer cmd)
{
cmd.SetGlobalVector(_WaterHeightCoords, Vector4.zero);
cmd.SetGlobalInt(_WaterHeightPrePassAvailable, 0);
cmd.DisableShaderKeyword(ShaderParams.Keywords.WaterHeightPass);
}
public void Dispose()
{
}
#if !UNITY_6000_4_OR_NEWER
#pragma warning disable CS0672
#pragma warning disable CS0618
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { }
#pragma warning restore CS0672
#pragma warning restore CS0618
#endif
}
}
#endif
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 994d894f45c86bd4791e3353806d7175
timeCreated: 1701077223
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/Rendering/HeightPrePass.cs
uploadId: 895866
@@ -0,0 +1,601 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
#if URP
using UnityEngine.Rendering.Universal;
#endif
namespace StylizedWater3
{
[ExecuteInEditMode]
[AddComponentMenu("Stylized Water 3/Planar Reflection Renderer")]
[HelpURL("https://staggart.xyz/unity/stylized-water-3/sw3-docs/?section=reflection-rendering")]
public class PlanarReflectionRenderer : MonoBehaviour
{
#if URP
public static List<PlanarReflectionRenderer> Instances = new List<PlanarReflectionRenderer>();
[NonSerialized]
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 scene fog in the reflection. Note that this doesn't strictly work correctly on large triangles, as it is incompatible with oblique camera projections." +
"\n\n" +
"This does not include to post-processing fog effects!")]
public bool enableFog;
[Tooltip("Also render for the scene-view camera. This may be prone to some issues, such a console errors.")]
public bool enableInSceneView = true;
//Quality
public bool renderShadows;
[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;
/// <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");
private UniversalRenderPipeline.SingleCameraRequest requestData;
[NonSerialized]
public bool isRendering;
private Camera m_reflectionCamera;
private static UniversalAdditionalCameraData m_cameraData;
private bool isUnderwater;
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;
}
/// <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();
}
[Obsolete("renderRange parameter has been deprecated. Use the SetQuality overload with this argument instead.")]
public static void SetQuality(bool enableReflections, float renderScale = -1f, float renderRange = -1f, int maxLodLevel = -1) { }
/// <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>
public static void SetQuality(bool enableReflections, float renderScale = -1f, int maxLodLevel = -1)
{
AllowReflections = enableReflections;
foreach (PlanarReflectionRenderer renderer in Instances)
{
if (renderScale > 0) renderer.renderScale = renderScale;
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 bool InvalidContext(Camera targetCamera)
{
//Definitely reject any reflection cameras!
if (targetCamera.hideFlags == HideFlags.DontSave) return true;
CameraType cameraType = targetCamera.cameraType;
#if UNITY_EDITOR
if (enableInSceneView == false && cameraType == CameraType.SceneView) return true;
//During the compilation of shaders reflection rendering is prone to causing issue, and can break the render pipeline
if(UnityEditor.ShaderUtil.anythingCompiling) return true;
//Avoid the "Screen position outside of frustrum" error
if (targetCamera.orthographic && Vector3.Dot(Vector3.up, targetCamera.transform.up) > 0.9999f) return true;
//Causes an internal error in URP's rendering code in the CopyColorPass
if (targetCamera.cameraType == CameraType.SceneView && UnityEditor.SceneView.lastActiveSceneView && UnityEditor.SceneView.lastActiveSceneView.isUsingSceneFiltering) return true;
#endif
//Skip for any special use camera's (except scene view camera)
//Note: Scene camera still rendering even if window not focused!
return (cameraType != CameraType.SceneView && (cameraType == CameraType.Reflection || cameraType == CameraType.Preview || 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.001f)
{
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) SetFogState(false);
int maxLODLevel = QualitySettings.maximumLODLevel;
QualitySettings.maximumLODLevel = maximumLODLevel;
GL.invertCulling = true;
RenderReflection(context, m_reflectionCamera);
if (fogEnabled) SetFogState(true);
QualitySettings.maximumLODLevel = maxLODLevel;
GL.invertCulling = false;
UnityEngine.Profiling.Profiler.EndSample();
}
private void RenderReflection(ScriptableRenderContext context, Camera target)
{
/* Uncomment to render NR's vegetation in the reflection
// Register the reflection camera for Nature Renderer
var cameraId = VisualDesignCafe.Rendering.Instancing.RendererPool.RegisterCamera(target);
// Render the instanced objects (details and trees)
VisualDesignCafe.Rendering.Instancing.RendererPool.GetCamera(cameraId).Render();
*/
requestData = new UniversalRenderPipeline.SingleCameraRequest
{
destination = target.targetTexture,
slice = -1
};
if (RenderPipeline.SupportsRenderRequest(target, requestData))
{
RenderPipeline.SubmitRenderRequest(target, requestData);
}
}
private void SetFogState(bool value)
{
#if UNITY_EDITOR
UnityEditor.Unsupported.SetRenderSettingsUseFogNoDirty(value);
#else
RenderSettings.fog = value;
#endif
}
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;
//Occlusion culling has to be disabled, otherwise objects culled by the main camera will be culled for the reflection camera
//Setting the culling matrix for the camera doesn't appear to have any effect
newCamera.useOcclusionCulling = false;
//Component required for the UniversalRenderPipeline.RenderSingleCamera call
if (newCamera.gameObject.TryGetComponent<UniversalAdditionalCameraData>(out var data) == false)
{
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;
//rtDsc.msaaSamples = UniversalRenderPipeline.asset.msaaSampleCount; //Waste of resources, water distortion makes it virtually unnoticeable.
targetCamera.targetTexture = RenderTexture.GetTemporary(rtDsc);
targetCamera.targetTexture.filterMode = scale < 1f ? FilterMode.Bilinear : FilterMode.Point;
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.SetFloat(_PlanarReflectionsEnabledID, 1);
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;
}
private void UpdatePerspective(Camera source, Camera reflectionCam)
{
if (!source || !reflectionCam) return;
Vector3 normal = rotatable ? this.transform.up : Vector3.up;
isUnderwater = (source.transform.position.y < bounds.center.y);
if (isUnderwater) normal = -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;
reflectionCam.projectionMatrix = projectionMatrix;
reflectionCam.worldToCameraMatrix = viewMatrix;
//Unfortunately has to effect, camera appears ti use the culling matrix from the source camera anyway
reflectionCam.cullingMatrix = projectionMatrix * 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,18 @@
fileFormatVersion: 2
guid: c1b629e2b9e73f34a859a756e1560ad1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: -1426863774865177168, guid: 0000000000000000d000000000000000, type: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/Rendering/PlanarReflectionRenderer.cs
uploadId: 895866
@@ -0,0 +1,139 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using System;
using UnityEngine;
using UnityEngine.Rendering;
namespace StylizedWater3
{
/// <summary>
/// Utility for creating an orthographic top-down projection
/// </summary>
public class PlanarProjection
{
private static readonly Quaternion viewRotation = Quaternion.Euler(new Vector3(90f, 0f, 0f));
private static readonly Vector3 viewScale = new Vector3(1, 1, -1);
private static readonly Plane[] frustrumPlanes = new Plane[6];
//Input
public Vector3 center;
public Vector3 offset;
public float scale;
public int resolution;
public bool expandHeight = true;
//Output
public Matrix4x4 projection;
public Matrix4x4 view;
public Rect viewportRect;
public Vector3 boundsMin;
public Vector3 boundsMax;
//Important to snap the projection to the nearest texel. Otherwise pixel swimming is introduced when moving, due to bilinear filtering
private static Vector3 Stabilize(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));
}
public void Recalculate()
{
float extent = scale * 0.5f;
Vector3 centerPosition = center + offset;
//var frustumHeight = 2.0f * scale * Mathf.Tan(camera.fieldOfView * 0.5f * Mathf.Deg2Rad); //Still clips, plus doesn't support orthographc
var frustumHeight = expandHeight ? 10000f : scale;
centerPosition += (Vector3.up * frustumHeight * 0.5f);
centerPosition = Stabilize(centerPosition, scale / resolution);
projection = Matrix4x4.Ortho(-extent, extent, -extent, extent, 0.02f, frustumHeight);
view = Matrix4x4.TRS(centerPosition, viewRotation, viewScale).inverse;
viewportRect = new Rect(0, 0, resolution, resolution);
boundsMin.x = centerPosition.x - extent;
boundsMin.y = centerPosition.y - extent;
boundsMin.z = centerPosition.z - extent;
boundsMax.x = centerPosition.z + extent;
boundsMax.y = centerPosition.z + extent;
boundsMax.z = centerPosition.z + extent;
}
/// <summary>
/// Sets a Vector4 uniform up to be passed onto shader land, where a world-space position can be used to calculate a sampling UV
/// </summary>
/// <param name="coords"></param>
public void SetUV(ref Vector4 coords)
{
coords.x = boundsMin.x;
coords.y = boundsMin.z;
coords.z = scale;
coords.w = 1; //Enable sampling shaders
}
public void SetProjection(RasterCommandBuffer cmd)
{
cmd.SetViewProjectionMatrices(view, projection);
//Unity 6000.0.30f1+ only
//RenderingUtils.SetViewAndProjectionMatrices(cmd, view, projection, true);
}
public static int CalculateResolution(float scale, int texelsPerUnit, int min, int max)
{
int res = Mathf.RoundToInt(scale * texelsPerUnit);
//if(NON_POWER_OF_TWO == false) res = Mathf.NextPowerOfTwo(res);
return Mathf.Clamp(res, min, max);
}
public static float FadePercentageToLength(float renderRange, float fadePercentage)
{
fadePercentage = Mathf.Max(0.01f, fadePercentage);
return (renderRange * 0.5f) * (fadePercentage / 100f);
}
public void CalculateFrustumPlanes()
{
GeometryUtility.CalculateFrustumPlanes(projection * view, frustrumPlanes);
}
public bool TestPlanesAABB(Bounds bounds)
{
return GeometryUtility.TestPlanesAABB(frustrumPlanes, bounds);
}
//Using data only from the matrices, to ensure what you're seeing closely represents them
public void DrawOrthographicViewGizmo()
{
Gizmos.matrix = Matrix4x4.identity;
CalculateFrustumPlanes();
float near = frustrumPlanes[4].distance;
float far = frustrumPlanes[5].distance;
float height = near + far;
Vector3 position = new Vector3(view.inverse.m03, view.inverse.m13, view.inverse.m23);
Vector3 orthoSize = new Vector3((frustrumPlanes[0].distance + frustrumPlanes[1].distance), height, frustrumPlanes[2].distance + frustrumPlanes[3].distance);
//orthoSize = Vector3.one * 50f;
Gizmos.DrawSphere(position, 1f);
position -= Vector3.up * height * 0.5f;
Gizmos.DrawWireCube(position, orthoSize);
Gizmos.color = Color.white * 0.25f;
Gizmos.DrawCube(position, orthoSize);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 5e9fd4f2cdd74b138fb2b5f8ba29094c
timeCreated: 1717679203
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/Rendering/ProjectionUtils.cs
uploadId: 895866
@@ -0,0 +1,149 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using UnityEngine;
using UnityEngine.Rendering;
#if URP
using UnityEngine.Rendering.Universal;
using UnityEngine.Rendering.Universal.Internal;
using UnityEngine.Rendering.RenderGraphModule;
namespace StylizedWater3
{
public class SetupConstants : ScriptableRenderPass
{
private ProfilingSampler m_ProfilingSampler;
private static readonly int _CausticsProjectionAvailable = Shader.PropertyToID("_CausticsProjectionAvailable");
private static readonly int CausticsProjection = Shader.PropertyToID("CausticsProjection");
private static readonly int _WaterSSRParams = Shader.PropertyToID("_WaterSSRParams");
private static readonly int _WaterSSRSettings = Shader.PropertyToID("_WaterSSRSettings");
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;
/*
//Whilst required for these features, do not impose onto the render pipeline
//User must manage the enabled state of depth texture
if (settings.screenSpaceReflectionSettings.allow || settings.allowDirectionalCaustics)
{
ConfigureInput(ScriptableRenderPassInput.Depth);
}
*/
}
private class PassData
{
public UniversalCameraData cameraData;
public bool directionalCaustics;
public Matrix4x4 causticsProjection;
public bool ssr;
public bool ssrSkybox;
}
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
UniversalLightData lightData = frameData.Get<UniversalLightData>();
UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();
#if UNITY_EDITOR || DEVELOPMENT_BUILD
frameData.GetOrCreate<StylizedWaterRenderFeature.DebugData>();
#endif
using (var builder = renderGraph.AddRasterRenderPass<PassData>("Water Constants", out var passData, m_ProfilingSampler))
{
passData.ssr = settings.screenSpaceReflectionSettings.allow;
passData.ssrSkybox = settings.screenSpaceReflectionSettings.reflectEverything;
passData.directionalCaustics = settings.allowDirectionalCaustics;
if (passData.directionalCaustics)
{
//When no lights are visible, main light will be set to -1.
if (lightData.mainLightIndex > -1)
{
mainLight = lightData.visibleLights[lightData.mainLightIndex];
if (mainLight.lightType == LightType.Directional)
{
causticsProjection = Matrix4x4.Rotate(mainLight.light.transform.rotation);
passData.causticsProjection = causticsProjection.inverse;
passData.cameraData = cameraData;
}
else
{
passData.directionalCaustics = false;
}
}
else
{
passData.directionalCaustics = false;
}
}
//Pass should always execute
builder.AllowPassCulling(false);
builder.AllowGlobalStateModification(true);
builder.SetRenderFunc((PassData data, RasterGraphContext rgContext) =>
{
Execute(rgContext.cmd, data);
});
}
}
static void Execute(RasterCommandBuffer cmd, PassData data)
{
cmd.SetGlobalVector(_WaterSSRParams, new Vector4(data.ssr ? 1 : 0, data.ssrSkybox ? 1 : 0, 0));
//Exposed settings not ready yet, would like to refactor the raymarching to use a step-distance, rather than a fixed number of steps.
cmd.SetGlobalVector(_WaterSSRSettings, new Vector4(12, 0.75f, 100f, 1.0f));
cmd.SetGlobalInt(_CausticsProjectionAvailable, data.directionalCaustics ? 1 : 0);
if (data.directionalCaustics)
{
cmd.SetGlobalMatrix(CausticsProjection, data.causticsProjection);
//Sets up the required View- -> Clip-space matrices
NormalReconstruction.SetupProperties(cmd, data.cameraData);
}
}
public override void OnCameraCleanup(CommandBuffer cmd)
{
//Important to disable these features, as the next camera rendering may be using a different renderer altogether
cmd.SetGlobalInt(_CausticsProjectionAvailable, 0);
cmd.SetGlobalVector(_WaterSSRParams, Vector4.zero);
}
public void Dispose()
{
}
#if !UNITY_6000_4_OR_NEWER
#pragma warning disable CS0672
#pragma warning disable CS0618
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { }
#pragma warning restore CS0672
#pragma warning restore CS0618
#endif
}
}
#endif
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 68f653ef6f17f454595b16a58a2cdbfd
timeCreated: 1701078225
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/Rendering/SetupConstants.cs
uploadId: 895866
@@ -0,0 +1,385 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
#if UNITY_EDITOR || DEVELOPMENT_BUILD
#define DEBUG_AVAILABLE
#endif
#if URP
using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.RenderGraphModule.Util;
using UnityEngine.Rendering.Universal;
using UnityEngine.Serialization;
namespace StylizedWater3
{
[DisallowMultipleRendererFeature("Stylized Water 3")]
public partial class StylizedWaterRenderFeature : ScriptableRendererFeature
{
[Tooltip("Render all transparent materials NOT on the \"Water\" layer into a screen-space texture, and use that for refraction rendering in the water." +
"\n\n" +
"If enabled, transparent materials can be submerged and refracted correctly")]
public bool transparencyRefraction;
public static StylizedWaterRenderFeature GetDefault()
{
return (StylizedWaterRenderFeature)PipelineUtilities.GetRenderFeature<StylizedWaterRenderFeature>();
}
[Serializable]
public class ScreenSpaceReflectionSettings
{
[FormerlySerializedAs("enable")]
[Tooltip("Allow SSR to be rendered in water materials that have it enabled." +
"\n\nDisable as a global performance scaling measure")]
public bool allow = true;
[FormerlySerializedAs("reflectSkybox")]
[Tooltip("Only enable when Reflection Probes cannot be used in a realtime lighting setup. If enabled, SSR will also reflects the skybox color and geometry in front of the water." +
"\n\nIdeally disabled, so that Reflection Probes can be relied on for a 1:1 accurate skybox reflection.")]
public bool reflectEverything = false;
}
public ScreenSpaceReflectionSettings screenSpaceReflectionSettings = new ScreenSpaceReflectionSettings();
[FormerlySerializedAs("directionalCaustics")]
[Tooltip("Pass on the main directional light's projection onto the water shader. Allows caustics to project along its direction (rather than top-down)." +
"\n\nThis shading operation is relative expensive, as it involves analyzing the depth texture at 4 different points")]
public bool allowDirectionalCaustics;
public HeightPrePass.Settings heightPrePassSettings = new HeightPrePass.Settings();
#if SWS_DEV
public TerrainHeightPrePass.Settings terrainHeightPrePassSettings = new TerrainHeightPrePass.Settings();
#endif
private SetupConstants constantsSetup;
private HeightPrePass heightPrePass;
private HeightQuerySystem.RenderPass heightQueryPass;
#if SWS_DEV
private TerrainHeightPrePass terrainHeightPrePass;
private DistanceFieldPass distanceFieldPass;
private RenderTransparentTexture transparentTexturePass;
#endif
#if DEBUG_AVAILABLE
private DebugInspectorPass debugInspectorPass;
#endif
[SerializeField]
public ComputeShader heightReadbackCS;
[SerializeField]
public Shader heightProcessingShader;
/// <summary>
/// Set this to true from a render pass if it requires the displacement pre-pass, despite it being disabled in the render feature settings.
/// </summary>
public static bool RequireHeightPrePass;
protected bool WillExecuteHeightPrePass => RequireHeightPrePass || heightPrePassSettings.enable || HeightQuerySystem.RequiresHeightPrepass;
private void OnValidate()
{
VerifyReferences();
}
private void Reset()
{
VerifyReferences();
}
public void VerifyReferences()
{
if (!heightReadbackCS)
{
#if UNITY_EDITOR
//HeightSampler.cs
string assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath("768e0c28dfdbc6b429fd59518fa03f2d");
ComputeShader cs = UnityEditor.AssetDatabase.LoadAssetAtPath<ComputeShader>(assetPath);
if (cs)
{
heightReadbackCS = cs;
}
#endif
}
if(!heightProcessingShader) heightProcessingShader = Shader.Find(ShaderParams.ShaderNames.HeightProcessor);
#if SWS_DEV
if(!terrainHeightPrePassSettings.terrainHeightVisualizationShader) terrainHeightPrePassSettings.terrainHeightVisualizationShader = Shader.Find(ShaderParams.ShaderNames.TerrainHeight);
#endif
VerifyUnderwaterRendering();
}
public class DebugData : ContextItem
{
public TextureHandle currentHandle;
public override void Reset()
{
currentHandle = TextureHandle.nullHandle;
}
}
public override void Create()
{
GraphicsDeviceType currentGraphicsAPI = SystemInfo.graphicsDeviceType;
//https://issuetracker.unity3d.com/issues/crash-on-gfxdeviced3d12base-drawbufferscommon-when-adding-specific-custom-render-pass-feature-to-renderer
//Now fixed in Unity 6000.0.60f1
#if !UNITY_6000_1_OR_NEWER
if (currentGraphicsAPI == GraphicsDeviceType.Direct3D12 || currentGraphicsAPI == GraphicsDeviceType.XboxOneD3D12)
{
//Using the "BeforeRendering" point causes a fatal crash when using DX12 when allocating a RT
//defaultInjectionPoint = RenderPassEvent.BeforeRenderingShadows;
}
#endif
constantsSetup = new SetupConstants
{
renderPassEvent = defaultInjectionPoint
};
heightPrePass = new HeightPrePass
{
renderPassEvent = defaultInjectionPoint
};
heightQueryPass = new HeightQuerySystem.RenderPass()
{
renderPassEvent = defaultInjectionPoint
};
#if SWS_DEV
terrainHeightPrePass = new TerrainHeightPrePass()
{
renderPassEvent = defaultInjectionPoint
};
if (transparencyRefraction)
{
transparentTexturePass = new RenderTransparentTexture();
transparentTexturePass.renderPassEvent = RenderPassEvent.BeforeRenderingTransparents;
}
#endif
CreateFlowMapPass();
CreateDynamicEffectsPasses();
CreateUnderwaterRenderingPasses();
#if DEBUG_AVAILABLE
debugInspectorPass = new DebugInspectorPass();
debugInspectorPass.renderPassEvent = RenderPassEvent.AfterRendering;
#endif
}
//Note: Actually prefer to render before transparents, but this creates a recursive RenderSingleCamera call
//Restoring the view/projection to that of the camera also breaks VR. Required functions are internal URP code
//In some cases, if no pre-passes render (depth, shadows, etc) then the projection does not get reset when rendering the opaque objects pass. Hence, things must render as early as possible.
private static RenderPassEvent defaultInjectionPoint = RenderPassEvent.BeforeRendering;
//Dynamic Effects
partial void CreateDynamicEffectsPasses();
partial void AddDynamicEffectsPasses(ScriptableRenderer renderer, ref RenderingData renderingData);
partial void DisposeDynamicEffectsPasses();
partial void VerifyUnderwaterRendering();
partial void CreateUnderwaterRenderingPasses();
partial void AddUnderwaterRenderingPasses(ScriptableRenderer renderer, ref RenderingData renderingData);
partial void DisposeUnderwaterRenderingPasses();
partial void CreateFlowMapPass();
partial void AddFlowMapPass(ScriptableRenderer renderer, ref RenderingData renderingData);
partial void DisposeFlowMapPass();
private bool IsInvalidContext(CameraType cameraType, CameraRenderType cameraRenderType)
{
//Skip for any special use camera's (except scene view camera)
if (cameraType != CameraType.SceneView && (cameraType == CameraType.Preview || hideFlags != HideFlags.None))
{
return true;
}
//Skip overlay cameras
if (cameraRenderType == CameraRenderType.Overlay)
{
return true;
}
return false;
}
private bool isMainCamera(Camera camera)
{
//Skip for any special use camera's (except scene view camera)
return (camera.cameraType == CameraType.SceneView || camera.CompareTag("MainCamera"));
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
var currentCam = renderingData.cameraData.camera;
if(IsInvalidContext(currentCam.cameraType, renderingData.cameraData.renderType)) return;
constantsSetup.Setup(this);
renderer.EnqueuePass(constantsSetup);
#if SWS_DEV
if (terrainHeightPrePassSettings.enable)
{
terrainHeightPrePass.Setup(terrainHeightPrePassSettings);
renderer.EnqueuePass(terrainHeightPrePass);
}
if (transparencyRefraction)
{
renderer.EnqueuePass(transparentTexturePass);
}
#endif
//Do not execute for reflection probe captures
if (currentCam.cameraType != CameraType.Reflection)
{
AddFlowMapPass(renderer, ref renderingData);
AddDynamicEffectsPasses(renderer, ref renderingData);
//Do not execute for the scene-view camera in play-mode. Even if the tab is not active, it would render around it instead of the main camera
var skipHeightPrePass = Application.isPlaying && heightPrePassSettings.disableInSceneView && currentCam.cameraType == CameraType.SceneView;
//In play-mode, strictly execute for the main camera
skipHeightPrePass |= Application.isPlaying && !currentCam.CompareTag("MainCamera");
if (WillExecuteHeightPrePass && skipHeightPrePass == false)
{
//Debug.Log($"Executing height pre-pass for {currentCam.name}");
heightPrePass.Setup(heightPrePassSettings);
renderer.EnqueuePass(heightPrePass);
if (HeightQuerySystem.QueryCount > 0)
{
heightQueryPass.Setup(this, heightReadbackCS);
renderer.EnqueuePass(heightQueryPass);
}
}
else
{
Shader.SetGlobalInt(HeightPrePass._WaterHeightPrePassAvailable, 0);
}
}
AddUnderwaterRenderingPasses(renderer, ref renderingData);
#if DEBUG_AVAILABLE
if (RenderTargetDebugger.InspectedProperty > 0)
{
renderer.EnqueuePass(debugInspectorPass);
}
#endif
}
protected override void Dispose(bool disposing)
{
constantsSetup.Dispose();
heightPrePass.Dispose();
heightQueryPass.Dispose();
#if SWS_DEV
terrainHeightPrePass.Dispose();
#endif
DisposeFlowMapPass();
DisposeDynamicEffectsPasses();
DisposeUnderwaterRenderingPasses();
}
#if DEBUG_AVAILABLE
private class DebugInspectorPass : ScriptableRenderPass
{
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
DebugData debugData = frameData.Get<DebugData>();
//Whichever pass's render target PropertyID matches the selected one in the inspector window get assigned as the 'currentHandle'
if (debugData.currentHandle.IsValid())
{
var destinationDesc = renderGraph.GetTextureDesc(debugData.currentHandle);
destinationDesc.clearBuffer = false;
RenderTextureDescriptor rtDsc = new RenderTextureDescriptor
{
width = destinationDesc.width,
height = destinationDesc.height,
//If you're seeing an error here you are not using a compatible Unity version!
graphicsFormat = destinationDesc.colorFormat,
#if UNITY_6000_1_OR_NEWER
colorFormat = RenderTextureFormat.Default,
#endif
volumeDepth = 1,
dimension = destinationDesc.dimension,
useMipMap = destinationDesc.useMipMap,
msaaSamples = 1
};
TextureDesc textureDesc = debugData.currentHandle.GetDescriptor(renderGraph);
var allocate = RenderTargetDebugger.CurrentRT == null || RenderTargetDebugger.CurrentRT.rt == null;
if (allocate == false)
{
allocate |= RenderTargetDebugger.CurrentRT.rt.name != textureDesc.name;
//RTMF :') Update to a supported Unity 6.0 version if this throws an error
allocate |= RenderTargetDebugger.CurrentRT.rt.graphicsFormat != textureDesc.format;
allocate |= RenderTargetDebugger.CurrentRT.rt.width != textureDesc.width;
allocate |= RenderTargetDebugger.CurrentRT.rt.height != textureDesc.height;
}
if (allocate)
{
textureDesc.name += " (Debug)";
//Debug.Log($"Reallocating debug RT ({textureDesc.name})");
RenderTargetDebugger.CurrentRT?.Release();
RenderTargetDebugger.CurrentRT = RTHandles.Alloc(rtDsc, name: textureDesc.name);
}
//Idiotic function keeps causing memory leaks since Unity 2021
//RenderingUtils.ReAllocateHandleIfNeeded(ref RenderTargetDebugger.CurrentRT, rtDsc, textureDesc.filterMode, textureDesc.wrapMode, textureDesc.anisoLevel, textureDesc.mipMapBias, textureDesc.name);
TextureHandle destination = renderGraph.ImportTexture(RenderTargetDebugger.CurrentRT);
if (destination.IsValid() == false)
{
throw new Exception("Failed to generate debugger texture");
}
else
{
var cameraData = frameData.Get<UniversalCameraData>();
RenderTargetDebugger.CurrentCameraName = cameraData.camera.name;
//Copy TextureHandle into persistent RT
renderGraph.AddCopyPass(debugData.currentHandle, destination, passName: "Water Debug");
}
}
else
{
RenderTargetDebugger.CurrentRT = null;
RenderTargetDebugger.CurrentCameraName = string.Empty;
}
}
#if !UNITY_6000_4_OR_NEWER
#pragma warning disable CS0672
#pragma warning disable CS0618
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { }
#pragma warning restore CS0672
#pragma warning restore CS0618
#endif
}
#endif //DEBUG_AVAILABLE
}
}
#endif
@@ -0,0 +1,21 @@
fileFormatVersion: 2
guid: 1c6e585d9d2317e438a403bbb1965d5e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- heightReadbackCS: {fileID: 7200000, guid: 768e0c28dfdbc6b429fd59518fa03f2d, type: 3}
- heightProcessingShader: {fileID: 4800000, guid: a1cfcf2c2c8140c084fb10845093540e,
type: 3}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/Rendering/StylizedWaterRenderFeature.cs
uploadId: 895866
@@ -0,0 +1,55 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using UnityEngine;
namespace StylizedWater3
{
public static class ShaderParams
{
public static class Properties
{
public static readonly int _Direction = Shader.PropertyToID("_Direction");
public static readonly int _Speed = Shader.PropertyToID("_Speed");
public static readonly int _WaveSpeed = Shader.PropertyToID("_WaveSpeed");
public static readonly int _WaveFrequency = Shader.PropertyToID("_WaveFrequency");
public static readonly int _WaveMaxLayers = Shader.PropertyToID("_WaveMaxLayers");
public static readonly int _WaveHeight = Shader.PropertyToID("_WaveHeight");
public static readonly int _WaveProfile = Shader.PropertyToID("_WaveProfile");
}
public static class Keywords
{
public const string Waves = "_WAVES";
public const string Translucency = "_TRANSLUCENCY";
public const string Caustics = "_CAUSTICS";
public const string Refraction = "_REFRACTION";
public const string River = "_RIVER";
public const string UnderwaterRendering = "UNDERWATER_ENABLED";
public const string DynamicEffects = "DYNAMIC_EFFECTS_ENABLED";
public const string WaterHeightPass = "WATER_HEIGHT_PASS";
}
public static class LightModes
{
public const string WaterHeight = "WaterHeight";
}
public static class ShaderNames
{
public const string TESSELLATION_NAME_SUFFIX = " (Tessellation)";
public const string HeightProcessor = "Hidden/StylizedWater3/HeightProcessor";
public const string TerrainHeight = "Hidden/StylizedWater3/TerrainHeight";
}
public static class Passes
{
//Keep in sync with shader!
public const string HeightPrePass = "Height";
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 2faf9739cf434720a27019e2ae81ce17
timeCreated: 1719930222
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/ShaderParams.cs
uploadId: 895866
@@ -0,0 +1,258 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Serialization;
using Random = System.Random;
namespace StylizedWater3
{
[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;
private static Vector4 defaultTangent = new Vector4(-1f, 0f, 0f, -1f);
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>();
List<Vector3> normals = new List<Vector3>();
List<Vector4> tangents = new List<Vector4>();
// 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)));
normals.Add(Vector3.up);
tangents.Add(defaultTangent);
}
// 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
int vertexCount = vertices.Count;
if (vertexCount >= 65536) m.indexFormat = IndexFormat.UInt32;
m.SetVertices(vertices, 0, vertexCount, MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontValidateIndices);
m.SetTriangles(tris, 0, false);
m.SetUVs(0, uvs);
m.SetUVs(1, uvs2);
m.colors = new Color[vertexCount];
m.SetNormals(normals, 0, vertexCount, MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontValidateIndices);
m.SetTangents(tangents, 0, vertexCount, MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontValidateIndices);
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 vertexCount = xCount * zCount;
Vector3[] vertices = new Vector3[vertexCount];
Vector2[] uvs = new Vector2[vertexCount];
Vector2[] uvs2 = new Vector2[vertexCount];
int[] triangles = new int[numTriangles];
Vector4[] tangents = new Vector4[vertexCount];
Vector3[] normals = new Vector3[vertexCount];
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);
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);
tangents[index] = defaultTangent;
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;
}
}
if (vertexCount >= 65536) m.indexFormat = IndexFormat.UInt32;
m.SetVertices(vertices, 0, vertexCount, MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontValidateIndices);;
m.SetTriangles(triangles, 0, false);
m.SetUVs(0, uvs);
m.SetUVs(1, uvs2);
m.SetNormals(normals, 0, vertexCount);
m.SetTangents(tangents, 0, vertexCount);
m.colors = new Color[vertexCount];
m.bounds = new Bounds(Vector3.zero, new Vector3(scale, boundsPadding, scale));
return m;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c61589328b4a7874a822ec745877bc7c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/WaterMesh.cs
uploadId: 895866
@@ -0,0 +1,352 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using System;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Rendering;
using Object = UnityEngine.Object;
using Random = UnityEngine.Random;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace StylizedWater3
{
[Serializable]
[HelpURL("https://staggart.xyz/unity/stylized-water-3/sw3-docs/?section=waves-2")]
public class WaveProfile : ScriptableObject
{
const float MAX_AMPLITUDE = 5f;
public const int MAX_LAYERS = 64;
[Min(0.01f)]
[InspectorName("Wave length")]
public float waveLengthMultiplier = 1f;
[Min(0.01f)]
public float amplitudeMultiplier = 1f;
[Min(0.01f)]
public float steepnessMultiplier = 1f;
[Range(0f, 1f)]
[Tooltip("A steepness value too high can result in wave crest \"looping\" the geometry.\n\n" +
"To avoid this from happening, the steepness value for each Layer can be clamped to an average.")]
public float steepnessClamping = 1f;
[Space]
[Header(("Curves (value over layer index)"))]
[Tooltip("Scales the wave length over each layer." +
"\n\n" +
"Left=first layer, Right=last layer")]
public AnimationCurve waveLengthCurve = AnimationCurve.Linear(0f, 1f, 1f, 1f);
public AnimationCurve amplitudeCurve = AnimationCurve.Linear(0f, 1f, 1f, 1f);
public AnimationCurve steepnessCurve = AnimationCurve.Linear(0f, 1f, 1f, 1f);
[Serializable]
public class ProceduralSettings
{
public int seed = 0;
[Range(1, MAX_LAYERS)]
[Tooltip("Number of individual wave layers. Aim for the lowest amount as possible")]
public int numLayers = 4;
[Space]
[Tooltip("The wave length represents the distance between two wave peaks. Use a high maximum value to create stormy ocean swells.")]
public Vector2 minMaxWaveLength = new Vector2(8f, 50f);
[Tooltip("Height of the wave, from its base to its peak")]
public Vector2 minMaxAmplitude = new Vector2(0.1f, 1f);
[Range(0f,1f)]
[Tooltip("Scale the amplitude by the wavelength. If at 1, short waves becomes very short as well")]
public float amplitudeByLength;
[Tooltip("Steepness is the amount of horizontal movement a wave creates")]
public Vector2 minMaxSteepness = new Vector2(0.1f, 1f);
[Range(0f,1f)]
[Tooltip("Scale up the steepness value by the wave length, making large waves displace the water more horizontally")]
public float steepnessByLength;
[Space]
[Range(0f,360f)]
public float directionBase = 0f;
[Range(0f, 360)]
[Tooltip("If at 0, all waves move in a single direction, If at 360, all of them go in a random direction")]
public float directionAngleVariation = 180f;
public void Apply(WaveProfile waveProfile)
{
Array.Resize(ref waveProfile.layers, numLayers);
float ScaleByLength(float value, float min, float max, float length, float amount)
{
value *= Mathf.Lerp(1f, value / length, amount);
value = Mathf.Max(value, min);
return value;
}
int layerCount = waveProfile.layers.Length;
for (int i = 0; i < layerCount; i++)
{
if (waveProfile.layers[i] == null) waveProfile.layers[i] = new Wave();
Wave layer = waveProfile.layers[i];
Random.InitState(seed + i);
float t = (float)i / (float)layerCount;
layer.direction = Mathf.Repeat(directionBase + Random.Range(-directionAngleVariation, directionAngleVariation), 360f);
layer.waveLength = Random.Range(minMaxWaveLength.x, minMaxWaveLength.y);
layer.amplitude = Random.Range(minMaxAmplitude.x, minMaxAmplitude.y);
layer.amplitude = ScaleByLength(layer.amplitude, minMaxAmplitude.x, minMaxAmplitude.y, layer.waveLength, amplitudeByLength);
layer.steepness = Random.Range(minMaxSteepness.x, minMaxSteepness.y);
layer.steepness = ScaleByLength(layer.steepness, minMaxSteepness.x, minMaxSteepness.y, layer.waveLength, steepnessByLength);
}
waveProfile.UpdateShaderParameters();
}
}
/// <summary>
/// House various parameter values used for randomize wave profile creation. Call the <see cref="Apply">Apply</see> function to use the settings to generated randomized wave layers.
/// </summary>
public ProceduralSettings proceduralSettings = new ProceduralSettings();
/// <summary>
/// Class to describe a single Gerstner Wave
/// </summary>
[Serializable]
public class Wave
{
[Tooltip("Directional: Waves move in a specific direction (angle)" +
"\n\nRadial: Wave originates from the position defined below")]
public enum Mode
{
Directional,
Radial
}
public bool enabled = true;
[Space]
[Tooltip("Distance between each crest")]
[Range(0.1f, 64f)]
public float waveLength = 10f;
[Tooltip("Height of the wave in units(m)")]
[Range(0.001f, MAX_AMPLITUDE)]
public float amplitude = 1f;
[Tooltip("Amount of horizontal movement. Values too high can cause the crest of a wave to \"loop\"")]
[Range(0.001f, 1f)]
public float steepness = 0.5f;
public Mode mode;
[Tooltip("Direction the wave travels forward in degrees (on the Y-axis)")]
[Range(0f, 360f)]
public float direction;
[Tooltip("Position in world-space")]
public Vector2 origin;
}
[Space]
public Wave[] layers = new Wave[8];
public Texture2D shaderParametersLUT;
public float averageSteepness
{
get;
private set;
}
public float averageAmplitude
{
get;
private set;
}
private void Reset()
{
for (int i = 0; i < layers.Length; i++)
{
layers[i] = new Wave();
float t = (float)i / (float)layers.Length;
if (i == 0)
{
layers[i].enabled = true;
}
else
{
layers[i].enabled = false;
layers[i].direction = t * 360f + Random.Range(-15f, 15f);
layers[i].waveLength = layers.Length - (t * Random.value);
}
}
UpdateShaderParameters();
}
public void UpdateShaderParameters()
{
if (layers.Length == 0) return;
shaderParametersLUT = CreateLookUpTable();
shaderParametersLUT.hideFlags = HideFlags.NotEditable;
shaderParametersLUT.name = this.name + " Shader Parameters";
#if UNITY_EDITOR
string path = AssetDatabase.GetAssetPath(this);
if (path == string.Empty) return;
Texture2D file = (Texture2D)AssetDatabase.LoadAssetAtPath(path, typeof(Texture2D));
if (file == null)
{
Object mainAsset = (WaveProfile)AssetDatabase.LoadAssetAtPath(path, typeof(WaveProfile));
AssetDatabase.AddObjectToAsset(shaderParametersLUT, mainAsset);
AssetDatabase.SaveAssets();
//Import
path = AssetDatabase.GetAssetPath(shaderParametersLUT);
//Reference serialized texture asset
shaderParametersLUT = (Texture2D)AssetDatabase.LoadAssetAtPath(path, typeof(Texture2D));
}
else
{
EditorUtility.CopySerialized(shaderParametersLUT, file);
file.name = shaderParametersLUT.name;
}
//Reference serialized texture asset on disk
shaderParametersLUT = file;
#endif
}
//Having a total of 8 float4 components, 2 rows are required to store them.
private const int LUT_ROWS = 2;
//Settings for a wave layer, converted for GPU use
public struct WaveParameters
{
public float waveLength;
public float amplitude;
public float direction;
public float steepness;
public float2 origin;
public uint mode;
public uint enabled;
};
private Texture2D CreateLookUpTable()
{
int layerCount = layers.Length;
if (layers.Length == 0)
{
throw new Exception("Cannot create a wave profile LUT from 0 wave layers!");
}
//16-bit precision since values can exceed 1
Texture2D texture = new Texture2D(layerCount, LUT_ROWS, TextureFormat.RGBAHalf, false, true)
{
filterMode = FilterMode.Point,
wrapMode = TextureWrapMode.Clamp,
};
//The summed steepness must never exceed a value of 1 in the final calculations
//This would otherwise incur visible loops in the wave crests
//To avoid this, divide the steepness parameter value by the average
RecalculateAverages();
float steepnessRCP = 1f / averageSteepness;
float amplitudeRCP = 1f / averageSteepness;
for (int x = 0; x < layerCount; x++)
{
//Normalized value of the layer (0-1)
float t = (float)x / (float)layerCount;
WaveParameters parameters = CreateWaveParameters(layers[x], t, steepnessRCP);
Color row0 = new Color(parameters.amplitude, parameters.waveLength, parameters.direction, parameters.enabled);
texture.SetPixel(x, 0, row0);
Color row1 = new Color(parameters.origin.x, parameters.origin.y, parameters.mode, parameters.steepness);
texture.SetPixel(x, 1, row1);
}
texture.Apply();
return texture;
}
public WaveParameters CreateWaveParameters(Wave wave, float t, float steepnessRCP)
{
WaveParameters parameters = new WaveParameters
{
enabled = (uint)(wave.enabled ? 1 : 0),
waveLength = Mathf.Max(0.01f, wave.waveLength * waveLengthMultiplier * waveLengthCurve.Evaluate(t)),
amplitude = (wave.amplitude * amplitudeMultiplier * amplitudeCurve.Evaluate(t)),
direction = wave.direction * Mathf.Deg2Rad,
};
parameters.origin.x = wave.origin.x;
parameters.origin.y = wave.origin.y;
parameters.mode = (uint)wave.mode;
parameters.steepness = wave.steepness * steepnessMultiplier * steepnessCurve.Evaluate(t) * Mathf.Lerp(1f, steepnessRCP, steepnessClamping);
return parameters;
}
public void RecalculateAverages()
{
averageSteepness = 0f;
averageAmplitude = 0f;
int activeLayers = 0;
for (int x = 0; x < layers.Length; x++)
{
//Normalized value of the layer (0-1)
float t = (float)x / (float)layers.Length;
if (layers[x].enabled)
{
activeLayers++;
averageSteepness += layers[x].steepness * steepnessMultiplier * steepnessCurve.Evaluate(t);
averageAmplitude += layers[x].amplitude * amplitudeMultiplier * amplitudeCurve.Evaluate(t);
}
}
if (activeLayers > 0)
{
averageSteepness = activeLayers;
averageAmplitude /= activeLayers;
}
}
/// <summary>
/// Assign this wave profile to a material using the Stylized Water 3 shader
/// </summary>
/// <param name="material"></param>
public void ApplyToMaterial(Material material)
{
material.SetTexture(ShaderParams.Properties._WaveProfile, shaderParametersLUT);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: ca52dc65c92791641abb4a20efacdf21
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 0c0ecf1cb80894043a5dd86626f73513, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/WaveProfile.cs
uploadId: 895866
@@ -0,0 +1,54 @@
{
"name": "sc.stylizedwater3.runtime",
"rootNamespace": "",
"references": [
"GUID:15fc0a57446b3144c949da3e2b9737a9",
"GUID:df380645f10b7bc4b97d4f5eb6303d95",
"GUID:d8b63aba1907145bea998dd612889d6b",
"GUID:75ecb28acc33857438e533566abcb3be",
"GUID:f06555f75b070af458a003d92f9efb00",
"GUID:9cb5eaf8df574e829047543e7b48b611",
"GUID:21d1eb854b91ade49bc69a263d12bee2",
"GUID:75469ad4d38634e559750d17036d5f7c"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [
{
"name": "com.unity.render-pipelines.universal",
"expression": "17.0.3",
"define": "URP"
},
{
"name": "com.unity.visualeffectgraph",
"expression": "17.0.3",
"define": "VFX_GRAPH"
},
{
"name": "com.unity.splines",
"expression": "2.4.0",
"define": "SPLINES"
},
{
"name": "com.unity.modules.xr",
"expression": "1.0.0",
"define": "ENABLE_XR_MODULE"
},
{
"name": "com.unity.mathematics",
"expression": "1.3.0",
"define": "MATHEMATICS"
},
{
"name": "com.unity.inputsystem",
"expression": "1.11.0",
"define": "INPUT_SYSTEM_INSTALLED"
}
],
"noEngineReferences": false
}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 4fd586483f5d4a24491f09604d74752b
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/sc.stylizedwater3.runtime.asmdef
uploadId: 895866