两个吓人的已经开始
细致入微的爸爸妈妈可能早已发现了,前段时间金狮子有关 3D 绘图绘图方面的该文输入比以后多了很多。
意念
接到很多源自 开发人员们进行咨询的 3D 工程项目有关问题。
在对这些工程项目进行市场需求沟通交流和控制技术软件控制系统深入探讨时,也斩获了许多有意思的肉体、感人的故事情节、新颖的计划。
独卷卷比不上众卷卷,期望能撷取出来,对有需要的人会略有协助。
既能照相狸尾豆,又能增长科学知识。
责任编辑结尾的图就是两个开发人员让我帮处置的工程项目,她们的市场需求是虚拟化 新浪网 3D 买车控制系统。
由于要在各网络平台正式发布,对照各控制技术栈后,最后优先选择了Cocos。
不过她们不熟识 Cocos Creator,特来求救。
晚期的新浪网买车,只需用两个微缩+领涨板块赖草两个可互动式流程方可。常用的效用如下表所示图右图:
从上图中可以明显看见,右上角早已已经开始形变。
像这样单纯的互动式流程或许早已不能满足现代人不断增长的化学物质人文市场需求。
如今的新浪网买车早已需要模拟现实环境、VR 查看、甚至还能像赛车游戏一样体验一把了。
然而,她们一已经开始发我的效用是像这样的:
这产品要是放出去,怕是会让人误会 Cocos Creator 的绘图能力吧。
金狮子觉得有必要拯救一下。
经过一番操作之后,效用如下表所示右图:
此效用主要用到了动态散射原理,但由于引擎目前没有内置动态散射组件,因此金狮子自己动手做了两个。
动态散射可以用在非常多的场合用于进一步增强环境效用。
比如,下面截图中的斗罗大陆-武魂殿大厅,整个地板就是带散射的,如果去掉散射,整个场景将失色许多。
基于这个动态散射Shader,金狮子又搭建了一些其他有意思场景(文末可查看高清效果音频和工程源文件):
接下来,让我们从最基础的空间几何已经开始,一步步推导公式,并实现上面的效用。
金狮小贴士: 推导过程非常单纯,童叟无欺。空间几何推导
基本原理
在中学物理课程中我们学过,平面镜中的虚像是由光的散射光线形成的,看见的虚像与实际的物体有关镜面对称,且大小相等,左右相反。如下表所示图右图:
结合 光路的可逆性 我们可以推导出:
如果将摄像机放到有关镜面对称的位置,移除镜子,再以等价的视线方向看物体。
此时所看见的镜头内容会与镜中镜头一致,如下表所示图右图:
有了上面的基础科学知识,我们的问题就变成了:
1、求摄像机有关镜面对称的位置2、求摄像机处于镜面后的视线方向平面对称点求解
书上和网络上有很多有关镜面对称的解题思路,但都是基于代数推导过程,不能直观的理解,且运算复杂。
金狮子今天以空间几何的方式给大家讲解:如何计算两个点有关任意平面的对称点。
金狮小贴士: 空间几何相比代数更直观,但有两个基本要求:
1、讲解问题的时候,需要配图。
2、需要一定的空间想象力。请大家先看上面这张图,点 Pm 是 点 P 有关平面 Plane(紫色线)的对称点,N 为平面的法向量。
接下来我们已经开始进行公式推导,大家一定要集中精力,不然一不留神推导过程就结束了,它真的单纯到让你难以相信。
设平面法向量为 N,到原点的距离为 d。
设点 P 到平面的距离为 Dist。
将点 P 沿 N 的反方向移动 2 倍 Dist 的距离,方可到达点 Pm 的位置。
从而得到公式:Pm = P + 2 * Dist * (-N)
由平面点法式有关科学知识可得: Dist = P·N – d 。
代入公式可以得到: Pm = P + 2 * (P·N – d)* (-N) 。
整理过后,最后的公式为:
在工程项目中的 Mirror.ts 文件里,金狮子写了两个 getMirrorPoint 函数,内容如下表所示:
金狮小贴士:
有关平面的表示法,请看金狮子的该文《Cocos Shader 入门基础六:平面、双面材质与自定义裁剪面》。
也可以查阅 白玉无冰 等社区 KOL 们写的 3D 数学 有关该文。通过 getMirrorPoint 函数,我们可以很容易求得两个点有关 3D 空间中任意平面的对称点。
平面对称向量求解
在三维空间中,每两个物体都有三个互相垂直的方向向量:
前方向量(Forward)上方向量(Up)右方向量(Right)我们至少需要求得两个方向向量才能确定两个物体各方向的旋转角度。
通常我们会优先选择 Forward 和 Up。
因为三个相向相互垂直,可以通过叉乘法则求得 Right 向量。
接下来,我们看看如何求得向量有关镜面对称的向量。
金狮小贴士:
摄像机并不是有关镜面对称的,它只是旋转到镜面背后。
大家可以用一张A4纸和自己的手机做实验,会发现只有 Forward 和 Up 是有关镜面对称的,Right 刚好是镜面对称方向的反方向。求方向向量的对称向量,可以借助上面的对称点公式。大家先看下面这张图:
我们以求 Forward 为例,可以采用下面的单纯步骤:
在 Forward 上找到两个点 Pf求得点 Pf 有关平面的对称点 Pfm点 Pfm – Pm 即为所求( Pm 是 P 有关平面的对称点)一切都是这么的直观、单纯、易懂。
既然点有自己有关平面对称的单纯公式,向量有没有直接的公式呢? 如果能够找到,我们应该是可以省下许多计算的。
由于向量是两个只有大小和方向,没有位置的量。
我们可以发现,在不改变法向量的情况下将平面在三维空间中平移,Forward 有关平面的对称向量是不会变的。
既然这样,我们就把平面移动到坐标原点,P点也移动到坐标原点。 此种情况下,P = (0,0,0) 且可以令 Pf = Forward, 代入公式可得:
Pfm – Pm = Pfm – P而 Pm = P = (0,0,0)可得:Pfm – Pm = Pfm最后,我们依旧可以采用镜面对称公式进行求解:
只是,其参数为向量,且 d 为 0, 整理后如下表所示:
金狮小贴士:在代码中,我们不用为向量对称新写函数,只需要在调用 getMirrorPoint 函数的时候 d 传 0 方可。金狮子搭了两个方便检查公式是否正确的测试组合,效用如下表所示:
镜面实现
镜面效用的实现,主要由两部分组成:
1、有关镜面翻转摄像机,并绘图到 RenderTexture2、使用投影纹理映射控制技术,对 RenderTexure 进行采样摄像机翻转
利用 getMirrorPoint 函数计算摄像机在镜像空间中的位置,如下表所示图右图:
利用 getMirrorPoint 函数计算摄像机的 Forward 和 Up 向量的镜像向量,如下表所示图右图:
们的摄像机翻转函数每帧都会执行,需要避免临时对象的产生。
最后,利用 Quat.fromViewUp 方法,求得最后的旋转四元数,并赋值给摄像机,如下表所示图右图:
金狮小贴士: 由于Cocos Creator 引擎默认的正方向为 **-Z (0,0,-1)**,我们的 forward 在使用 fromViewUp 时需要取反。投影纹理映射
投影纹理映射是指,将经过投影变换后的顶点信息用于计算 UV,进行纹理采样。
最后看见的结果,是以观察方向看过去的贴面效用。 镜面散射、ShadowMap、模型贴花等效用都是基于投影纹理映射控制技术呈现的。
要实现投影纹理映射只需要两步。
第一步:将投影过后的坐标信息从 vs 传到 fs,如下表所示图右图:
v_screenPos 是我们定义的两个 vs out 变量。
第二步: 执行透视除法,并将 xy 的值规范化到 [0.0, 1.0] 区间,如下表所示图右图:
经过了 MVP 变换的坐标,处于裁剪空间,再使用透视除法,可将此坐标变到 NDC 空间。
而 NDC 空间中 xy 的值在 [-1.0, 1.0] 区间,我们对其进行 *0.5 + 0.5 变换,可将 xy 的值规范化到 [0.0,1.0]区间内。
加入菲涅尔现象
到这一步,我们早已实现了基于任意平面的动态镜面散射效用。
但现实生活中的物体,并不是像镜子一样,在任意角度都具备很强的散射。
当视线与平面垂直时,它的散射最弱;当斜视与平面无限接近平行时,它的散射最强。这个现象就叫菲涅尔现象。
菲涅尔现象是一系列光学反应结果,如果要考虑到所有因素是不太科学的,但我们可以使用下面这个近似的公式:
每个参数含义如下表所示:
-V: 视线反方向N: 平面法线方向Ffactor: 视线因子Rmax: 物体最大散射系数Rmin: 物体最小散射系数Pow(Ffactor,p): 指数函数,表示Ffactor 的 p 次方Fresnel: 最后的菲涅尔因子通过调节 Rmax,Rmin,p 的值,我们可以非常容易地模拟出自然界中各物体表面的菲涅尔效用。 具体代码如下表所示:
从下图可以明显看见,越近的地砖,散射是越弱的。
散射掩码图
但当我们仔细观察身边的物体,某些物体的表面,部分散射很强,部分散射很弱。总不至于用非常多细小的平面来拼接吧?
一种常用的技巧就是:散射掩码图(Reflection Mask Map)。
我们用两个地砖效用为例,下图中,左边为颜色贴图,右边为掩码图。掩码图中越亮的区域表示散射越强,越暗的区域,表示散射越弱。
实现散射掩码非常单纯,只需要将掩码图乘以菲涅尔因子,从而达到控制最后散射率,如下表所示右图:
仔细观察,相邻两块地砖的接缝处几乎没有散射,就是因为掩码图中对应位置的像素值为黑色。如下表所示图右图:
扰动图
如果我们想要模拟出一些不太光滑的平面上的动态散射效用,又应该怎么做呢?
答案是:利用噪声图进行纹理采样干扰
噪声图几乎参与了所有非光滑表面效用的模拟场合,常用噪声图如下表所示右图:
单通道噪声图:只有两个通道,或者各通道的RGB值相等。
多通道噪声图:RGB+A通道分别存了不同的值,一般用于同时要分开扰动多个目标的情况。
我们先单纯的搭两个下面这样的场景,并创建两个动态散射的平面,效用如下表所示:
在 Shader 中加入 NoiseMap 有关参数,并在散射贴图的 uv 采样时进行干扰,如下表所示图右图:
reflNoiseScale: 控制噪声贴图的重复率reflNoiseMove: 控制噪声贴图 u 和 v 方向的流动速度reflNoiseStrengthen: 控制噪声干扰强度调节到适合的参数后,我们可以实现下面这样的效用:
看起来像水面了,但它只是单纯的反射+噪声扰动而已。
真正的水面绘图,至少还应该有水深、折射等效用。
思考题: 如果我们要实现下图的 巴黎街头艺术装置-环形镜,又当如何是好呢?。
金狮小贴士:当然可以使用这样的方法来拼装,但要注意性能问题。
两个折衷的计划是采用动态环境贴图实现,这样可以将场景绘图开销控制在六个面。DEMO与源码
DEMO内容
实现了第三人称主角摄像机控制器实现了 W A S D 角色移动控制器实现了任意平面散射组件 Mirror.ts实现了支持 投影纹理映射、掩码、噪声扰动的着色器 effect-mirror-pbr.effect/effect-mirror.effect两个城市风格的角色漫游场景两个动态散射的水面场景两个动态散射车展场景两个模拟的巴黎街头艺术装置-环形镜场景版权说明: 该文中使用的车模是免费模型,但不包含在本 DEMO 中,有需要的朋友可以在 Cocos Store 中搜索 车 方可找到。