目录
一、简介
本文在Catlike Coding 实现的【Custom SRP 2.5.0】基础上参考了Roystan的文章 【Grass Shader】实现了风格化草的渲染。
这个风格化草在网上有不少实现案例,本文主要是给出他在该管线下的一种实现参考以及添加多光源的支持

二,环境
Unity :2022.3.18f1
CRP Library :14.0.10
URP基本结构 : Custom SRP 2.5.0
三 ,实现简要步骤
该风格化草实现主要分为两个部分,第一步是在GPU中通过曲面细分与几何着色器构建草地的网格。第二步便是添加光影效果。
四,实现过程
1,准备基本工作
创建GrassShader.hlsl文件添加如下代码
#ifndef GRASSSHADER_COMMON_INCLUDED
#define GRASSSHADER_COMMON_INCLUDED
UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)
float rand(float3 co)
{
return frac(sin(dot(co.xyz, float3(12.9898, 78.233, 53.539))) * 43758.5453);
}
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
);
}
float4 vert(float4 vertex : POSITION) : SV_POSITION
{
return TransformObjectToHClip(vertex);
}
float4 frag(float4 vertex : SV_POSITION, float facing : VFACE) : SV_Target
{
return float4(1, 1, 1, 1);
}
#endif
创建GrassShader.shader 添加如下代码
Shader "Custom/GrassShader"
{
Properties
{
}
SubShader
{
HLSLINCLUDE
#include "../ShaderLibrary/Common.hlsl"
#include "../ShaderLibrary/GrassShader.hlsl"
ENDHLSL
Pass
{
Tags { "LightMode" = "CustomLit" }
Cull Off
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 4.6
ENDHLSL
}
}
FallBack "Diffuse"
}
创建GrassShader对应的材质,之后创建新物体添加Mesh Renderer 将该材质绑定,添加Plane(Mesh Filter) 网格,过程不演示
完成后,你应该是这个样子

编辑你的Common.hlsl ,添加如下定义
#define UNITY_PI 3.14159265359f
#define UNITY_TWO_PI 6.28318530718f
2,编辑草地网格
2.1 添加几何着色器,绘制草的基本形状
修改GrassShader.shader,添加如下代码
#pragma vertex vert
#pragma geometry geo//新增这行
#pragma fragment frag
修改GrassShader.hlsl ,添加如下代码
struct geometryOutput
{
float4 pos : SV_POSITION;
};
[maxvertexcount(3)]
void geo(triangle float4 IN[3] : SV_POSITION, inout TriangleStream<geometryOutput> triStream)
{
float3 pos = IN[0];
geometryOutput o;
o.pos = TransformObjectToHClip(pos + float3(0.5, 0, 0));
triStream.Append(o);
o.pos = TransformObjectToHClip(pos + float3(-0.5, 0, 0));
triStream.Append(o);
o.pos = TransformObjectToHClip(pos + float3(0, 1, 0));
triStream.Append(o);
}
这时,你的效果应该是这样的

几何着色器在每个网格上都输出了一个三角形,并在frag中涂成了白色
2.2 增加细节与随机朝向
修改GrassShader.shader,添加如下代码
Properties
{
_BendRotationRandom("Bend Rotation Random", Range(0, 1)) = 0.2 //转向随机参数
_BladeWidth("Blade Width", Float) = 0.05 //草的宽
_BladeWidthRandom("Blade Width Random", Float) = 0.02
_BladeHeight("Blade Height", Float) = 0.5 //草的高
_BladeHeightRandom("Blade Height Random", Float) = 0.3
_BladeForward("Blade Forward Amount", Float) = 0.38 //草尖位置
_BladeCurve("Blade Curvature Amount", Range(1, 4)) = 2
}
修改GrassShader.hlsl ,添加并修改如下代码
UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
.......
UNITY_DEFINE_INSTANCED_PROP(float, _BendRotationRandom)
UNITY_DEFINE_INSTANCED_PROP(float, _BladeHeight)
UNITY_DEFINE_INSTANCED_PROP(float, _BladeHeightRandom)
UNITY_DEFINE_INSTANCED_PROP(float, _BladeWidth)
UNITY_DEFINE_INSTANCED_PROP(float, _BladeWidthRandom)
UNITY_DEFINE_INSTANCED_PROP(float, _BladeForward)
UNITY_DEFINE_INSTANCED_PROP(float, _BladeCurve)
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)
#define BLADE_SEGMENTS 3
......
struct geometryOutput
{
float4 pos : SV_POSITION;
float3 posWS : VAR_POSITION;
float2 uv : TEXCOORD0;
};
struct vertexInput
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};
struct vertexOutput
{
float4 vertex : SV_POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};
//调整顶点着色器输出,vertexOutput作为中间变量,后续在曲面细分着色器中需要用到,这里提前设置
vertexOutput vert(vertexInput v)
{
vertexOutput o;
o.vertex = v.vertex;
o.normal = v.normal;
o.tangent = v.tangent;
return o;
}
geometryOutput GenerateGrassVertex(float3 vertexPosition, float width, float height, float forward, float2 uv, float3x3 transformMatrix)
{
float3 tangentPoint = float3(width, forward, height);
float3 localPosition = vertexPosition + mul(transformMatrix, tangentPoint);
return VertexOutput(localPosition, uv);
}
[maxvertexcount(BLADE_SEGMENTS * 2 + 1)]
void geo(triangle vertexOutput IN[3] : SV_POSITION, inout TriangleStream<geometryOutput> triStream)
{
float3 pos = IN[0].vertex;
// Place in the geometry shader, below the line declaring float3 pos.
float3 vNormal = IN[0].normal;
float4 vTangent = IN[0].tangent;
float3 vBinormal = cross(vNormal, vTangent) * vTangent.w;
float height = (rand(pos.zyx) * 2 - 1) * _BladeHeightRandom + _BladeHeight;
float width = (rand(pos.xzy) * 2 - 1) * _BladeWidthRandom + _BladeWidth;
// Add below the lines declaring the three vectors.
float3x3 tangentToLocal = float3x3(
vTangent.x, vBinormal.x, vNormal.x,
vTangent.y, vBinormal.y, vNormal.y,
vTangent.z, vBinormal.z, vNormal.z
);
float3x3 facingRotationMatrix = AngleAxis3x3(rand(pos) * UNITY_TWO_PI, float3(0, 0, 1));
float3x3 bendRotationMatrix = AngleAxis3x3(rand(pos.zzx) * _BendRotationRandom * UNITY_PI * 0.5, float3(-1, 0, 0));
float3x3 transformationMatrix = mul(mul(tangentToLocal, facingRotationMatrix), bendRotationMatrix);
float3x3 transformationMatrixFacing = mul(tangentToLocal, facingRotationMatrix);
float forward = rand(pos.yyz) * _BladeForward;
for (int i = 0; i < BLADE_SEGMENTS; i++)
{
float t = i / (float)BLADE_SEGMENTS;
float segmentHeight = height * t;
float segmentWidth = width * (1 - t);
float3x3 transformMatrix = i == 0 ? transformationMatrixFacing : transformationMatrix;
float segmentForward = pow(t, _BladeCurve) * forward;
triStream.Append(GenerateGrassVertex(pos, segmentWidth, segmentHeight, segmentForward, float2(0, t), transformMatrix));
triStream.Append(GenerateGrassVertex(pos, -segmentWidth, segmentHeight, segmentForward, float2(1, t), transformMatrix));
}
triStream.Append(GenerateGrassVertex(pos, 0, height, forward, float2(0.5, 1), transformationMatrix));
}
这个时候你的草应该是这样,想要细分形状的话,研究这段代码的作用即可
2.3 增加曲面细分着色器,增加草片数量
修改GrassShader.shader,添加如下代码
Properties
{
......
_TessellationUniform("Tessellation Uniform", Range(1, 64)) = 1
}
......
HLSLPROGRAM
#pragma vertex vert
#pragma hull hull //这行
#pragma domain domain //这行
#pragma geometry geo
#pragma fragment frag
#pragma target 4.6
ENDHLSL
......
修改GrassShader.hlsl,添加如下代码
UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
........
UNITY_DEFINE_INSTANCED_PROP(float, _TessellationUniform)
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)
........
........
struct TessellationFactors
{
float edge[3] : SV_TessFactor;
float inside : SV_InsideTessFactor;
};
........
TessellationFactors patchConstantFunction(InputPatch<vertexInput, 3> patch)
{
TessellationFactors f;
f.edge[0] = _TessellationUniform;
f.edge[1] = _TessellationUniform;
f.edge[2] = _TessellationUniform;
f.inside = _TessellationUniform;
return f;
}
[domain("tri")]
[outputcontrolpoints(3)]
[outputtopology("triangle_cw")]
[partitioning("integer")]
[patchconstantfunc("patchConstantFunction")]
vertexInput hull(InputPatch<vertexInput, 3> patch, uint id : SV_OutputControlPointID)
{
return patch[id];
}
vertexOutput tessVert(vertexInput v)
{
vertexOutput o;
// Note that the vertex is NOT transformed to clip
// space here; this is done in the grass geometry shader.
o.vertex = v.vertex;
o.normal = v.normal;
o.tangent = v.tangent;
return o;
}
[domain("tri")]
vertexOutput domain(TessellationFactors factors, OutputPatch<vertexInput, 3> patch, float3 barycentricCoordinates : SV_DomainLocation)
{
vertexInput 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(vertex)
MY_DOMAIN_PROGRAM_INTERPOLATE(normal)
MY_DOMAIN_PROGRAM_INTERPOLATE(tangent)
return tessVert(v);
}
.......
这个时候你的草应该长这样

