前面两篇提到场景和角色渲染的技术,这一篇讲一下水面.
水面最开始是美术制作的,一个大的面片来实现,对于湖泊多的场景,这个面片甚至场景那么大,我觉得这种做法不是很好,虽然只有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
5.法线和高光
水面法线贴图来实现扰动,在太阳或者月亮的方向产生高光.
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 m_VerticesList = new List();
List m_ColorsList = new List();
List m_UvList = new List();
List m_TrianglesList = new List();
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();
MeshRenderer mr = go.AddComponent();
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().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 _list = new List();
public bool demo;
public override void restart()
{
float aspectRatio = 1.0f;
if (_rt == null)
{
if (waterRenderer == null)
waterRenderer = GetComponent();
if (waterRenderer != null)
{
if (GetComponent() == null)
gameObject.AddComponent();
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 m_ReflectionCameras = new Dictionary();
private Dictionary m_RefractionCameras = new Dictionary();
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()
|| !GetComponent().sharedMaterial
|| !GetComponent().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().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().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 mysky = dest.GetComponent();
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();
reflectionCamera.enabled = false;
reflectionCamera.transform.position = transform.position;
reflectionCamera.transform.rotation = transform.rotation;
reflectionCamera.gameObject.AddComponent();
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();
refractionCamera.enabled = false;
refractionCamera.transform.position = transform.position;
refractionCamera.transform.rotation = transform.rotation;
refractionCamera.gameObject.AddComponent();
go.hideFlags = HideFlags.HideAndDontSave;
m_RefractionCameras[currentCamera] = refractionCamera;
}
}
}
bool FindHardwareWaterSupport()
{
if (!SystemInfo.supportsRenderTextures || !GetComponent())
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
}