几何向量:Gerstner

      感觉国庆到处跑比上班还忙,终于闲下来了,五天没写代码了怕手生,刚好无聊顺便写一下Gerstner计算。
      Gerstner是早期图形算法中模拟水面波浪的一种拟真算法,是基于正余弦波的一种组合叠加,具体详细的解释,首先上官方:
trochoidal_wave
wind_wave
wave
      那具体是怎么样的一个叠加形式呢?首先我们写一个正弦波的效果:
      网格构建:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExecuteInEditMode]
[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshFilter))]
public class WaveRectanglePanel : MonoBehaviour
{
    
    
    public int horizonCount = 50;
    public int verticalCount = 50;

    [Range(0.1f, 100f)]
    public float cellDistance = 1f;

    public bool isUpdate = false;

    private MeshRenderer meshRender;
    private MeshFilter meshFilter;

    private Mesh mesh;

    void Start()
    {
    
    
        meshRender = GetComponent<MeshRenderer>();
        meshFilter = GetComponent<MeshFilter>();
        mesh = new Mesh();
    }

    void Update()
    {
    
    
        if (isUpdate)
        {
    
    
            UpdateMesh();
            isUpdate = false;
        }
    }

    private void UpdateMesh()
    {
    
    
        if (mesh != null)
        {
    
    
            mesh.Clear();
        }
        Vector3[] vertices = new Vector3[(horizonCount + 1) * (verticalCount + 1)];
        Vector3[] normals = new Vector3[(horizonCount + 1) * (verticalCount + 1)];
        Vector2[] uvs = new Vector2[(horizonCount + 1) * (verticalCount + 1)];
        int[] triangles = new int[horizonCount * verticalCount * 6];
        int triindex = 0;
        for (int y = 0; y <= verticalCount; y++)
        {
    
    
            for (int x = 0; x <= horizonCount; x++)
            {
    
    
                int index = (horizonCount + 1) * y + x;
                vertices[index] = new Vector3(x * cellDistance, 0, -y * cellDistance);
                normals[index] = new Vector3(0, 1, 0);
                uvs[index] = new Vector2((float)x / (float)horizonCount, (float)y / (float)verticalCount);
                if (x < horizonCount && y < verticalCount)
                {
    
    
                    int topindex = x + y * (horizonCount + 1);
                    int bottomindex = x + (y + 1) * (horizonCount + 1);
                    triangles[triindex] = topindex;
                    triangles[triindex + 1] = topindex + 1;
                    triangles[triindex + 2] = bottomindex + 1;
                    triangles[triindex + 3] = topindex;
                    triangles[triindex + 4] = bottomindex + 1;
                    triangles[triindex + 5] = bottomindex;
                    triindex += 6;
                }
            }
        }
        mesh.vertices = vertices;
        mesh.normals = normals;
        mesh.triangles = triangles;
        mesh.uv = uvs;

        meshFilter.sharedMesh = mesh;
    }
}

      接下来实现sin wave效果

