目錄
- 三、眼球渲染
- 3.1 眼球的構造及理論
- 3.1.1 眼球的構造
- 3.1.2 眼球的渲染理論
- 3.2 眼球的渲染技術
- 3.2.1 角膜的半透和光澤反射
- 3.2.2 瞳孔的次表面散射
- 3.2.3 瞳孔的縮放
- 3.2.4 虹膜的顔色
- 3.2.5 其它眼球細節
- 3.3 眼球的底層實作
- 3.4 眼球的材質
- 3.4.1 眼球主材質
- 3.4.1.1 眼球的折射
- 3.4.1.2 瞳孔的縮放
- 3.4.1.3 眼球顔色的混合
- 3.4.1.4 眼球的法線
- 3.4.1.5 虹膜的遮罩和深度
- 3.4.1.6 清漆底部法線(ClearCoatBottomNormal)
- 3.4.1.7 眼球的其它部分
- 3.4.2 眼球附屬物材質
- 3.4.2.1 淚腺液體
- 3.4.2.2 遮蔽模糊體
- 3.4.2.3 眼角混合體
- 3.4.2.4 睫毛和眉毛
- 3.4.1 眼球主材質
- 3.5 眼球渲染總結
- 3.1 眼球的構造及理論
- 本系列文章其它部分
- 特别說明
- 參考文獻
都說眼睛是人類心靈的窗戶,若是眼睛渲染得逼真,将給虛拟角色點睛之筆,給予其栩栩如生的靈魂。
Mike那深邃的眼眸,唏噓的胡渣子,神乎其神的眼神。。。應該征服了不少迷妹
再來一張超近距離的特寫:
超近距離的眼睛特寫,細節刻畫得無與倫比,足以以假亂真。
然而,要渲染出如此逼真有神的眼睛,可不是那麼簡單,需要經過多道工序,運用許多渲染技法,刻畫很多細節。
生物學的眼球解剖圖非常複雜,涉及的部位數十種。(下圖)
人類眼球的生物學剖面圖,涉及部位多達數十種。
在圖形渲染領域,當然不可能關注這麼多細節,可以将眼球構造做簡化,隻關注其中的幾個部位:
上圖所示的序号代表的部位:
- 1 - 鞏膜(sclera):也稱為“眼白”,通常非常濕潤,包含少量的觸感紋理、血絲等細節。
- 2 - 角膜緣(limbus):角膜緣存在于虹膜和鞏膜之間的深色環形。有些眼睛中的角膜緣更為明顯,從側面看時往往會消退。
- 3 - 虹膜(iris):虹膜是圍繞在眼睛中心周圍的一圈色環。如果某個人有“綠”眼睛,就是因為虹膜主要是綠色的。在真實的眼睛中,虹膜是類似肌肉的纖維結構,有擴張和收縮功能,以讓更多光線進入瞳孔或者不讓光線進入瞳孔。還需要注意的是,在真實世界中,虹膜實際上更像是圓盤或錐形,不會向眼部其餘部分突出。
- 4 - 瞳孔(pupil):瞳孔是眼睛中心的黑點。這是一個孔,光線穿過這個孔後才會被視網膜的視杆和視錐捕捉到。
- 5 - 角膜(cornea):角膜是位于虹膜表面上的一層透明的、充滿液體的圓頂結構。
由于眼球充滿了液體,是以會折射照射進來的任何光線。在真實世界中從多個角度觀察眼球時就會看到這種效果。虹膜和瞳孔會因為折射而變形,因為它們是透過角膜觀看的。
遊戲和電影中用來解決這個問題的傳統方法是建立兩層獨立的眼睛表面,一層提供鞏膜、虹膜和瞳孔,另一層位于頂部,提供角膜和眼睛的總體濕潤度。這樣底層表面透過濕潤層觀看時就會産生折射。
《A Boy and His Kite》中的男孩眼睛中采用的就是兩層表面的渲染模型
根據上面的分析,以及簡化後的眼球解剖結構,就可以得出結論,要渲染好眼睛,需要着重實作的效果包括:
- 角膜的半透和光澤反射效果。
- 瞳孔的次表面散射。
- 瞳孔的縮放。最好根據整個場景的光照強度動态調整縮放大小。
- 虹膜的顔色變化。
- 其它眼球細節。
下節将詳細探讨。
本節主要參考來源:
- 角色渲染技術——毛發及其他。
- Next-Generation-Character-Rendering。
角膜的半透射和反射效果最能展現眼球渲染的效果。
簡單的做法就是直接把角膜看做一個半透明光澤球體的反射,正常的做法是用PBR流程計算其鏡面反射和IBL反射,然後給眼球一張虹膜和眼白的貼圖,這張貼圖作為角膜下面的折射效果,最後給角膜設定一個混合系數,把光澤球體反射效果和虹膜及眼白貼圖上的顔色進行混合。
角膜的鏡面反射和環境反射豐富了眼球的細節,增加了真實可信度
瞳孔本身實際上也是一個高低不平有縱深感的結構,它與角膜存在一定距離。這使得瞳孔會發生折射,并且,當光線到達瞳孔表面的時候,還會進一步在瞳孔結構内部發生次表面散射。
把眼球看成了一個雙層結構,外面一層是角膜,裡面一層是瞳孔的表面,而角膜和瞳孔之間我們可以認為是充斥了某種透明液體。
光線在進入瞳孔組織的内部前,首先會在角膜的表面發生一次折射,然後進入瞳孔組織的内部,産生散射,最後從瞳孔表面的另一個點散射出來。這裡就涉及到了兩個問題:
(1)一束射到角膜表面的光線在經過折射後,如何計算最終入射到瞳孔表面的位置;
(2)光線進入角膜内部後,如何計算其散射效果。
為了解決以上兩個問題,可使用次表面紋理映射(Subsurface texture mapping),這個方法旨在解決多層厚度不均勻的材質的次表面散射效果的計算。
如上圖,每一層材質都有一個單獨的深度圖,儲存在一個通道裡,然後每一層單獨的材質被認為是均勻的,擁有相同的散射、吸收系數以及相應的相位函數(散射相關的參數)。然後,以視線和第一層材質的交點為起點,沿着視線方向對多層材質進行ray-marching,每行進一步就根據位置和深度圖計算目前點位于材質的哪一層,對應什麼散射參數,再根據上一步的位置以及光照方向計算散射和吸收,直到ray-marching結束。具體到眼球的散射計算,實際上隻有一層散射材質,即瞳孔材質。是以我們隻需要提供瞳孔表面的深度圖,并設定好瞳孔材質的相關散射參數,再結合次表面紋理映射的方法計算即可。
這部分主要涉及的渲染技術:
- 視差貼圖(parallax mapping,也叫relief mapping)。可以通過ray marching的方法結合一張深度圖在相對平坦的幾何表面上實作視覺正确的高低起伏效果,法線效果雖然也能在平面上産生凹凸起伏,但在比較斜的視角下平面還是平面,視差貼圖則不會這樣。 左:normal mapping效果;右:parallax mapping效果。可見在傾斜視角下,後者效果要好很多。
- 基于實體的折射(Physically based Refraction)。與視差貼圖的欺騙式計算不同,基于實體的折射是根據真實的折射模型進行模拟,效果更真實。
float cosAlpha = dot(frontNormalW, -refractedW); float dist = height / cosAlpha; float3 offsetW = dist * refractedW; float2 offsetL = mul(offsetW, (float3x2) worldInverse); texcoord += float2(mask, -mask) * offsetL;
左:視差貼圖效果;右:基于實體的折射效果。
當光線從側面射進眼球時,經過折射和透射後,會在另一側發生較強烈的透射光環:
這種跟光線角度相關的折射,可以通過預計算的方式解決: - 參合多媒體渲染(participating media rendering)。它在近年來廣泛地被應用在體積光、雲彩和天空相關的渲染技術中。更多内容請參看:Rendering participating media。 利用participating media rendering技術渲染的體積霧。
瞳孔的放大和縮小實作非常簡單,通過控制采樣瞳孔貼圖的UV即可。
UE4的眼球模型的UV布局
Mike的眼球材質提供了縮放參數,以便調節瞳孔大小。
虹膜的顔色可以首先給定一個虹膜紋理的灰階圖,然後用給定虹膜顔色乘以灰階顔色,即可得到最終虹膜的顔色,這樣可以通過一套資源來實作不同顔色的眼球的渲染。
Mike的眼球材質提供了更改瞳孔、虹膜等顔色的參數。
眼球的細節刻畫可以增加其真實度,使畫面更上一個台階。
- 不平坦反射。真實的眼白不是完全鏡面平坦的,有一定程度的凹凸不平,可以通過類Sine函數擾動其法線貼圖達到模拟效果。
- 濕潤度。大多數人的眼睛都帶有不同程度的淚水,具有不同的濕潤度。可通過建立一層透明網格來模拟此效果。
不同濕潤度的網格模型
模拟出來的效果如下:
眼球的濕潤度從左到右:低、中、高。
此外,可以模糊濕潤網格,以便更好地将眼睛邊緣做融合:
- 眼睛自反射。由于眼球具體較強的反射,而已睫毛、眼皮會反射在上面,如果這部分被忽略,将會有點怪。
左:沒有自反射;右:有睫毛、眼皮等的自反射。
然而要實時地計算自反射會消耗較多的性能,可預先烘焙環境遮蔽圖,渲染時直接采樣:
- 瞳孔、虹膜、鞏膜等部位之間的過渡。由于它們分屬不同的材質,有着各自的屬性,如果它們的交界處不進行插值過渡,将會出現恐怖的效果(下圖右)。
左:采用了過渡;右:未采用過渡。
過渡曲線可采用類似Sine函數的變種:
- 血色和血絲。血絲可在眼白的紋理添加血管紋理細節,而血色可在計算時乘以由一張遮罩紋理控制的紅色來模拟。 帶血絲細節的眼球紋理。
- 接觸陰影(Contact Shadow)。半透明材質可以啟用接觸陰影。此功能使用類似于光源接觸陰影的功能,但不會連結到光源接觸陰影參數。這是螢幕空間效果,可以作為幾何體的補充,也可以取代幾何體,讓眼睛看起來牢牢地長在眼眶中,提高可信度。 左:未開啟接觸陰影;右:開啟接觸陰影,開啟後,反射光變弱了。
本節将深入源碼層剖析UE的眼睛渲染細節。需要注意的是,要将眼睛材質的
Shading Model
選擇為
Eye
(下圖),并且眼睛着色模式啟用了次表面散射,即眼睛着色模式是一種特殊化的次表面剖面(Subsurface Profile)着色模式。
在shader層,Eye的渲染模型跟普通的PBR流程和邏輯差別甚微,跟它相關的代碼檔案:
- G:\UnrealEngine\Engine\Shaders\Private\DeferredLightingCommon.ush。
- G:\UnrealEngine\Engine\Shaders\Private\BasePassPixelShader.usf。
- G:\UnrealEngine\Engine\Shaders\Private\ShadowProjectionPixelShader.usf。
- G:\UnrealEngine\Engine\Shaders\Private\ShadingModelsMaterial.ush。
首先分析
ShadingModelsMaterial.ush
在眼睛着色模式下GBuffer資料初始化相關的代碼:
void SetGBufferForShadingModel(
in out FGBufferData GBuffer,
in const FMaterialPixelParameters MaterialParameters,
const float Opacity,
const half3 BaseColor,
const half Metallic,
const half Specular,
const float Roughness,
const float3 SubsurfaceColor,
const float SubsurfaceProfile,
const float dither)
{
// ... (省略部分代碼)
#elif MATERIAL_SHADINGMODEL_EYE
GBuffer.ShadingModelID = SHADINGMODELID_EYE;
GBuffer.CustomData.x = EncodeSubsurfaceProfile(SubsurfaceProfile).x;
GBuffer.CustomData.w = 1.0f - saturate(GetMaterialCustomData0(MaterialParameters)); // Opacity = 1.0 - Iris Mask
GBuffer.Metallic = saturate(GetMaterialCustomData1(MaterialParameters)); // Iris Distance
// 如果定義了虹膜法線,進入了一段較複雜的資料處理。可見開啟虹膜法線需要消耗較多性能。
#if IRIS_NORMAL
float IrisMask = saturate( GetMaterialCustomData0(MaterialParameters) );
float IrisDistance = saturate( GetMaterialCustomData1(MaterialParameters) );
GBuffer.CustomData.x = EncodeSubsurfaceProfile(SubsurfaceProfile).x;
GBuffer.CustomData.w = 1.0 - IrisMask; // Opacity
float2 WorldNormalOct = UnitVectorToOctahedron( GBuffer.WorldNormal );
// CausticNormal stored as octahedron
#if NUM_MATERIAL_OUTPUTS_GETTANGENTOUTPUT > 0
// 通過法線的變換,建立一些凹陷度。
// Blend in the negative intersection normal to create some concavity
// Not great as it ties the concavity to the convexity of the cornea surface
// No good justification for that. On the other hand, if we're just looking to
// introduce some concavity, this does the job.
float3 PlaneNormal = normalize( GetTangentOutput0(MaterialParameters) );
float3 CausticNormal = normalize( lerp( PlaneNormal, -GBuffer.WorldNormal, IrisMask*IrisDistance ) );
float2 CausticNormalOct = UnitVectorToOctahedron( CausticNormal );
float2 CausticNormalDelta = ( CausticNormalOct - WorldNormalOct ) * 0.5 + (128.0/255.0);
GBuffer.Metallic = CausticNormalDelta.x;
GBuffer.Specular = CausticNormalDelta.y;
#else
float3 PlaneNormal = GBuffer.WorldNormal;
GBuffer.Metallic = 128.0/255.0;
GBuffer.Specular = 128.0/255.0;
#endif
// IrisNormal CustomData.yz
#if NUM_MATERIAL_OUTPUTS_CLEARCOATBOTTOMNORMAL > 0
float3 IrisNormal = normalize( ClearCoatBottomNormal0(MaterialParameters) );
#if MATERIAL_TANGENTSPACENORMAL
IrisNormal = normalize( TransformTangentVectorToWorld( MaterialParameters.TangentToWorld, IrisNormal ) );
#endif
#else
float3 IrisNormal = PlaneNormal;
#endif
float2 IrisNormalOct = UnitVectorToOctahedron( IrisNormal );
float2 IrisNormalDelta = ( IrisNormalOct - WorldNormalOct ) * 0.5 + (128.0/255.0);
GBuffer.CustomData.yz = IrisNormalDelta;
#else
GBuffer.Metallic = saturate(GetMaterialCustomData1(MaterialParameters)); // Iris Distance
#if NUM_MATERIAL_OUTPUTS_GETTANGENTOUTPUT > 0
float3 Tangent = GetTangentOutput0(MaterialParameters);
GBuffer.CustomData.yz = UnitVectorToOctahedron( normalize(Tangent) ) * 0.5 + 0.5;
#endif
#endif
// ... (省略部分代碼)
}
接着分析接觸陰影相關的代碼,在
DeferredLightingCommon.ush
内:
void GetShadowTerms(FGBufferData GBuffer, FDeferredLightData LightData, float3 WorldPosition, float3 L, float4 LightAttenuation, float Dither, inout FShadowTerms Shadow)
{
// 預設接觸陰影強度是0。
float ContactShadowLength = 0.0f;
// 接觸陰影長度螢幕空間縮放
const float ContactShadowLengthScreenScale = View.ClipToView[1][1] * GBuffer.Depth;
BRANCH
if (LightData.ShadowedBits)
{
// ... (省略部分代碼)
// 根據縮放因子計算接觸陰影長度。
FLATTEN
if (LightData.ShadowedBits > 1 && LightData.ContactShadowLength > 0)
{
ContactShadowLength = LightData.ContactShadowLength * (LightData.ContactShadowLengthInWS ? 1.0f : ContactShadowLengthScreenScale);
}
}
#if SUPPORT_CONTACT_SHADOWS
// 如果是頭發或者眼睛着色模式,接觸陰影長度強制縮放到0.2倍(這個值應該是測量過的值)。
if ((LightData.ShadowedBits < 2 && (GBuffer.ShadingModelID == SHADINGMODELID_HAIR))
|| GBuffer.ShadingModelID == SHADINGMODELID_EYE)
{
ContactShadowLength = 0.2 * ContactShadowLengthScreenScale;
}
#if MATERIAL_CONTACT_SHADOWS
ContactShadowLength = 0.2 * ContactShadowLengthScreenScale;
#endif
BRANCH
if (ContactShadowLength > 0.0)
{
float StepOffset = Dither - 0.5;
// 計算接觸陰影
float ContactShadow = ShadowRayCast( WorldPosition + View.PreViewTranslation, L, ContactShadowLength, 8, StepOffset );
Shadow.SurfaceShadow *= ContactShadow;
// 計算透射陰影
FLATTEN
if( GBuffer.ShadingModelID == SHADINGMODELID_HAIR )
Shadow.TransmissionShadow *= ContactShadow;
// 如果是眼睛渲染模式,則不加深陰影強度,否正加深。
else if( GBuffer.ShadingModelID != SHADINGMODELID_EYE )
Shadow.TransmissionShadow *= ContactShadow * 0.5 + 0.5;
}
#endif
}
還有小部分邏輯在
ShadowProjectionPixelShader.ush
,關于陰影計算的:
void Main(
in float4 SVPos : SV_POSITION,
out float4 OutColor : SV_Target0
)
{
// ... (省略部分代碼)
if (IsSubsurfaceModel(GBufferData.ShadingModelID))
{
float Opacity = GBufferData.CustomData.a;
// Derive density from a heuristic using opacity, tweaked for useful falloff ranges and to give a linear depth falloff with opacity
float Density = -.05f * log(1 - min(Opacity, .999f));
// 如果是頭發或眼睛渲染模式,不透明度和密度強制設為1。
if( GBufferData.ShadingModelID == SHADINGMODELID_HAIR || GBufferData.ShadingModelID == SHADINGMODELID_EYE )
{
Opacity = 1;
Density = 1;
}
// ... (省略部分代碼)
}
從上面分析可知,眼睛着色模式與次表面剖面着色模式基本一緻,隻是在GBuffer資料初始化、陰影計算上有所差别。
本節将分析Mike的眼球主材質和附屬物材質。
眼球主材質是
M_EyeRefractive
,下圖是眼球主材質的總覽圖,節點排布有點亂(UE材質編輯器并沒有提供自動排布功能)。下面将分小節重點分析眼球材質的重要或主要算法過程,其它的小細節将被忽略。
如上圖所示,眼球的折射主要通過材質函數
ML_EyeRefraction
實作,下面将對它的輸入參數和輸出參數進行分析。
材質函數
ML_EyeRefraction
的輸入參數:
-
:眼球内部折射,用于模拟光線進入虹膜後的折射率,數值通常在\([1.0,1.4]\)之間,越大折射效果越明顯。直接由變量InternalIoR
提供。IoR
-
:眼球(包含眼白、瞳孔、虹膜等)的縮放大小。直接由變量ScaleByCenter
ScaleByCenter
-
:角膜緣的UV寬度,由LimbusUVWidth
和LimbusUVWidthColor
組成的2D向量提供。LimbusUVWidthShading
-
:虹膜的深度縮放。數值越大,折射效果越明顯。由變量DepthScale
DepthScale
-
:深度平面偏移。決定瞳孔的大小和深度。由變量DepthPlaneOffset
Iris UV Radius
共同算出UV,然後采樣貼圖ScaleByCenter
的R通道提供資料。T_EyeMidPlaneDisplacement
-
:中平面偏移,決定角膜平面到瞳孔平面的深度偏移,瞳孔周邊的偏移會較小。直接采樣貼圖MidPlaneDisplacement
獲得。T_EyeMidPlaneDisplacement
如下:T_EyeMidPlaneDisplacement
-
:眼球模型的世界空間的法線。由EyeDirectionWorld
控制的兩張法線貼圖UseEyeBuldge
T_Eye_N
采樣後,由切線空間變換到世界空間獲得。其中T_Eye_Sphere_N
是中間有凸出的眼球結構(下圖左),而T_Eye_N
則沒有(下圖右):T_Eye_Sphere_N
-
:虹膜UV半徑,直接由變量IrisUVRadius
Iris UV Radius
ML_EyeRefraction
的輸出參數:
-
:折射後的UV,經過材質函數内部計算後,輸出的UV結果,後面可以用于采樣漫反射、其它遮罩貼圖。RefractedUV
-
:虹膜顔色透明度。Transparency
-
:辨別虹膜UV區域的遮罩。後續用于虹膜區域的相關着色處理。IrisMask
上面隻是分析了
ML_EyeRefraction
的輸入、輸出參數,下面将進入其内部計算過程:
首先分析折射向量(Refraction Direction)的計算:
float airIoR = 1.00029;
// 空氣對眼球内部的折射率比。
float n = airIoR / internalIoR;
// 法線和錄影機向量的夾角相關的縮放因子
float facing = dot(normalW, cameraW);
// 視角縮放後的折射率比。
float w = n * facing;
// 根據n和w計算中間因子。
float k = sqrt(1+(w-n)*(w+n));
// 根據n、w和k算出最終的折射向量。
float3 t;
t = (w - k)*normalW - n*cameraW;
t = normalize(t);
return -t;
再分析折射紋理偏移(Refracted UV Offset)的計算:
由上圖可見,要算出右邊紅色方框辨別的折射紋理偏移,需要用到衆多輸入參數,以及經過多次坐标運算和角度計算。雖然過程比較複雜,但原理跟[3.2.2 瞳孔的次表面散射](#3.2.2 瞳孔的次表面散射)的基于實體的折射一緻。
有了折射向量和折射紋理偏移,就可以通過數次基本運算調整,算出最終的輸出參數
RefractedUV
。
對于輸出參數
IrisMask
的計算,由以下shader代碼完成:
// 計算Iris遮罩(R通道)和角膜緣過渡區域(G通道)
UV = UV - float2(0.5f, 0.5f);
float2 m, r;
r = (length(UV) - (IrisUVRadius - LimbusUVWidth)) / LimbusUVWidth;
m = saturate(1 - r);
// 通過類sine函數變種,輸出柔和的混合因子,使得角膜緣過渡自然、柔和。
m = smoothstep(0, 1, m);
return m;
由上圖可以看出,如果開啟了折射(
Refraction On/Off
為true),則會使用上一小節計算的折射後的UV坐标,經過坐标換算和中心縮放,成為
Custom
shader節點的輸入參數,它的輸入還有
PupilScale
,用于決定瞳孔的大小。
Custom
shader節點的代碼如下:
// 主要是将UV坐标繞着紋理中心進行PupilScale縮放
// float2 UV, float PupilScale
float2 UVcentered = UV - float2(0.5f, 0.5f);
float UVlength = length(UVcentered);
// UV on circle at distance 0.5 from the center, in direction of original UV
float2 UVmax = normalize(UVcentered)*0.5f;
float2 UVscaled = lerp(UVmax, float2(0.f, 0.f), saturate((1.f - UVlength*2.f)*PupilScale));
return UVscaled + float2(0.5f, 0.5f);
眼球的顔色主要有兩種顔色提供:
- 眼白顔色(Sclera Color):由
采樣獲得,并且經過變量T_EyeScleraBaseColor
縮放。其中采樣的UV沒有折射,隻經過中心點縮放。ScleraBrightness
- 虹膜顔色(Iris Color):顔色采樣
獲得,并且紋理UV經過[3.4.1.1 眼球的折射](#3.4.1.1 眼球的折射)的折射計算,以及[3.4.1.2 瞳孔的縮放](#3.4.1.2 瞳孔的縮放)的中心點縮放。采樣得到的顔色經過T_EyeIrisBaseColor
和角膜緣(Limbus)相關的參數縮放。IrisBRightness
以上兩種顔色經過
ML_EyeRefraction
輸出的
IrisMask
進行插值,添加虹膜顔色(
CloudyIris
)後,最終輸出到Base Color引腳。
眼球法線的UV經過中心點縮放,接着去采用法線貼圖
T_Eye_Wet_N
,得出的法線經過材質函數
FlattenNormal
和縮放因子調整法線強度,最終輸出到法線引腳。
其中
FlattenNormal
的強度由
ML_EyeRefraction
IrisMask
訓示的虹膜區域在\([FlattenNormal, 1.0]\)進行插值。如果是虹膜區域,則不受法線影響,即完全光滑的。
虹膜的遮罩直接由
ML_EyeRefraction
IrisMask
虹膜的深度由折射後的紋理UV計算出距離圓心(0.5,0.5)的長度,獲得與
Iris UV Radius
的比值,再經過
Iris Concavity Scale
縮放和
Power
調整後,得到最終結果。(下圖)
如上圖,Custom節點與[3.4.1.2 瞳孔的縮放](#3.4.1.2 瞳孔的縮放)中的一樣,計算了UV沿着中心點縮放,接着去采樣瞳孔法線紋理
iris08_leftEye_nml
,獲得的結果經過
IrisDispStrength
控制的因子縮放,最後通過節點
BlendAngleCorrectedNormals
與眼球表面法線混合,輸出結果到Output節點
ClearCoatBottomNormal
眼球的其它屬性,如鏡面度、粗糙度、切線等,都比較簡單,直接看材質即可明白其計算過程,故這裡不做分析。
上小節分析了眼球的主材質,然而,眼睛的渲染還包含了很多附加物體,它們各自有着獨立的材質屬性(下圖)。
淚腺幾何體是一個包圍着眼皮周圍的網格體(上圖),提供了眼皮處的高光反射(下圖),用于模拟光線照射到淚腺後的鏡面反射。
左:無淚腺幾何體;右:有淚腺幾何體
它的材質如下圖,采用了透明混合模式:
它的顔色、金屬度預設都是1,可見用高反射率和高金屬度來獲得極強的鏡面反射效果。
它的粗糙度計算較複雜,如下圖:
紋理坐标經過變量
DetailScale_1
縮放後,去采樣細節紋理
skin_h
,獲得的結果再依次經過
DetailAmount
縮放、固定常量0.1和
Roughness
調整後,進入自定義shader節點
CurveToRoughness
計算,最終得到結果。其中
CurveToRoughness
的shader代碼如下:
// Specular antialiasing using derivatives and normal variance
float3 N = WorldNormal;
float3 dN = fwidth( N );
float Curvature = sqrt( 1 - dot( normalize( N + dN ), N ) );
// TODO find an approximation that more directly uses Roughness
float Power = 2 / pow( Roughness, 4 ) - 2;
float Angle = 4.11893 / sqrt( Power ) + Curvature;
Power = 16.9656 / ( Angle * Angle );
Roughness = sqrt( sqrt( 2 / (Power + 2) ) );
return Roughness;
上面涉及的粗糙度算法在[Rock-Solid Shading: Image Stability Without Sacrificing Detail](http://advances.realtimerendering.com/s2012/Ubisoft/Rock-Solid Shading.pdf)有較長的描述。
它的法線計算比較簡單,采樣法線貼圖
skin_n
後經過變量
DetailAmount
調整,就得到最終結果。
此外,它還增加了世界坐标偏移,由變量'DepthOffset'控制偏移量,經過材質函數
CameraOffset
得到相機空間的偏移。
遮蔽模糊體跟淚腺液體類似,環繞于眼角周邊,用于遮擋部分光照并模糊,使得周邊混合更真實(下圖)。
左:無遮蔽模糊體;右:有遮蔽模糊體
它的材質采用透明混合模式,并且光照模型是
Unlit
。它的總覽圖如下:
可分下面幾個部分進行分析:
-
不透明度(Opacity):
這部分主要是生成需要遮蔽和模糊的區域掩碼。過程大緻是通過采樣初始掩碼圖,加上紋理線性過渡、反向、加上Power運算調整,以及若幹變量控制的因子進行基本運算,獲得眼部周邊掩碼(下圖)。
-
模糊(Blur):
在目前UV周邊采樣16個
求得平均值。此處的Scene Color
一定是已經渲染眼球後的顔色,因為眼球是非透明物體,可保證在透明的遮蔽模糊體之前先繪制。Scene Color
-
顔色(Color):
原始顔色的輸出很簡單,利用上面計算的遮罩,在白色和
之間插值,然後與上面模糊後的場景顔色相乘。Blur Color
- 陰影(Shadow): 如上圖,通過UV的上下左右線性漸變及調整後獲得4個不同的值,進行相乘,獲得周邊黑色,最後通過變量在1.0之間插值,獲得頂部為深色的陰影圖。
-
綜合計算:
在此階段,利用上面的幾個計算結果,顔色和陰影相乘,并預乘了Alpha,獲得最終顔色和不透明度。
此外,還有位置偏移的計算,這裡将忽略。
眼角混合體為眼角增加血色及血絲細節,并調整亮度,使得眼白過渡更自然(下圖)。
左:無眼角混合體;右:有眼角混合體
它的材質啟用了次表面散射,并且混合模式是裁剪(Masked),材質總覽圖如下:
下面将其拆分成若幹部分進行分析:
- 漸變掩碼: 利用UV的橫坐标獲得線性過渡,用
調整強度,然後用Power
獲得平滑過渡的掩碼圖。SmoothStep
- 顔色: 通過幾個變量将UV坐标進行拉伸,去采樣眼睛貼圖
,獲得拉伸後的顔色,經過眼白亮度調整和由上節計算出的掩碼決定的眼白到血色的調整,獲得最終顔色。其中眼角偏紅,呈現出更多血色,而靠近瞳孔的區域受影響程度較低。eye_sclera_right_clr
-
法線:
法線的獲得,主要由上面計算出的掩碼,在向量[0, 0, 1]和[-1, 0, 0]插值獲得。
由于睫毛和眉毛的材質屬于
Hair
着色模式,雖然是眼睛的組成部分,但其實是毛發渲染的範疇,後續章節将會詳細闡述。
由上面可知,雖然眼睛的渲染技術不如皮膚渲染來得更高深、更系統,但由于其涉及的部位和細節多,環環相扣,各個材質之間相輔相成,形成了一套完整而逼真的眼睛渲染體系。
本章結尾,引用官方文檔的建議:
在開發數字人類角色時,我們在模型中使用了一些不同方法和材質提升了角色眼部的逼真度。如上所述,許多眼部設定與材質設定和采集的參考資料之間存在着互相依賴的關系。我們強烈建議使用我們的眼部設定作為您的起點。
可見,要完全從零開始制作一個成像逼真的眼睛的資源(模型、貼圖、材質等),還是有相當的難度。幸好慷慨的虛幻引擎官方已經給出了足夠多的示例及資源,以供個人及團隊研究和研發,大大縮短了學習、開發的周期。
- 剖析Unreal Engine超真實人類的渲染技術Part 1 - 概述和皮膚渲染
- 剖析Unreal Engine超真實人類的渲染技術Part 3 - 毛發渲染及其它
- 感謝參考文獻的所有作者們!
- 後續還有毛發渲染等部分,敬請期待!
- Next-Generation-Character-Rendering (ACM Transactions on Graphics, Vol. 29(5), SIGGRAPH Asia 2010)
- Separable Subsurface Scattering
- Real-Time Realistic Skin Translucency
- 《由淺入深學習PBR的原理和實作》
- 數字人類
- 照片級角色
- 角色渲染技術——毛發及其他
- Cry Engine Doc: Eye Shader
- A Survey on Participating Media Rendering Techniques
- Rendering participating media
- Modeling and real-time rendering of participating media using the GPU
- Deferred_POM-DirectX Renderer
- Subsurface Texture Mapping
- [Rock-Solid Shading: Image Stability Without Sacrificing Detail](http://advances.realtimerendering.com/s2012/Ubisoft/Rock-Solid Shading.pdf)
- The Process of Creating Volumetric-based Materials in Uncharted 4
- 眼睛模組化逐漸剖析教學-超詳細