2024 抖音歡笑中國年之渲染技術實踐與探索
前言
抖音在2024年春節期間推出了歡笑中國年系列活動,為用戶帶來了全新的體驗和樂趣。而SAR Creator則為該項目研發工作提供了重要的技術支持。SAR Creator是一款基于 Typescript 的高性能、輕量化的互動解決方案,目前支持了瀏覽器和跨端框架平臺,服務于字節內部的各種互動業務。
這些絢爛多彩的互動場景當然也離不開實時渲染技術的支持,因此本文將專門介紹春節活動招財神龍和神龍探寶中SAR Creator渲染相關的業務實踐經驗以及技術探索和嘗試。
春節—招財神龍
春節—神龍探寶
比如抖音歡笑中國年系列文章《招財神龍互動技術揭秘》中就有提到,項目中“家”場景就是由2D元素以及不同材質支持的3D元素共同組成的。出于性能和美術效果的考慮,各3D模型使用的材質會有所不同,比如無光照Unlit材質、基于物理的PBR材質。對于陰影這種移動端性能消耗比較大的特性,不同物體的接收也會做特殊處理。這些材質的選擇以及光照陰影的支持都是依托于SAR Creator材質庫能力的支持。如下圖所示即為SAR Creator Unlit材質(左圖)和PBR材質(右圖)的示例。
無光照Unlit材質示例
基于物理的PBR材質示例
此外,SAR Creator支持使用ShaderGraph插件制作自定義材質,幫助用戶制作更多可定制化的特效。神龍探寶項目中實現了多種基于ShaderGraph的特殊效果,包括:入場溶解特效、分區解凍特效、拖尾特效等。下圖展示了利用ShaderGraph定制卡通風格水體的效果。
ShaderGraph卡通水體
除了上述春節活動中順利落地的渲染效果外,我們還嘗試做了很多效果提升的技術探索,比如后處理輝光效果、凹凸貼圖等。希望可以更好的提升美術設計師的設計體驗和最終的渲染效果。
bloom輝光
招財神龍渲染實踐
“招財神龍”活動是2024年春節游戲化玩法之一,活動整體采用3D場景(龍在家場景)+ 2D場景(龍尋寶場景)結合的方式。在招財神龍的活動中,設計同學基于SAR Creator編輯器,進行場景搭建和效果還原工作;研發同學基于SAR Creator渲染能力,快速進行技術方案選型和實施。
2D&3D混合渲染
對于活動中的“龍”和“小女孩”元素,我們采用3D模型,提供更為逼真的體驗感。而針對場景中的房子、炮仗等,我們使用2D貼片來呈現。通過調整相機的遠近平面、fov等參數,展示出小女孩在炮仗前、龍在房子前、龍在炮仗后的視覺假象。
材質庫
SAR Creator提供了Unlit、PBR、Uber和NPR等多種材質的選擇。
例如,這次“招財神龍”中的白天/黑夜場景場景,小女孩和龍的皮膚顏色等需要有不同的表現,就是基于材質的“顏色貼圖”能力來實現的。
針對PBR材質來說,設計同學還基于SAR Creator提供的金屬度、粗糙度來進行小女孩身體細節的調整。
為了追求更佳的視覺體驗,在小女孩的模型上,設計同學為不同的部位(身體、頭發和衣服)賦予了不同的PBR材質的實例,再通過調整不同PBR材質的金屬度、粗糙度屬性,微調受光條件下,不同部位的表現細節。
這次活動,我們不僅使用了PBR材質,綜合性能和實用性的角度,還使用了Sar Creator提供的Unlit材質。比如“龍”模型中的身體、胡須、眼睛等模塊。Unlit材質是一種簡單的、不受光源影響的材質,在技術選型時,為了平衡性能和效果,通常是活動開發的首選。
光照陰影
除了上述所說的這些材質,為了實現場景中元素的真實性,設計同學借助SAR Creator提供的渲染能力,利用燈光、陰影來優化渲染場景。
SAR Creator提供了平行光的方向、顏色、強度等屬性,使得設計同學可以調整出不同效果。
為了更好的光照效果,我們這次使用了兩個平行光,利用PBR受光的特性,可以實現更貼近真實世界的效果。
只使用了環境光
使用了一個平行光(小女孩鞋子、臉、手部等部分都收到了光照影響)
使用了兩個平行光微調(小女孩背部收到了光照,更貼近真實效果)
只有光照,沒有陰影的話,同樣也不符合物理世界的客觀規律。SAR Creator通過在光源上設置“投射陰影”,在需要顯示陰影的物體上,設置“接受陰影”,即可快速的實現陰影的效果。
無陰影
有陰影
利用SAR Creator提供的ShadowMaterial這種自實現的材質,我們還能通過調整顏色、透明度等常用屬性,快速調整出設計師想要的陰影效果。
神龍探寶渲染實踐
神龍探寶是2024年春節系列活動中的一個以2D場景為主的互動玩法,其嘗試并成功落地了多種特效渲染技術。本章節主要有三個特效渲染技術點可分享給大家。分別是:入場溶解特效、分區解凍特效、拖尾特效。
入場溶解
實現入場溶解特效核心是采樣一張溶解圖(可低分辨率128x128),通過動畫step.edge即可。該方案主要通過ShaderGraph可視化界面開發Shader幫助實現美術預期效果,具體節點實現實現如下圖所示:
如想了解更多技術細節,各位同學可用WebGPU版ShaderGraph在線體驗(https://deepkolos.github.io/shader-graph-wgsl/?graph=demoSummberDissolve)(PC Chrome113+),也歡迎內部同學直接體驗SAR Creator。
地圖分區解凍
地圖分區解凍需要實現的效果是支持分區塊單獨控制其處于解凍/未解凍狀態。表現效果會隨著解凍狀態的變化而變化。
如果按照傳統前端實現估計需要7個區域小圖+1張底圖=8張圖片去實現, 即需要消耗8個繪制指令(DrawCall)。雖然傳統方式可以通過動態合批的方式優化繪制指令(DrawCall)為1個,但合批操作本身也有耗時,且每次資源替換+小圖位置調整,會帶來額外工作量。而利用ShaderGraph插件定義支持圖片存儲特定貼圖IDMap的Shader可解決這些問題,只需一張JPG 一張PNG 即可。首先我們需要將區域信息存儲在A通道,比如區域A = 0.9 區域B = 0.8 以此類推。
未點亮
已點亮
解凍過程
然后在Shader中根據點亮前后紋理采樣顏色值,混合計算出最終像素顏色值,以實現每個區域的解凍/未解凍狀態變化。具體計算邏輯如下所示:
如想了解更多技術細節,該例子也可用WebGPU版ShaderGraph在線體驗(https://deepkolos.github.io/shader-graph-wgsl/?graph=demoCustomMap)(PC Chrome113+)。
然而在上述效果的實現基礎上,設計同學提出了更高的渲染需求,要求冰凍區域沿邊緣浸染已解凍區域,來避免硬邊緣。由于項目時間節奏比較緊張,綜合考慮時間成本和收益后,邊緣浸染需求最終沒有推進支持。通過簡單的調研,該效果可能的一個解法是:增加7個2D光照,通過光照計算范圍來實現冰凍浸染效果,但問題在于沒法實現沿邊緣浸染。各位如果有什么好的思路也可以分享下,也許可以通過ShaderGraph插件快速支持這種定制需求。
粒子拖尾+幾何拖尾
設計效果
落地實現
我們可以分析表格第三列中效果參考的構成,得出技術要點為: 幾何拖尾+粒子拖尾+頭部星光。頭部星光較為簡單,只需要一個Sprite/Plane+Tween增加下旋轉動畫即可。下面將主要介紹粒子拖尾和幾何拖尾的技術實現。
粒子拖尾
目前SAR Creator現有非常強大的粒子系統,可快速實現粒子效果,提效非常明顯。
但完整的粒子系統在功能強大的同時包體積也相對較大,為了兼顧粒子效果的同時也避免包體積問題,需實現簡易版拖尾粒子。通過ShaderGraph結合EmitOverDisatance,抽離出的粒子拖尾特效資源打包后只有7.66KB。下圖左為: 簡易版粒子拖尾+EmitOverDistance+ShaderGraph聯動,下圖中/右為: 粒子系統示例和ShaderGraph定制材質的參數設置。
目前粒子拖尾已集成進SAR Creator以及ShaderGraph插件,方便用戶更加直觀的調試材質特效。
GPU Instancing是一個DrawCall繪制大量相同幾何,姿態不同的技術。
所以簡易版拖尾粒子本質上是一個GPU Instancing幾何更新器。
大量粒子的位移動畫通過Shader使用GPU并行算力完成,節約寶貴的CPU算力。
感興趣代碼實現,可查看??開源實現(https://github.com/deepkolos/three-js-trail) or 線上Demo(https://deepkolos.github.io/three-js-trail/),或者SAR Creator中直接體驗。
幾何拖尾
幾何拖尾本質上也是一個幾何更新器,不過并非更新Instanced數據,而是幾何本身數據Position+Index,使用下圖可直觀了解幾何拖尾的關鍵邏輯。
如上圖左所示為幾何拖尾的幾何部分實現,參考效果的游動效果則需要在Shader中增加UV動畫+拖尾沿Brush重心縮小,所以幾何拖尾同樣支持ShaderGraph擴展材質。
如感興趣代碼實現,可查看??開源實現(https://github.com/deepkolos/three-js-trail) or 線上Demo(https://deepkolos.github.io/three-js-trail/),或者SAR Creator中直接體驗。
ShaderGraph探索
ShaderGraph自定義Shader相較于研發編寫定制材質而言主要優勢在于更高的自由度。SAR Creator通過ShaderGraph插件可以邊看中間結果,邊理解特效的實現方式。同時幫助用戶更好的調試渲染結果高度不可控的特效。此外,ShaderGraph插件實現思路和節點能力實現方式與Unity ShaderGraph基本一致,用戶可以以極低成本的方式參考Unity已有特效并搬運到SAR Creator上。
比如抖音故障效果:
再或者卡通水體WebGPU版ShaderGraph在線預覽(https://deepkolos.github.io/shader-graph-wgsl/?graph=demoCartoonWater):
渲染技術探索
除了在項目實際落地的渲染技術外,我們也在春節項目中嘗試探索渲染技術可能應用場景。下面我們將通過后處理篇和材質篇來進一步介紹其中的技術點。
后處理篇
比如在“招財神龍”的龍須上,我們希望能增加輝光bloom的效果。bloom是屏幕后處理效果中較為常用的一種,表現為高光物體帶有泛光效果,通常會搭配HDR來得到更好的效果。
在技術方案的實現上:針對此類特定區域的輝光,我們引入了亮度閾值。第一步,對原場景圖進行篩選時,所有小于這個閾值的像素都會被篩掉,僅保留大于等于該亮度閾值的區域,即我們的龍須區域。第二步,對上一步操作的結果龍須區域進行模糊操作,達到光溢出的效果。最后,我們將處理過的圖像和原圖像進行疊加,就得到了最終的效果。
bloom渲染流程示意圖
bloom渲染流程中的第一步:過濾高亮區域,我們在shader的屬性中加一個lumaThreshold,然后提取圖片像素亮度進行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渲染流程中的第二步,圖像模糊算法在后處理渲染領域中占據著重要的地位,后處理中所采用模糊算法的優劣,直接決定了后處理管線最終的渲染品質和消耗性能的多少。高品質后處理:十種圖像模糊算法的總結與實現(https://zhuanlan.zhihu.com/p/125744132) 對十種模糊算法進行總結、對比和盤點,其中雙重模糊(dual blur) 獲得了高性價比評價,故SAR Creator中bloom的模糊算法采用雙重模糊進行了實現。雙重模糊的核心思路在于模糊的過程中進行了降采樣和升采樣,即對RT進行了降采樣以及升采樣。
部分代碼示例如下:
// 新建renderTexture數組
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,設計師可以切換對比,調整出自己想要的光暈效果。
所有的模糊算法都是利用周遭像素值加權疊加計算得到結果的,權重則取決于距離。部分模糊算法的shader實現如下:
#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;
}
材質篇
互動活動場景中經常會出現3D地形,如果通過建模軟件來生成三角形面片幾何體的方式支持該需求的話,會存在三角面片數過高從而消耗性能過大的問題。此外,設計師很難得到所需要的凹凸起伏的建模,設計調整成本過高。一般而言,設計師會希望通過一張灰度圖來表示相應區域的高低,從而通過控制平面中各個著色點在垂直方向上的偏移來表達起伏。
而支持這種實現的技術就是位移貼圖(DisplacementMap)。如下圖1、2所示展示了PBR材質修改位移貼圖縮放指數(DisplacementScale)為0和2的區別。如下圖3所示展示了控制高低的位移貼圖。
位移貼圖的實現方式是在材質頂點著色器VertexShader中基于原始頂點信息、結合位移貼圖對頂點偏移的計算,得到實際展示的頂點位置,具體shader中定義和實現如下所示:
// 前置信息定義
#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
此外,與位移貼圖調整當前繪制點頂點不同,還可以通過調整當前繪制點法線計算的方式使得渲染結果展示出一種比較細致的凹凸感,即凹凸貼圖(BumpMap)技術。如下圖1、2所示展示了PBR材質修改凹凸貼圖縮放指數(BumpMapScale)為0和100的區別。如下圖3所示展示了控制表面粗糙感的凹凸貼圖。
凹凸貼圖的實現方式是在材質片元著色器FragmentShader中基于原始頂點/法線信息、結合凹凸貼圖,計算出調整后的法線結果,具體shader中定義和實現如下所示:
// 前置信息定義
#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
所以我們可以看到,渲染引擎除了提供通用的基礎材質外,往往需要在原有材質基礎上靈活迭代,根據用戶使用場景和需求不斷支持新特性。
未來展望
SAR Creator 材質庫、ShaderGraph特效、后處理等渲染能力在24年春節活動中得到進一步完善和項目驗證,為互動場景提供了不錯的視覺效果。當然在業務落地過程中,我們也發現了一些不足之處。比如后處理可選效果還不夠多、功能還不夠完善,目前只初步支持bloom和fxaa后處理。比如美術工作流還不夠高效,美術資產的制作和落地過程中可能存在卡點,需要多方溝通協調。后續我們會和美術設計師保持更緊密的溝通,更深入的了解用戶需求,提供更多開箱即用的材質特性、后處理效果和ShaderGraph特效能力。