Shader "GerstnerWave/SinWaveShader"
{
    
    
    Properties
    {
    
    
        _MainTex ("Texture", 2D) = "white" {
    
    }
        _WaveSpeed("Wave Speed",Range(0.1,10)) = 1 
        _WavePower("Wave Power",Range(0.1,10)) = 1
        _WaveRange("Wave Range",Range(0.1,10)) = 1
    }
    SubShader
    {
    
    
        Tags {
    
     "RenderType"="Opaque" }
        LOD 100

        Pass
        {
    
    
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

            struct v2f
            {
    
    
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            float _WaveSpeed;
            float _WavePower;
            float _WaveRange;

            v2f vert (appdata v)
            {
    
    
                v2f o;
                //已x或z轴做sin入参系数控制波浪
                float s = sin(v.vertex.x*_WavePower+_Time.y*_WaveSpeed)*_WaveRange;
                v.vertex += float4(0,s,0,0);
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

      效果如下:
在这里插入图片描述
      首先我们创建y轴为0的水平平面网格,所以shader中通过网格顶点的x和y值进行正弦波入参计算就能得到波浪的效果。
      但现实中波浪波峰明显不是这种圆滑的,而是尖锐的波峰,如下图:
在这里插入图片描述
      官方数学示意图:
在这里插入图片描述      ps:上部位sin采样,下部为gerstner采样。
      同时我们看得出来gerstner采样就是在sin采样基础上叠加(或消减)了一个x(或z)轴的cos采样。我们可以在脑海中想一下,对比上下图形的黑色原点,看得出来原点在y轴上坐标没有变化,而在x(或z)轴上有cos的周期变化(sinx = cos(90-x))
在这里插入图片描述在这里插入图片描述
      那么为了验证我们的想法,实现一下就看得出来了。

Shader "GerstnerWave/GerstnerWaveShader"
{
    
    
    Properties
    {
    
    
        _MainTex ("Texture", 2D) = "white" {
    
    }
        _WaveRadius("Wave Circle Radius",Range(0.1,10)) = 1
        _WaveSpeed("Wave Speed",Range(0.1,10)) = 1
        _WaveRange("Wave Range",Range(0.1,10)) = 1
    }
    SubShader
    {
    
    
        Tags {
    
     "RenderType"="Opaque" }
        LOD 100

        Pass
        {
    
    
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

            struct v2f
            {
    
    
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            float _WaveRadius;
            float _WaveSpeed;
            float _WaveRange;

            v2f vert (appdata v)
            {
    
    
                v2f o;
                //angle入参
                float ang = v.vertex.x + _WaveSpeed*_Time.y;
                //y轴sin叠加
                float y = _WaveRange*sin(ang)*_WaveRadius;
                //x轴cos消减
                float x = _WaveRange*cos(ang)*_WaveRadius;
                v.vertex += float4(x,y,0,0);
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

      效果如下:
在这里插入图片描述      可以看得出来这样的水波就较为真实了。当然了,水波也不是一个固定x或z轴方向的,我们如果能增加一个二维(xz轴)的波动方向就好了,如下:
在这里插入图片描述
      可以看得出来这时候波峰因为朝向(dir)的影响,波动会对x和z轴造成运动,那么我们可以想象xoz平面顶点与dir的点积越大(即x’oz’与dir的夹角越小),则表明此vertex越接近波峰,则参与正余弦计算的入参越大,同时x和z波动横纵移动的分量也需要根据dir的分量x、y进行运算。

Shader "GerstnerWave/GerstnerWaveDirectionShader"
{
    
    
    Properties
    {
    
    
        _MainTex ("Texture", 2D) = "white" {
    
    }
        _WaveDirection("Wave Direction",vector) = (1,0,0,0)
        _WaveRadius("Wave Circle Radius",Range(0.1,10)) = 1
        _WaveSpeed("Wave Speed",Range(0.1,10)) = 1
        _WaveRange("Wave Range",Range(0.1,10)) = 1
    }
    SubShader
    {
    
    
        Tags {
    
     "RenderType"="Opaque" }
        LOD 100

        Pass
        {
    
    
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

            struct v2f
            {
    
    
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            float2 _WaveDirection;
            float _WaveRadius;
            float _WaveSpeed;
            float _WaveRange;

            v2f vert (appdata v)
            {
    
    
                v2f o;
                //直接输入单位化向量更好
                float2 dir = normalize(_WaveDirection);
                //dot(dir,v.vertex.xz)得到vertex建模空间xz二维向量与dir向量的点积(权重)
                //用来判断vertex的波峰权重(即正余弦函数入参)
                float ang = dot(dir,v.vertex.xz) + _WaveSpeed*_Time.y;
                //x方向的叠加和z方向的叠加需要根据dir分量进行计算
                float x = dir.x*_WaveRange*cos(ang)*_WaveRadius;
                float y = _WaveRange*sin(ang)*_WaveRadius;
                float z = dir.y*_WaveRange*cos(ang)*_WaveRadius;
                v.vertex += float4(x,y,z,0);
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

      效果如下:
在这里插入图片描述
      可以看得出实现了效果,接下来我们还得做一下多波叠加,水面确实是很多波浪叠加出的效果。

Shader "GerstnerWave/GerstnerMultiWaveShader"
{
    
    
     Properties
    {
    
    
        _MainTex ("Texture", 2D) = "white" {
    
    }
        _WaveDir1("Wave Direction 1",vector) = (1,0,0,0)
        _WaveDir2("Wave Direction 2",vector) = (1,0,0,0)
        _WaveDir3("Wave Direction 3",vector) = (1,0,0,0)
        _WaveRadius("Wave Circle Radius",Range(0.1,10)) = 1
        _WaveSpeed("Wave Speed",Range(0.1,10)) = 1
        _WaveRange("Wave Range",Range(0.1,10)) = 1
    }
    SubShader
    {
    
    
        Tags {
    
     "RenderType"="Opaque" }
        LOD 100

        Pass
        {
    
    
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

            struct v2f
            {
    
    
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            float2 _WaveDir1;
            float2 _WaveDir2;
            float2 _WaveDir3;
            float _WaveRadius;
            float _WaveSpeed;
            float _WaveRange;

            float4 getGerstnerWave(float4 vtx,float2 dir,float wavespd,float waverag,float waverad)
            {
    
    
                float ang = dot(dir,vtx.xz) + wavespd*_Time.y;
                float x = dir.x*waverag*cos(ang)*waverad;
                float y = waverag*sin(ang)*waverad;
                float z = dir.y*waverag*cos(ang)*waverad;
                return float4(x,y,z,0);
            }

            v2f vert (appdata v)
            {
    
    
                v2f o;
                float2 dir1 = normalize(_WaveDir1);
                float4 vtx1 = getGerstnerWave(v.vertex,dir1,_WaveSpeed,_WaveRange,_WaveRadius);
                float2 dir2 = normalize(_WaveDir2);
                float4 vtx2 = getGerstnerWave(v.vertex,dir2,_WaveSpeed,_WaveRange,_WaveRadius);
                float2 dir3 = normalize(_WaveDir3);
                float4 vtx3 = getGerstnerWave(v.vertex,dir3,_WaveSpeed,_WaveRange,_WaveRadius);
                v.vertex += (vtx1+vtx2+vtx3);
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

      效果如下:
在这里插入图片描述
      可以看得出来多波叠加功能是实现了,不过总觉得差点什么,那就是随机,因为我们除了direction是三个随机的,其他参数全是一样的,所以还得修改增加入参的设置功能。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GerstnerMultiRandomWave : MonoBehaviour
{
    
    
    [System.Serializable]
    public class WaveParams
    {
    
    
        public float WaveDirX;
        public float WaveDirY;
        public float WaveRadius;
        public float WaveSpeed;
        public float WaveRange;
    }

    public Material waveMat;

    public bool updateParam = false;

    public WaveParams[] waveParams;

    void Start()
    {
    
    

    }

    private void Update()
    {
    
    
        if (updateParam)
        {
    
    
            UpdateWaveParam();
            updateParam = false;
        }
    }

    private void UpdateWaveParam()
    {
    
    
        int count = waveParams.Length;
        waveMat.SetInt("_WaveCount", count);
        waveMat.SetFloatArray("_WaveParams", GetWaveParamFloats());
    }

    private List<float> GetWaveParamFloats()
    {
    
    
        List<float> floatlist = new List<float>();
        for (int i = 0; i < waveParams.Length; i++)
        {
    
    
            WaveParams param = waveParams[i];
            floatlist.Add(param.WaveDirX);
            floatlist.Add(param.WaveDirY);
            floatlist.Add(param.WaveRadius);
            floatlist.Add(param.WaveSpeed);
            floatlist.Add(param.WaveRange);
        }
        return floatlist;
    }
}

      shader修改:

Shader "GerstnerWave/GerstnerMultiParamWaveShader"
{
    
    
     Properties
    {
    
    
        _MainTex ("Texture", 2D) = "white" {
    
    }
    }
    SubShader
    {
    
    
        Tags {
    
     "RenderType"="Opaque" }
        LOD 100

        Pass
        {
    
    
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

            struct v2f
            {
    
    
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            const int _WaveCount;
            float _WaveParams[30];          //最多6个

            float4 getGerstnerWave(float4 vtx,float2 dir,float wavespd,float waverag,float waverad)
            {
    
    
                float ang = dot(dir,vtx.xz) + wavespd*_Time.y;
                float x = dir.x*waverag*cos(ang)*waverad;
                float y = waverag*sin(ang)*waverad;
                float z = dir.y*waverag*cos(ang)*waverad;
                return float4(x,y,z,0);
            }

            v2f vert (appdata v)
            {
    
    
                v2f o;
                for(int i=0;i<_WaveCount;i++)
                {
    
    
                    int index = i*5;
                    float2 dir = float2(_WaveParams[index],_WaveParams[index+1]);
                    float spd = _WaveParams[index+2];
                    float rag = _WaveParams[index+3];
                    float rad = _WaveParams[index+4];
                    float4 wvtx = getGerstnerWave(v.vertex,dir,spd,rag,rad);
                    v.vertex += wvtx;
                }
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

      效果如下:
在这里插入图片描述
      效果好不好就只能靠调整参数了,我就随便调了几个值。
      还没完,接下来我们还得处理着色方面的问题,那就是法向量的重计算,如果我们不重计算法向量,就会是这样:
在这里插入图片描述
      我添加了specular分量计算,发现高光也是一块板,如果我们增加normal向量的重计算,才能正常显示specular高光。
      那么怎么重计算呢?我们原本vertex的法向量是(0,1,0),然后vertex经过xyz三个轴向上波动(移动),就成了如下的情况:
在这里插入图片描述
      而此时新的法向量n‘比较简单的计算方法是通过切线和副切线来的叉积计算,因为切线和副切线还是挺好计算的,如下:
在这里插入图片描述
      我们可以理解为仿射坐标系tbn(tangent、bitangent、normal)经过旋转平移到达了t’b’n’,当然我们这里法向量不用计算平移,只考虑旋转就行了。所以我们通过z得到tangent、bitangent绕y轴在xz上的旋转后,再加上(0,y,0)向量得到tangent’、bitangent‘,通过叉积就能得到normal‘。
      当然也不必通过矩阵旋转,直接向量计算也可以。
      在当然,如果我们波动朝向使用的角度入参,那么矩阵旋转就更好。
      下面我们以“一次波动”为例,画出每次“波动”后tangent、bitangent变换到t’、bt‘的过程:
在这里插入图片描述
      想象xz平面上,tangent、bitangent通过旋转变换到t1、bt1,然后xyz三维空间中,加上(0,y,0)得到t’、bt‘,下面测试一下:

Shader "GerstnerWave/GerstnerMultiParamWaveShader"
{
    
    
    Properties
    {
    
    
        _MainColor("Main Color",Color) = (1,1,1,1)
        _MainTex ("Texture", 2D) = "white" {
    
    }
        [Toggle]_IsCalNorm("Calculate Normal?",int) = 0
        _LightFactor("Lighting Factor",Color) = (1,1,1,1)
        _DiffuseFactor("Diffuse Factor",Color) = (1,1,1,1)
        _SpecFactor("Specular Factor",Color) = (1,1,1,1)
        _SpecGloss("Specular Gloss",Range(0,500)) = 20
    }
    SubShader
    {
    
    
        Tags {
    
     "RenderType"="Opaque" }
        LOD 100

        Pass
        {
    
    
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

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

            struct v2f
            {
    
    
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 wdNorm : TEXCOORD1;
                float3 wdP2S : TEXCOORD2; 
                float3 wdP2V : TEXCOORD3;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            float4 _MainColor;

            int _IsCalNorm;

            float4 _LightFactor;
            float4 _DiffuseFactor;
            float4 _SpecFactor;
            float _SpecGloss;
            
            const int _WaveCount;
            float _WaveParams[30];          //最多6个

            float4 getGerstnerWave(float4 vtx,float2 dir,float wavespd,float waverag,float waverad,inout float3 tang,inout float3 bitang)
            {
    
    
                float ang = dot(dir,vtx.xz) + wavespd*_Time.y;
                float x = dir.x*waverag*cos(ang)*waverad;
                float y = waverag*sin(ang)*waverad;
                float z = dir.y*waverag*cos(ang)*waverad;
                float3 t1 = normalize(tang+float3(0,0,z));
                tang = normalize(t1+float3(0,y,0));
                float3 bt1 = normalize(bitang+float3(z,0,0));
                bitang = normalize(bt1+float3(0,y,0));
                return float4(x,y,z,0);
            }

            v2f vert (appdata v)
            {
    
    
                v2f o;
                //原tangent、bitangent
                float3 otan = float3(1,0,0);            
                float3 obitan = float3(0,0,1);
                for(int i=0;i<_WaveCount;i++)
                {
    
    
                    int index = i*5;
                    float2 dir = float2(_WaveParams[index],_WaveParams[index+1]);
                    float spd = _WaveParams[index+2];
                    float rag = _WaveParams[index+3];
                    float rad = _WaveParams[index+4];
                    float4 wvtx = getGerstnerWave(v.vertex,dir,spd,rag,rad,otan,obitan);
                    v.vertex += wvtx;
                }
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                if(_IsCalNorm){
    
    
                    //计算world space normal
                    o.wdNorm = UnityObjectToClipPos(cross(otan,obitan));
                }else{
    
    
                    o.wdNorm = UnityObjectToClipPos(v.normal);
                }
                o.wdP2S = WorldSpaceLightDir(v.vertex);
                o.wdP2V = WorldSpaceViewDir(v.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                fixed4 col = _MainColor;
                float3 wdnorm = normalize(i.wdNorm);
                float3 wdp2s = normalize(i.wdP2S);
                float3 wdp2v = normalize(i.wdP2V);
                float3 hdir =  normalize(wdp2s+wdp2v);
                float ndotl = dot(wdnorm,wdp2s);
                float ndoth = dot(wdnorm,hdir);

                //计算光照
                fixed4 light = _LightColor0*_LightFactor;

                float diff = max(0,ndotl);
                fixed4 diffcol = diff*_LightColor0*_DiffuseFactor;
                
                float spec = pow(max(0,ndoth),_SpecGloss);
                fixed4 speccol = spec*_LightColor0*_SpecFactor;

                col*=(light+diffcol+speccol);
                return col;
            }
            ENDCG
        }
    }
}

      效果如下:
在这里插入图片描述
      可以看得出来问题不大,法向量看上去是计算对了。好了,后面有时间继续。聊一下我写的一个可交互水体的功能。

猜你喜欢

转载自blog.csdn.net/yinhun2012/article/details/120412681
今日推荐