Unity3D手游项目的总结和思考(3) - 自动生成的水面

时间:2021-07-14 15:05:04

     前面两篇提到场景和角色渲染的技术,这一篇讲一下水面.

     水面最开始是美术制作的,一个大的面片来实现,对于湖泊多的场景,这个面片甚至场景那么大,我觉得这种做法不是很好,虽然只有4个顶点,2个三角面,但是由于很大,像素填充率惊人,效率自然也不够好.后来要处理一些水面岸边渐变的效果,我觉得程序实现一个自动化的,不再让美术去操心这个事情.

    我想尽可能地让这个水面完美,所以尝试做了以下一些技术

1.自动生成水面网格

       美术把这个水面摆到某个地方,然后水面根据摆的位置的高度,就自动生成一个适配地形的水面网格.这个技术我以前提到过,就是种子填充算法,从水面一个点逐渐往8个方向的点扩散,直到遇到比他高的地形.

        // 八方向的边界填充算法
        void WaterBoundaryFill8(int x, int z, float boundaryHeight)
        {
            int index = x + z * (m_GridNumX + 1);
            if (m_VerticesFlag[index])
                return;

            float height = GetHeight(x, z);
            if (height <= boundaryHeight)
            {
                m_VerticesFlag[index] = true;
                float difference = Mathf.Clamp(boundaryHeight - height, 0, maxWaterDepth);
                m_VerticesAlpha[index] = Mathf.Clamp01(difference / maxWaterDepth);

                if (x + 1 < m_GridNumX + 1 && x - 1 >= 0 && z + 1 < m_GridNumZ + 1 && z - 1 >= 0)
                {
                    WaterBoundaryFill8(x + 1, z, boundaryHeight);
                    WaterBoundaryFill8(x - 1, z, boundaryHeight);
                    WaterBoundaryFill8(x, z + 1, boundaryHeight);
                    WaterBoundaryFill8(x, z - 1, boundaryHeight);

                    WaterBoundaryFill8(x - 1, z - 1, boundaryHeight);
                    WaterBoundaryFill8(x + 1, z - 1, boundaryHeight);
                    WaterBoundaryFill8(x - 1, z + 1, boundaryHeight);
                    WaterBoundaryFill8(x + 1, z + 1, boundaryHeight);
                }
            }
        }

        float GetHeight(int x, int z)
        {
            float height = float.MinValue;
            Vector3 centerOffset = new Vector3(-m_GridNumX * 0.5f, 0, -m_GridNumZ * 0.5f);
            Vector3 worldPos = GetVertexLocalPos(x, z, centerOffset) + transform.position;
            worldPos.y += 100.0f;
            RaycastHit hit;
            if (Physics.Raycast(worldPos, -Vector3.up, out hit, 200.0f))
            {
                height = hit.point.y;
            }
            else
            {
                //LogSystem.DebugLog("Physics.Raycast失败,请检查是否有Collider. x:{0} z:{0}", x, z);
            }
            
            return height;
        }

Unity3D手游项目的总结和思考(3) - 自动生成的水面

2.水面和岸边的alpha过渡

       如果不处理水面和岸边的alpha过渡.那么相交的地方会有一条很明显的线.

       水面的过渡处理,如果有场景的深度图,那么处理起来就特别方便,因为你就知道了场景中每个像素点的深度,和水平面一比较就知道了这个alpha过渡值.深度数据的获取,如果是延迟渲染,就从G-Buffer,前向渲染,就得自己渲染深度图(pre-z).但是对于手游,自己额外渲染深度图会有一定开销.如果抛弃这种处理方式,那么我们该怎么办呢?

        一般有两种办法,一种是用额外一张alpha贴图来逐像素地保存这个alpha值,缺点是,如果水面大,这个alpha图就得很大,而且得美术来制作,修改起来也很麻烦.另外一种就是用水面网格顶点颜色来保存这个alpha值,这个alpha值,程序自动计算,不需要美术去处理.缺点是过渡效果是逐顶点的,和网格的密度有关系.毫无疑问,我选择第二种.

Unity3D手游项目的总结和思考(3) - 自动生成的水面

3.岸边自动生成动态海浪

       我们首先需要一个Foam的纹理来模拟浪花.其次需要一个梯度图来实现一层一层的浪花的运动.再次我们需要知道浪花的运动方向,这个可以通过水的深浅决定,因为浪花总是从水深的地方移动到水浅的地方.

Unity3D手游项目的总结和思考(3) - 自动生成的水面

Unity3D手游项目的总结和思考(3) - 自动生成的水面

4.水面的倒影和折射

       水面的实时倒影和折射,也支持,后来还是没用到,因为开销大了点,我用环境贴图反射来代替水面实时倒影,效率高很多,效果也还可以,适合手游.

				// reflection
	#if _RUNTIME_REFLECTIVE
				float4 uv1 = i.uvProjector; uv1.xy += noise.xy * 0.25;
				fixed4 refl = tex2Dproj(_ReflectionTex, UNITY_PROJ_COORD(uv1)) * _ReflectionColor;
	#else		
				half3 worldReflectionVector = normalize(reflect(-viewDir, normalNoise));
				fixed4 refl = texCUBE(_ReflectionCubeMap, worldReflectionVector) * _ReflectionColor;			
	#endif
5.法线和高光

         水面法线贴图来实现扰动,在太阳或者月亮的方向产生高光.

Unity3D手游项目的总结和思考(3) - 自动生成的水面

6.互动的水波纹

         如果角色走进水面,应该产生波纹的.实现原理就是先渲染一张水波纹扰动图片,然后水面再根据这个图来扰动水面.

------------------------------------------------------------------------------------------------------------------

以上6大功能实现后,这个水面看起来相对完美了,基本满足了我这个强迫症患者的需求.其实还有些地方没处理的,比如水面的LOD,

水面的减面等等.

整个水面shader的编辑界面:

Unity3D手游项目的总结和思考(3) - 自动生成的水面

水面生成工具:

// 2016.5.22 luoyinan 自动生成水面
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace Luoyinan
{
    public enum MeshType
    {
        Gizmos_FullMesh,
        Gizmos_WaterMesh,
        WaterMesh,
    }

    public class WaterGenerateTool : MonoBehaviour
    {
        [Range(1, 200)]
        public int halfWidth = 20;
        [Range(1, 200)]
        public int halfHeight = 20;
        public float gridSize = 2.0f;
        public float maxWaterDepth = 4.0f;
        public Material material;

        string m_ShaderName = "Luoyinan/Scene/Water/WaterStandard";
        Mesh m_GizmosMesh;
        Vector3 m_LocalPos;
        int m_GridNumX;
        int m_GridNumZ;

        bool[] m_VerticesFlag;
        float[] m_VerticesAlpha;
        Vector3[] m_Vertices;
        int[] m_Triangles;
        List<Vector3> m_VerticesList = new List<Vector3>();
        List<Color> m_ColorsList = new List<Color>();
        List<Vector2> m_UvList = new List<Vector2>();
        List<int> m_TrianglesList = new List<int>();

        void OnDrawGizmosSelected()
        {
            // 水面指示器
            Gizmos.matrix = transform.localToWorldMatrix;
            if (m_GizmosMesh != null)
                UnityEngine.Object.DestroyImmediate(m_GizmosMesh);
            m_GizmosMesh = CreateMesh(MeshType.Gizmos_WaterMesh);
            Gizmos.DrawWireMesh(m_GizmosMesh);
        }

        public void GenerateWater()
        {
            Mesh mesh = CreateMesh(MeshType.WaterMesh);

            // 渲染水面
            foreach (Transform child in transform)
            {
                DestroyImmediate(child.gameObject);
            }
            string name = string.Format("{0}_{1}_{2}", SceneManager.GetActiveScene().name, transform.name, transform.position); 
            mesh.name = name;
            GameObject go = new GameObject(name);
            go.transform.parent = transform;
            go.transform.localPosition = m_LocalPos;
            go.layer = LayerMask.NameToLayer("Water");
            MeshFilter mf = go.AddComponent<MeshFilter>();
            MeshRenderer mr = go.AddComponent<MeshRenderer>();
            if (material == null)
                material = new Material(Shader.Find(m_ShaderName));
            mr.sharedMaterial = material;
            mf.sharedMesh = mesh; 

            // obj模型不支持顶点颜色,所以暂时不导出了.
            // 导出obj模型
            //MeshToFile(mf, "Assets/" + name + ".obj");
        }

        Mesh CreateMesh(MeshType type)
        {
            Mesh mesh = new Mesh();
            mesh.MarkDynamic();
            m_GridNumX = halfWidth * 2;
            m_GridNumZ = halfHeight * 2;
            Vector3 centerOffset = new Vector3(-halfWidth, 0, -halfHeight);

            if (type == MeshType.Gizmos_FullMesh)
            {
                int vectices_num = m_GridNumX * m_GridNumZ * 4;
                int triangles_num = m_GridNumX * m_GridNumZ * 6;
                m_Vertices = new Vector3[vectices_num];
                m_Triangles = new int[triangles_num];
            
                // 从左下角开始创建,三角形索引顺时针是正面.
                // 2 3
                // 0 1
                for (int z = 0; z < m_GridNumZ; ++z)
                {
                    for (int x = 0; x < m_GridNumX; ++x)
                    {
                        int index = x + z * m_GridNumX;
                        int i = index * 4;
                        int j = index * 6;

                        m_Vertices[i] = GetVertexLocalPos(x, z, centerOffset);
                        m_Vertices[i + 1] = GetVertexLocalPos(x + 1, z, centerOffset);
                        m_Vertices[i + 2] = GetVertexLocalPos(x, z + 1, centerOffset);
                        m_Vertices[i + 3] = GetVertexLocalPos(x + 1, z + 1, centerOffset);

                        m_Triangles[j] = i;
                        m_Triangles[j + 1] = i + 2;
                        m_Triangles[j + 2] = i + 3;
                        m_Triangles[j + 3] = i + 3;
                        m_Triangles[j + 4] = i + 1;
                        m_Triangles[j + 5] = i;
                    }
                }

                mesh.vertices = m_Vertices;
                mesh.triangles = m_Triangles;
                mesh.RecalculateNormals();
                mesh.RecalculateBounds();
            }
            else if (type == MeshType.Gizmos_WaterMesh)
            {
                CalcWaterMesh();

                m_VerticesList.Clear();
                m_ColorsList.Clear();
                m_TrianglesList.Clear();
                int counter = 0;

                // 从左下角开始创建,三角形索引顺时针是正面.
                // 2 3
                // 0 1
                for (int z = 0; z < m_GridNumZ; ++z)
                {
                    for (int x = 0; x < m_GridNumX; ++x)
                    {
                        if (!IsValidGrid(x, z))
                            continue;

                        int i = counter * 4;

                        m_VerticesList.Add(GetVertexLocalPos(x, z, centerOffset));
                        m_VerticesList.Add(GetVertexLocalPos(x + 1, z, centerOffset));
                        m_VerticesList.Add(GetVertexLocalPos(x, z + 1, centerOffset));
                        m_VerticesList.Add(GetVertexLocalPos(x + 1, z + 1, centerOffset));

                        m_TrianglesList.Add(i);
                        m_TrianglesList.Add(i + 2);
                        m_TrianglesList.Add(i + 3);
                        m_TrianglesList.Add(i + 3);
                        m_TrianglesList.Add(i + 1);
                        m_TrianglesList.Add(i);

                        ++counter;
                    }
                }

                // 忘记添加collider了?
                if (m_VerticesList.Count == 0)
                    return CreateMesh(MeshType.Gizmos_FullMesh);

                mesh.vertices = m_VerticesList.ToArray();
                mesh.triangles = m_TrianglesList.ToArray();
                mesh.RecalculateNormals();
                mesh.RecalculateBounds();
            }
            else if (type == MeshType.WaterMesh)
            {
                CalcWaterMesh();

                m_VerticesList.Clear();
                m_ColorsList.Clear();
                m_UvList.Clear();
                m_TrianglesList.Clear();
                int counter = 0;

                // 先循环一次,找出最小最大的格子,让UV计算更精确.
                int minX = m_GridNumX - 1;
                int minZ = m_GridNumZ - 1;
                int maxX = 0;
                int maxZ = 0;
                for (int z = 0; z < m_GridNumZ; ++z)
                {
                    for (int x = 0; x < m_GridNumX; ++x)
                    {
                        if (!IsValidGrid(x, z))
                            continue;

                        minX = (x < minX) ? x : minX;
                        minZ = (z < minZ) ? z : minZ;
                        maxX = (x > maxX) ? x : maxX;
                        maxZ = (z > maxZ) ? z : maxZ;
                    }
                }
                int newGridNumX = maxX - minX + 1;
                int newGridNumZ = maxZ - minZ + 1;

                // 创建的水面模型应该做原点矫正,以自己形状的中心为原点,这样好支持水波纹的计算.详见WaterRippleGenerateTool
                float halfGridNumX = (float)newGridNumX * 0.5f;
                float halfGridNumZ = (float)newGridNumZ * 0.5f;
                Vector3 offsetAdjust = new Vector3(-(float)minX - halfGridNumX, 0, -(float)minZ - halfGridNumZ);
                m_LocalPos = (centerOffset - offsetAdjust) * gridSize;
                centerOffset = offsetAdjust;                               
                    
                // TODO: 水面中心某些alpha为1的顶点其实可以去掉,需要一个自动减面算法.

                // 从左下角开始创建,三角形索引顺时针是正面.
                // 2 3
                // 0 1
                for (int z = 0; z < m_GridNumZ; ++z)
                {
                    for (int x = 0; x < m_GridNumX; ++x)
                    {
                        if (!IsValidGrid(x, z))
                            continue;

                        int i = counter * 4;
                        int newX = x - minX;
                        int newZ = z - minZ;

                        m_VerticesList.Add(GetVertexLocalPos(x, z, centerOffset));
                        m_VerticesList.Add(GetVertexLocalPos(x + 1, z, centerOffset));
                        m_VerticesList.Add(GetVertexLocalPos(x, z + 1, centerOffset));
                        m_VerticesList.Add(GetVertexLocalPos(x + 1, z + 1, centerOffset));

                        m_ColorsList.Add(new Color(1.0f, 1.0f, 1.0f, GetVertexAlpha(x, z)));
                        m_ColorsList.Add(new Color(1.0f, 1.0f, 1.0f, GetVertexAlpha(x + 1, z)));
                        m_ColorsList.Add(new Color(1.0f, 1.0f, 1.0f, GetVertexAlpha(x, z + 1)));
                        m_ColorsList.Add(new Color(1.0f, 1.0f, 1.0f, GetVertexAlpha(x + 1, z + 1)));

                        m_UvList.Add(new Vector2((float)(newX) / (float)newGridNumX, (float)newZ / (float)newGridNumZ));
                        m_UvList.Add(new Vector2((float)(newX + 1) / (float)newGridNumX, (float)newZ / (float)newGridNumZ));
                        m_UvList.Add(new Vector2((float)newX / (float)newGridNumX, (float)(newZ + 1) / (float)newGridNumZ));
                        m_UvList.Add(new Vector2((float)(newX + 1) / (float)newGridNumX, (float)(newZ + 1) / (float)newGridNumZ));

                        m_TrianglesList.Add(i);
                        m_TrianglesList.Add(i + 2);
                        m_TrianglesList.Add(i + 3);
                        m_TrianglesList.Add(i + 3);
                        m_TrianglesList.Add(i + 1);
                        m_TrianglesList.Add(i);

                        ++counter;
                    }
                }

                mesh.vertices = m_VerticesList.ToArray();
                mesh.colors = m_ColorsList.ToArray();
                mesh.uv = m_UvList.ToArray();
                mesh.triangles = m_TrianglesList.ToArray();
                mesh.RecalculateNormals();
                mesh.RecalculateBounds();
            }

            return mesh;
        }

        void CalcWaterMesh()
        {
            int VerticesNum = (m_GridNumX + 1) * (m_GridNumZ + 1);
            m_VerticesFlag = new bool[VerticesNum];
            m_VerticesAlpha = new float[VerticesNum];

            WaterBoundaryFill8(halfWidth, halfHeight, transform.position.y);
        }

        // 八方向的边界填充算法
        void WaterBoundaryFill8(int x, int z, float boundaryHeight)
        {
            int index = x + z * (m_GridNumX + 1);
            if (m_VerticesFlag[index])
                return;

            float height = GetHeight(x, z);
            if (height <= boundaryHeight)
            {
                m_VerticesFlag[index] = true;
                float difference = Mathf.Clamp(boundaryHeight - height, 0, maxWaterDepth);
                m_VerticesAlpha[index] = Mathf.Clamp01(difference / maxWaterDepth);

                if (x + 1 < m_GridNumX + 1 && x - 1 >= 0 && z + 1 < m_GridNumZ + 1 && z - 1 >= 0)
                {
                    WaterBoundaryFill8(x + 1, z, boundaryHeight);
                    WaterBoundaryFill8(x - 1, z, boundaryHeight);
                    WaterBoundaryFill8(x, z + 1, boundaryHeight);
                    WaterBoundaryFill8(x, z - 1, boundaryHeight);

                    WaterBoundaryFill8(x - 1, z - 1, boundaryHeight);
                    WaterBoundaryFill8(x + 1, z - 1, boundaryHeight);
                    WaterBoundaryFill8(x - 1, z + 1, boundaryHeight);
                    WaterBoundaryFill8(x + 1, z + 1, boundaryHeight);
                }
            }
        }

        float GetHeight(int x, int z)
        {
            float height = float.MinValue;
            Vector3 centerOffset = new Vector3(-m_GridNumX * 0.5f, 0, -m_GridNumZ * 0.5f);
            Vector3 worldPos = GetVertexLocalPos(x, z, centerOffset) + transform.position;
            worldPos.y += 100.0f;
            RaycastHit hit;
            if (Physics.Raycast(worldPos, -Vector3.up, out hit, 200.0f))
            {
                height = hit.point.y;
            }
            else
            {
                //LogSystem.DebugLog("Physics.Raycast失败,请检查是否有Collider. x:{0} z:{0}", x, z);
            }
            
            return height;
        }

        bool IsValidGrid(int x, int z)
        {
            // 4个顶点只要有一个合法,就算合法.
            if (isValidVertex(x, z))
                return true;
            if (isValidVertex(x + 1, z))
                return true;
            if (isValidVertex(x, z + 1))
                return true;
            if (isValidVertex(x + 1, z + 1))
                return true;

            return false;
        }

        bool isValidVertex(int x, int z)
        {
            int index = x + z * (m_GridNumX + 1);
            return m_VerticesFlag[index];
        }

        float GetVertexAlpha(int x, int z)
        {
            int index = x + z * (m_GridNumX + 1);
            return m_VerticesAlpha[index];
        }
        Vector3 GetVertexLocalPos(int x, int z, Vector3 centerOffset)
        {
            return new Vector3((x + centerOffset.x) * gridSize, 0, (z + centerOffset.z) * gridSize);
        }
        // 暂时没用到
        bool IsNearbyBoundary(int x, int z, float boundaryHeight)
        {
            float height = GetHeight(x + 1, z);
            if (height > boundaryHeight)
                return true;
            height = GetHeight(x - 1, z);
            if (height > boundaryHeight)
                return true;
            height = GetHeight(x, z + 1);
            if (height > boundaryHeight)
                return true;
            height = GetHeight(x, z - 1);
            if (height > boundaryHeight)
                return true;

            return false;
        }

        public void MeshToFile(MeshFilter mf, string filename)
        {
            using (StreamWriter sw = new StreamWriter(filename))
            {
                sw.Write(MeshToString(mf));
            }
        }

        public string MeshToString(MeshFilter mf)
        {
            Mesh m = mf.sharedMesh;
            Material[] mats = mf.GetComponent<Renderer>().sharedMaterials;

            StringBuilder sb = new StringBuilder();

            sb.Append("g ").Append(mf.name).Append("\n");
            foreach (Vector3 v in m.vertices)
            {
                sb.Append(string.Format("v {0} {1} {2}\n", v.x, v.y, v.z));
            }
            sb.Append("\n");
            foreach (Vector3 v in m.normals)
            {
                sb.Append(string.Format("vn {0} {1} {2}\n", v.x, v.y, v.z));
            }
            sb.Append("\n");
            foreach (Vector3 v in m.uv)
            {
                sb.Append(string.Format("vt {0} {1}\n", v.x, v.y));
            }
            for (int material = 0; material < m.subMeshCount; material++)
            {
                sb.Append("\n");
                sb.Append("usemtl ").Append(mats[material].name).Append("\n");
                sb.Append("usemap ").Append(mats[material].name).Append("\n");

                int[] triangles = m.GetTriangles(material);
                for (int i = 0; i < triangles.Length; i += 3)
                {
                    sb.Append(string.Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n",
                        triangles[i] + 1, triangles[i + 1] + 1, triangles[i + 2] + 1));
                }
            }
            return sb.ToString();
        }
    }
}

