《纪念碑谷》里有一关开始是一个宝箱展开后里面有一个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的更多相关文章

  1. Unity Water Shader

    上图是一个物体浸入水中的效果 原理 我们使用相机渲染的整个场景的深度图减去需要忽略的模型的深度,这里忽略的是图中蓝色部分,就保留了其他的深度值. 用到Main Camera渲染的深度贴图: sampl ...

  2. 如何在Unity中分别实现Flat Shading(平面着色)、Gouraud Shading(高洛德着色)、Phong Shading(冯氏着色)

    写在前面: 先说一下为什么决定写这篇文章,我也是这两年开始学习3D物体的光照还有着色方式的,对这个特别感兴趣,在Wiki还有NVIDIA官网看了相关资料后,基本掌握了渲染物体时的渲染管道(The re ...

  3. unity技巧

    在之前的程序编写过程中,虽然对相关的方法进行了实例化,但是在运行的时候总是会出现“未将对象引用设置到对象的实例”,出现该种问题的原因是由于在实例化后,没有对实例化进行引用赋值,所以导致相关变量无法在其 ...

  4. unity的一些重要技巧(转)【整理他人的东西】

    刚开始学习Unity3D时间不长,在看各种资料.除了官方的手册以外,其他人的经验也是非常有益的.偶尔看到老外这篇文章,觉得还不错,于是翻译过来和大家共享.原文地址: http://devmag.org ...

  5. Unity中嵌入网页插件Embedded Browser2.1.0

    背景 最近刚换了工作,新公司不是做手游的,一开始有点抵触,总觉得不是做游戏自己就是跨行了,认为自己不对口,但是慢慢发现在这可以学的东西面很广,所以感觉又到了打怪升级的时候了,老子就在这进阶了. 一进公 ...

  6. 使用Unity3D的50个技巧

    使用Unity3D的50个技巧 刚开始学习Unity3D时间不长,在看各种资料.除了官方的手册以外,其他人的经验也是非常有益的.偶尔看到老外这篇文章,觉得还不错,于是翻译过来和大家共享.原文地址:ht ...

  7. 使用Unity3D的50个技巧:Unity3D最佳实践

    翻译故事 原文:http://devmag.org.za/2012/07/12/50-tips-for-working-with-unity-best-practices/ 这篇技巧,我自己也在翻译, ...

  8. 使用Unity3D的50个技巧:Unity3D最佳实践

    转自:http://www.tuicool.com/articles/buMz63I  刚开始学习unity3d时间不长,在看各种资料.除了官方的手册以外,其他人的经验也是非常有益的.偶尔看到老外这篇 ...

  9. Unity3D游戏开发最佳实践20技巧(一)

    关于这些技巧这些技巧不可能适用于每一个项目. 这些是基于我的一些项目经验.项目团队的规模从3人到20人不等. 框架结构的可重用性.清晰程度是有代价的--团队的规模和项目的规模决定你要在这个上面付出多少 ...

随机推荐

  1. Android支付接入之Google In-app-Billing

    原文链接:http://www.mobile-open.com/2016/966337.html 因为公司需要接入Google的应用内支付(即Google的in-app Billing V3),接入过 ...

  2. javascript基础编程の变量、对象、数据类型及函数

    在web标准中.网页由结构.表现形式和行为三个部分组成. 结构标准---->XHTML: 表现形式标准----->CSS: 行为标准----->javascript: javascr ...

  3. JDK JRE区别

    JDK里面的工具也是用JAVA编写的,它们本身运行的时候也需要一套JRE,如C:/Program Files/Java/jdk1.5.x/目录下的JRE.而C:/Program Files/Java/ ...

  4. Mysql 会导致锁表的语法

    最近再找一些Mysql锁表原因,整理出来一部分sql语句会锁表的,方便查阅,整理的不是很全,都是工作中碰到的,会持续更新 笔者能力有限,如果有不正确的,或者不到位的地方,还请大家指出来,方便你我,方便 ...

  5. 用10张图来看机器学习Machine learning in 10 pictures

    I find myself coming back to the same few pictures when explaining basic machine learning concepts. ...

  6. sonar使用故障Unable to load component class org.sonar.scanner.report.ActiveRulesPublisher/Unable to load component interface org.sonar.api.batch.rule.ActiveRules: NullPointerException

    nginx后两个sonar负载分担 解决办法 Credit to @teryk-sonarsource-team, just making it an answer: Delete the direc ...

  7. java.lang.Integer can not be cast to java.lang.Long

    hibernate 查询出来的结果 id 是int类型,xml文件配置的是int, 在jython 中调用hibernate 进行查询.字段的类型是java.lang.Long, 直接将id 传进去, ...

  8. ECShop 2.x 3.0代码执行漏洞分析

    0×00 前言 ECShop是一款B2C独立网店系统,适合企业及个人快速构建个性化网上商店.2.x版本跟3.0版本存在代码执行漏洞. 0×01 漏洞原理 ECShop 没有对 $GLOBAL[‘_SE ...

  9. CUDA使用Event进行程序计时

    GPGPU是众核设备,包含大量的计算单元,实现超高速的并行. 使用CUDA在nvidia显卡上面编程时,可以使用CUDA提供的Event进行程序计时. 当然,每种编程语言基本都提供了获取系统时间的函数 ...

  10. 作用JavaScript访问和操作数据库

    JS操作 Access 数据库 <SCRIPT LANGUAGE="JavaScript"> <!-- var filePath = location.href. ...