标签:
一个实用的实时毛发渲染及着色方法
Thorsten scheuermann
ATI Resarch,Inc.
翻译:潘曦
(译文里的(pancy:XXX)为译者注)
介绍:
我们提出了一个使用多边形模型的实时毛发渲染算法,并且将其应用于今年SIGGRAPH动画节上的一个实时动画《ruby:The Double Cross》上面。该毛发渲染算法是基于Kajiya-Kay 毛发渲染模型的算法,但是在其之上添加了一个实时的接近现实高光的镜面反射效果(pancy:原始算法可能没有考虑到头发的高光只计算了漫反射或者只是简单的模拟了镜面反射)。这个部分的工作主要由由 Marschner 等人来实现。此外,我们还定义了一个简明的渲染过程(pancy:图形引擎里面一般将一个渲染效果称作一个tecnique,暂且翻译为渲染过程),该过程将告诉大家如何近似达到back-to-front排序的效果来实现多层半透明毛发的渲染。(pancy:模拟back-to-front排序是ATI解决多层半透明渲染的重要GPU算法,在这篇论文下面会详细介绍),在以往的算法中,多层半透明材质的排序需要在每一帧里面依靠cpu来做,在我们的算法中这个方法将被摒弃,我们将依靠在gpu上进行多次渲染来做到对多层半透明毛发的排序。(pancy:这里解释一下为什么之前要对头发进行排序,半透明效果是通过alpha混合来产生的,但是alpha混合的缺点就是需要进行back-to-front排序,也就是说半透明是相对的,相对于在其之前绘制的物体才是半透明,在他之后绘制的东西则会完全盖住它。而头发模型只有一个,除非建模的时候分开否则就要由cpu来分层决定谁先绘制谁后绘制达到完美的半透明效果)
毛发模型:
我们算法所需要输入的模型是一种由很多层的2维多边形所模拟的毛发,这种多层模拟既很容易模拟头发细节,又可以凑成一套几何体。这种多边形模型非常容易进行渲染,因为他的顶点信息较少而能够降低vertex shader的载荷,并且其简化了多边形毛发的几何形状,使其易于进行back-to-front渲染。
该模型有两种最主要的纹理,其一是用于映射精细结构的映射,另一个是一组用于修补多层毛发之间不协调的不透明纹理。(pancy:毛发之所以需要半透明效果主要是由于毛发的发梢部分是逐渐渐透明的,这个效果是alpha-test不能代替的,但是多层毛发之间的不协调修补则不涉及发梢,可以作为完全不透明的材质)
毛发渲染:
1:漫反射部分:
我们使用了N-L偏移以及一个缩放来模拟漫反射光既:diffuse = max(0,0.75N*L + 0.25)(pancy:我们通常在计算漫反射光的时候是使用N*L作为漫反射角度的,而这篇论文通过上述公式,将头发的漫反射系数压缩到了一个比较小的区域,所计算的漫反射会比通常的计算效果色差更小,亮度更亮,不过大家可以根据自己模型的发质情况选择性的使用这个公式)这种计算漫反射的方法让头发的对光部分更加明亮,并且为整个头发创建了一个柔和的外观。
2:镜面反射高光:
我们用于模拟镜面反射光的基本方法是沿用了 Kajiya-Kay的着色模型,但是我们有更为新颖的做法。Marschner 等人指出头发应该有两种可以被肉眼辨认的镜面反射高光,其一便是头发表面直接所反射的光线,并且这部分光线会集中于头发的顶端。其二便是光线进入到很多头发组成的发束中,经过多次反射进入到观察者眼中(pancy:这个过程类似于环境光模拟要解决的问题,只不过是严格的高光经过了多次反射,如果要严格模拟必然会涉及到ray-tracing,不过这里既没有严格的头发模型,也不可能在实时渲染算法中出现ray-tracing,所以应该会根据这种光线的特点弄一些近似的算法)这种突出的高光颜色取决于头发的本身发色,并且它总是朝着头发的根部,并且外观也很杂乱。
为了模拟Marschner 等人发现的这一视觉效果,我们在每次渲染的时候会输入两个不同的镜面发射光源,这两个镜面反射光的颜色不同,镜面反射指数不同,并且,我们会根据头发丝的方向将这两个镜面反射光转换到相反的两个方向(pancy:大概就是一个顺着头发,一个逆着头发丝吧,这里就是为了模拟上一段提到的两种不同的镜面反射光),对于第二种镜面反射光,我们会通过一个噪声纹理来两次对其高光进行调整,这样就可以以极小的代价来实现类似于真实头发的闪烁外观(pancy:gpu shader没有很多内置函数,例如产生随机数,因此涉及到随机算法一般会传给shader一张由cpu产生的纹理来实现这个效果),为了得到两个沿着头发方向相反的镜面反射光方向,我们可以重新构造头发的表面切向量,通过使用用于各个层头发片的切向量以及头发片的法向量:T’ = normalize(T+s*N),其中s代表修改参数,根据这个参数取正或取负的不同,我们可以分别得到沿着头发指向发根,以及沿着头发指向发梢这两个不同的方向。这里为了修缮细节我们不会对一个头发片决定s,而是从纹理上来获得,这种方法类似于我们常用的法线贴图,只不过这里s是寻找头发的切线细节,所以我们称之为“切线贴图”
(pancy:正如我们之前所说,第二种镜面光想法不错但是实现难度很大,只能是近似模拟,这里他提到了用切线贴图,我解释一下这个名词,如果对于每一层的头发只使用一个切线,也就是那一层的多边形片所在平面切线,那么计算出来的高光就不是针对每个头发的,而是对那个多边形片的,因此他们想到了现在用于记录法线细节的法线贴图算法,也就是把头发的细节记录在一张图片上,当然只是切线细节而不是所有的细节。然后回头用的时候再根据uv坐标还原回去,这样就可以从视觉角度上还原一部分头发信息,当然这种方法是有缺陷的,比如不能平视等,顺便吐槽一下,如今曲面细分技术已经可以在大部分家用机器上实现了,所以这个算法应该可以再进行很大的改进吧例如切线位移贴图神马的云云.....当然只是吐槽,人家ATI或者NVIDA肯定有更好的方法渲染毛发了,今非昔比)
3环境光遮蔽部分:
我们为了简要的表示头发的自阴影,在程序的预处理阶段我们为每个顶点计算了一次环境光遮蔽,在最终的像素处理中,我们将像素的漫反射+镜面发射+ao的颜色综合作为像素输出。(pancy:这里基本上相当于啥都没说^O^,环境光遮蔽算法我说一下,这里基本上可以认为他们用的是SSAO了,环境光遮蔽主要是为了模拟环境光,也就是间接反射光线,跟上面的第二种镜面反射光差不多。不过真要准确模拟代价很大,因此就提出了”计算遮蔽”这种近似的算法。根据一个顶点前面没有完全遮挡住他的点来计算出一个环境光遮蔽值,如论文所述,对于毛发渲染来说这个算法也就是主要模拟头发自己遮蔽自己所造成的阴影)
近似深度排序渲染:
为了实现头发正确的半透明混合,我们必须还原头发模型之前的各个层面的深度顺序。我们在建模的时候就要先进行一遍处理,将个层头发根据它们离头部的距离进行排序,然后根据这个顺序记录将索引存放于静态的索引缓冲当中,然后再渲染的过程中,我们使用4个pass来描述我们的着色模型:
Pass1:
1,激活alpha-test,只允许不透明像素通过测试。
2,关闭背面消隐
3,激活深度缓冲区,设置其为less
4,关闭颜色缓冲区的写操作
这一步的pixelshader只返回alpha信息
Pass2:
1,关闭背面消隐
2,激活深度缓冲区,设置为equal
Pass3:
1,开启背面消隐,消隐掉正面
2,关闭z缓冲区
Pass4:
1,开启背面消隐,消隐掉背面
2,打开z缓冲区,设置其为less
(pancy:ATI著名的4-pass半透明渲染过程,大致思想就是前两个pass找到不透明的部分,把他们先绘制了,避免了透明部分遮住不透明部分这种尴尬的现象。然后再去正反两面绘制半透明部分,这样就能完美的绘制出半透明发梢,并且巧妙的避开了之前多层头发之间的互相遮挡所需要的多层排序问题)
提前的深度剔除( early-Z culling ):
我们之所以在第一个pass开启z缓冲区,因为激活alpha-test会让我们无法开启提前的z消隐(early-Z culling),我们提前将z缓冲区记录,然后使用通过z缓冲的像素来执行pixel shader是一种效率很高的做法。余下的三个pass都会由于这个提前的z缓冲区记录获得性能上的收益。(pancy:early-Z culling是指要求深度测试早于pixelshader,通常来讲,我们的z测试,模板测试等测试都是在pixelshader之后的,而将其提前的话在某些算法里面可以消隐掉很多不该出现的面,节省很多的资源,比如说这里的多层半透明毛发渲染)
我们的头发渲染方案最大的优点就是不需要在运行的每一帧都用cpu对头发进行空间上的排序,但是我们的算法只能对头发模型进行一些“温和”的动画,也就是说不能把头发片移动的过于相关联(pancy:大概就是头发不能完全按照物理模型相关联的受力,比如这片撞到了那一片然后一起运动一类的)。如果不符合这个假设的话。暂时还是只能依靠cpu基础的运行时逐帧毛发排序
标签:
原文地址:http://blog.csdn.net/pancy12138/article/details/51086668