剖析Unreal Engine超真實人類的渲染技術Part 2 - 眼球渲染

[TOC]html

3、眼球渲染

都說眼睛是人類心靈的窗戶,如果眼睛渲染得逼真,將給虛擬角色點睛之筆,給予其栩栩如生的靈魂。git

Mike那深邃的眼眸,唏噓的鬍渣子,神乎其神的眼神。。。應該征服了很多迷妹算法

再來一張超近距離的特寫:api

超近距離的眼睛特寫,細節刻畫得無與倫比,足以以假亂真。app

然而,要渲染出如此逼真有神的眼睛,可不是那麼簡單,須要通過多道工序,運用許多渲染技法,刻畫不少細節。less

3.1 眼球的構造及理論

3.1.1 眼球的構造

生物學的眼球解剖圖很是複雜,涉及的部位數十種。(下圖)編輯器

人類眼球的生物學剖面圖,涉及部位多達數十種。函數

在圖形渲染領域,固然不可能關注這麼多細節,能夠將眼球構造作簡化,只關注其中的幾個部位:佈局

上圖所示的序號表明的部位:post

  • 1 - 鞏膜(sclera):也稱爲「眼白」,一般很是溼潤,包含少許的觸感紋理、血絲等細節。
  • 2 - 角膜緣(limbus):角膜緣存在於虹膜和鞏膜之間的深色環形。有些眼睛中的角膜緣更爲明顯,從側面看時每每會消退。
  • 3 - 虹膜(iris):虹膜是圍繞在眼睛中心周圍的一圈色環。若是某我的有「綠」眼睛,就是由於虹膜主要是綠色的。在真實的眼睛中,虹膜是相似肌肉的纖維結構,有擴張和收縮功能,以讓更多光線進入瞳孔或者不讓光線進入瞳孔。還須要注意的是,在真實世界中,虹膜實際上更像是圓盤或錐形,不會向眼部其他部分突出。
  • 4 - 瞳孔(pupil):瞳孔是眼睛中心的黑點。這是一個孔,光線穿過這個孔後纔會被視網膜的視杆和視錐捕捉到。
  • 5 - 角膜(cornea):角膜是位於虹膜表面上的一層透明的、充滿液體的圓頂結構。

3.1.2 眼球的渲染理論

因爲眼球充滿了液體,所以會折射照射進來的任何光線。在真實世界中從多個角度觀察眼球時就會看到這種效果。虹膜和瞳孔會由於折射而變形,由於它們是透過角膜觀看的。

遊戲和電影中用來解決這個問題的傳統方法是建立兩層獨立的眼睛表面,一層提供鞏膜、虹膜和瞳孔,另外一層位於頂部,提供角膜和眼睛的整體溼潤度。這樣底層表面透過溼潤層觀看時就會產生折射。

《A Boy and His Kite》中的男孩眼睛中採用的就是兩層表面的渲染模型

根據上面的分析,以及簡化後的眼球解剖結構,就能夠得出結論,要渲染好眼睛,須要着重實現的效果包括:

  • 角膜的半透和光澤反射效果。
  • 瞳孔的次表面散射。
  • 瞳孔的縮放。最好根據整個場景的光照強度動態調整縮放大小。
  • 虹膜的顏色變化。
  • 其它眼球細節。

下節將詳細探討。

3.2 眼球的渲染技術

本節主要參考來源:

3.2.1 角膜的半透和光澤反射

角膜的半透射和反射效果最能體現眼球渲染的效果。

簡單的作法就是直接把角膜看作一個半透明光澤球體的反射,正常的作法是用PBR流程計算其鏡面反射和IBL反射,而後給眼球一張虹膜和眼白的貼圖,這張貼圖做爲角膜下面的折射效果,最後給角膜設定一個混合係數,把光澤球體反射效果和虹膜及眼白貼圖上的顏色進行混合。

角膜的鏡面反射和環境反射豐富了眼球的細節,增長了真實可信度

3.2.2 瞳孔的次表面散射

瞳孔自己實際上也是一個高低不平有縱深感的結構,它與角膜存在必定距離。這使得瞳孔會發生折射,而且,當光線到達瞳孔表面的時候,還會進一步在瞳孔結構內部發生次表面散射。

