视差贴图(Parallax Mapping)
视差贴图目前已经被广泛运用了,只需要增加一种模型表面的深度纹理信息之后,就能近似的模拟模型的凹凸,在不需要灯光,不需要环境反射的情况下,可以比较真实的模拟真实世界.
先来看一下视差贴图与其他贴图的区别
可以看出 Parallax Mapped 和Steep Parallax Mapped 实现的效果要好很多.
下面的图片更好的表现了视差贴图的好处
原理:
根据用户的观察角度来将纹理坐标偏移,就是把模型表面较高的位置来遮挡位置较低的位置.当用户观察角度改变的时候,让一些像素的纹理坐标偏移来遮挡一些像素,从而使观察者感觉到凹凸起伏.纹理的偏移需要一个描述模型的凹凸纹理和观察位置同时决定的.
这是一张原理的图片,在这里观察者的眼睛在V处,实际观察的物体的H(T1)点,也就是说物体的真实位置是在H(T1)处,但是眼睛观测的视线会落在To处.
要想得到真实的场景,我们需要把To处的光线偏移到H(T1)处,这样子就看起来就会比较真实.
如何来计算像素点To到H(T1)的偏移呢,需要观察者位置和当前像素点的位置才能计算出来.下面我们来看一下是如何实现计算的.
就是OffsetUV 和ViewDir.x 和ViewDir.y 我们是已经知道的.
通常用简化的公式
OffsetUV = OffsetUV + float2(-ViewDir.x,ViewDir,y)*v2Scale;
其中OffsetUV 表示最终得到的颜色纹理偏移,ViewDir 表示纹理观察向量(经归一处理过),v2Scale 表示与高度和模型有关的缩放因子,,这个缩放因子与表面起伏的大小和整个的大小的比相关.
在shader里面分为两部分写:
一部分是将每个顶点上的观察方向变化带模型空间,整个部分在Vertex里面计算实现;
另一部分是完成像素偏移的计算,此部分在PixelShader中完成.
PixelShader
//PixelShader
float4 RenderScenePS( VS_OUTPUT In ) :COLOR0
{
// 计算视差贴图的偏移
float3 ViewDir = normalize( In.vLookAt );
float2 OffsetUV = In.TextureUV ;
float3 h = tex2D( MeshHeightSampler ,In.TextureUV ).y ;
OffsetUV += float2( -ViewDir.x , ViewDir.y ) * ( h * 0.04 - 0.02 );
float4 texColor = tex2D( MeshTextureSampler ,OffsetUV );
//
return texColor ;
}
OffsetUV += float2( -ViewDir.x , ViewDir.y ) * ( h * 0.04 - 0.02 );
其中这行代码就是表示视差贴图的公式;
在VertexShader里面要实现Tangent Space(我的另一篇博客有提到)坐标,
VS_OUTPUT RenderSceneVS( VS_INPUT In )
{
VS_OUTPUT Out = ( VS_OUTPUT )0;
//正常流程变化模型的顶点
float4x4 matWorldView = mul( g_matWorld ,g_matView );
float4x4 matWorldViewProject = mul( matWorldView ,g_matProject );
Out.Position = mul( In.Position , matWorldViewProject );
Out.TextureUV = In.TextureUV;
//----------------- 在此计算一个变换矩阵 ---------------------
// 将世界坐标变换到切线空间系的变换矩阵
float3x3 matWorldToModel = float3x3 (
mul( In.Tanget , g_matWorld ).xyz ,
mul( cross( In.Tanget,In.Normal ), g_matWorld ).xyz ,
mul( In.Normal , g_matWorld ).xyz );
//
float4 Position = mul( In.Position ,g_matWorld );
// 每个顶点上的指向观察者的方向
Out.vLookAt = mul ( matWorldToModel ,normalize( Position - g_vEyePosition ));
return Out;
}
陡峭视差贴图Steep Parallax Mapped
我们接着看一下Steep Parallax Mapping,这种视图看起来会更加逼真,显示效果会好点,当然计算的方法也不一样.
下面是Steep Parallax Mapping的公式:
ti = s + (Ex , Ey) i / (n Ez) 0 ≤ i < n
陡峭视差映射,不像简单的视差映射近似,并不只是简单粗暴的对纹理坐标进行偏移而不检查合理性和关联性,会检查结果是否接近于正确值。这种方法的核心思想是把表面的深度切分成等距的若干层。然后从最顶端的一层开始采样高度图,每一次会沿着V的方向偏移纹理坐标。如果点已经低于了表面(当前的层的深度大于采样出的深度),停止检查并且使用最后一次采样的纹理坐标作为结果。
原理:
陡峭视差映射的工作方式在下面的图片上举例。深度被分割成8个层,每层的高度值是0.125。每层的纹理坐标偏移是V.xy/V.z * scale/numLayers。从顶层黄色方块的位置开始检查,下面是手动计算步骤:
1.层的深度为0,高度图深度H(T0)大约为0.75。采样到的深度大于层的深度,所以开始下一次迭代。
2.沿着V方向偏移纹理坐标,选定下一层。层深度为0.125,高度图深度H(T1)大约为0.625。采样到的深度大于层的深度,所以开始下一次迭代。
3.沿着V方向偏移纹理坐标,选定下一层。层深度为0.25,高度图深度H(T2)大约为0.4。采样到的深度大于层的深度,所以开始下一次迭代。
4.沿着V方向偏移纹理坐标,选定下一层。层深度为0.375,高度图深度H(T3)大约为0.2。采样到的深度小于层的深度,所以向量V上的当前点在表面之下。我们找到了纹理坐标Tp=T3是实际交点的近似点。
下面是Steep Parallax Mapped的算法.
vec2 steepPallaxMapping(in vec3 v, in vec2 t)
{
// determine number of layers from angle between V and N
const float minLayers = 5;
const float maxLayers = 15;
float numLayers = mix(maxLayers, minLayers, abs(dot(vec3(0, 0, 1), v)));
// height of each layer
float layerHeight = 1.0 / numLayers;
// depth of current layer
float currentLayerHeight = 0;
// shift of texture coordinates for each iteration
vec2 dtex = gHeightScale * v.xy / v.z / numLayers;
// current texture coordinates
vec2 currentTextureCoords = t;
// get first depth from heightmap
float heightFromTexture = texture(NormTexSampler, currentTextureCoords).a;
// while point is above surface
while(heightFromTexture > currentLayerHeight)
{
// to the next layer
currentLayerHeight += layerHeight;
// shift texture coordinates along vector V
currentTextureCoords -= dtex;
// get new depth from heightmap
heightFromTexture = texture(NormTexSampler, currentTextureCoords).a;
}
return currentTextureCoords;
}
Demo 下载