Unity 3D 海水的实现2 折射与反射 离屏相机的渲染
Posted 暗光之痕
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity 3D 海水的实现2 折射与反射 离屏相机的渲染相关的知识,希望对你有一定的参考价值。
版本:unity 5.4.1 语言:C#
在上节博文实现后,我添加了一些场景的元素,海水的效果大概是这个样子的:
接下来的目标是实现海水的折射和反射,书中使用的Unity应该是4.x时代的,Unity自带基础包是5.x的,然后我再在网上看了一个例子,看了下来基本原理都差不多。
还记得移动端简单阴影的实现吧,对,就是添加一个相机把照到的玩家传给Shader后,显示在地上,原理是一样的。
首先获取到玩家的相机,新建相机到玩家当前相机,经过一个反射矩阵的旋转后,截取海平面以上的渲染,然后再将渲染出来的Texture传递给Shader处理;折射更加简单,不用矩阵旋转,当前位置的海平面以下渲染出Texture,再传递给Shader。
下面是代码,生成Mesh的代码,我就去掉了:
public class Ocean : MonoBehaviour
{
// 一片区域网格横纵数量
public int width = 32;
public int height = 32;
int g_height; // 组成网格横纵的线条数量
int g_width;
Vector2 sizeInv;
// 区域的数量和大小
public int tiles = 2;
public Vector3 size = new Vector3(150f, 1f, 150f);
// 材质
public Material material;
public Shader oceanShader;
public Transform player;
public Transform sun;
public Vector4 SunDir;
// 网格相关
Vector3[] vertices; //顶点
Vector3[] normals; //法线
Vector4[] tangents; //三角
Mesh baseMesh;
// LOD,越在靠后List的Mesh,网格越少
int maxLOD = 4;
List<List<Mesh>> tiles_LOD;
// 折射反射相关
public bool renderReflection = true; //是否启用反射折射
public int renderTexWidth = 128;
public int renderTexHeight = 128;
RenderTexture reflectionTexture = null;
RenderTexture refractionTexture = null;
Camera offscreenCam = null;
bool reflectionRefractionEnabled = false; //是否初始化完成
// Use this for initialization
void Start()
{
// 折射反射
sizeInv = new Vector2(1f / size.x, 1f / size.z);
SetupOffscreenRendering(); // 添加离屏相机
if (!renderReflection)
EnableReflection(false);
else
EnableReflection(true);
// 计算线条数量
g_height = height + 1;
g_width = width + 1;
// LOD,Mesh所在的List的LOD List编号越小,Mesh的网格越多
tiles_LOD = new List<List<Mesh>>();
for (int LOD = 0; LOD < maxLOD; LOD++)
{
tiles_LOD.Add(new List<Mesh>());
}
for (int y = 0; y < tiles; ++y)
{
for (int x = 0; x < tiles; ++x)
{
Debug.Log("创建了一片水");
float cy = y - Mathf.Floor(tiles * 0.5f);
float cx = x - Mathf.Floor(tiles * 0.5f);
// 创建一片水
GameObject tile = new GameObject("WaterTile");
// 坐标以当前节点为中心
tile.transform.parent = transform;
tile.transform.localPosition = new Vector3(cx * size.x, 0f, cy * size.z);
// 添加Mesh渲染组件
tile.AddComponent<MeshFilter>();
tile.AddComponent<MeshRenderer>().material = material;
tile.layer = LayerMask.NameToLayer("Water");
tiles_LOD[0].Add(tile.GetComponent<MeshFilter>().mesh);
}
}
GenerateHeightmap();
}
// 设置折射反射
void SetupOffscreenRendering()
{
// 创建折射反射图
RecalculateRenderTextures();
// 创建Camera实现离屏渲染
GameObject cam = new GameObject();
cam.name = "DeepWaterOffscreenCam";
cam.transform.parent = transform;
offscreenCam = cam.AddComponent<Camera>();
offscreenCam.clearFlags = CameraClearFlags.Color;
offscreenCam.depth = -1;
offscreenCam.enabled = false;
}
// 当设置reflection和refraction被禁用的时候,设置lod为1
void EnableReflection(bool isActive)
{
renderReflection = isActive;
if (!isActive) // 关闭反射折射,使用波光粼粼的图片替代
{
material.SetTexture("_Reflection", null);
material.SetTexture("_Refraction", null);
oceanShader.maximumLOD = 1;
}
else // 启用反射折射
{
OnDisable();
oceanShader.maximumLOD = 2;
RecalculateRenderTextures();
}
}
// 重新生成反射折射的缓存图片
void RecalculateRenderTextures()
{
if (renderReflection)
{
reflectionTexture = new RenderTexture(renderTexWidth, renderTexHeight, 0);
refractionTexture = new RenderTexture(renderTexWidth, renderTexHeight, 0);
reflectionTexture.wrapMode = TextureWrapMode.Clamp;
refractionTexture.wrapMode = TextureWrapMode.Clamp;
reflectionTexture.isPowerOfTwo = true;
refractionTexture.isPowerOfTwo = true;
material.SetTexture("_Reflection", reflectionTexture);
material.SetTexture("_Refraction", refractionTexture);
material.SetVector("_Size", new Vector4(size.x, size.y, size.z, 0f));
}
}
// 删除反射折射使用的缓存图片
void OnDisable()
{
if (reflectionTexture != null)
{
DestroyImmediate(reflectionTexture);
}
if (refractionTexture != null)
{
DestroyImmediate(refractionTexture);
}
reflectionTexture = null;
refractionTexture = null;
}
// 折射反射渲染物体
void RenderObject()
{
if (Camera.current == offscreenCam)
return;
if (reflectionTexture == null || refractionTexture == null)
return;
if (renderReflection)
RenderReflectionAndRefraction();
}
public LayerMask renderLayers = -1;
// 具体的渲染,使用第二个相机拷贝当前相机的设置
void RenderReflectionAndRefraction()
{
// 获取当前角色身上的主相机
Camera renderCamera = Camera.main;
Matrix4x4 originalWorldToCam = renderCamera.worldToCameraMatrix; // 获取世界到相机的矩阵,如果改变了相机的矩阵就不会再根据原Transform渲染,除非调用ResetWorldToCameraMatrix
int cullingMask = ~(1 << 4) & renderLayers.value; //剪裁Mask,忽略水本身
// 计算反射矩阵
float d = -transform.position.y;
Matrix4x4 reflection = Matrix4x4.zero;
CameraHelper.CalculateReflectionMatrix(ref reflection, new Vector4(0f, 1f, 0f, d)); //这里不明白,总之弄出了反射矩阵
// 根据反射矩阵计算离屏相机位置和矩阵
offscreenCam.backgroundColor = RenderSettings.fogColor;
offscreenCam.transform.position = reflection.MultiplyPoint(renderCamera.transform.position); //当前相机置换到反射矩阵中
offscreenCam.transform.rotation = renderCamera.transform.rotation;
offscreenCam.worldToCameraMatrix = originalWorldToCam * reflection;
offscreenCam.cullingMask = cullingMask; //设置剪裁mask
offscreenCam.targetTexture = reflectionTexture; //将反射缓存图片添加到离屏相机里,跟之前阴影是一个原理
// 因为反射截取到的图片是翻转的,所以需要设置翻转
GL.SetRevertBackfacing(true);
// 获取剪裁平面,transform.position.y是当前海水的高度,最后两个值的正负表示剪裁的方向
Vector4 cameraSpaceClipPlane = CameraHelper.CameraSpacePlane(offscreenCam, new Vector3(0.0f, transform.position.y, 0.0f), Vector3.up, 1.0f);
Matrix4x4 projection = renderCamera.projectionMatrix; //获得渲染相机的投影矩阵
Matrix4x4 obliqueProjection = projection;
offscreenCam.fieldOfView = renderCamera.fieldOfView; //设置FOV
offscreenCam.aspect = renderCamera.aspect; //设置宽高比
CameraHelper.CalculateObliqueMatrix(ref obliqueProjection, cameraSpaceClipPlane);
// 开始真正的渲染
offscreenCam.projectionMatrix = obliqueProjection;
if (!renderReflection)
offscreenCam.cullingMask = 0;
offscreenCam.Render();
GL.SetRevertBackfacing(false);
// 折射渲染
offscreenCam.cullingMask = cullingMask;
offscreenCam.targetTexture = refractionTexture;
obliqueProjection = projection;
// 将渲染相机的各个参数设置给离屏相机
offscreenCam.transform.position = renderCamera.transform.position;
offscreenCam.transform.rotation = renderCamera.transform.rotation;
offscreenCam.worldToCameraMatrix = originalWorldToCam;
// 获取剪裁平面,计算投影矩阵
cameraSpaceClipPlane = CameraHelper.CameraSpacePlane(offscreenCam, new Vector3(0.0f, transform.position.y, 0.0f), Vector3.up, -1.0f);
CameraHelper.CalculateObliqueMatrix(ref obliqueProjection, cameraSpaceClipPlane);
offscreenCam.projectionMatrix = obliqueProjection;
offscreenCam.Render();
offscreenCam.projectionMatrix = projection;
offscreenCam.targetTexture = null;
}
// 初始化Mesh信息,请参考上一节
void GenerateHeightmap(){}
// 这边应该是Update的,但写在Update中会报GUI Window tries to begin rendering while something else has not finished rendering的错误
void OnGUI()
{
// 设置玩家、太阳角度,并更新反射折射,折射反射是根据玩家视角来计算的
if (player == null)
player = GameObject.FindGameObjectWithTag("Player").GetComponent<Transform>();
if (sun != null)
{
SunDir = sun.transform.forward;
material.SetVector("_SunDir", SunDir);
}
if (renderReflection)
RenderObject();
}
}
然后是CameraHelper的脚本:
public class CameraHelper
{
private static float sgn(float a)
{
if (a > 0.0f)
return 1.0f;
if (a < 0.0f)
return -1.0f;
return 0.0f;
}
public static void CalculateObliqueMatrix(ref Matrix4x4 projection, Vector4 clipPlane)
{
Vector4 q = projection.inverse * new Vector4(sgn(clipPlane.x), sgn(clipPlane.y), 1.0f, 1.0f);
Vector4 c = clipPlane * (2.0F / (Vector4.Dot(clipPlane, q)));
projection[2] = c.x - projection[3];
projection[6] = c.y - projection[7];
projection[10] = c.z - projection[11];
projection[14] = c.w - projection[15];
}
public static Vector4 CameraSpacePlane(Camera cam, Vector3 pos, Vector3 normal, float sideSign)
{
Vector3 offsetPos = pos + normal * 0.02f;
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));
}
public 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[0] * plane[3]);
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[1] * plane[3]);
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[2] * plane[3]);
reflectionMat.m30 = 0F;
reflectionMat.m31 = 0F;
reflectionMat.m32 = 0F;
reflectionMat.m33 = 1F;
}
}
花了三天时间终于整理出来了比较精简的代码,不过渲染部分的矩阵,我还是没有太理解。
这边提一下作者的Shader,因为是老版本创建的Shader,所以如果颜色空间不使用Gamma的话,反射的倒影会用问题。还有作者的代码是有点问题的,如果想直接用他的代码,最好对比一下我这边的代码,把一些错误排掉。
完成之后的效果是这样的:
有折射和反射效果,但总感觉这个颜色很不对劲,没错,这是上一节留下的一个BUG,Mesh中没有添加法线,你可以自己尝试在Mesh初始化的时候添加,或者像我这样马后炮:
normals = new Vector3[baseMesh.vertices.Length];
for (int i = 0; i < baseMesh.vertices.Length; ++i)
normals[i] = Vector3.Normalize(new Vector3(0, 1f, 0));
for (int k = 0; k < tiles_LOD[0].Count; ++k)
{
Mesh meshLOD = tiles_LOD[0][k];
meshLOD.vertices = baseMesh.vertices;
meshLOD.normals = normals;
meshLOD.tangents = baseMesh.tangents;
}
这段代码放到OnGUI中,不过只要运行一次就好,不然电脑会很卡。。
最终效果:
功夫不负有心人啊,花了我很长的时间,不过对比第一张图和最后一张图的效果,我感觉一切都是值得的,也了解了Unity4.x和5.x的一些区别,总的来说大概明白了折射反射的原理。
下一节的文章,我想挑战一下自己深入了解一下反射矩阵,波浪的东西留到后面再做。
以上是关于Unity 3D 海水的实现2 折射与反射 离屏相机的渲染的主要内容,如果未能解决你的问题,请参考以下文章
Unity Shader ------ 高级纹理之立方体纹理及光线反射折射的实现