把眼球當作了一個雙層結構,外面一層是角膜,裏面一層是瞳孔的表面,而角膜和瞳孔之間咱們能夠認爲是充斥了某種透明液體。

光線在進入瞳孔組織的內部前,首先會在角膜的表面發生一次折射,而後進入瞳孔組織的內部,產生散射,最後從瞳孔表面的另外一個點散射出來。這裏就涉及到了兩個問題:

(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技術渲染的體積霧。

3.2.3 瞳孔的縮放

瞳孔的放大和縮小實現很是簡單,經過控制採樣瞳孔貼圖的UV便可。

UE4的眼球模型的UV佈局

Mike的眼球材質提供了縮放參數,以便調節瞳孔大小。

3.2.4 虹膜的顏色

虹膜的顏色能夠首先給定一個虹膜紋理的灰度圖,而後用給定虹膜顏色乘以灰度顏色,便可獲得最終虹膜的顏色,這樣能夠經過一套資源來實現不一樣顏色的眼球的渲染。

Mike的眼球材質提供了更改瞳孔、虹膜等顏色的參數。

3.2.5 其它眼球細節

眼球的細節刻畫能夠增長其真實度,使畫面更上一個臺階。

  • 不平坦反射。真實的眼白不是徹底鏡面平坦的,有必定程度的凹凸不平,能夠經過類Sine函數擾動其法線貼圖達到模擬效果。

  • 溼潤度。大多數人的眼睛都帶有不一樣程度的淚水,具備不一樣的溼潤度。可經過創建一層透明網格來模擬此效果。

    不一樣溼潤度的網格模型

    模擬出來的效果以下:

    眼球的溼潤度從左到右:低、中、高。

    此外,能夠模糊溼潤網格,以便更好地將眼睛邊緣作融合:

  • 眼睛自反射。因爲眼球具體較強的反射,而已睫毛、眼皮會反射在上面,若是這部分被忽略,將會有點怪。

    左:沒有自反射;右:有睫毛、眼皮等的自反射。

    然而要實時地計算自反射會消耗較多的性能,可預先烘焙環境遮蔽圖,渲染時直接採樣:

  • 瞳孔、虹膜、鞏膜等部位之間的過渡。因爲它們分屬不一樣的材質,有着各自的屬性,若是它們的交界處不進行插值過渡,將會出現恐怖的效果(下圖右)。

    左:採用了過渡;右:未採用過渡。

    過渡曲線可採用相似Sine函數的變種:

  • 血色和血絲。血絲可在眼白的紋理添加血管紋理細節,而血色可在計算時乘以由一張遮罩紋理控制的紅色來模擬。

    帶血絲細節的眼球紋理。

  • 接觸陰影(Contact Shadow)。半透明材質能夠啓用接觸陰影。此功能使用相似於光源接觸陰影的功能,但不會連接到光源接觸陰影參數。這是屏幕空間效果,能夠做爲幾何體的補充,也能夠取代幾何體,讓眼睛看起來緊緊地長在眼眶中,提升可信度。

    左:未開啓接觸陰影;右:開啓接觸陰影,開啓後,反射光變弱了。

3.3 眼球的底層實現

本節將深刻源碼層剖析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數據初始化、陰影計算上有所差異。

3.4 眼球的材質

本節將分析Mike的眼球主材質和附屬物材質。

3.4.1 眼球主材質

眼球主材質是M_EyeRefractive,下圖是眼球主材質的總覽圖,節點排布有點亂(UE材質編輯器並無提供自動排布功能)。下面將分小節重點分析眼球材質的重要或主要算法過程,其它的小細節將被忽略。

3.4.1.1 眼球的折射

如上圖所示,眼球的折射主要經過材質函數ML_EyeRefraction實現,下面將對它的輸入參數和輸出參數進行分析。

材質函數ML_EyeRefraction的輸入參數:

  • InternalIoR:眼球內部折射,用於模擬光線進入虹膜後的折射率,數值一般在$[1.0,1.4]$之間,越大折射效果越明顯。直接由變量IoR提供。

  • ScaleByCenter:眼球(包含眼白、瞳孔、虹膜等)的縮放大小。直接由變量ScaleByCenter提供。

  • LimbusUVWidth:角膜緣的UV寬度,由LimbusUVWidthColorLimbusUVWidthShading組成的2D向量提供。

  • DepthScale:虹膜的深度縮放。數值越大,折射效果越明顯。由變量DepthScale提供。

  • DepthPlaneOffset:深度平面偏移。決定瞳孔的大小和深度。由變量Iris UV RadiusScaleByCenter共同算出UV,而後採樣貼圖T_EyeMidPlaneDisplacement的R通道提供數據。

  • MidPlaneDisplacement:中平面偏移,決定角膜平面到瞳孔平面的深度偏移,瞳孔周邊的偏移會較小。直接採樣貼圖T_EyeMidPlaneDisplacement得到。T_EyeMidPlaneDisplacement以下:

  • EyeDirectionWorld:眼球模型的世界空間的法線。由UseEyeBuldge控制的兩張法線貼圖T_Eye_NT_Eye_Sphere_N採樣後,由切線空間變換到世界空間得到。其中T_Eye_N是中間有凸出的眼球結構(下圖左),而T_Eye_Sphere_N則沒有(下圖右):

  • IrisUVRadius:虹膜UV半徑,直接由變量Iris UV Radius提供。

材質函數ML_EyeRefraction的輸出參數:

  • RefractedUV:折射後的UV,通過材質函數內部計算後,輸出的UV結果,後面能夠用於採樣漫反射、其它遮罩貼圖。
  • Transparency:虹膜顏色透明度。
  • IrisMask:標識虹膜UV區域的遮罩。後續用於虹膜區域的相關着色處理。

上面只是分析了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;

3.4.1.2 瞳孔的縮放

由上圖能夠看出,若是開啓了折射(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);

3.4.1.3 眼球顏色的混合

眼球的顏色主要有兩種顏色提供:

  • 眼白顏色(Sclera Color):由T_EyeScleraBaseColor採樣得到,而且通過變量ScleraBrightness縮放。其中採樣的UV沒有折射,只通過中心點縮放。
  • 虹膜顏色(Iris Color):顏色採樣T_EyeIrisBaseColor得到,而且紋理UV通過[3.4.1.1 眼球的折射](#3.4.1.1 眼球的折射)的折射計算,以及[3.4.1.2 瞳孔的縮放](#3.4.1.2 瞳孔的縮放)的中心點縮放。採樣獲得的顏色通過IrisBRightness和角膜緣(Limbus)相關的參數縮放。

以上兩種顏色通過ML_EyeRefraction輸出的IrisMask進行插值,添加虹膜顏色(CloudyIris)後,最終輸出到Base Color引腳。

3.4.1.4 眼球的法線

眼球法線的UV通過中心點縮放,接着去採用法線貼圖T_Eye_Wet_N,得出的法線通過材質函數FlattenNormal和縮放因子調整法線強度,最終輸出到法線引腳。

其中FlattenNormal的強度由ML_EyeRefraction輸出的IrisMask指示的虹膜區域在$[FlattenNormal, 1.0]$進行插值。若是是虹膜區域,則不受法線影響,即徹底光滑的。

3.4.1.5 虹膜的遮罩和深度

虹膜的遮罩直接由ML_EyeRefraction輸出的IrisMask得到。

虹膜的深度由折射後的紋理UV計算出距離圓心(0.5,0.5)的長度,得到與Iris UV Radius的比值,再通過Iris Concavity Scale縮放和Power調整後,獲得最終結果。(下圖)

3.4.1.6 清漆底部法線(ClearCoatBottomNormal)

如上圖,Custom節點與[3.4.1.2 瞳孔的縮放](#3.4.1.2 瞳孔的縮放)中的同樣,計算了UV沿着中心點縮放,接着去採樣瞳孔法線紋理iris08_leftEye_nml,得到的結果通過IrisDispStrength控制的因子縮放,最後經過節點BlendAngleCorrectedNormals與眼球表面法線混合,輸出結果到Output節點ClearCoatBottomNormal

3.4.1.7 眼球的其它部分

眼球的其它屬性,如鏡面度、粗糙度、切線等,都比較簡單,直接看材質便可明白其計算過程,故這裏不作分析。

3.4.2 眼球附屬物材質

上小節分析了眼球的主材質,然而,眼睛的渲染還包含了不少附加物體,它們各自有着獨立的材質屬性(下圖)。

3.4.2.1 淚腺液體

淚腺幾何體是一個包圍着眼皮周圍的網格體(上圖),提供了眼皮處的高光反射(下圖),用於模擬光線照射到淚腺後的鏡面反射。

左:無淚腺幾何體;右:有淚腺幾何體

它的材質以下圖,採用了透明混合模式:

它的顏色、金屬度默認都是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獲得相機空間的偏移。

3.4.2.2 遮蔽模糊體

遮蔽模糊體跟淚腺液體相似,環繞於眼角周邊,用於遮擋部分光照並模糊,使得周邊混合更真實(下圖)。

左:無遮蔽模糊體;右:有遮蔽模糊體

它的材質採用透明混合模式,而且光照模型是Unlit。它的總覽圖以下:

可分下面幾個部分進行分析:

  • 不透明度(Opacity):

    這部分主要是生成須要遮蔽和模糊的區域掩碼。過程大體是經過採樣初始掩碼圖,加上紋理線性過渡、反向、加上Power運算調整,以及若干變量控制的因子進行基本運算,得到眼部周邊掩碼(下圖)。

  • 模糊(Blur):

    在當前UV周邊採樣16個Scene Color求得平均值。此處的Scene Color必定是已經渲染眼球后的顏色,由於眼球是非透明物體,可保證在透明的遮蔽模糊體以前先繪製。

  • 顏色(Color):

    原始顏色的輸出很簡單,利用上面計算的遮罩,在白色和Blur Color之間插值,而後與上面模糊後的場景顏色相乘。

  • 陰影(Shadow):

    如上圖,經過UV的上下左右線性漸變及調整後得到4個不一樣的值,進行相乘,得到周邊黑色,最後經過變量在1.0之間插值,得到頂部爲深色的陰影圖。

  • 綜合計算:

    在此階段,利用上面的幾個計算結果,顏色和陰影相乘,並預乘了Alpha,得到最終顏色和不透明度。

此外,還有位置偏移的計算,這裏將忽略。

3.4.2.3 眼角混合體

眼角混合體爲眼角增長血色及血絲細節,並調整亮度,使得眼白過渡更天然(下圖)。

左:無眼角混合體;右:有眼角混合體

它的材質啓用了次表面散射,而且混合模式是裁剪(Masked),材質總覽圖以下:

下面將其拆分紅若干部分進行分析:

  • 漸變掩碼:

    利用UV的橫座標得到線性過渡,用Power調整強度,而後用SmoothStep得到平滑過渡的掩碼圖。

  • 顏色:

    經過幾個變量將UV座標進行拉伸,去採樣眼睛貼圖eye_sclera_right_clr,得到拉伸後的顏色,通過眼白亮度調整和由上節計算出的掩碼決定的眼白到血色的調整,得到最終顏色。其中眼角偏紅,呈現出更多血色,而靠近瞳孔的區域受影響程度較低。

  • 法線:

    法線的得到,主要由上面計算出的掩碼,在向量[0, 0, 1]和[-1, 0, 0]插值得到。

3.4.2.4 睫毛和眉毛

因爲睫毛和眉毛的材質屬於Hair着色模式,雖然是眼睛的組成部分,但實際上是毛髮渲染的範疇,後續章節將會詳細闡述。

3.5 眼球渲染總結

由上面可知,雖然眼睛的渲染技術不如皮膚渲染來得更高深、更系統,但因爲其涉及的部位和細節多,環環相扣,各個材質之間相輔相成,造成了一套完整而逼真的眼睛渲染體系。

本章結尾,引用官方文檔的建議:

在開發數字人類角色時,咱們在模型中使用了一些不一樣方法和材質提高了角色眼部的逼真度。如上所述,許多眼部設置與材質設置和採集的參考資料之間存在着相互依賴的關係。咱們強烈建議使用咱們的眼部設置做爲您的起點。

可見,要徹底從零開始製做一個成像逼真的眼睛的資源(模型、貼圖、材質等),仍是有至關的難度。幸虧慷慨的虛幻引擎官方已經給出了足夠多的示例及資源,以供我的及團隊研究和研發,大大縮短了學習、開發的週期。

本系列文章其它部分

特別說明

  • 感謝參考文獻的全部做者們!
  • 後續還有毛髮渲染等部分,敬請期待!

參考文獻

相關文章
相關標籤/搜索