UE Digital Human Eye Shading

名词解释

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

重要纹理(Textures)

条目 解释 图示
Wet Normal map 主法线贴图,为眼球湿面(Wet surface)提供细微的起伏感。
Sclera map 对应控制眼白部分基础色的贴图,可给予贴图血管和组织颜色以丰富眼白的细节。
Tangent map 控制表面切线走向,以便在不同的朝向下强调角膜和巩膜的变化和区别。注意图例中圆心中心和圆形外围的起伏变化,它们分别对应了角膜和巩膜。
Mid Plane Displacement map 该贴图用于锁定一个横切眼部中心的平面,之后基于这个平面来计算角膜的深度偏移。
Eye Diffuse 注意角膜贴图非常特殊,它与模型的UV布局并不匹配,相反,从图例中可以看出,角膜基础色填充满了整张矩形纹理。后续UE将会通过UV和Alpha Mask来控制整个角膜的尺寸,也包括瞳孔的大小。
Environment Texture Cube 控制眼部高光反射内容的环境光贴图
Noise 通用噪声图

主要参数(Values)

条目 解释 解释原文
Depth Scale[scaler] 控制经过角膜折射后,虹膜偏折的深度。 This controls the depth of the refraction of the iris underneath the cornea.
Flatten Normal[scaler] 控制全局眼球法线的平顺度,主要影响眼白部分(巩膜)。 This value controls how much flattening of the eye's normal map is taking place, focused particularly on the sclera.
IOR (Index of Refraction)[scaler] 角膜到晶状体之间房水的折射率,控制折射程度。 Index of refraction of the fluids underneath the cornea. Controls how much refraction takes place.
Iris Concavity Power[scaler] 与Iris Concavity Scale一起控制在虹膜表面因光线透射过角膜而形成的焦散(caustics)的形状和强度。通常只有在真实光照环境下才可见。 Used alongside Iris Concavity Scale to control the shape and amount of light caustics that are calculated on the surface of the iris as light passes through the cornea. This will generally only be visible in an actual lit scene, and can be difficult to visualize within the Material Instance Editor.
Iris Concavity Scale[scaler] 同上。 ditto.
Iris UV Radius[scaler] 控制眼球上虹膜的整体大小。 Controls the overall size of the iris on the eyeball.
Iris Brightness[scaler] 控制虹膜的明亮程度。 Controls the brightness of the iris.
Iris Roughness[scaler] 该值影响角膜(虹膜之上的表面)的闪亮(Shiny)程度。 This value drives how shiny the cornea is (the surface directly over the iris).
Limbus Dark Scale[scaler] 控制角膜边缘暗环的大小。 Controls the size of the darkening ring of the limbus.
Limbus Power[scaler] 控制角膜边缘暗环向巩膜(眼白)的扩散程度,过大的数值会染黑整个巩膜。 Controls the overall darkening of the limbus area. Overdriven values will darken the entire corneal area.
Limbus UV Width Color[scaler] 控制角膜缘的采样尺寸,或者说要分配多少眼球的表面积来显示角膜缘。这是个非常精巧的参数,小心设置它。 Controls the sample size for the limbus, or how much of the eye surface will be alloted for showing the limbus. This is a fairly delicate setting and should be adjusted with care.
Limbus UV Width Shading[scaler] 控制有多少光照能够影响角膜缘的渲染。同样非常精巧,需要小心设置。 Controls how much light will affect the shading of the limbus. Very delicate setting, adjust with care.
Normal UV Scale[scaler] 控制Wet Normal纹理的缩放。 Controls the scale of the normal map texture used across the surface of the eye.
Pupil Scale[scaler] 控制瞳孔大小,用这个参数来扩张/缩小瞳孔。 Controls the size of the pupil. This is the setting you would use to dilate the eyes.
Refraction On/Off[scaler] 用于混合带折射和不带折射版本的着色结果。 Blends between refracting and non-refracting versions of the shader.
Scale By Center[scaler] 调整整个虹膜/瞳孔的缩放(缩放基于中心)。 Adjusts the scale of the entire iris/pupil area from its center.
Sclera Brightness[scaler] 控制巩膜的明亮程度(或者白皙程度)。 Controls the brightness of the sclera, or white of the eye.
Sclera Roughness[scaler] 控制巩膜(眼白)部分材质的粗糙度。 Controls the Material Roughness value of the sclera.
Specularity Iris[scaler] 控制在角膜区域(虹膜和瞳孔)形成的高光强度。 Controls specularity level across the cornea (iris and pupil).
Specularity Sclera[scaler] 控制在巩膜(眼白)区域形成的高光强度。 Controls the specularity level across the sclera.
Shadow Hardness[scaler] 控制巩膜内外层颜色混合的锐利度。与Shadow Radius联合使用。 Controls the sharpness of the blend between the sclera inner and outer color. Used with Shadow Radius to help drive shading across the surface of the sclera, approximating the effect of the eyelid casting subsurface scattered shadows across the surface of the eyeball.
Shadow Radius[scaler] 控制巩膜内外层颜色混合的锐利度。与Shadow Hardness联合使用。 Controls the size of the blend between the inner and outer colors of the sclera. Used with Shadow Hardness .

