因有相关需求,这里开始学习如何渲染毛发,本章的内容主要来自英伟达相关技术的白皮书介绍,是相关技术的简介。技术英文名:Shells and Fins Technique。
前言
该技术大概是10年前左右出现的,主要是随着DX支持几何着色器,该技术开始流行起来,可以飞速地创建几何体。使用该技术可以在任意三角形网格上渲染毛发。鳍状体(毛发的形状)沿着网格的剪影边(绘画时剪影这一术语很常见,即某一物体,比如角色,所有外围边构成的形状)逐帧创建几何体来渲染。
背景
当时,毛发主要是通过两种方法渲染,要么是将毛发全部作为单独的几何体来渲染,或者通过体积渲染的方式渲染。
第一种暴力方法将每根毛发作为单独的几何体,可以创建很真实的毛发,尤其是在观察者靠近观察或者毛发很长的情况下。然而,大多数的模型要求数百亿的毛发,这样看起来才比较完整,很显然渲染的代价是巨大的。此外,渲染细的毛发束会导致很严重的走样问题,因为还要要解决这些问题,对性能来说更是雪上加霜,实时渲染几乎不可能使用(离线渲染可以这么玩)。
Kajiya和Kay的体积渲染方法在渲染毛发时比较快速,而且在渲染精确几何体时可以避免走样问题。毛发被表示为一张包含针对预生成的毛发的密度、朝向和光照信息的3D纹理。这个体被映射到模型上,通过光线步进技术渲染(ray marching)。在过去,这种技术并没有在实时渲染中考虑,因为光线步进技术需要较长的像素着色器中的循环控制。随着图形硬件的发展,这一需要在目前都不算什么了,因此非常适合在特定模型和场景下使用。
当前的交互式渲染毛发的标准技术是另一种相对于体积渲染的方法,外壳和鳍状体。这一技术是专门针对图形硬件的长处的,使用纹理映射和透明度混合。来自一张预生成的3D毛发纹理中的2D纹理切片被用来应用到从模型挤出的同轴alpha混合过的外壳上。这些外壳在观察方向和表面垂直的区域内的毛发提供了可信的近似模拟,然而,靠近剪影的话,外壳会变得非常透明,他们之间的间隔会变得很清晰。鳍状体用来缓和这一问题。
官方白皮书是针对DX10介绍的技术,这里直接摘抄。为了创建鳍状体,网格需要在所有边上包含退化三角形(即面积为0的三角形)。在DX10中几何着色器可以用来在需要创建的边上创建这些鳍状体,减少包含退化三角形的顶点缓冲的溢出。毛发纹理存储在纹理数组中,这是DX10发布时提出的新技术(目前的API应该都包含),减少对每个外壳过程重绑定一个不同纹理的需要。
技术
这里简要的介绍一下技术。毛发在几个渲染过程中渲染。第一个中,网格使用深度测试和深度写入进行渲染(普通),然后渲染鳍状体,最后在最上面混合外壳。鳍状体从网格的剪影中挤出,使用透明度混合渲染。最后外壳使用透明度混合进行渲染,每次一个,从里到外。
外壳
外壳从里到外渲染,通过将网格渲染n次完成,每次沿着网格法线按照一定数量的比例挤出。每个外壳应用一张近似纹理,来自包含所有2D纹理切片的纹理数组。在渲染外壳时,开启深度测试和透明度混合,但关闭深度值写入。
见下图:

鳍状体
鳍状体被渲染,用来维持剪影边毛发的光感,见下图:

为了渲染鳍状体,我们在几何着色器中处理所有的网格边,并测试每一条边来判断它是否需要被挤出。
为了决定一条边是否是一条剪影边,我们需要检查检查共享该边的三角形是否一个面朝观察者,另一个背朝。为了判断一个三角形是否面朝观察者,我们可以计算法线和观察矢量的点积(这在Phong模型计算漫反射光照时用过):
if dot(vectorToEye, triangleNormal) > 0
//面朝观察者
else
//背朝观察者
为了使用这个测试我们需要计算共享该边的三角形的法线。几何着色器的邻接边输入基本体可以用来同时输入边和两个用来计算法线的对立的顶点:
//这是HLSL的写法,之后会改成GLSL的写法
//挤出鳍状体的几何着色器
[maxvertexcount(4)]
Void GSFins(lineadj VS_OUTPUT inpt[4], inout TriangleStream<GS_OUTPUT_FINS> TriStream)
挤出鳍状体的几何着色器将4个lineadj格式的定点作为输入。根据这些顶点我们可以计算三角形的法线,例如:
N1 = normalize(cross(input[0].Position - input[1].Position, input[3].Position - input[1].Position));
在剪影上的边经历下面的测试:
if(eyeDotN1*eyeDotN2 < 0)
这些边被挤出为鳍状体
结果见下图的A和B:

这个测试检查一个面朝观察者的三角形和一个背朝的三角形的共享边,即剪影边的定义。
另外,任何几乎是剪影边的边,如果通过下面的式子定义:
if (abs(eyeDotN1) < finThreshold || abs(eyeDotN2) < finThreshold)
那么该边也可以挤出为一个鳍状体,用来防止在动画时,毛发会突然出现和消失。
总结
该技术要求使用邻接边索引缓冲,可以在加载模型后生成。这一缓冲编码每条边和它的两个对立顶点作为一个邻接边基本体,可以加载到几何着色器中。
外壳间的间隙是一个取决于模型大小的参数,需要一些拉伸。在官方白皮书推荐的另一篇文章里,推荐了一个参数,即空间间隔为千分之一级别的模型的直径比较好。为了创建尾巴上的更短的毛发,我们绘制到网格的透明度通道的纹理修改器,用来修改毛发的长度。这一修改器与外壳增量相乘,用于在空间上实现毛发的大小变化。没有毛发的区域,如眼睛和拽着,则透明度值为0,在像素着色器中丢弃。
最后,这一技术要求预生成的毛发纹理,这里没有涉及,会在下一章讲解。
网友评论