视差映射仅在一个方向上起作用

Parallax mapping works in only one direction

我已经实施了 Steep Parallax、Relief 和 Parallax Occlusion 贴图的多种变体,它们都有一个错误,即只能在一个方向上正常工作。这让我相信问题是在视差映射之外计算的值之一。我不知道哪里出了问题。

这是一个从不同角度进行视差映射的立方体,正如您所看到的,视差效果仅在立方体的一侧是正确的:

这里是使用的 GLSL 代码:

顶点着色器:

layout(location = 0) in vec3 vertexPosition;
layout(location = 1) in vec3 vertexNormal;
layout(location = 2) in vec2 textureCoord;
layout(location = 3) in vec3 vertexTangent;
layout(location = 4) in vec3 vertexBitangent;

out vec3 eyeVec;
out vec2 texCoord;

uniform vec3 cameraPosVec;  // Camera's position
uniform mat4 modelMat;      // Model matrix

void main(void)
{       
    texCoord = textureCoord;

    fragPos = vec3(modelMat * vec4(vertexPosition, 1.0));

    // Compute TBN matrix
    mat3 normalMatrix = transpose(inverse(mat3(modelMat)));
    TBN = mat3(normalMatrix * vertexTangent, 
               normalMatrix * vertexBitangent, 
               normalMatrix * vertexNormal);

    eyeVec = TBN * (fragPos - cameraPosVec);
}

片段着色器:

layout(location = 1) out vec4 diffuseBuffer;

// Variables from vertex shader
in vec3 eyeVec;
in vec3 fragPos;
in vec2 texCoord;

uniform sampler2D diffuseTexture;
uniform sampler2D heightTexture;

vec2 parallaxOcclusionMapping(vec2 p_texCoords, vec3 p_viewDir)
{       
    // number of depth layers
    float numLayers = 50;

    // calculate the size of each layer
    float layerDepth = 1.0 / numLayers;
    // depth of current layer
    float currentLayerDepth = 0.0;
    // the amount to shift the texture coordinates per layer (from vector P)
    vec2 P = p_viewDir.xy / p_viewDir.z * 0.025;
    return p_viewDir.xy / p_viewDir.z;
    vec2 deltaTexCoords = P / numLayers;

    // get initial values
    vec2  currentTexCoords     = p_texCoords;
    float currentDepthMapValue = texture(heightTexture, currentTexCoords).r;
    float previousDepth = currentDepthMapValue;

    while(currentLayerDepth < currentDepthMapValue)
    {
        // shift texture coordinates along direction of P
        currentTexCoords -= deltaTexCoords;
        // get depthmap value at current texture coordinates
        currentDepthMapValue = texture(heightTexture, currentTexCoords).r;  

        previousDepth = currentDepthMapValue;
        // get depth of next layer
        currentLayerDepth += layerDepth;  
    }

    // -- parallax occlusion mapping interpolation from here on
    // get texture coordinates before collision (reverse operations)
    vec2 prevTexCoords = currentTexCoords + deltaTexCoords;

    // get depth after and before collision for linear interpolation
    float afterDepth  = currentDepthMapValue - currentLayerDepth;
    float beforeDepth = texture(heightTexture, prevTexCoords).r - currentLayerDepth + layerDepth;

    // interpolation of texture coordinates
    float weight = afterDepth / (afterDepth - beforeDepth);
    vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);

    return finalTexCoords;
}

void main(void)
{   
    vec2 newCoords = parallaxOcclusionMapping(texCoord, eyeVec);

    // Get diffuse color
    vec4 diffuse = texture(diffuseTexture, newCoords).rgba;

    // Write diffuse color to the diffuse buffer
    diffuseBuffer = diffuse;
}

代码是从this tutorial

中复制粘贴的

P.S。 反转 eyeVar 向量的不同值 (x,y,z)(在使用 TBN 矩阵对其进行变换之后),会改变视差起作用的方向。例如,反转 X 分量,使其在面朝上的多边形上产生视差:

这表明 tangents/bitangents 或 TBN 矩阵可能存在问题,但我没有发现任何问题。此外,使用模型矩阵旋转对象不会影响视差效果的工作方向。