参数参考:

3,编辑草的颜色
3.1 设置基本颜色
修改GrassShader.shader,添加如下代码
Properties
{
.......
_TopColor("Top Color", Color) = (1,1,1,1)
_BottomColor("Bottom Color", Color) = (1,1,1,1)
}
修改GrassShader.hlsl,修改frag,颜色只是普通的线性插值
.......
UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
.......
UNITY_DEFINE_INSTANCED_PROP(float4, _BottomColor)
UNITY_DEFINE_INSTANCED_PROP(float4, _TopColor)
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)
........
float4 frag(geometryOutput i, float facing : VFACE) : SV_Target
{
float4 base = lerp(_BottomColor, _TopColor, i.uv.y);
return base;
}
调整好颜色后,这个时候你的草应该是这样

底部空空的不好看,再增加个Plane

到这一步这个草地已经基本成型了,接下来的步骤基本算是效果上的优化
3.2 添加光影
3.2.1 添加影子
修改GrassShader.shader,添加如下代码
SubShader
{
.........
Pass
{
Tags {
"LightMode" = "ShadowCaster"
}
ColorMask 0
HLSLPROGRAM
#pragma vertex vert
#pragma hull hull
#pragma domain domain
#pragma geometry geo
#pragma fragment fragShadow
#pragma shader_feature _ _SHADOWS_CLIP _SHADOWS_DITHER
#pragma multi_compile _ LOD_FADE_CROSSFADE
#pragma multi_compile_instancing
#pragma target 4.6
void fragShadow(geometryOutput i)
{
UNITY_SETUP_INSTANCE_ID(i);
InputConfig config = GetInputConfig(i.pos, i.uv);
ClipLOD(config.fragment, unity_LODFade.x);
#if defined(_SHADOWS_CLIP)
clip(base.a - GetCutoff(config));
#elif defined(_SHADOWS_DITHER)
float dither = InterleavedGradientNoise(input.positionCS_SS.xy, 0);
clip(base.a - dither);
#endif
}
ENDHLSL
}
}
修改GrassShader.hlsl,添加一些引用
#include "Surface.hlsl"
#include "Shadows.hlsl"
#include "LitInput.hlsl"
如果代码没问题,你应该能看到草片投射到别的物体上的阴影

