(Update) Clean Projet
This commit is contained in:
@@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -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
|
||||
Reference in New Issue
Block a user