Compare commits

..

No commits in common. "5c2d0c726004479418a92bdb0a4fdc2f54494bf9" and "794125696fd18cd6f0529ec065c318e4718a3736" have entirely different histories.

26 changed files with 37625 additions and 285596 deletions

View File

@ -1,31 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Grass LOD 0
m_Shader: {fileID: 4800000, guid: eebe2969663fc40449d6052c661a6c2e, type: 3}
m_Parent: {fileID: 2100000, guid: 1cd7df4d77cf1ae40ad59a15c2769be9, type: 2}
m_ModifiedSerializedProperties: 0
m_ValidKeywords:
- _LOD_LOD_0
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs: []
m_Ints: []
m_Floats:
- _LOD: 0
m_Colors: []
m_BuildTextureStacks: []

View File

@ -1,31 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Grass LOD 1
m_Shader: {fileID: 4800000, guid: eebe2969663fc40449d6052c661a6c2e, type: 3}
m_Parent: {fileID: 2100000, guid: 1cd7df4d77cf1ae40ad59a15c2769be9, type: 2}
m_ModifiedSerializedProperties: 0
m_ValidKeywords:
- _LOD_LOD_1
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs: []
m_Ints: []
m_Floats:
- _LOD: 1
m_Colors: []
m_BuildTextureStacks: []

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 91fb1ec3cf2133a4b931798a8822d055
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,31 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Grass LOD 2
m_Shader: {fileID: 4800000, guid: eebe2969663fc40449d6052c661a6c2e, type: 3}
m_Parent: {fileID: 2100000, guid: 1cd7df4d77cf1ae40ad59a15c2769be9, type: 2}
m_ModifiedSerializedProperties: 0
m_ValidKeywords:
- _LOD_LOD_2
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs: []
m_Ints: []
m_Floats:
- _LOD: 2
m_Colors: []
m_BuildTextureStacks: []

View File

@ -1,31 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Grass LOD 3
m_Shader: {fileID: 4800000, guid: eebe2969663fc40449d6052c661a6c2e, type: 3}
m_Parent: {fileID: 2100000, guid: 1cd7df4d77cf1ae40ad59a15c2769be9, type: 2}
m_ModifiedSerializedProperties: 0
m_ValidKeywords:
- _LOD_LOD_3
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs: []
m_Ints: []
m_Floats:
- _LOD: 3
m_Colors: []
m_BuildTextureStacks: []

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: b747ca32ff9b8ee408e794d093cbdd54
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,37 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Grass
m_Shader: {fileID: 4800000, guid: eebe2969663fc40449d6052c661a6c2e, type: 3}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords:
- _LOD_LOD_0
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs: []
m_Ints: []
m_Floats:
- _BendStrength: 1
- _BladeWidth: 0.03
- _LOD: 0
m_Colors:
- _Color0: {r: 0.5882353, g: 1, b: 0.5882353, a: 1}
- _Color1: {r: 1, g: 0.8209069, b: 0.495283, a: 1}
- _Color2: {r: 1, g: 0.49831352, b: 0.21226418, a: 1}
- _Color3: {r: 0.7075472, g: 0.15018693, b: 0.15018693, a: 1}
m_BuildTextureStacks: []

View File

@ -0,0 +1,72 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: GrassLOD0
m_Shader: {fileID: 4800000, guid: eebe2969663fc40449d6052c661a6c2e, type: 3}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords:
- _LOD_LOD_0
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BladeWidthTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _FrabVarianz:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _HeightMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MaskMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _WindTextur:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _AOStrength: 1
- _BendStrength: 1
- _BladeBow: 0.3
- _BladeWidth: 0.0325
- _ExtraSawnOnCutGras: 6
- _FieldSize: 5
- _LOD: 0
- _Metallic: 0
- _NumSegments_LOD_0: 11
- _NumSegments_LOD_1: 5
- _NumSegments_LOD_2: 1
- _Roughness: 0.3
- _SSSStrength: 1
- _SegmentsMinusOne: 11
- _Translucency: 1
m_Colors:
- _BoxSize: {r: 0, g: 0, b: 0, a: 0}
- _BoxWorldCenter: {r: 0, g: 0, b: 0, a: 0}
- _Braun: {r: 0.33999997, g: 0.85, b: 0.92, a: 1}
- _Farbe: {r: 0.33999997, g: 0.85, b: 0.92, a: 1}
- _InnerColor: {r: 1, g: 1, b: 1, a: 1}
- _SpecularColor: {r: 1, g: 1, b: 1, a: 1}
- _WindOffset: {r: 0, g: 0, b: 0, a: 0}
m_BuildTextureStacks: []

