使用距离函数的逐像素位移映射

来源:互联网 发布:知乎邀请回答 编辑:程序博客网 时间:2024/05/16 09:59

第8章
使用距离函数的逐像素位移映射
William Donnelly
Waterloo大学
在本章中,我们将演示distance map,一种在pixel shader中给对象添加小尺度位移映射的技术。我们把位移映射看作一个光线跟踪的问题,从基表面的纹理坐标开始,计算观察射线相交于偏移后表面的纹理坐标。为了达到这个目的,我们预计算一个三维distance map,它给定了空间中的点和偏移后表面距离的量度。这个distance map给了我们所有快速计算射线和表面相交所需的信息。我们的算法在保持了实时性能的情况下显著地提升了场景可以感知的几何复杂度。
8.1 导读
Cook(1984)引入了位移映射的方法,可以用于向表面增加小尺度细节。不像凹凸映射只影响表面的着色,位移映射调整了表面元素的位置。这可以形成凹凸映射不可能做到的效果,比如互相遮挡的表面特征和非多边形轮廓。图8-1演示了石头表面的渲染,互相的遮挡的特征造成了纵深的感觉。
位移映射一般的实现方式是反复镶嵌一个基表面,沿着基表面的法线挤出顶点,然后重复这个过程,直到产生的多边形接近于一个像素的大小。Michael Bunnell在本书的第7章演示了这个方法,“用于位移映射的自适应表面细分镶嵌”。
Figure 8-1. 一个偏移的石头表面 位移映射(上)给了凹凸映射(下)不可能做到的纵深的感觉。
虽然在流水线的顶点着色阶段实现位移映射看起来更合逻辑,但因为vertex shader没有产生新顶点的能力,所以那是不可能的。这时我们考虑基于pixel shader的技术。使用pixel shader有几个好处:
• 目前GPU的像素处理能力远大于顶点处理能力。例如,GeForce 6800 Ultra有16个像素着色流水线,但只有6个顶点着色流水线。另外,一个像素流水线在每个时钟周期经常可以比顶点流水线进行更多的操作。
• pixel shader访问纹理更容易。很多GPU不允许在vertex shader中访问纹理;而对于允许的,访问模式也有限制,而且会有性能开销。
• 像素处理的数量随着距离而放缩。vertex shader总是对模型中的每个顶点执行一次,但pixel shader只对屏幕上的每个像素执行一次。这意味着工作集中于临近的对象,当然它们也是最重要的。
使用pixel shader的缺点是我们不能在shader中改变像素的屏幕坐标。这意味着不像基于镶嵌的方法,使用pixel shader的方法不能用于任意大小的距离。但是,这也不是一个严格的限制,因为在实际中位移几乎总是有限的。
8.2 以前的工作
Parallax mapping是扩展bump mapping的一种简单方式,可以包含视差效果(Kaneko等,2001)。Parallax mapping使用关于表面一个点的高度信息来使纹理坐标靠近或远离观察者。虽然这是一个仅对平滑变化的高度场有效的粗糙近似,但它给出了惊人的好结果,特别是它只需增加三条shader指令就能实现。不幸的是,由于这项技术的内在问题,它不能处理大位移、高频特征或特征间的遮挡。
Policarpo(2004)演示的relif mapping使用了在height map上的根查找方法。它先把视线转换到纹理空间,然后进行线性搜索来定位与表面的交点,接着进行二分搜索来找到精确的交点。但是,线性搜索需要一个固定的步长。这意味着为了解决小的细节,就有必要增加步数,这就等于强迫用户以性能换取精度。我们的算法不需要这样的代价:它自动根据需要调整步长,在接近表面的地方提供良好的细节,而忽略远离表面的大片空间。
依赖于视点的位移映射(Wang等,2003)和它的扩展,通用位移映射(Wang等,2004),把位移映射看作光线跟踪问题。它们把所有可能的射线交点查询的结果存储在由三个位置坐标和两个角坐标来索引的五维map中。然后这个map用奇异值分解压缩到三维体中,然后在运行期由pixel shader解压。因为数据是五维的,所以需要大量的预处理和存储空间。
我们在这里演示的位移渲染算法的基础是球体跟踪(Hart 1996),一个用于加速隐表面光线跟踪技术。概念是一样的,但我们把该算法应用于在GPU上渲染位移映射,而不是把它用于隐表面的光线跟踪。
8.3 距离映射算法
假设我们要把位移映射应用到一个表面。我们可以想象这个表面被一个轴对齐的盒子包围。传统的位移算法将渲染盒子的底面然后把顶点往上推。而我们的算法则是渲染盒子的顶面,然后在shader中寻找在偏移表面上观察者可以看到的点。
从某种意义上说,我们计算了和传统位移映射相反的问题。位移映射问,“对于这片几何体,它映射到了图像中的哪个像素?”我们的算法问,“对于图像中的这个像素,我们可以看到哪片几何体?”第一个问题利用了光栅化算法,第二个问题利用了光线跟踪算法。所以我们的把距离映射看作光线跟踪问题。
一个常见的光线跟踪方法是以均匀的距离采样height map,测试视线是否和表面相交了。不幸的是,我们遇到了下列问题:如果我们的采样距离远大于一个texel,就不能保证在样本之间没有错过一个交点。这个问题在图8-2中表明。这些“越过目标”可能在渲染的几何体上产生缝隙,也可能造成剧烈的锯齿。因此我们有两个选择:接受低采样的artifact,或必须采样到视线上的每个texel。图8-3演示了我们的算法可以以很好的细节渲染物体,几何体上没有任何锯齿或裂口。
图8-2. 位移算法很难解决一个情况 固定步长的算法错过了一个重要的几何片。
图8-3. 渲染偏移的文字 因为需要很好的细节,文字对一些逐像素位移算法来说是一个很难解决的情况,特别是基于均匀距离采样的算法。注意我们的算法可以正确地捕捉字母间的遮挡,没有锯齿。
我们可以从前面的论述中得到两个结论:
1. 我们不能简单地以固定的间隔查询height map。它会导致低采样artifact或变成一个很难处理的算法。
2. 我们需要拥有比高度场更多的信息;我们还需要知道在任意给定区域可以在不越过表面的情况下对多远之外采样。
为了解决这些问题,我们定义了表面的distance map。对于纹理空间中的任意点p和表面S,我们定义一个函数dist(p, S) = min{d(p, q) : q in S}。换句话说,dist(p, S)是从p到表面S上最近点的最短距离。然后S的distance map可以简单地存储成3D纹理,对于每个点p,可以得到dist(p, S)的值。图8-4演示了一个一维height map和它对应的distance map的例子。
distance map给了我们选择采样位置所需要的精确信息。假设我们有一条原点为p0、方向向量为d的射线,其中d归一化到单位长度。我们定义一个新点p1 = p0 + dist(p0, S) × d。这个点有一个重要的属性,如果p0在表面之外,那么p1也在表面之外。然后我们再次用同样的操作来定义p2 = p1 + dist(p1, S) × d,等等。每个连续的点都靠近表面一些。因此,如果我们采了足够的点,那么点就能收敛到射线和表面最近的交点。图8-5阐明了该算法的效力。
图8-4. 二维distance map的例子 左上:一个一维height map,写成数组。左下:height map形象化为二维。右:对应的二维distance map。
图8-5. 球体跟踪 一个从点p0开始的射线。然后我们把距离确定为到表面上最近的点。在几何上,我们可以想象在p0周围扩大一个球体S0,直到它和表面相交。点p1是射线和球体的交点。我们重复这个过程,产生点p2..p4。从图中我们可以看到点p4实际上是和表面的交点,所以在这个例子中,该算法在四次迭代时收敛。
一个值得注意的地方是,距离函数不仅可以应用于高度场。实际上distance map可以再现任意voxel化的数据。这意味着可渲染拓扑复杂的小尺度细节。比如,锁子甲可以用这种方法渲染。
8.3.1 任意网格
直到这里,我们只讨论了把距离映射应用到平面。通过假设表面是局部平坦的,就能把距离映射应用到一般网格。基于这个假设,我们使用局部表面的正切结构来进行和平面情况一样的计算。我们使用局部表面正切来把观察向量转换到正切空间,正如我们在normal mapping中把光向量转换到正切空间一样。一旦我们把视线转换到正切空间,这个算法就能和完全平面的情况一样地处理了。
现在我们知道了怎么使用distance map进行光线跟踪,我们还需要知道怎么对任意高度场有效地计算distance map。
8.4 计算Distance Map
计算距离变换是一个经过透彻研究的问题。Danielsson(1980)演示了一个以O(n)运行的算法,其中n是map中的像素数目。想法是建立一个3D map,它的每个像素保存了到表面上最近点的3D距离向量。然后这个算法掠过3D空间,利用相邻像素的位移向量更新每个像素的位移向量。一旦得到了位移,那么每个像素的距离就是位移的大小。这个算法的一个实现在本书的CD上。
当距离变换算法完成后,我们就计算出了在distance map中从每个像素到表面上最近点的距离,单位是像素。要让这些距离分布于[0, 1]区间,我们把每个距离除以以像素为单位的3D纹理深度。
8.5 Shader
8.5.1 Vertex Shader
distance mapping的vertex shader在表8-1,非常相似于正切空间normal mapping的vertex shader,但有两个值得注意的不同。第一个是除了把光向量转换到正切空间外,我们也把观察方向转换到正切空间。正切空间中的眼向量在vertex shader中作为纹理空间中射线跟踪的方向。第二个不同是我们加入了一个和感觉深度成反比的系数。这允许我们交互地调整位移的比例。
8.5.2 Fragment Shader
现在我们拥有了光线跟踪所需的所有东西:我们有一个起点(从vertex shader传来的基纹理坐标)和一个方向(正切空间的视向量)。
Fragment shader要做的第一件事是归一化方向向量。注意保存在distance map中的距离是以像素的单位比例为单位的,而方向向量是以纹理坐标为单位。一般来说,distance map的宽度和高度要比深度多得多,所以这两个距离的单位非常不同。所以要保证我们的向量是由distance map使用的距离单位归一化的,我们先归一化方向向量,然后把归一化的向量乘上(depth/width, depth/height, 1)的“归一化系数”。
表8-1. Vertex Shader
v2fConnector distanceVertex(a2vConnector a2v,
uniform float4x4 modelViewProj,
uniform float3 eyeCoord,
uniform float3 lightCoord,
uniform float invBumpDepth)
{
v2fConnector v2f;
// 把位置投射到屏幕空间
// 并传递纹理坐标
v2f.projCoord = mul(modelViewProj, float4(a2v.objCoord, 1));
v2f.texCoord = float3(a2v.texCoord, 1);
// 把眼向量转换到正切空间
// 根据凹凸深度调整正切空间中的斜率
float3 eyeVec = eyeCoord - a2v.objCoord;
float3 tanEyeVec;
tanEyeVec.x = dot(a2v.objTangent, eyeVec);
tanEyeVec.y = dot(a2v.objBinormal, eyeVec);
tanEyeVec.z = -invBumpDepth * dot(a2v.objNormal, eyeVec);
v2f.tanEyeVec = tanEyeVec;
// 把光向量转换到正切空间
// 我们将在后面用它进行正切空间normal mapping
float3 lightVec = lightCoord - a2v.objCoord;
float3 tanLightVec;
tanLightVec.x = dot(a2v.objTangent, lightVec);
tanLightVec.y = dot(a2v.objBinormal, lightVec);
tanLightVec.z = dot(a2v.objNormal, lightVec);
v2f.tanLightVec = tanLightVec;
return v2f;
}
现在我们可以迭代地处理光线跟踪了。一开始先查询distance map,保守地估计一个可以前进且没有与表面相交的距离。我们可以用这种方式产生一个点的序列并收敛于偏移的表面。最后,一旦我们得到了交点的纹理坐标,我们就可以计算正切空间中的normal map光照。表8-2演示了fragment shader.
8.5.3 关于过滤的注意事项
当采样了纹理后,我们必须小心如何指定纹理查找的导数。一般来说,偏移的纹理坐标是不连续的(例如,因为纹理区域间的遮挡)。当mipmap或各向异性过滤开启时,GPU需要关于纹理坐标的导数的信息。因为GPU通过有限差来近
似求导,所以导数在间断点会不正确。这将导致错误地选择mipmap层,于是会在间断点周围产生明显的接缝。
我们使用基纹理坐标的导数来代替偏移的纹理坐标的导数。因为基纹理纹理坐标总是连续的,而且它们的变化速率近似于偏移后的坐标,所以这是可行的。
因为我们不使用GPU的机制来确定mipmap层,所以可能会有纹理走样。实际上,这不是大问题,因为基纹理坐标的导数很好地近似于偏移后的纹理坐标。
注意关于mipmap层的观点同样也可以应用于distance map的查找。因为在特征边缘周围纹理坐标可能间断,所以纹理单元会访问很粗糙的mipmap层。这会造成错误的偏移值。我们的解决方案是线性地过滤distance map,不用任何mipmap。因为distance map值从来不会被直接看到,而缺乏mipmap并不会造成走样。
8.6 结果
我们用OpenGL和Cg实现了distance-mapping算法,也用Sh(http://www.libsh.org)实现了。本章中的图片是用Sh实现建立的,可以在配套CD中找到。因为有很多关于纹理的读取并使用了导数指令,所以这个实现只能在GeForce FX和GeForce 6系列GPU上工作。
表8-2. Fragment Shader
f2fConnector distanceFragment(v2fConnector v2f,
uniform sampler2D colorTex,
uniform sampler2D normalTex,
uniform sampler3D distanceTex,
uniform float3 normalizationFactor)
{
f2fConnector f2f;
// 归一化纹理空间中的偏移向量。
// 归一化系数确保我们是用对应的以像素为单位的距离来归一化
float3 offset = normalize(v2f.tanEyeVec);
offset *= normalizationFactor;
float3 texCoord = v2f.texCoord;
// 进行光线跟踪
for (int i = 0; i < NUM_ITERATIONS; i++) {
float distance = f1tex3D(distanceTex, texCoord);
texCoord += distance * offset;
}
// 计算原先纹理坐标的导数。
// 这是因为偏移的纹理坐标会不连续,
// 将会导致不正确的过滤。
float2 dx = ddx(v2f.texCoord.xy);
float2 dy = ddy(v2f.texCoord.xy);
// 在正切空间中进行凹凸贴图的光照。
// 'normalTex'保存了正切空间中重新映射到区间[0, 1]中的法线。
float3 tanNormal = 2 * f3tex2D(normalTex, texCoord.xy, dx, dy) - 1;
float3 tanLightVec = normalize(v2f.tanLightVec);
float diffuse = dot(tanNormal, tanLightVec);
// 把散射光乘上纹理颜色
f2f.COL.rgb = diffuse * f3tex2D(colorTex, texCoord.xy, dx, dy);
f2f.COL.a = 1;
return f2f;
}
本章中的所有例子用的都是大小为256×256×16的3D纹理,但这个选择高度依赖于数据。我们也对复杂的数据集试验了大到512×512×32的纹理。在例子中我们把NUM_ITERATIONS设置为16次迭代。在大部分情况下,这都绰绰有余,8次迭代就能满足平滑的数据集。
假设进行了16次迭代,我们的fragment shader在GeForce FX上可以编译成48条指令。循环的每次迭代有两条指令:一次纹理获取和一次乘加。GeForce FX和GeForce 6系列GPU可以在一个时钟周期内执行这对操作,这意味着每次迭代只花费一个时钟周期。必须注意的是每次纹理获取依赖于前一个;在限制间接纹理操作个数的GPU上,这个shader需要分解为多遍。
我们在GeForce FX 5950 Ultra和GeForce 6800 GT上用多组不同的数据集测试了我们的distance-mapping实现。其中一个数据集是图8-6。在GeForce FX 5950 Ultra上,我们可以以大约30帧每秒的速度在1024×768的分辨率下渲染每个像素。如果我们把迭代次数减少到8,在同样的分辨率下可以得到大约75帧每秒。在GeForce 6800 GT上,我们可以稳定地运行在70帧每秒,甚至在1280×1024的分辨率下用16次迭代渲染每个像素。
8.7 结论
我们演示了distance mapping,一种基于隐表面光线跟踪的快速迭代的位移映射技术。我们演示了距离函数中包含的信息允许我们在光线远离表面时前进更大的距离,并保证我们不会跨得太远以至于在渲染的几何体上产生缝隙。实现的结果非常高效:会在很少的迭代次数内收敛,而且每次迭代在GeForce FX和GeForce 6系列GPU上只花费一个时钟周期。
以后,我们将通过对很快收敛的像素采取“early outs”策略的方法充分利用Shader Model 3 GPU的动态分支能力来改进本技术的性能。这使我们可以通过把更多的计算能力投入于收敛很慢的像素上来提升本技术的质量。
我们也想把本技术运用于曲面。虽然我们的算法可以用于有适当正切的任意模型,但在高度弯曲的区域它会造成扭曲变形。实际上,通用位移映射(Wang等,2004)使用一种四面体映射来解决曲面问题,而这种方法也能应用于我们
的算法。这样的四面体映射可以在vertex shader中完成(Wylie等,2002)。
8.8 参考
Cook,Robert L,1984。《Shade Trees》,Computer Graphics(SIGGRAPH 84学报)18(3),223-231页。
Danielsson,Per-Erik,1980。《Euclidean Distance Mapping》,Computer Graphics and Image Processing 14,227-248页。
Hart,John C,1996。《Sphere Tracing: A Geometric Method for the Antialiased Ray Tracing of Implicit Surfaces》,The Visual Computer 12(10),527-545页。
Kaneko,Tomomichi、Toshiyuki Takahei、Masahiko Inami、Naoki Kawakami、Yasuyuki Yanagida、Taro Maeda和Susumu Tachi,2001。《Detailed Shape Representation with Parallax Mapping》,ICAT 2001学报(The 11th International Conference on Artificial Reality and Telexistence),东京,2001年十二月,205-208页.
Policarpo,Fabio,2004。《Relief Mapping in a Pixel Shader Using Binary Search》,http://www.paralelo.com.br/arquivos/ReliefMapping.pdf
Wang,Lifeng、Xi Wang、Xin Tong、Stephen Lin、Shimin Hu、Baining Guo和Heung-Yeung Shum,2003,《View-Dependent Displacement Mapping》,ACM Transactions on Graphics (SIGGRAPH 2003学报) 22(3),334-339页。
Wang,Xi、Xin Tong、Stephen Lin、Shimin Hu、Baining Guo和Heung-Yeung Shum,2004,《Generalized Displacement Maps》,Eurographics Symposium on Rendering 2004,227-234页。
Wylie,Brian、Kenneth Moreland、Lee Ann Fisk和Patricia Crossno,2002,《Tetra-hedral Projection Using Vertex Shaders》,IEEE Volume Visualization and Graphics Symposium 2002学报,2002年十月,7-12页。
特别感谢Stefanus Du Toit,它实现了距离变换并写了Sh实现。

 

原创粉丝点击