眼睛构造

生物学眼睛的构造是很复杂的,包含十多种部位,但在渲染中只需要了解六个部位即可:

  • Sclera(巩膜):眼球表层的白色纤维膜,坚韧耐压,有支撑和保护眼球内部组织的作用(眼睛的白色部分)
  • Limbus(角膜缘):角膜同巩膜之间的一条灰白色过渡带,是角膜与巩膜的移行区,角膜镶嵌在巩膜而逐渐过渡到巩膜组织内。其宽度也不一致,上缘较宽,下缘次之,水平缘更窄
  • Cornea(角膜):人和某些动物眼球前方最外面的一层纤维膜。无色透明,没有血管,反应灵敏(折射源头)
  • Iris(虹膜/虹彩):眼球前部角膜和晶状体之间含色素的环形薄膜。膜的中间是瞳孔(眼睛的彩色部分)
  • Pupil(瞳孔):眼球虹膜中央进光的圆孔,可以随光线的强弱而缩小或扩大(虹膜内的黑色部分)
  • Aqueous Humor(眼房水):眼球晶状体和角膜之间的透明液体,由睫状体的无色素上皮细胞分泌

1732198985156.png

渲染方案

传统方法是创建两层独立的眼睛表面,一层提供巩膜、虹膜和瞳孔,另一层位于顶部,提供角膜和眼睛的总体湿润度。这样底层表面透过湿润层观看时就会产生折射

简单来说,要想得到效果不错的眼睛,需着重关注:

  • 角膜的半透和高光反射
  • 瞳孔的次表面散射
  • 瞳孔的缩放(高光强度)
  • 虹膜的颜色变化(高光颜色)

Cornea

这里需要实现角膜的半透明和高光反射效果,因此需要计算高光镜面反射、IBL反射,并在basecolor中加入虹膜贴图以实现角膜下面的折射效果

1732202571882.png

Pupil

次表面散射

把眼球看成双层结构,外层是角膜内层是瞳孔的表面,角膜和瞳孔间可以认为充斥着透明液体。又因为瞳孔和角膜间有一定的距离,因此会产生散射。且当光线到达瞳孔表面的时候,还会进一步在瞳孔结构内部发生次表面散射

1732202015740.jpg

光线在进入瞳孔内部前,会先在角膜的表面折射,再进入瞳孔内部,产生散射,最后从瞳孔表面的另一个点散射出来。这里就涉及到了两个问题:

  1. 光打在角膜表面折射后,如何计算入射到瞳孔表面
  2. 光线折射进角膜内部后,如何计算其散射效果

解决方案是次表面纹理映射(Subsurface texture mapping),用于解决多层厚度不均匀的材质的次表面散射计算

每层单独的材质用单独的深度图去存储,并每层单独的材质被认为是均匀的,拥有相同的散射、吸收系数以及相应的相位函数。随后,以视线和第一层材质的交点为起点,沿着视线方向对多层材质进行raymarching,每行进一步根据位置和深度图计算当前点位于材质的哪一层对应的散射参数,再根据上一步的位置和光照方向计算散射和吸收,直到ray-marching结束。但实际计算时,只有一层散射材质,即瞳孔材质。因此只需要提供瞳孔表面的深度图,并设定好瞳孔材质的相关散射参数,再结合次表面纹理映射的方法计算即可

为了让瞳孔的效果更逼真,在RayMarching时配合视差贴图技术。当然视差贴图是非物理的,想实现物理的效果可以使用基于物理的折射。下图左边是视差贴图,右边是基于物理的折射

1732203845198.png

缩放

缩放可以通过模型的uv来控制

1732203985190.png

Iris

虹膜的颜色实现比较简单。使用虹膜的灰度图,并乘以虹膜颜色来实现

1732204300829.png

不平坦反射

真实的眼白不是完全镜面平坦的,有一定程度的凹凸不平,可以通过类Sin函数扰动法线贴图来模拟

1732204602961.png

眼睛自反射

由于眼球具体较强的反射,睫毛、眼皮会反射在上面

这部分可以使用反射探针解决,也可以提前烘焙环境遮蔽图进行混合处理

瞳孔、虹膜、巩膜的过渡

由于它们分属不同的材质,有着各自的属性,需要对它们进行过渡处理。否则会出现以下情况:

1732205168577.png

过渡曲线可采用类似Sin函数的:

1732205226420.png

血色和血丝

血丝可在眼白的纹理中添加血管纹理细节

血色可在计算时用一个color乘以Mask贴图

1732205315520.png

通用纹理图

主要会用到以下纹理图:

  • Wet Normal map:主法线贴图,为眼球湿面(Wet surface)提供细微的起伏感

    1732205802477.png

  • Sclera map:控制眼白部分基础色的贴图,可给予贴图血管组织颜色以丰富眼白的细节

    1732205911795.png

  • Tangent map:控制表面切线走向,以便在不同的朝向下强调角膜和巩膜的变化和区别

    下图中圆心中心和圆形外围的起伏变化,它们分别对应角膜和巩膜

    1732205940730.png

  • Mid Plane Displacement map:用于锁定一个横切眼部中心的平面,之后基于这个平面来计算角膜的深度偏移

    1732206051005.png

  • Eye Diffuse:角膜贴图非常特殊,它与模型的UV布局并不匹配。后续通过UV和Alpha Mask来控制整个角膜的尺寸,也包括瞳孔的大小

    1732206299214.png

  • Environment Texture Cube:控制眼部高光反射内容的环境光贴图

  • Noise:通用噪声图

