本文以nVidia的ShaderLibrary中的ReliefMap特效为范例介绍一下在PixelShader中通过深度图实现遮挡的方法。
参考文章:《Real-Time Relief Mapping on Arbitrary Polygonal Surfaces》 by F´abio Policarpo & Manuel M. Oliveira & Jo˜ao L. D. Comba
注意:这张深度图Alpha值高的地方表示越凸
概念:
ReliefMap的基本思想和ParallaxMap完全一致:就是给通过光栅化计算出的贴图坐标加上一个偏移量来确定在凹凸的表面上这个坐标应该在的位置。区别是ParallaxMap仅仅是简单的根据高度沿着视方向移动UV,这样在深度高的时候就会出现严重的走样,但是ReliefMap则是通过确定视向量和凹凸表面的交点来计算偏移量的,如下图所示:
图中"点A"为光栅化计算出的贴图坐标,但因为我们的表面是凹下去的,所以摄像机看到的点应该是"点3",要实现这一点,我们就要计算出红色箭头所表示的那段偏移量,也就是要算出"点3"的坐标。
实现思想:
下面我们要做的就是确定一条从点A发出的方向为Vn的射线和这个凹凸表面的交点。这里要用到一些数值分析中的东西,就是二分法(Binary Search)和Linear Search(这东西我不知道它的中文应该是什么......)。
首先我们使用Linear Search方法确定这条射线最早同曲面所交点的大概位置,所谓LinearSearch其实就是沿着Vn方向按照一个指定的深度的步长"d"去找到那个第一个进入物体的位置所在的有根区间,如图所示:
点2在表面外而点3在表面内,于是我们可以确认交点必定在点2和点3之间的这段区间上。下面我们就在这段区间中使用二分法来进一步确定交点的位置,如下图所示:
剩下的最后一个问题就是如何确定ds,又要涉及到倒霉的视空间向切向空间的变换,以下就是浮雕贴图的PixelShader的片段,我只列出最主要的计算偏移量的部分:
float4 relief_map_shadowsPS(VertexOutput IN) : COLOR
{
// IN.vpos是该点在视空间下的坐标
float3 p = IN.vpos;
// 所以单位化的p就是Vn
float3 Vn = normalize(p);
// 把Vn变换到切向空间下以便在纹理空间中运算
// 因为Vn的方向和切向空间的法线方向肯定是相反的(你不可能从背面看到一张贴图)
// 所以s的法线分量a使用取反的Vn
float a = dot(-Vn, IN.normal);
// x,y分量既为UV
float3 s = float3(dot(Vn,IN.tangent.xyz), dot(Vn,IN.binormal.xyz), a);
// Depth是一个全局的参数,决定表面凹多少
// (UV*Depth)/a既是我们需要的比值ds
s *= Depth/a;
float2 ds = s.xy;
float2 dp = IN.UV;
// 计算深度
float d = ray_intersect_rm(ReliefSampler,dp,ds);
// 计算偏移后的贴图坐标
float2 uv = dp+ds*d;
// 把uv当作贴图坐标用于DiffuseMap或是NormalMap的取样
// Do something
参考文章:《Real-Time Relief Mapping on Arbitrary Polygonal Surfaces》 by F´abio Policarpo & Manuel M. Oliveira & Jo˜ao L. D. Comba
注意:这张深度图Alpha值高的地方表示越凸
概念:
ReliefMap的基本思想和ParallaxMap完全一致:就是给通过光栅化计算出的贴图坐标加上一个偏移量来确定在凹凸的表面上这个坐标应该在的位置。区别是ParallaxMap仅仅是简单的根据高度沿着视方向移动UV,这样在深度高的时候就会出现严重的走样,但是ReliefMap则是通过确定视向量和凹凸表面的交点来计算偏移量的,如下图所示:
图中"点A"为光栅化计算出的贴图坐标,但因为我们的表面是凹下去的,所以摄像机看到的点应该是"点3",要实现这一点,我们就要计算出红色箭头所表示的那段偏移量,也就是要算出"点3"的坐标。
实现思想:
下面我们要做的就是确定一条从点A发出的方向为Vn的射线和这个凹凸表面的交点。这里要用到一些数值分析中的东西,就是二分法(Binary Search)和Linear Search(这东西我不知道它的中文应该是什么......)。
首先我们使用Linear Search方法确定这条射线最早同曲面所交点的大概位置,所谓LinearSearch其实就是沿着Vn方向按照一个指定的深度的步长"d"去找到那个第一个进入物体的位置所在的有根区间,如图所示:
点2在表面外而点3在表面内,于是我们可以确认交点必定在点2和点3之间的这段区间上。下面我们就在这段区间中使用二分法来进一步确定交点的位置,如下图所示:
点4,5,6既为二分法所使用的取样点,收敛速度非常理想,一般情况下四五次循环即可获得精确的数值。
实现方法:
让我们先看看Shader中计算交点的这个函数:
这里的三个参数分别为法线/深度图的取样器reliefmap,光栅化计算出的贴图坐标"点A"的坐标(纹理空间)dp,深度和偏移量的比值ds(这个值的计算方法会在后面提到)。
float ray_intersect_rm( // use linear and binary search
in sampler2D reliefmap,
in float2 dp,
in float2 ds)
{
// LinearSearch总共搜索的步数
const int linear_search_steps=15;
// LinearSearch所使用的深度的步长d
float size = 1.0/linear_search_steps;
// 初始化起始点A的深度,如果使用的是高度图那么这里就是1
float depth = 0.0;
// 从点A位置dp开始通过LinearSearch确认有根区间
for( int i=0;i<linear_search_steps-1;i++ ) {
// 要注意这里ds*depth的结果也就是偏移量是矢量的
float4 t = tex2D(reliefmap,dp+ds*depth);
// 如果发现还没有进入物体,那么就增加总步长,继续搜索
if (depth<t.w)
depth += size;
// 其实如果发现进入了就可跳出循环了,遗憾的是SM3.0不支持Break
// else
// break;
}
const int binary_search_steps=5;
// 使用二分查找确定depth的准确值
for( int i=0;i<binary_search_steps;i++ ) {
// 缩小有根区间
size*=0.5;
float4 t = tex2D(reliefmap,dp+ds*depth);
// 确定根的位置,上半个区间还是后半个区间
if (depth<t.w)
depth += (2*size);
depth -= size;
}
// 最终计算出的深度值,ds*depth就是所要计算的偏移量,dp+ds*depth就是 // Vn和凹凸表面相交的正确位置。
return depth;
}
实现方法:
让我们先看看Shader中计算交点的这个函数:
这里的三个参数分别为法线/深度图的取样器reliefmap,光栅化计算出的贴图坐标"点A"的坐标(纹理空间)dp,深度和偏移量的比值ds(这个值的计算方法会在后面提到)。
float ray_intersect_rm( // use linear and binary search
in sampler2D reliefmap,
in float2 dp,
in float2 ds)
{
// LinearSearch总共搜索的步数
const int linear_search_steps=15;
// LinearSearch所使用的深度的步长d
float size = 1.0/linear_search_steps;
// 初始化起始点A的深度,如果使用的是高度图那么这里就是1
float depth = 0.0;
// 从点A位置dp开始通过LinearSearch确认有根区间
for( int i=0;i<linear_search_steps-1;i++ ) {
// 要注意这里ds*depth的结果也就是偏移量是矢量的
float4 t = tex2D(reliefmap,dp+ds*depth);
// 如果发现还没有进入物体,那么就增加总步长,继续搜索
if (depth<t.w)
depth += size;
// 其实如果发现进入了就可跳出循环了,遗憾的是SM3.0不支持Break
// else
// break;
}
const int binary_search_steps=5;
// 使用二分查找确定depth的准确值
for( int i=0;i<binary_search_steps;i++ ) {
// 缩小有根区间
size*=0.5;
float4 t = tex2D(reliefmap,dp+ds*depth);
// 确定根的位置,上半个区间还是后半个区间
if (depth<t.w)
depth += (2*size);
depth -= size;
}
// 最终计算出的深度值,ds*depth就是所要计算的偏移量,dp+ds*depth就是 // Vn和凹凸表面相交的正确位置。
return depth;
}
剩下的最后一个问题就是如何确定ds,又要涉及到倒霉的视空间向切向空间的变换,以下就是浮雕贴图的PixelShader的片段,我只列出最主要的计算偏移量的部分:
float4 relief_map_shadowsPS(VertexOutput IN) : COLOR
{
// IN.vpos是该点在视空间下的坐标
float3 p = IN.vpos;
// 所以单位化的p就是Vn
float3 Vn = normalize(p);
// 把Vn变换到切向空间下以便在纹理空间中运算
// 因为Vn的方向和切向空间的法线方向肯定是相反的(你不可能从背面看到一张贴图)
// 所以s的法线分量a使用取反的Vn
float a = dot(-Vn, IN.normal);
// x,y分量既为UV
float3 s = float3(dot(Vn,IN.tangent.xyz), dot(Vn,IN.binormal.xyz), a);
// Depth是一个全局的参数,决定表面凹多少
// (UV*Depth)/a既是我们需要的比值ds
s *= Depth/a;
float2 ds = s.xy;
float2 dp = IN.UV;
// 计算深度
float d = ray_intersect_rm(ReliefSampler,dp,ds);
// 计算偏移后的贴图坐标
float2 uv = dp+ds*d;
// 把uv当作贴图坐标用于DiffuseMap或是NormalMap的取样
// Do something