Skip to content

Instantly share code, notes, and snippets.

@niuage
Created May 25, 2020 14:11
Show Gist options
  • Save niuage/1cc88222a5b947620c439003caff76ac to your computer and use it in GitHub Desktop.
Save niuage/1cc88222a5b947620c439003caff76ac to your computer and use it in GitHub Desktop.
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
}
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 multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _SHADOWS_SOFT
// Defines
#define BLADE_SEGMENTS 3
// 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.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
}
// Used for rendering shadowmaps
//UsePass "Universal Render Pipeline/Lit/ShadowCaster"
Pass {
Name "ShadowCaster"
Tags {"LightMode" = "ShadowCaster"}
ZWrite On
ZTest LEqual
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
// -------------------------------------
// Material Keywords
//#pragma shader_feature _ALPHATEST_ON
//--------------------------------------
// GPU Instancing
//#pragma multi_compile_instancing
//#pragma shader_feature _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
#pragma require geometry
#pragma vertex vert
#pragma geometry geom
#pragma fragment ShadowPassFragment2
#define BLADE_SEGMENTS 3
#define SHADOW
//#include "Packages/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"
#include "grass.hlsl"
half4 ShadowPassFragment2(GeometryOutput input) : SV_TARGET{
//Alpha(SampleAlbedoAlpha(input.uv, TEXTURE2D_ARGS(_BaseMap, sampler_BaseMap)).a, _BaseColor, _Cutoff);
return 0;
}
ENDHLSL
}
// Used for depth prepass
// If shadows cascade are enabled we need to perform a depth prepass.
// We also need to use a depth prepass in some cases camera require depth texture
// (e.g, MSAA is enabled and we can't resolve with Texture2DMS
//UsePass "Universal Render Pipeline/Lit/DepthOnly"
// Note, can't UsePass + SRP Batcher due to UnityPerMaterial CBUFFER having incosistent size between subshaders..
// Had to comment this out for now so it doesn't break SRP Batcher.
// Would have to copy the pass in here and use the same UnityPerMaterial buffer the other passes use
}
}
--------------------------------------------------
(grass.hlsl)
// @Cyanilux
// Grass Geometry Shader, Written for Universal RP with help from https://roystan.net/articles/grass-shader.html
// Note, doesn't include Lighting or Tessellation
// Structs
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;
float2 texcoord : TEXCOORD0;
};
struct GeometryOutput {
float4 positionCS : SV_POSITION;
float3 positionWS : TEXCOORD1;
float2 uv : TEXCOORD0;
};
// 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 _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;
output.texcoord = input.texcoord;
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;
//VertexPositionInputs vertexInput = GetVertexPositionInputs(input[0].positionOS.xyz);
// Note, this works fine without SRP Batcher but seems to break when using it. See vert function above.
/* (Normal mesh vertices. Need to add 3 to maxvertexcount if this is uncommented)
output.positionCS = TransformWorldToHClip(input[0].positionWS);
output.uv = input[0].texcoord;
triStream.Append(output);
output.positionCS = TransformWorldToHClip(input[1].positionWS);
output.uv = input[1].texcoord;
triStream.Append(output);
output.positionCS = TransformWorldToHClip(input[2].positionWS);
output.uv = input[2].texcoord;
triStream.Append(output);
triStream.RestartStrip();
*/
// 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)) * 0.1 * sin(_Time.y + r);
float3x3 windMatrix = AngleAxis3x3(wind * PI, 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);
// Center (2 vertices per BLADE_SEGMENTS)
for (int i = 1; i < BLADE_SEGMENTS; i++) {
float t = i / (float)BLADE_SEGMENTS;
float h = height * t;
float w = width * (1-t);
float b = bend * pow(t, 2);
output.positionWS = positionWS + mul(transformMatrixWithWind, float3(w, b, h));
output.positionCS = WorldToHClip(output.positionWS, normalWS);
output.uv = float2(0, t);
triStream.Append(output);
output.positionWS = positionWS + mul(transformMatrixWithWind, float3(-w, b, h));
output.positionCS = WorldToHClip(output.positionWS, normalWS);
output.uv = float2(0, t);
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();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment