Skip to content

Instantly share code, notes, and snippets.

@niuage
Last active March 8, 2024 22:07
Show Gist options
  • Save niuage/933abbc7a26a311f7d9e016e124afbd5 to your computer and use it in GitHub Desktop.
Save niuage/933abbc7a26a311f7d9e016e124afbd5 to your computer and use it in GitHub Desktop.
///////////////////////////////////////////////////
//////////////// grass_struct.hlsl ////////////////
///////////////////////////////////////////////////
struct Attributes {
float4 positionOS : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 texcoord : TEXCOORD0;
};
struct Varyings {
float4 positionOS : SV_POSITION;
float3 positionWS : TEXCOORD1;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};
struct GeometryOutput {
float4 positionCS : SV_POSITION;
float3 positionWS : TEXCOORD1;
float2 uv : TEXCOORD0;
};
////////////////////////////////////////////
//////////////// grass.hlsl ////////////////
////////////////////////////////////////////
// Original by @Cyanilux
// Grass Geometry Shader, Written for Universal RP with help from https://roystan.net/articles/grass-shader.html
// Modified by @niuage to add tessellation. Also removed some code I didnt need in my own game, so feel free to revert to the original
// version from Cyanilux here: https://pastebin.com/Ey01tzLq
// Methods
float rand(float3 seed) {
return frac(sin(dot(seed.xyz, float3(12.9898, 78.233, 53.539))) * 43758.5453);
}
// https://gist.github.com/keijiro/ee439d5e7388f3aafc5296005c8c3f33
float3x3 AngleAxis3x3(float angle, float3 axis) {
float c, s;
sincos(angle, s, c);
float t = 1 - c;
float x = axis.x;
float y = axis.y;
float z = axis.z;
return float3x3(
t * x * x + c, t * x * y - s * z, t * x * z + s * y,
t * x * y + s * z, t * y * y + c, t * y * z - s * x,
t * x * z - s * y, t * y * z + s * x, t * z * z + c
);
}
float3 _LightDirection;
float4 GetShadowPositionHClip(float3 positionWS, float3 normalWS) {
float4 positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, _LightDirection));
#if UNITY_REVERSED_Z
positionCS.z = min(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE);
#else
positionCS.z = max(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE);
#endif
return positionCS;
}
float4 WorldToHClip(float3 positionWS, float3 normalWS) {
#ifdef SHADOW
return GetShadowPositionHClip(positionWS, normalWS);
#else
return TransformWorldToHClip(positionWS);
#endif
}
// Variables
CBUFFER_START(UnityPerMaterial) // Required to be compatible with SRP Batcher
float4 _Color;
float4 _Color2;
float _Width;
float _RandomWidth;
float _WindStrength;
float _Height;
float _RandomHeight;
CBUFFER_END
// Vertex, Geometry & Fragment Shaders
Varyings vert(Attributes input) {
Varyings output = (Varyings)0;
VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
// Seems like GetVertexPositionInputs doesn't work with SRP Batcher inside geom function?
// Had to move it here, in order to obtain positionWS and pass it through the Varyings output.
output.positionOS = input.positionOS; //vertexInput.positionCS; //
output.positionWS = vertexInput.positionWS;
output.normal = input.normal;
output.tangent = input.tangent;
return output;
}
[maxvertexcount(BLADE_SEGMENTS * 2 + 1)]
void geom(uint primitiveID : SV_PrimitiveID, triangle Varyings input[3], inout TriangleStream<GeometryOutput> triStream) {
GeometryOutput output = (GeometryOutput)0;
// Construct World -> Tangent Matrix (for aligning grass with mesh normals)
float3 normal = input[0].normal;
float4 tangent = input[0].tangent;
float3 binormal = cross(normal, tangent) * tangent.w;
float3x3 tangentToLocal = float3x3(
tangent.x, binormal.x, normal.x,
tangent.y, binormal.y, normal.y,
tangent.z, binormal.z, normal.z
);
float3 positionWS = input[0].positionWS;
float r = rand(positionWS.xyz);
float3x3 randRotation = AngleAxis3x3(r * TWO_PI, float3(0, 0, 1));
// Wind (based on sin / cos, aka a circular motion, but strength of 0.1 * sine)
float2 wind = float2(sin(_Time.y + positionWS.x * 0.5), cos(_Time.y + positionWS.z * 0.5)) * _WindStrength * sin(_Time.y + r);
float3x3 windMatrix = AngleAxis3x3((wind * PI).y, normalize(float3(wind.x, wind.y, 0)));
float3x3 transformMatrix = mul(tangentToLocal, randRotation);
float3x3 transformMatrixWithWind = mul(mul(tangentToLocal, windMatrix), randRotation);
float bend = rand(positionWS.xyz) - 0.5;
float width = _Width + _RandomWidth * (rand(positionWS.zyx) - 0.5);
float height = _Height + _RandomHeight * (rand(positionWS.yxz) - 0.5);
float3 normalWS = mul(transformMatrix, float3(0, 1, 0)); //?
// Handle Geometry
// Base 2 vertices
output.positionWS = positionWS + mul(transformMatrix, float3(width, 0, 0));
output.positionCS = WorldToHClip(output.positionWS, normalWS);
output.uv = float2(0, 0);
triStream.Append(output);
output.positionWS = positionWS + mul(transformMatrix, float3(-width, 0, 0));
output.positionCS = WorldToHClip(output.positionWS, normalWS);
output.uv = float2(0, 0);
triStream.Append(output);
// Final vertex at top of blade
output.positionWS = positionWS + mul(transformMatrixWithWind, float3(0, bend, height));
output.positionCS = WorldToHClip(output.positionWS, normalWS);
output.uv = float2(0, 1);
triStream.Append(output);
triStream.RestartStrip();
}
/////////////////////////////////////////////////
//////////////// GeoGrass.shader ////////////////
/////////////////////////////////////////////////
// @Cyanilux
// Grass Geometry Shader, Written for Universal RP with help from https://roystan.net/articles/grass-shader.html
Shader "Unlit/GeoGrass" {
Properties {
_Color("Colour", Color) = (1,1,1,1)
_Color2("Colour2", Color) = (1,1,1,1)
_Width("Width", Float) = 1
_RandomWidth("Random Width", Float) = 1
_Height("Height", Float) = 1
_RandomHeight("Random Height", Float) = 1
_WindStrength("Wind Strength", Float) = 0.1
[Space]
_TessellationUniform("Tessellation Uniform", Range(1, 64)) = 1
}
SubShader {
Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }
LOD 300
Cull Off
Pass {
Name "ForwardLit"
Tags {"LightMode" = "UniversalForward"}
HLSLPROGRAM
// Required to compile gles 2.0 with standard srp library
#pragma prefer_hlslcc gles
#pragma exclude_renderers d3d11_9x gles
#pragma target 4.5
#pragma require geometry
#pragma vertex vert
#pragma geometry geom
#pragma fragment frag
#pragma hull hull
#pragma domain domain
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _SHADOWS_SOFT
// Defines
#define BLADE_SEGMENTS 1
// Includes
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"
# include "grass_structs.hlsl"
#include "tessellation/CustomTessellation.hlsl"
#include "grass.hlsl"
// Fragment
float4 frag(GeometryOutput input) : SV_Target {
#if SHADOWS_SCREEN
float4 clipPos = TransformWorldToHClip(input.positionWS);
float4 shadowCoord = ComputeScreenPos(clipPos);
#else
float4 shadowCoord = TransformWorldToShadowCoord(input.positionWS);
#endif
Light mainLight = GetMainLight(shadowCoord);
return lerp(_Color, _Color2, input.uv.y) * mainLight.shadowAttenuation;
}
ENDHLSL
}
}
}
/////////////////////////////////////////////////////////////////////
//////////////////// tessellation/CustomTessellation.hlsl ////////////////////////
/////////////////////////////////////////////////////////////////////
// Tessellation programs based on this article by Catlike Coding:
// https://catlikecoding.com/unity/tutorials/advanced-rendering/tessellation/
#if defined(SHADER_API_D3D11) || defined(SHADER_API_GLES3) || defined(SHADER_API_GLCORE) || defined(SHADER_API_VULKAN) || defined(SHADER_API_METAL) || defined(SHADER_API_PSSL)
#define UNITY_CAN_COMPILE_TESSELLATION 1
# define UNITY_domain domain
# define UNITY_partitioning partitioning
# define UNITY_outputtopology outputtopology
# define UNITY_patchconstantfunc patchconstantfunc
# define UNITY_outputcontrolpoints outputcontrolpoints
#endif
struct TessellationFactors
{
float edge[3] : SV_TessFactor;
float inside : SV_InsideTessFactor;
};
CBUFFER_START(UnityPerMaterial)
float _TessellationUniform;
CBUFFER_END
TessellationFactors patchConstantFunction (InputPatch<Varyings, 3> patch)
{
TessellationFactors f;
f.edge[0] = _TessellationUniform;
f.edge[1] = _TessellationUniform;
f.edge[2] = _TessellationUniform;
f.inside = _TessellationUniform;
return f;
}
[UNITY_domain("tri")]
[UNITY_outputcontrolpoints(3)]
[UNITY_outputtopology("triangle_cw")]
[UNITY_partitioning("integer")]
[UNITY_patchconstantfunc("patchConstantFunction")]
Varyings hull (InputPatch<Varyings, 3> patch, uint id : SV_OutputControlPointID)
{
return patch[id];
}
[UNITY_domain("tri")]
Varyings domain(TessellationFactors factors, OutputPatch<Varyings, 3> patch, float3 barycentricCoordinates : SV_DomainLocation)
{
Varyings v;
#define MY_DOMAIN_PROGRAM_INTERPOLATE(fieldName) v.fieldName = \
patch[0].fieldName * barycentricCoordinates.x + \
patch[1].fieldName * barycentricCoordinates.y + \
patch[2].fieldName * barycentricCoordinates.z;
MY_DOMAIN_PROGRAM_INTERPOLATE(positionWS)
MY_DOMAIN_PROGRAM_INTERPOLATE(positionOS)
MY_DOMAIN_PROGRAM_INTERPOLATE(normal)
MY_DOMAIN_PROGRAM_INTERPOLATE(tangent)
return v;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment