Skip to content

언리얼 엔진의 초현실적인 휴먼 렌더링 기술 분석 2부 - 안구 렌더링

🌐 원문 링크: 剖析Unreal Engine超真实人类的渲染技术Part 2 - 眼球渲染 (cnblogs.com/timlly)
📅 원문 발행일: 2019-07-07 | ✍️ 저자: Timlly (00 / )

💡 시리즈 분류: Digital Human국내 업계 표준 용어 감수 및 수식/도해 복원 적용 완료


눈은 인간의 영혼을 들여다보는 창이라고 합니다. 눈이 사실적으로 렌더링되면 가상 캐릭터에 마무리 터치를 추가하고 실제와 같은 영혼을 부여하게 됩니다.

마이크의 깊은 눈, 슬픈 수염, 그리고 기적적인 눈. . . 많은 팬을 사로잡았나봐요

또 다른 슈퍼 클로즈업은 다음과 같습니다.

초밀착으로 눈을 클로즈업한 디테일은 비교할 수 없을 정도로 실제처럼 보입니다.

그러나 실제와 같은 눈을 렌더링하는 것은 그리 간단하지 않습니다. 여러 프로세스, 많은 렌더링 기술 및 많은 세부 사항이 필요합니다.

3.1 안구의 구조와 이론

3.1.1 안구의 구조

안구의 생물학적 해부학은 수십 개의 부품으로 구성되어 매우 복잡합니다. (아래 사진)

수십 개의 부품이 포함된 인간 안구의 생물학적 단면.

그래픽 렌더링 분야에서는 물론 그렇게 많은 세부 사항에 주의를 기울이는 것은 불가능합니다. 안구 구조를 단순화하고 몇 가지 부분에만 집중할 수 있습니다.

위 그림에 표시된 일련 번호로 표시되는 부품:

  • 1 - 공막: "눈의 흰자위"라고도 알려져 있으며 일반적으로 매우 촉촉하고 소량의 촉각 질감, 충혈 및 기타 세부 사항이 포함되어 있습니다.

  • 2 - 윤부: 윤부란 홍채와 공막 사이에 존재하는 어두운 고리 모양의 고리입니다. 윤부(윤부)는 어떤 눈에서는 더 뚜렷하며 옆에서 볼 때 희미해지는 경향이 있습니다.

  • 3 - 홍채: 홍채는 눈 중앙을 둘러싸는 색상의 고리입니다. 누군가가 "녹색" 눈을 가지고 있다면 이는 홍채가 주로 녹색이기 때문입니다. 실제 눈에서 홍채는 더 많은 빛이 동공으로 들어가도록 하거나 빛이 동공으로 들어가는 것을 방지하기 위해 확장 및 수축하는 섬유질 근육과 같은 구조입니다. 실제 세계에서 홍채는 실제로 원반이나 원뿔과 비슷하며 눈의 나머지 부분을 향해 돌출되지 않는다는 점에 유의하는 것도 중요합니다.

  • 4 - 동공(pupil) : 눈동자는 눈 중앙에 있는 검은 점입니다. 이것은 빛이 망막의 간상체와 원추체에 포착되기 전에 통과하는 구멍입니다.

  • 5 - 한국: 각막은 홍채 표면에 위치한 투명한 액체로 채워진 돔 구조입니다.

3.1.2 안구 렌더링 이론

안구는 액체로 채워져 있기 때문에 안구에 닿는 모든 빛을 굴절시킵니다. 이 효과는 현실 세계에서 여러 각도에서 안구를 바라볼 때 나타납니다. 홍채와 동공은 각막을 통해 보이기 때문에 굴절에 의해 왜곡됩니다.

게임과 영화에서 이 문제를 해결하는 데 사용되는 전통적인 방법은 눈 표면에 두 개의 별도 레이어를 만드는 것입니다. 하나는 공막, 홍채, 동공을 제공하고 그 위에는 각막과 눈의 전반적인 보습을 제공하는 다른 레이어를 만듭니다. 이로 인해 습윤층을 통해 볼 때 기본 표면이 굴절됩니다.

"소년과 그의 연"에 나오는 소년의 눈은 2레이어 렌더링 모델을 사용합니다

위의 분석과 단순화된 안구 해부학을 바탕으로 눈을 잘 표현하기 위해 달성해야 하는 효과는 다음과 같다는 결론을 내릴 수 있습니다.

  • 각막의 반투명하고 윤기 있는 반사 효과.

  • 동공으로부터의 서브서피스 스캐터링(SSS).

  • 학생들의 스케일링. 전체 장면의 빛 강도에 따라 확대/축소 크기를 동적으로 조정하는 것이 가장 좋습니다.

  • 홍채의 색상 변화.

  • 기타 안구 세부 사항.

이에 대해서는 다음 섹션에서 자세히 설명합니다.

3.2 안구 렌더링 기술

이 섹션의 주요 참고 자료:

  • 캐릭터 렌더링 기술 - 머리카락 등.

-차세대-캐릭터-렌더링.

3.2.1 각막 반투명도 및 광택 반사

각막의 반투과 및 반사 효과는 안구 렌더링의 효과를 가장 잘 반영합니다.

간단한 방법은 각막을 반투명 광택 구의 반사로 직접 간주하는 것입니다. 일반적인 방법은 PBR 프로세스를 사용하여 스페큘러 반사 및 IBL 반사를 계산한 다음 안구에 홍채와 눈 흰자위의 맵을 제공하는 것입니다. 이 맵은 각막 아래의 굴절 효과 역할을 합니다. 마지막으로, 각막에 대한 혼합 계수를 설정하여 광택 있는 구 반사 효과를 눈 맵의 홍채 및 흰색 색상과 혼합합니다.

각막의 스페큘러 반사 및 환경반사로 안구의 디테일을 풍부하게 하여 실제 신뢰성을 높입니다

3.2.2 동공 지하 산란

실제로 동공 자체는 깊이감이 있는 울퉁불퉁한 구조로 각막과 일정한 거리가 있습니다. 이로 인해 동공이 굴절되고, 빛이 동공 표면에 도달하면 동공 구조 내에서 서브서피스 스캐터링(SSS)이 더 진행됩니다.

안구를 이중층 구조로 생각해보세요. 바깥층은 각막이고, 안쪽 층은 동공의 표면입니다. 각막과 동공 사이의 공간은 일종의 투명한 액체로 채워져 있다고 생각할 수 있습니다.

빛이 동공 조직 내부로 들어가기 전에 먼저 각막 표면에서 굴절된 다음 동공 조직 내부로 들어가서 산란되고 마지막으로 동공 표면의 다른 지점에서 산란됩니다. 여기에는 두 가지 문제가 관련되어 있습니다.

(1) 각막 표면에 부딪힌 빛의 광선이 굴절된 후 최종적으로 동공 표면에 들어가는 위치를 계산하는 방법;

(2) 빛이 각막 내부로 들어간 후의 산란 효과를 계산하는 방법.

위의 두 가지 문제를 해결하기 위해 Subsurface Texture Mapping(Subsurface Texture Mapping)을 사용할 수 있습니다. 이 방법은 두께가 고르지 않은 다층 재료의 서브서피스 스캐터링(SSS) 효과 계산을 해결하기 위해 설계되었습니다.

위에 표시된 것처럼 각 재료 레이어에는 채널에 저장되는 별도의 깊이 맵이 있습니다. 그런 다음 별도의 재료로 구성된 각 층은 동일한 산란, 흡수 계수 및 해당 위상 함수(산란 관련 매개변수)를 사용하여 균일한 것으로 간주됩니다. 그런 다음 시선과 첫 번째 재료 레이어의 교차점에서 시작하여 시선을 따라 다층 재료를 레이 행진합니다. 각 라인은 위치 및 깊이 맵을 기반으로 현재 지점이 위치한 재료의 레이어와 해당 지점에 해당하는 산란 매개변수를 추가로 계산합니다. 그런 다음 Ray-marching이 완료될 때까지 이전 단계의 위치와 조명 방향을 기반으로 산란 및 흡수를 계산합니다. 안구의 산란 계산과 관련하여 실제로는 산란 물질의 한 층, 즉 동공 물질만 있습니다. 따라서 우리는 동공 표면의 깊이 맵을 제공하고 동공 재료의 관련 산란 매개변수를 설정한 다음 표면 텍스처 매핑 방법을 사용하여 이를 계산하면 됩니다.

이 부분에 주로 관련된 렌더링 기술은 다음과 같습니다.

  • 시차 매핑(시차 매핑, 릴리프 매핑이라고도 함). 깊이 맵과 결합된 레이 행진 방법을 사용하여 상대적으로 평평한 기하학적 표면에서 시각적으로 올바른 기복 효과를 얻을 수 있습니다. 노멀 효과로 인해 평면에 울퉁불퉁한 부분과 기복이 생길 수도 있지만 비스듬한 시야각에서는 평면이 여전히 편평합니다. 시차 매핑의 경우에는 그렇지 않습니다.

왼쪽: 일반 매핑 효과; 오른쪽: 시차 매핑 효과. 비스듬한 시야각에서는 후자의 효과가 훨씬 더 우수하다는 것을 알 수 있습니다.

  • 물리적 기반 굴절(물리적 기반 굴절). 시차 맵의 기만적인 계산과 달리 물리적 기반 굴절은 실제 굴절 모델을 기반으로 시뮬레이션되므로 효과가 더 현실적입니다.

cpp
float cosAlpha = dot(frontNormalW, -refractedW);
float dist = height / cosAlpha;
float3 offsetW = dist * refractedW;
float2 offsetL = mul(offsetW, (float3x2) worldInverse);
texcoord += float2(mask, -mask) * offsetL;

왼쪽: 시차 매핑 효과; 오른쪽: 물리적 기반 굴절 효과.

빛이 측면에서 안구로 들어가면 굴절 및 투과 후 반대쪽에 더 강한 투과 후광이 발생합니다.

빛의 각도와 관련된 굴절은 사전 계산을 통해 해결할 수 있습니다.

  • 미디어 렌더링 참여(미디어 렌더링 참여). 최근에는 체적광, 구름, 하늘과 관련된 렌더링 기술에 널리 사용되어 왔습니다. 자세한 내용은 참여 미디어 렌더링을 참조하세요.

참여 미디어 렌더링 기술을 사용하여 렌더링된 볼륨 포그.

3.2.3 동공 스케일링

동공의 확대 및 축소는 샘플링된 동공 맵의 UV를 제어하기만 하면 매우 간단합니다.

UE4 안구 모델의 UV 레이아웃

마이크의 안구 재질은 동공 크기를 조정하는 스케일링 매개변수를 제공합니다.

3.2.4 붓꽃의 색깔

붓꽃의 색상에는 먼저 붓꽃 텍스처의 회색조 이미지가 주어질 수 있으며, 그런 다음 주어진 붓꽃 색상에 회색조 색상을 곱하여 최종 붓꽃 색상을 얻을 수 있습니다. 이러한 방식으로 다양한 색상의 안구 렌더링은 일련의 리소스를 통해 달성될 수 있습니다.

마이크의 안구 재질은 눈동자, 홍채 등의 색상을 변경하는 매개변수를 제공합니다.

3.2.5 기타 안구 세부정보

안구의 세밀한 묘사는 현실감을 높이고 사진의 완성도를 높여줍니다.

  • 평탄화되지 않은 반사. 실제 눈의 흰자위는 완전히 거울처럼 평평하지는 않지만 어느 정도 불균일한 부분이 있습니다. 노멀 맵은 시뮬레이션 효과를 얻기 위해 사인과 같은 함수로 교란될 수 있습니다.

  • 수분. 대부분의 사람들의 눈에는 다양한 수준의 눈물이 포함되어 있으며 수분 함량도 다양합니다. 이 효과는 투명한 메시 레이어를 생성하여 시뮬레이션할 수 있습니다.

습도 수준이 다른 메쉬 모델

시뮬레이션된 효과는 다음과 같습니다.

안구의 촉촉함은 왼쪽부터 : 낮음, 중간, 높음.

또한 눈의 가장자리를 더 잘 혼합하기 위해 젖은 메시를 흐리게 할 수 있습니다.

  • 눈의 자기 반성. 안구는 반사가 강하기 때문에 속눈썹이나 눈꺼풀도 반사됩니다. 이 부분을 무시하면 좀 이상해지죠.

왼쪽: 자기성찰 없음; 오른쪽 : 속눈썹, 눈꺼풀 등에서 자기반성이 있습니다.

하지만 실시간으로 자기성찰을 계산하는 것은 많은 성능을 소모하게 됩니다. 환경 오클루전 맵을 미리 굽고 렌더링 중에 직접 샘플링할 수 있습니다.

  • 동공, 홍채, 공막 및 기타 부분 간의 전환. 서로 다른 재질에 속하고 고유한 속성을 갖고 있기 때문에 교차점에서 보간 전환이 수행되지 않으면 끔찍한 효과가 발생합니다(아래 오른쪽 그림).

왼쪽: 전환이 사용됩니다. 오른쪽: 전환이 사용되지 않습니다.

전환 곡선은 사인 함수와 유사한 변형을 사용할 수 있습니다.

  • 피와 충혈. Bloodshot은 눈 흰자위 텍스처에 정맥 텍스처 디테일을 추가할 수 있으며, 혈액 색상은 계산 중에 마스크 텍스처로 제어되는 빨간색을 곱하여 시뮬레이션할 수 있습니다.

충혈된 디테일이 있는 안구 텍스처.

  • 섀도우에게 연락하세요. 반투명 재질은 접촉 그림자를 활성화할 수 있습니다. 이 기능은 Light Contact Shadows와 유사한 기능을 사용하지만 Light Contact Shadow 매개변수에는 연결되지 않습니다. 이는 눈이 안와에 단단히 고정되어 있는 것처럼 보이도록 형상을 보완하거나 대체할 수 있는 화면 공간 효과로, 신뢰성을 높입니다.

왼쪽: 컨택트 섀도우가 켜지지 않음; 오른쪽: 컨택트 섀도우가 켜져 있습니다. 전원을 켜면 반사광이 약해집니다.

3.3 Eyeball의 기본 구현

이 섹션에서는 UE의 눈 렌더링 세부 사항을 분석하기 위해 소스 코드 계층에 대해 자세히 살펴보겠습니다. 눈 재질의 셰이딩 모델은 눈(아래 그림)으로 선택해야 하며 눈 셰이딩 모드에는 하위 표면 산란이 활성화되어 있습니다. 즉, 눈 셰이딩 모드는 특수한 하위 표면 프로파일(Subsurface Profile) 셰이딩 모드입니다.

셰이더 레이어에서 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 데이터 초기화와 관련된 코드를 분석합니다.

cpp
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

// 만약 정의 노멀(Normal),의 데이터 처리(Process)。활성화(Enable)노멀(Normal)성능 。
#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
		// 노멀(Normal)의 변환(Transform),생성(Create)。
		// 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에서 Contact Shadow와 관련된 코드를 분석합니다.

hlsl
void GetShadowTerms(FGBufferData GBuffer, FDeferredLightData LightData, float3 WorldPosition, float3 L, float4 LightAttenuation, float Dither, inout FShadowTerms Shadow)
{
	// 기본값(Default)그림자(Shadow)는 0。
	float ContactShadowLength = 0.0f;
	// 그림자(Shadow)스크린 스페이스스케일
	const float ContactShadowLengthScreenScale = View.ClipToView[1][1] * GBuffer.Depth;

	BRANCH
	if (LightData.ShadowedBits)
	{

		// ... (코드 )
		
		// 에 따라 스케일 계산/산출(Calculate)그림자(Shadow)。
		FLATTEN
		if (LightData.ShadowedBits > 1 && LightData.ContactShadowLength > 0)
		{
			ContactShadowLength = LightData.ContactShadowLength * (LightData.ContactShadowLengthInWS ? 1.0f : ContactShadowLengthScreenScale);
		}
	}

#if SUPPORT_CONTACT_SHADOWS
	// 만약 는 또는 셰이딩 ,그림자(Shadow)스케일 까지 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;
		// 계산/산출(Calculate)그림자(Shadow)
		float ContactShadow = ShadowRayCast( WorldPosition + View.PreViewTranslation, L, ContactShadowLength, 8, StepOffset );
		
		Shadow.SurfaceShadow *= ContactShadow;
		
		// 계산/산출(Calculate)투과 그림자(Shadow)
		FLATTEN
		if( GBuffer.ShadingModelID == SHADINGMODELID_HAIR )
			Shadow.TransmissionShadow *= ContactShadow;
		// 만약 는 렌더링(Render),이면 그림자(Shadow),。
		else if( GBuffer.ShadingModelID != SHADINGMODELID_EYE )
			Shadow.TransmissionShadow *= ContactShadow * 0.5 + 0.5;
	}
#endif
}

ShadowProjectionPixelShader.ush에는 그림자 계산과 관련된 로직의 작은 부분도 있습니다.

hlsl
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));
			// 만약 는 또는 렌더링(Render),반투명(Translucent) 및 로 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: LimbusUVWidthColor 및 LimbusUVWidthShading으로 구성된 2D 벡터에서 제공되는 윤부의 UV 너비입니다.

  • DepthScale: 홍채의 깊이 스케일링입니다. 값이 클수록 굴절 효과가 더 분명해집니다. 변수 DepthScale에 의해 제공됩니다.

  • DepthPlaneOffset: 깊이 평면 오프셋. 동공의 크기와 깊이를 결정합니다. Iris UV Radius 및 ScaleByCenter 변수는 UV를 공동으로 계산한 다음 텍스처 T_EyeMidPlaneDisplacement의 R 채널을 샘플링하여 데이터를 제공합니다.

  • MidPlaneDisplacement: 각막 평면에서 동공 평면까지의 깊이 오프셋을 결정하는 미드플레인 오프셋입니다. 동공 주변의 오프셋은 더 작아집니다. 텍스처 T_EyeMidPlaneDisplacement를 직접 샘플링하여 얻습니다. T_EyeMidPlaneDisplacement는 다음과 같습니다:

  • EyeDirectionWorld: 안구 모델의 월드 공간 법선입니다. UseEyeBuldge에 의해 제어되는 두 개의 노멀 맵 T_Eye_N 및 T_Eye_Sphere_N은 샘플링되어 탄젠트 공간에서 월드 공간으로 변환됩니다. 그 중 T_Eye_N은 중앙이 돌출된 안구 구조(아래 그림 왼쪽)인 반면, T_Eye_Sphere_N은 그렇지 않습니다(아래 그림 오른쪽).

  • IrisUVRadius: Iris UV 반경. Iris UV Radius 변수에 의해 직접 제공됩니다.

머티리얼 함수 ML_EyeRefraction의 출력 매개변수:

  • RefractedUV: 굴절된 UV는 재료 함수의 내부 계산 후 출력 UV 결과를 나중에 디퓨즈 및 기타 마스크 맵을 샘플링하는 데 사용할 수 있습니다.

  • 투명도 : 아이리스 색상의 투명도입니다.

  • IrisMask: 홍채의 UV 영역을 식별하는 마스크입니다. 이후 홍채 영역의 관련 착색 처리에 사용됩니다.