水波纹产生工具:

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

public class WaterRippleGenerateTool : EffectTool
{
    public RenderTexture _rt;

    public int RT_SIZE_SCALE = 2;
    private Material _mat;

    public float frequency = 2.5f;
    public float scale = 0.1f;
    public float power = 2;
    public float centralized = 0.25f;
    public float falloff = 3;

    public Renderer waterRenderer;

    private List<Vector4> _list = new List<Vector4>();


    public bool demo;
    public override void restart()
    {
        float aspectRatio = 1.0f;

        if (_rt == null)
        {
            if (waterRenderer == null)
                waterRenderer = GetComponent<MeshRenderer>();

            if (waterRenderer != null)
            {           
                if (GetComponent<MeshCollider>() == null)
                    gameObject.AddComponent<MeshCollider>();

                int w = (int)waterRenderer.bounds.size.x * RT_SIZE_SCALE;
                int h = (int)waterRenderer.bounds.size.z * RT_SIZE_SCALE;
                _rt = new RenderTexture(w, h, 0);
                waterRenderer.sharedMaterial.SetTexture("_RippleTex", _rt);

                aspectRatio = (float)w / (float)h;
            }             
        }
        if (_mat == null)
        {
            _mat = new Material(Shader.Find("Luoyinan/Scene/Water/WaterRipple"));
            _mat.SetVector("_RippleData", new Vector4(frequency, scale, centralized, falloff));
            _mat.SetFloat("_AspectRatio", aspectRatio);
        }
    }

    public void setDrop(Vector3 pos)
    {
        Vector3 rel = pos - transform.position;
        float width = waterRenderer.bounds.size.x;
        float height = waterRenderer.bounds.size.z;
        Vector4 dd = new Vector4(rel.x / width + 0.5f, rel.z / height + 0.5f, 0, power); // MVP空间位置[0, 1]
        _list.Add(dd);
    }

    void Update()
    {
        int count = _list.Count;
        float deltaTime = Time.deltaTime;
        RenderTexture oldRT = RenderTexture.active;
        Graphics.SetRenderTarget(_rt);
        GL.Clear(false, true, Color.black);

        if (count > 0)
        {
            _mat.SetVector("_RippleData", new Vector4(frequency, scale, centralized, falloff));
        }

        for (int i = count - 1; i >= 0; i--)
        {
            Vector4 drop = _list[i];
            drop.z = drop.z + deltaTime;

            if (drop.z > 3)
            {
                _list.RemoveAt(i);
                continue;
            }
            else
            {
                _list[i] = drop;
            }

            GL.PushMatrix();
            _mat.SetPass(0);
            _mat.SetVector("_Drop1", drop);
            GL.LoadOrtho();
            GL.Begin(GL.QUADS);
            GL.Vertex3(0, 1, 0);
            GL.Vertex3(1, 1, 0);
            GL.Vertex3(1, 0, 0);
            GL.Vertex3(0, 0, 0);
            GL.End();
            GL.PopMatrix();
        }
        RenderTexture.active = oldRT;

        if (demo)
        {
            if (Input.GetKeyDown(KeyCode.Space))
            {
                setDrop(transform.position);
            }
        }
    }

