# [译] GLSL 中的视差遮蔽映射（Parallax Occlusion Mapping in GLSL）

## 视差映射基础

``````// Basic vertex shader for parallax mapping
#version 330

// attributes
layout(location = 0) in vec3    i_position;    // xyz - position
layout(location = 1) in vec3    i_normal;    // xyz - normal
layout(location = 2) in vec2    i_texcoord0;    // xy - texture coords
layout(location = 3) in vec4 i_tangent; // xyz - tangent, w - handedness

// uniforms
uniform mat4 u_model_mat;
uniform mat4 u_view_mat;
uniform mat4 u_proj_mat;
uniform mat3 u_normal_mat;
uniform vec3 u_light_position;
uniform vec3 u_camera_position;

out vec2    o_texcoords;
out vec3 o_toLightInTangentSpace;
out vec3 o_toCameraInTangentSpace;

///////////////////////////////////////////////////////////////////

void main(void)
{
// transform to world space
vec4 worldPosition    = u_model_mat * vec4(i_position, 1);
vec3 worldNormal    = normalize(u_normal_mat * i_normal);
vec3 worldTangent    = normalize(u_normal_mat * i_tangent.xyz);

// calculate vectors to the camera and to the light
vec3 worldDirectionToLight    = normalize(u_light_position - worldPosition.xyz);
vec3 worldDirectionToCamera    = normalize(u_camera_position - worldPosition.xyz);

// calculate bitangent from normal and tangent
vec3 worldBitangnent    = cross(worldNormal, worldTangent) * i_tangent.w;

// transform direction to the light to tangent space
o_toLightInTangentSpace = vec3(
dot(worldDirectionToLight, worldTangent),
dot(worldDirectionToLight, worldBitangnent),
dot(worldDirectionToLight, worldNormal)
);

// transform direction to the camera to tangent space
o_toCameraInTangentSpace= vec3(
dot(worldDirectionToCamera, worldTangent),
dot(worldDirectionToCamera, worldBitangnent),
dot(worldDirectionToCamera, worldNormal)
);

// pass texture coordinates to fragment shader
o_texcoords    = i_texcoord0;

// calculate screen space position of the vertex
gl_Position    = u_proj_mat * u_view_mat * worldPosition;
}``````
``````// basic fragment shader for Parallax Mapping
#version 330

in vec2    o_texcoords;
in vec3    o_toLightInTangentSpace;
in vec3    o_toCameraInTangentSpace;

// textures
layout(location = 0) uniform sampler2D u_diffuseTexture;
layout(location = 1) uniform sampler2D u_heightTexture;
layout(location = 2) uniform sampler2D u_normalTexture;

// color output to the framebuffer
out vec4    resultingColor;

////////////////////////////////////////

// scale for size of Parallax Mapping effect
uniform float    parallaxScale; // ~0.1

//////////////////////////////////////////////////////
// Implements Parallax Mapping technique
// Returns modified texture coordinates, and last used depth
vec2 parallaxMapping(in vec3 V, in vec2 T, out float parallaxHeight)
{
// ...
}

//////////////////////////////////////////////////////
float parallaxSoftShadowMultiplier(in vec3 L, in vec2 initialTexCoord,
in float initialHeight)
{
// ...
}

//////////////////////////////////////////////////////
// Calculates lighting by Blinn-Phong model and Normal Mapping
// Returns color of the fragment
vec4 normalMappingLighting(in vec2 T, in vec3 L, in vec3 V, float shadowMultiplier)
{
// restore normal from normal map
vec3 N = normalize(texture(u_normalTexture, T).xyz * 2 - 1);
vec3 D = texture(u_diffuseTexture, T).rgb;

// ambient lighting
float iamb = 0.2;
// diffuse lighting
float idiff = clamp(dot(N, L), 0, 1);
// specular lighting
float ispec = 0;
if(dot(N, L) > 0.2)
{
vec3 R = reflect(-L, N);
ispec = pow(dot(R, V), 32) / 1.5;
}

vec4 resColor;
resColor.rgb = D * (ambientLighting + (idiff + ispec) * pow(shadowMultiplier, 4));
resColor.a = 1;

return resColor;
}

/////////////////////////////////////////////
// Entry point for Parallax Mapping shader
void main(void)
{
// normalize vectors after vertex shader
vec3 V = normalize(o_toCameraInTangentSpace);
vec3 L = normalize(o_toLightInTangentSpace);

// get new texture coordinates from Parallax Mapping
float parallaxHeight;
vec2 T = parallaxMapping(V, o_texcoords, parallaxHeight);

// get self-shadowing factor for elements of parallax

// calculate lighting
resultingColor = normalMappingLighting(T, L, V, shadowMultiplier);
}``````

