前面两篇提到场景和角色渲染的技术,这一篇讲一下水面.
水面最开始是美术制作的,一个大的面片来实现,对于湖泊多的场景,这个面片甚至场景那么大,我觉得这种做法不是很好,虽然只有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; #endif5.法线和高光
水面法线贴图来实现扰动,在太阳或者月亮的方向产生高光.
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 }