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


三、眼球渲染

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

Mike那深邃的眼眸,唏噓的胡渣子,神乎其神的眼神。。。應該征服了不少迷妹

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

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

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

3.1 眼球的構造及理論

3.1.1 眼球的構造

生物學的眼球解剖圖非常復雜,涉及的部位數十種。(下圖)

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

在圖形渲染領域,當然不可能關注這么多細節,可以將眼球構造做簡化,只關注其中的幾個部位:

上圖所示的序號代表的部位:

  • 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 眼球渲染總結

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

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

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

可見,要完全從零開始制作一個成像逼真的眼睛的資源(模型、貼圖、材質等),還是有相當的難度。幸好慷慨的虛幻引擎官方已經給出了足夠多的示例及資源,以供個人及團隊研究和研發,大大縮短了學習、開發的周期。

本系列文章其它部分

特別說明

  • 感謝參考文獻的所有作者們!
  • 后續還有毛發渲染等部分,敬請期待!

參考文獻


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM