《纪念碑谷》里有一关开始是一个宝箱展开后里面有一个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. CMoLineMgr

    #ifndef __E3GLOGOBJECTDB_H__ #define __E3GLOGOBJECTDB_H__ #include "PubCommon\Singleton.h" ...

  2. .NET:为什么不能在子类或外部发布C#事件

    背景 一个朋友问了一个问题:“为什么不能在子类或外部发布C#事件?”,我说我不知道,要看看生产的IL代码,下面我们看看. 测试 代码 using System; using System.Collec ...

  3. ExtJs4.0日期控件只显示年月按年月格式会跳月的解决办法

    如果是Ext.form.panel的话,只要设置一下属性就可以.如下代码: { text : '期间', width : 80, sortable : true, dataIndex : 'accou ...

  4. unity3D总结的一些细节,不注意有些要折腾非常多天!

    1. 注意!!ps保存图片时,若保存为ps格式,若关闭最大兼容将会导致unity导入失败!(n天) 2.switch 推断NGUI popuplist传来的value字符串时一定要trim一下去掉空格 ...

  5. 分享一个基于 Node.js 的 Web 开发框架 - Nokitjs

    简介 Nokit 是一个简单易用的基于 Nodejs 的 Web 开发框架,默认提供了 MVC / NSP / RESTful 等支持,并提供对应项目模板.管理工具. 资源 GitHub https: ...

  6. 不要用cudnn7.2,用7.3可以兼容

    tensorflow1.11.0 非常的过分了[捂脸]官网上写的cuda 9.0,cudnn 7.2.然而Nvidia官网上根本没有这两个的对应...之前一直都依赖anaconda的一键安装,被惯坏了 ...

  7. 对 getaddrinfo Android 返回错误 EAI_BADFLAGS

    我们尝试使用 getaddrinfo 对 Android API 14 及以上 (在 c + + 代码使用 NDK r12) 从 IPV4 获得合成的 IPV6 地址 address .这是在 IPV ...

  8. JNI/NDK开发指南(十)——JNI局部引用、全局引用和弱全局引用

    转自:http://blog.csdn.net/xyang81/article/details/44657385   这篇文章比较偏理论,详细介绍了在编写本地代码时三种引用的使用场景和注意事项.可能看 ...

  9. go语言基础之闭包捕获外部变量特点

    1.闭包捕获外部变量特点 示例: package main //必须 import "fmt" func main() { a := 10 str := "mike&qu ...

  10. C#特性杂谈

    文中充满了各种C#与其他语言的对比及吐槽, 希望介意者勿观… 当然, 鉴于太乱, 我怀疑有没有人能看完. 学习C# Hello World 变量与表达式 动态类型 值类型和引用类型 checked支持 ...