    public override void end()
    {
        if (_rt != null)
        {
            _rt.Release();
        }
        _rt = null;
    }
}

水面脚本:

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

namespace Luoyinan
{
    [ExecuteInEditMode] 
    public class WaterStandard : MonoBehaviour
    {
        public bool runtimeReflective = false;
        public bool runtimeRefractive = true;
        public int textureSize = 256;
        public float clipPlaneOffset = 0.07f;
        public LayerMask reflectLayers = -1;
        public LayerMask refractLayers = -1;

        private Dictionary<Camera, Camera> m_ReflectionCameras = new Dictionary<Camera, Camera>(); 
        private Dictionary<Camera, Camera> m_RefractionCameras = new Dictionary<Camera, Camera>();
        public RenderTexture m_ReflectionTexture;
        public RenderTexture m_RefractionTexture;
        private int m_OldReflectionTextureSize;
        private int m_OldRefractionTextureSize;
        private static bool s_InsideWater;

        public void OnWillRenderObject()
        {
            if (!FindHardwareWaterSupport())
            {
                enabled = false;
                return;
            }           

            if (!enabled || !GetComponent<Renderer>() 
                || !GetComponent<Renderer>().sharedMaterial 
                || !GetComponent<Renderer>().enabled)
            {
                return;
            }

            Camera cam = Camera.current;
            if (!cam)
                return;

            // 防止递归调用
            if (s_InsideWater)
                return;
            s_InsideWater = true;

            // 创建摄像机和渲染纹理
            Camera reflectionCamera, refractionCamera;
            CreateWaterObjects(cam, out reflectionCamera, out refractionCamera);
            
            Vector3 pos = transform.position;
            Vector3 normal = transform.up;
            if (runtimeReflective)
            {
                UpdateCameraModes(cam, reflectionCamera);

                // 水平面作为反射平面
                float d = -Vector3.Dot(normal, pos) - clipPlaneOffset;
                Vector4 reflectionPlane = new Vector4(normal.x, normal.y, normal.z, d);

                // 反射矩阵
                Matrix4x4 reflection = Matrix4x4.zero;
                CalculateReflectionMatrix(ref reflection, reflectionPlane);
                Vector3 oldpos = cam.transform.position;
                Vector3 newpos = reflection.MultiplyPoint(oldpos);
                reflectionCamera.worldToCameraMatrix = cam.worldToCameraMatrix * reflection;

                // 反射平面作为摄像机近截面,这样可以自动裁剪水面或者水下.
                Vector4 clipPlane = CameraSpacePlane(reflectionCamera, pos, normal, 1.0f);
                reflectionCamera.projectionMatrix = cam.CalculateObliqueMatrix(clipPlane);
                reflectionCamera.cullingMatrix = cam.projectionMatrix * cam.worldToCameraMatrix;

                reflectionCamera.cullingMask = ~(1 << LayerMask.NameToLayer("Water")) & reflectLayers.value; // 不要渲染水面本身
                reflectionCamera.targetTexture = m_ReflectionTexture;
                bool oldCulling = GL.invertCulling; // 渲染背面
                GL.invertCulling = !oldCulling;
                reflectionCamera.transform.position = newpos;
                Vector3 euler = cam.transform.eulerAngles;
                reflectionCamera.transform.eulerAngles = new Vector3(-euler.x, euler.y, euler.z);
                reflectionCamera.Render();
                reflectionCamera.transform.position = oldpos;
                GL.invertCulling = oldCulling;
                GetComponent<Renderer>().sharedMaterial.SetTexture("_ReflectionTex", m_ReflectionTexture);
            }

            if (runtimeRefractive)
            {
                UpdateCameraModes(cam, refractionCamera);

                refractionCamera.worldToCameraMatrix = cam.worldToCameraMatrix;

                // 反射平面作为摄像机近截面,这样可以自动裁剪水面或者水下.
                Vector4 clipPlane = CameraSpacePlane(refractionCamera, pos, normal, -1.0f);
                refractionCamera.projectionMatrix = cam.CalculateObliqueMatrix(clipPlane);
                refractionCamera.cullingMatrix = cam.projectionMatrix * cam.worldToCameraMatrix;

                refractionCamera.cullingMask = ~(1 << LayerMask.NameToLayer("Water")) & refractLayers.value; // 不要渲染水面本身
                refractionCamera.targetTexture = m_RefractionTexture;
                refractionCamera.transform.position = cam.transform.position;
                refractionCamera.transform.rotation = cam.transform.rotation;
                refractionCamera.Render();
                GetComponent<Renderer>().sharedMaterial.SetTexture("_RefractionTex", m_RefractionTexture);
            }

            // keyword
            if (runtimeReflective)
                Shader.EnableKeyword("_RUNTIME_REFLECTIVE");
            else
                Shader.DisableKeyword("_RUNTIME_REFLECTIVE");

            if (runtimeRefractive)
                Shader.EnableKeyword("_RUNTIME_REFRACTIVE");
            else
                Shader.DisableKeyword("_RUNTIME_REFRACTIVE");

            s_InsideWater = false;
        }

