像素位移_[GPU Gems2] 8. 使用距离函数的逐像素位移映射

Chapter 8. Per-Pixel Displacement Mapping with Distance Functions​developer.nvidia.com
bf38e6f71d772ad33bfef1805e838512.png

介绍

本文讲解距离映射(distance mapping),是一种在像素着色器中为物体添加小规模的位移映射的技术。将位移映射看作光线追踪问题,从基表面的纹理坐标开始并计算视线和位移表面的交点的纹理坐标,这需要使用存储了位移距离的位移贴图。

位移映射是为表面添加局部细节的技术,它改变了表面的几何信息,并达到了自遮挡和非多边形轮廓等凹凸映射无法达到的效果。如下图:

4ce1830227ea88699ab764f7f6a7c1c3.png

一般的位移映射方法是迭代细分一个基表面并把顶点位置沿着法线移动,重复该操作直到细分网格大小接近像素大小,在上一章已经做过说明:

Tolo:[GPU Gems2] 7. 带有位移映射的细分表面的自适应镶嵌​zhuanlan.zhihu.com

使用像素着色器的原因是顶点着色器无法生成新顶点(在2004年的技术语境下),使用像素着色器的好处在于当时GPU的像素处理能力大于顶点处理能力,像素着色器更适合访问纹理以及像素处理的数目随距离变化。使用像素着色器的缺点在于不能再着色器中改变像素的屏幕坐标,意味着位移是有限的。

本文所使用的位移渲染是基于球体追踪(sphere tracing)的,球体追踪是用来加速隐表面(implicit surface)的技术,此处不同的是把它用来在GPU上渲染位移。

距离映射算法

不同于传统的位移映射中计算几何体对应的像素,本文的位移映射采用类似光线追踪的方法,找到像素对应的几何体的部分。且基纹理在位移映射表面的上方。通用的光线追踪方法是使用统一的间隔对高度图采样来测试实现是否和表面相交,但会引起采样间隔过大时可能会错过交点的问题,从而产生缝隙或走样,但间隔过密时算法代价则很大,如下图:

45cedfe82dacfccbb4ef7bf331b3f489.png

为了解决该问题,需要额外的信息来使得在不和表面相交的情况下采样间隔足够大,由此引入距离贴图(diatance map)。对于纹理空间的任意点p和表面S,定义函数dist(p, S) = min{d(p, q) : q in S},即p到表面S的最近距离,S的距离贴图时一张为每点p存储dist(p, S)的3D纹理。下图是一维的高度图和它对应的2D距离贴图:

cde03eac5635203a70a7b28a4a8bfcbb.png
A Sample Distance Map in Two Dimensions

生成距离贴图的算法来自于Danielsson (1980),复杂度为o(n),n为像素数,算法思想是生成一张每个像素存储到表面最近点的3D位移向量的3D贴图,并根据邻近位移向量来更新每个像素的位移向量。当所有的位移被计算完毕后,根据每个像素的位移向量的长度和3D纹理深度计算每个像素的距离。

假设有一条原点是p0、方向向量为d的射线(d为单位长度),定义新点p 2 = p 1+ dist(p 1, S) xd,对足够的点采样那么点就能收敛到射线和表面最近的交点,如下图:

f5a1224aea1ae1d00146c5e889136535.png
Sphere Tracing

此时距离映射还是应用于平面,可以通过假设表面是局部平滑的来把距离映射应用到一般网格,使用表面切线把观察向量转换到切线空间,由此在切线空间中实现算法(类似法线映射转化到切线空间计算的原理)

实现和结果

顶点着色器:类似在切线空间做法线映射的着色器,不同点在于观察方向也转换到切线空间,以及加入了和感知深度成反比的系数方便动态调整位移比例。

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;    
}

片元着色器:现在已经有了光线追踪的要素:起点(顶点着色器传来的基纹理坐标),方向(切线空间的视线向量)。首先要归一化方向向量然后把它乘上标准化系数(depth/width, depth/height, 1)(方向向量和距离贴图的单位不同需要转化)。接下来就可以迭代处理光线追踪,先查询距离贴图估计前进距离,然后从当前位置按该距离前进,重复此步骤得到点的序列并收敛于位移的表面。得到交点的纹理坐标后就可以计算切线空间中的法线映射的光照。

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;    
}

结果如下:

4a033a4c9cea1a8c665aa85a71a5ef18.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值