水面最开始是美术制作的,一个大的面片来实现,对于湖泊多的场景,这个面片甚至场景那么大,我觉得这种做法不是很好,虽然只有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;
- }
2.水面和岸边的alpha过渡
如果不处理水面和岸边的alpha过渡.那么相交的地方会有一条很明显的线.
水面的过渡处理,如果有场景的深度图,那么处理起来就特别方便,因为你就知道了场景中每个像素点的深度,和水平面一比较就知道了这个alpha过渡值.深度数据的获取,如果是延迟渲染,就从G-Buffer,前向渲染,就得自己渲染深度图(pre-z).但是对于手游,自己额外渲染深度图会有一定开销.如果抛弃这种处理方式,那么我们该怎么办呢?
一般有两种办法,一种是用额外一张alpha贴图来逐像素地保存这个alpha值,缺点是,如果水面大,这个alpha图就得很大,而且得美术来制作,修改起来也很麻烦.另外一种就是用水面网格顶点颜色来保存这个alpha值,这个alpha值,程序自动计算,不需要美术去处理.缺点是过渡效果是逐顶点的,和网格的密度有关系.毫无疑问,我选择第二种.
3.岸边自动生成动态海浪
我们首先需要一个Foam的纹理来模拟浪花.其次需要一个梯度图来实现一层一层的浪花的运动.再次我们需要知道浪花的运动方向,这个可以通过水的深浅决定,因为浪花总是从水深的地方移动到水浅的地方.
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
水面法线贴图来实现扰动,在太阳或者月亮的方向产生高光.
6.互动的水波纹
如果角色走进水面,应该产生波纹的.实现原理就是先渲染一张水波纹扰动图片,然后水面再根据这个图来扰动水面.
------------------------------------------------------------------------------------------------------------------
以上6大功能实现后,这个水面看起来相对完美了,基本满足了我这个强迫症患者的需求.其实还有些地方没处理的,比如水面的LOD,
水面的减面等等.
整个水面shader的编辑界面:
水面生成工具:
- // 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
- }