重要代码段解析

1. ScaleUVsByCenter
float2 ScaleUVsByCenter(float2 uv, float Scale = 1)
{
    float2 outUV;
    outUV = uv / Scale + 0.5 - 0.5 / Scale;
    return outUV;
}

解释:令 b = 1 / Scale,则有,outUV = uv * b - 0.5 * b + 0.5 = (uv - 0.5) * b + 0.5;
该式将整个分布在[0, 1]区间的uv先平移到中心点对齐0点的位置,然后进行尺度为b的缩放,最后再平移回中心点为(0.5, 0.5)的正常位置。
当Scale调大 -> b缩小 -> 令uv的分布向中心点紧缩 -> 原本可以采样到纹理外围的点,现在只能采样到靠近中心的纹素了 -> 反过来想就是纹理的中心部分纹素向外扩张。

2. IrisiUVMask
float2 IrisiUVMask(float IrisUVRadius, float2 UV, float2 LimbusUVWidth)
{
    // Iris Mask with Limbus Ring falloff
    UV = UV - float2(0.5f, 0.5f);

    float2 m, r;
    r = (length(UV) - (IrisUVRadius - LimbusUVWidth)) / LimbusUVWidth;
    m = saturate(1 - r);
    m = smoothstep(0, 1, m);
    return m;
}

解释:入参IrisUVRadius是用户设置参数,表示UV尺度上虹膜的半径,而虹膜的边缘有一圈暗色的角膜缘(Limbus),参考入参LimbusUVWidth,注意这是个float2类型的值,可以认为其xy分量分别代表了Limbus的外侧和内侧边缘,比如LimbusUVWidth=[0.01, 0.045]表示从虹膜半径IrisUVRadius=0.133往内移动0.01的UV距离是Limbus的外侧,继续移动(0.045 - 0.01 = 0.035)的UV距离则可触碰到Limbus的内边缘。
方法现将UV的中心点对齐到0点,这样计算length(UV)时就能获得着色点的UV半径。
算式:r = (len(UV) - (R - W)) / W; 获得的返回值r可以这样理解:

  • 若r < 0 则说明点在Limbus环以内;
  • 若0 < r < 1 则说明点在Limbus环上;
  • 若 r > 1 表示点在整个虹膜之外。

后面通过 m = sat(1 - r);r重新映射,使得:

  • 若m < 0 -> 点在Limbus环外;
  • 若m 从0过渡到1 -> 点从环外边缘过渡到环内边缘;
  • 若m > 1 -> 点在虹膜上(不在Limbus环上)。

最终通过 m = smoothstep(0, 1, m);将原本的线性过渡替换为平滑过渡。

上图黄色外边缘实际上是Limbus的内边缘,而红色外边缘则是Limbus的外边缘。

