眼睛构造
生物学眼睛的构造是很复杂的,包含十多种部位,但在渲染中只需要了解六个部位即可:
- Sclera(巩膜):眼球表层的白色纤维膜,坚韧耐压,有支撑和保护眼球内部组织的作用(眼睛的白色部分)
- Limbus(角膜缘):角膜同巩膜之间的一条灰白色过渡带,是角膜与巩膜的移行区,角膜镶嵌在巩膜而逐渐过渡到巩膜组织内。其宽度也不一致,上缘较宽,下缘次之,水平缘更窄
- Cornea(角膜):人和某些动物眼球前方最外面的一层纤维膜。无色透明,没有血管,反应灵敏(折射源头)
- Iris(虹膜/虹彩):眼球前部角膜和晶状体之间含色素的环形薄膜。膜的中间是瞳孔(眼睛的彩色部分)
- Pupil(瞳孔):眼球虹膜中央进光的圆孔,可以随光线的强弱而缩小或扩大(虹膜内的黑色部分)
- Aqueous Humor(眼房水):眼球晶状体和角膜之间的透明液体,由睫状体的无色素上皮细胞分泌
渲染方案
传统方法是创建两层独立的眼睛表面,一层提供巩膜、虹膜和瞳孔,另一层位于顶部,提供角膜和眼睛的总体湿润度。这样底层表面透过湿润层观看时就会产生折射
简单来说,要想得到效果不错的眼睛,需着重关注:
- 角膜的半透和高光反射
- 瞳孔的次表面散射
- 瞳孔的缩放(高光强度)
- 虹膜的颜色变化(高光颜色)
Cornea
这里需要实现角膜的半透明和高光反射效果,因此需要计算高光镜面反射、IBL反射,并在basecolor中加入虹膜贴图以实现角膜下面的折射效果
Pupil
次表面散射
把眼球看成双层结构,外层是角膜,内层是瞳孔的表面,角膜和瞳孔间可以认为充斥着透明液体。又因为瞳孔和角膜间有一定的距离,因此会产生散射。且当光线到达瞳孔表面的时候,还会进一步在瞳孔结构内部发生次表面散射
光线在进入瞳孔内部前,会先在角膜的表面折射,再进入瞳孔内部,产生散射,最后从瞳孔表面的另一个点散射出来。这里就涉及到了两个问题:
- 光打在角膜表面折射后,如何计算入射到瞳孔表面
- 光线折射进角膜内部后,如何计算其散射效果
解决方案是次表面纹理映射(Subsurface texture mapping),用于解决多层厚度不均匀的材质的次表面散射计算
每层单独的材质用单独的深度图去存储,并每层单独的材质被认为是均匀的,拥有相同的散射、吸收系数以及相应的相位函数。随后,以视线和第一层材质的交点为起点,沿着视线方向对多层材质进行raymarching,每行进一步根据位置和深度图计算当前点位于材质的哪一层、对应的散射参数,再根据上一步的位置和光照方向计算散射和吸收,直到ray-marching结束。但实际计算时,只有一层散射材质,即瞳孔材质。因此只需要提供瞳孔表面的深度图,并设定好瞳孔材质的相关散射参数,再结合次表面纹理映射的方法计算即可
为了让瞳孔的效果更逼真,在RayMarching时配合视差贴图技术。当然视差贴图是非物理的,想实现物理的效果可以使用基于物理的折射。下图左边是视差贴图,右边是基于物理的折射
缩放
缩放可以通过模型的uv来控制
Iris
虹膜的颜色实现比较简单。使用虹膜的灰度图,并乘以虹膜颜色来实现
不平坦反射
真实的眼白不是完全镜面平坦的,有一定程度的凹凸不平,可以通过类Sin函数扰动法线贴图来模拟
眼睛自反射
由于眼球具体较强的反射,睫毛、眼皮会反射在上面
这部分可以使用反射探针解决,也可以提前烘焙环境遮蔽图进行混合处理
瞳孔、虹膜、巩膜的过渡
由于它们分属不同的材质,有着各自的属性,需要对它们进行过渡处理。否则会出现以下情况:
过渡曲线可采用类似Sin函数的:
血色和血丝
血丝可在眼白的纹理中添加血管纹理细节
血色可在计算时用一个color乘以Mask贴图
通用纹理图
主要会用到以下纹理图:
- Wet Normal map:主法线贴图,为眼球湿面(Wet surface)提供细微的起伏感
-
Sclera map:控制眼白部分基础色的贴图,可给予贴图血管和组织颜色以丰富眼白的细节
-
Tangent map:控制表面切线走向,以便在不同的朝向下强调角膜和巩膜的变化和区别
下图中圆心中心和圆形外围的起伏变化,它们分别对应角膜和巩膜
-
Mid Plane Displacement map:用于锁定一个横切眼部中心的平面,之后基于这个平面来计算角膜的深度偏移
-
Eye Diffuse:角膜贴图非常特殊,它与模型的UV布局并不匹配。后续通过UV和Alpha Mask来控制整个角膜的尺寸,也包括瞳孔的大小
-
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;
Comments | NOTHING