위는 ML_EyeRefraction의 입력 및 출력 매개변수만 분석합니다. 다음은 내부 계산 프로세스를 시작합니다.

먼저 굴절 벡터(굴절 방향) 계산을 분석합니다.

cpp
float airIoR = 1.00029;

// 의 굴절 。
float n = airIoR / internalIoR;
// 노멀(Normal) 및 벡터 의 의 스케일
float facing = dot(normalW, cameraW);
// 스케일 의 굴절 。
float w = n * facing;
// 에 따라 n 및 w계산/산출(Calculate) 내에서 。
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 Pupil Subsurface Scattering](#3.2.2 Pupil Subsurface Scattering)의 물리적 기반 굴절과 일치합니다.

굴절 벡터와 굴절 텍스처 오프셋을 사용하면 몇 가지 기본 계산 조정을 통해 최종 출력 매개변수 RefractedUV를 계산할 수 있습니다.

출력 매개변수 IrisMask의 계산은 다음 셰이더 코드로 완료됩니다.

cpp
// 계산/산출(Calculate)Iris(R채널/패스(Pass)) 및 (G채널/패스(Pass))
UV = UV - float2(0.5f, 0.5f);

float2 m, r;
r = (length(UV) - (IrisUVRadius - LimbusUVWidth)) / LimbusUVWidth;
m = saturate(1 - r);
// sine함수 ,출력 및 의 블렌딩(Blending),、 및 。
m = smoothstep(0, 1, m);
return m;

3.4.1.2 동공 스케일링

위 그림에서 볼 수 있듯이 굴절이 켜져 있으면(Refraction On/Off가 true) 이전 섹션에서 계산된 굴절된 UV 좌표가 사용됩니다. 좌표 변환 및 중심 크기 조정 후에는 사용자 정의 셰이더 노드의 입력 매개변수가 됩니다. 입력에는 눈동자 크기를 결정하는 데 사용되는 PupilScale도 포함됩니다. Custom 셰이더 노드의 코드는 다음과 같습니다:

cpp
// 는 UV좌표 텍스처(Texture) 내에서 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를 구하고, [3.4.1.1 안구 굴절](#3.4.1.1 안구 굴절)의 굴절과 [3.4.1.2 동공 확대](#3.4.1.2 동공 확대)의 중심점 스케일링을 통해 텍스처 UV를 계산합니다. 샘플링된 색상은 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)까지 조리개의 깊이를 계산하고 조리개 UV 반경에 대한 비율을 구합니다. Iris Concavity Scale 스케일링 및 Power 조정 후 최종 결과를 얻습니다. (아래 사진)

3.4.1.6 ClearCoatBottomNormal)

