2024 抖音歡笑中國年之渲染技術(shù)實踐與探索
前言
抖音在2024年春節(jié)期間推出了歡笑中國年系列活動,為用戶帶來了全新的體驗和樂趣。而SAR Creator則為該項目研發(fā)工作提供了重要的技術(shù)支持。SAR Creator是一款基于 Typescript 的高性能、輕量化的互動解決方案,目前支持了瀏覽器和跨端框架平臺,服務(wù)于字節(jié)內(nèi)部的各種互動業(yè)務(wù)。
這些絢爛多彩的互動場景當(dāng)然也離不開實時渲染技術(shù)的支持,因此本文將專門介紹春節(jié)活動招財神龍和神龍?zhí)綄?/strong>中SAR Creator渲染相關(guān)的業(yè)務(wù)實踐經(jīng)驗以及技術(shù)探索和嘗試。
春節(jié)—招財神龍
春節(jié)—神龍?zhí)綄?/span>
比如抖音歡笑中國年系列文章《招財神龍互動技術(shù)揭秘》中就有提到,項目中“家”場景就是由2D元素以及不同材質(zhì)支持的3D元素共同組成的。出于性能和美術(shù)效果的考慮,各3D模型使用的材質(zhì)會有所不同,比如無光照Unlit材質(zhì)、基于物理的PBR材質(zhì)。對于陰影這種移動端性能消耗比較大的特性,不同物體的接收也會做特殊處理。這些材質(zhì)的選擇以及光照陰影的支持都是依托于SAR Creator材質(zhì)庫能力的支持。如下圖所示即為SAR Creator Unlit材質(zhì)(左圖)和PBR材質(zhì)(右圖)的示例。
無光照Unlit材質(zhì)示例
基于物理的PBR材質(zhì)示例
此外,SAR Creator支持使用ShaderGraph插件制作自定義材質(zhì),幫助用戶制作更多可定制化的特效。神龍?zhí)綄?/strong>項目中實現(xiàn)了多種基于ShaderGraph的特殊效果,包括:入場溶解特效、分區(qū)解凍特效、拖尾特效等。下圖展示了利用ShaderGraph定制卡通風(fēng)格水體的效果。
ShaderGraph卡通水體
除了上述春節(jié)活動中順利落地的渲染效果外,我們還嘗試做了很多效果提升的技術(shù)探索,比如后處理輝光效果、凹凸貼圖等。希望可以更好的提升美術(shù)設(shè)計師的設(shè)計體驗和最終的渲染效果。
bloom輝光
招財神龍渲染實踐
“招財神龍”活動是2024年春節(jié)游戲化玩法之一,活動整體采用3D場景(龍在家場景)+ 2D場景(龍尋寶場景)結(jié)合的方式。在招財神龍的活動中,設(shè)計同學(xué)基于SAR Creator編輯器,進(jìn)行場景搭建和效果還原工作;研發(fā)同學(xué)基于SAR Creator渲染能力,快速進(jìn)行技術(shù)方案選型和實施。
2D&3D混合渲染
對于活動中的“龍”和“小女孩”元素,我們采用3D模型,提供更為逼真的體驗感。而針對場景中的房子、炮仗等,我們使用2D貼片來呈現(xiàn)。通過調(diào)整相機(jī)的遠(yuǎn)近平面、fov等參數(shù),展示出小女孩在炮仗前、龍在房子前、龍在炮仗后的視覺假象。
材質(zhì)庫
SAR Creator提供了Unlit、PBR、Uber和NPR等多種材質(zhì)的選擇。
例如,這次“招財神龍”中的白天/黑夜場景場景,小女孩和龍的皮膚顏色等需要有不同的表現(xiàn),就是基于材質(zhì)的“顏色貼圖”能力來實現(xiàn)的。
針對PBR材質(zhì)來說,設(shè)計同學(xué)還基于SAR Creator提供的金屬度、粗糙度來進(jìn)行小女孩身體細(xì)節(jié)的調(diào)整。
為了追求更佳的視覺體驗,在小女孩的模型上,設(shè)計同學(xué)為不同的部位(身體、頭發(fā)和衣服)賦予了不同的PBR材質(zhì)的實例,再通過調(diào)整不同PBR材質(zhì)的金屬度、粗糙度屬性,微調(diào)受光條件下,不同部位的表現(xiàn)細(xì)節(jié)。
這次活動,我們不僅使用了PBR材質(zhì),綜合性能和實用性的角度,還使用了Sar Creator提供的Unlit材質(zhì)。比如“龍”模型中的身體、胡須、眼睛等模塊。Unlit材質(zhì)是一種簡單的、不受光源影響的材質(zhì),在技術(shù)選型時,為了平衡性能和效果,通常是活動開發(fā)的首選。
光照陰影
除了上述所說的這些材質(zhì),為了實現(xiàn)場景中元素的真實性,設(shè)計同學(xué)借助SAR Creator提供的渲染能力,利用燈光、陰影來優(yōu)化渲染場景。
SAR Creator提供了平行光的方向、顏色、強(qiáng)度等屬性,使得設(shè)計同學(xué)可以調(diào)整出不同效果。
為了更好的光照效果,我們這次使用了兩個平行光,利用PBR受光的特性,可以實現(xiàn)更貼近真實世界的效果。
只使用了環(huán)境光
使用了一個平行光(小女孩鞋子、臉、手部等部分都收到了光照影響)
使用了兩個平行光微調(diào)(小女孩背部收到了光照,更貼近真實效果)
只有光照,沒有陰影的話,同樣也不符合物理世界的客觀規(guī)律。SAR Creator通過在光源上設(shè)置“投射陰影”,在需要顯示陰影的物體上,設(shè)置“接受陰影”,即可快速的實現(xiàn)陰影的效果。
無陰影
有陰影
利用SAR Creator提供的ShadowMaterial這種自實現(xiàn)的材質(zhì),我們還能通過調(diào)整顏色、透明度等常用屬性,快速調(diào)整出設(shè)計師想要的陰影效果。
神龍?zhí)綄氫秩緦嵺`
神龍?zhí)綄?/strong>是2024年春節(jié)系列活動中的一個以2D場景為主的互動玩法,其嘗試并成功落地了多種特效渲染技術(shù)。本章節(jié)主要有三個特效渲染技術(shù)點可分享給大家。分別是:入場溶解特效、分區(qū)解凍特效、拖尾特效。
入場溶解
實現(xiàn)入場溶解特效核心是采樣一張溶解圖(可低分辨率128x128),通過動畫step.edge即可。該方案主要通過ShaderGraph可視化界面開發(fā)Shader幫助實現(xiàn)美術(shù)預(yù)期效果,具體節(jié)點實現(xiàn)實現(xiàn)如下圖所示:
如想了解更多技術(shù)細(xì)節(jié),各位同學(xué)可用WebGPU版ShaderGraph在線體驗(https://deepkolos.github.io/shader-graph-wgsl/?graph=demoSummberDissolve)(PC Chrome113+),也歡迎內(nèi)部同學(xué)直接體驗SAR Creator。
地圖分區(qū)解凍
地圖分區(qū)解凍需要實現(xiàn)的效果是支持分區(qū)塊單獨控制其處于解凍/未解凍狀態(tài)。表現(xiàn)效果會隨著解凍狀態(tài)的變化而變化。
如果按照傳統(tǒng)前端實現(xiàn)估計需要7個區(qū)域小圖+1張底圖=8張圖片去實現(xiàn), 即需要消耗8個繪制指令(DrawCall)。雖然傳統(tǒng)方式可以通過動態(tài)合批的方式優(yōu)化繪制指令(DrawCall)為1個,但合批操作本身也有耗時,且每次資源替換+小圖位置調(diào)整,會帶來額外工作量。而利用ShaderGraph插件定義支持圖片存儲特定貼圖IDMap的Shader可解決這些問題,只需一張JPG 一張PNG 即可。首先我們需要將區(qū)域信息存儲在A通道,比如區(qū)域A = 0.9 區(qū)域B = 0.8 以此類推。
未點亮
已點亮
解凍過程
然后在Shader中根據(jù)點亮前后紋理采樣顏色值,混合計算出最終像素顏色值,以實現(xiàn)每個區(qū)域的解凍/未解凍狀態(tài)變化。具體計算邏輯如下所示:
如想了解更多技術(shù)細(xì)節(jié),該例子也可用WebGPU版ShaderGraph在線體驗(https://deepkolos.github.io/shader-graph-wgsl/?graph=demoCustomMap)(PC Chrome113+)。
然而在上述效果的實現(xiàn)基礎(chǔ)上,設(shè)計同學(xué)提出了更高的渲染需求,要求冰凍區(qū)域沿邊緣浸染已解凍區(qū)域,來避免硬邊緣。由于項目時間節(jié)奏比較緊張,綜合考慮時間成本和收益后,邊緣浸染需求最終沒有推進(jìn)支持。通過簡單的調(diào)研,該效果可能的一個解法是:增加7個2D光照,通過光照計算范圍來實現(xiàn)冰凍浸染效果,但問題在于沒法實現(xiàn)沿邊緣浸染。各位如果有什么好的思路也可以分享下,也許可以通過ShaderGraph插件快速支持這種定制需求。
粒子拖尾+幾何拖尾
設(shè)計效果
落地實現(xiàn)
我們可以分析表格第三列中效果參考的構(gòu)成,得出技術(shù)要點為: 幾何拖尾+粒子拖尾+頭部星光。頭部星光較為簡單,只需要一個Sprite/Plane+Tween增加下旋轉(zhuǎn)動畫即可。下面將主要介紹粒子拖尾和幾何拖尾的技術(shù)實現(xiàn)。
粒子拖尾
目前SAR Creator現(xiàn)有非常強(qiáng)大的粒子系統(tǒng),可快速實現(xiàn)粒子效果,提效非常明顯。
但完整的粒子系統(tǒng)在功能強(qiáng)大的同時包體積也相對較大,為了兼顧粒子效果的同時也避免包體積問題,需實現(xiàn)簡易版拖尾粒子。通過ShaderGraph結(jié)合EmitOverDisatance,抽離出的粒子拖尾特效資源打包后只有7.66KB。下圖左為: 簡易版粒子拖尾+EmitOverDistance+ShaderGraph聯(lián)動,下圖中/右為: 粒子系統(tǒng)示例和ShaderGraph定制材質(zhì)的參數(shù)設(shè)置。
目前粒子拖尾已集成進(jìn)SAR Creator以及ShaderGraph插件,方便用戶更加直觀的調(diào)試材質(zhì)特效。
GPU Instancing是一個DrawCall繪制大量相同幾何,姿態(tài)不同的技術(shù)。
所以簡易版拖尾粒子本質(zhì)上是一個GPU Instancing幾何更新器。
大量粒子的位移動畫通過Shader使用GPU并行算力完成,節(jié)約寶貴的CPU算力。
感興趣代碼實現(xiàn),可查看??開源實現(xiàn)(https://github.com/deepkolos/three-js-trail) or 線上Demo(https://deepkolos.github.io/three-js-trail/),或者SAR Creator中直接體驗。
幾何拖尾
幾何拖尾本質(zhì)上也是一個幾何更新器,不過并非更新Instanced數(shù)據(jù),而是幾何本身數(shù)據(jù)Position+Index,使用下圖可直觀了解幾何拖尾的關(guān)鍵邏輯。
如上圖左所示為幾何拖尾的幾何部分實現(xiàn),參考效果的游動效果則需要在Shader中增加UV動畫+拖尾沿Brush重心縮小,所以幾何拖尾同樣支持ShaderGraph擴(kuò)展材質(zhì)。
如感興趣代碼實現(xiàn),可查看??開源實現(xiàn)(https://github.com/deepkolos/three-js-trail) or 線上Demo(https://deepkolos.github.io/three-js-trail/),或者SAR Creator中直接體驗。
ShaderGraph探索
ShaderGraph自定義Shader相較于研發(fā)編寫定制材質(zhì)而言主要優(yōu)勢在于更高的自由度。SAR Creator通過ShaderGraph插件可以邊看中間結(jié)果,邊理解特效的實現(xiàn)方式。同時幫助用戶更好的調(diào)試渲染結(jié)果高度不可控的特效。此外,ShaderGraph插件實現(xiàn)思路和節(jié)點能力實現(xiàn)方式與Unity ShaderGraph基本一致,用戶可以以極低成本的方式參考Unity已有特效并搬運到SAR Creator上。
比如抖音故障效果:
再或者卡通水體WebGPU版ShaderGraph在線預(yù)覽(https://deepkolos.github.io/shader-graph-wgsl/?graph=demoCartoonWater):
渲染技術(shù)探索
除了在項目實際落地的渲染技術(shù)外,我們也在春節(jié)項目中嘗試探索渲染技術(shù)可能應(yīng)用場景。下面我們將通過后處理篇和材質(zhì)篇來進(jìn)一步介紹其中的技術(shù)點。
后處理篇
比如在“招財神龍”的龍須上,我們希望能增加輝光bloom的效果。bloom是屏幕后處理效果中較為常用的一種,表現(xiàn)為高光物體帶有泛光效果,通常會搭配HDR來得到更好的效果。
在技術(shù)方案的實現(xiàn)上:針對此類特定區(qū)域的輝光,我們引入了亮度閾值。第一步,對原場景圖進(jìn)行篩選時,所有小于這個閾值的像素都會被篩掉,僅保留大于等于該亮度閾值的區(qū)域,即我們的龍須區(qū)域。第二步,對上一步操作的結(jié)果龍須區(qū)域進(jìn)行模糊操作,達(dá)到光溢出的效果。最后,我們將處理過的圖像和原圖像進(jìn)行疊加,就得到了最終的效果。
bloom渲染流程示意圖
bloom渲染流程中的第一步:過濾高亮區(qū)域,我們在shader的屬性中加一個lumaThreshold,然后提取圖片像素亮度進(jìn)行step過濾,在片段著色器中代碼示例如下:
uniform float lumaThreshold;
float luminance(vec3 color) {
return dot(color,vec3( 0.299,0.587,0.114 ));
}
void main{
vec4 texel = texture2D( tDiffuse,vUv );
float luminosity = luminance(texel.xyz);
float contribute = step( lumaThreshold,luminosity );
gl_FragColor = texel * contribute;
}
bloom渲染流程中的第二步,圖像模糊算法在后處理渲染領(lǐng)域中占據(jù)著重要的地位,后處理中所采用模糊算法的優(yōu)劣,直接決定了后處理管線最終的渲染品質(zhì)和消耗性能的多少。高品質(zhì)后處理:十種圖像模糊算法的總結(jié)與實現(xiàn)(https://zhuanlan.zhihu.com/p/125744132) 對十種模糊算法進(jìn)行總結(jié)、對比和盤點,其中雙重模糊(dual blur) 獲得了高性價比評價,故SAR Creator中bloom的模糊算法采用雙重模糊進(jìn)行了實現(xiàn)。雙重模糊的核心思路在于模糊的過程中進(jìn)行了降采樣和升采樣,即對RT進(jìn)行了降采樣以及升采樣。
部分代碼示例如下:
// 新建renderTexture數(shù)組
for (let i = 0; i < downSampleNum; i++) {
this.fboArr[i] = new RenderTexture(0,0,fboOptions);
if (i !== downSampleNum - 1) {
this.fboArr[(downSampleNum - 1) * 2 - i] = new RenderTexture(0,0,fboOptions);
}
}
// 下采樣
for (let i = 0; i < downSampleNum - 1; i++) {
uniforms.tDiffuse.value = fboArr[i].texture;
uniforms.halfPixel.value.set(1 / fboArr[i].width,1 / fboArr[i].height);
fsQuad.render(renderer,fboArr[i + 1]);
}
// 上采樣
const n = downSampleNum;
for (let i = downSampleNum - 1; i < (downSampleNum - 1) * 2; i++) {
uniforms.tDiffuse.value = fboArr[i].texture;
uniforms.downTexture.value = fboArr[2 * n - i - 3].texture;
uniforms.halfPixel.value.set(1 / fboArr[i].width,1 / fboArr[i].height);
fsQuad.render(renderer,fboArr[i + 1]);
}
此外,SAR Creator提供了多種blur kernel,設(shè)計師可以切換對比,調(diào)整出自己想要的光暈效果。
所有的模糊算法都是利用周遭像素值加權(quán)疊加計算得到結(jié)果的,權(quán)重則取決于距離。部分模糊算法的shader實現(xiàn)如下:
#if BLUR_KERNEL == 0 // --------------- Kawase ---------------
void blurKernel() {
#if SAMPLE_PHASE == 0 // down
vec4 sum = texture2D(tDiffuse,vUv) * 4.0
+ texture2D(tDiffuse,uv01.xy) + texture2D(tDiffuse,uv01.zw)
+ texture2D(tDiffuse,uv23.xy) + texture2D(tDiffuse,uv23.zw);
gl_FragColor = sum * 0.125;
#elif SAMPLE_PHASE == 1 // up
vec4 sum = texture2D(tDiffuse,uv01.xy) + texture2D(tDiffuse,uv23.xy)
+ texture2D(tDiffuse,uv45.xy) + texture2D(tDiffuse,uv67.xy)
+ (texture2D(tDiffuse,uv01.zw) + texture2D(tDiffuse,uv23.zw)
+ texture2D(tDiffuse,uv45.zw) + texture2D(tDiffuse,uv67.zw)) * 2.0;
gl_FragColor = sum * 0.0833;
#endif
}
#elif BLUR_KERNEL == 1 // --------------- Box4Tap ---------------
void blurKernel() {
vec4 sum = texture2D(tDiffuse,vUv + uvOffset.xy) + texture2D(tDiffuse,vUv + uvOffset.zy)
+ texture2D(tDiffuse,vUv + uvOffset.xw) + texture2D(tDiffuse,vUv + uvOffset.zw);
gl_FragColor = sum * 0.25;
}
#elif BLUR_KERNEL == 2 // --------------- Tent9Tap ---------------
void blurKernel() {
vec4 sum = texture2D(tDiffuse,vUv + uvOffset.xy)
+ texture2D(tDiffuse,vUv - uvOffset.xy)
+ texture2D(tDiffuse,vUv + uvOffset.zy)
+ texture2D(tDiffuse,+ vUv - uvOffset.zy)
+ (texture2D(tDiffuse,vUv - uvOffset.wy)
+ texture2D(tDiffuse,vUv + uvOffset.zw)
+ texture2D(tDiffuse,vUv + uvOffset.xw)
+ texture2D(tDiffuse,vUv + uvOffset.wy)) * 2.0
+ texture2D(tDiffuse,vUv) * 4.0;
gl_FragColor = sum * 0.0625;
}
材質(zhì)篇
互動活動場景中經(jīng)常會出現(xiàn)3D地形,如果通過建模軟件來生成三角形面片幾何體的方式支持該需求的話,會存在三角面片數(shù)過高從而消耗性能過大的問題。此外,設(shè)計師很難得到所需要的凹凸起伏的建模,設(shè)計調(diào)整成本過高。一般而言,設(shè)計師會希望通過一張灰度圖來表示相應(yīng)區(qū)域的高低,從而通過控制平面中各個著色點在垂直方向上的偏移來表達(dá)起伏。
而支持這種實現(xiàn)的技術(shù)就是位移貼圖(DisplacementMap)。如下圖1、2所示展示了PBR材質(zhì)修改位移貼圖縮放指數(shù)(DisplacementScale)為0和2的區(qū)別。如下圖3所示展示了控制高低的位移貼圖。
位移貼圖的實現(xiàn)方式是在材質(zhì)頂點著色器VertexShader中基于原始頂點信息、結(jié)合位移貼圖對頂點偏移的計算,得到實際展示的頂點位置,具體shader中定義和實現(xiàn)如下所示:
// 前置信息定義
#ifdef USE_DISPLACEMENTMAP
vec2 displacementUV; // 位移貼圖UV
uniform mat3 displacementUVMatrix; // 位移貼圖UV變換鋸齒
uniform sampler2D displacementMap; // 位移貼圖
uniform float displacementScale; // 位移縮放比例
uniform float displacementBias; // 位移偏移
#endif
// 頂點計算
#ifdef USE_DISPLACEMENTMAP
positionV4.xyz += normalize( positionV4.xyz ) * ( texture2D( displacementMap, displacementUV ).x * displacementScale + displacementBias );
#endif
此外,與位移貼圖調(diào)整當(dāng)前繪制點頂點不同,還可以通過調(diào)整當(dāng)前繪制點法線計算的方式使得渲染結(jié)果展示出一種比較細(xì)致的凹凸感,即凹凸貼圖(BumpMap)技術(shù)。如下圖1、2所示展示了PBR材質(zhì)修改凹凸貼圖縮放指數(shù)(BumpMapScale)為0和100的區(qū)別。如下圖3所示展示了控制表面粗糙感的凹凸貼圖。
凹凸貼圖的實現(xiàn)方式是在材質(zhì)片元著色器FragmentShader中基于原始頂點/法線信息、結(jié)合凹凸貼圖,計算出調(diào)整后的法線結(jié)果,具體shader中定義和實現(xiàn)如下所示:
// 前置信息定義
#ifdef USE_BUMPMAP
varying vec2 bumpUV; // 凹凸貼圖UV
uniform sampler2D bumpMap; // 凹凸貼圖
uniform float bumpScale; // 凹凸縮放比例
#endif
#ifdef USE_BUMPMAP
vec2 dHdxy_fwd() {
vec2 dSTdx = dFdx( bumpUV );
vec2 dSTdy = dFdy( bumpUV );
float Hll = bumpScale * texture2D( bumpMap,bumpUV ).x;
float dBx = bumpScale * texture2D( bumpMap,bumpUV + dSTdx ).x - Hll;
float dBy = bumpScale * texture2D( bumpMap,bumpUV + dSTdy ).x - Hll;
return vec2( dBx,dBy );
}
vec3 perturbNormalArb( vec3 surf_pos,vec3 surf_norm,vec2 dHdxy,float faceDirection ) {
vec3 vSigmaX = normalize( dFdx( surf_pos.xyz ) );
vec3 vSigmaY = normalize( dFdy( surf_pos.xyz ) );
vec3 vN = surf_norm;
vec3 R1 = cross( vSigmaY,vN );
vec3 R2 = cross( vN,vSigmaX );
float fDet = dot( vSigmaX,R1 ) * faceDirection;
vec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );
return normalize( abs( fDet ) * surf_norm - vGrad );
}
#endif
// 法線信息計算
#ifdef USE_BUMPMAP
normal = perturbNormalArb( posWorld,normal,dHdxy_fwd(),faceDirection );
#endif
所以我們可以看到,渲染引擎除了提供通用的基礎(chǔ)材質(zhì)外,往往需要在原有材質(zhì)基礎(chǔ)上靈活迭代,根據(jù)用戶使用場景和需求不斷支持新特性。
未來展望
SAR Creator 材質(zhì)庫、ShaderGraph特效、后處理等渲染能力在24年春節(jié)活動中得到進(jìn)一步完善和項目驗證,為互動場景提供了不錯的視覺效果。當(dāng)然在業(yè)務(wù)落地過程中,我們也發(fā)現(xiàn)了一些不足之處。比如后處理可選效果還不夠多、功能還不夠完善,目前只初步支持bloom和fxaa后處理。比如美術(shù)工作流還不夠高效,美術(shù)資產(chǎn)的制作和落地過程中可能存在卡點,需要多方溝通協(xié)調(diào)。后續(xù)我們會和美術(shù)設(shè)計師保持更緊密的溝通,更深入的了解用戶需求,提供更多開箱即用的材質(zhì)特性、后處理效果和ShaderGraph特效能力。