快手動效渲染引擎Crab,解鎖“游戲化動效”開發(fā)新方式! 原創(chuàng)
導讀:在上一篇文章中,我們?nèi)轿坏亟馕隽丝焓諺ision動效平臺的整體架構(gòu)及其演進思路。快手前端動效大揭秘:告別低效,vision平臺來襲!?????今天,我們將進一步深入,詳細介紹Vision動效平臺的渲染引擎——Crab,并分享在復雜動效渲染場景下積累的實踐經(jīng)驗和精彩案例。
?
一、項目背景
?
1.1 快手大型活動中的動效
動效在設(shè)計和用戶體驗領(lǐng)域中有重要的價值,表現(xiàn)力強的動效不僅能夠激發(fā)受眾用戶的興趣,提高參與度,還能提高留存和用戶活躍度,最終增強用戶對產(chǎn)品的粘性,因此活動中的動效越來越復雜。
下圖是我們開發(fā)過的具有復雜動效的活動案例。
可以看到,在這些活動中,最顯眼的KV部分由持續(xù)播放的動效進行承接,并且動效需要在用戶游玩活動的過程中進行反饋。另外,因為這些動效的持續(xù)時間非常長,且位置顯眼,所以為了保證用戶的體驗,需要這些動效在盡可能多的設(shè)備上正常展示。
總的來說,這類動效有三個特點:高表現(xiàn)力,高可交互性和高兼容度,為了方便說明,我們將同時滿足這三個特點的動效稱為「游戲化動效」。?
1.2 “常規(guī)”動效的實現(xiàn)方案和局限
一般情況下,動效的實現(xiàn)可以分成兩種類型的方案:關(guān)鍵幀方案以及逐幀方案。
常規(guī)的關(guān)鍵幀方案有CSS和Lottie,比較簡單的動效選用CSS更好,比較復雜的動效選擇Lottie可以更好的保證開發(fā)效率和動效還原。CSS和Lottie的優(yōu)勢是通用和常規(guī),但缺點是只能適合實現(xiàn)基礎(chǔ)Transform或矢量圖形變化的平面動效。
常規(guī)的逐幀方案有序列幀,以及APNG,視頻和透明視頻這些針對不同場景的改進版本的類序列幀方案。逐幀方案的動效單幀表現(xiàn)力上限非常高,但代價是幾乎沒有可交互能力,基本只能制作單純的播片邏輯動效。
總的來說,“常規(guī)”的動效實現(xiàn)方案只能滿足可交互性和表現(xiàn)力的其中一種,很難兼顧,無法滿足業(yè)務中游戲化動效的需求。
1.3 如何實現(xiàn)兼顧表現(xiàn)力和可交互性?
要實現(xiàn)兼顧可交互性和表現(xiàn)力的動效,就需要相比常規(guī)動效實現(xiàn)手段對動畫元素控制性更強的實現(xiàn)手段。對于這個需求,最適配的方案就是使用WebGL。
WebGL是一個給基于OpenGL ES的低級3D圖形API使用的開放Web標準。通過WebGL的能力,我們對動畫元素的控制粒度可以精細到該動畫元素的細分圖元層面,對動畫元素渲染表現(xiàn)控制可以精細到單個像素層面。有了這種控制精度,就有了同時兼顧可交互性和表現(xiàn)力的底層支持。
為什么是Crab?
WebGL的API是低級3D圖形API,在實際業(yè)務使用中很難直接使用這個API,下面是使用WebGL直接繪制一張三角形的示例:
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
}
function createProgram(
gl,
vertexShader,
fragmentShader
) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
return program;
}
let fragStr = `#version 300 es
precision mediump float;
in vec2 position;
out vec4 outColor;
void main () {
outColor = vec4(1., 0., 0., 1.);
}
`;
let vertStr = `#version 300 es
in vec2 a_position;
uniform vec2 u_resolution;
out vec2 position;
void main() {
position = a_position;
gl_Position = vec4(a_position * 2. - 1., 0. ,1.);
}
`;
let positions = new Float32Array([.5, 1., 1., 0., 0., 0.]);
let canvas = document.querySelector('#can');
let video = document.querySelector('video');
canvas.width = 640;
canvas.height = 320;
let gl = canvas.getContext('webgl2');
let program = createProgram(
gl,
createShader(gl, gl.VERTEX_SHADER, vertStr),
createShader(gl, gl.FRAGMENT_SHADER, fragStr)
);
gl.useProgram(program);
gl.viewport(0, 0, canvas.width, canvas.height);
const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
const positionbuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionbuffer);
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
gl.enableVertexAttribArray(positionAttributeLocation);
const size = 2;
const type = gl.FLOAT;
const normalize =false;
const stride = 0;
const offset = 0;
gl.vertexAttribPointer(positionAttributeLocation, size, type, normalize, stride, offset);
gl.bindVertexArray(vao);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
const primitiveType = gl.TRIANGLES;
const doffset = 0;
const count = 3;
gl.drawArrays(primitiveType, doffset, count);
近百行的代碼量才能渲染出如下的一個簡單的三角形:
因此我們應通過封裝完成的引擎來使用WebGL能力。
在進行動效渲染引擎的挑選之前,我們首先明確了四點挑選原則:
- 實現(xiàn)輕量化:實現(xiàn)動效時,只引入所需的引擎內(nèi)容,控制依賴包的體積
- 可擴展性:可以方便的進行動效的渲染特性和交互特性的開發(fā),而無需任何細節(jié)都強依賴于引擎的具體能力支持
- 便于沉淀:渲染特性和交互特性的擴展有相對標準的接口和使用規(guī)范,方便進行沉淀和日后其他場景的復用。
- 游戲化動效和業(yè)務在同一倉庫下維護:減少協(xié)作成本,便于開發(fā)、調(diào)試和維護。
?
基于這四點原則,我們調(diào)研了已有的可選方案,發(fā)現(xiàn)它們都不能完全滿足我們的需求,因此我們選擇了自研Crab動效渲染引擎。
?
二、Crab簡介
Crab是一款可在支持WebGL的環(huán)境(Web,快手小游戲等)中使用的游戲化動效渲染引擎。
2.1 流程Crab的分層架構(gòu)方式
Crab大體可以分為接入層、資產(chǎn)抽象層、擴展層、運行層和功能層,其中接入層、資產(chǎn)抽象層、擴展層、運行層都位于引擎的核心包中,功能層則由業(yè)務使用中積累的一些具有通用性功能的第一方或第三方的獨立包組成。
這種分層架構(gòu)方式有什么好處?
我們的游戲化動效引擎實現(xiàn)的指導原則就是上面提到過的實現(xiàn)輕量化、可擴展性、便于沉淀以及方便維護,這種架構(gòu)方式可以很好滿足我們的要求:
- 可擴展性: 擴展層的RenderProcessor和RenderPipeline
?提供了渲染上的擴展能力, Component和System提供了邏輯上的擴展能力, 通過它們,我們保證了這個引擎的擴展能力 - 便于沉淀: 使用擴展層擴展出來的功能如果有沉淀價值, 可以單獨發(fā)包, 作為功能層的一員
- 實現(xiàn)輕量化: 正如剛才提到的, 大部分具體功能都位于功能層的不同包內(nèi), 所以我們的核心包比較輕量, 在使用功能的時候也可以只引入對應功能的包.
- 游戲化動效和業(yè)務在同一倉庫下維護: 作為一個主要使用TS和WebGL API的前端庫,Crab可以自然的在前端項目中使用。
2.2 Crab一個Tick的處理流程
接下來我們從Crab的一個Tick入手,介紹下擴展層的實現(xiàn)方式。
Crab在一個Tick的不同時機,放置了許多可以執(zhí)行邏輯鉤子的階段,以此作為暴露交互能力的接口。使用者可以注冊自己的鉤子來執(zhí)行自定義的邏輯操作。
在這些邏輯鉤子中間的渲染處理部分(Render Processor)則用來執(zhí)行渲染操作,同樣提供暴露渲染能力的接口。
2.2.1 邏輯處理部分
Crab中渲染的場景是通過一個由許多節(jié)點組成的樹狀結(jié)構(gòu)來描述的,節(jié)點上可以掛載不同的組件,組件中可以包含關(guān)于該節(jié)點的不同描述信息,比如Transform節(jié)點用于描述一個節(jié)點的位置信息,Renderable節(jié)點用于描述一個可渲染節(jié)點的渲染信息。
節(jié)點組件中的信息可以被用戶注冊的邏輯鉤子讀寫,用戶可以通過實現(xiàn)和注冊System的方式來進行邏輯鉤子的實現(xiàn)和注冊。不同的System可以指定只對滿足某些特定條件的節(jié)點生效(比如必須掛載某個組件/必須沒有掛載某個組件)以及要注冊哪些階段的邏輯鉤子。
通過這種方式,使用者可以將不同的描述信息放入不同的組件中,將不同的操作邏輯放入不同的System中,有相關(guān)性的組件和System可以自然的組合作為一個功能的實現(xiàn),可以增強代碼的可讀性并簡化進行功能沉淀所需的前置操作。
2.2.2 渲染處理部分
渲染處理部分(Render Processor)主要由1個或多個渲染階段(Render Stage)連接組成,每個渲染階段的渲染結(jié)果可以作為該幀畫面的最終展示效果,或者作為渲染出一幀畫面需要的中間結(jié)果。每個渲染階段可以執(zhí)行場景中匹配的可渲染節(jié)點中的渲染管線(Render Pipeline),渲染管線是Crab中渲染的最小單元,會執(zhí)行一次Draw Call,以及該Draw Call相關(guān)的數(shù)據(jù)綁定,標志設(shè)置等邏輯。
使用不同的渲染階段和連接方式,以及不同的渲染管線設(shè)置,就可以滿足不同的渲染需求。
渲染階段
渲染階段是組成Crab的渲染處理部分的基礎(chǔ)模塊,Crab提供了四種類型的渲染階段,來滿足從FrameBuffer/場景到FrameBuffer/屏幕的不同連接需求。
四種渲染階段都可選接收用戶傳入的渲染信息,比如Shader,Viewport,是否刷新FrameBuffer等。Crab中的渲染階段具有自由度很高的可擴展性,且可以像樂高積木一樣互相拼接,用戶可以利用它自由實現(xiàn)陰影,延遲渲染,后處理效果等渲染特性,也可以組合各種不同的渲染特性,積累自定義渲染流程,具有通用性的自定義渲染流程也可以通過發(fā)布為Crab功能層的一部分,獲得更多的使用。
渲染管線
渲染管線是執(zhí)行渲染的最小單元。內(nèi)部維護了引用的Shader信息,以及對應的Shader變量,數(shù)據(jù)和宏開關(guān)等與一次Draw Call相關(guān)的信息。當要執(zhí)行一個渲染管線的渲染時,Crab會根據(jù)其包含的數(shù)據(jù)和宏等生成實例ID,以此判斷渲染執(zhí)行時是否需要創(chuàng)建新的實例,減少實例創(chuàng)建和切換成本。
渲染管線一般被裝載在可渲染(Renderable)組件中,Crab的內(nèi)置渲染System會自動執(zhí)行匹配節(jié)點的可渲染組件中的渲染管線進行渲染,使用者可以直接使用Crab已經(jīng)封裝的可渲染組件,比如通用Unlit
?渲染組件、通用Blinn-Phong渲染組件、粒子渲染組件、Spine渲染組件等來拼裝需要的效果。使用者也可以擴展自己的可渲染組件,通過傳入Shader、混合模式等信息來實現(xiàn)自定義的渲染效果。
三、游戲化動效的應用與實踐
在業(yè)務中使用Crab支持游戲化動效的過程中,我們也積累了一些通用的方案和功能,比如粒子系統(tǒng),Spine支持、一些材質(zhì)和后處理效果等。
3.1 2D動效方案應用
Spine
Spine是一種基于2D的骨骼動畫實現(xiàn)方案,相比于常規(guī)動效,它超越了傳統(tǒng)2D動畫的限制,能帶來2.5D的體驗。相比于3D動效,它的制作和渲染更加輕量,是一種比較平衡的動畫方案。
?,時長00:07
Crab對使用Spine動畫時常用的動畫播放、皮膚疊加/替換以及掛點功能進行了封裝和支持,同時還提供了Spine資產(chǎn)的加載器,使用者在使用過程中可以不關(guān)注Spine的實現(xiàn)細節(jié),從而更好的聚焦在業(yè)務邏輯等其他部分的實現(xiàn)上。
置換貼圖
置換貼圖是AE提供的一項能力,它能實現(xiàn)類似于Spine的2D骨骼動畫的簡易的效果,且素材生產(chǎn)成本相比Spine更低,因此在2D的氛圍動效等相對重要性不高的動效實現(xiàn)時是一種具有性價比的實現(xiàn)方案。
置換貼圖的實現(xiàn)原理:
要實現(xiàn)置換貼圖的效果,需要三個素材,分別是:
- 一張顏色貼圖,用于表明渲染的像素色值
- 一張位移系數(shù)貼圖,用來表明每個像素進行形變的相對強度
- 一組包含全局位移方向和強度的動畫片段
設(shè)計師在AE中對一個圖層的全局位移方向和強度進行動畫關(guān)鍵幀的設(shè)置,就可以得到一個非線形形變的動畫效果。簡單說明的話,置換貼圖效果可以被看作只由一根骨骼控制的,在一個貼圖分辨率大小的平面網(wǎng)格上的骨骼動畫。
3.2 3D動效方案應用
模型材質(zhì)
上限最高的渲染方式自然是基于物理的渲染方式,但是這種渲染方式有一個問題在于,在簡單的光照條件且不引入光線追蹤時,渲染效果一般比較粗糙,而如果設(shè)置復雜的光照條件和材質(zhì)參數(shù),則渲染參數(shù)的調(diào)試成本和性能消耗都會很大。
對于這種性能和渲染效果上的困境,我們在實踐中選擇了另一種材質(zhì)——Matcap材質(zhì)。Matcap材質(zhì)的本質(zhì)是通過預計算貼圖保留了一個材質(zhì)在各個可見方向上的顏色信息,可以很好的保留C4D等軟件中的渲染效果;引擎實時渲染時,只需要讀取貼圖,當場景中不存在動態(tài)光源時,可以在較低的性能消耗下,實現(xiàn)接近離線渲染的表現(xiàn)效果。
Matcap材質(zhì)的實現(xiàn)原理
要實現(xiàn)這個材質(zhì),需要三種貼圖:索引貼圖,Matcap貼圖以及可選的顏色貼圖。
當進行著色時,首先訪問索引貼圖,獲得該像素點對應的Matcap貼圖索引,然后依據(jù)該點上的法線向量,從對應的Matcap貼圖獲取特定位置的像素作為最終使用的像素即可。
對于顏色比較少的材質(zhì),直接使用Matcap貼圖上的顏色信息沒什么問題,但如果顏色比較多,需要的Matcap貼圖的數(shù)量將會是難以接受的,為了避免Matcap貼圖數(shù)量的暴漲,可以使用一張顏色貼圖,最終顏色不再直接使用Matcap貼圖中存儲的顏色,而是通過Mapcap貼圖中的顏色和顏色貼圖中的顏色混合得到。
模型動作
動作過渡
要實現(xiàn)一個生動的3D角色,需要保證它持續(xù)活動著,最簡單的方法就是時刻保持在模型上至少應用一個動畫片段。
不同的動畫片段之間切換的時候,如果不做任何優(yōu)化,效果會很生硬,對此我們應用了動畫過渡和混合的能力,可以在不增加設(shè)計師工作量的前提下,提高動畫的流暢度。
未應用動作過渡 | 應用動作過渡 |
頂點形變動畫
骨骼動畫是基于關(guān)節(jié)Transform混合的動畫,雖然在大部分情況下的表現(xiàn)都足夠優(yōu)秀,但是在諸如表情動畫等對形變的精細性要求更高的場景下往往顯得不夠生動。
對此我們應用了頂點形變動畫的方案,頂點形變動畫的原理是保存網(wǎng)格的多份快照,并在動畫過程中改變不同快照的應用權(quán)重,從而實現(xiàn)精細動畫的效果。
表情快照 | 動畫效果 |
多快照的兼容性實現(xiàn)
頂點快照信息一般使用貼圖的方式在Shader中進行使用,最理想的實現(xiàn)方式是使用Texture Array的方式存儲頂點快照,但Texture Array是在WebGL2上才全面支持的特性,對只能使用WebGL1的設(shè)備,如果用普通的2D貼圖存儲頂點快照,則受限于貼圖數(shù)量的限制,以及材質(zhì)的其他特性所需貼圖(顏色貼圖,法線貼圖,AO貼圖等)擠占位置,會導致可以應用的快照數(shù)量非常有限,為了讓只能使用WebGL1的設(shè)備也可以實現(xiàn)多快照的頂點形變動畫,我們在這種情況下將所有快照存放在同一個2D貼圖中,并實現(xiàn)專門的查找函數(shù)查找對應的頂點快照信息,在WebGL1的上下文中實現(xiàn)了兼容更多快照的頂點形變動畫。
3.3 特效應用
粒子系統(tǒng)
粒子系統(tǒng)是非常常見的特效方案,可以用于模擬火、煙、云、水、落葉等自然現(xiàn)象,也可以用來模擬發(fā)光軌跡、速度線等抽象視覺效果。
6
Crab中提供了一套粒子系統(tǒng)以供使用。
Crab的粒子系統(tǒng)
Crab的粒子系統(tǒng)實現(xiàn)參考了Unity的粒子系統(tǒng)組織方式,由多個組件構(gòu)成,這些組件可以氛圍核心組件和可選組件。
核心組件中的主組件負責控制粒子系統(tǒng)的基本參數(shù),比如生命周期、粒子存活時間等,發(fā)射組件負責控制粒子的發(fā)射。
可選組件實現(xiàn)了不同的功能,比如形狀組件可以控制發(fā)射器的形狀,Velocity Over Lifetime組件控制粒子生命周期中的速度變化等等。使用者可以根據(jù)想要的效果只啟用對應的可選組件。
Shader Effects
對于一些特殊的,或者不具有通用性的特效,一種值得考慮的實現(xiàn)方式就是使用Shader
在一些情況下,Shader實現(xiàn)會比使用通用方式性能更好,或得到特別的表現(xiàn)效果。
9
借助Crab提供的渲染階段和渲染管線上的擴展能力,使用者可以自由實現(xiàn)期望的Shader效果。
下為使用渲染階段實現(xiàn)Crab文字內(nèi)流體效果的部分代碼示意:
this.scene.renderProcess
.addStage(advectVelPass)
.addStage(disturbVelPass)
.addStage(advergencePass)
.addStage(iteraGroup)
.addStage(applyForcePass)
.addStage(disturbDyePass)
.addStage(advectDyePass)
.addStage(bloomGroup)
.addStage(displayPass);
四、游戲化動效的應用與實踐
對于一個場景的游戲化動效,里面不同的原子動效,研發(fā)會接收到來自上游的許多不同種類的交付素材。其中既有來自平面動效交付鏈路的Lottie、序列幀、AE參數(shù)等,也有來自游戲化動效交付鏈路的3D模型、粒子特效和模型材質(zhì)等。
這么多種類的來自不同鏈路的動效交付,給我們帶來了幾個挑戰(zhàn):
- 混合播放
交付的素材中既會有3D模型等游戲化素材, 又會有Lottie, 序列幀等平面動效素材, 如何保證這些不同來源的資產(chǎn)可以一同展示?
- 素材生產(chǎn)
一些動效素材, 比如粒子特效參數(shù), 材質(zhì)參數(shù)等, 必須基于動效渲染引擎進行所見即所得的編輯來生產(chǎn), 如何實現(xiàn)?
- 素材轉(zhuǎn)換
FBX模型,、序列幀等素材,無在runtime直接使用的表現(xiàn)不佳,需要進行格式轉(zhuǎn)換。每種類型的素材需要的轉(zhuǎn)換方式不同,如何降低轉(zhuǎn)換過程中的心智負擔?
交付痛點的解決方案
為了解決上述的痛點,我們?yōu)镃rab開發(fā)了一個編輯器,編輯器提供了素材的導入導出、格式轉(zhuǎn)換、編輯以及預覽功能。
上游提供的任意已經(jīng)支持的動效素材類型,經(jīng)過編輯器中可選的格式轉(zhuǎn)換以及編輯處理之后,都可以導出為可以在Crab直接進行使用的動效素材,而編輯器提供的預覽功能也可以保證導出的原子動效的效果符合設(shè)計同學的預期,將走查問題盡量前置。
?
交付示例
以材質(zhì)交付為例,當在編輯器的屬性面板編輯材質(zhì)參數(shù)時,主視區(qū)的預覽效果可以實時更新,當設(shè)計師編輯完成后,可以直接導出為研發(fā)在runtime時可用的材質(zhì)素材。
五、游戲化動效的性能指標和調(diào)優(yōu)
Crab內(nèi)部執(zhí)行渲染操作時,會執(zhí)行一些基礎(chǔ)的性能優(yōu)化,比如資產(chǎn)在顯存創(chuàng)建后,及時銷毀資產(chǎn)在內(nèi)存的緩存,減少不必要的WebGL上下文切換,如活躍shader program切換、混合模式切換等。
除了Crab引擎內(nèi)部的優(yōu)化,在特定場景的調(diào)優(yōu)對一個游戲化動效的性能常具有更顯著的影響。在進行游戲化動效開發(fā)時,主要關(guān)注的性能指標有以下幾個:幀率、Draw Call數(shù)量,三角形數(shù)量,內(nèi)存占用以及卡頓率。
- 幀率:我們可以通過鎖定引擎幀率的方式,控制每秒執(zhí)行的邏輯計算和渲染計算的數(shù)量,從而減少對計算資源的占用,提供頁面整體的性能。
- Draw Call數(shù)量:我們可以通過視錐剪裁或使用實例化渲染的方式來減少Draw Call的數(shù)量。比如當場景中存在N個相同模型的時候,使用實例化渲染可以將Draw Call的數(shù)量從N次減少到一次。
- 三角形數(shù)量:一幀渲染使用的三角形數(shù)量對性能影響很大,對一些不太重要或不必要的場景物體,我們會盡量減少它包含的三角形數(shù)量,比如使用單面片或十字面片,可將物體所需的三角形數(shù)量降低至10個內(nèi)。
- 內(nèi)存占用:要降低內(nèi)存占用,主要可以通過減少貼圖分辨率或使用壓縮貼圖的方式來進行優(yōu)化,另外也要注意使用網(wǎng)格的物體三角形的數(shù)量不要爆炸,否則也會對內(nèi)存占用帶來負面影響。
- 卡頓率:對于卡頓率,我們一般可以通過優(yōu)化shader中的邏輯實現(xiàn)進行優(yōu)化,比如在shader中要盡量避免動態(tài)循環(huán)次數(shù)的循環(huán)語句和條件語句的使用。
以上就是本篇文章的全部內(nèi)容,介紹了自研的Crab動效渲染引擎,以及Crab在實際業(yè)務的使用中落地過的具體動效方案,交付方案和性能調(diào)優(yōu)方案。希望能給看到這里的你帶來一些收獲,如果有任何問題或建議,也歡迎留言,不吝賜教。
- END -