View File

@ -0,0 +1,72 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: GrassLOD1
m_Shader: {fileID: 4800000, guid: eebe2969663fc40449d6052c661a6c2e, type: 3}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords:
- _LOD_LOD_1
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BladeWidthTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _FrabVarianz:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _HeightMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MaskMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _WindTextur:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _AOStrength: 1
- _BendStrength: 1
- _BladeBow: 0.3
- _BladeWidth: 0.0276
- _ExtraSawnOnCutGras: 6
- _FieldSize: 5
- _LOD: 1
- _Metallic: 0
- _NumSegments_LOD_0: 11
- _NumSegments_LOD_1: 3
- _NumSegments_LOD_2: 1
- _Roughness: 0.3
- _SSSStrength: 1
- _SegmentsMinusOne: 11
- _Translucency: 1
m_Colors:
- _BoxSize: {r: 0, g: 0, b: 0, a: 0}
- _BoxWorldCenter: {r: 0, g: 0, b: 0, a: 0}
- _Braun: {r: 0.33999997, g: 0.85, b: 0.92, a: 1}
- _Farbe: {r: 0.33999997, g: 0.85, b: 0.92, a: 1}
- _InnerColor: {r: 1, g: 1, b: 1, a: 1}
- _SpecularColor: {r: 1, g: 1, b: 1, a: 1}
- _WindOffset: {r: 0, g: 0, b: 0, a: 0}
m_BuildTextureStacks: []

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: ad7fd76c0f67ff04e8f51f21faf1739d
guid: 826d52a86cbdbd148ac471a227237ffe
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000

View File

@ -0,0 +1,72 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: GrassLOD2
m_Shader: {fileID: 4800000, guid: eebe2969663fc40449d6052c661a6c2e, type: 3}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords:
- _LOD_LOD_2
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BladeWidthTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _FrabVarianz:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _HeightMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MaskMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _WindTextur:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _AOStrength: 1
- _BendStrength: 1
- _BladeBow: 0.3
- _BladeWidth: 0.047
- _ExtraSawnOnCutGras: 6
- _FieldSize: 5
- _LOD: 2
- _Metallic: 0
- _NumSegments_LOD_0: 11
- _NumSegments_LOD_1: 5
- _NumSegments_LOD_2: 1
- _Roughness: 0.3
- _SSSStrength: 1
- _SegmentsMinusOne: 11
- _Translucency: 1
m_Colors:
- _BoxSize: {r: 0, g: 0, b: 0, a: 0}
- _BoxWorldCenter: {r: 0, g: 0, b: 0, a: 0}
- _Braun: {r: 0.33999997, g: 0.85, b: 0.92, a: 1}
- _Farbe: {r: 0.33999997, g: 0.85, b: 0.92, a: 1}
- _InnerColor: {r: 1, g: 1, b: 1, a: 1}
- _SpecularColor: {r: 1, g: 1, b: 1, a: 1}
- _WindOffset: {r: 0, g: 0, b: 0, a: 0}
m_BuildTextureStacks: []

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: d979d9bbf3760d948a452ba192cbf563
guid: f7693e3a8eaaa814480ba4085681ed02
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000

File diff suppressed because one or more lines are too long

View File

@ -3,5 +3,4 @@ using UnityEngine;
public struct BladeData {
public Vector3 rootPosition;
public Vector3 tipOffset;
public bool missingInNextLOD;
}

View File

@ -4,7 +4,6 @@ using UnityEngine;
[CustomEditor(typeof(GrassField))]
public class GrassFieldEditor : Editor {
// ReSharper disable Unity.PerformanceAnalysis
public override void OnInspectorGUI() {
base.OnInspectorGUI();
@ -15,9 +14,5 @@ public class GrassFieldEditor : Editor {
EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
}
}
if (GUILayout.Button("Update LODs")) {
((GrassField) target).UpdateLODsAndMaterials();
}
}
}

View File

