diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity index 071391c..3a489f0 100644 --- a/Assets/Scenes/SampleScene.unity +++ b/Assets/Scenes/SampleScene.unity @@ -140790,6 +140790,7 @@ GameObject: - component: {fileID: 963194227} - component: {fileID: 963194226} - component: {fileID: 963194229} + - component: {fileID: 963194230} m_Layer: 0 m_Name: Main Camera m_TagString: MainCamera @@ -140864,14 +140865,26 @@ Transform: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 963194225} serializedVersion: 2 - m_LocalRotation: {x: 0.23506844, y: 0.3851214, z: -0.1021131, w: 0.8865649} - m_LocalPosition: {x: 6.34, y: 2.02, z: 3.8} + m_LocalRotation: {x: 0.23339066, y: 0.37273654, z: -0.097438015, w: 0.8928058} + m_LocalPosition: {x: 6.6, y: 3.5, z: 4.2} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 29.7, y: 46.96, z: 0} + m_LocalEulerAnglesHint: {x: 29.3, y: 45.32, z: 0} --- !u!114 &963194229 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_Enabled: 0 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 78c9485abe801d74184e00172d4b42c4, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &963194230 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -140880,7 +140893,7 @@ MonoBehaviour: m_GameObject: {fileID: 963194225} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 78c9485abe801d74184e00172d4b42c4, type: 3} + m_Script: {fileID: 11500000, guid: d9be733811a3b0342953552aa8d5ab8a, type: 3} m_Name: m_EditorClassIdentifier: --- !u!43 &963631122 @@ -290519,7 +290532,6 @@ MonoBehaviour: numChunks: 20 bladesPerSquareMeter: 100 chunkPrefab: {fileID: 6414307754184109852, guid: abb47bb4e9622f7428302dfa4730d722, type: 3} - ignoreHeightForLOD: 0 grassMaterialLOD0: {fileID: 2100000, guid: d979d9bbf3760d948a452ba192cbf563, type: 2} minDistanceLOD0: 3.5 densityLOD0: 1 diff --git a/Assets/Scripts/GrassField.cs b/Assets/Scripts/GrassField.cs index 0dc2cc4..b057a5b 100644 --- a/Assets/Scripts/GrassField.cs +++ b/Assets/Scripts/GrassField.cs @@ -1,37 +1,30 @@ using System; using System.Collections.Generic; using UnityEngine; -using UnityEngine.Serialization; public class GrassField : MonoBehaviour { public int size = 100; public int numChunks = 20; public int bladesPerSquareMeter = 100; public GrassChunk chunkPrefab; - public bool ignoreHeightForLOD = true; - [Header("LOD 0")] - public Material grassMaterialLOD0; + [Header("LOD 0")] public Material grassMaterialLOD0; public float minDistanceLOD0 = 2f; [Range(0f, 1f)] public float densityLOD0 = 1f; public bool enableShadowsLOD0 = true; - [Header("LOD 1")] - public float distanceLOD1 = 5f; + [Header("LOD 1")] public float distanceLOD1 = 5f; public Material grassMaterialLOD1; [Range(0f, 1f)] public float densityLOD1 = 0.5f; public bool enableShadowsLOD1 = true; - [Header("LOD 2")] - public float distanceLOD2 = 15f; + [Header("LOD 2")] public float distanceLOD2 = 15f; public Material grassMaterialLOD2; [Range(0f, 1f)] public float densityLOD2 = 0.25f; public bool enableShadowsLOD2 = false; - [Header("LOD 3")] - public float distanceLOD3 = 25f; + [Header("LOD 3")] public float distanceLOD3 = 25f; public Material grassMaterialLOD3; [Range(0f, 1f)] public float densityLOD3 = 0.125f; public bool enableShadowsLOD3 = false; - + private const float SQRT_2 = 1.4142135623730f; - private static readonly int pId_WorldSpaceCameraPosEditor = Shader.PropertyToID("_WorldSpaceCameraPosEditor"); private static readonly int pId_TransitionRange = Shader.PropertyToID("_TransitionRange"); // a 2D array would be cleaner, but it can't be serialized @@ -49,12 +42,12 @@ public class GrassField : MonoBehaviour { var camPosWorld = Camera.main ? Camera.main.transform.position : Vector3.zero; UpdateLODs(camPosWorld); - UpdateMaterials(camPosWorld); + UpdateMaterials(); } public void UpdateLODsAndMaterials() { var camPosWorld = Camera.main ? Camera.main.transform.position : Vector3.zero; - UpdateMaterials(camPosWorld); + UpdateMaterials(); UpdateLODs(camPosWorld); } @@ -64,11 +57,12 @@ public class GrassField : MonoBehaviour { if (distanceLOD2 < minDistLOD2) { Debug.LogWarning("It is recommended that minDistLOD2 is greater than " + minDistLOD2 + "."); } + var minDistLOD3 = distanceLOD2 + chunkSize * SQRT_2; if (distanceLOD3 < minDistLOD3) { Debug.LogWarning("It is recommended that minDistLOD3 is greater than " + minDistLOD3 + "."); } - + if (_chunks.Length != numChunks * numChunks) { Debug.LogError(this.name + ": Existing chunks does not match numChunks."); gameObject.SetActive(false); @@ -81,26 +75,21 @@ public class GrassField : MonoBehaviour { UpdateLODsAndMaterials(); } - private void FixedUpdate() { + private void Update() { +#if UNITY_EDITOR + // update LOD distances (might have been changed via inspector) + UpdateLODsAndMaterials(); +#else + if (_chunks.Length != numChunks * numChunks) return; var cam = Camera.main; if (!cam) return; - var camPosWorld = cam.transform.position; - -#if UNITY_EDITOR - // update LOD distances and camera position - UpdateMaterials(camPosWorld); + UpdateLODs(cam.transform.position, false); // while the game is running, only update the chunks near the camera #endif - - if (_chunks.Length != numChunks * numChunks) return; - UpdateLODs(camPosWorld, false); // while the game is running, only the chunks near the camera are updated } private void UpdateLODs(Vector3 camPosWorld, bool forceCompleteCheck = true) { var camPos = camPosWorld - transform.position; // cam position relative to grass field - if (ignoreHeightForLOD) { - camPos.y = 0; - } - + var chunkSize = ((float)size) / numChunks; var bounds = new Bounds( new Vector3(0.5f * chunkSize, 0f, 0.5f * chunkSize), @@ -109,7 +98,7 @@ public class GrassField : MonoBehaviour { // small performance difference (0.13ms vs 0.02ms on a 100m 30x30 grid, LOD dist 26.6, camera at corner) var range = forceCompleteCheck - ? new GridRange{ fromX = 0, toX = numChunks-1, fromZ = 0, toZ = numChunks-1 } + ? new GridRange { fromX = 0, toX = numChunks - 1, fromZ = 0, toZ = numChunks - 1 } : GetLODGridRange(camPos, chunkSize); for (int x = range.fromX; x <= range.toX; x++) @@ -142,17 +131,17 @@ public class GrassField : MonoBehaviour { private GridRange GetLODGridRange(Vector3 camPos, float chunkSize) { GridRange range; - + var maxCamMove = 3f; // assume that the camera did not move further than this distance var fromXWorld = camPos.x - distanceLOD3 - maxCamMove; var toXWorld = camPos.x + distanceLOD3 + maxCamMove; var fromZWorld = camPos.z - distanceLOD3 - maxCamMove; var toZWorld = camPos.z + distanceLOD3 + maxCamMove; - - range.fromX = Math.Max( 0, (int) (fromXWorld / chunkSize) ); - range.toX = Math.Max( 0, (int) (toXWorld / chunkSize) ); - range.fromZ = Math.Max( 0, (int) (fromZWorld / chunkSize) ); - range.toZ = (int) (toZWorld / chunkSize); + + range.fromX = Math.Max(0, (int)(fromXWorld / chunkSize)); + range.toX = Math.Max(0, (int)(toXWorld / chunkSize)); + range.fromZ = Math.Max(0, (int)(fromZWorld / chunkSize)); + range.toZ = (int)(toZWorld / chunkSize); range.fromX = Math.Clamp(range.fromX, 0, numChunks - 1); range.toX = Math.Clamp(range.toX, 0, numChunks - 1); range.fromZ = Math.Clamp(range.fromZ, 0, numChunks - 1); @@ -161,28 +150,19 @@ public class GrassField : MonoBehaviour { return range; } - private void UpdateMaterials(Vector3 camPosWorld) { + private void UpdateMaterials() { var chunkSize = ((float)size) / numChunks; var chunkDiagonalLength = chunkSize * SQRT_2; var transition0From = minDistanceLOD0; - var transition0To = distanceLOD1; + var transition0To = distanceLOD1; var transition1From = distanceLOD1 + chunkDiagonalLength; - var transition1To = distanceLOD2; + var transition1To = distanceLOD2; var transition2From = distanceLOD2 + chunkDiagonalLength; - var transition2To = distanceLOD3; + var transition2To = distanceLOD3; grassMaterialLOD0.SetVector(pId_TransitionRange, new Vector2(transition0From, transition0To)); grassMaterialLOD1.SetVector(pId_TransitionRange, new Vector2(transition1From, transition1To)); grassMaterialLOD2.SetVector(pId_TransitionRange, new Vector2(transition2From, transition2To)); -#if UNITY_EDITOR - if (ignoreHeightForLOD) { - camPosWorld.y = 0; - } - grassMaterialLOD0.SetVector(pId_WorldSpaceCameraPosEditor, camPosWorld); - grassMaterialLOD1.SetVector(pId_WorldSpaceCameraPosEditor, camPosWorld); - grassMaterialLOD2.SetVector(pId_WorldSpaceCameraPosEditor, camPosWorld); - grassMaterialLOD3.SetVector(pId_WorldSpaceCameraPosEditor, camPosWorld); -#endif } private void CreateChunks( @@ -195,6 +175,7 @@ public class GrassField : MonoBehaviour { // Destroy(obj) does not work in edit mode DestroyImmediate(chunksTransform.gameObject); } + // create GameObject "Chunks" as a container for all GrassChunk objects chunksContainer = new GameObject("Chunks"); chunksContainer.transform.SetParent(this.transform, false); @@ -216,7 +197,7 @@ public class GrassField : MonoBehaviour { var meshLOD3 = GenerateChunkMesh(chunkPos, chunkBounds, bladesLOD3, "grass_chunk_" + x + "_" + y + "_lod3"); var chunk = Instantiate(chunkPrefab, chunksContainer.transform); - chunk.name = "Chunk [" + x + ", " + y +"]"; + chunk.name = "Chunk [" + x + ", " + y + "]"; chunk.transform.localPosition = chunkPos; chunk.MaterialLOD0 = grassMaterialLOD0; @@ -260,6 +241,7 @@ public class GrassField : MonoBehaviour { mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; Debug.LogWarning(meshName + " has more than 65536 grass blades. Mesh.indexFormat set to UInt32."); } + mesh.SetVertices(rootPositions); mesh.SetUVs(0, tipOffsets_missingInNextLOD); // Vector4 UVs // AABB gets calculated automatically (considers all root positions) @@ -272,13 +254,16 @@ public class GrassField : MonoBehaviour { const float height = 1f; // a blade should not be higher than this mesh.bounds = new Bounds( previousBounds.center + new Vector3(0f, height * 0.5f, 0f), - previousBounds.size + new Vector3(2f*width, height, 2f*width) + previousBounds.size + new Vector3(2f * width, height, 2f * width) ); return mesh; } - + private void OnDrawGizmos() { + // when in editor, update LODs every frame. nothing to do with gizmos + UpdateLODsAndMaterials(); + var chunkSize = ((float)size) / numChunks; var tPos = transform.position; @@ -299,17 +284,12 @@ public class GrassField : MonoBehaviour { if (!cam) return; var center = cam.transform.position; var oldMatrix = Gizmos.matrix; - if (ignoreHeightForLOD) { - center.y = 0.01f; - // draw a circle instead of a sphere - Gizmos.matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(1f, 0.001f, 1f)); - } - + Gizmos.color = new Color(0f, 0.3f, 1f, 1f); Gizmos.DrawWireSphere(center, distanceLOD1); Gizmos.DrawWireSphere(center, distanceLOD2); Gizmos.DrawWireSphere(center, distanceLOD3); - + Gizmos.color = new Color(1.0f, 1.0f, 0.0f, 1f); var minDistLOD0 = minDistanceLOD0; var minDistLOD1 = distanceLOD1 + chunkSize * SQRT_2; @@ -317,7 +297,7 @@ public class GrassField : MonoBehaviour { Gizmos.DrawWireSphere(center, minDistLOD0); Gizmos.DrawWireSphere(center, minDistLOD1); Gizmos.DrawWireSphere(center, minDistLOD2); - + Gizmos.matrix = oldMatrix; } } diff --git a/Assets/Scripts/MainCameraPositionShaderSetter.cs b/Assets/Scripts/MainCameraPositionShaderSetter.cs new file mode 100644 index 0000000..62868c3 --- /dev/null +++ b/Assets/Scripts/MainCameraPositionShaderSetter.cs @@ -0,0 +1,17 @@ +using UnityEngine; +using UnityEditor; + +[ExecuteAlways] +public class MainCameraPositionShaderSetter : MonoBehaviour { + private static readonly int pId_MainCameraPosition = Shader.PropertyToID("_MainCameraPosition"); + + private void OnEnable() { + if (!Camera.main || Camera.main.gameObject != this.gameObject) { + Debug.LogWarning("MainCameraPositionShaderSetter set on object that is not the main camera"); + } + } + + private void Update() { + Shader.SetGlobalVector(pId_MainCameraPosition, this.transform.position); + } +} diff --git a/Assets/Scripts/MainCameraPositionShaderSetter.cs.meta b/Assets/Scripts/MainCameraPositionShaderSetter.cs.meta new file mode 100644 index 0000000..6a685de --- /dev/null +++ b/Assets/Scripts/MainCameraPositionShaderSetter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d9be733811a3b0342953552aa8d5ab8a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Shaders/include/GrassBladePass.hlsl b/Assets/Shaders/include/GrassBladePass.hlsl index 1865a28..9917fb9 100644 --- a/Assets/Shaders/include/GrassBladePass.hlsl +++ b/Assets/Shaders/include/GrassBladePass.hlsl @@ -6,9 +6,9 @@ sampler2D _FrabVarianz, _BladeWidthTex; float2 _TransitionRange; -#if UNITY_EDITOR -float3 _WorldSpaceCameraPosEditor; -#endif +// Custom variable that is set to the position of the main camera in the scene +// (instead of Unity's _WorldSpaceCameraPos, which uses the scene view camera in editor mode) +float3 _MainCameraPosition; struct MeshData { float4 vertex : POSITION; @@ -40,11 +40,7 @@ v2g vert (MeshData v) { } float getCameraDistance(float3 pos) { -#if UNITY_EDITOR - return distance(_WorldSpaceCameraPosEditor, pos); -#else - return distance(_WorldSpaceCameraPos, pos); -#endif + return distance(_MainCameraPosition, pos); } #if _LOD_LOD_0 @@ -92,17 +88,18 @@ void geom(point v2g IN[1], inout TriangleStream triStream) { g2f o; float3 previousSegmentCenter = basePos - float3(0,-1,0); // point beneath the basePos + int numSegments; #if _LOD_LOD_0 - int numSegments = lerp(11.99, 5.0, interpolator); // 11 - 5 + numSegments = lerp(11.99, 5.0, interpolator); // 11 - 5 #elif _LOD_LOD_1 - int numSegments = lerp(5.99, 2.0, interpolator); // 5 - 2 + numSegments = lerp(5.99, 2.0, interpolator); // 5 - 2 #elif _LOD_LOD_2 - int numSegments = 2; + numSegments = 2; #elif _LOD_LOD_3 - int numSegments = 1; + numSegments = 1; #endif - float3 localCameraPos = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos.xyz, 1.0)).xyz; + float3 localCameraPos = mul(unity_WorldToObject, float4(_MainCameraPosition.xyz, 1.0)).xyz; for (int i = 0; i < numSegments; i++) { const float lowerThickness = 0.8; @@ -136,7 +133,7 @@ void geom(point v2g IN[1], inout TriangleStream triStream) { } //normalWS = float3(1,1,1) * lookingFromAbove; - //normalWS = basePosWS - _WorldSpaceCameraPos.xyz; + //normalWS = basePosWS - _MainCameraPosition.xyz; //normalWS = camVec; o.pos = UnityObjectToClipPos(vertLeftBase); @@ -262,7 +259,7 @@ float4 frag(g2f i) : SV_Target{ half3 skyColor = DecodeHDR(skyData, unity_SpecCube0_HDR); float3 lightReflectDirection = reflect(-lightDirection, worldNormal); - float3 viewDirection = normalize(float3(float4(_WorldSpaceCameraPos.xyz, 1.0) - i.worldPos.xyz)); + float3 viewDirection = normalize(float3(float4(_MainCameraPosition.xyz, 1.0) - i.worldPos.xyz)); float atten = 1.0;