위에 표시된 대로 Custom 노드는 [3.4.1.2 Pupil scale](#3.4.1.2 The same as in Pupil Scaling)과 관련되어 있으며 중심점을 따라 UV 스케일링을 계산한 다음 동공 노멀 텍스처 iris08_leftEye_nml을 샘플링합니다. 얻은 결과는 IrisDispStrength에 의해 제어되는 요소에 따라 크기가 조정되고 마지막으로 BlendAngleCorlectedNormals 노드를 통해 안구 표면 노멀과 혼합되고 결과가 Output 노드 ClearCoatBottomNormal에 출력됩니다.

3.4.1.7 안구의 기타 부분

반사성, 러프니스, 탄젠트 등과 같은 안구의 다른 속성은 비교적 간단합니다. 계산 과정은 자료를 직접 보시면 알 수 있으므로 여기서는 분석하지 않겠습니다.

3.4.2 안구 부속물 재료

앞 절에서는 안구의 주요 소재를 분석했다. 그러나 눈의 렌더링에는 각각 독립적인 재질 속성을 갖는 많은 추가 개체도 포함되어 있습니다(아래 그림).

3.4.2.1 눈물샘액

눈물샘 형상은 눈꺼풀(위)을 둘러싸는 메시로, 눈꺼풀(아래)에 스페큘러 반사를 제공하며, 이는 눈물샘에 닿는 빛의 스페큘러 반사를 시뮬레이션하는 데 사용됩니다.

왼쪽: 눈물샘이 없는 형상; 오른쪽: 눈물샘이 있는 형상

해당 재질은 투명 혼합 모드를 사용하여 아래와 같습니다.

색상과 금속성은 기본적으로 1입니다. 높은 반사율과 높은 금속성을 사용하여 강한 스페큘러 반사 효과를 얻는 것을 볼 수 있습니다.

러프니스 계산은 아래와 같이 더 복잡합니다.

텍스처 좌표가 DetailScale_1 변수로 스케일링된 후 디테일 텍스처 skin_h가 샘플링됩니다. 그런 다음 얻은 결과를 DetailAmount, 고정 상수 0.1 및 러프니스 조정으로 조정한 다음 사용자 정의 셰이더 노드 CurveToRoughness 계산에 입력하고 마지막으로 결과를 얻습니다. CurveToRoughness의 셰이더 코드는 다음과 같습니다.

cpp
// 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: 세부 사항을 희생하지 않는 이미지 안정성](http://advances.realtimerendering.com/s2012/Ubisoft/Rock-Solid Shading.pdf)에 자세히 설명되어 있습니다.

일반적인 계산은 비교적 간단합니다. 노멀 맵 skin_n을 샘플링하고 DetailAmount 변수를 조정한 후 최종 결과를 얻습니다.

게다가 'DepthOffset' 변수로 제어되는 월드 좌표 오프셋도 추가하고, 카메라 공간 오프셋은 머티리얼 함수 CameraOffset을 통해 구합니다.

3.4.2.2 흐릿한 몸체 마스킹

마스킹 블러 본체는 눈물샘 액체와 유사합니다. 눈의 모서리를 둘러싸고 빛의 일부를 차단하고 흐리게 하는 데 사용되어 주변 혼합을 더욱 사실적으로 만듭니다(아래 그림).

왼쪽: 폐색되지 않은 흐릿한 몸체; 오른쪽: 가려져 흐릿한 몸체

해당 머티리얼은 투명 블렌딩 모드를 사용하고 조명 모델은 Unlit입니다. 그 개요는 다음과 같습니다.

분석은 다음과 같은 부분으로 나눌 수 있습니다.

  • 불투명도:

주로 마스킹과 블러링이 필요한 영역 마스크를 생성하는 부분입니다. 프로세스는 대략적으로 초기 마스크 이미지 샘플링, 텍스처 선형 전환 추가, 반전, 전력 연산 조정 추가, 눈 주변 마스크를 얻기 위해 여러 변수 제어 요소를 사용하여 기본 연산을 수행하는 방식으로 이루어집니다(아래 그림).

  • 흐림:

현재 UV 주변의 16개 장면 색상을 샘플링하고 평균을 계산합니다. 여기의 장면 색상은 안구가 렌더링된 후의 색상이어야 합니다. 안구는 불투명한 객체이기 때문에 투명한 마스킹 블러 바디 이전에 그려지는 것이 보장됩니다.

  • 색상:

원래 색상의 출력은 간단합니다. 위에서 계산한 마스크를 사용하여 흰색과 흐림 색상 사이를 보간한 다음 이를 위의 흐릿한 장면 색상과 곱합니다.

  • 그림자:

위 그림과 같이 UV의 상하좌우 선형 기울기를 통해 4개의 서로 다른 값을 구하고 조정한 후 이를 곱하여 주변의 검은색을 얻고, 최종적으로 변수를 통해 1.0 사이를 보간하여 상단이 어두운 그림자 맵을 얻습니다.

  • 종합 계산:

이 단계에서는 위의 여러 계산 결과를 사용하여 색상과 그림자를 곱하고 알파를 미리 곱하여 최종 색상과 불투명도를 얻습니다.

또한 위치 오프셋 계산도 있는데 여기서는 무시됩니다.

3.4.2.3 아이믹스

눈 혼합은 눈꼬리 부분에 혈색과 핏빛 디테일을 추가하고, 밝기를 조절하여 눈 흰자 사이의 전환을 더욱 자연스럽게 만듭니다(아래 그림).

왼쪽: 안각이 없는 혼합물; 오른쪽: 칸서스와의 혼합물

해당 머티리얼에는 서브서피스 스캐터링(SSS)이 활성화되어 있으며 블렌딩 모드는 마스크됨입니다. 자료개요는 다음과 같습니다.

분석을 위해 여러 부분으로 나누어 보겠습니다.

  • 그라데이션 마스크:

UV 가로좌표를 사용하여 선형 전환을 얻고 Power를 사용하여 강도를 조정한 다음 SmoothStep을 사용하여 부드러운 전환 마스크를 얻습니다.

  • 색상:

여러 변수를 통해 UV 좌표를 늘리고 눈 맵 eye_sclera_right_clr을 샘플링하여 늘어난 색상을 얻습니다. 눈 흰자 밝기를 조정하고, 앞선 절에서 계산한 마스크에 의해 결정된 혈색에 눈 흰자를 조정한 후 최종 색상을 얻는다. 눈의 모서리가 더 붉어져 혈색이 더 많이 나타나며, 동공 근처 부분은 덜 영향을 받습니다.

  • 보통:

법선은 주로 위에서 계산된 마스크를 벡터 [0, 0, 1]과 [-1, 0, 0] 사이에 보간하여 얻습니다.

3.4.2.4 속눈썹 및 눈썹

속눈썹과 눈썹의 재질은 헤어 셰이딩 모드에 속하므로 눈의 일부이기는 하지만 실제로는 헤어 렌더링의 범위에 속합니다. 이에 대해서는 다음 장에서 자세히 설명하겠습니다.

3.5 안구 렌더링 요약

위에서 볼 수 있듯이 눈의 렌더링 기술은 피부 렌더링만큼 진보적이고 체계적이지는 않지만 많은 부분과 세부 사항이 포함되기 때문에 서로 맞물리고 각 재료가 서로 보완하여 완전하고 사실적인 눈 렌더링 시스템을 형성한다는 것을 알 수 있습니다.

이 장의 마지막 부분에서는 공식 문서의 조언을 인용합니다.

디지털 휴먼 캐릭터를 개발할 때 우리는 모델에 몇 가지 다른 방법과 재료를 사용하여 캐릭터 눈의 사실성을 향상시켰습니다. 위에서 언급한 것처럼 많은 눈 설정은 수집된 자료 설정 및 참조 자료와 상호 의존성을 갖습니다. 시작점으로 눈 설정을 사용하는 것이 좋습니다.

사실적인 눈 자원(모델, 텍스처, 재질 등)을 처음부터 생성하는 것은 여전히 ​​상당히 어렵다는 것을 알 수 있습니다. 다행스럽게도, 관대한 언리얼 엔진 관계자가 개인 및 팀 연구 개발을 위한 충분한 예제와 리소스를 제공하여 학습 및 개발 주기가 크게 단축되었습니다.

이 기사 시리즈의 다른 부분

  • 언리얼 엔진의 초현실적 휴먼 렌더링 기술 분석 1부 - 개요 및 피부 렌더링

  • 언리얼 엔진의 초현실적 휴먼 렌더링 기술 분석 3부 - 헤어 렌더링 등

특별 지침

  • 참고문헌을 작성해주신 모든 분들께 감사드립니다!

  • 헤어렌더링 등 추가 부분이 추가될 예정이니 지켜봐주세요!








📚 참고 자료 및 공식 링크 (References)


🔗 연관 지식 베이스 (Wiki & Tools)

Based on the legendary technical series by Timlly (0向往0 / 毛星云)