@ -1,9 +0,0 @@
using UnityEngine;
using UnityEditor;
[InitializeOnLoad]
public class Startup {
static Startup() {
Shader.EnableKeyword("UNITY_EDITOR");
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: c6a6c758bb82b2040ad5ca8a7c20f0b1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,5 +1,7 @@
using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Serialization;
[RequireComponent(typeof(MeshRenderer)), RequireComponent(typeof(MeshFilter))]
public class GrassChunk : MonoBehaviour {
@ -84,33 +86,6 @@ public class GrassChunk : MonoBehaviour {
}
}
[SerializeField] private Material _materialLOD3;
public Material MaterialLOD3 {
get { return _materialLOD3; }
set {
_materialLOD3 = value;
UpdateLODAssets();
}
}
[SerializeField] private Mesh _meshLOD3;
public Mesh MeshLOD3 {
get { return _meshLOD3; }
set {
_meshLOD3 = value;
UpdateLODAssets();
}
}
[SerializeField] private bool _enableShadowsLOD3;
public bool EnableShadowsLOD3 {
get { return _enableShadowsLOD3; }
set {
_enableShadowsLOD3 = value;
UpdateLODAssets();
}
}
private int _lod;
public int LOD {
get { return _lod; }
@ -161,19 +136,11 @@ public class GrassChunk : MonoBehaviour {
MeshRenderer.shadowCastingMode = EnableShadowsLOD2 ? ShadowCastingMode.On : ShadowCastingMode.Off;
MeshFilter.sharedMesh = _meshLOD2;
break;
case 3:
MeshRenderer.sharedMaterial = _materialLOD3;
MeshRenderer.shadowCastingMode = EnableShadowsLOD3 ? ShadowCastingMode.On : ShadowCastingMode.Off;
MeshFilter.sharedMesh = _meshLOD3;
break;
default:
Debug.LogError("ehm?");
break;
}
}
private void OnDrawGizmosSelected() {
Gizmos.color = new Color(1f, 0.7f, 0f, 0.2f);
private void OnDrawGizmos() {
Gizmos.color = new Color(1f, 0.7f, 0f, 0.5f);
var meshRenderer = GetComponent<MeshRenderer>();
var bounds = meshRenderer.bounds;
Gizmos.DrawWireCube(bounds.center, bounds.size);

View File

@ -1,73 +1,51 @@
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 int numChunks = 5;
public int bladesPerMeter = 100;
public GrassChunk chunkPrefab;
public bool ignoreHeightForLOD = true;
[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;
public float distanceLOD1 = 7.5f;
public Material grassMaterialLOD1;
[Range(0f, 1f)] public float densityLOD1 = 0.5f;
public bool enableShadowsLOD1 = true;
[Header("LOD 2")]
public float distanceLOD2 = 15f;
public float distanceLOD2 = 7.5f + Mathf.Sqrt(2 * ((100f / 10f) * (100f / 10f))) + 5f;
public Material grassMaterialLOD2;
[Range(0f, 1f)] public float densityLOD2 = 0.25f;
[Range(0f, 1f)] public float densityLOD2 = 0.2f;
public bool enableShadowsLOD2 = false;
[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
// serialize it, because the chunks are generated offline and used during runtime
[SerializeField, HideInInspector] private GrassChunk[] _chunks;
// ReSharper disable Unity.PerformanceAnalysis
public void GenerateGrassField() {
var bladesLOD0 = GrassMeshGeneration.GenerateBladesRandom(
size, size * size * (int)(bladesPerSquareMeter * densityLOD0)
);
Debug.Log("Generating grass field... ");
var bladesLOD0 = GrassMeshGeneration.GenerateBladesRandom(size, size * size * (int)(bladesPerMeter * densityLOD0));
// var bladesLOD1 = GenerateBladesRandom(size, size * size * (int)(bladesPerMeter * densityLOD1));
// var bladesLOD2 = GenerateBladesRandom(size, size * size * (int)(bladesPerMeter * densityLOD2));
var bladesLOD1 = GrassMeshGeneration.GenerateBladesReduced(bladesLOD0, densityLOD1);
var bladesLOD2 = GrassMeshGeneration.GenerateBladesReduced(bladesLOD1, densityLOD2 / densityLOD1);
var bladesLOD3 = GrassMeshGeneration.GenerateBladesReduced(bladesLOD2, densityLOD3 / densityLOD2);
CreateChunks(bladesLOD0, bladesLOD1, bladesLOD2, bladesLOD3);
CreateChunks(bladesLOD0, bladesLOD1, bladesLOD2);
var camPosWorld = Camera.main ? Camera.main.transform.position : Vector3.zero;
UpdateLODs(camPosWorld);
UpdateMaterials(camPosWorld);
}
public void UpdateLODsAndMaterials() {
var camPosWorld = Camera.main ? Camera.main.transform.position : Vector3.zero;
UpdateMaterials(camPosWorld);
UpdateLODs(camPosWorld);
Debug.Log("Generating grass field done");
}
private void OnEnable() {
var chunkSize = ((float)size) / numChunks;
var minDistLOD2 = distanceLOD1 + chunkSize * SQRT_2;
var minDistLOD2 = distanceLOD1 + Mathf.Sqrt( 2 * Mathf.Pow( (float)size / numChunks, 2f ) );
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.");
@ -75,26 +53,17 @@ public class GrassField : MonoBehaviour {
}
}
private void OnDisable() {
UpdateLODsAndMaterials();
private void Update() {
UpdateLODs(false);
}
private void FixedUpdate() {
private void UpdateLODs(bool forceCompleteCheck = true) {
var cam = Camera.main;
if (!cam) return;
var camPosWorld = cam.transform.position;
#if UNITY_EDITOR
// update LOD distances and camera position
UpdateMaterials(camPosWorld);
#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
var camPos = cam.transform.position - transform.position; // cam position relative to grass field
if (ignoreHeightForLOD) {
camPos.y = 0;
}
@ -116,10 +85,7 @@ public class GrassField : MonoBehaviour {
var localCamPos = camPos - chunkPos; // cam position relative to chunk
var sqrCamDist = bounds.SqrDistance(localCamPos);
if (sqrCamDist > distanceLOD3 * distanceLOD3) {
_chunks[x + y * numChunks].LOD = 3;
}
else if (sqrCamDist > distanceLOD2 * distanceLOD2) {
if (sqrCamDist > distanceLOD2 * distanceLOD2) {
_chunks[x + y * numChunks].LOD = 2;
}
else if (sqrCamDist > distanceLOD1 * distanceLOD1) {
@ -142,10 +108,10 @@ public class GrassField : MonoBehaviour {
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;
var fromXWorld = camPos.x - distanceLOD2 - maxCamMove;
var toXWorld = camPos.x + distanceLOD2 + maxCamMove;
var fromZWorld = camPos.z - distanceLOD2 - maxCamMove;
var toZWorld = camPos.z + distanceLOD2 + maxCamMove;
range.fromX = Math.Max( 0, (int) (fromXWorld / chunkSize) );
range.toX = Math.Max( 0, (int) (toXWorld / chunkSize) );
@ -159,33 +125,7 @@ public class GrassField : MonoBehaviour {
return range;
}
private void UpdateMaterials(Vector3 camPosWorld) {
var chunkSize = ((float)size) / numChunks;
var chunkDiagonalLength = chunkSize * SQRT_2;
var transition0From = minDistanceLOD0;
var transition0To = distanceLOD1;
var transition1From = distanceLOD1 + chunkDiagonalLength;
var transition1To = distanceLOD2;
var transition2From = distanceLOD2 + chunkDiagonalLength;
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(
BladeData[] bladesLOD0, BladeData[] bladesLOD1, BladeData[] bladesLOD2, BladeData[] bladesLOD3
) {
private void CreateChunks(BladeData[] bladesLOD0, BladeData[] bladesLOD1, BladeData[] bladesLOD2) {
GameObject chunksContainer;
// If a child called "Chunks" exists, destroy it -> children are also destroyed
var chunksTransform = gameObject.transform.Find("Chunks");
@ -211,7 +151,6 @@ public class GrassField : MonoBehaviour {
var meshLOD0 = GenerateChunkMesh(chunkPos, chunkBounds, bladesLOD0, "grass_chunk_" + x + "_" + y + "_lod0");
var meshLOD1 = GenerateChunkMesh(chunkPos, chunkBounds, bladesLOD1, "grass_chunk_" + x + "_" + y + "_lod1");
var meshLOD2 = GenerateChunkMesh(chunkPos, chunkBounds, bladesLOD2, "grass_chunk_" + x + "_" + y + "_lod2");
var meshLOD3 = GenerateChunkMesh(chunkPos, chunkBounds, bladesLOD3, "grass_chunk_" + x + "_" + y + "_lod3");
var chunk = Instantiate(chunkPrefab, chunksContainer.transform);
chunk.name = "Chunk [" + x + ", " + y +"]";
@ -220,33 +159,29 @@ public class GrassField : MonoBehaviour {
chunk.MaterialLOD0 = grassMaterialLOD0;
chunk.MaterialLOD1 = grassMaterialLOD1;
chunk.MaterialLOD2 = grassMaterialLOD2;
chunk.MaterialLOD3 = grassMaterialLOD3;
chunk.MeshLOD0 = meshLOD0;
chunk.MeshLOD1 = meshLOD1;
chunk.MeshLOD2 = meshLOD2;
chunk.MeshLOD3 = meshLOD3;
chunk.EnableShadowsLOD0 = enableShadowsLOD0;
chunk.EnableShadowsLOD1 = enableShadowsLOD1;
chunk.EnableShadowsLOD2 = enableShadowsLOD2;
chunk.EnableShadowsLOD3 = enableShadowsLOD3;
_chunks[x + y * numChunks] = chunk;
}
UpdateLODs();
}
private Mesh GenerateChunkMesh(Vector3 chunkPos, Bounds chunkBounds, BladeData[] blades, string meshName) {
List<Vector3> rootPositions = new(); // vertices
List<Vector4> tipOffsets_missingInNextLOD = new(); // uv0
List<Vector3> tipOffsets = new(); // uv0
List<int> indices = new();
foreach (var blade in blades) {
// position relative to chunk
var localRootPos = blade.rootPosition - chunkPos;
if (chunkBounds.Contains(localRootPos)) {
rootPositions.Add(localRootPos);
tipOffsets_missingInNextLOD.Add(new Vector4(
blade.tipOffset.x, blade.tipOffset.y, blade.tipOffset.z,
blade.missingInNextLOD ? 1f : 0f)
);
tipOffsets.Add(blade.tipOffset);
indices.Add(rootPositions.Count - 1);
}
}
@ -259,7 +194,7 @@ public class GrassField : MonoBehaviour {
Debug.LogWarning(meshName + " has more than 65536 grass blades. Mesh.indexFormat set to UInt32.");
}
mesh.SetVertices(rootPositions);
mesh.SetUVs(0, tipOffsets_missingInNextLOD); // Vector4 UVs
mesh.SetUVs(0, tipOffsets); // Vector3 UVs
// AABB gets calculated automatically (considers all root positions)
mesh.SetIndices(indices, MeshTopology.Points, 0);
// update the AABB to match the actual grass (not just the root positions)
@ -280,7 +215,6 @@ public class GrassField : MonoBehaviour {
var chunkSize = ((float)size) / numChunks;
var tPos = transform.position;
// grid
for (int i = 0; i < numChunks + 1; i++) {
Gizmos.color = new Color(0f, 0.3f, 1f, 1f);
Gizmos.DrawLine(
@ -303,17 +237,12 @@ public class GrassField : MonoBehaviour {
Gizmos.matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(1f, 0.001f, 1f));
}
Gizmos.color = new Color(0f, 0.3f, 1f, 1f);
Gizmos.color = new Color(1.0f, 0.0f, 1.0f, 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;
var minDistLOD2 = distanceLOD2 + chunkSize * SQRT_2;
Gizmos.DrawWireSphere(center, minDistLOD0);
Gizmos.DrawWireSphere(center, minDistLOD1);
var minDistLOD2 = distanceLOD1 + Mathf.Sqrt( 2 * Mathf.Pow( (float)size / numChunks, 2f ) );
Gizmos.DrawWireSphere(center, minDistLOD2);
Gizmos.matrix = oldMatrix;

View File

@ -15,16 +15,12 @@ public class GrassMeshGeneration {
return blades;
}
// modifies "from" (marks blades as "missingInNextLOD")
// r = 0.5 means that the generated blades will have 50% of the blades of the input blades
static public BladeData[] GenerateBladesReduced(BladeData[] from, float r) {
List<BladeData> blades = new();
for (int i = 0; i < from.Length; i++) {
if (Random.Range(0f, 1f) > r) {
from[i].missingInNextLOD = true;
continue;
}
if (Random.Range(0f, 1f) > r) continue;
BladeData blade = from[i];
blades.Add(blade);

View File

@ -1,13 +1,8 @@
Shader "Grass/GrassBlade" {
Properties {
[KeywordEnum(LOD 0, LOD 1, LOD 2, LOD 3)] _LOD("Level of Detail", int) = 0
_BladeWidth("Blade Width", Range(0.003, .1)) = 0.01
[KeywordEnum(LOD 0, LOD 1, LOD 2)] _LOD("Level of Detail", int) = 0
_BladeWidth("Breite", Range(0.003, .1)) = 0.01
_BendStrength("Bend Strength", Range(0, 1)) = 1
_Color0("Color LOD 0", Color) = (0.3, 1.0, 0.3, 1.0)
_Color1("Color LOD 1", Color) = (1.0, 0.7, 0.3, 1.0)
_Color2("Color LOD 2", Color) = (1.0, 0.3, 0.1, 1.0)
_Color3("Color LOD 3", Color) = (0.8, 0.0, 0.0, 1.0)
}
SubShader {
LOD 100
@ -23,8 +18,7 @@
#pragma multi_compile _ SHADOWS_SCREEN
// shader_feature is very similar to multi_compile. Use multi_compile for keywords that are set from code.
#pragma shader_feature _LOD_LOD_0 _LOD_LOD_1 _LOD_LOD_2 _LOD_LOD_3
#pragma shader_feature UNITY_EDITOR
#pragma shader_feature _LOD_LOD_0 _LOD_LOD_1 _LOD_LOD_2
#include "UnityCG.cginc"
#include "UnityPBSLighting.cginc"
@ -50,8 +44,7 @@
#pragma fragment frag
#pragma geometry geom
#pragma shader_feature _LOD_LOD_0 _LOD_LOD_1 _LOD_LOD_2 _LOD_LOD_3
#pragma shader_feature UNITY_EDITOR
#pragma shader_feature _LOD_LOD_0 _LOD_LOD_1 _LOD_LOD_2
#include "UnityCG.cginc"

View File

@ -1,21 +1,13 @@
// Material Properties
float _BladeWidth, _BendStrength;
float4 _Color0, _Color1, _Color2, _Color3;
float2 _TransitionRange;
#if UNITY_EDITOR
float3 _WorldSpaceCameraPosEditor;
#endif
int _NumSegments_LOD_0, _NumSegments_LOD_1, _NumSegments_LOD_2;
struct MeshData {
float4 vertex : POSITION;
float4 tipOffset_missingInNextLOD : TEXCOORD0;
float3 tipOffset : TEXCOORD0;
};
struct v2g {
float4 vertex : SV_POSITION;
float3 tipOffset : TEXCOORD0;
float missingInNextLOD : TEXCOORD1;
};
struct g2f {
@ -23,139 +15,117 @@ struct g2f {
#ifdef IS_IN_BASE_PASS
float2 uv : TEXCOORD0; // bottom left 0.0 top right 1.1 (values converge - 0 and 1 meet at the tip)
SHADOW_COORDS(1) // put shadows data into TEXCOORD1
float transitionInterpolator : TEXCOORD2;
#endif
};
v2g vert (MeshData v) {
v2g o;
o.vertex = v.vertex;
o.tipOffset = v.tipOffset_missingInNextLOD.xyz;
o.missingInNextLOD = v.tipOffset_missingInNextLOD.w;
o.tipOffset = v.tipOffset;
return o;
}
float getCameraDistance(float3 pos) {
#if UNITY_EDITOR
return distance(_WorldSpaceCameraPosEditor, pos);
#else
return distance(_WorldSpaceCameraPos, pos);
#endif
}
const int ns = 11;
#if _LOD_LOD_0
[MaxVertexCount(4 + ( 11 - 1 ) * 2)]
#elif _LOD_LOD_1
[MaxVertexCount(4 + ( 5 - 1 ) * 2)]
#elif _LOD_LOD_2
[MaxVertexCount(4 + ( 2 - 1 ) * 2)]
#elif _LOD_LOD_3
[MaxVertexCount(4)]
#endif
void geom(point v2g IN[1], inout TriangleStream<g2f> triStream) {
const float3 basePos = IN[0].vertex.xyz;
const float3 tipOffset = IN[0].tipOffset;
const float3 tipPosObjectSpace = basePos + tipOffset;
const float3 tipPosBladeSpace = IN[0].tipOffset;
const float3 tipPosObjectSpace = basePos + tipPosBladeSpace;
// const float4 basePosClipSpace = UnityObjectToClipPos(float4(basePos, 1));
// const float4 tipPosClipSpace = UnityObjectToClipPos(float4(tipPosObjectSpace, 1));
// if (!isInFrustum(basePosClipSpace, 0.1) && !isInFrustum(tipPosClipSpace, 0.1)) return;
const float3 basePosWS = mul(unity_ObjectToWorld, float4(basePos.xyz, 1.0)).xyz;
const float cameraDistance = distance(_WorldSpaceCameraPos, basePosWS);
const float cameraDistance = getCameraDistance(basePosWS);
float interpolator = invLerp(_TransitionRange.x, _TransitionRange.y, cameraDistance);
// randomly cull grass blades
// float randoVal = N21(basePos);
// #if _LOD_LOD_0
// if (randoVal * 10.0 > max(1, 2 / (cameraDistance / 20))) {
// return;
// }
// #elif _LOD_LOD_1
// if (randoVal * 10.0 > max(1, 5 / (cameraDistance / 20))) {
// return;
// }
// #endif
float halfWidth = _BladeWidth/2;
// randomly shrink blades that will be removed in the next LOD
if (IN[0].missingInNextLOD > 0.0) {
float widthMultiplier = remap(N21(basePos.xz) * 0.75, 1, 1, 0, interpolator);
widthMultiplier = max(0, widthMultiplier);
halfWidth *= widthMultiplier;
}
// randomly shrink shadow blades to create a smooth transition between shadowed and non-shadowed area
#ifdef IS_IN_SHADOW_PASS
#if _LOD_LOD_1
float widthMultiplier = remap(N21(basePos.xz) * 0.5, 1, 1, 0, interpolator);
widthMultiplier = max(0, widthMultiplier);
halfWidth *= widthMultiplier;
#endif
#endif
g2f o;
float3 previousSegmentCenter = basePos - float3(0,-1,0); // point beneath the basePos
// int numSegments = max(1, (int)(11) - ((cameraDistance) / 8.0) + N21(basePos));
#if _LOD_LOD_0
int numSegments = lerp(11.99, 5.0, interpolator); // 11 - 5
int numSegments = 11;
#elif _LOD_LOD_1
int numSegments = lerp(5.99, 2.0, interpolator); // 5 - 2
int numSegments = 5;
#elif _LOD_LOD_2
int numSegments = 2;
#elif _LOD_LOD_3
int numSegments = 1;
#endif
for (int i = 0; i < numSegments; i++) {
const float lowerThickness = 0.8;
// LOD 3 has a different algorithm
#if _LOD_LOD_3
#if _LOD_LOD_2
{
const float segmentWidth = halfWidth * pow(sin(lowerThickness * 3.141), .8);
const float3 widthOffset = getWidthOffset(segmentWidth, tipOffset.xz) * max(1, cameraDistance / 10);
const float3 widthOffset = getWidthOffset(segmentWidth, tipPosBladeSpace.xz) * max(1, cameraDistance / 8);
float3 segmentCenterBase = basePos;
float3 segmentCenterTip = basePos + tipOffset;
const float3 vertLeftBase = segmentCenterBase + widthOffset * 2.5;
const float3 vertRightBase = segmentCenterBase - widthOffset * 2.5;
float3 segmentCenterTip = basePos + tipPosBladeSpace;
const float3 vertLeftBase = segmentCenterBase + widthOffset;
const float3 vertRightBase = segmentCenterBase - widthOffset;
const float3 vertLeftTip = segmentCenterTip + widthOffset * 0.4;
const float3 vertRightTip = segmentCenterTip - widthOffset * 0.4;
o.pos = UnityObjectToClipPos(vertLeftBase);
#ifdef IS_IN_BASE_PASS
#ifdef IS_IN_BASE_PASS
o.uv = float2(0, 0);
TRANSFER_SHADOW(o)
o.transitionInterpolator = interpolator;
#endif
triStream.Append(o);
o.pos = UnityObjectToClipPos(vertRightBase);
#ifdef IS_IN_BASE_PASS
#ifdef IS_IN_BASE_PASS
o.uv = float2(1, 0);
TRANSFER_SHADOW(o)
o.transitionInterpolator = interpolator;
#endif
triStream.Append(o);
o.pos = UnityObjectToClipPos(vertLeftTip);
#ifdef IS_IN_BASE_PASS
#ifdef IS_IN_BASE_PASS
o.uv = float2(0, 1);
TRANSFER_SHADOW(o)
o.transitionInterpolator = interpolator;
#endif
triStream.Append(o);
o.pos = UnityObjectToClipPos(vertRightTip);
#ifdef IS_IN_BASE_PASS
#ifdef IS_IN_BASE_PASS
o.uv = float2(1, 1);
TRANSFER_SHADOW(o)
o.transitionInterpolator = interpolator;
#endif
triStream.Append(o);
}
continue;
#endif
// in "blade space"
const float segmentWidth = halfWidth * pow(sin(lowerThickness * 3.141 * (numSegments - i) / numSegments), .8);
const float segmentHeightNormalized = i / (float)numSegments;
// float nextSegmentHeightNormalized = (i+1) / (float)numSegments;
// generate new verts
const float3 widthOffset = getWidthOffset(segmentWidth, tipOffset.xz) * max(1, cameraDistance / 10);
float3 segmentCenter = basePos + bendParabula(tipOffset, segmentHeightNormalized, _BendStrength);
const float3 widthOffset = getWidthOffset(segmentWidth, tipPosBladeSpace.xz) * max(1, cameraDistance / 8);
float3 segmentCenter = basePos + bendParabula(tipPosBladeSpace, segmentHeightNormalized, _BendStrength);
const float3 segmentCenterSnapshot = segmentCenter;
const float3 vertLeft = segmentCenter + widthOffset;
@ -169,11 +139,7 @@ void geom(point v2g IN[1], inout TriangleStream<g2f> triStream) {
const float3 normal = normalize(cross(surfTangent1, surfTangent2)); // TODO remove normalize
float3 normalWS = UnityObjectToWorldNormal(normal);
#if UNITY_EDITOR
const float3 camVec = normalize(segmentCenter - _WorldSpaceCameraPosEditor.xyz);
#else
const float3 camVec = normalize(segmentCenter - _WorldSpaceCameraPos.xyz);
#endif
// at which side of the blade are we looking?
const bool lookingFromAbove = dot(camVec, normalWS) > 0;
@ -185,7 +151,6 @@ void geom(point v2g IN[1], inout TriangleStream<g2f> triStream) {
#ifdef IS_IN_BASE_PASS
o.uv = float2(0, segmentHeightNormalized);
TRANSFER_SHADOW(o)
o.transitionInterpolator = interpolator;
#endif
triStream.Append(o);
@ -193,52 +158,44 @@ void geom(point v2g IN[1], inout TriangleStream<g2f> triStream) {
#ifdef IS_IN_BASE_PASS
o.uv = float2(1, segmentHeightNormalized);
TRANSFER_SHADOW(o)
o.transitionInterpolator = interpolator;
#endif
triStream.Append(o);
previousSegmentCenter = segmentCenterSnapshot;
}
o.pos = UnityObjectToClipPos(basePos + tipOffset * float3(_BendStrength,1,_BendStrength));
o.pos = UnityObjectToClipPos(basePos + tipPosBladeSpace * float3(_BendStrength,1,_BendStrength));
#ifdef IS_IN_BASE_PASS
o.uv = float2(.5, 1);
TRANSFER_SHADOW(o)
o.transitionInterpolator = interpolator;
#endif
triStream.Append(o);
}
float4 frag(g2f i) : SV_Target{
fixed4 frag(g2f i) : SV_Target{
#ifdef IS_IN_SHADOW_PASS
return 0;
#else
const float shadow = SHADOW_ATTENUATION(i);
float atten = 1.0;
float light = i.uv.y;
const fixed shadow = SHADOW_ATTENUATION(i);
float3 light = float3(i.uv.yyy);
light *= max(.35, shadow);
#if _LOD_LOD_1
light = pow(light, 1 + 0.4 * i.transitionInterpolator);
#elif _LOD_LOD_2
light = pow(light, 1.4); // fake shadow
#elif _LOD_LOD_3
light = pow(light, 1.4) * 0.7; // fake shadow
#if _LOD_LOD_2
light *= 0.75;
#endif
float3 albedo;
#if _LOD_LOD_0
albedo = lerp(_Color0.xyz, _Color1.xyz, i.transitionInterpolator);
float3 albedo = float3(0.3,1.0,0.3);
#elif _LOD_LOD_1
albedo = lerp(_Color1.xyz, _Color2.xyz, i.transitionInterpolator);
float3 albedo = float3(1.0,0.7,0.3);
albedo = float3(0.3,1.0,0.3);
#elif _LOD_LOD_2
albedo = lerp(_Color2.xyz, _Color3.xyz, i.transitionInterpolator);
#elif _LOD_LOD_3
albedo = _Color3.xyz;
float3 albedo = float3(1.0,0.3,0.1);
albedo = float3(0.3,1.0,0.3);
#endif
albedo = _Color0.xyz;
return float4(light * albedo, 1);
#endif
}

View File

@ -1,8 +1,3 @@
float invLerp(float a, float b, float v) {
return clamp( (v-a) / (b-a) , 0, 1);
}
float remap(float iMin, float iMax, float oMin, float oMax, float v) {
const float t = invLerp(iMin, iMax, v);
return lerp(oMin, oMax, t);
}

View File

@ -301,11 +301,7 @@ PlayerSettings:
- m_BuildTarget: WebGL
m_StaticBatching: 0
m_DynamicBatching: 0
m_BuildTargetShaderSettings:
- m_BuildTarget: Standalone
m_ChunkSizeInMB: 16
m_MaxChunks: 0
m_OverrideDefaultSettings: 0
m_BuildTargetShaderSettings: []
m_BuildTargetGraphicsJobs:
- m_BuildTarget: MacStandaloneSupport
m_GraphicsJobs: 0

View File

@ -1,15 +1,3 @@
# Grass Rendering Prototype
Optimized rendering of huge grass fields.
## To-Do
- [x] LODs: reduced detail (number of vertices)
- [x] LODs: reduced number of blades
- [x] Smooth LOD transition: number of blades
- [x] Smooth LOD transition: detail
- [x] Thicker blades with increased distance
- [ ] Cut map
- [ ] Cut map LOD versions
- [ ] Lighting
- [ ] Grass field generation: spawn in patches