动态草地的Shaderlab实现方法

最近游戏《对马岛之魂》发售了。
其中的草地特动效十分先进:
在这里插入图片描述
(图片来自网络)
于是我也萌生了写一个动态草地的shader

草模型

现如今的许多手游甚至端游的草地考虑到硬件消耗,常常把草做成星形或者广告牌草地
在这里插入图片描述
(图片来自网络)
但是这样的草地很容易露出破绽,效果也不是特别好,于是这里我让每根草都拥有单独的模型。
在这里插入图片描述
实际上更好的方法是做多段的长方形,然后使用mask贴图指定草地形状,这样可以使草地形状更加圆润。
完成单个草模型后视情况调整、套用这篇文章中的Maya脚本制作出草丛簇。完成模型的制作。

草地动效实现

草地的动态效果初步分为两个部分:
1、采样一个白噪音贴图通过偏移UV的方式实现草地顶点的随机抖动
2、根据顶点位置实现草地的波浪

随机抖动

如上面所说,我们先搞一个四通道的白噪声贴图(RGBA通道均由白噪声随机生成,实际上我们只会任意使用其中两个通道,甚至一个)然后用顶点位置+_Time作为UV。_WindPow为强度控制属性

float2 samplePos = worldPos.xz / _WindPow + _Time.x;
fixed4 noiseSample = tex2Dlod(_Noise,float4(samplePos,0,0));

顶点x、y轴的位移函数为

worldPos.x +=_WindPow *noiseSample.x*v.uv.y;
worldPos.z +=_WindPow *noiseSample.y*v.uv.y;

我们用两个采样出的噪声分别叠加进入x,y轴中,乘上uv.y是为了
对不同高度的顶点区别处理,让草尖抖动大于草根。
由于抖动幅度小,这里暂时不处理顶点y轴。减少运算。

草地波浪

草地的波浪做了两个版本:
一个版本是使用正弦函数模拟草地波浪;
第二个版本采样一个波浪灰度图模拟草地波浪;

1、直接使用正弦函数模拟波浪
首先,引入一个_WindDir属性,用于表示风方向,范围在0~2π(6.28)中,然后转为方向

//从方向转为xy值
half windX = cos(_WindDir);
half windY = sin(_WindDir);

新的顶点位移函数为:

 //下面是无noise波浪方案
worldPos.x +=noisewindpow*noiseSample.x*_WindPow*v.uv.y/6
	 +(sin(6*_Time.y*_WindPow+(windX*worldPos.y+windY*worldPos.z))+1.3)*0.3f*_WindPow*windX*v.uv.y;
worldPos.z +=noisewindpow*noiseSample.y*_WindPow*v.uv.y/6
	 +(sin(6*_Time.y*_WindPow+(windX*worldPos.y+windY*worldPos.z))+1.3)*0.3f*_WindPow*windY*v.uv.y;
o.col = (sin(6*_Time.y*_WindPow+windX*worldPos.x+windY*worldPos.z)+1.0)*0.2f;
worldPos.y -= o.col*v.uv.y;

波浪函数增加在之前的随机飘动函数后(其中所有的数字都是可调的)
在sin函数内6*_Time.y*_WindPow影响波浪速度,windX*worldPos.y+windY*worldPos.z决定波浪方向。
sin函数外所有数值都只影响波浪强度和范围。
由于波浪的抖动幅度大,必须要考虑草的高度。这里也采用正弦函数模拟。方法与上面类似。这里同时为片面着色器传入了数据,可以为浪峰和浪谷增加色差。
下图是效果展示:
在这里插入图片描述
这样的做的波浪一旦处理大范围草地就会显得重复单调。所以引入使用一张灰度图处理波浪的办法。

2、采样灰度图模拟草地波浪
首先对灰度图进行采样:

float2 sampleNoise = worldPos.xz/50-float2(windX,windY)*(_Time.x/0.5+0.7)*_WindPow*4;
fixed noisewindpow = tex2Dlod(_WindNoise,float4(sampleNoise,0,0));
noisewindpow += _WindPow;