        void OnDisable()
        {
            if (m_ReflectionTexture)
            {
                DestroyImmediate(m_ReflectionTexture);
                m_ReflectionTexture = null;
            }
            if (m_RefractionTexture)
            {
                DestroyImmediate(m_RefractionTexture);
                m_RefractionTexture = null;
            }
            foreach (var kvp in m_ReflectionCameras)
            {
                DestroyImmediate((kvp.Value).gameObject);
            }
            m_ReflectionCameras.Clear();
            foreach (var kvp in m_RefractionCameras)
            {
                DestroyImmediate((kvp.Value).gameObject);
            }
            m_RefractionCameras.Clear();
        }

        void UpdateCameraModes(Camera src, Camera dest)
        {
            if (dest == null)
                return;

            dest.clearFlags = src.clearFlags;
            dest.backgroundColor = src.backgroundColor;
            if (src.clearFlags == CameraClearFlags.Skybox)
            {
                Skybox sky = src.GetComponent<Skybox>();
                Skybox mysky = dest.GetComponent<Skybox>();
                if (!sky || !sky.material)
                {
                    mysky.enabled = false;
                }
                else
                {
                    mysky.enabled = true;
                    mysky.material = sky.material;
                }
            }

            dest.farClipPlane = src.farClipPlane;
            dest.nearClipPlane = src.nearClipPlane;
            dest.orthographic = src.orthographic;
            dest.fieldOfView = src.fieldOfView;
            dest.aspect = src.aspect;
            dest.orthographicSize = src.orthographicSize;
        }

        void CreateWaterObjects(Camera currentCamera, out Camera reflectionCamera, out Camera refractionCamera)
        {
            reflectionCamera = null;
            refractionCamera = null;

            if (runtimeReflective)
            {
                if (!m_ReflectionTexture || m_OldReflectionTextureSize != textureSize)
                {
                    if (m_ReflectionTexture)
                    {
                        DestroyImmediate(m_ReflectionTexture);
                    }
                    m_ReflectionTexture = new RenderTexture(textureSize, textureSize, 16);
                    m_ReflectionTexture.name = "__WaterReflection" + GetInstanceID();
                    m_ReflectionTexture.isPowerOfTwo = true;
                    m_ReflectionTexture.hideFlags = HideFlags.DontSave;
                    m_OldReflectionTextureSize = textureSize;
                }

                m_ReflectionCameras.TryGetValue(currentCamera, out reflectionCamera);
                if (!reflectionCamera) 
                {
                    GameObject go = new GameObject("Water Refl Camera id" + GetInstanceID() + " for " + currentCamera.GetInstanceID(), typeof(Camera), typeof(Skybox));
                    reflectionCamera = go.GetComponent<Camera>();
                    reflectionCamera.enabled = false;
                    reflectionCamera.transform.position = transform.position;
                    reflectionCamera.transform.rotation = transform.rotation;
                    reflectionCamera.gameObject.AddComponent<FlareLayer>();
                    go.hideFlags = HideFlags.HideAndDontSave;
                    m_ReflectionCameras[currentCamera] = reflectionCamera;
                }
            }

            if (runtimeRefractive)
            {
                if (!m_RefractionTexture || m_OldRefractionTextureSize != textureSize)
                {
                    if (m_RefractionTexture)
                    {
                        DestroyImmediate(m_RefractionTexture);
                    }
                    m_RefractionTexture = new RenderTexture(textureSize, textureSize, 16);
                    m_RefractionTexture.name = "__WaterRefraction" + GetInstanceID();
                    m_RefractionTexture.isPowerOfTwo = true;
                    m_RefractionTexture.hideFlags = HideFlags.DontSave;
                    m_OldRefractionTextureSize = textureSize;
                }

                m_RefractionCameras.TryGetValue(currentCamera, out refractionCamera);
                if (!refractionCamera) 
                {
                    GameObject go =
                        new GameObject("Water Refr Camera id" + GetInstanceID() + " for " + currentCamera.GetInstanceID(),
                            typeof(Camera), typeof(Skybox));
                    refractionCamera = go.GetComponent<Camera>();
                    refractionCamera.enabled = false;
                    refractionCamera.transform.position = transform.position;
                    refractionCamera.transform.rotation = transform.rotation;
                    refractionCamera.gameObject.AddComponent<FlareLayer>();
                    go.hideFlags = HideFlags.HideAndDontSave;
                    m_RefractionCameras[currentCamera] = refractionCamera;
                }
            }
        }

        bool FindHardwareWaterSupport()
        {
            if (!SystemInfo.supportsRenderTextures || !GetComponent<Renderer>())
                return false;

            return true;
        }

        Vector4 CameraSpacePlane(Camera cam, Vector3 pos, Vector3 normal, float sideSign)
        {
            Vector3 offsetPos = pos + normal * clipPlaneOffset;
            Matrix4x4 m = cam.worldToCameraMatrix;
            Vector3 cpos = m.MultiplyPoint(offsetPos);
            Vector3 cnormal = m.MultiplyVector(normal).normalized * sideSign;
            return new Vector4(cnormal.x, cnormal.y, cnormal.z, -Vector3.Dot(cpos, cnormal));
        }