3.2.2 添加光的影响
这部分自由发挥空间很大,这边给出我自己的实现方案仅作参考
修改GrassShader.shader,添加如下代码
Properties
{
........
_TranslucentGain("Translucent Gain", Range(0,1)) = 0.5
[Toggle(_RECEIVE_SHADOWS)] _ReceiveShadows("Receive Shadows", Float) = 1
}
........
Pass
{
Tags { "LightMode" = "CustomLit" }
Cull Off
HLSLPROGRAM
//标记接受阴影,在frag计算颜色的时候会用到
#pragma shader_feature _RECEIVE_SHADOWS
#pragma multi_compile _ _OTHER_PCF3 _OTHER_PCF5 _OTHER_PCF7
#pragma multi_compile _ _SHADOW_MASK_ALWAYS _SHADOW_MASK_DISTANCE
#pragma multi_compile _ _CASCADE_BLEND_SOFT _CASCADE_BLEND_DITHER
#pragma multi_compile _ _DIRECTIONAL_PCF3 _DIRECTIONAL_PCF5 _DIRECTIONAL_PCF7
.......
ENDHLSL
}
修改GrassShader.hlsl,添加一些引用
.......
#include "Surface.hlsl"
#include "Shadows.hlsl"
#include "Light.hlsl"
#include "BRDF.hlsl"
#include "GI.hlsl"
#include "Lighting.hlsl"
#include "LitInput.hlsl"
.......
UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
.......
UNITY_DEFINE_INSTANCED_PROP(float, _TranslucentGain)
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)
.......
struct geometryOutput
{
.......
float3 normalWS : VAR_NORMAL;
};
//添加法线向量
geometryOutput VertexOutput(float3 pos, float2 uv, float3 normal)
{
.......
o.normalWS = TransformObjectToWorldNormal(normal);
return o;
}
geometryOutput GenerateGrassVertex(float3 vertexPosition, float width, float height, float forward, float2 uv, float3x3 transformMatrix)
{
.......
float3 tangentNormal = normalize(float3(0, -1, forward));
float3 localNormal = mul(transformMatrix, tangentNormal);
return VertexOutput(localPosition, uv, localNormal);
}
.......
//计算各个光源的影响,这个部分直接参考的LitPass的代码编写
float3 GetGLighting(Surface surfaceWS, BRDF brdf, GI gi)
{
ShadowData shadowData = GetShadowData(surfaceWS);
shadowData.shadowMask = gi.shadowMask;
float3 color = IndirectBRDF(surfaceWS, brdf, gi.diffuse, gi.specular);
for (int i = 0; i < GetDirectionalLightCount(); i++) {
Light light = GetDirectionalLight(i, surfaceWS, shadowData);
if (RenderingLayersOverlap(surfaceWS, light)) {
float NdotL = saturate(saturate(dot(surfaceWS.normal, light.direction)) + _TranslucentGain) * light.attenuation;
color += NdotL * light.color;
}
}
#if defined(_LIGHTS_PER_OBJECT)
for (int j = 0; j < min(unity_LightData.y, 8); j++) {
int lightIndex = unity_LightIndices[(uint)j / 4][(uint)j % 4];
Light light = GetOtherLight(lightIndex, surfaceWS, shadowData);
if (RenderingLayersOverlap(surfaceWS, light)) {
float NdotL = saturate(saturate(dot(surfaceWS.normal, light.direction)) + _TranslucentGain) * light.attenuation;
color += NdotL * light.color;
}
}
#else
for (int j = 0; j < GetOtherLightCount(); j++) {
Light light = GetOtherLight(j, surfaceWS, shadowData);
if (RenderingLayersOverlap(surfaceWS, light)) {
float NdotL = saturate(saturate(dot(surfaceWS.normal, light.direction)) + _TranslucentGain) * light.attenuation;
color += NdotL * light.color;
}
}
#endif
return color;
}
float4 frag(geometryOutput i, float facing : VFACE) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(i);
InputConfig config = GetInputConfig(i.pos, i.uv);
ClipLOD(config.fragment, unity_LODFade.x);
float4 base = lerp(_BottomColor, _TopColor, i.uv.y);
float3 normal = facing > 0 ? i.normalWS : -i.normalWS;
Surface surface;
surface.position = i.posWS;
surface.normal = normalize(normal);
surface.interpolatedNormal = surface.normal;
surface.viewDirection = normalize(_WorldSpaceCameraPos - i.posWS);
surface.depth = -TransformWorldToView(i.posWS).z;
surface.color = base.rgb;
surface.alpha = base.a;
surface.metallic = 0;
surface.occlusion = 1;
surface.smoothness = 0;
surface.fresnelStrength = 1;
surface.dither = 1;
surface.renderingLayerMask = asuint(unity_RenderingLayer.x);
BRDF brdf = GetBRDF(surface);
GI gi = GetGI(GI_FRAGMENT_DATA(i), surface, brdf);
float4 lightIntensity = float4(GetGLighting(surface, brdf, gi),1);
float4 col = lerp(_BottomColor, _TopColor * lightIntensity, i.uv.y);
return col;
}
如果代码没有问题的话,你应该会得到这样一个效果