3. RefractionDirection
float3 RefractionDirection(float internalIoR, float3 normalWS, float3 cameraWS)
{
    float airIoR = 1.00029;
    float n = airIoR / internalIoR;
    
    float facing = dot(normalWS, cameraWS);
    
    float w = n * facing;
    
    float k = sqrt(1 + (w - n) * (w + n));
    
    float3 t;
    t = (w - k) * normalWS - n * cameraWS;
    t = normalize(t);
    return -t;
}

图示:


如图[A]所示,入射光线经介质分界面形成折射,假设入射角为 θ1,折射角为θ2,入射介质和出射介质的折射率(IoR)分别为n1n2,且由Snell折射定律可知:n1sinθ1 = n2sinθ2。方法RefractionDirection利用入射向量V,介质分界面法线N,以及折射率n1n2来计算折射向量R

首先方法定义了 n = airIoR / internalIoR = n1 / n2 = sinθ2 / sinθ1; (1)
另外定义了facing = dot(N, V) = cosθ1; (2)
接下来将 w = n * facing = n * cosθ1 带入到 k = sqrt(1 + (w - n) * (w + n))中,并重组多项式有:
k = sqrt(1 + n^2 * cosθ1^2 - n^2);
k = sqrt(1 - n^2(1 - cosθ1^2) );
由三角公式已知:sinθ^2 + cosθ^2 = 1;代入上式可得:
k = sqrt(1 - n^2 * sinθ1^2);
由折射定律可知 n^2 = (sinθ2 / sinθ1)^2 -> n^2 * sinθ1^2 = sinθ2^2,代入上式可得:
k = sqrt(1 - sinθ2^2);
继续套用三角公式最终可得
k = cosθ2; (θ2 ∈[0, π/2]) (3)

其次是计算折射向量R,原始是这个公式:t = (w - k) * N - n * V;
展开重组一下:t = w * N - k * N - n * V;
其中 w = n * cosθ1,代入上式得:
t = n * cosθ1 * N - n * V - k * N; 合并含n的项:
t = n * (cosθ1 * N - V) - k * N;
这里NV都是单位向量,参考图示[B],由cosθ1 = dot(N, V)可知,cosθ1 * N在物理上可以认为是单位向量V在单位向量N上的投影N'
这样上式括号中的部分就可以转换为切线T',参考图示[C]T'沿着切线朝向,其模长正好为:sinθ1。这样上式可改写成如下形式:
t = n * T' - k * N; 代入(1)可得:
t = (sinθ2 / sinθ1) * T' - k * N; 其中的T'如前文所示,可以改写为sinθ1 * T, 其中T代表切线朝向的单位向量。
t = (sinθ2 / sinθ1) * (sinθ1 * T) - k * N; 化简并带入(3)式可得:
t = sinθ2 * T - cosθ2 * N; 参考图示[D],其中切线方向的深红色向量就是(sinθ2 * T),它代表折射单位向量R在切线方向上的投影。
t = sinθ2 * T + cosθ2 * (-N); 借用符号,将N转换为-N,参考图示[E],其中负法线方向的深红色向量就是(cosθ2 * (-N)),它代表折射单位向量R在负法线上的投影。至此可得: t = R;

最后通过:t = normalize(t); return -t; 获得了归一化后的折射方向R的反方向。