        static void CalculateReflectionMatrix(ref Matrix4x4 reflectionMat, Vector4 plane)
        {
            reflectionMat.m00 = (1F - 2F * plane[0] * plane[0]);
            reflectionMat.m01 = (-2F * plane[0] * plane[1]);
            reflectionMat.m02 = (-2F * plane[0] * plane[2]);
            reflectionMat.m03 = (-2F * plane[3] * plane[0]);

            reflectionMat.m10 = (-2F * plane[1] * plane[0]);
            reflectionMat.m11 = (1F - 2F * plane[1] * plane[1]);
            reflectionMat.m12 = (-2F * plane[1] * plane[2]);
            reflectionMat.m13 = (-2F * plane[3] * plane[1]);

            reflectionMat.m20 = (-2F * plane[2] * plane[0]);
            reflectionMat.m21 = (-2F * plane[2] * plane[1]);
            reflectionMat.m22 = (1F - 2F * plane[2] * plane[2]);
            reflectionMat.m23 = (-2F * plane[3] * plane[2]);

            reflectionMat.m30 = 0F;
            reflectionMat.m31 = 0F;
            reflectionMat.m32 = 0F;
            reflectionMat.m33 = 1F;
        }
    }
}

水面shader:

Shader "Luoyinan/Scene/Water/WaterStandard" 
{
	Properties 
	{
		_ReflectionCubeMap("Reflection CubeMap",Cube) = ""{}
		_BumpMap("Normal Map", 2D) = "bump" {}
		_FoamTex ("Foam Texture ", 2D) = "black" {}
		_FoamGradientTex("Foam Gradient Texture ", 2D) = "white" {}
		_MainColor("Main Color", Color) = (0.3, 0.4, 0.7, 1.0)
		_ReflectionColor("Reflection Color", Color) = (1.0, 1.0, 1.0, 1.0)
		_SpecularIntensity("Specular Intensity", Range (0, 2)) = 1
		_SpecularSharp("Specular Sharp", Float) = 96
		_WaveIntensity("Wave Intensity", Range(0, 1)) = 1.0
		_FoamIntensity("Foam Intensity", Range (0, 1.0)) = 0.75
		_FoamSpeed("Foam Speed", Range (0, 1.0)) = 0.25
		_FoamFadeDepth("Foam Fade Depth", Range (0, 1.0)) = 0.4
		_FoamBrightness("Foam Brightness", Range (0, 2.0)) = 0
		_Force("Wave Speed&Direction", Vector) = (0.5, 0.5, -0.5, -0.5)
	}

	SubShader 
	{
		Tags
		{ 
			"Queue" = "Geometry+100" 
			"IgnoreProjector" = "True"
		}

		Pass 
		{ 
			Lighting Off
			//ColorMask RGB
			Blend SrcAlpha OneMinusSrcAlpha
			CGPROGRAM

			#include "UnityCG.cginc"
			#pragma multi_compile_fog
			#pragma vertex vert
			#pragma fragment frag
			#pragma fragmentoption ARB_precision_hint_fastest
			
			#pragma shader_feature _FOAM_ON
			#pragma shader_feature _RUNTIME_REFLECTIVE
			#pragma shader_feature _RUNTIME_REFRACTIVE
			#pragma multi_compile __ _FANCY_STUFF

			struct appdata_water 
			{
				float4 vertex : POSITION;
				float2 texcoord : TEXCOORD0;
				fixed4 color : COLOR;
			};

			struct v2f 
			{
				float4 pos : POSITION;
				fixed4 color : COLOR;
				float2 uv0 : TEXCOORD0;
				UNITY_FOG_COORDS(1)
#if _FANCY_STUFF
				float2 uvNoise : TEXCOORD2;
				float3 posWorld : TEXCOORD3;
				half3 normal : TEXCOORD4;
	#if _FOAM_ON
				float2 uvFoam : TEXCOORD5;	
	#endif		
	#if _RUNTIME_REFLECTIVE || _RUNTIME_REFRACTIVE
				float4 uvProjector : TEXCOORD6;
	#endif
#endif
			};
		
			uniform fixed4 _MainColor;
#if _FANCY_STUFF
			uniform fixed4 _Force;
			uniform sampler2D _BumpMap;
			float4 _BumpMap_ST;
			uniform fixed _WaveIntensity;
			uniform fixed4 _ReflectionColor;
			uniform fixed _SpecularIntensity;
			uniform half _SpecularSharp;
			half4 _GlobalMainLightDir;
			fixed4 _GlobalMainLightColor;

	#if _FOAM_ON
			uniform sampler2D _FoamTex;
			uniform sampler2D _FoamGradientTex;
			float4 _FoamTex_ST;
			uniform fixed _FoamIntensity;
			uniform fixed _FoamSpeed;
			uniform fixed _FoamFadeDepth;
			uniform fixed _FoamBrightness;
	#endif

	#if _RUNTIME_REFLECTIVE
			uniform sampler2D _ReflectionTex;
	#else
			uniform samplerCUBE _ReflectionCubeMap;
	#endif

	#if _RUNTIME_REFRACTIVE
			uniform sampler2D _RefractionTex;
	#endif

			uniform sampler2D _RippleTex;
#endif
			v2f vert (appdata_water v)
			{
				v2f o;
				o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
				o.color = v.color;
				o.uv0 = v.texcoord;
#if _FANCY_STUFF
				o.uvNoise = TRANSFORM_TEX(v.texcoord, _BumpMap);
				o.posWorld = mul(unity_ObjectToWorld, v.vertex).xyz;
				o.normal = half3(0, 1 - _WaveIntensity, 0);	
	#if _FOAM_ON
				o.uvFoam = TRANSFORM_TEX(v.texcoord, _FoamTex);
	#endif

	#if _RUNTIME_REFLECTIVE || _RUNTIME_REFRACTIVE
				o.uvProjector = ComputeScreenPos(o.pos);
	#endif
#endif
				UNITY_TRANSFER_FOG(o, o.pos);
				return o;
			}
			
			fixed4 frag (v2f i) : COLOR
			{
				fixed waterDepth = i.color.a;
#if _FANCY_STUFF
                // noise
				half3 noise = UnpackNormal(tex2D(_BumpMap, i.uvNoise + _Time.xx * _Force.xy));
				noise += UnpackNormal(tex2D(_BumpMap, i.uvNoise + _Time.xx * _Force.zw));
				noise = normalize(noise.xzy) * _WaveIntensity; // 在水平面扰动.

				// ripple
				fixed4 ripple = tex2D(_RippleTex, i.uv0) * 2;
				half3 normalNoise = normalize(i.normal + noise + ripple.xyz);

				// fresnel
				half3 viewDir = normalize(_WorldSpaceCameraPos - i.posWorld);
				half fresnel = 1 - saturate(dot(viewDir, normalNoise));
				fresnel = 0.25 + fresnel * 0.75;

				// reflection
	#if _RUNTIME_REFLECTIVE
				float4 uv1 = i.uvProjector; uv1.xy += noise.xy * 0.25;
				fixed4 refl = tex2Dproj(_ReflectionTex, UNITY_PROJ_COORD(uv1)) * _ReflectionColor;
	#else		
				half3 worldReflectionVector = normalize(reflect(-viewDir, normalNoise));
				fixed4 refl = texCUBE(_ReflectionCubeMap, worldReflectionVector) * _ReflectionColor;			
	#endif
				// refractive
	#if _RUNTIME_REFRACTIVE
				float4 uv2 = i.uvProjector; uv2.xy += noise.xy * 0.5;
				fixed4 refr = tex2Dproj(_RefractionTex, UNITY_PROJ_COORD(uv2));
	#else
				fixed4 refr = _MainColor;
	#endif

				fixed4 finalColor = lerp(refr, refl, fresnel);

	#if _FOAM_ON
				// foam
				half foamFactor = 1 - saturate(waterDepth / _FoamIntensity); 
				half foamGradient = 1 - tex2D(_FoamGradientTex, half2(foamFactor - _Time.y * _FoamSpeed, 0) + normalNoise.xy).r;
				foamFactor *= foamGradient;
				half4 foam = tex2D(_FoamTex, i.uvFoam + normalNoise.xy);
				finalColor += foam * foamFactor; 
    #endif
				// specular
				half3 h = normalize(_GlobalMainLightDir.xyz + viewDir);
				half nh = saturate(dot(noise, h));
				nh = pow(nh, _SpecularSharp);
				finalColor += _GlobalMainLightColor * nh * _SpecularIntensity;

				// alpha
	#if _FOAM_ON
				half factor = step(_FoamFadeDepth, waterDepth);
				half newDepth = waterDepth / _FoamFadeDepth;
				finalColor.a = _MainColor.a * waterDepth + foamFactor * _FoamBrightness * (factor +  newDepth * (1 - factor));
	#else
				finalColor.a = _MainColor.a * waterDepth;
	#endif
#else
				fixed4	finalColor = _MainColor;
				finalColor.a *= waterDepth;
#endif
		
				UNITY_APPLY_FOG(i.fogCoord, finalColor);
				return finalColor;
			}

			ENDCG
		} 

		// 没用Unity自带的阴影,只是用来来渲染_CameraDepthsTexture.
		Pass
		{
			Tags { "LightMode" = "ShadowCaster" }

			Fog { Mode Off }
			ZWrite On 
			Offset 1, 1

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag
			#pragma multi_compile_shadowcaster
			#pragma fragmentoption ARB_precision_hint_fastest
			#include "UnityCG.cginc"

			struct v2f
			{
				V2F_SHADOW_CASTER;
			};

			v2f vert(appdata_base v)
			{
				v2f o;
				TRANSFER_SHADOW_CASTER(o)
				return o;
			}

			fixed4 frag(v2f i) : COLOR
			{
				SHADOW_CASTER_FRAGMENT(i)
			}

			ENDCG
		}
	}

	Fallback off
	CustomEditor "WaterStandard_ShaderGUI"
}