## 视差映射和带偏移上限的视差映射

• 从高度图读取纹理坐标T0位置的高度H(T0)

• 根据H(T0)和摄像机向量V，在初始的纹理坐标基础上进行偏移。

``````vec2 parallaxMapping(in vec3 V, in vec2 T, out float parallaxHeight)
{
// get depth for this fragment
float initialHeight = texture(u_heightTexture, o_texcoords).r;

// calculate amount of offset for Parallax Mapping
vec2 texCoordOffset = parallaxScale * V.xy / V.z * initialHeight;

// calculate amount of offset for Parallax Mapping With Offset Limiting
texCoordOffset = parallaxScale * V.xy * initialHeight;

// retunr modified texture coordinates
return o_texcoords - texCoordOffset;
}``````

## 陡峭视差映射

• 层的深度为0，高度图深度H(T0)大约为0.75。采样到的深度大于层的深度，所以开始下一次迭代。

• 沿着V方向偏移纹理坐标，选定下一层。层深度为0.125，高度图深度H(T1)大约为0.625。采样到的深度大于层的深度，所以开始下一次迭代。

• 沿着V方向偏移纹理坐标，选定下一层。层深度为0.25，高度图深度H(T2)大约为0.4。采样到的深度大于层的深度，所以开始下一次迭代。

• 沿着V方向偏移纹理坐标，选定下一层。层深度为0.375，高度图深度H(T3)大约为0.2。采样到的深度小于层的深度，所以向量V上的当前点在表面之下。我们找到了纹理坐标Tp=T3是实际交点的近似点。

``````vec2 parallaxMapping(in vec3 V, in vec2 T, out float parallaxHeight)
{
// 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 = parallaxScale * V.xy / V.z / numLayers;

// current texture coordinates
vec2 currentTextureCoords = T;

// get first depth from heightmap
float heightFromTexture = texture(u_heightTexture, currentTextureCoords).r;

// 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(u_heightTexture, currentTextureCoords).r;
}

// return results
parallaxHeight = currentLayerHeight;
return currentTextureCoords;
}``````

## 浮雕视差映射

（译者：这一段的内容和原文区别较大，因为直接按照原文翻译有很多容易混淆的名词，所以我加入了变量声明。）

• 在陡峭视差映射之后，我们知道交点肯定在T2和T3之间。真实的交点在图上用绿点标出来了。

• 设每次迭代时的纹理坐标变化量ST，它的初始值等于向量V在穿过一个层的深度时的XY分量。

• 设每次迭代时的深度值变化量SH，它的初始值等于一个层的深度。

• 把ST和SH都除以2。

• 把纹理坐标T3沿着反方向偏移ST，把层深度沿反方向偏移SH，得到此次迭代的纹理坐标T4和层深度H(T4)。

• （*）采样高度图，把ST和SH都除以2。

• 如果高度图中的深度值大于当前迭代层的深度H(T4)，则将当前迭代层的深度增加SH，迭代的纹理坐标沿着V的方向增加ST。

• 如果高度图中的深度值小于当前迭代层的深度H(T4)，则将当前迭代层的深度减少SH，迭代的纹理坐标沿着V的相反方向增加ST。

• 从(*)处循环，继续二分搜索，直到规定的次数。

• 最后一步得到的纹理坐标就是浮雕视差映射的结果。

``````vec2 parallaxMapping(in vec3 V, in vec2 T, out float parallaxHeight)
{
// determine required number of layers
const float minLayers = 10;
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 = parallaxScale * V.xy / V.z / numLayers;

// current texture coordinates
vec2 currentTextureCoords = T;

// depth from heightmap
float heightFromTexture = texture(u_heightTexture, currentTextureCoords).r;

// while point is above surface
while(heightFromTexture > currentLayerHeight)
{
// go to the next layer
currentLayerHeight += layerHeight;
// shift texture coordinates along V
currentTextureCoords -= dtex;
// new depth from heightmap
heightFromTexture = texture(u_heightTexture, currentTextureCoords).r;
}

///////////////////////////////////////////////////////////
// Start of Relief Parallax Mapping

// decrease shift and height of layer by half
vec2 deltaTexCoord = dtex / 2;
float deltaHeight = layerHeight / 2;

currentTextureCoords += deltaTexCoord;
currentLayerHeight -= deltaHeight;

// binary search to increase precision of Steep Paralax Mapping
const int numSearches = 5;
for(int i=0; i<numSearches; i++)
{
// decrease shift and height of layer by half
deltaTexCoord /= 2;
deltaHeight /= 2;

// new depth from heightmap
heightFromTexture = texture(u_heightTexture, currentTextureCoords).r;

// shift along or agains vector V
if(heightFromTexture > currentLayerHeight) // below the surface
{
currentTextureCoords -= deltaTexCoord;
currentLayerHeight += deltaHeight;
}
else // above the surface
{
currentTextureCoords += deltaTexCoord;
currentLayerHeight -= deltaHeight;
}
}

// return results
parallaxHeight = currentLayerHeight;    return currentTextureCoords;
}``````

