역자의 말
캐릭터에게 마지막으로 생명을 불어넣는 작업은 눈동자에 있다고 단언컨데 말할 수 있습니다. 오랜 시간 역자 역시도 눈동자 표현과 그 주변 표현에 대한 관심을 갖고 있었고 구현을 해 왔는데요... 최근 5년간은 모바일 게임만 만들었기 때문에 이렇게 세부적인 분석에 도달 할 기회가 없었습니다. 그래픽스 프로그래머인 장 선생의 세부적인 분석 글을 보면 아티스트보다 더 뛰어난 관찰력을 갖고 있음이 느껴집니다. 중국에서는 전문가 집단이라는게 있고 많은 엔지니어와 미술가 들이 전문가 등급이 되기 위해 많은 노력을 하는데요... 이 기사 전문에서도 그런 부분들이 느껴집니다.
저자
Timlly-Chang
이 전에 동 저자의 멋진 언리얼 엔진 모바일 해부 분석글을 읽으신 적이 있습니다.
최근에 알았지만 넷이즈에서 같이 근무 했었군요. ㅎㅎ
https://techartnomad.tistory.com/185
III. 안구 렌더링
눈은 인간 영혼의 창이라고 하는데, 눈을 사실적으로 렌더링하면 가상 캐릭터에 생동감을 불어넣어 생동감 넘치는 영혼을 부여할 수 있습니다.
마이크의 깊은 눈빛, 한숨을 쉬는 콧수염, 끔찍한 눈빛. 많은 팬들을 정복했어야 했어요.
슈퍼 클로즈업 한 번 더:
초근접 거리의 눈 클로즈업은 실제처럼 보일 정도로 디테일이 뛰어납니다.
하지만 이렇게 사실적이고 표현력이 풍부한 눈을 렌더링하는 것은 그리 간단한 일이 아니며, 수많은 과정과 다양한 렌더링 기법, 그리고 많은 디테일을 조작해야 합니다.
3.1 눈의 구조와 이론
3.1.1 눈의 구조
눈의 생물학적 해부학적 구조는 매우 복잡하며 수십 개의 부분으로 이루어져 있습니다. (아래 그림)
수십 개의 부위를 포함하는 인간 눈의 생물학적 프로파일.
그래픽 렌더링 분야에서는 당연히 많은 디테일에 집중하는 것은 불가능하며, 눈 구조를 단순화하여 일부에만 집중하는 것이 가능합니다:
부품은 위에 표시된 일련 번호로 표시됩니다:
- 1 - 공막: "눈의 흰자위"라고도 하며, 보통 매우 촉촉하고 소량의 촉감, 혈액 및 기타 세부 사항을 포함하고 있습니다.
- 2 - 각막 윤부: 각막 윤부는 홍채와 공막 사이에 어두운 색의 고리 모양으로 존재합니다. 각막 융기는 일부 눈에서 더 뚜렷하게 나타나며 측면에서 볼 때 희미해지는 경향이 있습니다.
- 3 - 홍채(아이리스): 홍채는 눈의 중앙에 있는 색의 고리입니다. 눈이 "녹색"인 사람은 홍채가 주로 녹색이기 때문입니다. 실제 눈에서 홍채는 동공에 더 많은 빛을 받아들이거나 동공에서 빛을 차단하기 위해 확장 및 수축하는 섬유질 근육과 같은 구조입니다. 또한 실제 세계에서 홍채는 실제로 원반이나 원뿔에 가깝고 눈의 나머지 부분을 향해 돌출되어 있지 않다는 점도 중요합니다.
- 4 - 동공(눈동자): 동공은 눈의 중앙에 있는 검은 점입니다. 빛이 망막의 시신경과 원추체에 맺히기 전에 통과하는 구멍입니다.
- 5 - 각막: 각막은 홍채 표면에 위치한 투명하고 액체로 채워진 돔형 구조물입니다.
3.1.2 눈의 렌더링 이론
안구는 액체로 채워져 있기 때문에 들어오는 모든 빛을 굴절시킵니다. 이 효과는 안구를 여러 각도에서 볼 때 실제 세계에서 볼 수 있습니다. 홍채와 동공은 각막을 통해 보이기 때문에 굴절에 의해 왜곡됩니다.
이 문제를 해결하기 위해 게임과 영화에서 사용되는 전통적인 방법은 공막, 홍채, 동공을 제공하는 한 층과 각막과 눈의 전반적인 습기를 제공하는 두 번째 층으로 구성된 두 개의 분리된 눈 표면을 만드는 것입니다. 이렇게 하면 젖은 층을 통해 아래쪽 표면을 볼 때 굴절이 발생합니다.
'소년과 연'에 등장하는 소년의 눈은 2레이어 서피스 렌더링을 사용하여 모델링되었습니다.
위의 분석과 단순화된 눈의 해부학적 구조를 바탕으로 눈을 잘 표현하기 위해 집중해야 할 효과는 다음과 같다는 결론을 내릴 수 있습니다:
- 각막의 반투명하고 광택이 나는 반사 효과.
- 동공의 표면 아래 산란.
- 동공 확대/축소. 전체 장면의 빛의 강도에 따라 줌 크기를 동적으로 조정하는 것이 가장 좋습니다.
- 홍채 색상 변경.
- 기타 눈 디테일.
이에 대해서는 다음 섹션에서 자세히 설명합니다.
3.2 눈을 위한 렌더링 기법
이 섹션의 주요 참고 자료
- 각색 염색 기술-모발 및 기타。
- 차세대-캐릭터-렌더링。
3.2.1 각막의 반투과성 및 광택 반사
각막의 반투과 및 반사 효과는 눈의 렌더링을 가장 잘 반영합니다.
이를 수행하는 간단한 방법은 각막을 반투명 광택 구의 반사라고 직접 생각하면 됩니다. 일반적인 방법은 PBR 프로세스를 사용하여 각막의 스페큘러 및 IBL 반사를 계산한 다음 각막 아래에 굴절 효과 역할을 하는 홍채 및 눈 백색 맵을 안구에 부여하고 마지막으로 각막에 블렌딩 인수를 설정하여 광택 구 반사 효과와 홍채 및 눈 백색 맵의 색상을 혼합하는 것입니다.
각막의 스페큘러 및 주변 반사가 눈의 디테일을 풍부하게 하고 사실감과 사실감을 더합니다.
3.2.2 동공의 표면 아래 산란
동공 자체는 실제로 각막에서 멀리 떨어져 있는 고르지 않은 세로 구조입니다. 이로 인해 동공이 굴절되고 빛이 동공 표면에 도달하면 동공 구조 내에서 추가적인 표면 산란이 발생합니다.
눈을 두 층으로 이루어진 구조로 생각하면 바깥층은 각막, 안쪽 층은 동공 표면이며 각막과 동공 사이는 일종의 투명한 액체로 채워져 있다고 생각할 수 있습니다.
빛은 동공 조직 내부로 들어가기 전에 먼저 각막 표면에서 한 번 굴절된 후 동공 조직 내부로 들어가 산란되고 마지막으로 동공 표면의 다른 지점에서 산란됩니다. 여기에는 두 가지 문제가 관련되어 있습니다:
(1) 각막 표면으로 향하는 광선이 굴절된 후 동공 표면에 입사하는 최종 입사각을 계산하는 방법;
(2) 각막 내부로 들어오는 광선의 산란 효과를 계산하는 방법.
위의 두 가지 문제를 해결하기 위해 두께가 고르지 않은 여러 레이어가 있는 머티리얼의 서브서피스 스캐터링 효과 계산을 해결하도록 설계된 서브서피스 텍스처 매핑를 사용할 수 있습니다.
위와 같이 각 머티리얼 레이어에는 채널에 저장된 별도의 뎁스 맵이 있으며, 각 개별 머티리얼 레이어는 동일한 산란 및 흡수 계수와 해당 위상 함수(산란 관련 파라미터)로 동질적인 것으로 간주됩니다. 그런 다음 시선과 첫 번째 재료 층의 교차점에서 시작하여 시선 방향을 따라 다층 재료가 레이 마칭되고, 각 추가 라인은 위치 및 깊이 맵을 기반으로 현재 지점이 재료의 어느 층에 위치하고 어떤 산란 파라미터에 해당하는지 계산한 다음 이전 단계의 위치와 레이 마칭이 끝날 때까지 빛의 방향을 기반으로 산란 및 흡수를 계산합니다. 특히 눈 산란 계산의 경우 실제로 산란 머티리얼은 동공 머티리얼이라는 하나의 레이어만 존재합니다. 따라서 동공 표면의 뎁스 맵만 제공하고 동공 머티리얼의 관련 산란 파라미터를 설정한 다음 계산을 서브서피스 텍스처 매핑 방법과 결합하기만 하면 됩니다.
이 섹션에서는 주로 렌더링 기술을 다룹니다:
- 패럴랙스 매핑(릴리프 매핑이라고도 함). 시차 매핑은 레이 마칭을 통해 깊이 맵과 결합하여 비교적 평평한 기하학적 표면에서 시각적으로 정확한 릴리프를 얻을 수 있습니다. 일반 효과도 평평한 표면에서 릴리프를 만들 수 있지만 평평한 표면은 더 비스듬한 뷰에서는 여전히 평평한 표면이며, 시차 매핑에서는 그렇지 않습니다.
- 왼쪽: 노멀 매핑 효과, 오른쪽: 패럴랙스 매핑 효과. 기울어진 시야각에서는 후자의 효과가 훨씬 더 좋다는 것을 알 수 있습니다.
- 물리 기반 굴절. 기만적으로 계산되는 시차 매핑과 달리 물리 기반 굴절은 실제 굴절 모델을 기반으로 시뮬레이션하여 더욱 사실적인 효과를 구현합니다.
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 기타 눈 세부 정보
눈알의 디테일이 사실감을 더하고 이미지를 한 단계 업그레이드합니다.
- 평탄하지 않은 반사. 실제 눈의 흰자위는 완전히 거울처럼 평평하지 않고 어느 정도 요철이 있으며, 사인 유사 함수를 사용하여 노멀 맵을 교란하여 시뮬레이션할 수 있습니다.
습도. 대부분의 사람의 눈에는 다양한 정도의 습윤도가 있는 눈물이 있습니다. 이 효과는 투명한 메쉬 레이어를 생성하여 시뮬레이션할 수 있습니다.
다양한 습도 수준에 맞는 그리드 모델
시뮬레이션 결과는 다음과 같습니다:
왼쪽에서 오른쪽으로 눈의 수분 수준: 낮음, 중간, 높음.
또한 젖은 메쉬를 흐리게 처리하여 눈의 가장자리를 더 잘 블렌딩할 수 있습니다:
눈의 특정 반사가 강하기 때문에 이미 속눈썹과 눈꺼풀이 반사되어 이 부분을 무시하면 약간 이상하게 보일 것입니다.
왼쪽: 자체 반사 없음; 오른쪽: 속눈썹, 눈꺼풀 등으로 자체 반사됨.
그러나 셀프 리플렉션을 실시간으로 계산하면 성능이 더 많이 소모되므로 환경 오클루전 맵을 미리 베이크하고 렌더링 중에 직접 샘플링할 수 있습니다:
동공, 홍채, 공막 등의 전환. 이들은 고유한 속성을 가진 서로 다른 머티리얼이므로 교차점이 보간되지 않으면 전환이 끔찍하게 나타납니다(오른쪽 아래).
왼쪽: 전환 사용, 오른쪽: 전환 미사용.
전환 커브는 사인 함수와 유사한 변형으로 사용할 수 있습니다:
혈색 및 혈우. 혈관 텍스처 디테일로 눈 흰자위 텍스처에 혈관을 추가할 수 있으며, 계산 중에 마스크 텍스처 시트로 제어되는 붉은 색을 곱하여 혈액 색을 시뮬레이션할 수 있습니다.
- 핏자국 디테일이 있는 눈동자 텍스처.
- 컨택트 섀도. 반투명 머티리얼은 컨택트 섀도를 활성화할 수 있습니다. 이 기능은 광원 접촉 그림자와 유사한 기능을 사용하지만 광원 접촉 그림자 파라미터와 연결되지는 않습니다. 지오메트리와 함께 또는 지오메트리 대신 사용할 수 있는 스크린 스페이스 효과로, 눈이 소켓에서 단단히 자라나는 것처럼 보이게 하여 사실감을 높입니다.
3.3 눈의 기본 구현
이 섹션에서는 소스 코드 레이어를 자세히 살펴보고 UE의 눈 렌더링에 대한 세부 사항을 분석해 보겠습니다. 눈 머티리얼의 셰이딩 모델은 눈(아래)으로 선택되어 있고 눈 셰이딩 모드에는 서브서피스 스캐터링이 활성화되어 있습니다(즉, 눈 셰이딩 모드는 특수한 서브서피스 프로파일 셰이딩 모드입니다).
셰이더 계층에서 눈의 조명 모델은 일반적인 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 안구의 재질
이 섹션에서는 마이크의 안구 주재료와 부속 재료를 분석합니다.
3.4.1 눈 주요 소재
눈 메인 머티리얼은 M_EyeRefractive이고, 다음 그림은 눈 메인 머티리얼의 개요이며, 노드 레이아웃이 약간 지저분합니다(UE 머티리얼 에디터는 자동 레이아웃 기능을 제공하지 않습니다). 다음 하위 섹션에서는 눈 머티리얼의 중요하거나 주요 알고리즘 프로세스를 분석하는 데 초점을 맞출 것이며, 다른 작은 세부 사항은 무시하겠습니다.
3.4.1.1 눈의 굴절
위 그림에서 볼 수 있듯이 눈의 굴절은 주로 머티리얼 함수 ML_EyeRefraction에 의해 이루어지며, 그 입력 및 출력 파라미터는 아래에서 분석할 것입니다.
머티리얼 함수 ML_EyeRefraction의 입력 파라미터입니다:
- 내부굴절률: 빛이 홍채로 들어올 때 굴절률을 시뮬레이션하는 데 사용되는 눈의 내부 굴절률로, 일반적으로 [< 1.0< ">1.0,1.4], 굴절이 클수록 더 뚜렷한 효과를 나타냅니다. IoR 변수에 의해 직접 제공됩니다.
- ScaleByCenter: 눈의 눈금 크기(눈의 흰자위, 동공, 홍채 등 포함). ScaleByCenter 변수에 의해 직접 제공됩니다.
- LimbusUVWidth: 각막 테두리의 UV 폭으로, LimbusUVWidthColor 및 LimbusUVWidthShading으로 구성된 2D 벡터로 제공됩니다.
- DepthScale: 조리개의 깊이 스케일입니다. 값이 클수록 굴절 효과가 더 뚜렷해집니다. DepthScale 변수에 의해 제공됩니다.
- DepthPlaneOffset: 깊이 평면 오프셋. 동공의 크기와 깊이를 결정합니다. UV는 Iris UV Radius와 ScaleByCenter 변수를 함께 계산한 다음 샘플 매핑의 R 채널인 T_EyeMidPlaneDisplacement에서 데이터를 제공합니다.
- MidPlaneDisplacement: 각막 평면에서 동공 평면까지의 깊이 오프셋을 결정하는 중간 평면 변위로, 동공 주변 오프셋이 더 작아집니다. 매핑 T_EyeMidPlaneDisplacement를 직접 샘플링하여 다음과 같이 T_EyeMidPlaneDisplacement를 얻습니다:
눈 방향 월드: 눈 모델의 월드 스페이스에 대해 일반입니다. UseEyeBuldge제어용 노멀 맵 2개 T_Eye_N및T_Eye_Sphere_N탄젠트 공간에서 월드 공간으로 변환하여 샘플링하고 얻었습니다. 여기서 T_Eye_N는 중앙에 돌출된 눈 구조(아래, 왼쪽)이고, T_Eye_Sphere_N하지 않음(오른쪽 아래):
- 홍채 자외선 반경: 홍채 자외선 반경 변수가 직접 제공하는 홍채 자외선 반경입니다.
머티리얼 함수 ML_EyeRefraction의 출력 파라미터입니다:
- 굴절 UV: 굴절 UV는 머티리얼 함수의 내부 계산 후 출력 UV 결과를 나중에 샘플링 확산 반사 및 기타 마스크 매핑에 사용할 수 있습니다.
- 투명도: 홍채 색상 투명도.
- 홍채 마스크: 홍채의 UV 영역을 식별하는 마스크입니다. 이후 홍채 영역의 관련 색상에 사용됩니다.
위에서는 ML_EyeRefraction의 입력 및 출력 파라미터를 분석했으며, 다음은 내부 계산 프로세스에 대해 설명합니다:
먼저 굴절 방향(RD)의 계산이 분석됩니다:
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;
굴절 UV 오프셋 계산이 다시 분석됩니다:
위 그림에서 볼 수 있듯이 오른쪽의 빨간색 상자로 식별되는 굴절 텍스처 오프셋에는 여러 좌표 연산과 각도 계산뿐만 아니라 수많은 입력 파라미터가 필요합니다. 프로세스는 더 복잡하지만 원리는 [3.2.2 동공의 표면 아래 산란]의 물리 기반 굴절(#3.2.2 동공의 표면 아래 산란)과 일치합니다.
굴절 벡터와 굴절 텍스처 오프셋을 사용하여 몇 가지 기본 연산으로 조정하여 최종 출력 파라미터 RefractedUV를 계산할 수 있습니다.
출력 파라미터 IrisMask의 계산은 다음 셰이더 코드를 통해 이루어집니다:
// 计算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 동공 크기 제어
위 그림에서 볼 수 있듯이 굴절이 켜져 있으면(굴절 켜짐/꺼짐이 참) 이전 하위 섹션에서 계산된 굴절된 UV 좌표가 사용되며, 좌표 변환 및 중심 스케일링 이후에는 Custom 셰이더 노드의 입력 파라미터가 되며, 동공 크기를 결정하는 입력으로 PupilScale도 있습니다.custom의 코드입니다. 셰이더 노드 코드는 다음과 같습니다:
// 主要是将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 눈동자 색상 혼합
두 가지 주요 눈동자 색상이 제공됩니다:
- 눈 흰자위 색(공막 색): T_EyeScleraBaseColor를 샘플링하여 얻은 후 ScleraBrightness 변수에 의해 스케일링합니다. 샘플링된 UV는 굴절되지 않으며 중심점을 기준으로만 스케일링됩니다.
- 홍채 색상: 색상은 T_EyeIrisBaseColor를 샘플링하여 얻고 텍스처 UV는 [3.4.1.1 눈의 굴절](#3.4.1.1 눈의 굴절)의 굴절 계산과 [3.4.1.2 동공 스케일](#3.4.1.2 동공 스케일)의 동심도 스케일로 스케일링됩니다. 샘플링에서 얻은 색상은 홍채밝기 및 각막 테두리(림버스)와 관련된 매개변수에 의해 스케일링됩니다.
위의 두 색상은 ML_EyeRefraction의 IrisMask 출력에 의해 보간되고 홍채 색상(CloudyIris)을 추가한 후 최종적으로 Base Color 핀에 출력됩니다.
3.4.1.4 눈의 법선 라인
눈 노멀의 UV는 중심점을 기준으로 스케일을 조정한 다음 노멀 맵 T_Eye_Wet_N을 사용하고, 결과 노멀은 머티리얼 함수 FlattenNormal과 스케일 인수를 통해 노멀 강도를 조정하여 최종적으로 노멀 핀에 출력됩니다.
여기서 FlattenNormal의 강도는 홍채 영역에 대한 ML_EyeRefraction의 IrisMask 출력 [FlattenNormal,1.0][𝐹𝑙𝑎𝑡𝑡𝑒𝑛𝑁]로 표시됩니다. 𝑜𝑟𝑚𝑎𝑙,1.0]을 보간합니다. 홍채 영역의 경우 노멀의 영향을 받지 않아 완전히 부드럽습니다.
3.4.1.5 홍채의 마스킹 및 깊이
홍채 마스크는 ML_EyeRefraction이 출력하는 IrisMask에서 직접 얻습니다.
홍채의 깊이는 굴절된 텍스처 UV를 원의 중심으로부터의 길이(0.5, 0.5)로 계산하여 홍채 UV 반경에 대한 비율을 구한 다음 홍채 오목 스케일로 스케일을 조정하고 파워로 조정하여 최종 결과를 얻습니다. (아래)
3.4.1.6 바니시 하단 법선
위와 같이 커스텀 노드는 [#3.4.1.2 동공 스케일 조정하기]에서와 같이 중심을 따라 UV 스케일을 계산한 다음 동공 노멀 텍스처 iris08_leftEye_nml의 디샘플링을 진행하여 얻은 결과를 IrisDispStrength로 제어되는 계수에 따라 스케일을 조정하고 최종적으로 노드 BlendAngleCorrectedNormals 노드를 거쳐 눈 표면 노멀과 블렌딩하고 그 결과를 출력 노드 ClearCoatBottomNormal에 출력합니다.
3.4.1.7 눈의 다른 부위
반사도, 거칠기, 탄젠시 등과 같은 눈의 다른 속성은 비교적 간단하고 머티리얼을 직접 보면 계산 과정을 이해할 수 있으므로 여기서는 분석하지 않습니다.
3.4.2 안구 부속물 머티리얼
눈의 주요 머티리얼은 이전 하위 섹션에서 분석했지만, 눈의 렌더링에는 각각 별도의 머티리얼 속성을 가진 여러 개의 추가 오브젝트도 포함되어 있습니다(아래 참조).
3.4.2.1 눈물샘액
눈물샘 지오메트리는 눈꺼풀 주변(위)을 둘러싸는 메시로, 눈꺼풀(아래)에서 높은 빛 반사를 제공하여 눈물샘에 닿는 빛의 스페큘러 반사를 시뮬레이션하는 데 사용됩니다.
왼쪽: 눈물샘이 없는 지오메트리, 오른쪽: 눈물샘이 있는 지오메트리
투명도 블렌드 모드를 사용하여 아래 표시된 재질로 만들어졌습니다:
기본적으로 색상과 금속성은 모두 1이므로 높은 반사율과 높은 금속성을 사용하여 매우 강력한 스페큘러 반사 효과를 얻는 것을 볼 수 있습니다.
아래와 같이 거칠기 계산이 더 복잡합니다:
텍스처 좌표는 DetailScale_1 변수로 스케일링한 다음 디테일 텍스처 skin_h를 디샘플링하고 얻은 결과를 DetailAmount로 스케일링하고 상수 0.1을 고정하고 Roughness로 조정한 다음 사용자 정의 셰이더 노드 CurveToRoughness에 입력하면 최종 결과를 얻을 수 있습니다. CurveToRoughness의 셰이더 코드는 다음과 같습니다:
// 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)에 자세한 설명이 나와 있습니다.
노멀 계산은 비교적 간단한데, 변수 DetailAmount를 조정한 후 노멀 맵 skin_n을 샘플링하면 최종 결과를 얻을 수 있습니다.
또한 월드 좌표 오프셋을 추가하는데, 이 오프셋은 카메라 공간에서 오프셋을 얻기 위해 머티리얼 함수 CameraOffset을 거치는 변수 'DepthOffset'으로 제어됩니다.
3.4.2.2 퍼지 바디 마스킹
마스킹 블러는 눈물과 유사하며 눈 주변을 둘러싸고 빛의 일부를 차단하고 흐리게 하여 주변부를 더욱 사실적으로 표현하는 데 사용됩니다(아래).
왼쪽: 언마스크드 블러, 오른쪽: 마스크드 블러
머티리얼은 투명 블렌드 모드이고 조명 모델은 조명 해제입니다. 개요는 아래와 같습니다:
다음 섹션에서 분석할 수 있습니다:
- 불투명도:
- 이 단계에서는 마스킹 및 블러 처리할 영역의 마스크를 생성하는 데 중점을 둡니다. 이 과정은 초기 마스크 맵을 샘플링하여 눈 주변 마스크(아래)를 얻는 것과 거의 동일하며, 텍스처 선형 전환, 반전, 전원 작동 조정 및 기본 작업을 위한 여러 변수 제어 요소를 추가합니다.
- 흐리게:
- 현재 UV 주변의 씬 컬러 16개를 샘플링하여 평균을 구합니다. 여기서 씬 컬러는 눈알이 투명하지 않은 오브젝트이고 투명 오클루전 블러 전에 그려지도록 보장되므로 이미 렌더링된 눈알의 컬러여야 합니다.
- 색상:
- 원시 색상의 출력은 위에서 계산한 마스크를 사용하여 흰색과 흐림 색상 사이에 보간한 다음 위의 흐린 장면 색상을 곱하는 간단한 방식입니다.
- 그림자:
위와 같이 선형 그라데이션과 UV의 상하좌우 조정을 통해 4가지 값을 얻고, 이를 곱하여 주변 검정색을 얻은 다음 마지막으로 1.0 사이의 변수로 보간하여 상단이 더 어두운 색으로 된 그림자 맵을 얻습니다.
- 종합적인 계산:
- 이 단계에서는 위의 여러 계산 결과를 사용하여 색상과 그림자를 곱하고 알파를 미리 곱하여 최종 색상과 불투명도를 얻습니다.
위치 오프셋 계산도 있지만 여기서는 무시하겠습니다.
3.4.2.3 안과용 혼합물
아이 블렌드는 눈꼬리에 핏빛과 혈색 디테일을 더하고 밝기를 조정하여 눈 흰자위로의 전환을 더욱 자연스럽게 만듭니다(아래).
왼쪽: 눈꼬리가 없는 하이브리드, 오른쪽: 눈꼬리가 있는 하이브리드
이 머티리얼은 서브서피스 스캐터링이 활성화되어 있고 블렌드 모드가 마스킹으로 설정되어 있으며, 머티리얼 개요는 아래와 같습니다:
이를 섹션별로 분류하여 아래에서 분석합니다:
- 그라데이션 마스크:
- UV의 수평 좌표를 사용하여 선형 전환을 얻고, 파워로 강도를 조정한 다음 SmoothStep을 사용하여 부드러운 전환의 마스크 맵을 얻습니다.
- 색상:
- UV 좌표는 여러 변수를 통해 확장되어 눈 매핑 eye_sclera_right_clr의 샘플을 제거하여 확장된 색상을 얻고, 이전 섹션에서 계산된 마스크에 따라 눈 백색 밝기 조정과 눈 백색에서 혈색으로 조정한 후 최종 색상을 얻습니다. 눈꼬리가 붉어지고 혈색이 더 많이 나타나는 반면 동공 근처의 영역은 영향을 덜 받습니다.
- 일반 라인:
- 노멀은 주로 위에서 계산한 마스크를 [0, 0, 1] 및 [-1, 0, 0] 벡터에서 보간하여 얻습니다.
3.4.2.4 속눈썹 및 눈썹
속눈썹과 눈썹의 머티리얼은 헤어 셰이딩 모드에 속하기 때문에 눈의 필수적인 부분이지만 실제로는 다음 장에서 자세히 설명할 헤어 렌더링의 영역에 속합니다.
3.5 아이볼 렌더링 요약
위에서 볼 수 있듯이 눈 렌더링 기술은 스킨 렌더링만큼 깊고 체계적이지는 않지만 많은 부분과 디테일이 관련되어 있고 다양한 머티리얼 간의 상호 연동 및 보완으로 인해 완벽하고 사실적인 눈 렌더링 시스템을 형성한다는 것을 알 수 있습니다.
이 장은 공식 문서를 인용하는 것을 권장하는 것으로 마무리합니다:
디지털 휴먼 캐릭터를 개발할 때 캐릭터의 눈의 사실감을 높이기 위해 모델에 다양한 방법과 재료를 사용했습니다. 위에서 언급했듯이 많은 눈 설정과 캡처한 머티리얼 설정 및 레퍼런스 간에는 상호 의존성이 있습니다. 유니티의 눈 설정을 시작점으로 사용하는 것을 적극 권장합니다.
보시다시피 사실적인 이미지 눈을 위한 리소스(모델, 텍스처, 머티리얼 등)를 처음부터 완전히 새로 제작하는 것은 여전히 매우 어렵습니다. 다행히도 관대한 언리얼 엔진 관계자는 개인과 팀의 연구 개발을 위한 충분한 예제와 리소스를 제공하여 학습 및 개발 주기를 크게 단축했습니다.
이 시리즈의 나머지 글
- 언리얼 엔진의 초현실적인 휴먼 렌더링 기법 해부하기 1부 - 개요 및 스킨 렌더링
- 언리얼 엔진의 초현실적인 휴먼 렌더링 기법 해부 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
- 眼睛建模逐步剖析教学-超详细
원문
https://www.cnblogs.com/timlly/p/11144950.html
엮어 보기.
역자의 유니티용 간단한 눈동자 구현 기사.
https://leegoonz.blog/2020/01/07/development-of-three-dimensional-pupil-shader/
'TECH.ART.FLOW.IO' 카테고리의 다른 글
[번역] 【UE5.4】Slate Post-buffer를 사용해보자. (0) | 2024.06.25 |
---|---|
[번역] 언리얼 엔진 RDG 소스 코드 분석 (0) | 2024.06.10 |
[번역] 《명조》언리얼 엔진 4를 기반으로 한 멀티 플랫폼 이펙트 및 퍼포먼스 최적화 사례. (0) | 2024.06.04 |
[번역]캐릭터 카툰 렌더링(디퍼드 라이팅) 참고 사항 (1) | 2024.06.03 |
[번역] Enemy AI in Unity (0) | 2024.05.26 |