4. EyeRefraction
void EyeRefraction(
    float IoR, float ScaleByCenter, float2 LimbusUVWidth, float DepthScale,
    float DepthPlaneOffset, float3 MidPlaneDisplacement, float3 EyeDirectionWorld,
    float IrisUVRadius, float2 InputUV, float3 PixelNormalWS, float3 ViewDirWS,
    float3 TangentWS, out float2 OutputRefractedUV, out float2 OutputIrisMask)
{
    //Step#1 计算虹膜遮罩
    float2 scaledUV = ScaleUVsByCenter(InputUV, ScaleByCenter);
    OutputIrisMask = IrisiUVMask(IrisUVRadius, scaledUV, LimbusUVWidth);

    //Step#2 计算折射向量并据此计算折射UV
    float3 RefractionDir = RefractionDirection(IoR, PixelNormalWS, ViewDirWS); 

    float cosAlpha = dot(EyeDirectionWorld, ViewDirWS);
    float facingRatio = lerp(0.325, 1, cosAlpha * cosAlpha);

    float heightW = max(0, MidPlaneDisplacement - DepthPlaneOffset);
    heightW *= DepthScale;

    float ScaleRefractionFactor = heightW / facingRatio;
    RefractionDir *= ScaleRefractionFactor;

    float3 IrisTangentWS = TangentWS - dot(TangentWS, EyeDirectionWorld) * EyeDirectionWorld;
    IrisTangentWS = normalize(IrisTangentWS);

    float3 IrisBitangentWS = cross(IrisTangentWS, EyeDirectionWorld);

    float2 RefractedUVOffset = float2(dot(RefractionDir, IrisTangentWS), dot(RefractionDir, IrisBitangentWS));

    float2 RefractedUV = IrisUVRadius * float2(-1, 1) * RefractedUVOffset + scaledUV;

    OutputRefractedUV = lerp(scaledUV, RefractedUV, OutputIrisMask.r);
}

解释:入参用有一些是前文定义的全局材质入参,自行对照查找即可,有一些不是,这里说一说:

  • EyeDirectionWorld -> 虹膜平面上的世界空间法线
  • PixelNormalWS -> 眼球湿面对应的世界空间法线(Wet Normal: 包含Cornea和Sclera表面法线)
  • ViewDirWS -> 视方向(从着色点指向摄像机)
  • TangentWS -> 眼球模型对应的世界空间切线
  • MidPlaneDisplacement -> 是基于虹膜平面的整个眼睛前半球的深度偏移
  • DepthPlaneOffset -> 是基于虹膜平面的位于虹膜边缘处的深度偏移(所有像素都是这个值)
  • OutputRefractedUV -> 输出变量: 经过折射影响后的UV分布
  • OutputIrisMask -> 输出变量: UV空间中定义的虹膜范围

所以总体上该方法是用于计算视角光线经过角膜后房水的折射,到达虹膜时的坐标的。当然因为是实时渲染环境,方法返回的虹膜坐标实际上是以UV采样坐标的形式表述的。下面找一些重要代码简单解读一下:

cosAlpha = dot(EyeDirectionWorld, ViewDirWS)是摄像机视角与虹膜平面法线形成的夹角余弦 (Facing Ratio),注意EyeDirectionWorld代表虹膜表面的法线分布,区别于眼球表面法线,该法线相对眼球法线而言呈内凹状态。

heightW = max(0, MidPlaneDisplacement - DepthPlaneOffset)所得计算结果是整个虹膜区域的深度差,可以简单理解为:当heightW=0时虹膜到角膜之间的深度差为0,意味着该区域位于虹膜(角膜缘)之外,反之则在虹膜以内。另外由于深度图中心数值高,边缘低的设计特性,当说色点接近瞳孔时(靠近中心),深度差值大,反之亦然。

ScaleRefractionFactor = heightW / facingRatio 是一个正比于高度差,反比于视角与虹膜法线相似度的标量,用于修正折射向量的模长,从而使得:

  • 越靠近虹膜中心,折射效果越强
  • 视角越偏(侧视),折射效果越强