## 视差遮蔽映射

• nextHeight = H(T3) - currentLayerHeight

• prevHeight = H(T2) - (currentLayerHeight - layerHeight)

• weight = nextHeight / (nextHeight - prevHeight)

• Tp = T(T2) weight + T(T3) (1.0 - weight)

``````vec2 parallaxMapping(in vec3 V, in vec2 T, out float parallaxHeight)
{
// determine optimal number of layers
const float minLayers = 10;
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;
// current depth of the layer
float curLayerHeight = 0;
// shift of texture coordinates for each layer
vec2 dtex = parallaxScale * V.xy / V.z / numLayers;

// current texture coordinates
vec2 currentTextureCoords = T;

// depth from heightmap
float heightFromTexture = texture(u_heightTexture, currentTextureCoords).r;

// while point is above the surface
while(heightFromTexture > curLayerHeight)
{
// to the next layer
curLayerHeight += layerHeight;
// shift of texture coordinates
currentTextureCoords -= dtex;
// new depth from heightmap
heightFromTexture = texture(u_heightTexture, currentTextureCoords).r;
}

///////////////////////////////////////////////////////////

// previous texture coordinates
vec2 prevTCoords = currentTextureCoords + texStep;

// heights for linear interpolation
float nextH    = heightFromTexture - curLayerHeight;
float prevH    = texture(u_heightTexture, prevTCoords).r
- curLayerHeight + layerHeight;

// proportions for linear interpolation
float weight = nextH / (nextH - prevH);

// interpolation of texture coordinates
vec2 finalTexCoords = prevTCoords * weight + currentTextureCoords * (1.0-weight);

// interpolation of depth values
parallaxHeight = curLayerHeight + prevH * weight + nextH * (1.0 - weight);

// return result
return finalTexCoords;
}``````

## 视差映射和自阴影

• 沿着L向前步进到Ha。Ha小于H(TL1)，所以该点在表面之下。计算半阴影系数为Ha-H(TL1)。这是第一次检查，总共的检查次数为4，计算距离影响，将半阴影系数乘以(1.0 - 1.0/4.0)。保存这个半阴影系数。

• 沿着L向前步进到Hb。Hb小于H(TL2)，所以该点在表面之下。计算半阴影系数为Hb-H(TL2)。这事第二次检查，总共的检查次数为4，计算距离影响，将半阴影系数乘以(1.0 - 2.0/4.0)。保存这个半阴影系数。

• 沿着L向前步进，这个点在表面之上。

• 最后一次沿着L向前步进，这个点也在表面之上。

• 迭代的点已经高于了水平线0.0，结束迭代。

• 选取最大的半阴影系数作为最终的阴影系数值。

``````float parallaxSoftShadowMultiplier(in vec3 L, in vec2 initialTexCoord,
in float initialHeight)
{

const float minLayers = 15;
const float maxLayers = 30;

// calculate lighting only for surface oriented to the light source
if(dot(vec3(0, 0, 1), L) > 0)
{
// calculate initial parameters
float numSamplesUnderSurface    = 0;
float numLayers    = mix(maxLayers, minLayers, abs(dot(vec3(0, 0, 1), L)));
float layerHeight    = initialHeight / numLayers;
vec2 texStep    = parallaxScale * L.xy / L.z / numLayers;

// current parameters
float currentLayerHeight    = initialHeight - layerHeight;
vec2 currentTextureCoords    = initialTexCoord + texStep;
float heightFromTexture    = texture(u_heightTexture, currentTextureCoords).r;
int stepIndex    = 1;

// while point is below depth 0.0 )
while(currentLayerHeight > 0)
{
// if point is under the surface
if(heightFromTexture < currentLayerHeight)
{
numSamplesUnderSurface    += 1;
float newShadowMultiplier    = (currentLayerHeight - heightFromTexture) *
(1.0 - stepIndex / numLayers);
}

// offset to the next layer
stepIndex    += 1;
currentLayerHeight    -= layerHeight;
currentTextureCoords    += texStep;
heightFromTexture    = texture(u_heightTexture, currentTextureCoords).r;
}

// Shadowing factor should be 1 if there were no points under the surface
if(numSamplesUnderSurface < 1)
{
}
else
{