Files
CartoonRPG/Assets/StixGames/GrassShader/GrassGeom.hlsl
Caleb Sandford deQuincey 02c4b5ab66 Adding in new packages
2025-06-05 22:59:18 +01:00

517 lines
15 KiB
HLSL

#ifndef GRASS_GEOM
#define GRASS_GEOM
inline FS_INPUT geomToFrag(GS_OUTPUT v)
{
FS_INPUT o = (FS_INPUT) 0;
UNITY_INITIALIZE_OUTPUT(FS_INPUT, o)
float3 worldPos = v.vertex.xyz;
//This is necessary for shadow calculation
v.vertex = mul(unity_WorldToObject, v.vertex);
#ifdef GRASS_CURVED_WORLD
V_CW_TransformPoint(v.vertex);
#endif
o.worldPos = worldPos;
#if !defined(SIMPLE_GRASS)
o.uv = v.uv;
o.texIndex = v.texIndex;
#ifdef GRASS_TEXTURE_ATLAS
o.textureAtlasIndex = v.textureAtlasIndex;
#endif
#endif
o.color = v.color;
o.floorColor = v.floorColor;
#ifndef GRASS_PASS_SHADOWCASTER
#if defined(GRASS_URP)
VertexPositionInputs vertexInput = GetVertexPositionInputs(v.vertex.xyz);
o.pos = vertexInput.positionCS;
#else
o.pos = UnityObjectToClipPos(v.vertex);
#endif
o.normal = v.normal;
// HDRP uses deferred rendering, so the following values aren't necessary
#if !defined(GRASS_HDRP)
#if defined(GRASS_HYBRID_NORMAL_LIGHTING)
o.specularNormal = v.specularNormal;
#endif
// Ambient light UV
o.ambientOrLightmapUV = 0;
#if UNITY_SHOULD_SAMPLE_SH
o.ambientOrLightmapUV.rgb = ShadeSHPerVertex (v.normal, o.ambientOrLightmapUV.rgb);
#endif
#if defined(GRASS_URP)
o.shadowCoord = GetShadowCoord(vertexInput);
o.fogCoord = ComputeFogFactor(vertexInput.positionCS.z);
#else
TRANSFER_SHADOW(o); // pass shadow coordinates to pixel shader
UNITY_TRANSFER_FOG(o, o.pos); // pass fog coordinates to pixel shader
#endif
#endif
#else // GRASS_PASS_SHADOWCASTER
#if defined(GRASS_URP)
VertexPositionInputs vertexInput = GetVertexPositionInputs(v.vertex.xyz);
o.pos = vertexInput.positionCS;
#elif defined(GRASS_HDRP)
o.pos = UnityObjectToClipPos(v.vertex);
#else
TRANSFER_SHADOW_CASTER(o)
#endif
#endif
return o;
}
inline void generateBladeOfGrass(inout TriangleStream<FS_INPUT> triStream, GS_OUTPUT pIn, int lod,
float3 rendererPos, float3 oPos, half3 up, half3 groundRight, half realHeight,
half width, half4 color, half4 floorColor,
half2 windDir, half3 interaction, half softness, half grassWeight)
{
//Get lightDir
#if defined(GRASS_HDRP)
// HDRP is deferred only
half3 lightDir = half3(0,0,0);
#elif defined(GRASS_URP)
half3 lightDir = GetMainLight().direction;
#else
#if defined(USING_DIRECTIONAL_LIGHT)
half3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
half3 lightDir = normalize(UnityWorldSpaceLightDir(oPos));
#endif
#endif
float3 lastPos = oPos - up * 0.01f;
half stepSize = 1.0f / lod;
// Movement direction
half3 interactionRight = half3(1, 0, 0);
// This is probably not forward, but I have to keep it consistent with the interaction system
half3 interactionForward = cross(interactionRight, up);
half3 windInteraction = windDir.x * interactionRight + windDir.y * interactionForward;
#if defined(GRASS_INTERACTION)
half3 interactionVector = normalize(interaction.x * interactionRight + interaction.z * up + interaction.y * interactionForward);
half interactionStrength = 1 - dot(interactionVector, up);
half3 interactionDir = normalize(windInteraction * (1-interactionStrength) + interactionVector);
#else
half3 interactionDir = normalize(windInteraction + up);
half interactionStrength = 0;
#endif
half adjustedSoftness = 1 - (1 - interactionStrength) * (1 - softness);
half3 grassDir = normalize(interactionDir * adjustedSoftness + up * (1 - adjustedSoftness));
half stepWeight = grassWeight * stepSize;
float3 pos = oPos;
for(half i = 0; i <= lod; i++)
{
half segment = i*stepSize;
half sqrSegment = segment*segment;
half uvHeight = segment;
// TODO: Custom lighting effects for additional lights in URP / Probably not possible unless it's done per-pixel
#if !defined(GRASS_HDRP) && !defined(GRASS_URP) && !defined(USING_DIRECTIONAL_LIGHT)
lightDir = normalize(UnityWorldSpaceLightDir(pos));
#endif
half3 currentUp = pos - lastPos;
half3 right = groundRight;
//Vertex definition
#if defined(SIMPLE_GRASS)
//Simple grass has no texture, so the mesh has to look like a blade of grass
pIn.vertex = float4((pos - width * right * (1 - sqrSegment)).xyz, 1);
#else
pIn.vertex = float4((pos - width * right).xyz, 1);
#endif
#if defined(GRASS_HYBRID_NORMAL_LIGHTING)
// Use the hybrid normal mode
pIn.specularNormal = getNormal(currentUp, right, lightDir);
#elif !defined(GRASS_SURFACE_NORMAL_LIGHTING)
// The regular lighting mode is used, there is no normal information in pIn yet
pIn.normal = getNormal(currentUp, right, lightDir);
#endif
#if !defined(SIMPLE_GRASS)
pIn.uv = half2(0.0f, uvHeight);
#endif
pIn.color = color;
pIn.floorColor = half4(floorColor.rgb, 1-segment);
triStream.Append(geomToFrag(pIn));
//Vertex definition
#if defined(SIMPLE_GRASS)
//Simple grass has no texture, so the mesh has to look like a blade of grass
pIn.vertex = float4((pos + width * right * (1 - sqrSegment)).xyz, 1);
#else
pIn.vertex = float4((pos + width * right).xyz, 1);
#endif
#if defined(GRASS_HYBRID_NORMAL_LIGHTING)
// Use the hybrid normal mode
pIn.specularNormal = getNormal(currentUp, right, lightDir);
#elif !defined(GRASS_SURFACE_NORMAL_LIGHTING)
// The regular lighting mode is used, there is no normal information in pIn yet
pIn.normal = getNormal(currentUp, right, lightDir);
#endif
#if !defined(SIMPLE_GRASS)
pIn.uv = half2(1.0f, uvHeight);
#endif
triStream.Append(geomToFrag(pIn));
lastPos = pos;
// =========== Grass position / direction code ===========
// Next step in grass growth
pos += grassDir * realHeight * stepSize;
// Calculate change in grass direction
grassDir = normalize(grassDir - stepWeight * up);
}
triStream.RestartStrip();
}
inline void pointGeometryShader(GS_INPUT p, float3 randCalcOffset, inout TriangleStream<FS_INPUT> triStream)
{
float3 rendererPos = p.cameraPos;
float3 cameraPos = _WorldSpaceCameraPos;
//Init pos, uv
float3 oPos = p.position.xyz;
half4 uv = half4(p.uv, 0, 0);
//This variable is used for calculating random values. If you have a better name for it, I'm all ears!
#ifdef GRASS_OBJECT_MODE
float3 randCalcPos = p.objectSpacePos;
#else
float3 randCalcPos = oPos;
#endif
randCalcPos += randCalcOffset;
//Calculate viewDir and groundRight vector
#ifdef GRASS_FOLLOW_SURFACE_NORMAL
half3 up = normalize(p.normal);
#else
half3 up = half3(0, 1, 0);
#endif
half3 viewDir = normalize(rendererPos - oPos);
half3 cameraForward = UNITY_MATRIX_V[2].xyz;
//Set grass orientation
#if defined(GRASS_RANDOM_DIR)
half3 orientationDir = half3(rand(randCalcPos.xz + float2(-58.88, 77.51)), 0, rand(randCalcPos.xz + float2(54.85, -12.3))) * 2 - half3(1, 0, 1);
#else
half3 orientationDir = viewDir;
#endif
//Grass variable declaration
half maxHeight = 0;
half minHeight = 0;
half width = 0;
half softness = 0;
half grassWeight = 0;
#ifndef GRASS_PASS_SHADOWCASTER
half4 mainColor = half4(0, 0, 0, 0);
half4 secColor = half4(0, 0, 0, 0);
#endif
#ifdef GRASS_TEXTURE_ATLAS
int textureAtlasIndex = 0;
#endif
#if !defined(UNIFORM_DENSITY)
#ifdef VERTEX_DENSITY
//Vertex density
half4 density = p.color;
#else
//Texture density
half4 density = tex2Dlod(_DensityTexture, uv);
#endif
#endif
#if defined(SIMPLE_GRASS)
float randVal = rand(randCalcPos.xz + float2(17.89, -23.60));
if(randVal < DENSITY00)
{
maxHeight = _MaxHeight00;
minHeight = _MinHeight00;
width = _Width00;
softness = _Softness00;
grassWeight = _Weight00;
#ifndef GRASS_PASS_SHADOWCASTER
mainColor = _Color00;
secColor = _SecColor00;
#endif
}
else
{
GS_OUTPUT pIn = (GS_OUTPUT) 0;
triStream.Append(geomToFrag(pIn));
return;
}
#else //If textured grass
//Grass Type
//Selects a random type of grass. If the probability is over 1, it will be scaled down.
#ifdef FOUR_GRASS_TYPES
float randVal = rand(randCalcPos.xz + float2(17.89, -23.60)) * max(DENSITY00 + DENSITY01 + DENSITY02 + DENSITY03, 1);
#elif defined(THREE_GRASS_TYPES)
float randVal = rand(randCalcPos.xz + float2(17.89, -23.60)) * max(DENSITY00 + DENSITY01 + DENSITY02, 1);
#elif defined(TWO_GRASS_TYPES)
float randVal = rand(randCalcPos.xz + float2(17.89, -23.60)) * max(DENSITY00 + DENSITY01, 1);
#else
float randVal = rand(randCalcPos.xz + float2(17.89, -23.60));
#endif
int texIndex = 0;
#ifdef GRASS_TEXTURE_ATLAS
float textureAtlasRandVal = rand(randCalcPos.xz + float2(-23.46, 12.46));
#endif
if(randVal < DENSITY00)
{
texIndex = 0;
maxHeight = _MaxHeight00;
minHeight = _MinHeight00;
width = _Width00;
softness = _Softness00;
grassWeight = _Weight00;
#ifndef GRASS_PASS_SHADOWCASTER
mainColor = _Color00;
secColor = _SecColor00;
#endif
#ifdef GRASS_TEXTURE_ATLAS
textureAtlasIndex = (int)(textureAtlasRandVal * _TextureAtlasWidth00 * _TextureAtlasHeight00);
#endif
}
#if !defined(ONE_GRASS_TYPE)
else if(randVal < (DENSITY00 + DENSITY01))
{
texIndex = 1;
maxHeight = _MaxHeight01;
minHeight = _MinHeight01;
width = _Width01;
softness = _Softness01;
grassWeight = _Weight01;
#ifndef GRASS_PASS_SHADOWCASTER
mainColor = _Color01;
secColor = _SecColor01;
#endif
#ifdef GRASS_TEXTURE_ATLAS
textureAtlasIndex = (int)(textureAtlasRandVal * _TextureAtlasWidth01 * _TextureAtlasHeight01);
#endif
}
#if !defined(TWO_GRASS_TYPES)
else if(randVal < (DENSITY00 + DENSITY01 + DENSITY02))
{
texIndex = 2;
maxHeight = _MaxHeight02;
minHeight = _MinHeight02;
width = _Width02;
softness = _Softness02;
grassWeight = _Weight02;
#ifndef GRASS_PASS_SHADOWCASTER
mainColor = _Color02;
secColor = _SecColor02;
#endif
#ifdef GRASS_TEXTURE_ATLAS
textureAtlasIndex = (int)(textureAtlasRandVal * _TextureAtlasWidth02 * _TextureAtlasHeight02);
#endif
}
#if !defined(THREE_GRASS_TYPES)
else if(randVal < (DENSITY00 + DENSITY01 + DENSITY02 + DENSITY03))
{
texIndex = 3;
maxHeight = _MaxHeight03;
minHeight = _MinHeight03;
width = _Width03;
softness = _Softness03;
grassWeight = _Weight03;
#ifndef GRASS_PASS_SHADOWCASTER
mainColor = _Color03;
secColor = _SecColor03;
#endif
#ifdef GRASS_TEXTURE_ATLAS
textureAtlasIndex = (int)(textureAtlasRandVal * _TextureAtlasWidth03 * _TextureAtlasHeight03);
#endif
}
#endif
#endif
#endif
else
{
//If no grass type was randomized, return a single vertex, so no blade of grass will be rendered.
GS_OUTPUT pIn = (GS_OUTPUT)0;
pIn.texIndex = -1;
triStream.Append(geomToFrag(pIn));
return;
}
#endif
//Calculate wind
#if defined(GRASS_CALC_GLOBAL_WIND)
half2 windDir = wind(randCalcPos, _WindRotation);
#else
half2 windDir = half2(0, 0);
#endif
//Add disorder offset
half randX = (rand(randCalcPos.xz + 10) * 2 - 1) * _Disorder;
half randZ = (rand(randCalcPos.xz - 10) * 2 - 1) * _Disorder;
//If grass is looked at from the top, it should still look like grass
#if defined(GRASS_TOP_VIEW_COMPENSATION)
half topViewCompensation = 1 + pow(max(0, dot(viewDir, up)), 20) * 0.8;
width *= topViewCompensation;
windDir += half2(randX, randZ) * topViewCompensation;
#else
windDir += half2(randX, randZ);
#endif
//Grass height from color map
half4 tex = tex2Dlod(_ColorMap, uv);
//Grass height from distance falloff
half dist = distance(oPos, cameraPos);
half grassHeightMod = tex.a * smoothstep(_GrassFadeEnd, _GrassFadeStart, dist);
//Tessellation smoothing by height
#if defined(GRASS_HEIGHT_SMOOTHING)
grassHeightMod *= p.smoothing;
#endif
//Tessellation smoothing by width
#if defined(GRASS_WIDTH_SMOOTHING)
width *= p.smoothing;
#endif
//Calculate real height
half realHeight = (rand(randCalcPos.xz) * (maxHeight - minHeight) + minHeight) * grassHeightMod;
//LOD
int lod = (int) max(smoothstep(_LODEnd, _LODStart, dist)*_LODMax, 1);
//Calculate grass interaction
half3 interaction = half3(0, 0, 1);
half4 burnFactor = half4(1, 1, 1, 1);
#if defined(GRASS_INTERACTION)
#if defined(GRASS_RENDERTEXTURE_INTERACTION)
#if defined(GRASS_HDRP)
float2 coords = (oPos.xz + _WorldSpaceCameraPos.xz - _GrassRenderTextureArea.xy) / _GrassRenderTextureArea.zw;
#else
float2 coords = (oPos.xz - _GrassRenderTextureArea.xy) / _GrassRenderTextureArea.zw;
#endif
half4 interactionTexture = tex2Dlod(_GrassRenderTextureInteraction, float4(coords, 0, 0));
interaction.xyz = normalize(interactionTexture.rgb * 2 - float3(1, 1, 1));
burnFactor = tex2Dlod(_GrassRenderTextureBurn, float4(coords, 0, 0));
#else
interaction = tex2Dlod(_Displacement, uv).rgb;
//Convert from texture to vector
interaction.xy = (interaction.xy * 2.0) - half2(1, 1);
#endif
#endif
//Width is split up by offset to right and to left, so we only need half
width *= 0.5;
realHeight *= burnFactor.a;
//width *= burnFactor;
//Abort generating grass, when certain values are too low
//Both could probably be replaced by realHeight < 0.01, but I don't want to break compatibility with very small scenes
if (dist > _GrassFadeEnd || burnFactor.a < _BurnCutoff)
{
GS_OUTPUT pIn = (GS_OUTPUT)0;
triStream.Append(geomToFrag(pIn));
return;
}
//Color
half4 color = half4(1,1,1,1);
#if !defined(GRASS_PASS_SHADOWCASTER)
color = tex;
color *= lerp(mainColor, secColor, rand(randCalcPos.xz + half2(10, -10)));
color *= half4(burnFactor.rgb, 1);
#endif
#if defined(GRASS_ALPHA_SMOOTHING) && !defined(SIMPLE_GRASS)
color.a = p.smoothing;
#else
color.a = 1.0f;
#endif
//Calculate the floor color
half4 floorColor = _GrassFloorColor * tex2Dlod(_GrassFloorColorTexture, uv);
floorColor.rgb *= burnFactor.rgb;
//Cacluate ground right direction
half3 groundRight = normalize(cross(up, orientationDir));
//Set all default values, that (can) stay the same for the whole blade of grass
GS_OUTPUT pIn;
#if !defined(SIMPLE_GRASS)
pIn.texIndex = texIndex;
#endif
#ifdef GRASS_TEXTURE_ATLAS
pIn.textureAtlasIndex = textureAtlasIndex;
#endif
//Set the surface normal for rendering
#if defined(GRASS_SURFACE_NORMAL_LIGHTING) || defined(GRASS_HYBRID_NORMAL_LIGHTING)
pIn.normal = normalize(p.normal);
#endif
//Generate the grass itself
generateBladeOfGrass(/* inout */ triStream, pIn,
lod, rendererPos, oPos, up, groundRight,
realHeight, width,
color, floorColor, windDir, interaction, softness, grassWeight);
#if defined(GRASS_RANDOM_DIR)
generateBladeOfGrass(/* inout */ triStream, pIn,
lod, rendererPos, oPos, up, -groundRight,
realHeight, width,
color, floorColor, windDir, interaction, softness, grassWeight);
#endif
}
#if defined(GRASS_RANDOM_DIR)
[maxvertexcount(2 * MAX_VERTEX_COUNT)]
#else
[maxvertexcount(MAX_VERTEX_COUNT)]
#endif
void geom(triangle GS_INPUT p[3], inout TriangleStream<FS_INPUT> triStream)
{
pointGeometryShader(p[0], float3(0, 0, 0), /* inout */ triStream);
//pointGeometryShader(p[1], float3(0.01, 0, 0), /* inout */ triStream);
//pointGeometryShader(p[2], float3(0, 0, 0.01), /* inout */ triStream);
}
#endif