簡談實時皮膚渲染之預積分SSS

前言

縱然前文提及的Disney BRDF可以用來描述(渲染)世間絕大多數不透明幾何物體,但是一套BRDF也絕不是靈丹妙藥精刷,解決不了所有渲染上的需求。比如角色渲染中非常重要的皮膚處理疯搅,人們一般不會自縛手腳將通用的PBR渲染方程套用在這類“特殊”材質上(需求和判斷標準與傳統BRDF渲染結果有較大差異)崎场。本文簡述了一種主流的基于預積分次表面散射(SSS)的渲染方案,介紹中會提及SSS效果的成因砂竖,以及如何利用數學模型對這種物理現象進行描述真椿,另外還結合《GPU Pro 2》中的“Pre-Integrated Skin Shading”一文簡要介紹了對次表面散射的預積分模型;同時又結合了《GPU Gen 3》中相關內容乎澄,對一種皮膚高光處理的預積分模型也進行了梳理突硝;最后我們在Unity URP管線下展示了基于預積分技術對面部皮膚進行實時渲染的效果。

什么是SSS

SSS既Subsurface Scattering置济,次表面散射解恰,其實在前文介紹有關PBR的一些重要概念時已經概略的提及過,是一種在微觀尺度上對物體表面漫反射光的解構舟肉。我們知道當一束光線照射到材質表面修噪,一部分光(光能)被立即鏡面反射回了空氣中,另一部分進入物體內部路媚,要么被吸收,要么再次發(fā)生折射或反射樊销。對于不太透明的電介質來說整慎,比如人的皮膚脏款,這些折射光不太可能直接從另一側透射出人體,更可能的是裤园,在某一層介質中經過了多次光路改變撤师,最后出射光以一個相對隨機的角度再次回到入射光一側的空氣中,成為漫反射拧揽。這里有2處需要額外說明的地方:

  • 其一是隨機性剃盾,由于材質內部介質通常是不連續(xù)的,如皮膚有油脂層淤袜,表皮層,真皮層铡羡,以及大量毛細血管和里面的血液構成,光路在內部的折射/反射往往是無規(guī)律可循的尽爆,導致最終出射時方向的隨機化漱贱;
  • 其二是電介質的著色性夭委,既漫反射光會被染上顏色,這是因為光線在物體內部傳播時闰靴,一部分波長的光能被吸收所致(通常是金屬成分中的自由電子)彪笼,比較典型的是人體皮膚中血液的鐵離子吸收了藍綠波長的光譜,從而形成表皮翻紅的染色效果蚂且。

借用一下網上的示例圖:


sss_local

漫反射來自于這些重新回到空氣中的折射光配猫。當觀察尺度足夠小,我們便不能近似的認為這些漫反射光的出射點恰好位于入射光所在的入射點處杏死,而是如上圖所示泵肄,出射光被認為分布在入射光周圍一定范圍內(綠色圓圈)。在圖像渲染領域淑翼,因為顏色的離散化(既光路以及顏色相關的值最終都會通過積分腐巢,以離散化數值的形式記錄到像素點上),所以如果上圖中綠色圓圈的區(qū)域恰好落在某個像素內玄括,那么反映的宏觀表現層面冯丙,我們看到的依然是普通漫反射效果。

那么在一般觀察距離上遭京,會不會有“綠圈”兜不住一個像素的情況呢胃惜?其實挺常見的泞莉,舉個例子,我們知道人的皮膚一般可以分為表面油脂層船殉,上皮層和下皮層(真皮層)鲫趁,當光線穿透油脂和上皮層,遇到人體皮膚中富含血管和組織液的真皮部分利虫,光線很可能會沿著局部的均質(血液挨厚,組織液)做較長范圍的折射遷移疫剃。

layer of skin

如果一速光線能夠沿著路徑在入射介質中傳播足夠遠時,我們就正式進入了次表面散射的世界蹄溉。