水波纹shader:

Shader "Luoyinan/Scene/Water/WaterRipple" 
{
	Properties
	{
		_RippleData("frequency, scale, centralized, falloff", Vector) = (1,1,1,1)
		_AspectRatio("AspectRatio", Float) = 1
	}

	SubShader
	{
		Pass 
		{
			Blend One One

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma fragmentoption ARB_precision_hint_fastest
			#include "UnityCG.cginc"

			struct appdata_t 
			{
				fixed4 vertex : POSITION;
			};

			struct v2f 
			{
				fixed4 vertex : POSITION;
				fixed3 pos : TEXCOORD0;
			};

			
			uniform fixed _AspectRatio;
			uniform fixed4 _RippleData; // Vector4(frequency, scale, centralized, falloff));
			fixed4 _Drop1;              // Vector4(origin.x, origin.z, time, power));
			static const half pi = 3.1415927;

			fixed4 ripple(fixed2 position, fixed2 origin, fixed time, fixed power)
			{
				fixed2 vec = position - origin;
				vec.x *= _AspectRatio; // 做个矫正,让非正方形水面也适用.
				fixed len = length(vec);
				vec = normalize(vec);

				//fixed center = time * frequency * scale;
				fixed center = time * _RippleData.y * _RippleData.x;

				// fixed phase = 2 * pi * ( time * frequency - len / scale);
				fixed phase = 2 * pi * time * _RippleData.x - 2 * pi * len / _RippleData.y;
				fixed intens = max(0, 0.1 - abs(center - len) * _RippleData.z) * power;
				fixed fallOff = max(0, 1 - len * _RippleData.w);
				fixed cut = step(0, phase);
				return fixed4(vec.x, 1, vec.y, 0) * sin(phase) * intens * fallOff * cut;
			}

			v2f vert(appdata_t v)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
				o.pos = v.vertex.xyz;
				return o;
			}

			fixed4 frag(v2f i) : COLOR
			{
				fixed4 rip = ripple(i.pos, _Drop1.xy, _Drop1.z, _Drop1.w);
				return rip;
			}
			ENDCG
		}
	}

	Fallback Off
}