一墩难求?莫慌!Cocos Creator 带你30分钟拥有一只专属冰墩墩
看到了没,看到了没,看到了没?
上面这只被套在塑料壳里,也挡不住它继续蹦哒的“熊猫”,就是这次的冬奥会的吉祥物:冰墩墩。
小萌新一出道,就迅速火爆全网。
这几天总是听到有人在唱:“我只想要一只冰墩墩……”,看来,还真是一墩难求啊。
既然买不到(起),那做一个不行吗?要做就要做一个下面这样的:
今天麒麟子就手把手,教大家用 Cocos Creator 制作一只属于自己的冰墩墩。
本次的 DEMO 麒麟子共做了10个小时左右,迫不及待想要上(白)手(嫖)的朋友,可以滚动到文末点击【阅读原文】下载(免费的哟)。
一、去搞个 3D 模型
首先去网上找一个冰墩墩 3D 模型,如果有格式方面的问题,可以放入 Blender 等软件中调整,大概找到的模型和下图中的差不多。
二、导入 Cocos Creator
调整好模型之后,导出为引擎可识别的模型格式(本文用的是 *.glb 格式)。
将导出的文件导入到 Cocos Creator 中(麒麟子目前用的 v3.4.1 版本)。本文使用的冰墩墩 3D 模型由3个部分组成。
身体 胸圈 外壳
如下图所示:
不要忘了设置天空盒,并开启 IBL。
三、调整材质
默认的材质已经很适合身体和胸圈了,所以,我们重点要处理的是外壳效果。
新建一个材质,将材质的 technique 切换为 transparent 。 调节 Albedo 的颜色和 Alpha 值,使外壳透明度和颜色值是自己看上去觉得喜欢的。 调节 roughness 和 matellic 参数,形成高光和反射 并按下图中调节参数(具体参数根据个人喜好,不需要严格遵守)。
可能的效果如下图所示:
四、搭配场景
1、新建了一个圆柱体,调节适合的大小,并将我们制作好的冰墩墩放到上面。如下图所示:
2、新建几个 Quad,经过一系列的缩放、旋转、平移后,选两个面贴上贴图,就可以得到下面这样的台历:
麒麟子还写了一个简单的脚本,用于定时切换台历贴图,大家可以在 DEMO 源码中找到。
五、光影与视觉调节
1、开启 ShadowMap 并调整光源方向,使之与环境贴图的光线方向吻合。
2、调整好摄像机位置。
这里麒麟子写了一个脚本让摄像机围绕着冰墩墩旋转。
最终效果如下图所示:
好了,我们的冰墩墩就做好了。 我知道骗不了人民群众雪亮的眼睛,所以自己先把这话删了。
疑惑三连:
桌子上的倒影呢? 冰墩墩身上的 Blingbling 呢? 冰墩墩边缘看上去的轮廓效果呢?
六、桌子上的倒影
桌子上的倒影采用的是实时反射效果进行渲染的。核心思路如如下:
1、渲染反射的摄像机
在中学时,我们学过上图中的平面镜成像原理。
根据这一原理我们知道,要想获得平面镜中的内容,只需要计算出物体关于镜面对称的空间数据(坐标、旋转)即可。
但整个世界的对象非常多,更高效的方法是计算出摄像机关于镜面的空间数据,生成镜像摄像机,并用这个摄像机进行渲染。
渲染反射的摄像机需要将内容输出到渲染纹理。
关于镜像矩阵的推导,请看
Mirror.ts
。
2、自定义用户裁剪面
当摄像机镜像翻转后,可能遇上某物体处于镜面后,且刚好在镜像摄像机视野内的情况。
这种情况下,会渲染出不应该被渲染的物体(我们只想要镜面前方的物体),严重情况下,摄像机会被物体遮挡,无法看到(渲染)前面的物体,此时就要用到自定义用户裁剪面。
自定义用户裁剪面的基本逻辑是:若一个像素处于某平面之前,则保留,若处于平面之后,则丢弃(discard)。
关于 3D 编程中的平面相关内容不属于本文范围,大家可自行搜索,或等麒麟子后面的教程分享。
3、全局 Uniform
由于所有参与渲染的物体都需要进行用户自定义裁剪面的判断,而目前引擎版本没有提供自定义全局 Uniform 的机制。
当镜面发生改变时,需要遍历所有物体找到材质,设置相关 uniform 参数。
为了解决这样的麻烦事,麒麟子灵光一闪:不让新增,还不让借吗?空闲的全局变量是否可以借来用用?
在查阅了相关代码后,麒麟子找到了几个空闲的全局变量分量:
cc_time.w cc_fogBase.w cc_fogAdd.w cc_nearFar.zw
Shader代码中如下使用:
void checkClipPlane(){
vec4 clipPlane = vec4(0.0,0.0,0.0,0.0);
clipPlane.w = cc_time.w;
clipPlane.x = cc_fogBase.w;
clipPlane.y = cc_nearFar.z;
clipPlane.z = cc_nearFar.w;
bool enabled = (clipPlane.x != 0.0) || (clipPlane.y != 0.0) || (clipPlane.z != 0.0);
float dist = dot(v_position,clipPlane.xyz) + clipPlane.w;
if( enabled && dist <= 0.0 ){
discard;
}
}
与之协作的 TypeScript 代码请查看 Demo 中的 GlobalUniformMgr.ts、Mirro.ts。
最终我们得到的纹理内容如下:
可以很明显看出来,它是倒着的。
4、投影纹理映射
投影纹理映射是一个专有名词,它的原理其实非常简单:在进行纹理映射时,不采用顶点的 uv 坐标信息,而使用顶点投影到屏幕上的位置信息。
当顶点 Pl(x,y,z,1)经过 WVP 变换后,进入裁剪坐标系,得到点 Pc(x,y,z,w),Pc 已经是一个屏幕空间的坐标了。
Pc 经过透视除法后,可以得到 NDC 坐标系中的点 Pndc(x/w,y/w,z/w,1)。
而 NDC 坐标系中 x,y 的取值为[-1,1],z的取值为[0,1]或者[-1,1](视图形 API 环境而定)。
//计算屏幕空间坐标 通常在 vs 中进行
v_screenPos = cc_matProj * (cc_matView * matWorld) * In.position;
//计算屏幕空间 uv 坐标, 需要在 fs 中进行,否则会出现精度问题。
vec2 screenUV = v_screenPos.xy / v_screenPos.w * 0.5 + 0.5;
screenUV = vec2(1.0 - screenUV.x,screenUV.y);
上面源码示例中的 screenUV 就是需要用到的 uv 值,用它进行反射贴图采样,即可得到我们想要的镜面反射效果,如下图所示:
详细代码请参考 Mirror.ts、effect-mirror.effect。
七、Blingbling 与轮廓
先给我们的冰墩墩来个特写吧。
上图中的冰墩墩外壳看起来晶莹剔透的主要原因,是因为渲染走的是折射机制,并不是普通的 Alpha 混合。
这个机制和把大象装进冰箱一样简单,只需三步:
第一步
使用一个专门的摄像机,渲染非透明物体到一张 RenderTexture 上,如下图所示:
第二步
切换到主摄像机,正常渲染场景物体。
第三步
在渲染半透明物体时,使用投影纹理技术对非透明物体 RenderTexture。核心代码如下:
#if AS_REAL_TRANSPARENT
vec3 V = normalize(v_position - cc_cameraPos.xyz);
vec3 N = normalize(s.normal);
float fresnel = 1.0 - dot( -V, N);
fresnel = pow(fresnel,fresnelPower);
vec3 R = V - 2.0 * N * dot(N,V);
vec2 offset = -v_normal_view.xy * refrIntensity;
vec2 screenUV = v_screenPos.xy / v_screenPos.w * 0.5 + 0.5;
vec3 sceneColor = texture(sceneMap,screenUV + offset).rgb;
vec4 temp = texture(envMap,R);
vec3 reflectionColor = unpackHDRI(temp);
s.albedo.rgb = mix(sceneColor, reflectionColor * reflIntensity,fresnel);
s.metallic = 0.1;
#endif
当摄像机转动的时候,可以清晰地感知到折射造成的扭曲效果。
冰墩墩身体边缘也会出现这样的效果。
目前版本的折射并未使用折射率进行计算,而是使用摄像机空间的法线方向进行采样偏移。
八、添加参数控制面板
为了所见即所得的调出满意的效果,参数控制面板是必须的,如下图所示:
九、最终效果与源码
点击文末【阅读原文】即可下载 DEMO。
特别提醒:本 DEMO 中使用的冰墩墩 3D 模型来自网络素材库,本文仅供学习,不涉及商业用途,如有侵权请与我们联系。