sss_nonlocal

如上圖所示,這種情況下棉胀,我們必須考慮因長距離傳播再出射而導致的著色差異唁奢。我們不再將計算局限于當前像素(圖中綠圈)所在的范圍,此時材質上(入射光點附近區(qū)域內)其他各個方向的影響分量都需要納入考量脊奋,并反映到出射點光亮度的計算過程中。

關于BSSDF

在了解了SSS效果成因后久又,我們也許會好奇這么復雜的現象烈评,如何用簡練的數學語言描述清楚,進而指導我們構建渲染方程呢适瓦?類似于 BRDF,人們專門為SSS引入了新的方程嗦随,全稱是 Bidirectional Surface Scattering Distribution Function(雙向次表面散射分布方程),也可以簡稱為 BSSDF署恍,該方程最早源自2001年由Jensen發(fā)布的論文,其核心概念可以簡單總結為如下公式:

BSSRDF

等式左側的積分輸出是出射位置和出射角度下的光亮度Lo呼巷。為了得到Lo,我們先從最右側積分看起:

在給定某一個面積微元A的前提下配名,對半球空域Ω所有入射光的光亮度積分,這種積分需要考慮光線對表面投影造成的衰減(i dot ni)芋膘,當第一輪積分做完臂拓,我們得到的是單位面元(像素)的輝度值(irradiance)。

之后將積分所得輝度帶入到外側積分中理解:我們很容易可以看出孵滞,這部分是對面積A進行積分坊饶,被積函數是R與半球空域輝度值的乘積。仔細觀察法線痘绎,函數R的入參是|xi - xo|,代表了從入射點到出射點之間距離散庶。多提一嘴,積分函數R的本質是一個模糊函數(Blur Kernel)须教,用于近似我們后文會提及的散射方程Scattering(xi, i; xo, o)轻腺。為啥?當然是因為完整的散射方程十分且非常且尤為復雜误算,它既要考慮入射和出射的位置咖杂,還要考慮入射和出射的角度诉字,是個高緯方程导披。而我們的R函數之所以能夠成立撩匕,是因為我們假設了物體表面是各向同性的(isotropic)止毕,如此一來忍疾,我們可以認為散射方程只受到光線在材質內傳播的距離|xi - xo|影響卤妒,既 Scattering(|xi - xo|),而和具體入射和出射的方位無關士复。R的選擇多種多樣,可以是Gaussian kernel冗荸,smoothstep俏竞,cubic function或者是Disney的normalized diffusion玻佩,但最好保證能量的守恒,既對整個A區(qū)域積分這些核的結構應該等于1烦秩,同時積分結果也要符合人眼對于目標材質的預期兜蠕。

次表面散射中的預積分皮膚概念

如BSSDF方程所示,如果要考慮SSS效果晶府,并得到某一個點處沿著某個方向發(fā)射的光強度,那么就得老老實實對整個區(qū)域的每個點積分较沪,這顯然非常的“昂貴”购对。因此自然有人開始嘗試預積分技術,將BSSDF的部分或整體預先計算出來解幽,并以一定方式保存進紋理。我們來參考下來自《GPU Pro 2》中由Eric Penner和George Borshukov提出的預積分技術的部分相關內容:

The obvious caveat of pre-intergation is that in order to pre-integrate a function, we need to know that it won't change in the future. Since the incident light on skin can conceivably be almost arbitrary, it seems as though precomputing this effect will prove difficult, especially for changing surfaces. However, by focusing only on skin rather than arbitrary materials, and choosing specifically where and what to pre-integrate, we found what we believe is a happy medium. In total, we pre-integrate the effect of scattering in three special steps:
* on the lighting model
* on small surface details
* on occluded light(shadows)
By applying all of these in tandem, we achieve similar results to texture-space diffusion approaches in a completely local pixel shader, with few additional constraints.
To understand the reasoning behind our approach, it first helps to picture a completely flat piece of skin under uniform directional light. In this particular case, no visible scattering will occur because the incident light is the same everywhere. The only three things that introduce visible scattering are
* changes in the surrounding mesh curvature,
* bumps in the normal map,
* and occluded light(shadow).
We deal with each of these phenomena seperately.