接下来是如何将折射向量转化为贴图UV,过程非常有意思。
首先是计算折射向量在虹膜平面上的投影,这需要用到虹膜平面的切线和副切线,我们来看看UE的做法:
IrisTangentWS = TangentWS - dot(TangentWS, EyeDirectionWorld) * EyeDirectionWorld;
上式利用眼球模型切线和虹膜法线,直接构造出垂直于虹膜法线的“虹膜切线”。
然后利用虹膜切线和已知的虹膜法线,构造“虹膜副切线”。
IrisBitangentWS = cross(IrisTangentWS, EyeDirectionWorld);
这样就能如下式所示,将折射向量(-R)分别投影到虹膜切线和虹膜副切线上:
RefractedUVOffset = float2(dot(RefractionDir, IrisTangentWS), dot(RefractionDir, IrisBitangentWS));
该计算结果将会得到一个位于[0,1]区间上虹膜贴图采样的UV偏移(Offset) -> RefractedUVOffset。
应用这个偏移,如下式所示,UE重新调整折射UV偏移范围,使其落在IrisUVRadius规定的UV范围之内,同时翻转U方向,使水平朝向上抵消折射向量(-R)中的符号影响,匡正视觉效果上的错误:
StructuredRefractedUV = IrisUVRadius * float2(-1, 1) * RefractedUVOffset;
然后在经过如下公式,将调整后UV偏移叠加到缩放过的UV上,得到折射后采样纹理可用的UV:
RefractedUV = StructuredRefractedUV + scaledUV;
对比效果参考下图:

在方法的最后,为了只让虹膜和瞳孔部分产生折射偏移,UE使用Step#1中的虹膜遮罩参与lerp:
OutputRefractedUV = lerp(scaledUV, RefractedUV, OutputIrisMask.r) ;
注意,OutputIrisMask.r1趋向0的过程对应了在UV空间中由瞳孔趋向角膜缘外边缘的过程。

5. CalcIrisDistance
float CalcIrisDistance(float2 RefractedUV, float IrisUVRadius,
    float IrisConcavityScale, float IrisConcavityPower)
{
    float UVRadius = length(RefractedUV - 0.5);
    float IrisUVSpaceDistance = UVRadius / IrisUVRadius; 
    IrisUVSpaceDistance *= IrisConcavityScale;
    return saturate(pow(IrisUVSpaceDistance, IrisConcavityPower)); 
}

解释:方法通过数学公式拟合的方式,将水平方向上折射点UV到瞳孔中心点的距离(UV空间中的半径)映射到着色点之于虹膜整体的距离(既Iris Distance)。
方法首先通过length(RefractedUV - 0.5)折算出UV空间中距离中心点距离,在通过除以虹膜UV半径(IrisUVRadius)获得定义在虹膜UV空间内的UV距离IrisUVSpaceDistance。最后进过参数IrisConcavityScale和IrisConcavityPower的缩放和乘方调整,映射成虹膜距离,具体映射趋势参考下图:

该映射后结果即是IrisDistance,又作为GBuffer的Metallic属性。

6. ScalePupils
float2 ScalePupils(float2 UV, float PupilScale)
{
    float2 UVcentered = UV - float2(0.5f, 0.5f);
    float UVlength = length(UVcentered);
    float2 UVmax = normalize(UVcentered) * 0.5f;

    float2 UVscaled = lerp(UVmax, float2(0.f, 0.f), saturate((1.f - UVlength * 2.f) * PupilScale));
    return saturate(UVscaled + float2(0.5f, 0.5f));
}

解释:方法将输入的沿着贴图纹理轴向均匀展开的UV重新映射成由中心点(0.5,0.5)向四周环状散射展开的形式,同时按照PupilScale规定的比例将UV沿着放射线朝向进行缩放,从而达到扩展/缩小瞳孔的目的。

方法构造了中心点(0点)对齐的最大环状散射UV:UVmax = normalize(UVcentered) * 0.5f,通过巧妙的normalize将UV的模长固定为1,同时保留原有的朝向,之后再通过 (* 0.5)构造出UV的最大半径(在中心0点对齐的UV空间,UV取值范围是-0.5 ~ +0.5)。
方法随后利用:UVscaled = lerp(UVmax, float2(0.f, 0.f), saturate((1.f - UVlength * 2.f) * PupilScale)) 完成缩放,具体来说就是构造lerp,在UV与0点连线的放射状朝向上从0UVmax进行缩放,缩放因子是另一个入参:PupilScale,式中的(1.f - UVlength * 2.f)部分确保缩放不会超过UV的最大值。

