I am working on a project in Unity where I use a compute shader to draw grass. I’m referencing https://www.youtube.com/watch?v=XC0-dsqK1iA as a guide. Below are the results— the first image shows what I made using Shader Graph, and the second image is the result of writing shader code based on that graph. The third image is the corresponding shader graph.
And the following codes are the shader code I created by referencing the shader graph.
TestGrass.Shader
Shader "Custom/TestGrass"
{
Properties
{
[Toggle(BLEND)] _BlendFloor("Blend with floor", Float) = 0
_BlendMult("Blend Multiply", Range(0, 5)) = 1
_BlendOff("Blend Offset", Range(0, 1)) = 1
[HDR] _AmbientAdjustmentColor("Ambient Adjustment Color", Color) = (0.5, 0.5, 0.5, 1)
[Header(Main Light)]
_ShadowStrength("Shadow Strength", Range(0, 1)) = 0.5
[HDR] _ShadowColor("Shadow Color", Color) = (0.5, 0.5, 0.5, 1)
[HideInInspector] _BaseMap("Base Color", 2D) = "white" {}
[Header(Additional Light)]
_AdditionalLightIntensity("Additional Light Intensity", Range(0, 1)) = 0.3
_AdditionalLightShadowStrength("Additional Light Shadow Strength", Range(0, 1)) = 0.5
[HDR] _AdditionalLightShadowColor("Additional Light Shadow Color", Color) = (0.5, 0.5, 0.5, 1)
_Surface("__surface", Float) = 0.0
[HideInInspector][NoScaleOffset]unity_Lightmaps("unity_Lightmaps", 2DArray) = "" {}
[HideInInspector][NoScaleOffset]unity_LightmapsInd("unity_LightmapsInd", 2DArray) = "" {}
[HideInInspector][NoScaleOffset]unity_ShadowMasks("unity_ShadowMasks", 2DArray) = "" {}
}
SubShader
{
Tags
{
"RenderType" = "Opaque"
"RenderPipeline" = "UniversalPipeline"
}
Pass
{
Name "Universal Forward"
Tags
{
"LightMode" = "UniversalForward"
}
Cull Off
ZWrite On
ZTest LEqual
HLSLPROGRAM
#pragma target 2.0
// -------------------------------------
// Shader Stages
#pragma vertex vert
#pragma fragment frag
#pragma shader_feature BLEND
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN
#pragma multi_compile _ _FORWARD_PLUS
#pragma multi_compile_fragment _ _ADDITIONAL_LIGHT_SHADOWS
#include "GrassInput.hlsl"
#include "GrassPass.hlsl"
ENDHLSL
}
Pass
{
Name "ShadowCaster"
Tags
{
"LightMode" = "ShadowCaster"
}
ZWrite On
ZTest LEqual
HLSLPROGRAM
#pragma target 2.0
// -------------------------------------
// Shader Stages
#pragma vertex ShadowPassVert
#pragma fragment ShadowPassFrag
// -------------------------------------
// Includes
#include "GrassInput.hlsl"
#include "GrassShadowCasterPass.hlsl"
ENDHLSL
}
Pass
{
Name "DepthOnly"
Tags
{
"LightMode" = "DepthOnly"
}
ZWrite On
ZTest LEqual
HLSLPROGRAM
#pragma target 2.0
// -------------------------------------
// Shader Stages
#pragma vertex DepthOnlyVert
#pragma fragment DepthOnlyFrag
// -------------------------------------
// Includes
#include "GrassInput.hlsl"
#include "GrassDepthOnlyPass.hlsl"
ENDHLSL
}
}
Fallback "Hidden/Universal Render Pipeline/FallbackError"
}
GrassInput.hlsl
#ifndef UNIVERSAL_SIMPLE_LIT_INPUT_INCLUDED
#define UNIVERSAL_SIMPLE_LIT_INPUT_INCLUDED
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float2 uv : TEXCOORD0;
uint vertexID : SV_VertexID;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float3 worldPos : TEXCOORD0;
float3 normalWS : TEXCOORD1;
float2 uv : TEXCOORD2;
float3 diffuseColor : TEXCOORD3;
float4 extraBuffer : TEXCOORD4;
};
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
CBUFFER_START(UnityPerMaterial)
float4 _TopTint;
float4 _BottomTint;
float _BlendMult, _BlendOff;
uniform TEXTURE2D(_TerrainDiffuse);
uniform SAMPLER(sampler_TerrainDiffuse);
float4 _AmbientAdjustmentColor;
float _AdditionalLightIntensity;
float _AdditionalLightShadowStrength;
float4 _AdditionalLightShadowColor;
float _ShadowStrength;
float4 _ShadowColor;
CBUFFER_END
#endif
GrassPass.hlsl
#include "GrassInput.hlsl"
#include "Grass.hlsl"
#include "CustomLighting.hlsl"
float CalculateVerticalFade(float2 uv)
{
float blendMul = uv.y * _BlendMult;
float blendAdd = blendMul + _BlendOff;
return saturate(blendAdd);
}
float3 CalculateBaseColor(float3 diffuseColor, float verticalFade)
{
return lerp(_BottomTint, _TopTint * _AmbientAdjustmentColor, verticalFade) * diffuseColor;
}
float4 BlendWithTerrain(Varyings input, float verticalFade)
{
float2 uv = input.worldPos.xz - _OrthographicCamPosTerrain.xz;
uv /= _OrthographicCamSizeTerrain * 2;
uv += 0.5;
float4 terrainForBlending = SAMPLE_TEXTURE2D(_TerrainDiffuse, sampler_TerrainDiffuse, uv);
return lerp(terrainForBlending,
terrainForBlending + _TopTint * float4(input.diffuseColor, 1) *
_AmbientAdjustmentColor, verticalFade);
}
void CalculateCutOff(float extraBufferX, float worldPosY)
{
float cutOffTop = extraBufferX >= worldPosY ? 1 : 0;
clip(cutOffTop - 0.01);
}
float4 ApplyMainLightShadow(Varyings input, float4 finalColor)
{
float shadow = 0;
#if _MAIN_LIGHT_SHADOWS_CASCADE || _MAIN_LIGHT_SHADOWS
half4 shadowCoord = TransformWorldToShadowCoord(input.worldPos);
Light mainLight = GetMainLight(shadowCoord);
#else
Light mainLight = GetMainLight();
#endif
shadow = mainLight.shadowAttenuation;
if (shadow <= 0)
{
finalColor.rgb *= lerp(float3(1, 1, 1), _ShadowColor.rgb, _ShadowStrength);
}
return finalColor;
}
Varyings vert(Attributes input)
{
Varyings output;
GetComputeData_float(input.vertexID, output.worldPos, output.normalWS, output.uv, output.diffuseColor,
output.extraBuffer);
output.positionCS = TransformObjectToHClip(output.worldPos);
return output;
}
float4 frag(Varyings input) : SV_Target
{
float verticalFade = CalculateVerticalFade(input.uv);
float4 finalColor = float4(CalculateBaseColor(input.diffuseColor, verticalFade), 1);
#if defined(BLEND)
finalColor = BlendWithTerrain(input, verticalFade);
#endif
CalculateCutOff(input.extraBuffer.x, input.worldPos.y);
finalColor = ApplyMainLightShadow(input, finalColor);
finalColor = ApplyAdditionalLight(input.worldPos, input.normalWS, finalColor,
_AdditionalLightIntensity,
_AdditionalLightShadowStrength,
_AdditionalLightShadowColor.rgb);
return finalColor;
}
Grass.hlsl
// This describes a vertex on the generated mesh
struct DrawVertex
{
float3 positionWS; // The position in world space
float2 uv;
};
// A triangle on the generated mesh
struct DrawTriangle
{
float3 normalOS;
float3 diffuseColor;
float4 extraBuffer;
DrawVertex vertices[3]; // The three points on the triangle
};
// A buffer containing the generated mesh
StructuredBuffer<DrawTriangle> _DrawTriangles;
float _OrthographicCamSizeTerrain;
float3 _OrthographicCamPosTerrain;
//get the data from the compute shader
void GetComputeData_float(uint vertexID, out float3 worldPos, out float3 normal, out float2 uv, out float3 col,
out float4 extraBuffer)
{
DrawTriangle tri = _DrawTriangles[vertexID / 3];
DrawVertex input = tri.vertices[vertexID % 3];
worldPos = input.positionWS;
normal = tri.normalOS;
uv = input.uv;
col = tri.diffuseColor;
// for some reason doing this with a comparison node results in a glitchy alpha, so we're doing it here, if your grass is at a point higher than 99999 Y position then you should make this even higher or find a different solution
if (tri.extraBuffer.x == -1)
{
extraBuffer = float4(99999, tri.extraBuffer.y, tri.extraBuffer.z, tri.extraBuffer.w);
}
else
{
extraBuffer = tri.extraBuffer;
}
}
// Calculate world space UV for blending
void GetWorldUV_float(float3 worldPos, out float2 worldUV)
{
float2 uv = worldPos.xz - _OrthographicCamPosTerrain.xz;
uv = uv / (_OrthographicCamSizeTerrain * 2);
uv += 0.5;
worldUV = uv;
}
CustomLighting.hlsl
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
float4 ApplyAdditionalLight(float3 worldPos, float3 worldNormal, float4 finalColor, float additionalLightIntensity,
float additionalLightShadowStrength, float3 additionalLightShadowColor)
{
float3 diffuseColor = 0;
uint pixelLightCount = GetAdditionalLightsCount();
InputData inputData;
float4 screenPos = ComputeScreenPos(TransformWorldToHClip(worldPos));
inputData.normalizedScreenSpaceUV = screenPos.xy / screenPos.w;
inputData.positionWS = worldPos;
LIGHT_LOOP_BEGIN(pixelLightCount)
Light light;
#if _MAIN_LIGHT_SHADOWS_CASCADE || _MAIN_LIGHT_SHADOWS
half4 shadowMask = CalculateShadowMask(inputData);
light = GetAdditionalLight(lightIndex, worldPos, shadowMask);
#else
light = GetAdditionalLight(lightIndex, worldPos);
#endif
#ifdef _LIGHT_LAYERS
if (IsMatchingLightLayer(light.layerMask, meshRenderingLayers))
#endif
{
if (light.shadowAttenuation >= 0)
{
float3 lightColor = light.color * light.distanceAttenuation * additionalLightIntensity;
if (light.shadowAttenuation == 0)
{
lightColor *= additionalLightShadowStrength * additionalLightShadowColor;
}
diffuseColor += LightingLambert(lightColor, light.direction, worldNormal);
}
}
LIGHT_LOOP_END
finalColor.rgb += diffuseColor;
return finalColor;
}
I included the ShadowCaster and DepthOnly Passes just for testing, and the two files used for them, GrassShadowCasterPass.hlsl and GrassDepthOnlyPass.hlsl, are identical to Unity’s ShadowCasterPass.hlsl and DepthOnlyPass.hlsl files. The only difference is that I used the struct from GrassInput.hlsl.
So what I want to know is why the grass doesn’t show a grid when using the shader graph, but when I use the shader code, the grid is visible on the grass. I initially thought it was because the grass is transparent and the grid behind it is showing through, so I tried switching it to opaque and added ZWrite On Off and ZTest every options, but there was no change, which made me even more confused. How should I approach this?
Thank you for reading my long question. ![]()