上述文章的片段概述了預積分技術在次表面散射方面的局限霜定,為了確保預計算的東西不被模型和表面參數所束縛辖所,就必須限定積分對象的材質(這里自然是指皮膚),并且僅預計算那些不變的組成部分酥宴。

至于SSS的形成條件及處理步驟,這邊稍作整理用中文再輸出一邊重點:

對宏觀上平坦的表面倒庵,由于平行光均勻分布于各個點,我們是無法察覺到次表面散射現象的,要達成這一現象必須滿足3個要素顾彰,分別是有弧度變化的模型,有法線的擾動以及存在光線遮擋厕隧;而預積分也分為3個步驟,分別是處理光照,處理微表面以及處理遮蔽波势。對于3個步驟拴曲,我個人的理解是:

  • 處理光照 -> 確定Li 入射光函數
  • 處理微表面 -> 確定散射分布函數 S 或者 R
  • 處理遮蔽 -> 計算光照的投影 N dot L

文章片段還提及了形成SSS效果的3條重要因素:

  • 模型弧度變化 -> 統一了模型的尺寸和形狀2個維度,對于人體而言能用一個標量很好的區(qū)分諸如臉頰蕉汪、耳鼻等不同部位的SSS特征福澡。
  • 法線擾動 -> 表面法線完全沒有擾動的話,那么微表面區(qū)域完全鏡面和平坦算利,SSS失去了形成細節(jié)特征的條件;如果表面法線擾動過于銳利缎患,那么看起來效果會不好(像非散射的邊緣效果)风题。
  • 光線的遮擋 -> 就是說必須包含陰影俯邓,從而形成局部由暗至明的轉向骡楼,避免全方位的亮光造成SSS效果無法被察覺。

如何理解3要素呢稽鞭?我相信很多人可能和我一樣鸟整,對“模型需要有弧度的變化”印象最為深刻,畢竟它將微觀上的SSS現象同物體的宏觀形狀關聯了起來朦蕴。這乍聽起來有點不可思議篮条,然而深究其原因涉茧,連同另外2個要素一起思考很容易發(fā)現,它們表示的都是一個意思,既:在物體表面要有明顯的光強度變化舰涌,要能夠形成明和暗部乾闰。

sss effect

一種預積分SSS技術

講解了半天各種前因后果硫朦,也許你會好奇到底SSS效果是如何通過預積分方式在實時渲染過程中體現的呢祷舀?別急铺纽,這節(jié)我就和大家一起來梳理下《GPU Pro 2》提出的預積分技術原理和實現。首先大家需要注意所謂預積分積分的是皮膚表面漫反射分量,是基于BSSDF進行的呜笑,這點和BRDF著重于Specular是不同的(皮膚高光我們后面會談到)。此外不得不感謝網絡上的幽玄大神對公式的細心推理,本節(jié)大部分數學推導是基于他的工作進行的碉咆。

我們先從原文作者給出的對SSS這一物理現象的數學模型示意圖開始吧:

SSS Distribution

我先簡要歸納下圖中要點竟稳。

  • 左圖表示BSSDF中的散射函數R,入參x應當是一個角度標量混聊,可以通過關聯半徑r轉換成從入射點到出射點的距離(Distance)冻记,而返回值(既縱坐標)是對應距離的貢獻強度摄悯。
  • 右圖中的圓形代表抽象的幾何模型廉涕,你可以把這圓想象成角色頭模。
  • 右圖中由法線N與入射光線L所構成的夾角定義為θ涤伐。
  • 右圖中圓形的半徑為r眷射。
  • 紅綠藍色的包絡線代表不同R函數(但是能量守恒)冗茸,是以當前點(法線N與圓的交點)為中心挂绰,對周圍各點計算光亮度貢獻所得的曲線。
  • 右圖中N+θ表示從法線N開始逆時針旋轉θ角對于的位置。