参数参考:

4,来点风
预先准备工作,添加时间变量_TimeDelta
新增SetupBeForeLightPass.cs 文件,添加如下代码
using UnityEngine.Experimental.Rendering.RenderGraphModule;
using UnityEngine.Rendering;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
public class SetupBeForeLightPass
{
static readonly ProfilingSampler sampler = new("SetupBeForeLight");
static int
TimeDelta = Shader.PropertyToID("_TimeDelta");
void Render(RenderGraphContext context)
{
CommandBuffer buffer = context.cmd;
buffer.SetGlobalFloat(TimeDelta, Time.time);
context.renderContext.ExecuteCommandBuffer(context.cmd);
context.cmd.Clear();
}
public static void Record(
RenderGraph renderGraph)
{
using RenderGraphBuilder builder =
renderGraph.AddRenderPass(sampler.name, out SetupBeForeLightPass pass, sampler);
builder.AllowPassCulling(false);
builder.SetRenderFunc<SetupBeForeLightPass>(
static (pass, context) => pass.Render(context));
}
}
修改CameraRenderer.cs 添加如下代码
.......
public void Render(RenderGraph renderGraph, ScriptableRenderContext context, Camera camera, CameraBufferSettings bufferSettings, bool useLightsPerObject, ShadowSettings shadowSettings, PostFXSettings postFXSettings,int colorLUTResolution)
{
.......
using (renderGraph.RecordAndExecute(renderGraphParameters))
{
// Add passes here.
using var _ = new RenderGraphProfilingScope(renderGraph, cameraSampler);
SetupBeForeLightPass.Record(renderGraph);
.......
}
}
.......
修改 Common.hlsl 添加变量
......
float _TimeDelta;
......
从【Grass Shader】拿取风的贴图

修改GrassShader.shader
Properties
{
.......
_WindDistortionMap("Wind Distortion Map", 2D) = "white" {}
_WindFrequency("Wind Frequency", Vector) = (0.05, 0.05, 0, 0)
_WindStrength("Wind Strength", Float) = 1
.......
}
修改GrassShader.hlsl
.......
UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
.......
UNITY_DEFINE_INSTANCED_PROP(float2, _WindFrequency)
UNITY_DEFINE_INSTANCED_PROP(float, _WindStrength)
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)
TEXTURE2D(_WindDistortionMap);
SAMPLER(sampler_WindDistortionMap);
float4 _WindDistortionMap_TexelSize;
#define BLADE_SEGMENTS 3
.......
void geo(triangle vertexOutput IN[3] : SV_POSITION, inout TriangleStream<geometryOutput> triStream)
{
.......
float2 uv = pos.xz * _WindDistortionMap_TexelSize.xy + _WindDistortionMap_TexelSize.zw + _WindFrequency * _TimeDelta;
float4 _WindD = SAMPLE_TEXTURE2D_LOD(_WindDistortionMap, sampler_WindDistortionMap, uv, 0);
float2 windSample = (_WindD.xy * 2 - 1) * _WindStrength;
float3 wind = normalize(float3(windSample.x, windSample.y, 0));
float3x3 windRotation = AngleAxis3x3(UNITY_PI * windSample, wind);
.......
float3x3 transformationMatrix = mul(mul(mul(tangentToLocal, windRotation), facingRotationMatrix), bendRotationMatrix);
.......
}
如果代码没问题的话,效果应该是这样子

运行的时候应该是有运动的
参数参考:

五,参考代码
GrassShader.shader
Shader "Custom/GrassShader"
{
Properties
{
_TopColor("Top Color", Color) = (1,1,1,1)
_BottomColor("Bottom Color", Color) = (1,1,1,1)
_TranslucentGain("Translucent Gain", Range(0,1)) = 0.5
_BendRotationRandom("Bend Rotation Random", Range(0, 1)) = 0.2
_BladeWidth("Blade Width", Float) = 0.05
_BladeWidthRandom("Blade Width Random", Float) = 0.02
_BladeHeight("Blade Height", Float) = 0.5
_BladeHeightRandom("Blade Height Random", Float) = 0.3
_TessellationUniform("Tessellation Uniform", Range(1, 64)) = 1
_WindDistortionMap("Wind Distortion Map", 2D) = "white" {}
_WindFrequency("Wind Frequency", Vector) = (0.05, 0.05, 0, 0)
_WindStrength("Wind Strength", Float) = 1
_BladeForward("Blade Forward Amount", Float) = 0.38
_BladeCurve("Blade Curvature Amount", Range(1, 4)) = 2
[Toggle(_RECEIVE_SHADOWS)] _ReceiveShadows("Receive Shadows", Float) = 1
}
SubShader
{
HLSLINCLUDE
#include "../ShaderLibrary/Common.hlsl"
#include "../ShaderLibrary/GrassShader.hlsl"
ENDHLSL
Pass
{
Tags { "LightMode" = "CustomLit" }
Cull Off
HLSLPROGRAM
#pragma shader_feature _RECEIVE_SHADOWS
#pragma multi_compile _ _OTHER_PCF3 _OTHER_PCF5 _OTHER_PCF7
#pragma multi_compile _ _SHADOW_MASK_ALWAYS _SHADOW_MASK_DISTANCE
#pragma multi_compile _ _CASCADE_BLEND_SOFT _CASCADE_BLEND_DITHER
#pragma multi_compile _ _DIRECTIONAL_PCF3 _DIRECTIONAL_PCF5 _DIRECTIONAL_PCF7
#pragma vertex vert
#pragma hull hull
#pragma domain domain
#pragma geometry geo
#pragma fragment frag
#pragma target 4.6
ENDHLSL
}
Pass
{
Tags {
"LightMode" = "ShadowCaster"
}
ColorMask 0
HLSLPROGRAM
#pragma vertex vert
#pragma hull hull
#pragma domain domain
#pragma geometry geo
#pragma fragment fragShadow
#pragma shader_feature _ _SHADOWS_CLIP _SHADOWS_DITHER
#pragma multi_compile _ LOD_FADE_CROSSFADE
#pragma multi_compile_instancing
#pragma target 4.6
void fragShadow(geometryOutput i)
{
UNITY_SETUP_INSTANCE_ID(i);
InputConfig config = GetInputConfig(i.pos, i.uv);
ClipLOD(config.fragment, unity_LODFade.x);
#if defined(_SHADOWS_CLIP)
clip(base.a - GetCutoff(config));
#elif defined(_SHADOWS_DITHER)
float dither = InterleavedGradientNoise(input.positionCS_SS.xy, 0);
clip(base.a - dither);
#endif
}
ENDHLSL
}
}
FallBack "Diffuse"
}
GrassShader.hlsl
#ifndef GRASSSHADER_COMMON_INCLUDED
#define GRASSSHADER_COMMON_INCLUDED
#include "Surface.hlsl"
#include "Shadows.hlsl"
#include "Light.hlsl"
#include "BRDF.hlsl"
#include "GI.hlsl"
#include "Lighting.hlsl"
#include "LitInput.hlsl"
UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
UNITY_DEFINE_INSTANCED_PROP(float4, _BottomColor)
UNITY_DEFINE_INSTANCED_PROP(float4, _TopColor)
UNITY_DEFINE_INSTANCED_PROP(float, _BendRotationRandom)
UNITY_DEFINE_INSTANCED_PROP(float, _BladeHeight)
UNITY_DEFINE_INSTANCED_PROP(float, _BladeHeightRandom)
UNITY_DEFINE_INSTANCED_PROP(float, _BladeWidth)
UNITY_DEFINE_INSTANCED_PROP(float, _BladeWidthRandom)
UNITY_DEFINE_INSTANCED_PROP(float, _TessellationUniform)
UNITY_DEFINE_INSTANCED_PROP(float2, _WindFrequency)
UNITY_DEFINE_INSTANCED_PROP(float, _WindStrength)
UNITY_DEFINE_INSTANCED_PROP(float, _BladeForward)
UNITY_DEFINE_INSTANCED_PROP(float, _BladeCurve)
UNITY_DEFINE_INSTANCED_PROP(float, _TranslucentGain)
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)
#define BLADE_SEGMENTS 3
TEXTURE2D(_WindDistortionMap);
SAMPLER(sampler_WindDistortionMap);
float4 _WindDistortionMap_TexelSize;
float rand(float3 co)
{
return frac(sin(dot(co.xyz, float3(12.9898, 78.233, 53.539))) * 43758.5453);
}
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
);
}
struct vertexInput
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};
struct vertexOutput
{
float4 vertex : SV_POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};
struct geometryOutput
{
float4 pos : SV_POSITION;
float3 posWS : VAR_POSITION;
float2 uv : TEXCOORD0;
float3 normalWS : VAR_NORMAL;
};
struct TessellationFactors
{
float edge[3] : SV_TessFactor;
float inside : SV_InsideTessFactor;
};
vertexInput vert(vertexInput v)
{
return v;
}
TessellationFactors patchConstantFunction(InputPatch<vertexInput, 3> patch)
{
TessellationFactors f;
f.edge[0] = _TessellationUniform;
f.edge[1] = _TessellationUniform;
f.edge[2] = _TessellationUniform;
f.inside = _TessellationUniform;
return f;
}
[domain("tri")]
[outputcontrolpoints(3)]
[outputtopology("triangle_cw")]
[partitioning("integer")]
[patchconstantfunc("patchConstantFunction")]
vertexInput hull(InputPatch<vertexInput, 3> patch, uint id : SV_OutputControlPointID)
{
return patch[id];
}
vertexOutput tessVert(vertexInput v)
{
vertexOutput o;
// Note that the vertex is NOT transformed to clip
// space here; this is done in the grass geometry shader.
o.vertex = v.vertex;
o.normal = v.normal;
o.tangent = v.tangent;
return o;
}
[domain("tri")]
vertexOutput domain(TessellationFactors factors, OutputPatch<vertexInput, 3> patch, float3 barycentricCoordinates : SV_DomainLocation)
{
vertexInput 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(vertex)
MY_DOMAIN_PROGRAM_INTERPOLATE(normal)
MY_DOMAIN_PROGRAM_INTERPOLATE(tangent)
return tessVert(v);
}
geometryOutput VertexOutput(float3 pos, float2 uv,float3 normal)
{
geometryOutput o;
o.pos = TransformObjectToHClip(pos);
o.posWS = TransformObjectToWorld(pos);
o.uv = uv;
o.normalWS = TransformObjectToWorldNormal(normal);
return o;
}
geometryOutput GenerateGrassVertex(float3 vertexPosition, float width, float height, float forward, float2 uv, float3x3 transformMatrix)
{
float3 tangentPoint = float3(width, forward, height);
float3 localPosition = vertexPosition + mul(transformMatrix, tangentPoint);
float3 tangentNormal = normalize(float3(0, -1, forward));
float3 localNormal = mul(transformMatrix, tangentNormal);
return VertexOutput(localPosition, uv, localNormal);
}
[maxvertexcount(BLADE_SEGMENTS * 2 + 1)]
void geo(triangle vertexOutput IN[3], inout TriangleStream<geometryOutput> triStream)
{
float3 pos = IN[0].vertex;
// Place in the geometry shader, below the line declaring float3 pos.
float3 vNormal = IN[0].normal;
float4 vTangent = IN[0].tangent;
float3 vBinormal = cross(vNormal, vTangent) * vTangent.w;
float height = (rand(pos.zyx) * 2 - 1) * _BladeHeightRandom + _BladeHeight;
float width = (rand(pos.xzy) * 2 - 1) * _BladeWidthRandom + _BladeWidth;
float2 uv = pos.xz * _WindDistortionMap_TexelSize.xy + _WindDistortionMap_TexelSize.zw + _WindFrequency * _TimeDelta;
float4 _WindD = SAMPLE_TEXTURE2D_LOD(_WindDistortionMap, sampler_WindDistortionMap, uv, 0);
float2 windSample = (_WindD.xy * 2 - 1) * _WindStrength;
float3 wind = normalize(float3(windSample.x, windSample.y, 0));
float3x3 windRotation = AngleAxis3x3(UNITY_PI * windSample, wind);
// Add below the lines declaring the three vectors.
float3x3 tangentToLocal = float3x3(
vTangent.x, vBinormal.x, vNormal.x,
vTangent.y, vBinormal.y, vNormal.y,
vTangent.z, vBinormal.z, vNormal.z
);
float3x3 facingRotationMatrix = AngleAxis3x3(rand(pos) * UNITY_TWO_PI, float3(0, 0, 1));
float3x3 bendRotationMatrix = AngleAxis3x3(rand(pos.zzx) * _BendRotationRandom * UNITY_PI * 0.5, float3(-1, 0, 0));
float3x3 transformationMatrix = mul(mul(mul(tangentToLocal, windRotation), facingRotationMatrix), bendRotationMatrix);
float3x3 transformationMatrixFacing = mul(tangentToLocal, facingRotationMatrix);
float forward = rand(pos.yyz) * _BladeForward;
for (int i = 0; i < BLADE_SEGMENTS; i++)
{
float t = i / (float)BLADE_SEGMENTS;
float segmentHeight = height * t;
float segmentWidth = width * (1 - t);
float3x3 transformMatrix = i == 0 ? transformationMatrixFacing : transformationMatrix;
float segmentForward = pow(t, _BladeCurve) * forward;
triStream.Append(GenerateGrassVertex(pos, segmentWidth, segmentHeight, segmentForward, float2(0, t), transformMatrix));
triStream.Append(GenerateGrassVertex(pos, -segmentWidth, segmentHeight, segmentForward, float2(1, t), transformMatrix));
}
triStream.Append(GenerateGrassVertex(pos, 0, height, forward, float2(0.5, 1), transformationMatrix));
}
float3 GetGLighting(Surface surfaceWS, BRDF brdf, GI gi)
{
ShadowData shadowData = GetShadowData(surfaceWS);
shadowData.shadowMask = gi.shadowMask;
float3 color = IndirectBRDF(surfaceWS, brdf, gi.diffuse, gi.specular);
for (int i = 0; i < GetDirectionalLightCount(); i++) {
Light light = GetDirectionalLight(i, surfaceWS, shadowData);
if (RenderingLayersOverlap(surfaceWS, light)) {
float NdotL = saturate(saturate(dot(surfaceWS.normal, light.direction)) + _TranslucentGain) * light.attenuation;
color += NdotL * light.color;
}
}
#if defined(_LIGHTS_PER_OBJECT)
for (int j = 0; j < min(unity_LightData.y, 8); j++) {
int lightIndex = unity_LightIndices[(uint)j / 4][(uint)j % 4];
Light light = GetOtherLight(lightIndex, surfaceWS, shadowData);
if (RenderingLayersOverlap(surfaceWS, light)) {
float NdotL = saturate(saturate(dot(surfaceWS.normal, light.direction)) + _TranslucentGain) * light.attenuation;
color += NdotL * light.color;
}
}
#else
for (int j = 0; j < GetOtherLightCount(); j++) {
Light light = GetOtherLight(j, surfaceWS, shadowData);
if (RenderingLayersOverlap(surfaceWS, light)) {
float NdotL = saturate(saturate(dot(surfaceWS.normal, light.direction)) + _TranslucentGain) * light.attenuation;
color += NdotL * light.color;
}
}
#endif
return color;
}
float4 frag(geometryOutput i, float facing : VFACE) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(i);
InputConfig config = GetInputConfig(i.pos, i.uv);
ClipLOD(config.fragment, unity_LODFade.x);
float4 base = lerp(_BottomColor, _TopColor, i.uv.y);
float3 normal = facing > 0 ? i.normalWS : -i.normalWS;
Surface surface;
surface.position = i.posWS;
surface.normal = normalize(normal);
surface.interpolatedNormal = surface.normal;
surface.viewDirection = normalize(_WorldSpaceCameraPos - i.posWS);
surface.depth = -TransformWorldToView(i.posWS).z;
surface.color = base.rgb;
surface.alpha = base.a;
surface.metallic = 0;
surface.occlusion = 1;
surface.smoothness = 0;
surface.fresnelStrength =1;
surface.dither = 1;
surface.renderingLayerMask = asuint(unity_RenderingLayer.x);
BRDF brdf = GetBRDF(surface);
GI gi = GetGI(GI_FRAGMENT_DATA(i), surface, brdf);
float4 lightIntensity = float4(GetGLighting(surface, brdf, gi),1);
float4 col = lerp(_BottomColor, _TopColor * lightIntensity, i.uv.y);
return col;
}
#endif
735





