UnityShader-基于网格顶点的草丛
发现了一篇写草丛的文章,自己跟着写了遍,并添加了自定义的形状和密度。
思路:
1、利用几何着色器,将每个顶点转为一个叶片网格
2、利用曲面细分生成新顶点控制草丛密度
3、贴图加clip决定叶片形状
4、利用旋转矩阵完成动态和随机效果
shader文件
Shader "Custom/GrassShaderWithTexture"
{
Properties
{
[Header(Color)]
[Space]
_TopCol("topColor",Color)=(1,1,1,1)
_BottomCol("bottomColor",Color)=(1,1,1,1)
_TranslucentGain("表面颜色权重", Range(0,1)) = 0.5
[Header(Density)]
[Space]
_TessellationUniform ("基础密度", Range(0, 6)) = 1
_TextureStrength("纹理强度",Range(0,6))=1
_DensityTexture("密度纹理(A)",2D)="black"{}
[Header(Shape)]
[Space]
[Toggle(_USE_GRASS_TEXTURE)]_UseTexture("使用纹理形状",Float)=0
[NoScaleOffset]_GrassTexture("草叶形状(A)",2D)="white"{}
_BladeWidthBase("基础宽度",Float)=0.5
_BladeWidthRandom("宽度扰乱",Float)=0.5
_BladeHeightBase("基础高度",Float)=0.5
_BladeHeightRandom("高度扰乱",Float)=0.5
[Header(Wind)]
[Space]
_WindMap("风向贴图",2D)="white"{}
_WindSpeed("风速(XY)",Vector)=(0.05,0.05,0,0)
_WindStrength("风力",Float)=1
[Header(Bend)]
[Space]
_BendStrength("bend Strength",Range(0,1))=0.4
_BladeForward("Blade Forward Amount", Float) = 0.38
_BladeCurve("Blade Curvature Amount", Range(1, 4)) = 2
}
CGINCLUDE
#include "UnityCG.cginc"
#include "AutoLight.cginc"
#include "Lighting.cginc"
#include "Assets/Shaders/GrassTessellationWithTexture.cginc"
#pragma shader_feature _USE_GRASS_TEXTURE
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 gOut{
float4 pos:SV_POSITION;
float2 uv:TEXCOORD0;
#if UNITY_PASS_FORWARDBASE
float3 normal : NORMAL;
unityShadowCoord4 _ShadowCoord : TEXCOORD1;
#endif
};
gOut vertexOutput(float3 pos,float2 uv,float3 normal){
gOut o;
o.pos=UnityObjectToClipPos(pos);
o.uv=uv;
#if UNITY_PASS_FORWARDBASE
o._ShadowCoord = ComputeScreenPos(o.pos);
o.normal = UnityObjectToWorldNormal(normal);
#endif
#if UNITY_PASS_SHADOWCASTER
o.pos = UnityApplyLinearShadowBias(o.pos);
#endif
return o;
}
gOut GenerateGrassPoint(float3 pos,float width,float forward,float height,float2 uv,float3x3 transform){
//width height构成平面,forward构成3维的弯曲
float3 tangentPoint=float3(width,forward,height);
float3 tangentNormal = normalize(float3(0, -1, forward));
float3 localNormal = mul(transform, tangentNormal);
return vertexOutput(pos+mul(transform,tangentPoint),uv,localNormal);
}
float _BendStrength;
float _BladeWidthBase,_BladeWidthRandom,_BladeHeightBase,_BladeHeightRandom;
sampler2D _WindMap;
float4 _WindMap_ST;
float2 _WindSpeed;
float _WindStrength;
float _BladeForward,_BladeCurve;
#ifdef _USE_GRASS_TEXTURE
sampler2D _GrassTexture;
#endif
#define BLADE_SEGMENTS 3
#ifdef _USE_GRASS_TEXTURE
[maxvertexcount(BLADE_SEGMENTS*2+2)]
#else
[maxvertexcount(BLADE_SEGMENTS*2+1)]
#endif
void geo(point vOut i[1],inout TriangleStream<gOut> stream){
float3 pos=i[0].pos;
float3 vNormal=i[0].normal;
float4 vTangent=i[0].tangent;
float3 vBinormal=cross(vNormal,vTangent.xyz)*vTangent.w;
//风向选择矩阵
float2 windUV=pos.xz*_WindMap_ST.xy+_WindMap_ST.zw+_WindSpeed*_Time.y;
float2 windSample=(tex2Dlod(_WindMap,float4(windUV,0,0)).xy*2-1)*_WindStrength;
float3 windAxis=normalize(float3(windSample.x,windSample.y,0));
float3x3 WindRoationMatrix=AngleAxis3x3(windSample.y*UNITY_PI,windAxis);
//随机面向矩阵
float3x3 RandomFaceMatrix=AngleAxis3x3(rand(pos)*UNITY_TWO_PI,float3(0,0,1));
//随机弯曲矩阵
float3x3 RandomBendMatrix=AngleAxis3x3(rand(pos.xzz)*UNITY_PI*0.5*_BendStrength,float3(-1,0,0));
//切线空间转对象空间矩阵
float3x3 tangentToObject=float3x3(
vTangent.x, vBinormal.x, vNormal.x,
vTangent.y, vBinormal.y, vNormal.y,
vTangent.z, vBinormal.z, vNormal.z
);
float3x3 transformMatrix=mul(mul(mul(tangentToObject,WindRoationMatrix),RandomFaceMatrix),RandomBendMatrix);
float3x3 transformBottomMatrix = mul(tangentToObject, RandomFaceMatrix);//底部两点应当基于xz平面
float height=rand(pos.xxz)*_BladeHeightRandom+_BladeHeightBase;
float width=rand(pos.xyy)*_BladeWidthRandom+_BladeWidthBase;
float forward=rand(pos.zxy)*_BladeForward;
for(int i=0;i<BLADE_SEGMENTS;i++){
float3x3 transform=i==0?transformBottomMatrix:transformMatrix;
float t=i/(float)BLADE_SEGMENTS;//越往上,t值越大 0->1
float segmentHeight=height*t;
float segmentforward=pow(t,_BladeCurve)*forward;
#ifdef _USE_GRASS_TEXTURE
float segmentWidth=width;
#else
float segmentWidth=width*(1.0-t);
#endif
stream.Append(GenerateGrassPoint(pos,segmentWidth,segmentforward,segmentHeight,float2(0,t),transform));
stream.Append(GenerateGrassPoint(pos,-segmentWidth,segmentforward,segmentHeight,float2(1,t),transform));
}
#ifdef _USE_GRASS_TEXTURE
stream.Append(GenerateGrassPoint(pos,width,forward,height,float2(0,1),transformMatrix));
stream.Append(GenerateGrassPoint(pos,-width,forward,height,float2(1,1),transformMatrix));
#else
stream.Append(GenerateGrassPoint(pos,0,forward,height,float2(0.5,1),transformMatrix));
#endif
}
ENDCG
SubShader
{
Pass{
Tags{"LightMode"="ForwardBase"}
Cull Off
CGPROGRAM
#pragma target 4.6
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma hull hull
#pragma domain domain
#pragma geometry geo
#pragma fragment frag
float4 _BottomCol,_TopCol;
float _TranslucentGain;
float4 frag(gOut i,fixed facing : VFACE):SV_Target{
float3 normal = facing >0 ? i.normal : -i.normal;
float shadow = SHADOW_ATTENUATION(i);
float NdotL = saturate(saturate(dot(normal, _WorldSpaceLightPos0)) + _TranslucentGain) * shadow;
float3 ambient = ShadeSH9(float4(normal, 1));
float4 lightIntensity = NdotL * _LightColor0 + float4(ambient, 1);
#ifdef _USE_GRASS_TEXTURE
float4 textureGrass=tex2D(_GrassTexture,i.uv);
clip(textureGrass.a-0.01);
#endif
float4 col = lerp(_BottomCol, _TopCol * lightIntensity, i.uv.y);
return col;
}
ENDCG
}
Pass{
Tags{"LightMode"="ShadowCaster"}
CGPROGRAM
#pragma vertex vert
#pragma geometry geo
#pragma fragment frag
#pragma hull hull
#pragma domain domain
#pragma target 4.6
#pragma multi_compile_shadowcaster
float4 frag(gOut i) : SV_Target
{
#ifdef _USE_GRASS_TEXTURE
float4 textureGrass=tex2D(_GrassTexture,i.uv);
clip(textureGrass.a-0.01);
#endif
return 0;
}
ENDCG
}
}
FallBack "Diffuse"
}
GrassTessellationWithTexture.cginc
#ifndef GRASS_TESSELLATION_TEXTURE_INCLUDE
#define GRASS_TESSELLATION_TEXTURE_INCLUDE
struct vIn{
float4 vertex:POSITION;
float3 normal:NORMAL;
float4 tangent:TANGENT;
float2 texcoord:TEXCOORD0;
};
struct vOut{
float4 pos:SV_POSITION;
float3 normal:NORMAL;
float4 tangent:TANGENT;
};
struct TessellationFactors
{
float edge[3]:SV_TessFactor;
float inside:SV_InsideTessFactor;
};
//--------------------vertex------------------------
vIn vert(vIn v){
return v;
}
//---------------------hull-----------------------
float _TessellationUniform,_TextureStrength;
sampler2D _DensityTexture;
float4 _DensityTexture_ST;
TessellationFactors patchConstantFunction(InputPatch<vIn,3>patch){
float2 uv=(patch[0].texcoord+patch[1].texcoord+patch[2].texcoord)*0.333;
uv=uv*_DensityTexture_ST.xy+_DensityTexture_ST.zw;
float addDensity=tex2Dlod(_DensityTexture,float4(uv,0,0)).a;
addDensity*=_TextureStrength;
TessellationFactors f;
f.edge[0] = _TessellationUniform+addDensity;
f.edge[1] = _TessellationUniform+addDensity;
f.edge[2] = _TessellationUniform+addDensity;
f.inside = _TessellationUniform+addDensity;
return f;
}
[UNITY_domain("tri")]
[UNITY_outputcontrolpoints(3)]
[UNITY_outputtopology("triangle_cw")]
[UNITY_partitioning("integer")]
[UNITY_patchconstantfunc("patchConstantFunction")]
vIn hull(InputPatch<vIn,3> patch,uint id:SV_OutputControlPointID)
{
return patch[id];
}
//GPU进行曲面细分操作 in->out
//-----------------------domain-------------------
vOut tessVert(vIn v){
vOut o;
o.pos=v.vertex;
o.normal=v.normal;
o.tangent=v.tangent;
return o;
}
#define MY_DOMAIN_PROGRAM_INTERPOLATE(fieldName) v.fieldName = \
patch[0].fieldName * barycentricCoordinates.x + \
patch[1].fieldName * barycentricCoordinates.y + \
patch[2].fieldName * barycentricCoordinates.z;
[UNITY_domain("tri")]
vOut domain(TessellationFactors factors,OutputPatch<vIn,3> patch,float3 barycentricCoordinates : SV_DomainLocation){
vIn v;
MY_DOMAIN_PROGRAM_INTERPOLATE(vertex)
MY_DOMAIN_PROGRAM_INTERPOLATE(normal)
MY_DOMAIN_PROGRAM_INTERPOLATE(tangent)
return tessVert(v);
}
#endif