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人不等. 框架结构的可重用性.清晰程度是有代价的--团队的规模和项目的规模决定你要在这个上面付出多少 ...
随机推荐
- struts2 action通配符
首先,看一个struts2的配置文件: <package name="actions" extends="struts-default" namespac ...
- SPSS Clementine 数据挖掘入门3
转摘:http://www.cnblogs.com/dekevin/archive/2012/04/27/2473683.html 了解SPSS Clementine的基本应用后,再对比微软的SSAS ...
- JavaScript实现瀑布流
前端内容: 使用JavaScript和四个div,将照片放入四个div中 <!DOCTYPE html> <html lang="en"> <head ...
- js 获取url的get传值函数
function getvl(name) { var reg = new RegExp("(^|\\?|&)"+ name +"=([^&]*)(\\s| ...
- eclipse启动tomcat, http://localhost:8080无法访问的解决方案
问题:: tomcat在eclipse里面能正常启动,但在浏览器中访问http://localhost:8080/不能访问tomcat管理页面,且报404错误.同时其他项目页面也不能访问.访问的时候出 ...
- TCP/IP具体解释学习笔记——地址解析协议ARP
一 概述 我们知道,IP协议是用来在不同的物理网络之间数据传输的.要在不同的网络之间数据传输,至少须要将IP协议所用的地址转换成特定网络所使用的物理地址. 一般来说.就是将IPv4地址转换为mac地址 ...
- Linux统计/监控工具SAR详细介绍
转载:http://www.ctohome.com/FuWuQi/1b/688.html sysstat 工具简介 sysstat 是 Linux 系统中的常用工具包.它的主要用途是观察服务负载,比如 ...
- FileAlreadyExistsException: Output directory hdfs://ubuntu:9000/output09 already exists
14/07/21 17:49:59 ERROR security.UserGroupInformation: PriviledgedActionException as:chenlongquan ca ...
- cocos2d-x 音乐与音效
1.背景音乐 要使用一个音乐,首先要预加载这个音乐,预加载的方法如下 SimpleAudioEngine::sharedEngine()->preloadBackgroundMusic( CCF ...
- Tengine是由淘宝网发起的Web服务器项目。它在Nginx的基础上,针对大访问量网站的需求,添加了很多高级功能和特性
简介 Tengine是由淘宝网发起的Web服务器项目.它在Nginx的基础上,针对大访问量网站的需求,添加了很多高级功能和特性.Tengine的性能和稳定性已经在大型的网站如淘宝网,天猫商城等得到了很 ...