字人著色器全解析:皮膚/頭發(fā)/眼睛渲染與實時驅(qū)動)
前陣子帶一個數(shù)字人項目美術(shù)同事第一次把角色丟進UE4里做燈光測試截圖發(fā)到群里幾個人看著那張圖都沉默了。模型布線、貼圖精度都是按寫實標準做的但燈光一打人就是個蠟像皮膚是硅膠質(zhì)感眼神像貼紙頭發(fā)像方便面。我花了兩周時間把UE4里跟“人”有關(guān)的著色鏈路從上到下翻了一遍——皮膚、頭發(fā)、眼睛、口腔再接入動捕和面捕設(shè)備做實時驅(qū)動才終于讓這個角色在鏡頭里算得上“像活人”。這篇文章就是記錄那段時間拆出來的底層邏輯和踩過的坑。UE4里沒有叫“人類著色器”的官方材質(zhì)節(jié)點中文社區(qū)這么說的通常是指一整套專門用來渲染寫實人物的著色方案。它不解決“怎么做一個會動的模型”而是解決“為什么模型明明很精細看起來卻不像人”的問題。下面我按渲染棧的每一層展開。1. 先從“塑料人”說起人體著色器在解決什么問題1.1 人眼對“活人”的容忍度極低人類視覺系統(tǒng)識別人臉和膚色幾乎是“專用電路”。大腦會特別留意皮膚的血色、眼睛的濕潤度、頭發(fā)的高光形狀任何一項光學特征不對都會被潛意識標記為“不自然”。這就是為什么很多3D角色在技術(shù)指標上并不差——面數(shù)高、貼圖清晰但觀眾一眼就覺得恐怖谷。問題不在模型精度而在著色器反饋給視覺系統(tǒng)的信息錯了。舉個例子把一塊淺灰色PBR材質(zhì)球放在角色臉上法線和UV都接對了燈光也打得柔和結(jié)果看起來依然像橡皮泥。因為真實皮膚不是均勻漫反射體它內(nèi)部有散射、有毛細血管、有油脂層的微弱鏡面反射這些光學行為需要專門的著色模型才能模擬。1.2 數(shù)字人的渲染是一套著色器組合不是單個材質(zhì)寫實人物渲染從來不是“一個材質(zhì)搞定全身”。至少需要五類著色模型協(xié)同工作皮膚用次表面散射SSS模擬光在皮膚下的擴散和紅色透射這是“血色感”的來源。頭發(fā)每一根頭發(fā)都是半透明圓柱體需要各向異性雙高光普通PBR高光做出來是綢緞不是頭發(fā)。眼睛角膜是凸面透明體虹膜在它下面會發(fā)生視差偏移普通球體材質(zhì)會顯得像玻璃珠或死魚眼??谇谎例X半透明、牙齦充血紅潤、舌頭有濕潤反射一個SSS材質(zhì)包打不了全場。汗毛和膚色過渡帶人臉還有眉毛、睫毛、細汗毛和皮膚與嘴唇的過渡區(qū)都需要獨立控制粗糙度和高光強度。所以“人類著色器”嚴格說是“人體渲染?!薄T赨E4里這個棧由幾個不同的ShadingModel配合SubsurfaceProfile、Groom、自寫Custom Expression共同組成。1.3 為什么默認Lit總是差一口氣UE4的默認Lit假設(shè)表面是不透明的剛性物質(zhì)反射靠粗糙度加金屬度這套邏輯對地面、墻面、車漆是對的但人體幾乎每一種組織都在打破這個假設(shè)。皮膚有內(nèi)散射頭發(fā)表面有次表面透射眼球是折射透鏡口腔有黏膜體液默認Lit完全覆蓋不了。UE4給你提供了Subsurface Profile、Hair、Eye這些專門的ShadingModel但需要你自己組裝。最花時間的不是某個節(jié)點怎么寫而是把整條鏈路串起來之后逐一微調(diào)參數(shù)讓每個部位在同一個燈光環(huán)境下互相配合。這一章先講清楚概念下面逐一拆解鏈路里最重要的三塊皮膚、頭發(fā)、眼睛。2. 皮膚渲染次表面散射的工程化實現(xiàn)2.1 物理為什么皮膚必須是“透”的用手電筒照手指另一側(cè)會透出紅光。光不是只在皮膚表面反彈還會進入皮膚內(nèi)部被真皮層的膠原蛋白和毛細血管散射最后從鄰近位置射出。散射時藍綠光被血紅蛋白吸收得比紅光更厲害所以透射光偏紅。這個現(xiàn)象叫次表面散射。渲染上如果你把皮膚當成完全不透明的表面哪怕法線貼圖做到8K看起來還是像噴了漆的塑料。因為缺少了“光在皮下散步”產(chǎn)生的柔和過渡和血色感。這也是為什么我判斷一個角色像不像真人第一步就是把側(cè)逆光打開看臉部邊緣透不透紅。2.2 UE4里的SSS實現(xiàn)路徑SubsurfaceProfile加屏幕空間模糊UE4皮膚渲染的標準做法是材質(zhì)ShadingModel選擇Subsurface Profile。新建一個SubsurfaceProfile資產(chǎn)引用給材質(zhì)。材質(zhì)輸出SubsurfaceColor、Normal、Roughness等常規(guī)項。運行時UE4先按光照計算出一張帶SubsurfaceColor信息的圖然后在屏幕空間做兩個方向的高斯卷積先水平后垂直用ScatterRadius控制模糊半徑。這個模糊過程模擬的就是“光向鄰近像素擴散”。所以皮膚邊緣的紅色透光、臉頰的柔和泛紅、耳尖的反光都來自這一步。我實際調(diào)整的時候發(fā)現(xiàn)SSS效果在正面直射光下并不明顯但在側(cè)逆光、邊緣逆光、柔和天光下差異極大。給角色打燈做最終效果時一定要有背光通道否則SSS調(diào)了等于白調(diào)。2.3 SubsurfaceProfile關(guān)鍵參數(shù)與我的調(diào)參習慣下面這張表是我在項目里最常用的參數(shù)調(diào)整清單參數(shù)作用我的經(jīng)驗值ScatterRadius光的橫向擴散距離越大越像蠟?zāi)槻砍S?.5到2.0超過3會糊SubsurfaceColor內(nèi)部散射顏色決定血色臉頰偏紅鼻翼輕微額頭偏白BoundaryColorBlend邊界區(qū)域顏色混合強度耳朵和鼻尖高一點大概0.6Transmission背部透射強度手指、耳朵明顯臉部不放大Opacity透射顏色的深度控制別拉滿拉滿像半透明蠟像調(diào)參順序我一般是先確定ScatterRadius穩(wěn)定大局再用貼圖遮罩去加細節(jié)。臉頰做一塊偏紅潤的mask額頭和下巴稍微壓暗胸口的皮膚和臉部用同一個Profile但SubsurfaceColor分開控制。這樣做的好處是角色在暖光和冷光下看起來的“血色感”是一致的不會出現(xiàn)臉白脖子紅這種穿幫。2.4 皮膚調(diào)參的三個“一看就假”的坑第一個坑是慘白。可能是Albedo貼圖在線性空間和sRGB之間沒理清或者SubsurfaceColor給成了純白也可能ScatterRadius拉太大把整個臉都糊成了白蠟。我遇到一次排查很久最后發(fā)現(xiàn)是材質(zhì)實例里引用的SubsurfaceProfile丟失引擎靜默回退到了默認Lit整個SSS等于沒開。第二個坑是糊。SSS本質(zhì)上會抹掉毛孔和微結(jié)構(gòu)細節(jié)所以要做細節(jié)法線疊加。我的做法是角色臉上放兩層法線一層大尺度面部形狀法線一層高頻毛孔細節(jié)法線兩層的UV大概率不一樣用Detail Normal節(jié)點混合。糊臉問題一半靠這個解決。第三個坑是油膩。皮膚不是塑料更不是油面真實皮膚的高光是寬而弱的不應(yīng)該有清晰的鏡面光斑。把Roughness控制好皮膚眼周、鼻頭稍微低一點臉頰和額頭相對高別一刀切。3. 頭發(fā)從“方便面”到真頭發(fā)各向異性高光的秘密3.1 一根頭發(fā)是一根半透明圓柱頭發(fā)的光學行為不是“一根粗糙的細線”它是一根半透明圓柱體。光在表面直接反射形成第一道高光一部分光穿透表面進入發(fā)絲內(nèi)部從背面射出形成透射光還有一部分在內(nèi)部反射一次后重新射出形成第二道高光。第二道高光的位置比第一道靠下色調(diào)偏暖偏深。這就解釋了為什么真人頭發(fā)在逆光和側(cè)光下經(jīng)常能看到兩條光帶而許多廉價手游頭發(fā)只有一道白色高光像用梳子刷了油。3.2 UE4傳統(tǒng)管線與Groom Strand的選擇UE4傳統(tǒng)角色的頭發(fā)材質(zhì)ShadingModel里有一個Hair選項內(nèi)部實現(xiàn)是Kajiya-Kay風格提供兩個高光峰值和一個透射通道。你可以控制PrimarySpecular和SecondarySpecular的比例、強度、光澤的分布。這套方案在遠景和普通游戲鏡頭里夠用但近景特寫很容易穿幫因為發(fā)絲之間沒有真實幾何遮擋。UE4從4.26開始Hair Strands系統(tǒng)穩(wěn)定之后Groom才是寫實近景的正解。發(fā)絲是真正的曲線幾何材質(zhì)層使用更接近Marschner風格的雙高光散射模型并且有發(fā)絲間的遮擋與自陰影。項目里近景角色我基本不考慮傳統(tǒng)網(wǎng)格頭發(fā)加Hair材質(zhì)了。3.3 頭發(fā)實操中的四個關(guān)鍵點切線方向是頭發(fā)高光的靈魂。傳統(tǒng)網(wǎng)格頭發(fā)的高光延展方向靠切線而切線又與你拆UV的方向掛鉤。如果UV接縫沒處理好或者UV走向和發(fā)絲方向不一致高光會一段段斷開。Groom則要保證導入曲線時正確烘焙切線這一步最容易在DCC里漏掉。發(fā)根發(fā)尖要區(qū)分。現(xiàn)實中發(fā)根靠近頭皮處有皮脂和陰影看起來偏暗發(fā)梢更干更散粗糙度更高、反光更弱。這些可以用漸變貼圖或頂點色控制偷懶的話至少用一張alpha漸變把發(fā)尾的粗糙度提上去否則整頭頭發(fā)會像塑料簾子。發(fā)絲自陰影不能省。Groom如果關(guān)了高級陰影發(fā)絲之間會互相曝光整頭頭發(fā)像半透明薄紗。開啟深層陰影和發(fā)絲AO之后近景頭發(fā)才有一綹一綹的體積感。性能預(yù)算要在做項目時就定死。幾百萬根發(fā)絲配合實時陰影在高分辨率下可以吃掉3到6毫秒。我的做法是同一套角色給三套Groom LOD近景高密度、中景中密度、遠景直接切卡片頭發(fā)加透明貼圖寧可切得明顯也不要卡死幀率。4. 眼球與面部微細節(jié)決定“像不像真人”的最后1%4.1 眼球是一個多層光學系統(tǒng)眼球最外是凸面透明的角膜中間是虹膜里面是晶狀體。我們看到虹膜時光實際上穿過了角膜然后被虹膜反射回來再穿過角膜進入鏡頭。角膜本身是凸透鏡所以虹膜的位置會隨著視角變化發(fā)生視差偏移。角膜表面還會反射環(huán)境光和光源形成我們熟悉的“眼睛里的光點”。如果只是把眼球當普通球體材質(zhì)處理做出來的東西叫“死魚眼”因為光澤位置不對、虹膜看起來是貼在表面的貼紙而不是浮在角膜之下的結(jié)構(gòu)。4.2 UE4里的兩種眼球方案UE4自帶Eye ShadingModel提供了角膜處理相關(guān)的輸入通道能控制虹膜遮罩和角膜高光。項目里我用得更多還是自己用Custom Expression加視差偏移的做法因為可以精細控制每一條UV流眼球模型拆成眼白和虹膜兩層網(wǎng)格角膜用半透明或低糙度覆蓋層模擬凸面。虹膜UV以瞳孔為中心做視差偏移偏移方向與視線方向掛鉤模擬水下的折射錯位。角膜層單獨采樣法線貼圖和反射捕獲制造干凈銳利的“玻璃凸面”高光。瞳孔直徑做成參數(shù)方便后續(xù)被外部設(shè)備數(shù)據(jù)驅(qū)動。這樣一套下來眼睛在搖頭時虹膜會微微“漂移”高光會跟著環(huán)境變化而不是死死釘在眼球上。4.3 面部微細節(jié)毛孔、皺紋、牙齒、牙齦面部只有SSS還不夠。真人臉的質(zhì)感很大程度來自高頻毛孔細節(jié)和肌肉運動產(chǎn)生的皺紋。我的做法是兩張法線貼圖疊乘一張是面部整體形態(tài)的法線另一張是高頻毛孔細節(jié)UV不同也沒關(guān)系用DetailNormal節(jié)點處理。近景特寫還要加皺紋法線集中在額紋、眼周紋、法令紋附近表情驅(qū)動時過渡要柔和不能出現(xiàn)切開的“刀口”。牙齒和牙齦也不能隨手給一個白球。牙齒本身半透明高光尖銳偏冷牙齦充血紅潤吸光度強粗糙度比牙齒高得多??谇粌?nèi)部的AO尤其重要不然角色一張嘴里頭就是一片慘白。5. 讓數(shù)字人“活”起來外接設(shè)備映射與動態(tài)參數(shù)驅(qū)動5.1 靜態(tài)著色器只是底子外部數(shù)據(jù)才是靈魂寫實角色只靠靜態(tài)著色器即使材質(zhì)全對看起來也像一尊半透明蠟像。活人每一幀都在微動呼吸的起伏、表情肌肉的抽動、眼球的掃視、臉頰因為情緒涌上的潮紅。想要打破恐怖谷“數(shù)據(jù)驅(qū)動”比“材質(zhì)精度”更關(guān)鍵。外接設(shè)備映射就是干這個的把真實人的表情、姿態(tài)、視線、甚至心率/呼吸數(shù)據(jù)實時喂進UE4讓骨骼網(wǎng)格體和材質(zhì)參數(shù)跟著動。5.2 從設(shè)備到UE4的數(shù)據(jù)鏈路我項目里用的標準鏈路是外部設(shè)備如iPhone的ARKit面捕、Xsens/Vicon動捕、眼動儀→ 中間件LiveLink Face、第三方動捕插件→ UE4的LiveLink → 動畫藍圖 → Morphtarget權(quán)重或骨骼變換 → 材質(zhì)參數(shù)更新 → 著色器實時反饋。面捕設(shè)備輸出的一般是52個BlendShape系數(shù)這些系數(shù)可以直接驅(qū)動角色的Morphtarget權(quán)重。動捕設(shè)備輸出骨骼旋轉(zhuǎn)數(shù)據(jù)經(jīng)過重定向后驅(qū)動角色的骨骼。眼動儀輸出視線方向換算成眼球模型的旋轉(zhuǎn)角度和瞳孔縮放參數(shù)。這條鏈路只是第一步。真正讓我覺得角色“活過來”的是把這些數(shù)據(jù)進一步映射到著色器參數(shù)上。5.3 把外部數(shù)據(jù)映射到Shader參數(shù)幾個可以直接抄的套路我用得最多的幾個映射效果情緒臉紅。把面捕的smile、browDown等BlendShape系數(shù)加權(quán)成一個“激動值”用這個值去驅(qū)動皮膚材質(zhì)里SubsurfaceColor的紅色通道。角色說話說嗨了臉頰自然會有充血的視覺效果。這個效果比直接調(diào)Diffuse紅潤感要真實得多因為SSS的紅色會像皮膚下面透出來一樣。瞳孔縮放。把眼動儀獲取的注視距離或者環(huán)境亮度數(shù)據(jù)映射到眼睛材質(zhì)里控制虹膜UV縮放的參數(shù)。談話對象離得近瞳孔縮小燈光變暗瞳孔放大。這是一個很小的細節(jié)但增加眼球的空間深度感特別明顯。呼吸驅(qū)動皮膚微動。用動捕胸腔的Y軸位移或心率數(shù)據(jù)驅(qū)動一個低頻Panner噪音讓皮膚細節(jié)法線的強度發(fā)生周期性微變。鏡頭懟臉時觀眾潛意識能感覺到皮膚下面的微循環(huán)在動。眨眼聯(lián)動淚膜高光。面捕的blink系數(shù)控制眼瞼閉合時同時抬起眼睛材質(zhì)里“淚膜濕潤高光”的強度。因為真實眨眼瞬間淚膜會重新鋪展產(chǎn)生短促的高光反射。這種聯(lián)動讓眼睛的高光變化有了生理邏輯不再是干巴巴的鏡面反射。5.4 外接設(shè)備映射落地的幾個大坑數(shù)據(jù)要平滑不能直接用原始值。BlendShape系數(shù)自帶高頻噪聲直接驅(qū)動形變會抖得像抽搐。我一般對所有面部曲線做一階低通濾波或者限制每幀最大變化速率讓情緒表達更接近真實肌肉狀態(tài)。ARKit的BlendShape是相對系數(shù)范圍是0到1但不同表情對同一個系數(shù)的敏感度完全不一樣。比如JawOpen系數(shù)0.5不等于角色嘴巴真的張開了50%必須針對每個角色的形變范圍做單獨校準。多設(shè)備接入時先統(tǒng)一時間基準。動捕設(shè)備、面捕設(shè)備、眼動儀的數(shù)據(jù)如果時間戳不對齊角色會出現(xiàn)身體和臉錯位的“魂體分離”。我習慣在系統(tǒng)里做一個統(tǒng)一的數(shù)據(jù)時鐘所有設(shè)備數(shù)據(jù)進來先打上時間戳再分發(fā)。調(diào)試時設(shè)備不可能一直在線所以一定要做一個回放通道。把設(shè)備數(shù)據(jù)錄制成曲線數(shù)據(jù)調(diào)試時用鍵盤或UI模擬回放否則每次調(diào)材質(zhì)參數(shù)都要把動捕演員叫回來效率極低。6. 性能預(yù)算與調(diào)優(yōu)一張RTX卡上的取舍6.1 開銷從哪里來數(shù)字人的性能開銷分布和普通場景完全不同。我做過幾次GPU Profile大致分布是模塊典型開銷說明皮膚SSS后處理0.5到1.5毫秒分辨率越高越貴可半分辨率計算Groom發(fā)絲渲染和陰影1到6毫秒發(fā)絲數(shù)量和陰影質(zhì)量影響最大眼球復合層0.1到0.3毫秒視差偏移和反射采樣不貴但要做多層動態(tài)光源和陰影2到5毫秒甚至更高實時陰影是全鏈路里最重的部分這些數(shù)字會隨硬件和場景變化但比例關(guān)系基本穩(wěn)定頭發(fā)和實時陰影是大頭皮膚SSS是固定稅眼睛反而廉價。6.2 我調(diào)過的幾項優(yōu)化SSS后處理可以降半分辨率。UE4提供了控制臺變量用于調(diào)整SSS的質(zhì)量和采樣規(guī)模遠景角色可以調(diào)低近景特寫再拉回來。這樣既省了固定消耗又不影響角色出鏡時的畫質(zhì)。Groom的LOD必須提前做好。我的經(jīng)驗是近景Groom給足發(fā)絲量中景切換到一個減少一半發(fā)絲的版本遠景直接切到帶透明貼圖的卡片頭發(fā)。UE4里同一套Groom可以配多個LOD曲線切換時機盡量放在旋轉(zhuǎn)鏡頭看不到的瞬間避免穿幫。皮膚AO能用貼圖烘焙就不實時跑。全屏SSAO和Groom自陰影同時開幀時間直接起飛。我的做法是角色自身的AO烘焙進貼圖場景級別的AO用SSAO開低檔Groom陰影只給最靠前的發(fā)絲層計算。眼球反射用靜態(tài)捕獲不要反射實時平面。反射捕獲加Cubemap在絕大多數(shù)鏡頭里夠用還能避免兩個角色互相映出對方那種詭異效果。6.3 用貼圖“騙”回來的性能有些效果完全可以作弊而不損失觀感。耳尖和手指的邊緣透紅光只在角色特寫時開實時Transmission中遠景用貼圖里的透射色假抬亮度。角膜的折射效果不用真的屏幕空間折射用UV視差貼圖就能騙過眼睛除非有專門的角膜特寫鏡頭。做數(shù)字人項目的優(yōu)化邏輯是把最貴的實時計算留給觀眾最注意的部位。臉部特寫時花預(yù)算在SSS和Groom上遠景中景靠貼圖和LOD撐住。用控制臺變量配合LOD在運行時動態(tài)調(diào)整這些閾值能兼顧效率和畫質(zhì)。7. 留檔的踩坑記錄排查鏈路三則7.1 皮膚一片慘白像死尸現(xiàn)象是角色在正常燈光下臉白得發(fā)光看不到血色連嘴唇都像涂了白蠟。排查過程我先從最簡單的驗起檢查Albedo貼圖在材質(zhì)里被當成sRGB還是線性色如果連色帶都錯了后面全白搭。然后確認材質(zhì)ShadingModel是不是SubsurfaceProfile這一步經(jīng)常被美術(shù)同事誤改成Default Lit。再查材質(zhì)實例里的SubsurfaceProfile資產(chǎn)是否有效我之前就遇到過資產(chǎn)引用丟失但材質(zhì)不報錯的情況。最后查ScatterRadius如果被拉到5以上整張臉自然糊成白板。解決方式就是把ShadingModel切回SubsurfaceProfile、重新指定Profile、ScatterRadius降到1.5左右再配合SubsurfaceColor貼圖把臉頰的血色提回來。7.2 頭發(fā)近景高光斷裂、發(fā)絲間一片黑現(xiàn)象是角色頭發(fā)正面看問題不大稍微轉(zhuǎn)到側(cè)面高光一條一條斷開發(fā)絲縫隙里全是黑塊和噪點。排查鏈路比較長。先排除發(fā)絲數(shù)量不夠?qū)е碌那凡蓸影寻l(fā)絲密度調(diào)高發(fā)現(xiàn)還是有。再檢查材質(zhì)里的Tangent是否完整尤其是Groom引導線從DCC導出時有沒有把切線烘焙進資產(chǎn)。我最后在材質(zhì)預(yù)覽里把Normal和Tangent可視化發(fā)現(xiàn)側(cè)面鏡頭的發(fā)絲片段方向瘋狂跳動。問題根源是導出Groom時沒有烘焙切線引擎在運行時計算出的發(fā)絲切線不穩(wěn)定。修復方法是回到DCC里重新烘焙引導線切線再導出進UE4后高光立刻穩(wěn)定了。7.3 眼睛像玻璃球沒有焦點現(xiàn)象是眼球反光很亮但看起來像假眼珠虹膜完全沒有了“盯人”的感覺。我先看角膜層和虹膜層的混合順序發(fā)現(xiàn)角膜反射Cubemap的貢獻值壓過了虹膜導致眼球表面像鏡子。再檢查虹膜UV的視差偏移方向發(fā)現(xiàn)偏移取反了虹膜在鏡頭左右移動時反著漂。還檢查了瞳孔縮放參數(shù)被外部設(shè)備的初始值頂?shù)搅?瞳孔變成針孔完全沒了自然光下的狀態(tài)。修復方式是角膜反射的強度限制在0.3以內(nèi)虹膜視差偏移方向調(diào)回來瞳孔給一個基于環(huán)境亮度變化的默認映射曲線。改完再開側(cè)逆光測試眼球終于有了“從角膜下面透出來的虹膜”的分層感。這三個坑排查下來我有個很深的體會大多數(shù)“假人”問題不是某一個節(jié)點參數(shù)沒調(diào)對而是渲染鏈路里的某一環(huán)被替換成了通用缺省方案。數(shù)字人著色沒有一個萬能按鈕它是一套可組合的著色器棧加外部設(shè)備數(shù)據(jù)流。如果你也在做類似的角色強烈建議做一個專門的調(diào)試關(guān)卡把皮膚、頭發(fā)、眼睛、口腔拆成獨立可開關(guān)的層誰出問題就關(guān)掉其它層單獨看。這比對著材質(zhì)節(jié)點一個個猜快得多也是我最近項目效率提升最明顯的一步。