Per-Pixel Displacement Mapping with Distance Functions
William Donnelly
University of Waterloo
翻译:netghost(netghostcn@gmail.com) 欢迎转载,转载请注明出处
由于本人水平有限,不准确的地方请大家参考原文
Blog:http://netghost.cnblogs.com/
在这一章中,我们介绍距离映射(distance mapping),一种在pixel shader中添加小规模位移映射的技术。我们把位移映射视为一个光线跟踪问题,开始于基础表面的纹理坐标,然后计算观察光线和位移表面的交点的纹理坐标。由于这个原因,我们预先计算一个提供了空间点和位移表面之间距离量度的3维的距离映射。这个距离映射给我们提供快速计算光线与表面交点的全部所需要的信息。我们的算法显著的增加了场景中的感知物体的复杂性,但是却维持了实时的性能。
8.1 简介
Cook (1984)提出了位移映射作为增加表面小规模细节的方法。与凹凸映射之影响表面的明暗不同,位移映射调整表面元素的位置。这就导致凹凸映射不可能的效果,例如:表面之间的遮蔽(occlude)特性和非多边形轮廓。图8-1显示了石头表面的渲染效果,表面之间的遮蔽特性对深度感觉有很大的贡献。
位移映射的通常实现是迭代细分(tessellates)一个基本的表面,把顶点沿着基本表面的法向量方向推出去,这样一直继续下去,直到生成的多边形的大小接近单一像素的大小。Michael Bunnell在本书的第七章中提出了这种方法,“Adaptive Tessellation of Subdivision Surfaces with Displacement Mapping.”
虽然看起来在渲染管线的顶点着色阶段(vertex shading stage)实现位移映射是更合理的,但是这是不可能的,因为顶点着色不能产生新的顶点。这使得我们考虑依赖于像素着色(pixel shader)的技术。因为以下的原因使用像素着色是个好的想法:
●当前的GPUs的像素处理能力比顶点处理能力更强大。举例来说:GeForce 6800 Ultra有16个像素渲染管线,而顶点染管线只有6个。另外,一个单独的像素渲染管线在一个时钟周期内通常比单独顶点渲染管线执行更多的操作。
●像素着色器更适合访问纹理。许多GPUs在像素着色阶段不允许访问纹理;正是由于这个原因,访问模式是受限制的,会带来性能上的开销。
●带有距离的像素处理的规模。顶点着色总是在模型的每个顶点上执行一次,但是像素着色确是在屏幕的每个像素上执行一次。这意味着工作集中在临近的目标,这也是最需要的。
使用像素着色的缺点是我们不能在shader内部改变像素的屏幕坐标。这意味着与基于细分的方法不同,使用像素着色的方法不能被使用在任意大的位移。可是这并不是严格的限制,因为在实际中位移几乎总是有限制的。
8.2 之前的工作
视差映射(Parallax mapping)是一种增加凹凸映射来包含视差效果的简单的方法(Kaneko et al. 2001).视差映射使用表面上单一点的高度信息来向观察者或者远离观察者偏移纹理坐标。虽然这是一种仅在平稳变化的高度场上才有效的粗糙的近似,但是却得到了好的令人惊讶的结果,尤其是它可以仅在三个额外的shader指令中实现。不幸运的是,因为这种技术所固有的假设,它不能处理巨大的位移,高频的特征或者遮蔽特性
浮雕映射(Relief mapping)是由Policarpo (2004)提出的,它在高度映射(height map)上使用一种根查找的方法。它开始于把观察实现变换到纹理空间。然后执行一个线性的查找来定位与表面的交点,跟随着二分查找的是找到一个精确的交点。不幸运的是线性查找需要确定的步幅大小。这意味着为了解决小的细节,必需增加步幅的数目,强迫用户在精确性和性能之间做出权衡。
视点相关的位移映射(Wang et al. 2003)和它的扩展,通用的位移映射(Wang et al. 2004),把位移映射看作是光线跟踪问题。它在5维映射中保存了所有可能的光线交点查询的结果的3维体积,这个5维的映射是以3维位置坐标和两个角度坐标为索引的。这个映射然后使用奇异值分解压缩,在运行时使用像素着色解压缩。因为数据是5维的,需要大量的预处理和存储器。
我们这里提出的位移渲染得算法是基于球体跟踪(Hart 1996)的,一种为了加速隐式表面的光线跟踪而开发的技术。虽然在概念上是相同的,但是不在隐式表面的光线跟踪上应用这个算法,而是在GPU上渲染位移映射是应用它。
8.3 距离映射算法
假设我们想在一个平面上应用位移映射。我们可以想象表面被轴对齐的包围盒包围。传统的位移映射算法将渲染包围盒的底面,并把顶点推向上。我们的算法改为替换包围盒的上表面。然后在shader内部,我们将得到位移表面上的那个点是观察者实际看到的。
在场景中,我们将处理与传统的位移映射相反的问题。位移映射询问的是“对于几何体的片元,将被映射到图像中的那个像素?”,而我们算法询问的是,图像中的这个像素,“我们将看到哪个几何体片元?”第一个方法被光栅算法使用,第二个方法被光线跟踪算法使用。因此我们把距离映射视为一个光线跟踪问题。
一个普通的光线跟踪方法在单位间隔位置的高度映射(height map)上采样,来测试视线是否与表面相交。不幸运的是,我们将面临下面的问题:只要我们的采样间隔大于单一纹理元素,我们不能保证没有遗漏在我们的采样之间的交点。这个问题在图8-2中说明。这些“过辐射(overshoots)”会造成渲染得几何体上存在缝隙,结果导致严重的走样。这样我们有两个选择:我们或者接受这种低采样下的古董,或者我们必须为沿着视线的每个纹理元素采样。图8-3显示了我们的算法可以以很好的细节渲染物体而几何体上没有任何的走样或者空隙。
我们可以从前面的例子中得出两个结论:
1. 我们不能简单的以固定的间隔查询高度映射。它要么造成低采样的,要么就会产生难以处理的算法的结果。
2. 在处理中我们需要比高度场更多的信息;我们需要知道在任何的给定区域,我们的采样到什么程度而不会产生过辐射表面。
为了解决这些问题,我们定义了表面的距离映射。对于纹理空间的任何的给定点p 和表面S,我们定义一个函数dist(p, S) = min{d(p, q) : q in S}. 换句话说,dist(p, S)是p 到表面 S 的上最近点的最短距离。S 的距离映射就是简单的3D纹理,为每个点p 保存了dist(p, S)的值。图8-4显示了一个1维高度映射和它对应的距离映射的例子。
这个距离映射给我们提供了需要的选择采样位置的正确的信息。假设我们有一个起点在p0,方向为向量d的光线,d已经规格化为单位长度。我们定义一个新的点p1 = p0 + dist( p0, S) × d.这个新点的重要特性就是p0 在表面的外侧,p1 也在表面的外侧。然后我们通过定义p2 = p1 + dist(p1, S) × d应用相同的操作,等等。每个连续的点都更接近表面一些。这样,要是我们做了足够的采样,我们的点汇聚在光线与表面的最近焦点上。图8-5说明了这个算法的效力。
距离函数不应用到高度映射上是没有任何意义的。实际上,距离映射可以表示任意的voxelized的数据。这意味着渲染带有复杂布局的小规模细节是有可能的。例如:chain mail 可以以这种方式渲染。
8.3.1任意的网格
到现在为止,我们仅仅讨论在平面上应用距离映射。我们很愿通常意在网格上应用距离映射。我们通过假设表面是局部平坦的来这样做。基于这个假设,我们可以使用局部的切线框架,像平面的情形执行相同的计算。我们使用局部表面的切线来变换观察向量到切线空间,正像我们将要变化光向量到切空间法向映射。一旦我们将观察光线变化到切空间,算法就会像平面情形一样正确执行。
既然我们知道怎样使用距离映射进行光线跟踪,我们需要知道怎样有效的为任意的高度场计算距离映射。
8.4 计算距离映射
计算距离变换是一个已经仔细研究过的问题。Danielsson (1980)提出了一个O(n)时间的算法,这里的n 是映射中像素的数目。想法是创建一个3维的映射,映射的每个像素保存一个到达表面最近点的3维的位移向量。算法然后执行一个3维范围的很小的连续交换,基于临近的位移向量更新每个像素的位移向量。一旦位移被计算完成,每个像素的距离使用大量的位移计算。这个算法的实现在本书的CD中。
当距离变化算法完成,我们已经计算了距离映射中每个像素到表面最近点的距离,使用像素衡量。为了使距离在范围[0, 1]之间,我们把每个距离除以3D纹理中像素的深度。
8.5 The Shaders
8.5.1顶点着色
距离映射的顶点着色程序,如Listing 8-1所示,与切空间的法向映射十分的相似,但是有两个显著的不同。第一个是除了把光源向量变换到切空间,我们还把视线方向变换到切空间。这个切空间的视点向量被用在顶点着色中作为在纹理空间中被跟踪的光线的方向。第二个不同是我们以感知深度的反比例来加入一个额外的因子。这使得我们交互的调整位移的比例。
8.5.2片断着色
现在,我们已经有了我们所需的开始光线进展的全部信息:我们拥有一个开始点(从顶点着色向下传递的基本的纹理坐标)和一个方向(切空间的视线方向)。
片断着色要做的第一件事就是单位化方向向量。注意存贮在距离映射中的距离是以对像素的单位比例来衡量的;方向向量是以纹理坐标的为单位来衡量的。通常,距离映射比它的深处更高,更宽,因此这两种距离的度量是完全不同的。为了确保我们的向量是使用距离映射中的距离度量来规格化的,我们首先规格化方向向量,然后把规格化向量和额外的标准因子(depth/width, depth/height, 1)相乘。
Listing 8-1. The Vertex Shader
v2fConnector distanceVertex(a2vConnector a2v,
uniform float4x4 modelViewProj,
uniform float3 eyeCoord,
uniform float3 lightCoord,
uniform float invBumpDepth)
{
v2fConnector v2f;
// Project position into screen space
// and pass through texture coordinate
v2f.projCoord = mul(modelViewProj, float4(a2v.objCoord, 1));
v2f.texCoord = float3(a2v.texCoord, 1);
// Transform the eye vector into tangent space.
// Adjust the slope in tangent space based on bump depth
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;
// Transform the light vector into tangent space.
// We will use this later for tangent-space 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;
}
现在可以开始我们的光线迭代过程了。开始查询距离映射,得到一个与表面不相交的保守的距离估计,我们可以沿着光线向前。然后,我们可以使用那个距离使我们当前的位置向前来得到表面外的另一个点。我们可以以这种方式继续下去生成汇聚在位移表面的一系列的点。最后,一旦我们得到交点的纹理坐标,我们在切空间计算法向映射的照明。Listing 8-2显示了片断着色。
Listing 8-2. The Fragment Shader
f2fConnector distanceFragment(v2fConnector v2f,
uniform sampler2D colorTex,
uniform sampler2D normalTex,
uniform sampler3D distanceTex,
uniform float3 normalizationFactor)
{
f2fConnector f2f;
// Normalize the offset vector in texture space.
// The normalization factor ensures we are normalized with respect
// to a distance which is defined in terms of pixels.
float3 offset = normalize(v2f.tanEyeVec);
offset *= normalizationFactor;
float3 texCoord = v2f.texCoord;
// March a ray
for (int i = 0; i < NUM_ITERATIONS; i++) {
float distance = f1tex3D(distanceTex, texCoord);
texCoord += distance * offset;
}
// Compute derivatives of unperturbed texcoords.
// This is because the offset texcoords will have discontinuities
// which lead to incorrect filtering.
float2 dx = ddx(v2f.texCoord.xy);
float2 dy = ddy(v2f.texCoord.xy);
// Do bump-mapped lighting in tangent space.
// ‘normalTex’ stores tangent-space normals remapped
// into the range [0, 1].
float3 tanNormal = 2 * f3tex2D(normalTex, texCoord.xy, dx, dy) - 1;
float3 tanLightVec = normalize(v2f.tanLightVec);
float diffuse = dot(tanNormal, tanLightVec);
// Multiply diffuse lighting by texture color
f2f.COL.rgb = diffuse * f3tex2D(colorTex, texCoord.xy, dx, dy);
f2f.COL.a = 1;
return f2f;
}
8.5.3过滤的说明
当采样纹理时,我们必须注意怎样指定纹理查找的派生(derivatives),通常,位移纹理坐标是不连续的(例如:由于sections of the texture that are occluded)当分级细化纹理(mipmapping)或者各向异性的过滤打开的时候,GPU需要纹理坐标的派生信息。因为GPU近似的从有限的微分派生,派生在间断处会有不正确的值。这导致不正确的分级细化纹理层次的选择,反过来导致在不连续处可见的接缝。
不使用位移纹理映射坐标的派生,我们使用基本纹理坐标来代替派生。因为位移纹理坐标始终是连续的,所以这是可行的,它们在基本纹理坐标的相同比率上近似的有所不同。因为我们没有使用GPU的机制来决定分级细化纹理层次,可能会产生纹理的走样。在实际中,这并不是个大问题,因为基本纹理坐标的派生是位移纹理坐标的派生的很好的近似。
注意关于分级细化纹理层次的相同的参数同样被应用在距离映射的查找中。因为纹理坐标在特征边缘的周围可能是不连续的,纹理单元可能会访问能过于粗糙的分级细化纹理层次。这反过来导致不正确的距离值。我们的解决办法是不使用分级细化纹理,线性的过滤距离映射。因为距离映射的值不会直接的显示,这里就不会产生缺少分级细化纹理的走样。
8.6 结论
我们使用OpenGL,Cg 和Sh (http://www.libsh.org)实现了距离映射算法。本章中的图片是使用Sh的实现创建的,在附带的CD中提供。因为依赖大量的纹理读取和使用派生指令,这个实现只在GeForce FX 和GeForce 6系列的GPUs上可以工作。
本章的所有例子中,我们使用一个大小为256×256×16的3D纹理,但是这个选择是高度数据相关的。我们同样在一个512×512×32大小的映射上试验更复杂的数据集。在我们的例子中,设定NUM_ITERATIONS的值为16次迭代。在大多数情况下,这是足够的了,使用8次迭代就可以满足光滑的数据集。
我们的片断着色程序,假定16次迭代,为GeForce FX编译48条指令。每次循环迭代有两条指令:一条纹理查找,跟随着一条多重加法。GeForce FX 和GeForce 6系列的GPUs上可以在一个时钟周期内执行这两条指令,意味着每次迭代只占用一个时钟周期。应该注意的是每次纹理查找依赖于前一次;在有限制的纹理操作的GPUs上,这个shader需要被拆成多遍(multiple passes)执行。
我们在GeForce FX 5950 Ultra 和GeForce 6800 GT上使用不同的数据集测试了我们的距离映射的实现。在图8-6所示的数据集上,在GeForce FX 5950 Ultra,我们可以渲染1024×768分辨率的照明近似的在每秒30帧。如果我们把迭代次数减少到8,我们在相同的分辨率下大约得到每秒75帧。在GeForce 6800 GT上,甚至使用16次迭代,照明1280×1024分辨率像素,我们可以恒定的运行在每秒70帧。
8.7 Conclusion
8.7 结论
我们已经介绍了距离映射,一个基于隐式表面的光线跟踪的位移映射的快速迭代的技术。我们展示了距离函数包含的内容,使得我们在光线与表面很远时采用较大的步幅,而不会大到以至于在渲染的集合体上产生缝隙的程度。实现的结果是非常高效的;它包含了少数的迭代,并且每次迭代仅仅花费GeForce FX 和GeForce 6系列的GPUs上的一个时钟周期。
将来,我们将使用Shader Model 3 GPUs的动态分支的能力来提高我们的技术的性能,对像素使用“early outs”使得会聚更快。这使得我们增加技术的质量,关注于更强的像素的计算能力…
我们也十分愿意使我们的技术适合于曲面。虽然我们的算法可以被使用在任何代有适当的切向量的模型中,但是可能会在高度弯曲的区域导致扭曲变形。在实际中,通常的位移映射(Wang et al. 2004)使用一类四面体的投影来考虑曲面,而且这个方法同要可以被应用到我们的算法中。这种四面体投影可以在顶点着色中完成(Wylie et al. 2002)。
8.8 References
Cook, Robert L. 1984. “Shade Trees.” In Computer Graphics (Proceedings of SIGGRAPH84) 18(3), pp. 223–231.
Danielsson, Per-Erik. 1980. “Euclidean Distance Mapping.” Computer Graphics and Image Processing 14, pp. 227–248.
Hart, John C. 1996. “Sphere Tracing: A Geometric Method for the Antialiased Ray Tracing of Implicit Surfaces.” The Visual Computer 12(10), pp. 527–545.
Kaneko, Tomomichi, Toshiyuki Takahei, Masahiko Inami, Naoki Kawakami, Yasuyuki Yanagida, Taro Maeda, and Susumu Tachi. 2001. “Detailed Shape Representation with Parallax Mapping.” In Proceedings of the ICAT 2001 (The 11th International Conference on Artificial Reality and Telexistence), Tokyo, December 2001, pp. 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, and Heung-Yeung Shum. 2003. “View-Dependent Displacement Mapping.” ACM Transactions on Graphics (Proceedings of SIGGRAPH 2003) 22(3), pp. 334–339.
Wang, Xi, Xin Tong, Stephen Lin, Shimin Hu, Baining Guo, and Heung-Yeung Shum.2004. “Generalized Displacement Maps.” In Eurographics Symposium on Rendering 2004, pp. 227–234.
Wylie, Brian, Kenneth Moreland, Lee Ann Fisk, and Patricia Crossno. 2002. “Tetrahedral Projection Using Vertex Shaders.” In Proceedings of IEEE Volume Visualization and Graphics Symposium 2002, October 2002, pp. 7–12. Special thanks to Stefanus Du Toit for his help implementing the distance transform and writing the Sh implementation.