学习草地效果的shader制作。参考文章-草地shader
思路:
通过几何着色器,使用网格顶点生成面片来模拟草,使用曲面细分着色器改变顶点量来控制草密度。
添加阴影的投射和接收,光的接收。
给每个面片草添加随机属性。
使用流动纹理模拟风吹效果。
添加交互效果。(未完成)
知识点:
- 几何着色器的使用
- 曲面细分着色器的使用
- 在使用几何着色器后,阴影的使用方式(与顶点片元着色器的使用区别)
- 切线空间TBN的相关知识和计算
- 旋转矩阵的构造
代码:
跟着教程写的代码存在几个问题:
- 草自身接收阴影的问题 添加宏之后并没有效果,还存在横线
- 法线朝向貌似存在问题
- 两个pass的tags标签定义
shader:
Shader "Unlit/GrassGeometryShader"
{
//草效果
//1.模型每个顶点生成三角面片(面片的坐标需要在切线空间TBN矩阵构造)_几何着色器
//2.加颜色
//3.加随机变量(朝向(z轴),弯曲(x轴),尺寸)_矩阵构造
//4.曲面细分控制密度(动态细分性能优化)_曲面细分着色器
//5.草晃动(扭曲纹理,草底部顶点不动,顶部移动)_流动纹理采样
//6.单个草细致化(将草由一个三角面细化为多个三角面组成,使其可弯曲)
//7.对分段后的草添加弯曲
//8.投射阴影(增加投射阴影的pass)
//9.接收阴影(和正常阴影三剑客区别,结构体属性unityShadowCoord4,)
//10.接收光照
Properties
{
_MainTex ("Texture", 2D) = "white" {
}
_GrassTopColor("Top Color",COLOR) = (1,1,1,1)
_GrassBottomColor("Botton Color",COLOR) = (1,1,1,1)
_BendDegree("Grass Bend Degree",Range(0,1)) = 1
_GrassWidth("Grass Width",Range(0,1))=0.2
_GrassHeight("Grass Height",Range(0,2)) = 1
_TessellationUniform("TessellationUniform",Range(1,64)) = 1
//流动纹理
_FlowMap("Flow Map",2D)="black"{
}
_WindScale("Wind Scale",Range(0,0.1))=0.01
_WindStrength("Wind Strength",Range(0,5))=1
//弯曲
_BladeForward("Blade Forward Amount", Range(-1,1)) = 0.38
_BladeCurve("Blade Curvature Amount", Range(1, 4)) = 2
}
SubShader
{
CGINCLUDE
#include "UnityCG.cginc"
//#include "Lighting.cginc"
#include "Autolight.cginc"
#define PI 3.1415926
//草叶的段数
#define BLADE_SEGMENTS 3
struct v2g
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};
struct g2f{
float4 vertex:SV_POSITION;
float2 uv:TEXCOORD0;
unityShadowCoord4 _ShadowCoord : TEXCOORD1;
float3 normal:NORMAL;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _GrassTopColor;
fixed4 _GrassBottomColor;
float _BendDegree;
float _GrassWidth;
float _GrassHeight;
float _TessellationUniform;
sampler2D _FlowMap;
float _WindStrength;
float _WindScale;
float _BladeCurve;
float _BladeForward;
g2f CreateTriangle(float3 pos,float2 uv,float3 normal){
g2f o;
o.vertex = UnityObjectToClipPos(pos);
o.uv = uv;
o._ShadowCoord = ComputeScreenPos(o.vertex);
o.normal = UnityObjectToWorldNormal(normal);
#if UNITY_PASS_SHADOWCASTER
o.vertex = UnityApplyLinearShadowBias(o.vertex);
#endif
return o;
}
float rand(float3 z){
return frac(sin(dot(z.xyz,float3(12.9898,78.233,53.539))) * 43758.5453);
}
float3x3 RotMatrixByAxisAngle(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
);
}
v2g vert (appdata_tan v)
{
v2g o;
o.vertex = v.vertex;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.normal = v.normal;
o.tangent = v.tangent;
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
///曲面细分部分
//有些硬件不支持曲面细分着色器,定义了该宏就能够在不支持的硬件上不会变粉,也不会报错
#ifdef UNITY_CAN_COMPILE_TESSELLATION
//顶点着色器结构的定义
struct TessVertex{
float4 vertex : INTERNALTESSPOS;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 uv : TEXCOORD0;
};
struct OutputPatchConstant {
//不同的图元,该结构会有所不同
//该部分用于Hull Shader里面
//定义了patch的属性
//Tessellation Factor和Inner Tessellation Factor
float edge[3] : SV_TESSFACTOR;
float inside : SV_INSIDETESSFACTOR;
};
TessVertex tessvert (appdata_tan v){
//顶点着色器函数
TessVertex o;
o.vertex = v.vertex;
o.normal = v.normal;
o.tangent = v.tangent;
o.uv = v.texcoord;
return o;
}
OutputPatchConstant hsconst (InputPatch<TessVertex,3> patch){
//定义曲面细分的参数
OutputPatchConstant o;
o.edge[0] = _TessellationUniform;
o.edge[1] = _TessellationUniform;
o.edge[2] = _TessellationUniform;
o.inside = _TessellationUniform;
return o;
}
[UNITY_domain("tri")]//确定图元,quad,triangle等
[UNITY_partitioning("fractional_odd")]//拆分edge的规则,equal_spacing,fractional_odd,fractional_even
[UNITY_outputtopology("triangle_cw")]
[UNITY_patchconstantfunc("hsconst")]//一个patch一共有三个点,但是这三个点都共用这个函数
[UNITY_outputcontrolpoints(3)] //不同的图元会对应不同的控制点
TessVertex hull (InputPatch<TessVertex,3> patch,uint id : SV_OutputControlPointID){
//定义hullshaderV函数
return patch[id];
}
[UNITY_domain("tri")]//同样需要定义图元
v2g ds (OutputPatchConstant tessFactors, const OutputPatch<TessVertex,3>patch,float3 bary :SV_DOMAINLOCATION)
//bary:重心坐标
{
appdata_tan v;
v.vertex = patch[0].vertex*bary.x + patch[1].vertex*bary.y + patch[2].vertex*bary.z;
v.tangent = patch[0].tangent*bary.x + patch[1].tangent*bary.y + patch[2].tangent*bary.z;
v.normal = patch[0].normal*bary.x + patch[1].normal*bary.y + patch[2].normal*bary.z;
v.texcoord.xy = patch[0].uv*bary.x + patch[1].uv*bary.y + patch[2].uv*bary.z;
v2g o = vert (v);
return o;
}
#endif
///
//生成三角面片
[maxvertexcount(BLADE_SEGMENTS*2 +1 )]
void geom(point v2g IN[1],inout TriangleStream<g2f> triStream){
float4 centerPos = IN[0].vertex;
float2 uv = IN[0].uv;
float3 pos;
//构造切线空间
float3 binormal = cross(IN[0].normal,IN[0].tangent) * IN[0].tangent.w;
float3x3 TBN = float3x3(
IN[0].tangent.x,binormal.x,IN[0].normal.x,
IN[0].tangent.y,binormal.y,IN[0].normal.y,
IN[0].tangent.z,binormal.z,IN[0].normal.z
);
//获取随机数
float randangle = rand(centerPos) * PI;
//z轴旋转矩阵和切线空间合并(朝向)(切线空间内z轴是朝上的)
float3x3 finalMatrix = mul (TBN , RotMatrixByAxisAngle(randangle,float3(0,0,1)));//进行一次旋转
//x轴旋转(草的弯曲程度)
randangle = rand(centerPos.xzy) * _BendDegree * PI * 0.5;//0-90度内弯曲,_BendDegree弯曲程度
float3x3 bendMatrix = RotMatrixByAxisAngle(randangle,float3(1,0,0));
finalMatrix = mul(finalMatrix,bendMatrix);
风场效果
//构造uv坐标
float2 moveUV = centerPos.xz * _Time.y *_WindScale;
//Wind采样
float2 windSample = (tex2Dlod(_FlowMap, float4(moveUV, 0, 0)).xy * 2 - 1) * _WindStrength;
float3 wind = normalize(float3(windSample.x, windSample.y, 0));//Wind Vector
//Wind旋转矩阵
float3x3 windRotation = RotMatrixByAxisAngle(UNITY_PI * windSample, wind);
float3x3 finalMatrixWind = mul(finalMatrix,windRotation);
//尺寸
float wid = _GrassWidth + _GrassWidth * rand(centerPos.xxz);
float hei = _GrassHeight + _GrassHeight * rand(centerPos.zzy);
float forward = rand(centerPos.zyy)*_BladeForward;//弯曲
//法线
float3 normaldir = float3(0,-1,forward);
float3 normaldirTBN = mul(TBN,normaldir);
//草叶分段后的尺寸计算(注意顺序,必须按照顺序来写,从底到高,否则虽然看起来一致,带面片是错的。)
//最底部的两个顶点 (不需要弯曲)
pos = mul(finalMatrix,float3(-wid,0,0));
triStream.Append(CreateTriangle(centerPos + pos,float2(0,0),normaldirTBN));
pos = mul(finalMatrix,float3(wid,0,0));
triStream.Append(CreateTriangle(centerPos + pos,float2(1,0),normaldirTBN));
//中间顶点
for (int i = 1; i < BLADE_SEGMENTS; i++)
{
float t = i / (float)BLADE_SEGMENTS;
float segmentHeight = hei * t;
float segmentWidth = wid * (1 - t);
float segmentForward = pow(t,_BladeCurve)*forward;
pos = mul(finalMatrixWind,float3(-segmentWidth,segmentForward,segmentHeight));
triStream.Append(CreateTriangle(centerPos + pos,float2(0,t),normaldirTBN));
pos = mul(finalMatrixWind,float3(segmentWidth,segmentForward,segmentHeight));
triStream.Append(CreateTriangle(centerPos + pos,float2(1,t),normaldirTBN));
}
//最顶部
pos = mul(finalMatrixWind,float3(0,forward,hei));
triStream.Append(CreateTriangle(centerPos + pos,float2(0.5,1),normaldirTBN));
}
ENDCG
Pass
{
Tags{
"LightMode"="ForwardBase"
"RenderType"="Opaque"
}
Cull Off
CGPROGRAM
#pragma vertex tessvert
#pragma geometry geom
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#pragma hull hull
#pragma domain ds
#pragma multi_compile_fwdbase
#include "Lighting.cginc"
//fixed4 frag (g2f i) : SV_Target
//{
// float shadowatt = SHADOW_ATTENUATION(i);
// // sample the texture
// fixed4 col = tex2D(_MainTex, i.uv);
// // apply fog
// UNITY_APPLY_FOG(i.fogCoord, col);
// col = lerp(_GrassTopColor,_GrassBottomColor,1-i.uv.y)*shadowatt;
// return col;
//}
fixed4 frag(g2f i,fixed facing :VFACE):SV_Target{
//float3 normal = facing >0 ?i.normal:-i.normal;
//return float4(normal*0.5+0.5,1);
// 判断内外表面
float3 normal = facing > 0 ? i.normal : -i.normal;
// 获取阴影
float shadow = SHADOW_ATTENUATION(i);
//半罗伯特反射
float diffuse = (1 + dot(_WorldSpaceLightPos0, normal) )/ 2 * shadow;
//详看链接文章
float3 ambient = ShadeSH9(float4(normal, 1));
float4 lightIntensity = diffuse * _LightColor0 +float4(ambient, 1);
float4 col = lerp(_GrassBottomColor, _GrassTopColor * lightIntensity, i.uv.y);
return col;
//return SHADOW_ATTENUATION(i);
}
ENDCG
}
//投射阴影pass
Pass{
Tags{
"LightMode"="ShadowCaster"
}
CGPROGRAM
#pragma vertex tessvert
#pragma geometry geom
#pragma fragment frag
// make fog work
#pragma hull hull
#pragma domain ds
// #include "Lighting.cginc"
#pragma multi_compile_shadowcaster
float4 frag(g2f i):SV_Target{
SHADOW_CASTER_FRAGMENT(i);
}
ENDCG
}
}
}