原文链接:基于物理的眼球折射效果
前言
之前写过一篇基于 Marmoset 中的线性步进实现的眼球折射效果 马甲:Marmoset 眼球效果在 Unity 和 UE4 中的实现,虽然这种方法折射的效果很好,但是弊端很多,首先性能上,多重采样,还会打断 GPU 的高并行计算。制作流程上,双层模型结构,美术给角色的蒙皮会带来额外的工作量,所以进行优化折射的消耗和简化美术的制作流程。最终实现和上一篇效果对比如下
仔细看区别不大,虽然还是之前的折射效果更真实,但是放到手机上这部分的差距也可以忽略不计
优化实现
1、模型合并:
双模型换成单模型计算,利用双法线贴图,分别计算内外两层高光
2、折射修改:
主要基于Next-Generation-Character-Rendering-v6.pptx 这篇 ppt 里的折射算法
有源码有图,而且仅仅就用了一张 paper 来阐述原理,本以为很简单就能理解,但是实际深入却卡在很多点,问了很多引擎大佬才搞明白整个流程!
折射是初中物理就学的内容,一开始奔着斯涅尔定律(Snell's Law)去梳理整个流程,最后发现在Real-Time Rendering 中有更快速的拟合公式来模拟折射
至于拟合推导的过程
Ray Tracing News, Volume 10, Number 1
有兴趣的可以看看,没兴趣的直接把公式拿来用,我属于后者。资料已经准备全了,接下来开始计算,首先 refractedW 的计算直接拿快速拟合的折射公式来进行推导,除了 L 部分其他都一模一样。
至于 L 的计算 PPT 里用的是预计算的方式将水平方向不同角度的 L 烘焙到一张 3Dtexture 里,由于 3Dtexture,真的很占内存,所以我们索性用视线的向量来模拟灯光向量,这样反而更能模拟真实灯光的方向的折射,而不仅限于水平方向。
代码如下:
float w = _IOR * dot( normalW, i.viewW );
float k = sqrt( 1.0 + ( w - _IOR ) * ( w + _IOR ) );
float3 refractedW = ( w - k ) * normalW - _IOR * i.viewW;
至于 cosAlpha 是 frontNormal 和 refacted 两向量的夹角,用点积就可以直接计算出
因为 frontNormal 和 refacted 都是归一化后的向量,所以
float cosAlpha = dot(_frontNormalW, -refractedW);// refacted向量要取一下反,这样才是这两向量的夹角,否则取到的是他俩的补角
利用等角转换可以知道 height 和 refract 的夹角值也是 cosAlpha ,利用已知height 可以算出 refract 的长度
float dist = height / cosAlpha;
有了长度和归一化后的 refracted,可以算出 refracted 实际的向量
float3 offsetW = dist * refractedW;
这块儿我思考了好久,一开始不明白,算出来的明明是 refracted 的实际向量,按理说是三角形的斜边,怎么突然和直边等同了,最后终于想明白,一开始受最初那张图的迷惑,感官上觉得,平面上肯定会存在一个弧形透明膜,光线通过这个膜折射进去,然后进行采样!但是实际上并不存在,他就是一个平面,refracted 就是等同 offset!所以接下来一切就通了!接着用一张 mask 图来标注需要进行折射处理的区域!折射的完整代码如下:
fixed3 normalW = i.normal;
float height = _anteriorChamberDepth * saturate( 1.0 - 18.4 * _radius * _radius );
// Refration
float w = _IOR * dot( normalW, i.viewW );
float k = sqrt( 1.0 + ( w - _IOR ) * ( w + _IOR ) );
float3 refractedW = ( w - k ) * normalW - _IOR * i.viewW;
float mask = tex2D(_MaskTex,i.uv).r;
fixed2 eyeUV = i.uv;
float cosAlpha = dot(_frontNormalW, -refractedW);
float dist = height / cosAlpha;
float3 offsetW = dist * refractedW;
float2 offsetL = mul(offsetW,(float3x2)unity_ObjectToWorld);
eyeUV += float2(mask,-mask) * offsetL;
3.功能性效果添加
改变颜色很简单这里就不说了,大概说一下瞳孔和虹膜的缩放,其实就是改变 uv 的区间范围,利用 offset 和 scale 的线性变换,让贴图的中心点始终保持在 uv 坐标系中间
代码如下:
half2 offset = -0.5*(1 / _IrisSize) + 0.5;
o.uv.xy = v.uv*(1 / _IrisSize) + offset;
Shader 的核心部分就是这些,接下来是美术部分,所谓一个效果,三分靠着色器,七分靠贴图,贴图的制作直接影响最终的效果实现
懒得敲了,我直接把给美术写的制作规范贴上来了
后记
目前为止眼球的主要功能算是开发完了,但是后期肯定还会有迭代,比如说虹膜的形状图和瞳孔的形状图,其实就是一个灰阶图,为了可以程序化生成不同的颜色和形状,目前是单拎出来的,以后不同的形状可以合在一张图里面,假如单通道可以放 3X3 个形状,那么一张图可以支持 27 种,足够了吧!