下面是作者Eric給出的積分公式暂氯,我這邊采用幽玄大神重解讀后的改版,與原本相比亮蛔,積分區(qū)間縮小到了半球空域痴施,同時顯式得引入了模型半徑r。注意原始公式是為了描述關于距離的一維函數(上圖左側Profile函數)究流,所以r被默認設置成了1,我們最終的積分函數是需要包含rdistance(或者角度x)這2個控制維度的芬探。

SSS Distribution Formula

因為式子中已經提出了作為常量的入射光強 Li,為了便于理解灯节,我們姑且認為其值為1。如下圖所示炎疆,對于圓形上任一點Q卡骂,假設圓心OQ構成的向量QO與法線N的夾角為xx可以在-π/2+π/2之間取值)形入。那么該點的入射光強度可以簡單得表示為:Lq = Li* (OQ dot OL) = 1 * cos(x + θ)

SSS Distribution 2

設法線N與圓的交點為P,這是我們的積分目標點亿遂,所有圍繞在點P周圍半球空域內的表面積都需要被積分公式舔一遍浓若,加總它們對點P的貢獻度。Q點我們抽象出來的無數被積點之一蛇数,假設QP的散射率是q(x),前面說過碌上,xOQ與法線N的夾角,它隨Q的移動而變化馏予。采用微分思想盔性,我們視點Q所在的表面弧長為△x,那么這段小區(qū)間所切走的概率密度蛋糕應當是 q(x)△x冕香,然后乘以Q點接受到的光亮度后豫,我們可以得到該處△x區(qū)域提供給P點的散射分量(既有多少光線從Q點所在的△x區(qū)域折射進物體內部箩言,然后傳播到了P點并出射)硬贯,公式如下:

scattering contribution

我們對每一個符合要求的點Q做同樣的事情:

(1)

前面我們引入了概率密度函數q(x),它本質上就是圖(SSS Distribution)中的散射分布函數R鸵赖,只不過這里的入參使用的是角度x。現在我們希望使用切線距離(distance)作為函數的入參它褪,從xd的轉換公式如下,應用了一點三角函數和中學幾何知識居触,這里不再贅述。

x to distance

我們還知道散射分布函數 R需要保證能量守恒轮洋,也就是如下積分結果:

(2)

理解所謂的能量守恒需要一些技巧抬旺,我們并不是說來自所有半球面積上的微元分量△x加總的貢獻度等于100%的當前點P的入射能量,這是不合理也不可能的开财,上式的物理意義在于說明汉柒,對于所有符合條件的當前點P,計算他們周邊區(qū)域的散射貢獻度總和應當是一個確定的常數责鳍,比如此處的常數1碾褂。更深一層的含義是:只要給定某種材質,那么光能在材質內沿著特定方向傳播的概率和損耗與觀察位置無關历葛,只與傳播的距離有關正塌。

其實q(x)長什么樣并不重要,重要的是

  1. 分布方程需要讓最終結果看起來還不錯啃洋。
  2. 分布方程需要滿足“能量守恒”传货。

假設我們找到了一個滿足條件公式(1)的分布函數R(d),為了使之也滿足條件公式(2)宏娄,我們不妨令q(x) = k*R(d),其中k是歸一化因子孵坚,確保分布R在半球積分后能量守恒窥淆。
于是我們可以很容易帶入積分公式巍杈,求取k的表達式,以及qR直接的關系:

R to q

q(x)以R形式的表達式導入到公式(1)式中词裤,這樣同時考慮到了各點的光亮度:

Distruibution of theta

上式最后就是《GPU Pro 2》相關文章中提出的公式原型鳖宾,注意這個公式中,只有θr是變量渔肩。

