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

时间:2016-02-18 19:24:14

标签: opengl matrix graphics glsl parallax

我已经实现了Steep Palaxlax,Relief和Parallax Occlusion映射的多种变体,它们都有一个只能在一个方向上正常工作的错误。这让我相信问题是在Parallax映射之外计算的值之一。我无法弄清楚出了什么问题。

这是一个从不同角度进行视差映射的立方体,你可以看到视差效果只在立方体的一侧是正确的:

Comparison of parallax mapping on a cube from different angles

以下是使用的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分量,使其对面向上的多边形进行视差处理:

Working parallax mapping on a ground plane

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

编辑:

我已经通过改变计算切线/比特数的方式,将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);
}

关于切线/比特计算,我之前使用ASSIMP为我生成它们。现在我已经编写了手动计算切线和比特数的代码,但是我的新代码生成了“平坦/硬”切线(即三角形的所有3个顶点的相同切线/比特切线),而不是平滑切线。查看ASSIMP(平滑切线)和我的(平坦切线)实现之间的区别:

Difference between ASSIMP (smooth tangents) and my (flat tangents) implementations on a plane

视差映射现在可以在所有方向上运行: Difference between ASSIMP (smooth tangents) and my (flat tangents) implementations on a cube

然而,这现在引入了另一个问题,它使得所有圆角对象都具有平面着色(在执行法线贴图之后): Difference of smooth and flat shading on a sphere

此问题是否特定于我的引擎(即某处的某个错误),或者这是其他引擎以某种方式处理的常见问题?

2 个答案:

答案 0 :(得分:2)

所有评论,您的解决方案和编辑错过了标记。虽然您已使用正确的代码替换着色器代码,但您在平滑切线上的工作实际上并不是修复它的原因。并且解决方案中的切线/比特代码不正确。

但是,据我所知,你在几个不同的地方都不正确 -

  1. 从此处的代码mat3 normalMatrix = transpose(inverse(mat3(modelMat))); is actually called the inverse transpose, and it's used for lighting using non-uniform scaling objects开始。
  2. 据推测,您的modelMat是来自object space to world space的同质转换。相反,它将从world space to object space转换 - 因为您通过转换为mat3来剥离翻译信息,所以它不会“翻译”。什么都有。其转置称为逆转置,它实际上只能对一个对象或向量进行缩放preserving ONLY the rotation information。然后,您通过此逆缩放矩阵将顶点切线,比特率和法线相乘,以形成矢量的正交基础(之前是单位长度),其与您的模型相同的因子未缩放,并且仍然在切线空间中。所以在这个阶段你的TBN实际上是某种奇怪的rotated object-space to tangent-space转换矩阵,它也会对矢量进行缩放。

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

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

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

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

      1. 您的解决方案中最明显的问题是您的CPU代码多次索引超过每个数组的边界,并且也是错误的。通过一次遍历数组3,最后2个索引将滑出边界(您可以尝试使用%运算符包装最后两个) - 并且因为您在此处使用赋值运算符
      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个连续的顶点形成一个有效的三角形(我甚至不确定是否可能,当然不是没有重复的顶点,但谁知道)。基本上,你应该通过迭代由索引组成的三角形列表到你的顶点数组中来完成所有这些操作,你应该使用+=运算符对切线求和,然后将其标准化以获得平均值所有面孔。

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

答案 1 :(得分:0)

主要的根本问题是我的切线/比特币。我一直依靠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));
}