编辑:

我已经通过更改我的计算方式 tangents/bitangents、将 viewDirection 计算移至片段着色器以及不对 TBN 仅对这一计算进行矩阵求逆来解决此问题。在此处查看新添加的着色器代码:

顶点着色器:

out vec3 TanViewPos;
out vec3 TanFragPos;

void main(void)
{       
    vec3 T = normalize(mat3(modelMat) * vertexTangent);
    vec3 B = normalize(mat3(modelMat) * vertexBitangent);
    vec3 N = normalize(mat3(modelMat) * vertexNormal);
    TBN = transpose(inverse(mat3(T, B, N)));

    mat3 TBN2 = transpose((mat3(T, B, N)));
    TanViewPos = TBN2 * cameraPosVec;
    TanFragPos = TBN2 * fragPos;
}

片段着色器:

in vec3 TanViewPos;
in vec3 TanFragPos;

void main(void)
{       
    vec3 viewDir = normalize(TanViewPos - TanFragPos);
    vec2 newCoords = parallaxOcclusionMapping(texCoord, viewDir);
}

关于tangent/bitangent计算,我之前是用ASSIMP帮我生成的。现在我已经编写了代码来手动计算切线和副切线,但是我的新代码生成 "flat/hard" 切线(即三角形的所有 3 个顶点的相同 tangent/bitangent),而不是平滑的。查看 ASSIMP(平滑切线)和我的(平面切线)实现之间的区别:

视差映射现在适用于所有方向:

然而,这又引入了另一个问题,这使得所有的圆形物体都有平坦的阴影(在执行法线贴图之后):

这个问题是我的引擎特有的(即某处的错误)还是其他引擎以某种方式处理的常见问题?

主要的潜在问题是我的 tangents/bitangents。我一直依靠 ASSIMP 来为我计算它们,并且直到现在它总是工作正常(例如对于法线贴图)。解决方案是自己计算它们,使用一些平滑过程:

for(decltype(m_positions.size()) i = 0, size = m_positions.size(); i < size; i++)
{
    // Get vertex positions of the polygon
    const Math::Vec3f &v0 = m_positions[i + 0];
    const Math::Vec3f &v1 = m_positions[i + 1];
    const Math::Vec3f &v2 = m_positions[i + 2];

    // Get texture coordinates of the polygon
    const Math::Vec2f &uv0 = m_texCoords[i + 0];
    const Math::Vec2f &uv1 = m_texCoords[i + 1];
    const Math::Vec2f &uv2 = m_texCoords[i + 2];

    // Get normals of the polygon
    const Math::Vec3f &n0 = m_normals[i + 0];
    const Math::Vec3f &n1 = m_normals[i + 1];
    const Math::Vec3f &n2 = m_normals[i + 2];

    // Calculate position difference
    Math::Vec3f deltaPos1 = v1 - v0;
    Math::Vec3f deltaPos2 = v2 - v0;

    // Calculate texture coordinate difference
    Math::Vec2f deltaUV1 = uv1 - uv0;
    Math::Vec2f deltaUV2 = uv2 - uv0;

    // Calculate tangent and bitangent
    float r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
    Math::Vec3f tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y) * r;
    Math::Vec3f bitangent = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x) * r;

    // Orthogonalize using Gram–Schmidt process, to make tangents and bitangents smooth based on normal
    m_tangents[i + 0] = Math::normalize(tangent - n0 * Math::dot(n0, tangent));
    m_tangents[i + 1] = Math::normalize(tangent - n1 * Math::dot(n1, tangent));
    m_tangents[i + 2] = Math::normalize(tangent - n2 * Math::dot(n2, tangent));

    m_bitangents[i + 0] = Math::normalize(bitangent - n0 * Math::dot(n0, bitangent));
    m_bitangents[i + 1] = Math::normalize(bitangent - n1 * Math::dot(n1, bitangent));
    m_bitangents[i + 2] = Math::normalize(bitangent - n2 * Math::dot(n2, bitangent));
}