將上述公式轉化為代碼拇惋,可以求取不同曲率和角度下的散射分布,并將結果記錄到一張完整的紋理上:

private void PreIntegrateSSSLUT(Texture texture)
{
    for (int j = 0; j < texture.height; ++j)
    {
        for (int i = 0; i < texture.width; ++i)
        {
            float NDotL = Mathf.Lerp(-1f, 1f, i / (float)texture.width); 
            float oneOverR = 2.0f * 1f / ((j + 1) / (float)texture.height);  
            //Integrate Diffuse Scattering
            Vector3 diff = Integrate(NDotL, oneOverR);
            texture.SetPixel(i, j, new Color(diff.x, diff.y, diff.z, 1));
        }
    }   
}

private Vector3 Integrate(float cosTheta, float skinRadius)
{
    float theta = Mathf.Acos(cosTheta);  // theta -> the angle from lighting direction
    Vector3 totalWeights = Vector3.zero;
    Vector3 totalLight = Vector3.zero;

    float a = -(Mathf.PI / 2.0f);

    const float inc = 0.05f;

    while (a <= (Mathf.PI / 2.0f))
    {
        float sampleAngle = theta + a;
        float diffuse = Mathf.Clamp01(Mathf.Cos(sampleAngle));

        // calc distance
        float sampleDist = Mathf.Abs(2.0f * skinRadius * Mathf.Sin(a * 0.5f));

        // estimated by Gaussian pdf
        Vector3 weights = Gaussian(sampleDist);

        totalWeights += weights;
        totalLight += diffuse * weights;
        a += inc;
    }

    Vector3 result = new Vector3(totalLight.x / totalWeights.x, totalLight.y / totalWeights.y, totalLight.z / totalWeights.z);
    return result;
}

如何使用預積分LUT

我們先看看積分結果被保存到2D紋理后的樣子蓉坎,如下圖:

pre-integrated lut

橫坐標是NdotL磷仰,對應上節(jié)最終公式里的cosθ;縱坐標是1/r伺通,對應公式中的模型半徑r的倒數逢享,也可以理解為物體當前點附近的曲率。我們知道uv坐標的定義域是[0,1]弓柱,而NdotL1/rr>=1)恰好是符合[0,1]區(qū)間的定義侧但,這使得將預積分數據壓縮在一張2D紋理中成為可能。

那么如何在運行時采樣取值呢禀横?需要處理好如下2個采樣維度:

  • N dot L
    這部分相對簡單,對于主平行光酿箭,L應當能夠直接從引擎中獲取到;法線N建議使用法線貼圖獲取缭嫡,一方面是為了更好的精度和細節(jié)表現,另一方面是我們可以視情況對法線貼圖進行預處理(濾波器模糊或者使用mipmap技術)耕突,之所以要模糊法線貼圖评架,理由原作者的解釋是“避免看起來過于僵硬的邊界出現”,我想為了更好的藝術效果古程,大家還是盡量讓法線的分布盡量均勻連續(xù)喊崖,避免過于陡峭的“邊界”出現。

  • 1/r
    其本質是半徑為r的圓上任意一點的曲率荤懂,我們已知1/r定義在[0,1]區(qū)間,這就意味著r的取值范圍被規(guī)定在[1, ∞]區(qū)間上晤锥,當r遞減至最小單位1時廊宪,1/r趨向于1,從pre-integrated LUT圖來看箭启,散射能量在各個光照角度都相對提高了,且在可以類比下人臉上的鼻子和耳廓等處在光線照射下陰影附近透紅的效果放妈;另一方面當r非常大時荐操,1/r趨向0,LUT采樣的結果更加接近一般光照下的明暗交替托启,無明顯SSS效果。

那么我們如何在運行時獲取到1/r呢唉堪?考慮到采樣LUT圖時的運行上下文環(huán)境,可以得出一個很簡單的方法:嘗試找到局部曲率链方,然后把曲率映射成為球體半徑r灶搜,最后用1/r采樣LUT。于是重點就變成了如何尋找這個局部曲率割卖,特別是如何在像素著色器中找到它:

scattering param 1/r

Eric 給出了一個很巧妙的方法:先求取當前像素點對應法線的局部4領域差值△N(相當于求導數),然后比上模型頂點坐標相對于其周圍4領域像素的差值△p(也相當于求導數)罢维,根據如上圖左側所示的等比三角形法則丙挽,所得結果就是 1/r 的近似。
計算代碼如下:

float cuv = saturate(_CurveFactor * (length(fwidth(worldNormal)) / length(fwidth(worldPos))));

其中

fwidth(v) = abs(ddx(v))+ abs(ddy(v))

特別得:

ddx(v) = 該像素點右邊的v值 - 該像素點的v值
ddy(v) = 該像素點下面的v值 - 該像素點的v值
fwidth(Position) -> 該像素與相鄰兩個像素的坐標位置的差值
fwidth(Normal)  ->  該像素與相鄰兩個像素的法線的差值

而_CurveFactor用于調整比值的強度平窘,用于影響最終效果凳怨。

實時渲染的片元著色器代碼

float curve = saturate(_CurveFactor * (length(fwidth(worldNormal)) / length(fwidth(worldPos))));
fixed NDotL = dot(blurNormalDirection, lightDirection);
fixed4 sssColor = tex2D(_SSSLUTTex, float2(NDotL * 0.5 + 0.5, curve)) * _LightColor0;

當然我們也可以走捷徑,預先將模型表面各點的曲率烘焙到uv貼圖上肤舞,然后運行時直接采樣獲得1/r的計算結果。

計算皮膚的高光

人們經過測量發(fā)現只有很少一部分的光在接觸到皮膚表面后會被鏡面反射弊琴,這種反射光具有若干性質杖爽,可以歸納如下:

  • 反射光不帶有任何顏色信息,這是電介質作為反射層的固有特性腋寨;
  • 反射光中的大部分來自于皮膚最表面那很薄的一層油脂層(Thin Oily Layer)
  • 反射光遵循菲尼爾效應化焕,掠射角處反射量增大

我們還知道人類皮膚的反射光不可能構成干凈又存粹的鏡面像(吳克的頭?),這是由于皮膚的微表面其實并不平整键兜,所有反射光線會分布到一個范圍內穗泵,很顯然要描述好皮膚的Specular必須要引入BRDF(bidirectional reflectance distribution function雙向反射分布函數),那么如何選擇合適的BRDF呢佃延?本文的后續(xù)內容將依據《GPU Gem 3》第14.3節(jié)內容,簡單梳理一下基于Kelemen/Szirmay-Kalos BRDF模型的皮膚高光渲染方案仔沿。

首先我們回顧下PBR高光模型的一般實現形式尺棋,它由直接入射光強度(可能被遮蔽影響),BRDF項膘螟,還有一個余弦函數( 控制入射光能的衰減強度)組成,具體形式參考如下代碼:

specularLight += lightColor[i] * lightShadow[i] * rho_s * specBRDF( N, V, L[i], eta, m) * saturate( dot( N, L[i] ) );

上述代碼中的rho_s是一個引入的“非物理”因子用于控制整體強度,而specBRDF一般是由菲尼爾項F脊阴,幾何遮蔽項G蚯瞧,還有法線分布項D構成的,需要用到的入參有:法線方向N埋合, 視方向V,光方向L蜜猾,菲尼爾系數F0振诬,以及反應粗糙度的m項。

首先是菲尼爾項F赶么,和Disney BRDF一樣《GPU Gem 3》也采用了Schlick菲尼爾近似,它有如下代碼形式清钥,簡單理解就是當視方向V越接近掠射角(與微表面法線H垂直)所形成的反射光照強度越高。

