Add minimal shader, generate more grass

This commit is contained in:
Timo Eberl 2024-09-27 01:45:12 +02:00
parent 2a75b8aa27
commit 268a22ad63
Signed by: Timo
SSH Key Fingerprint: SHA256:swVjhbVzKCLQZNtwPqMEmtOUG3FTydzVrpIKpUZYTQw
19 changed files with 22848 additions and 2858 deletions

View File

@ -0,0 +1,67 @@
%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: []
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.05
- _ExtraSawnOnCutGras: 6
- _FieldSize: 5
- _Metallic: 0
- _Roughness: 0.3
- _SSSStrength: 1
- _SegmentsMinusOne: 10
- _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,8 @@
fileFormatVersion: 2
guid: 1cd7df4d77cf1ae40ad59a15c2769be9
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because one or more lines are too long

View File

@ -10,7 +10,9 @@ public class GrassFieldEditor : Editor {
if (GUILayout.Button("Generate")) {
((GrassField) target).GenerateGrassField();
// Scene may have been modified
EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
if (!Application.isPlaying) {
EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
}
}
}
}

View File

@ -1,17 +1,18 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;
public class GrassField : MonoBehaviour {
public int size = 100;
public int numChunks = 5;
public Material grassMaterial;
public GrassChunk chunkPrefab;
private struct BladeData {
public Vector3 rootPosition;
public Vector3 tipPosition;
public Vector3 tipOffset;
}
// ReSharper disable Unity.PerformanceAnalysis
public void GenerateGrassField() {
Debug.Log("Generating grass field... ");
@ -22,13 +23,13 @@ public class GrassField : MonoBehaviour {
}
private BladeData[] GenerateBlades() {
var blades = new BladeData[size * size];
var blades = new BladeData[size * size * 100];
for (int x = 0; x < size; x++)
for (int y = 0; y < size; y++) {
var i = x + y * size;
blades[i].rootPosition = new Vector3(x + 0.5f, 0.0f, y + 0.5f);
blades[i].tipPosition = blades[i].rootPosition + new Vector3(0.0f, 1.0f, 0.0f);
for (int x = 0; x < size * 10; x++)
for (int y = 0; y < size * 10; y++) {
var i = x + y * size * 10;
blades[i].rootPosition = new Vector3(x/10.0f, 0.0f, y/10.0f);
blades[i].tipOffset = new Vector3(0.01f, 1.0f, 0.01f);
}
return blades;
@ -57,27 +58,26 @@ public class GrassField : MonoBehaviour {
);
List<Vector3> rootPositions = new();
List<Vector3> tipPositions = new();
List<Vector3> tipOffsets = new();
List<int> indices = new();
foreach (var blade in blades) {
var localRootPos = blade.rootPosition - chunkPos;
if (bounds.Contains(localRootPos)) {
rootPositions.Add(localRootPos);
var localTipPos = blade.tipPosition - chunkPos;
tipPositions.Add(localTipPos);
tipOffsets.Add(blade.tipOffset);
indices.Add(rootPositions.Count - 1);
}
}
var mesh = new Mesh();
// 16 bit (default) supports 65536 vertices, 32 bit supports 4 billion
// mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
mesh.name = "grass_chunk[" + x + "," + y + "]";
mesh.SetVertices(rootPositions);
// do not calculate bounding box - will be set manually
mesh.SetIndices(indices, MeshTopology.Points, 0, false);
// Vector3 UVs are possible
mesh.SetUVs(0, tipPositions);
mesh.SetUVs(0, tipOffsets);
var chunk = Instantiate(chunkPrefab, chunks.transform);
chunk.name = "Chunk [" + x + ", " + y +"]";
@ -88,6 +88,7 @@ public class GrassField : MonoBehaviour {
var meshRenderer = chunk.GetComponent<MeshRenderer>();
meshRenderer.localBounds = bounds;
meshRenderer.sharedMaterial = grassMaterial;
}
}
}

8
Assets/Shaders.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 90499031c9fd4764095827ebbf1cf418
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,59 @@
Shader "Grass/GrassBlade" {
Properties {
_BladeWidth("Breite", Range(0.003, .1)) = 0.01
_SegmentsMinusOne("Segments Minus One", Range(1, 60)) = 20
_BendStrength("Bend Strength", Range(0, 1)) = 1
}
SubShader {
LOD 100
Cull Off
//Render Pass
Pass {
Tags { "LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma geometry geom
#pragma multi_compile _ SHADOWS_SCREEN
#include "UnityCG.cginc"
#include "UnityPBSLighting.cginc"
#include "AutoLight.cginc"
#include "./include/Random.hlsl"
#include "./include/Math.hlsl"
#include "./include/Culling.hlsl"
#include "./include/GrassHelpers.hlsl"
#define IS_IN_BASE_PASS
#include "./include/GrassBladePass.hlsl"
ENDCG
}
// Shadow Pass
Pass {
Tags { "LightMode" = "ShadowCaster" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma geometry geom
#include "UnityCG.cginc"
#include "./include/Random.hlsl"
#include "./include/Math.hlsl"
#include "./include/Culling.hlsl"
#include "./include/GrassHelpers.hlsl"
#define IS_IN_SHADOW_PASS
#include "./include/GrassBladePass.hlsl"
ENDCG
}
}
}

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: eebe2969663fc40449d6052c661a6c2e
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7c6dc0a6fc332df448a4476c05ef82dd
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,14 @@
bool isInFrustum(float4 clipSpacePosition, float screenExpansion) {
float nearClippingPlane = _ProjectionParams.y; // y near plane, z far plane
float3 weirdspace = clipSpacePosition.xyz;
weirdspace /= -clipSpacePosition.w;
weirdspace.x = weirdspace.x / 2.0f + 0.5f;
weirdspace.y = weirdspace.y / 2.0f + 0.5f;
weirdspace.z = -clipSpacePosition.w;
const bool notClippedByNearClippingPlane = weirdspace.z >= -nearClippingPlane ? 0 : 1;
const bool inFrustum =
(weirdspace.x > -screenExpansion) && (weirdspace.x < (1+screenExpansion))
&& notClippedByNearClippingPlane;
return inFrustum;
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 84659d9a0ae985d4e8cd00395dc2ea8b
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,124 @@
float _BladeWidth, _SegmentsMinusOne, _BendStrength;
struct MeshData {
float4 vertex : POSITION;
float3 tipOffset : TEXCOORD0;
};
struct v2g {
float4 vertex : SV_POSITION;
float3 tipOffset : TEXCOORD0;
};
struct g2f {
float4 pos : SV_POSITION;
#if !defined(IS_IN_SHADOW_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
#endif
};
v2g vert (MeshData v) {
v2g o;
o.vertex = v.vertex;
o.tipOffset = v.tipOffset;
return o;
}
#if defined(IS_IN_SHADOW_PASS)
[MaxVertexCount(24)]
#else
[MaxVertexCount(24)]
#endif
void geom(point v2g IN[1], inout TriangleStream<g2f> triStream) {
const float3 basePos = IN[0].vertex.xyz;
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);
float halfWidth = _BladeWidth/2;
g2f o;
float3 previousSegmentCenter = basePos - float3(0,-1,0); // point beneath the basePos
int segmentsMinusOne = max(1, (int)(_SegmentsMinusOne) - ((cameraDistance) / 8.0) + N21(basePos));
for (int i = 0; i < segmentsMinusOne; i++) {
// in "blade space"
const float lowerThickness = 0.8;
const float segmentWidth = halfWidth * pow(sin(lowerThickness * 3.141 * ((int)_SegmentsMinusOne - i) / (int)_SegmentsMinusOne), .8);
const float segmentHeightNormalized = i / _SegmentsMinusOne;
// float nextSegmentHeightNormalized = (i+1) / _SegmentsMinusOne;
// generate new verts
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;
const float3 vertRight = segmentCenter - widthOffset;
// const float3 nextSegmentCenter = basePos + bendParabula(tipPos, nextSegmentHeight01);
#if !defined(IS_IN_SHADOW_PASS)
// calculate normal
const float3 surfTangent1 = previousSegmentCenter - segmentCenter;
const float3 surfTangent2 = previousSegmentCenter - vertLeft;
const float3 normal = normalize(cross(surfTangent1, surfTangent2)); // TODO remove normalize
float3 normalWS = UnityObjectToWorldNormal(normal);
const float3 camVec = normalize(segmentCenter - _WorldSpaceCameraPos.xyz);
// at which side of the blade are we looking?
const bool lookingFromAbove = dot(camVec, normalWS) > 0;
if (lookingFromAbove) {
normalWS = -normalWS;
}
#endif
o.pos = UnityObjectToClipPos(vertLeft);
#if !defined(IS_IN_SHADOW_PASS)
o.uv = float2(0, segmentHeightNormalized);
TRANSFER_SHADOW(o)
#endif
triStream.Append(o);
o.pos = UnityObjectToClipPos(vertRight);
#if !defined(IS_IN_SHADOW_PASS)
o.uv = float2(1, segmentHeightNormalized);
TRANSFER_SHADOW(o)
#endif
triStream.Append(o);
previousSegmentCenter = segmentCenterSnapshot;
}
o.pos = UnityObjectToClipPos(basePos + tipPosBladeSpace * float3(_BendStrength,1,_BendStrength));
#if !defined(IS_IN_SHADOW_PASS)
o.uv = float2(.5, 1);
TRANSFER_SHADOW(o)
#endif
triStream.Append(o);
}
fixed4 frag(g2f i) : SV_Target{
#if defined(IS_IN_SHADOW_PASS)
return 0;
#else
float atten = 1.0;
const fixed shadow = SHADOW_ATTENUATION(i);
float3 lightFinal = float3(i.uv.yyy);
lightFinal *= max(.35, shadow);
fixed4 albedo = fixed4(0.3,1.0,0.3,1.0);
fixed3 color = fixed3(lightFinal * albedo);
return fixed4(color, 1);
#endif
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 2de784b5b53165946b9e346393407b85
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,17 @@
// tipPos2D = xz coordinates of tip
float3 getWidthOffset(float width, float2 tipPos2D) {
float2 dirBaseToTip = normalize(tipPos2D);
return float3(-dirBaseToTip.y * width, 0, dirBaseToTip.x * width);
}
float3 bendParabula(float3 tipPos, float posOnBlade, float bendStrength) {
float len = length(tipPos.xz); // Länge des flachen Grashalms
float b = tipPos.y;
posOnBlade = posOnBlade * len;
float y = (-pow(posOnBlade - len, 2) * b) / pow(len, 2) + b;
float2 xz = normalize(float2(tipPos.x, tipPos.z)) * posOnBlade * bendStrength;
return float3(xz.x, y, xz.y);
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 02464ba14b990ef42af7f595f0e65dd8
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,3 @@
float invLerp(float a, float b, float v) {
return clamp( (v-a) / (b-a) , 0, 1);
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e6a02633975791d4491ac937efacf7a8
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,6 @@
// hash function to get a random number of a 2d coordinate -> return value between 0 and 1
float N21(float2 p) {
p = frac(p * float2(123.34, 345.45));
p += dot(p, p + 34.345);
return frac(p.x * p.y);
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 449b724a594ffa148b2b2ad6f2ecc510
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: