随着花椒直播业务的拓展及主播、看播群体对于多维度动态观感需求的提高,传统的2D平面渲染已逐渐不能满足需求,而突出立体感,互动性,随机性、个性化的3D技术越来越得到重视。通过3D渲染技术可以实现礼物的多维度互动体验,且虚拟直播为兴趣社交赋与了更大的空间,为此我们开发了基于3D渲染的虚拟形象系统。本文旨在讲述花椒的3D渲染技术,分为以下八个方面:
多级渲染层千变,分层渲染
虚拟形象化万端,换装挂件
鞋高摘云纤衣挺,捏骨整形
霜染层林百花争,染色易容
远近踪迹迷雾起,深度颠簸
月透湖镜轻纱穿,透明发质
梅影仙子霓裳舞,阴影效果
赤日苍穹星河天!物理光照
其中前四个方面(分层渲染、换装挂件、捏骨整形、染色易容)主要介绍系统功能组件的实现,后四个方面(深度颠簸、透明发质、阴影效果、物理光照)突出3D渲染整体画质效果的提升。
1.多级渲染层千变,分层渲染
直播是一个复杂的系统,针对渲染而言,分层渲染尤为重要。如下图所示,直播间下层有3D buff特效,上层有虚拟形象礼物。不同的分层同时使用了3D渲染,而每个特效的生命周期却不同,这就需要渲染必须支持多实例,多窗口,为此分层渲染必不可少。
以Android为例,UI层可以设置SurfaceView,以及TextureView,每个View里面可以含有多个window即渲染目标,而每个window又可以同时存在多个渲染场景layer,layer指具体的场景渲染如3D粒子、虚拟形象等具体实例,层级如图所示: UI View层为最高层包含了最终的渲染目的,而分层中的多个window可以设置不同的屏幕尺寸,分层渲染的优势在于:
各层独立,适合扩展,且各层生命周期可控,上层业务随意组合开发;
多层渲染之间可以穿插相应的上层View,例如弹幕等可放在两个3D渲染层之间显示;
编码上传、视频录制、截屏等可以随意由上层选择所需层级;
2.虚拟形象化万端,换装挂件
换装挂件编辑是虚拟形象的重要组成部分,其中换装和挂件的实现略有不同,换装是指在原有的骨骼动画基础上替换新的mesh顶点,例如古装旗袍是mesh0,而换为牛仔衣则替换为mesh1,mesh0与mesh1共享了相同的骨骼动画,顶点随骨骼动画而变换,这是换装的基本思路。
挂件与之不同,挂件相当于原有物体的子物体,子物体也可以包含骨骼动画,例如虚拟形象可以有动画,而挂件翅膀同样可以有骨骼动画,子物体随着父物体的运动也随之运动,一般武器、翅膀等适合作为挂件存在,也就是形象的子物体,目前渲染系统为了扩展性,支持子物体同样可以换肤、染色、捏骨等与虚拟形象一样的所有操作,如下图所示,其中染色、捏骨后面章节介绍。
形象1,形象2之间进行换装,且脸型添加了捏骨操作,形象3则为挂件翅膀的附加。
如图所示:
3.鞋高摘云纤衣挺,捏骨整形
豹头环眼,燕颔虎须,
面如重枣,唇如涂脂,
丹凤眼,卧蚕眉,----------------------------典型人物典型外形
虚拟形象的长相是可随用户喜好进行调整,用户喜好不同可以修改形象的外部特征,如肤色、眉毛、腮红、胡须、脸型、鼻子、嘴等,通过捏骨整形完成千人千面;其中肤色、眉目、腮红、胡须等可以使用纹理贴图完成替换,而脸型的修改则需要利用骨骼动画偏移来完成,下面主要讲述一下通过骨骼调整完成高跟鞋替换技术原理,高跟鞋与平底鞋的变化包含了换装、捏骨两个组合,其中换装与第2部分一致,将mesh顶点对应替换即可,而骨骼也需要随之变化,由平底鞋换为高跟鞋时,标准姿态的身高及脚骨都会发生变化,只需要将对应骨骼的变换矩阵修改,一般进行骨骼位置偏移,使其顶点随之变化达到。
平底鞋换为高跟鞋时,利用骨骼增量将标准姿态在运行时变化身高及脚骨,通过增量变化达到相应骨骼改变。
高跟鞋换回平底鞋时再将身高及脚骨恢复,平垫鞋顶点与标准姿态随之匹配。
胸部变化与高跟鞋的实现完全一致,包含了换装及捏骨两个组合操作。如图所示:前两个形象为平底鞋高跟鞋的变化,后两个形象为胸部的骨骼变换。
4.霜染层林百花争,染色易容
虚拟形象千人千面的其中一个重要的组成部分是染色,如肤色、眼球、衣服、挂件等都可以染色进行动态变化。
这里需要提及一个HSV色彩空间,与熟知的RGB空间有些不同,HSV包含色相、饱和度、色彩的明暗度,色相表示RGB三种颜色的混合程度,饱和度是指接近纯色的程度,明暗度表示亮暗。如图所示,左侧为RGB空间模型,右侧为HSV空间模型。
HSV空间是与人眼最为贴合的空间,染色需要在HSV空间进行,且饱和度和明暗度保持不变,仅仅调整色相值完成变换,其变换为RGB->HSV->H调整->RGB完成染色,如图所示为色相环对应的颜色值,通过调整色相环值,达到染色目的。
通过上述方法,形象的头发染色效果如下图所示:
5.远近踪迹迷雾起,深度颠簸
3D渲染里面有一个重要的概念z深度,渲染不透明mesh顶点时,每个顶点的实际深度会记录在一个z-buffer中,当光栅化后在window空间进行逐像素着色时会比较对应的插值后的z深度,如果深度值相对之前的大,则表明离得更远,则剔除,只有z较小的颜色替换保留,这样就保证了渲染的每一个面都与顺序无关,且保证正确,其中z深度就涉及到了摄像机视角,常见的一个3D渲染问题即为z-fighting,随着物体的运动或者摄像机的变化,会导致严重的穿模问题,z-fighting问题实质是计算精度问题,无法从技术上根除,只能在一定程度上减少瑕疵的程度,如图所示,红色部分即为z-fighting问题。
尽量使所画物体不要离得太近,比如牛仔裤与腿这两个mesh顶点过近,则容易产生问题,故设计时尽量考虑稍微偏离一些。
将摄像机的投影近平面稍微拉远一些,因为近平面的精度很敏感,上图中如果这个近平面值为1会出现瑕疵,但是如果设置为100则瑕疵消失,前提是设置过大需要考虑clip三角面过多问题,需要权衡。
保证摄像机离物体不要过远,过远要求精度过高,易出现该问题。
提升z-buffer精度,需要和计算效率权衡折中。
6.月透湖镜轻纱穿,透明发质
虚拟形象的头发渲染和树叶草地类似,都涉及到透明物体的渲染,即某些顶点的颜色是透明的,这样头发才更加靓丽,而如果全是不透明的设计,得到的头发完全是条片状,则显示效果会大打折扣。在第5章中我们提到了z深度检测,但是如果涉及到透明物体的渲染,打开z检测,由于顺序无关,则会渲染出现镂空错误,如下图所示错误:左侧原本茂盛的草地出现镂空稀疏,右侧的头发出现镂空错误。
透明物体的渲染一直是相对复杂的问题,一般解决方案有:
画家算法,所有三角面严格排序,从远到近进行依次渲染;缺点,三角面如果交叉存在则失效,顶点远近计算会出现问题。
深度剥离,需要对透明层的深度进行排序,对于z深度层需要多个pass完成,缺点是计算量过大,不适合移动端实时渲染。
Alpha to coverage,透明测试,设置阈值透明度达到一定阈值则不画透明面,去除镂空错误,结合MSAA进行反走样处理,缺点是毛刺现象严重。
综合计算复杂度计算,我们采用两个pass,第一个pass先进行不透明顶点的渲染,第二个pass进行透明顶点的渲染,透明顶点进行颜色叠加,且不进行深度写入,经过测试,如下图所示,左图为Alpha to coverage效果毛刺过多,右图为当前采用算法效果。
7.梅影仙子霓裳舞,阴影效果
阴影是3D渲染中不可缺少的一环,体现了物体之间的相对位置,缺少阴影贴出的物体会感觉缺乏立体感,没有阴影估计一千年前的诗仙也会倍感孤寂,“举杯邀明月,对影成三人,月既不解饮,影徒随我身”,古人对阴影也深有体会。当前主流的阴影渲染主要分两种实现:
硬阴影,stencil volume方式,经过尝试,对于自阴影存在问题,特别是过渡带,如果三角面数量精度有限时,随着动画,过渡带的阴影会出现严重的锯齿闪烁,瑕疵感严重,且计算量大,故我们采用软阴影方案。
软阴影,shadow map为主,阴影体现的是光照与物体之间的关系,当光被物体遮挡后自然会存在暗部阴影,不被遮挡的部分则被点亮, 基于这个思想,shadow map采用2pass完成,1pass中从光照的视角将其深度图得到;2pass时采用摄像机角度进行渲染,其中每个顶点再次进入光照视角中得到相应的z深度,并且与之前1pass深度图对应的z深度比较,如果较大则为阴影,否则是光照亮处,软阴影存在的问题是由于深度图分辨率所限,以及摄像机、灯光与物体的距离角度,会导致过采样、欠采样,同样会导致阴影边缘处的锯齿走样,此时采用PCF滤波,比较时利用邻域多个点按比例进行滤波叠加,软化阴影边缘,达到柔化阴影的效果,最终的效果如图所示,头顶佩饰,衣服丝带,及腿部对袍子产生的暗部阴影。
8.赤日苍穹星河天!物理光照
光照无疑是3D渲染中最为重要的一个元素,早期的光照模型主要是PhongShading,Blinn-Phong,但效果与当前基于物理的渲染PBR(physically based rendering)差距较大,PBR与之前的光照模型最大区别就是,考虑到了光照的能量守恒,且基于粗糙值的微表面,对于光的反射计算更加符合统计结果,这样渲染效果更贴近真实物理渲染。当前渲染采用了PBR技术作为光照模型,最大程度上提升渲染质量。
PBR光照分为直接光照,间接光照;其中直接光照即为点光源、方向光源、聚光灯等,而间接光照则采用一张高动态图HDR,进行全局的环境光照,高动态图HDR是针对于一般的LDR图而言,LDR问题是当多个光源亮度叠加时很容易超过8bit的精度,超过的部分一律饱和到1,这样高亮细节基本都会丢失,HDR采用了比8bit更高的精度如float,当多光源叠加后不会受到饱和到1的影响,因而提升了高亮度和阴影部分的细节,更适合作为全局的环境光模拟,目前两种光照都在当前渲染系统中得以实现。
光照对物体的影响分为两种,漫反射及高光反射(镜面反射),光照射到物体后会出现折射和反射两种情况,折射到物体内部会再次进行原子碰撞继而散射到外界,表现为漫反射效应;而高光反射是对周围环境的反射,比如光滑的金属材质,光到达金属时折射到内部基本全部转换为内能,而没有散射出,则其漫反射基本忽略,而镜面高光部分则较为明显,可以反射出周围的实际光照环境,其中高光部分主要有下面三个函数计算公式:
法线分布函数 (Normal Distribution Function),描述微表面法线分布的概率,即朝向材质顶点法线的概率,由于粗糙度不同导致微表面法线产生不同的朝向。
菲涅尔方程(Fresnel Equation),描述不同的观察角度下表面反射光线所占的比率,如当从高角度(接近顶部)看河流水面时主要是折射,能够看到水下的石块鱼群,而从接近地面的低角度看向水面时主要是周围的反射倒影。
几何函数(Geometry Function):描述微平面自成阴影的属性,由于粗糙度影响可能导致相互遮挡的情况,该函数描述这种现象。
漫反射和高光反射的核心是通过逐点对周围所有光源的反射影响的球面积分,所有反射光的叠加得到最终颜色显示。直接光源中多个光源叠加即可,而间接光照部分,相对复杂,HDR可以被认为周围无数点光源组成,对此实时积分则计算量过大,可以采用烘焙方式离线得到,这里涉及到对半球面积分,此时利用重要性采样对一些关键点进行积分计算,为得到更好效果,漫反射部分可以使用球谐函数模拟,高光反射部分利用重要性采样、逆采样等进行模拟,之后在实时渲染中利用这些烘焙结果即可,由于微表面的粗糙度影响,可使用mipmap方式考虑不同的粗糙度颗粒产生不同的烘焙纹理,实际渲染时根据粗糙度进行mip level选择,提升渲染质量。
PBR需要几张纹理提升具体效果,第一是固有色贴图,即为基本漫反射纹理,如果是金属材质则认为是其反射度,但是不能有光照影响及阴影效果,这样就可以在不同的光源环境下产生不同的渲染效果;第二是粗糙度贴图,反映了微表面的平坦情况,这个是PBR最为重要的参数之一;第三是金属度贴图,金属与非金属在漫反射贡献上有巨大区别,另外金属材质对于镜面反射中的菲涅尔计算中有不同的反映;第四是法线贴图,可以通过光照使低模营造出具有高模的细节效果;第五是环境遮罩贴图,可以设置静态物体的暗角;第六是自发光,可以提供独特的光源,如钢铁侠战衣的金钛合金可以发出更强烈的光,以突出效果,下图为虚拟形象衣服的贴图依次展示,从左到右,固有色、粗糙度、金属度、法线、环境遮罩、自发光。
最终的PBR及阴影效果图如下,其中背景为环境HDR,佩饰中有纯金属可以反射周围环境光照,而衣服法线贴图使裙摆褶皱,底部的暗角为环境遮罩贴图,丝带上的花纹为自发光效果,袖子分别有磨砂及金属质感。
渲染技术的实现及适合移动端的特效需要长期的积累尝试,涉及图形学,GPU,数学物理的一些基本知识,例如:
白衣胜雪风中舞,长发泼墨伴雪飞---物理引擎,裙摆,长发随风力而动,流畅自然。
残烟何须惹絮柳,怒剑飞花啸狂沙---粒子特效,烟、柳絮、飞花、沙石,随机性,群体性,周期性。
3D渲染技术的底层知识需要沉淀,不积跬步无以至千里,不积小流无以成江海,渲染系统的搭建需要时间的打磨洗练。
路漫漫其修远,
踏歌而行,
不觉抬望眼,
霞光满天,
花椒红遍!
你们懂的,读者三部曲