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