7. ScaleIrisAndPupilsUV
void ScaleIrisAndPupilsUV(float IrisUVRadius, float PupilScale, float2 RefractedUV,
    float LimbusDarkScale, float LimbusPow, out float2 ScaledIrisPupilsUV, out float ScaledLimbusDarkMask)
{
    float IrisUVDiameter = IrisUVRadius * 2;
    float2 ScaledIrisUV = ScaleUVsByCenter(RefractedUV, IrisUVDiameter)

    ScaledIrisPupilsUV = ScalePupils(ScaledIrisUV, PupilScale);

    float2 CenterLimbusDarkUV = (ScaledIrisPupilsUV - 0.5) * LimbusDarkScale;
    float LimbusDarkDistance = length(CenterLimbusDarkUV);

    ScaledLimbusDarkMask = 1 - pow(LimbusDarkDistance, LimbusPow);
}

void main(...)
{
        ...    
    float3 IrisColor = SAMPLE_TEXTURE2D(IrisMap, mySampler, ScaledIrisPupilsUV).rgb * ScaledLimbusDarkMask;
    ...    
}

解释:构造出最终采样虹膜贴图的UV,以及对虹膜之外部分进行剔除的遮罩。

方法首先计算出虹膜的UV空间直径:IrisUVDiameter = IrisUVRadius * 2,这个数值是接下来进行ScaleUVsByCenter的目标缩放倍率,目的是将原本纹理占比100%的虹膜部分通过UV缩放到在纹理空间中占比为IrisUVDiameter(图例中为0.3)大小的尺寸。

接下来通过ScalePupils方法将UV沿着中心点向外的放射方向进行缩放,从而对瞳孔的显示效果进行调节。这时我们就得到了最终的IrisMap采样UV:ScaledIrisPupilsUV。

最后是计算能够覆盖角膜缘以内部分的遮罩,现将上述UV进行中心对齐,并再次进行缩放:CenterLimbusDarkUV = (ScaledIrisPupilsUV - 0.5) * LimbusDarkScale,用户通过调节缩放因子:LimbusDarkScale的大小,以便使得:LimbusDarkDistance = length(CenterLimbusDarkUV) 对应角膜缘的UV位置正好落在下图中x轴向上[0, 1]区间内,从而使角膜缘外的Mask值恒为0,角膜缘内的Mask值参考下图所示从01分布。

总之UE使用输出的ScaledIrisPupilsUV采样虹膜贴图,结果与遮罩ScaledLimbusDarkMask相乘,从而将完整的虹膜部分绘制出来,结果参考下列组图:

8. SphereMask && EyeBaseColor
float SphereMask(float4 Coords, float4 Center, float Radius, float Hardness)
{
    return 1 - saturate((distance(Coords, Center) - Radius) / (1 - Hardness));
}
void main(...)
{
    ...
    //#Step 1
    float2 ScaledScleraUV = ScaleUVsByCenter(RefractedUV, ScaleByCenter);  //计算巩膜纹理的缩放
    float3 ScleraColor = SAMPLE_TEXTURE2D(ScleraMap, mySampler, ScaledScleraUV).rgb * ScleraBrightness; //采样获得巩膜颜色
    float3 EyeBaseColor = lerp(ScleraColor, IrisColor, ScaledLimbusDarkMask);  //混合得到EyeBaseColor

    //#Step 2
    float3 EyeCloudyColor = SphereMask(ScaledIrisPupilsUV, 0.5, 0.18, 0.2) * CloudyIrisColor;
    float EyeDarknessFactor = SphereMask(ScaledScleraUV, 0.5, ShadowRadius, ShadowHardness);
    float3 EyeDraknessColor = lerp(EyeCornerDarknessColor, 1, EyeDarknessFactor);
    EyeBaseColor = (BaseColor + EyeCloudyColor) * EyeDraknessColor;
    ...    
}

解释:在#Step1中UE将先前获得的虹膜颜色“IrisColor”在主方法内与另行采样获得的巩膜颜色“ScleraColor”进行混合,混合遮罩使用了上文提及的“ScaledLimbusDarkMask”遮罩,从而获得较为完整的EyeBaseColor:

随后在#Step2中,UE为这个眼睛基础上增加了2项额外颜色:分别是控制角膜内浑浊度的“EyeCloudyColor”和控制环境光遮蔽程度的“EyeDraknessColor”。其中前者以Add的形式附加在基础色上,后者则以 Mulityply(乘法)的形式将基础上按比例压暗。

如代码所示,生成“EyeCloudyColor”和“EyeDraknessColor”这两种遮罩使用到了SphereMask方法:
该方法用于生成一个均匀分布在UV空间中的圆形遮罩,圆心由入参“Center”控制,“Radius”则定义了圆心到外边缘的半径,最后“Hardness”控制圆心向边缘强度衰减半径,所有这些参数都基于UV空间。

生成的遮罩和最终合成效果参考如下图例:

9. FlattenNormal & WetNormalWS
float3 FlattenNormal(float3 Normal, float flatness)
{
    return lerp(Normal, float3(0, 0, 1), flatness);
}
void main(...)
{
    ...
    float2 ScaledNormalUV = ScaleUVsByCenter(InputUV, NormalUVScale);  
    float3 WetNormalTS = SAMPLE_TEXTURE2D(WetNormalMap, mySampler, ScaledNormalUV).rgb;
    float flatness = lerp(FlattenNormal, 1, IrisMask.r);
    WetNormalTS = FlattenNormal(WetNormalTS, flatness);
    WetNormalTS = WetNormalTS * GlobalNormalScale + GlobalNormalBias;
    
    float WetNormalWS = TransformTangentToWorld(WetNormalTS, tbn);
    ...    
}

解释:这部分比较简单就不赘述了,由于湿件部分(角膜+巩膜)的法线需要对角膜部分进行特殊处理,使其表面法线更加平坦,因此如上述代码所示,UE对采样后的WetNormalTS进行了一次基于flatness参数的平滑处理,而flatness确保其在角膜范围内达到最大值1。切线空间法线的处理后效果如下图所示:

后记:

作为UE4.27新版电子人之前的眼球渲染解决方案,虽然在角膜折射方面,存在某些角度观察时出现形变的现象,但是其整体效果对得起所付出的计算消耗,甚至非常有性价比。如果有兴趣了解具体源码,可以参考虚幻商城的“数字人类”资产中眼球部分的Shader实现,以及UE4.26源码部分“YourUnrealEngineFolder\Engine\Shaders\Private\ShadingModelsMaterial.ush”文件中ShadingModel == SHADINGMODELID_EYE的部分。

参考

[1] Digital Humans
[2] Digital Human Eye Shader
[3] Making of The Heretic: Digital Human tech package
[4] [UE4.26] 眼球渲染笔记
[5] ue眼球渲染
[6] Eye Shaders - Advanced Materials - Episode 12

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 主材质节点 材质 是使用一种称为 高级着色语言 (简称 HLSL)的专用编码语言来创建的。 HLSL 使材质能够直...
    深呼吸10911阅读 2,895评论 0 4
  • 模型 我们可以明显看到眼球前方有一个凸起的部位,包含了角膜房水等结构,因此在建模时需要体现出这一效果。 UV映射直...
    techiz阅读 6,066评论 0 4
  • Main (主要) 这是您的所有其他节点最终将链接到的节点。它拥有为不同目的服务的多个输入。 以下动画图像显示所有...
    冰冰棒bling阅读 1,918评论 0 1
  • 这里来介绍一下米哈游崩3的相关技术,技术分享于2018年,虽然过去5年了,但其中依然有些内容值得借鉴学习。 崩3、...
    离原春草阅读 321评论 0 0
  • 眼睛是人体最重要的感觉器官,眼球的结构接近于球形,位于眼眶内。眼球包括眼球壁、眼内腔和内容物、神经、血管等组织,眼...
    苏吉儿阅读 25,016评论 0 4