所有评论、您的解决方案和编辑都没有达到目标。虽然您已经用正确的代码替换了着色器代码,但您在平滑切线方面的工作实际上并没有解决它。您的解决方案中的 tangent/bitangent 代码不正确。

但是,据我了解,您在几个不同的地方是不正确的-

  1. 从这里的代码开始 mat3 normalMatrix = transpose(inverse(mat3(modelMat)));

推测您的 modelMatobject space to world space 的齐次变换。取它的逆将从 world space to object space 转换 - 因为你已经通过转换为 mat3 来剥离翻译信息,它也不会 'un-translate' 任何东西。其转置称为逆转置,它仅有效地取消缩放对象或向量 preserving ONLY the rotation information 。然后,您将顶点切线、副切线和法线乘以这个逆比例矩阵,形成矢量的正交基(以前是单位长度),未按与您的模型相同的因子缩放,并且仍然处于切线 space。所以你的 TBN 在这个阶段实际上是某种奇怪的 rotated object-space to tangent-space 变换矩阵,它也会对向量进行缩放。

如果它是正确的,您实际上不需要对示例对象的照明使用反向转置,因为它们是统一缩放的。

  1. 您的使用方式不正确。 eyeVec = TBN * (fragPos - cameraPosVec); 首先(假设您使用的是 phong model),您错误地将顶点位置 (fragPos) 减去相机位置用作您的视图向量,它在phong 模型的方向。然后将它乘以 world to tangent 矩阵 TBN,有效地将世界 space vec3 转换为切线 space vec3。

  2. 代码在某种程度上是正确的,因为着色器正确地使用和创建了 TBN 矩阵。将每个切线 space 向量乘以 object to world 变换是正确的,因为您尝试修复它的方式似乎是在对象 space 中计算的,它们代表了变换为切线 space。如果你没有再次使用反正切,它会更正确。再次使用 3x3 进行逆转置,将使矩阵变成完全相同的变换,该变换也会取消缩放以原点为中心的任何 3D 方向向量。这就是为什么它没有产生任何明显的效果,因为所有的切线、副切线和法线向量从一开始(单位长度)都是统一比例的,变化非常小,所有东西都使用相同的比例,所以它几乎不明显。

然后您继续在 TBN 矩阵上正确使用常规转置 - 这是可能的,因为您在响应的更正计算中对切线和双切线向量进行了正交归一化。

  1. 您的解决方案最值得注意的问题是您的CPU代码索引多次超出每个数组的边界,而且也不正确。通过一次遍历数组 3,最后 2 个索引将滑出边界(您可以尝试使用 % 运算符来包装最后两个)- 并且因为您在此处使用赋值运算符

m_tangents[i + 0] = Math::normalize(tangent - n0 * Math::dot(n0, tangent)); m_tangents[i + 1] = Math::normalize(tangent - n1 * Math::dot(n1, tangent)); m_tangents[i + 2] = Math::normalize(tangent - n2 * Math::dot(n2, tangent));

m_bitangents[i + 0] = Math::normalize(bitangent - n0 * Math::dot(n0, bitangent));
m_bitangents[i + 1] = Math::normalize(bitangent - n1 * Math::dot(n1, bitangent));
m_bitangents[i + 2] = Math::normalize(bitangent - n2 * Math::dot(n2, bitangent));

您每次计算时都会覆盖每个切线和副切线向量。唯一使用的索引是 [i + 0]。您使用的数学旨在用于三角形的面上,因此您应该迭代三角形。此外,此代码假设此列表中没有重复的顶点 - 同时假设任何 3 个连续的顶点形成一个有效的三角形(我什至不确定这是可能的,当然不是没有重复顶点但谁知道)。本质上,您应该通过将由索引组成的三角形列表迭代到顶点数组来完成所有这些操作,并且您应该使用 += 运算符对切线求和,然后对其进行归一化以获得平均值所有面孔。

旁注:关于您的评论,也许您理解切线 space 但计算肯定不正确,并且视差遮挡贴图与球形 UV 贴图的工作方式不同,因为您正在使用的遮挡参数。此外,如果您使用与这些平面对象相同的平面贴图技术,它可能看起来不适合您(您会使用球面贴图,但您不会注意到任何遮挡)