float fresnelReflectance( float3 H, float3 V, float F0 ) 
{   
    float base = 1.0 - dot( V, H );   
    float exponential = pow( base, 5.0 );   
    return exponential + F0 * ( 1.0 - exponential ); 
}

另外代碼中的F0表示當皮膚遇到垂直入射光照時的反射率缕坎,《GPU Gem 3》中使用的是測量常數0.028从橘。

下面是G項和D項,結合論文 《A Microface Based Coupled Specular-Matte BRDF Model with Importance Sampling》所提出的一種簡化版 Cook-Torrance模型BRDF:

Cook-Torrance BRDF

公式中的 P 項代表微表面法線分布的概率密度恰力,也就是傳統意義上的D項。而右側分子上的F就是菲尼爾項停局,余下的部分是簡化后的G項香府,沒錯,整個Cook-Torrance模型中的G項連同 1/(4(NdotL)(NdotV))因子被合并簡化成了 1/(hdoth)企孩,而h代表的是(V+L)勿璃,既半角向量未被歸一化前的樣子。

我們現在已經明確G項和F項的公式补疑,這部分計算可以放在PS階段處理,一來是因為計算消耗不大诊胞,而來也是因為涉及多項入參锹杈,特別是光照方向L和物體表面的菲尼爾系數F0,這些量變化范圍廣竭望,很難壓縮到速查表里。為啥要強調這些呢霞扬?那是因為我們希望盡可能預計算公式中的復雜部分,以供實時快速查詢和取用喻圃,比如余下我們還沒說得P項(D項),因為只涉及到微表面法線H一個控制變量雀扶,非常有利于我們創(chuàng)建速查表肆汹。舉個例子(實際處理并非如此):我們有一張2D的紋理,讓uv各自代表球面坐標系中的天頂角和方位角浪册,就能模擬出H的朝向岗照,然后以uv組合為索引,采樣紋理獲得預計算好的概率密度值攒至。

再來看Kelemen/Szirmay-Kalos在論文中引用的的P項表述,其本質是Beckmann distribution:

Beckmann distribution

式中α表示宏觀法線N與微表面法線H的夾角库菲。符號m代表當前面元的粗糙度志膀,可以通過對各個方向梯度值求取均方根(root mean square)來得到,但在實際工程中一般是由導入的粗糙度貼圖采樣獲得。所以上式是一個由夾角α以及粗糙度m一共2個維度變量控制的函數败去。

We employ a similar approach to efficiently compute the Kelemen/Szirmay-Kalos specular BRDF, but instead we precompute a single texture (the Beckmann distribution function) and use the Schlick Fresnel approximation for a fairly efficient specular reflectance calculation that allows m, the roughness parameter, to vary over the object.

上述片段來自于《GPU Gem 3》,表述了作者對Beckmann分布函數預處理以便生成速查紋理的設想广鳍,如下代碼同樣摘錄自書中吓妆,是對編碼紋理的實現:

float PHBeckmann( float ndoth, float m ) 
{   
    float alpha = acos( ndoth );   
    float ta = tan( alpha );   
    float val = 1.0/(m*m*pow(ndoth,4.0))*exp(-(ta*ta)/(m*m));   
    return val; 
} 

// Render a screen-aligned quad to precompute a 512x512 texture.    
float KSTextureCompute(float2 tex : TEXCOORD0) 
{   
    // Scale the value to fit within [0,1] – invert upon lookup.    
    return 0.5 * pow( PHBeckmann( tex.x, tex.y ), 0.1 ); 
}

很簡單,PHBeckmann方法負責計算Beckmann分布祖秒,注意其入參為ndothm,其中m不必多說竭缝,ndoth是宏觀法線N與微表面法線H的夾角的余弦值,具體計算時需要使用反余弦函數acos處理出真實α夾角咙俩。方法KSTextureCompute是對紋理的編碼湿故,主要利用了uv軸[0,1]區(qū)間的特性,將y=cosα以及y=m在這個區(qū)間上展開坛猪,帶入PHBeckmann方法進行計算。有2點需要注意独撇,其一是粗糙度m需要確保提前轉化到[0,1]區(qū)間躁锁;其二是我們需要將計算結果同樣壓縮到[0,1]區(qū)間中,具體方法參考代碼搜立。