通用参数

  • Depth Scale:控制经过角膜折射后,虹膜偏折的深度
  • Flatten Normal:控制全局眼球法线的平顺度,主要影响眼白部分(巩膜)
  • IOR:角膜到晶状体之间房水的折射率,控制折射程度
  • Iris Concavity Power & Iris Concavity Scale:Iris Concavity Power 和Iris Concavity Scale一起控制在虹膜表面因光线透射过角膜而形成的焦散(caustics)的形状和强度。通常只有在真实光照环境下才可见
  • Iris UV Radius:控制眼球上虹膜的整体大小
  • Iris Brightness:控制虹膜的明亮程度
  • Iris Roughness:影响角膜(虹膜之上的表面)的闪亮(Shiny)程度
  • Limbus Dark Scale:控制角膜边缘暗环的大小
  • Limbus Power:控制角膜边缘暗环向巩膜(眼白)的扩散程度,过大的数值会染黑整个巩膜
  • Limbus UV Width Color:控制角膜缘的采样尺寸,或者说要分配多少眼球的表面积来显示角膜缘
  • Limbus UV Width Shading:控制有多少光照能够影响角膜缘的渲染
  • Normal UV Scale:控制Wet Normal纹理的缩放
  • Pupil Scale:控制瞳孔大小
  • Refraction On/Off:混合带折射和不带折射版本的着色结果
  • Scale By Center:调整整个虹膜/瞳孔的缩放
  • Sclera Brightness:控制巩膜的明亮程度
  • Sclera Roughness:控制巩膜(眼白)部分材质的粗糙度
  • Specularity Iris:控制在角膜区域(虹膜和瞳孔)形成的高光强度
  • Specularity Sclera:控制在巩膜(眼白)区域形成的高光强度
  • Shadow Hardness & Shadow Radius:控制巩膜内外层颜色混合的锐利度

实现

这里笔者想还原最终幻想7中爱丽丝的效果,并没有用到这么多贴图和参数(具体的可以参考UE的数字人资产)

Scale UV By Center

以贴图中心进行uv缩放

float2 ScaleUVByCenter(float2 uv, float scale = 1)
{
    float2 o;

    o = uv / scale + 0.5f - 0.5f / scale;

    return o;
}

该式将整个分布在[0, 1]区间的uv先平移到中心点对齐0点的位置,然后进行\frac{1}{scale}的缩放,最后再平移回中心点为(0.5, 0.5)的正常位置

折射

这部分可以用视差贴图或基于物理的折射实现,由于基于物理的折射实现比较复杂,这里只展示视差贴图部分,关于物理的折射可以看ue的源码

  • 视差贴图
float2 ParallaxMapping(float3 viewDir, half2 uv, half height, half heightScale)
{ 
    float2 p = viewDir.xy * (height * heightScale);
    return uv - p;    
}

需要注意的,这里最好使用三张贴图,一张是巩膜(眼白和血丝),一张是虹膜的灰度图(彩色部分),还有一张虹膜的Mask。在计算视差时只对虹膜的灰度图和Mask图做视差

高光

NDF项 GGX

// GGX / Trowbridge-Reitz
// [Walter et al. 2007, "Microfacet models for refraction through rough surfaces"]
// 在流行的模型中,GGX拥有最长的尾部。而GGX其实与Blinn (1977)推崇的Trowbridge-Reitz(TR)(1975)分布等同。然而,对于许多材质而言,即便是GGX分布,仍然没有足够长的尾部
float NDF_GGX( float roughness2, float NoH )
{
    const float a2 = pow2(roughness2);
    const float NoH2 = pow2(NoH);
    const float d = PI * pow2(NoH2 * (a2 - 1.f) + 1.f);

    if(d < FLT_EPS) return 1.f;

    return a2 / d;
}

F项 Schlick近似足已

float3 SchlickFresnel(float HdotV, float3 F0)
{
    float m = clamp(1-HdotV, 0, 1);
    float m2 = m * m;
    float m5 = m2 * m2 * m; // pow(m,5)
    return F0 + (1.0 - F0) * m5;
}

G项 Smith近似

float Vis_SmithJointApprox( float a2, float NoV, float NoL )
{
    float a = sqrt(a2);
    float Vis_SmithV = NoL * ( NoV * ( 1 - a ) + a );
    float Vis_SmithL = NoV * ( NoL * ( 1 - a ) + a );
    return 0.5 * rcp( Vis_SmithV + Vis_SmithL );
}

如果不需要完全物理的话,可以只使用NDF项,也可以得到高光形状

MatCap

MatCap可以进一步提升眼睛质感

half2 GetMatCapUV(half3 viewDirWS, half3 normalWS)
{
    half3 cameraFoward = -viewDirWS;
    half3 viewUpDir = mul(UNITY_MATRIX_I_V, half4(half3 (0, 1, 0), 0)).xyz;
    half3 cameraRight = normalize(cross(viewUpDir,cameraFoward));
    half3 cameraUp = normalize(cross(cameraFoward,cameraRight));

    half2 uv = mul(float3x3(cameraRight,cameraUp,cameraFoward),normalWS).xy * 0.5 + 0.5;
    return uv;
}

焦散

这里采用Caustic Mask图,用于控制哪些区域会存在焦散亮斑

float causticMask =  _CausticMaskTex.SampleLevel(Smp_RepeatU_RepeatV_Linear, Unity_Rotate_Degrees_float(eyeUV, 0.5, LightDir.r), 0);
o.emission = causticMask * _CausticColor;

最终效果

1732294167028.png

Reference

UE Digital Human Eye Shading

剖析Unreal Engine超真实人类的渲染技术Part 2 - 眼球渲染

【UE4.26】眼球渲染笔记


他们曾如此骄傲的活过,贯彻始终