本文将分享来自淘宝人生团队齐力打造的新玩法——“人生小屋”背后的技术方案,详细介绍如何使用3D渲染引擎从0到1搭建虚拟场景、控制虚拟角色、实现人景交互、优化渲染效果等。笔者在其中主要负责了小屋3D室内场景的搭建部分。非常欢迎大家给我们提出建议,探讨交流。
01项目背景
淘宝人生,是一款手机淘宝App内可以玩转虚拟形象的轻游戏,包含了捏脸、穿搭、美颜、拍照等功能,逛街、家园等玩法。在它核心玩法日益成熟后,我们希望虚拟角色可以和虚拟场景进行交互互动,提供一些新的社交场景和玩法。因此,我们向虚拟人和虚拟场景方向展开了新的技术探索,开发“人生小屋”玩法。(上图展示了淘宝App中人生小屋的入口路径:搜淘宝人生跳转 -> 右侧去旅行 -> 人生小屋icon,欢迎大家前来体验!!)
- 增加游戏的可玩性。给淘宝人生提供一个“家”的场景,在该场景中可以进行室内家具摆放、装饰,人与家具交互,成为用户串门的场所,提供新的社交场景和玩法。
- 丰富淘宝人生旅行玩法。通过旅行玩法,用户可获取家具材料,合成家具,在【家】中进行摆放,提升旅行玩法的游戏性和时长。
02 技术框架
人生小屋基于我们团队的自研web3d渲染引擎Hilo3d开发,使用webgl渲染到前端的canvas画布上。笔者的主要工作内容即为绿框中的业务功能,概括来讲,可以划分为:室内场景搭建、角色管理、游戏逻辑开发 三大块。下面,将会分享在一步步搭建过程中,考虑以及采用的各个技术方案。
室内场景
- Step 1:加载室内场景
首先,从场景搭建开始。
如何加载房屋3D模型,整个过程概括来讲,大致经历这几个步骤:
- 首先使用一些3d软件,比如MAYA,制作出3d房屋模型,把过程产物暂存在OSS,并提供预览工具
- 给模型上贴图,用Photoshop制作贴图,把贴图上传至CDN
- 导出一个USD文件
- 使用houdini 程序化生成阴影和lod类型的gltf
- 我们使用GLTF作为我们的资产标准
- 最后,使用hilo3d 引擎,通过GLTF Importer导入glTF模型
- Step2:加载家具模型
同理,使用和房间模型相似的步骤,可以用同样的方式去加载各个家具的模型:
在加载了所需的各个模型后,开始搭建整个场景的结构:
初始化一个场景节点作为根节点,然后,把各个平面节点加入场景节点中,再把属于各个平面的家具放入平面节点中。之所以会按照平面先划分平面节点,是为了便于统一操作平面节点,比如在某些情况下,我们想将地板的家具全部隐藏,那么选择直接隐藏地板节点即可。
- Step 3:加载相机
3d场景渲染的核心三要素:scene, camera, renderer ⇒ render the scene with camera
我们所看到的的场景内的画面,就是渲染器render根据新的相机对象参数渲染出场景图像,更简单的来讲,即将相机捕捉到的场景帧,通过渲染器渲染到画布上展示给用户。
相机选择
下一步,加载相机。在相机的选择方面,有两个最常用的相机:透视相机和正交相机。
正交相机:元素在屏幕上的大小与离相机的距离无关,按照正投影算法自动计算几何体的投影结果。
透视相机:透视相机看见的结果除了与几何体的角度有关,还和距离相关。透视相机能够更模拟出人眼所看到的世界,有一种近大远小的效果。比如你观察一条铁路距离越远你会感到两条轨道之间的宽度越小。在3D场景的渲染中,往往会使用透视投影照相机。
相机视场角FOV
值得一提的是透视投影相机中的视场角变量:
视场角:相机视锥体的两端的夹角。视场角的大小决定了视野范围,视场角越大,视野就越大。目标物体超过这个角就不会被收在镜头里。
相机控件OrbitControl
在选好了相机后,现在在画布上已经可以看见当前相机视角捕捉的3d室内场景了,但是这是一个静态的模型。我们下一个需求点,肯定是希望这个模型可以随着鼠标转起来,也就是我们鼠标左右移动,可以切换视角,看到房屋的各个角落。于是,我们引入orbitcontrol控件。
orbitControl可以使相机可以围绕目标进行轨道运动,以场景中心为中心,左右拖动屏幕会让镜头围绕着场景中心旋转,镜头始终会看着中心点。
它的原理是OrbitControls可以控制浏览器实时监控鼠标或键盘触发事件,比如鼠标左键发生拖动,那么拖动的距离就会改变相机的数据:比如位置和拍摄角度, 渲染器就会根据新的相机对象参数渲染,渲染出来的图像就会变化。
观察视频,我们有了新的需求点:当相机在房间顶部,俯视这个房间的时候,视野可不可以变大,可以看见整个房间,这就涉及到相机视野的变换。
相机视野变换:调整FOV
“相机视场角FOV”中我们介绍过,当视场角FOV增加,相机的张角变大,视野也就会变大。因此,当相机移动到顶部俯视视角的时候,我们不妨将相机的视场角调整增大。对比一下当相机平视和相机俯视时的室内场景,可以发现,当视场角的增大,我们的视野也变大,可以让房间的各个角落清楚地进入我们的视野,解决了我们的需求。
由于我们使用的是webgl默认的右手坐标系,其实相机的上下移动,可以认为是绕着x轴的旋转角度。
因此,在实现的时候,我们把相机的fov和相机在x轴的旋转角度rotationX关联起来。
- Step 4:特定mesh隐藏
我们可能会提出一些疑问:
- 为什么相机旋转到墙壁背后的时候,墙壁自动被隐藏了?这是因为我们在搭建3d模型的时候,把墙壁建成了单面墙,也就是只能在墙壁的正面才能看见墙。
- 当我们的视角切换到入户门后时,但是这个门框并没有被隐藏?怎么也能隐藏掉门框?
观察一下相机的位置。既然门框是垂直于z轴的,那么其实我们只要比较相机坐标在z轴上的投影和门框在z轴上的投影位置。当相机z坐标大于门框z坐标,隐藏门框的mesh即可。同理,其实可以根据相机的位置,去隐藏掉室内模型某些特定的mesh。
// 相机z坐标 > 门框z坐标,隐藏门框mesh
camera.worldPosition.z > doorEdge.worldPosition.z
角色控制
- Step 1:加载虚拟人并使用摇杆操控
如何创建一个虚拟角色,这是一个有很多可以探究的领域,我们团队在第十六届D2论坛上分享过关于【虚拟偶像诞生记】的专题分享,如果大家感兴趣可以去更详细的了解一下,附上相关文章链接:《虚拟数字人行业现状和技术研究》
加载虚拟人后,我们使用joystick摇杆去操控人物在室内场景内的自由移动:
在这里,我们发现了问题:操控人物走动起来后,这个相机的视角没有转动,那么操控人物就能自由的走出我们的视野。
- Step 2:相机跟随
因此,为了能让人物能始终在我们的视野中,我们让相机跟随人物节点移动。具体做法:
把场景的相机挂载到人物node节点上,以它为父节点。相对于人物节点,相机有一个固定的偏移量,让相机始终位于人物的正前方,并且距离人物一定的距离。那么当操控人物节点移动的时候,相机的节点也就会同步跟随移动,并且始终保持人物在最中心。
游戏逻辑
在完成了室内场景加载和虚拟角色加载后,我们来到了游戏逻辑开发部分。首先,我们来实现对家具的操作,先来看看家具是如何选中的?
- Step 1: 家具选中
我们考虑到了一种业界非常通用的方法:raycast光线投射。raycast多用于实现物体选择或相交。用一句话概括来讲:通过三维空间中相机视点与鼠标在屏幕上的位置的连线,形成一条直线,捕获与此直线相交的空间中的物体,即为选中的物体。
具体计算步骤:
- 第一步,需要确定射线方向,也就是确定射线的起点(比如常用的相机世界原点)
- 然后确定射线终点:也就是鼠标点击处。计算点击像素点坐标是世界坐标中的位置
- 知道起点和方向就可以得到一条无限长的射线,使用点击的像素点的世界坐标减去相机位置,标准化后得到方向矢量(射线的方向)
- 再把射线先和检测物体的包围和求交,检测是否相交。
- 如果一条射线和多个物体相交,则把相交的物体按照深度排序返回。
所以怎么去选中家具?
在鼠标点击处投射出屏幕射线,检测物体为家具,返回与家具碰撞的结果.
- Step 2:家具拖拽移动
第二步: 如何能拖拽家具,在我们的小屋内进行移动?最核心的点在于:如何得知鼠标拖拽位置的世界坐标?从而实现家具随着用户触摸点的移动而移动的效果(拖拽效果)。
我们同样可以使用raycast,创建出一条相机到点击处的射线,只是此时,检测物体变成了地板。那么,当鼠标/触摸点移动的时候,就可以获取到鼠标在地板上的具体位置,然后把家具中心移动过去,实现家具拖拽的效果。
- Step 3:人物与家具碰撞方案
第三步,当人物移动后,人物与家具如何实现碰撞?
我们首先考虑了是否可以采用上一节所讲的raycast方法,上一节讲了raycast是一种检测碰撞相交的方案。更具体的来讲,在这种情况下,射线的发射源变成了人物,射线方向变成人物走动的方向,而检测物体变成了场景内摆放的所有家具或墙壁。以这种方法,我们可以检测到人物在走动过程中,与各个家具的碰撞情况。
然而,我们不得不考虑到一个问题:随着室内家具的逐渐增加,每次碰撞检测都会需要遍历检测物体,即场景内全部家具的节点-> 这会带来一个问题:性能不佳。经过我们实验,当室内有大量家具时,低端机达不到30fps。
因此,我们寻求了其他的方案:格子碰撞。
我们会以地砖格子为单位,使用二维数组去描述地砖信息,保存地砖上相应家具信息。
我们保存了一张看不见的逻辑层。这个层的大小和地板等大,并且也进行了格子划分,主要目的就是为了碰撞检测,使用一个数组描述信息。
我们会以地砖格子为单位,比如将地板划分为n*n个格子,使用二维数组去描述地砖信息,假设家具所占地砖面积为x*y,那么就在这一片的地砖上保存相应家具信息。用这种方式,我们就可以实现人物和家具的碰撞,检测地图中人物是否碰到了NPC或者障碍物。
- Step 4:人物与家具交互
至此,我们的人物可以与家具发生碰撞了。下一步,碰撞到的家具若是床,若是椅子,我们自然希望我们的人物可以实现与家具的交互:上床躺下 / 椅子坐下。
于是,我们在家具可以交互的位置,比如床的两侧,添加了动作触发点。当人物走动到足够靠近家具的动作触发点,出现动作按钮,点击后,将以动画播放的形式,让人物实现上床的一系列动作。
视效优化
最后,我们希望用阴影来增加人生小屋的视觉效果。
- 传统的实时渲染
首先,了解一下传统的实时渲染是怎么做的?
假设说场景内有n个光源:
第一个光源,spotlight,也就是视频中地上这个圆圈的光源,它会对整个场景产生一个shadow map。
第二个光源,directional light,它也会产生一个shadow map。
……
若有n个光源,则有n个shadow map。
最终将n次渲染的阴影结果进行合并,每个mesh渲染的时候,都会读取n个shadow map。
这也就是为什么这个天猫的模型,它同时会拥有来自spot light和来自directional light这两个阴影。
因此实际上,阴影可以被认为是贴图的叠加。
然而,这种传统的实时渲染,大家可以预见到,由于每个mesh渲染都要读取n个shadow map,它会需要大量的计算和性能。
- what is a shadow map?
因此,我们可以回过头看看对于Shadow map 阴影贴图的概念定义。所谓 ”贴图“,你可以想象成 ”一层层窗户纸“。
假设现在有一个窗户,你可以一层一层的粘贴不同透明度的窗户纸,每一层窗户纸都会叠加到之前的那一层,最终窗户纸所呈现的效果是所有窗户纸最终合并后一块呈现的效果。
- 如何降低阴影所需性能?(小屋中家具阴影)
如前面讲的,传统的实时渲染会需要大量的计算和性能,有降低阴影所需性能的方案吗?
方案1:可以有多个灯光,但只有一个平行光可产生阴影
方案2:使用 光照贴图 或 环境光照遮挡贴图 来预先计算离线照明的效果 — 比如:床缝间的这些家具自阴影
方案3:使用 假阴影,添加一个平面放到物体下方的地面上,同时赋予一个看着像阴影的纹理图片材质。具体做法,会添加一个面光源,置于家具的顶部,向下投影出家具底部的阴影。
可以看出,在小屋中添加家具阴影时,对针对单个家具,进行离线渲染烘焙,因此大大降低了所需性能。
- 小屋中人物阴影渲染
同理,我们在添加人物阴影时,可以在人物的头顶放一个面光源,投射到地面上,添加一个平面放到人下方的地面上,同时赋予一个看着像阴影的纹理图片材质。然而,不同于家具阴影允许的离线渲染,人物阴影必须要求一个实时渲染。因为人物在各个动作、姿势、服饰变化的时候,底部的渲染阴影会随之变化。如何实现人物阴影实时渲染?我们使用离屏渲染Off-Screen Rendering。在这里不再非常详细展开,感兴趣的可以去了解一下GPU屏幕渲染的两种方式:
- On-Screen Rendering
- Off-Screen Rendering
03 总结与展望
人生小屋初次上线后,未来我们将积极在更多的方向展开探索:
- 增强社交属性,提供更多样的社交玩法,好友聚会,通信交流。
- 提升视觉效果,增加全局光照离线烘焙,使光影效果更加真实。
欢迎大家持续关注我们淘宝人生!未来即将有更多玩法上线!
04 参考文献
- 《物体的点击和碰撞》https://www.jianshu.com/p/7b0aba80cc59
- 《游戏常用算法之碰撞检测 地图格子算法实例详解》https://www.jb51.net/article/152638.htm
- 《渲染物体raycast picking拾取交互》https://zhuanlan.zhihu.com/p/129864543
- 《射线拾取、缓冲区拾取原理》: https://juejin.cn/post/6988013072686252046
- 《mdn Touch events》: https://developer.mozilla.org/en-US/docs/Web/API/Touch_events
- 《pointer input》: https://blog.csdn.net/keneyr/article/details/99076835
- 《Three.js基础之阴影》https://github.com/puxiao/threejs-tutorial/blob/main/13%20Three.js%E5%9F%BA%E7%A1%80%E4%B9%8B%E9%98%B4%E5%BD%B1.md
- 《cartoon-shading》:http://zhangwenli.com/blog/2017/03/05/cartoon-shading-1/
- 《opengl-matrix-transformations》http://zhangwenli.com/blog/2015/08/28/opengl-matrix-transformations/
团队介绍
我们是大淘宝互动前端团队,致力于开发淘宝内多款有趣的互动产品,包括淘金币、芭芭农场、淘宝人生、斗地主、小流浪旅舍等。此外,在每年的618或双11大促期间,我们还负责推出限定的大促玩法。我们拥有丰富的技术积累,涵盖2D互动技术、3D游戏技术以及前端工程化/低代码技术等多个领域。同时,这是一只充满活力和想象力的队伍,我们有对技术的追求和对生活的热爱,欢迎加入我们,研究更多好玩的互动,打造更好的互动产品。
版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。