實時取用的方法參考如下代碼槐秧,同樣很簡單,共分為3個步驟:

  • 首先是準備諸如h刁标,H以及ndoth等中間變量;
  • 其次是采樣預計算紋理顿锰,注意采樣uvfloat2(ndoth启搂,m),同時注意要反壓縮采樣獲得的數據胳赌;
  • 最后是參考Kelemen/Szirmay-Kalos提出的簡化版BRDF,裝配上GF項和P項熏版。
float KS_Skin_Specular( 
    float3 N,     // Bumped surface normal    
    float3 L,     // Points to light    
    float3 V,     // Points to eye    
    float m,      // Roughness    
    float rho_s,  // Specular brightness    
    uniform texobj2D beckmannTex ) 
{   
    float result = 0.0;   
    float ndotl = dot( N, L ); 
    if( ndotl > 0.0 ) 
    {    
        float3 h = L + V; // Unnormalized half-way vector    
        float3 H = normalize( h );    
        float ndoth = dot( N, H );    
        float PH = pow( 2.0*f1tex2D(beckmannTex,float2(ndoth,m)), 10.0 );    
        float F = fresnelReflectance( H, V, 0.028 );    
        float frSpec = max( PH * F / dot( h, h ), 0 );    
        result = ndotl * rho_s * frSpec; // BRDF * dot(N,L) * rho_s  
    }  
    return result; 
}

下圖展示了預計算速查紋理(右),以及對人像采樣該圖后直接輸出的效果(左)

precomputed p term

DEMO

TODO...[稍后放出]

Reference

[1] Advanced Techniques for Realistic Real-Time Skin Rendering
[2] Pre-Integrated Skin Shading
[3] Pre-Integrated Skin Shading 數學模型理解
[4] Pre-Integrated Skin Shading實現筆記
[5] GPU Gems 3 全書提煉總結

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末碰逸,一起剝皮案震驚了整個濱河市阔加,隨后出現的幾起案子,更是在濱河造成了極大的恐慌胳喷,老刑警劉巖夭织,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尊惰,死亡現場離奇詭異,居然都是意外死亡题禀,警方通過查閱死者的電腦和手機膀捷,發(fā)現死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秀仲,“玉大人壶笼,你說我怎么就攤上這事「才” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長侯勉。 經常有香客問我,道長铐拐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任吹害,我火速辦了婚禮虚青,結果婚禮上,老公的妹妹穿的比我還像新娘棒厘。我一直安慰自己,他們只是感情好谓媒,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布何乎。 她就那樣靜靜地躺著,像睡著了一般支救。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蒙保,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天欲主,我揣著相機與錄音,去河邊找鬼详恼。 笑死引几,一個胖子當著我的面吹牛,可吹牛的內容都是我干的伟桅。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼玖雁,長吁一口氣:“原來是場噩夢啊……” “哼盖腕!你這毒婦竟也來了浓镜?” 一聲冷哼從身側響起劲厌,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哄啄,沒想到半個月后辽幌,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡乌企,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年加酵,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片猪腕。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖亚亲,靈堂內的尸體忽然破棺而出腐缤,到底是詐尸還是另有隱情,我是刑警寧澤岭粤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布剃浇,位于F島的核電站,受9級特大地震影響虎囚,放射性物質發(fā)生泄漏。R本人自食惡果不足惜淘讥,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一适揉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嫉嘀,春花似錦、人聲如沸拭宁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽彩匕。三九已至,卻和暖如春掸犬,著一層夾襖步出監(jiān)牢的瞬間绪爸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工奠货, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人柔滔。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓丹墨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親贩挣。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內容