unity, water cube
《纪念碑谷》里有一关开始是一个宝箱展开后里面有一个water cube,其中还有小鱼在游。如下截图:

因为我们知道《纪念碑谷》是unity做的,而现在正开始学unity,所以也想做一个类似的。
unity5的standard assets里面有一个WaterProDaytime,折射和反射都有,开始以为用它一拼就完事儿了,可没想到这个东西只能平放效果才对,竖着放效果就不对了,如下图所示:
(下面水平放置的water水波纹是正常的,但上面竖立放置的water水纹变成竖直的长条,这种效果对于我们想实现的water cube来说是不可接受的)

肯定是官方实现这个效果的人下意识假定我们使用它时一定是会水平放置。于是只好去看它的实现代码,看哪里使用了“水平放置假定”,找到了两处:
第一处:
FXWaterPro.shader中的顶点shader中有下面代码:
// scroll bump waves
float4 temp;
float4 wpos = mul (_Object2World, v.vertex);
temp.xyzw = wpos.xzxz * _WaveScale4 + _WaveOffset;
o.bumpuv0 = temp.xy;
o.bumpuv1 = temp.wz;
其中这一句:temp.xyzw = wpos.xzxz * _WaveScale4 + _WaveOffset;它用顶点世界坐标的xz分量来生成水面凹凸(bump)贴图的纹理坐标,这显然是假定水面是水平放置(或至少水面在XZ平面上投影不为零)。
最具通用性的改法是把水面的平面方程传入顶点shader,在其中计算wpos在此平面上的投影坐标projPos,然后再将projPos转化到局部空间得到projPosInLocalSpace,然后再temp.xyzw = projPosInLocalSpace.xzxz * _WaveScale4 + _WaveOffset;但是等一下,由于wpos是水面的顶点世界坐标,所以其在水面的投影projPos不就是wpos本身吗!然后将projPos转化成局部空间也就是将wpos转化到局部空间,但由于世界顶点坐标wpos就是由局部顶点坐标v.vertex转来的,所以wpos转到局部空间的结果不就是v.vertex吗!所以最后结论是起初直接用局部坐标v.vertex就ok了。即上面代码改为:
// scroll bump waves
float4 temp;
float4 wpos = v.vertex;//mul (_Object2World, v.vertex);
temp.xyzw = wpos.xzxz * _WaveScale4 + _WaveOffset;
o.bumpuv0 = temp.xy;
o.bumpuv1 = temp.wz;
这就是对任意角度水面都正确的代码了。
第二处:
在Water.cs中有下面代码:
// find out the reflection plane: position and normal in world space
Vector3 pos = transform.position;
Vector3 normal = transform.up;
// Optionally disable pixel lights for reflection/refraction
int oldPixelLightCount = QualitySettings.pixelLightCount;
if (disablePixelLights)
{
QualitySettings.pixelLightCount = 0;
}
UpdateCameraModes(cam, reflectionCamera);
UpdateCameraModes(cam, refractionCamera);
// Render reflection if needed
if (mode >= WaterMode.Reflective)
{
// Reflect camera around reflection plane
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;
// Setup oblique projection matrix so that near plane is our reflection
// plane. This way we clip everything below/above it for free.
Vector4 clipPlane = CameraSpacePlane(reflectionCamera, pos, normal, 1.0f);
reflectionCamera.projectionMatrix = cam.CalculateObliqueMatrix(clipPlane);
reflectionCamera.cullingMask = ~(1 << 4) & reflectLayers.value; // never render water layer
reflectionCamera.targetTexture = m_ReflectionTexture;
GL.invertCulling = true;
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 = false;
GetComponent<Renderer>().sharedMaterial.SetTexture("_ReflectionTex", m_ReflectionTexture);
}
其中如下两句:
Vector3 euler = cam.transform.eulerAngles;
reflectionCamera.transform.eulerAngles = new Vector3(-euler.x, euler.y, euler.z);
意思是想把反射相机transform姿态设成与当前相机transform姿态关于水平面(XZ平面)对称,所以这句含有“水平放置假定”。
一般性的做法应该是让反射相机transform姿态与当前相机transform姿态关于反射面对称,可如下计算:
1,计算反射相机的位姿矩阵:
由于上面代码中已经求出了镜像矩阵reflection,所以反射向机的位姿矩阵:
reflectCamMatrix=reflection*cam.transform.localToWorldMatrix。
(注意:镜像矩阵reflection与其逆矩阵是同一个矩阵,因为镜像的镜像就是本身。)
2,由位姿矩阵reflectCamMatrix提取姿态矩阵reflectCamRotationMatrix。
3,将姿态矩阵reflectCamRotationMatrix转成四元数,赋值给反射相机的transform.rotation。
关于由矩阵提取 位置、姿态 和 缩放,参考:http://forum.unity3d.com/threads/how-to-assign-matrix4x4-to-transform.121966/
不过真的需要做这些事儿吗?看上面代码中有这样一句:
reflectionCamera.worldToCameraMatrix = cam.worldToCameraMatrix * reflection;
查看Camera.worldToCameraMatrix的文档,写道:
If you change this matrix, the camera no longer updates its rendering based on its Transform. This lasts until you call ResetWorldToCameraMatrix.
是说,reflectionCamera.worldToCameraMatrix被设置以后,渲染就不再按reflectionCamera.transform走了(除非再调用reflectionCamera.ResetWorldToCameraMatrix)。因此代码中后面再对reflectionCamera.transform进行设置其实是多余、无效的,即上面代码中蓝字部分都是多余的。至于作者为何要写这些代码,我想可能是为了体现思维严谨吧。
为了简单验证我们的结论,可以把蓝色部分的代码全部删除,或者改成别的什么值,然后运行,可以看到渲染结果确实不受影响。
于是我们的最终结论是:虽然reflectionCamera.transform.eulerAngles = new Vector3(-euler.x, euler.y, euler.z);这句代码包含“水平放置假定”,但是由于其根本不起作用,所以整个Water.cs脚本仍然是具有通用性的。
另外要注意,由于上面代码中有下面两句:
Vector3 normal = transform.up;
Vector4 reflectionPlane = new Vector4(normal.x, normal.y, normal.z, d);
也就是说,它是将挂有Water.cs脚本的gameObject的transform.up用作反射面的法线,这就要求我们为此gameObject添加的mesh面片在未经任何变换之前必须是法线朝上的。如果我们想得到倾斜或者竖立的水面,我们应通过调整gameObject的rotation来实现。
所以,我们不能用unity里自带的Quad作为水面gameObject的mesh,因为它原始是竖立的。所以我们或者用建模软件建一个原始即为法线向上的面片模型导进unity中来,或者在unity中直接用脚本生成一个这样的面片,我采用的是后者,生成面片的脚本如下:
(生成一个法线为Y轴正方向的单位面片)
参考:http://www.cnblogs.com/wantnon/p/4522415.html
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
public class genQuadFacePY : MonoBehaviour {
void Awake() {
gameObject.GetComponent<MeshFilter> ().mesh = CreateMeshFacePY (1,1);
}
Mesh CreateMeshFacePY(float width, float height)
{
Mesh m = new Mesh();
m.name = "quadFacePY";
//note: unity is left-hand system
m.vertices = new Vector3[] {
new Vector3(-width/2, 0, -height/2),
new Vector3(-width/2, 0, height/2),
new Vector3(width/2, 0, height/2),
new Vector3(width/2, 0, -height/2)
};
m.uv = new Vector2[] {
new Vector2 (0, 0),
new Vector2 (0, 1),
new Vector2 (1, 1),
new Vector2 (1, 0)
};
m.triangles = new int[] { 0, 1, 2, 0, 2, 3};
m.RecalculateNormals();
m.RecalculateBounds();
return m;
}
}
最后注意事项:
1,组成water cube的六个水面一定要用六个不同的material,万万不可共用同一个material。
2,WaterProDaytime在Orthographic相机下有bug。
详情参考:http://www.cnblogs.com/wantnon/p/4569096.html
---------------------------------------------------
最后得到的water cube截图:

补充:
一种更简单的waterCube的实现方法是使用GrabPass。
unity, water cube的更多相关文章
- Unity Water Shader
上图是一个物体浸入水中的效果 原理 我们使用相机渲染的整个场景的深度图减去需要忽略的模型的深度,这里忽略的是图中蓝色部分,就保留了其他的深度值. 用到Main Camera渲染的深度贴图: sampl ...
- 如何在Unity中分别实现Flat Shading(平面着色)、Gouraud Shading(高洛德着色)、Phong Shading(冯氏着色)
写在前面: 先说一下为什么决定写这篇文章,我也是这两年开始学习3D物体的光照还有着色方式的,对这个特别感兴趣,在Wiki还有NVIDIA官网看了相关资料后,基本掌握了渲染物体时的渲染管道(The re ...
- unity技巧
在之前的程序编写过程中,虽然对相关的方法进行了实例化,但是在运行的时候总是会出现“未将对象引用设置到对象的实例”,出现该种问题的原因是由于在实例化后,没有对实例化进行引用赋值,所以导致相关变量无法在其 ...
- unity的一些重要技巧(转)【整理他人的东西】
刚开始学习Unity3D时间不长,在看各种资料.除了官方的手册以外,其他人的经验也是非常有益的.偶尔看到老外这篇文章,觉得还不错,于是翻译过来和大家共享.原文地址: http://devmag.org ...
- Unity中嵌入网页插件Embedded Browser2.1.0
背景 最近刚换了工作,新公司不是做手游的,一开始有点抵触,总觉得不是做游戏自己就是跨行了,认为自己不对口,但是慢慢发现在这可以学的东西面很广,所以感觉又到了打怪升级的时候了,老子就在这进阶了. 一进公 ...
- 使用Unity3D的50个技巧
使用Unity3D的50个技巧 刚开始学习Unity3D时间不长,在看各种资料.除了官方的手册以外,其他人的经验也是非常有益的.偶尔看到老外这篇文章,觉得还不错,于是翻译过来和大家共享.原文地址:ht ...
- 使用Unity3D的50个技巧:Unity3D最佳实践
翻译故事 原文:http://devmag.org.za/2012/07/12/50-tips-for-working-with-unity-best-practices/ 这篇技巧,我自己也在翻译, ...
- 使用Unity3D的50个技巧:Unity3D最佳实践
转自:http://www.tuicool.com/articles/buMz63I 刚开始学习unity3d时间不长,在看各种资料.除了官方的手册以外,其他人的经验也是非常有益的.偶尔看到老外这篇 ...
- Unity3D游戏开发最佳实践20技巧(一)
关于这些技巧这些技巧不可能适用于每一个项目. 这些是基于我的一些项目经验.项目团队的规模从3人到20人不等. 框架结构的可重用性.清晰程度是有代价的--团队的规模和项目的规模决定你要在这个上面付出多少 ...
随机推荐
- 掌握11项技能,你就是优秀的前端开发project师
导读: 你或许会认为前端开发是一个非常easy的工作,对呀,你就是刚刚从网页设计转型过来的.但当你深入当中时,一定会发现好像前端开发不是那么简单,光站点性能优化.响应式.框架就让你焦头烂额, 确实,做 ...
- C语言跟内存分配方式-alloc malloc calloc
转载:http://blog.csdn.net/ubuntulover/article/details/7581317 (1) 从静态存储区域分配.内存在程序编译的时候就已经分配好,这块内存在程序的整 ...
- 电子书mobi的格式详解
https://wiki.mobileread.com/wiki/MOBI#Format Like PalmDOC, the Mobipocket file format is that of a s ...
- [Android Pro] android 杀死进程的方法
1: 杀死自己进程的方法 android.os.Process.killProcess(Process.myPid()); 2:杀死别人进程的方法(不能杀死自己) -------a: activity ...
- C++内存管理学习堆和栈
来源:http://c.chinaitlab.com/basic/936306_2.html 一 C++内存管理 1.内存分配方式 在讲解内存分配之前,首先,要了解程序在内存中都有什么区域,然后再详细 ...
- Resin install document
Centos6快速安装文档 resin3.1.13 软件下载地址: http://caucho.com/products/resin/download/gpl#download #系统环境[root@ ...
- [Python爬虫] 之八:Selenium +phantomjs抓取微博数据
基本思路:在登录状态下,打开首页,利用高级搜索框输入需要查询的条件,点击搜索链接进行搜索.如果数据有多页,每页数据是20条件,读取页数 然后循环页数,对每页数据进行抓取数据. 在实践过程中发现一个问题 ...
- 使用dulilib DirectUI库(一)
1.在创建的窗口类里面 需要继承CWindowWnd.INotifyUI 对于CWindowWnd里面的方法: 实现;,重载virtualUINTGetClassStyle()const;返回窗口的风 ...
- Win10系统开启Linux Bash命令行
Win10系统开启Linux Bash命令行 导读 在Build2016上微软为了拉拢开发者发了个大招,那就是Win10一周年更新集成原生Linux Bash命令行功能,这将允许开发者或用户在Wind ...
- 比较String.substring()、String.slice()、String.substr()的区别
String.substring().String.slice().String.substr()这三者都能从String字符串中截取一部分,那么它们在使用上有什么不同么? 一.slice() 方法提 ...