模型
我們可以明顯看到眼球前方有一個(gè)凸起的部位,包含了角膜房水等結(jié)構(gòu),因此在建模時(shí)需要體現(xiàn)出這一效果。
UV映射直接采用了Y軸平面映射。
虹膜大小調(diào)整
這里將模型自帶的UV首先轉(zhuǎn)換成關(guān)于原點(diǎn)中心對稱,比較方便計(jì)算。然后將UV到原點(diǎn)長度大于設(shè)定值的返回鞏膜顏色,小于設(shè)定值的我們做虹膜的渲染,并且使用一個(gè)新的UV(虹膜邊緣UV長度為0.5,中心依然為原點(diǎn))方便之后的計(jì)算。
float2 sUV = i.uv - float2(0.5, 0.5);
float2 sUVIris = sUV / _IrisRadius / 2;
虹膜視差效果
由于虹膜和瞳孔的部分是處于角膜下一定距離,并且房水和角膜作為介質(zhì)有一定的的折射率,因此這里我們需要引入一個(gè)視差效果來給予眼球立體感。
我將所有的計(jì)算首先轉(zhuǎn)換到模型空間下。這里如果將虹膜視為一個(gè)平面,(模型采用平面投影UV,不過此處為了讓計(jì)算更簡單一點(diǎn)采用表面法線和虹膜平面的交點(diǎn)作為原UV)我們可以得到如下的光路圖:
只要求得了偏移量x以及偏移方向,我們就能得到偏移后的UV,使用此UV采樣貼圖就可得到折射后產(chǎn)生的視差效果。
具體計(jì)算方式是:
在有些情況下這里的UpR可能成為一個(gè)負(fù)值,不過最終我們得到的偏移量是一個(gè)標(biāo)量,這個(gè)矯正需要用偏移方向向量來做。至于式子中的d,可以用一個(gè)高度紋理采樣,越靠近虹膜邊緣處折射越小,我這里是在shader里直接計(jì)算了球面減去平面的高度差。
float cosUpN = dot(objectNormal, float3(0,1,0));
float sinUpN = sqrt(1 - cosUpN * cosUpN);
float cosVN = dot(objectNormal, objectViewDir);
float sinVN = sqrt(1 - cosVN * cosVN);
float sinRN = sinVN / _IOR;
float cosRN = sqrt(1 - sinRN * sinRN);
float cosUpR = sinUpN * sinRN + cosUpN * cosRN;
float2 sUVIris = sUV / _IrisRadius / 2;
float d1 = sqrt(1 - sUVIris.x * sUVIris.x - sUVIris.y * sUVIris.y) - 0.86603;
float d2 = sqrt(0.3025 - sUVIris.x * sUVIris.x - sUVIris.y * sUVIris.y) - 0.22913;
float2 deltaUV = _Distortion * (d2 - d1) * sinRN / cosUpR * uvDirection;
一開始我直接將偏移方向設(shè)定為視線方向在虹膜平面上的投影方向向量,不過這實(shí)際上是不對的,因?yàn)閷?shí)際偏移方向也是和眼球表面法線相關(guān)的。
如圖所示,圖中偏移方向應(yīng)該向左,然而視線在平面上的投影是向右的。這個(gè)問題花費(fèi)了我不少時(shí)間排除……最終我使用的計(jì)算方式是:
float3 T = cross(objectViewDir,objectNormal);
float3 D = cross(float3(0,1,0),T);
float2 uvDirection = float2(D.x,D.z);
這里采用了叉乘的方式矯正了VN空間關(guān)系導(dǎo)致的UV偏移方向反轉(zhuǎn)。
使用得到的偏移量乘以偏移方向作為UVOffset,就能夠產(chǎn)生比較令人信服的折射效果。
最后我們會得到一些虹膜紋理并不存在的UV(UV長度大于0.5),于是我們還要引入一張虹膜的遮罩mask讓這些片段去采樣鞏膜紋理,以形成自然過渡的虹膜邊緣。
瞳孔大小調(diào)節(jié)
我們需要對偏移后的虹膜UV做一定的remap處理達(dá)到這個(gè)效果。首先求UV到原點(diǎn)的長度,以及方向向量,然后經(jīng)過一定對長度的處理得到一個(gè)新的長度,乘回方向向量就是我們要的原始UV數(shù)據(jù)。
float2 RemapUV(float2 uv)
{
float lengthUV = length(uv);
float2 uvNormalized = uv / lengthUV ;
float newLength = 0;
if (lengthUV < _PupilRadius)
{
newLength = lengthUV / _PupilRadius * 0.14;
} else
{
newLength = (lengthUV - _PupilRadius) / (0.5 - _PupilRadius) * 0.36 + 0.14;
}
return uvNormalized * newLength;
}
上一段代碼中的0.14是原始紋理中瞳孔的半徑,這和我們采用的虹膜紋理有關(guān),需要手動調(diào)節(jié)。
高光
這個(gè)沒什么好說的,我目前只簡單的在Base Pass和Add Pass中寫了Phong高光公式。