sampleNoise中的_Time需要乘上之前算出的windXwindY以使采样偏移和风向相同。
采样出的值作为新的风强度来影响顶点。

 //随机飘动+定向波浪(噪声纹理版本)
 worldPos.x +=noisewindpow*noiseSample.x*v.uv.y/6
     +windX*(noisewindpow+1)*v.uv.y/5;
 worldPos.z +=noisewindpow*noiseSample.y*v.uv.y/6
     +windY*(noisewindpow+1)*v.uv.y/5;
 //大幅度位移过程中的高度变换
 o.col = noisewindpow*0.5f;
 worldPos.y -= o.col*v.uv.y;

方法与上一个波浪相似。甚至更加简单。
最后就是颜色、阴影接受等功能的实现,这里就不多说了。
在这里插入图片描述
方法需要改进的地方:
1、首先是之前提到的可以采用mask贴图绘制草
2、草在随机抖动过程中会变得扭曲,也许可以用法线对顶点移动方向进行一定的限制。

以下是shader源码:

Shader "sence/Grass"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color("Color",Color) = (1,1,1,1)
        _Noise ("Noise",2D) = "white"{}
        //风
        _WindNoise("WindNoise",2D) = "white"{}
        _WindDir("GrassDirection",Range(0,6.28)) = 0
        _WindPow("WindPower",Range(0,1)) = 1
    }
    SubShader
    {
        cull off
        Tags { "RenderType"="Opaque" "ForceNoShadowCasting" = "True" "IgnoreProjector" = "True"}
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase
            // make fog work
            #pragma multi_compile_fog
            #include "AutoLight.cginc"
            #include "Lighting.cginc"
            #include "UnityCG.cginc"


            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 pos : SV_POSITION;
                fixed4 col :COLOR;
                UNITY_FOG_COORDS(1)
                SHADOW_COORDS(2)
            };

            sampler2D _MainTex;
            sampler2D _Noise;
            sampler2D_float _WindNoise;
            float4 _MainTex_ST;
            half _WindDir;
            half _WindPow;
            fixed4 _Color;

            v2f vert (appdata v)
            {
                v2f o;

                float4 worldPos = mul(unity_ObjectToWorld,v.vertex);
                float3 worldnormal = UnityObjectToWorldNormal(v.normal);
                //从方向转为xy值
                half windX = cos(_WindDir);
                half windY = sin(_WindDir);
                //噪声采样
                float2 samplePos = worldPos.xz / _WindPow + _Time.x;
                fixed4 noiseSample = tex2Dlod(_Noise,float4(samplePos,0,0));

                float2 sampleNoise = worldPos.xz/50-float2(windX,windY)*(_Time.x/0.5+0.7)*_WindPow*4;
                fixed noisewindpow = tex2Dlod(_WindNoise,float4(sampleNoise,0,0));
                noisewindpow += _WindPow;

                //随机飘动+定向波浪(噪声纹理版本)
                worldPos.x +=noisewindpow*noiseSample.x*v.uv.y/6
                    +windX*(noisewindpow+1)*v.uv.y/5;
                worldPos.z +=noisewindpow*noiseSample.y*v.uv.y/6
                    +windY*(noisewindpow+1)*v.uv.y/5;
                //大幅度位移过程中的高度变换
                o.col = noisewindpow*0.8f;
                worldPos.y -= o.col*v.uv.y;

                //下面是无noise波浪方案
      //          worldPos.x +=noisewindpow*noiseSample.x*_WindPow*v.uv.y/6
      //              +(sin(6*_Time.y*_WindPow+(windX*worldPos.y+windY*worldPos.z))+1.3)*0.3f*_WindPow*windX*v.uv.y;
      //          worldPos.z +=noisewindpow*noiseSample.y*_WindPow*v.uv.y/6
      //              +(sin(6*_Time.y*_WindPow+(windX*worldPos.y+windY*worldPos.z))+1.3)*0.3f*_WindPow*windY*v.uv.y;

      //          o.col = (sin(6*_Time.y*_WindPow+windX*worldPos.x+windY*worldPos.z)+1.0)*0.2f;
      //          worldPos.y -= o.col*v.uv.y;

                o.pos = mul(UNITY_MATRIX_VP,worldPos);

                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.pos);
                TRANSFER_SHADOW(o)
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                fixed4 shadow = SHADOW_ATTENUATION(i);
                col = float4(col.rgb,1)-i.col/2;
                col *=shadow*_Color/*+_LightColor0.a/3*/;
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值