我的法线贴图出了问题吗?我认为是我的切线有问题。

18

编辑:您可能想从“编辑3”开始,因为我已经解决了很多问题

这是一个将我的普通立方体贴图应用于一个二十面体的截图:

enter image description here

我的立方体贴图的正切线是用以下代码生成的。 m_indices 是指向 m_vertices 中的顶点的std::vector索引的std::vector
std::vector<glm::vec3> storedTan(m_vertices.size(),glm::vec3(0,0,0));

// tangents
for(int i = 0; i < m_indices.size(); i+=3)
{
    int i1 = m_indices[i];
    int i2 = m_indices[i+1];
    int i3 = m_indices[i+2];

    VertexData v1 = m_vertices[i1];
    VertexData v2 = m_vertices[i2];
    VertexData v3 = m_vertices[i3];

    glm::vec3 p1 = glm::vec3(v1.position[0],v1.position[1],v1.position[2]);
    glm::vec3 p2 = glm::vec3(v2.position[0],v2.position[1],v2.position[2]);
    glm::vec3 p3 = glm::vec3(v3.position[0],v3.position[1],v3.position[2]);

    glm::vec3 t1 = glm::vec3(v1.tcoords[0],v1.tcoords[1],v1.tcoords[2]);
    glm::vec3 t2 = glm::vec3(v2.tcoords[0],v2.tcoords[1],v2.tcoords[2]);
    glm::vec3 t3 = glm::vec3(v3.tcoords[0],v3.tcoords[1],v3.tcoords[2]);

    std::function<glm::vec2(glm::vec3)> get_uv = [=](glm::vec3 STR)
    {
        float sc, tc, ma;
        float x = std::abs(STR.x);
        float y = std::abs(STR.y);
        float z = std::abs(STR.z);
        if(x > y && x > z)
        {
            if(STR.x > 0)
            {
                sc = -STR.z;
                tc = -STR.y;
                ma = STR.x;
            }
            else
            {
                sc = STR.z;
                tc = -STR.t;
                ma = STR.x;
            }
        }
        else if(y > z)
        {
            if(STR.y > 0)
            {
                sc = STR.x;
                tc = STR.z;
                ma = STR.y;
            }
            else
            {
                sc = STR.x;
                tc = -STR.z;
                ma = STR.y;
            }
        }
        else
        {
            if(STR.z > 0)
            {
                sc = STR.x;
                tc = -STR.y;
                ma = STR.z;
            }
            else
            {
                sc = -STR.x;
                tc = -STR.y;
                ma = STR.z;
            }
        }
        return glm::vec2((sc/std::abs(ma) + 1.0) / 2.0,(tc/std::abs(ma) + 1.0) / 2.0);
    };

    glm::vec2 uv1 = get_uv(t1);
    glm::vec2 uv2 = get_uv(t2);
    glm::vec2 uv3 = get_uv(t3);

    glm::vec3 edge1 = p2 - p1;
    glm::vec3 edge2 = p3 - p1;

    glm::vec2 tedge1 = uv2 - uv1;
    glm::vec2 tedge2 = uv3 - uv1;

    float r = 1.0f / (tedge1.x * tedge2.y - tedge2.x - tedge1.y);

    glm::vec3 sdir((tedge2.y * edge1.x - tedge1.y * edge2.x) * r,
                   (tedge2.y * edge1.y - tedge1.y * edge2.y) * r,
                   (tedge2.y * edge1.z - tedge1.y * edge2.z) * r);

    glm::vec3 tdir((tedge1.x * edge2.x - tedge2.x * edge1.x) * r,
                   (tedge1.x * edge2.y - tedge2.x * edge1.y) * r,
                   (tedge1.x * edge2.z - tedge2.x * edge1.z) * r);

    m_vertices[i1].tangent[0] += sdir.x;
    m_vertices[i1].tangent[1] += sdir.y;
    m_vertices[i1].tangent[2] += sdir.z;

    m_vertices[i2].tangent[0] += sdir.x;
    m_vertices[i2].tangent[1] += sdir.y;
    m_vertices[i2].tangent[2] += sdir.z;

    m_vertices[i3].tangent[0] += sdir.x;
    m_vertices[i3].tangent[1] += sdir.y;
    m_vertices[i3].tangent[2] += sdir.z;

    storedTan[i1] += sdir;
    storedTan[i2] += sdir;
    storedTan[i3] += sdir;
}

for(int i = 0; i < m_vertices.size(); ++i)
{
    glm::vec3 n = glm::vec3(m_vertices[i].normal[0],m_vertices[i].normal[1],m_vertices[i].normal[2]);
    glm::vec3 t = glm::vec3(m_vertices[i].tangent[0],m_vertices[i].tangent[1],m_vertices[i].tangent[2]);

    glm::vec3 newT = glm::normalize(t - n * glm::dot(n,t));
    m_vertices[i].tangent[0] = newT.x;
    m_vertices[i].tangent[1] = newT.y;
    m_vertices[i].tangent[2] = newT.z;
    m_vertices[i].tangent[3] = (glm::dot(glm::cross(n,t), storedTan[i]) < 0.0f) ? -1.0f : 1.0f;
}

顺便说一下,我的VertexData看起来像这样:

struct VertexData
{
    GLfloat position[4];
    GLfloat normal[3];
    GLfloat tcoords[3];
    GLfloat tangent[4];
};

我知道当前的tcoordspositionnormal都很好(否则你就看不到上面的截图了)。
然后我的顶点着色器看起来像这样:
#version 400

layout (location = 0) in vec4 in_position;
layout (location = 1) in vec3 in_normal;
layout (location = 2) in vec3 in_UV;
layout (location = 3) in vec4 in_tangent;

struct PointLight
{
    bool active;

    vec3 position;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;

    float constant;
    float linear;
    float quadratic;
};

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform mat4 lightMVP;

uniform PointLight uLight;

smooth out vec3 ex_UV;
out vec3 ex_normal;
out vec3 ex_positionCameraSpace;
out vec3 ex_originalPosition;
out vec3 ex_positionWorldSpace;
out vec4 ex_positionLightSpace;
out vec3 ex_tangent;
out vec3 ex_binormal;

out PointLight ex_light;

void main()
{
    gl_Position = projection * view * model * in_position;

    ex_UV = in_UV;
    ex_normal = mat3(transpose(inverse(view * model))) * in_normal;
    ex_positionCameraSpace =  vec3(view * model * in_position);
    ex_originalPosition = vec3(in_position.xyz);
    ex_positionWorldSpace = vec3(model*in_position);
    ex_positionLightSpace = lightMVP * model * in_position;

    ex_tangent = mat3(transpose(inverse(view * model))) * in_tangent.xyz;
    ex_binormal = cross(ex_normal,ex_tangent);

    // provide the fragment shader with a light in view space rather than world space
    PointLight p = uLight;
    p.position = vec3(view * vec4(p.position,1.0));
    ex_light = p;
}

最后,我的片段着色器看起来像这样:
#version 400

layout (location = 0) out vec4 color;

struct Material
{
    bool useMaps;
    samplerCube diffuse;
    samplerCube specular;
    samplerCube normal;
    float shininess;
    vec4 color1;
    vec4 color2;
};

struct PointLight
{
    bool active;

    vec3 position;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;

    float constant;
    float linear;
    float quadratic;
};

uniform Material uMaterial;

smooth in vec3 ex_UV;
in vec3 ex_normal;
in vec3 ex_positionCameraSpace;
in vec3 ex_originalPosition;
in vec3 ex_positionWorldSpace;
in vec4 ex_positionLightSpace;

in vec3 ex_tangent;
in vec3 ex_binormal;

in PointLight ex_light;

/* ******************
Provides a better lookup into a cubemap
******************* */
vec3 fix_cube_lookup(vec3 v, float cube_size)
{
    float M = max(max(abs(v.x), abs(v.y)), abs(v.z));
    float scale = (cube_size - 1) / cube_size;
    if (abs(v.x) != M)
        v.x *= scale;
    if (abs(v.y) != M)
        v.y *= scale;
    if (abs(v.z) != M)
        v.z *= scale;
    return v;
}

/* *********************
Calculates the color when using a point light. Uses shadow map
********************* */
vec3 CalcPointLight(PointLight light, Material mat, vec3 normal, vec3 fragPos, vec3 originalPos, vec3 viewDir)
{
    // replace the normal with lookup normal. This is now in tangent space
    vec3 textureLookup = fix_cube_lookup(normalize(ex_originalPosition),textureSize(mat.normal,0).x);
    normal = texture(mat.normal,textureLookup).rgb;

    // the direction the light is in in the light position - fragpos
    // light dir and view dir are now in tangent space
    vec3 lightDir = transpose(mat3(ex_tangent,ex_binormal,ex_normal)) * normalize(fragPos - light.position);
    viewDir = transpose(mat3(ex_tangent,ex_binormal,ex_normal)) * viewDir;

    // get the diffuse color
    textureLookup = fix_cube_lookup(normalize(ex_originalPosition),textureSize(mat.diffuse,0).x);
    vec3 diffuseMat = vec3(0.0);
    if(mat.useMaps)
        diffuseMat = texture(mat.diffuse,textureLookup).rgb;
    else
        diffuseMat = mat.color1.rgb;

    // get the specular color
    textureLookup = fix_cube_lookup(normalize(ex_originalPosition),textureSize(mat.specular,0).x);
    vec3 specularMat = vec3(0.0);
    if(mat.useMaps)
        specularMat = texture(mat.specular,textureLookup).rgb;
    else
        specularMat = mat.color2.rgb;

    // the ambient color is the amount of normal ambient light hitting the diffuse texture
    vec3 ambientColor = light.ambient * diffuseMat;

    // Diffuse shading
    float diffuseFactor = dot(normal, -lightDir);
    vec3 diffuseColor = vec3(0,0,0);
    vec3 specularColor = vec3(0,0,0);
    if(diffuseFactor > 0)
        diffuseColor = light.diffuse * diffuseFactor * diffuseMat;

    // Specular shading
    vec3 reflectDir = normalize(reflect(lightDir, normal));
    float specularFactor = pow(dot(viewDir,reflectDir), mat.shininess);
    if(specularFactor > 0 && diffuseFactor > 0)
        specularColor = light.specular * specularFactor * specularMat;

    float lightDistance = length(fragPos - light.position);
    float attenuation = light.constant + light.linear * lightDistance + light.quadratic * lightDistance * lightDistance;

    return ambientColor + (diffuseColor + specularColor) / attenuation;
}

void main(void)
{
    vec3 norm = normalize(ex_normal);
    vec3 viewDir = normalize(-ex_positionCameraSpace);

    vec3 result = CalcPointLight(ex_light,uMaterial,norm,ex_positionCameraSpace, ex_positionWorldSpace,viewDir);

    color = vec4(result,1.0);
}

据我所知:
  1. 我的正切线计算正确。
  2. 我的法线贴图看起来像一张法线贴图。
  3. 我将光照和视角方向改为正切空间以匹配我的法线贴图。
结果是什么也没有。即屏幕上没有任何绘制。完全没有实心颜色。因此,所有在后面的东西都没有被遮挡。
如果我放弃查找我的法线贴图,而是使用正切矩阵的光照和视角,我会得到以下结果:

enter image description here

这里有一个后期处理的镜头光晕,导致出现了一些奇怪的东西。我认为重要的是表面上压倒性的耀斑,法线看起来比较准确。
如果我只是通过切线矩阵转换光线,我会得到这个:

enter image description here

所有这些都告诉我,我不知道哪里出错了。

我有一个想法,那就是我的正切生成可能有问题,因为其他部分似乎遵循我阅读的每个教程所说的。正切是根据以立方体贴图的icosphere为基础生成的。因此,为了确定从cubemaps通常的3D坐标到<S,T><U,V> 2D坐标,我:

  1. 使用最大值来确定我所在的面
  2. 使用https://www.opengl.org/registry/specs/ARB/texture_cube_map.txt中的代码来确定S、T坐标

这是我所说的https://www.opengl.org/registry/specs/ARB/texture_cube_map.txt的摘录。

  major axis
  direction     target                             sc     tc    ma
  ----------    -------------------------------    ---    ---   ---
   +rx          TEXTURE_CUBE_MAP_POSITIVE_X_ARB    -rz    -ry   rx
   -rx          TEXTURE_CUBE_MAP_NEGATIVE_X_ARB    +rz    -ry   rx
   +ry          TEXTURE_CUBE_MAP_POSITIVE_Y_ARB    +rx    +rz   ry
   -ry          TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB    +rx    -rz   ry
   +rz          TEXTURE_CUBE_MAP_POSITIVE_Z_ARB    +rx    -ry   rz
   -rz          TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB    -rx    -ry   rz

 Using the sc, tc, and ma determined by the major axis direction as
 specified in the table above, an updated (s,t) is calculated as
 follows

    s   =   ( sc/|ma| + 1 ) / 2
    t   =   ( tc/|ma| + 1 ) / 2

 This new (s,t) is used to find a texture value in the determined
 face's 2D texture image using the rules given in sections 3.8.5
 and 3.8.6." ...

编辑 我以前不知道为什么没有这样做,但是现在我已经在几何着色器中输出了法线、切线和副切线,以查看它们的朝向。我使用了这个教程

这里它们是

黄色是面法线,绿色是顶点法线。我不确定为什么顶点法线看起来有问题,它们不会影响任何其他光照,所以可能只是我的几何着色器中存在错误。

切线是红色的,副切线是蓝色的。它们似乎(很难说)是相互垂直的,这是正确的,但除此之外它们没有指向均匀的方向。这就是我之前显示出斑驳图案的原因。

我不知道该如何解决这个问题。

编辑 2 我已经找到了显示法线等内容的问题,现在已经解决了。

我添加了一些阴影以使结果更清晰,每种颜色代表不同的立方体面。

enter image description here

我改变的另一件事是查找我的法线贴图。我忘记将范围调整回-1到1(从0到1)。

normal = texture(mat.normal,textureLookup).rgb * 2.0 - 1.0;

这并没有解决我的问题。

令人困惑的是,当我尝试使用纹理中的法线时,我没有渲染出任何东西。深度缓冲区中什么也没有。我已经检查了多次,确认着色器可以访问纹理(这就是最初截图显示球体应用了纹理的原因)。

即使我的切线和副法线指向各个方向,我仍然期望能够展示一些东西,即使它是错误的。但连环境颜色都不显示。(即使我不改变我的 lightDirviewdir,如果我只忽略顶点法线并查找纹理,我也会失去环境颜色)......

编辑3:最后一个问题

通常情况下,问题的一部分与您认为的错误无关。我的问题在于我正在使用另一个纹理覆盖我的法线贴图的绑定。

所以,现在这个问题解决了,我可以看到我的颜色展示了出来。带有美妙的凸凹映射效果。

然而,现在立方体贴图的接缝处出现了问题。我不确定这是由于切线计算还是由于我的法线贴图生成方式所致。我的法线贴图是从每个面的高度图独立生成的。
我认为这可以解释一些接缝效果,我将修改它以在这些边缘采样相邻的面,并观察结果。
我仍然认为生成的切线也会对这些接缝产生不利影响。我的想法是它们在接缝处会指向相反的方向。
屏幕截图: normal map seams 编辑4: 在测试EDIT1以下时,我使用了一个非常低多边形网格来制作我的球体。因此我只有最少的子网格。
我想看看我的不太完美的法线贴图球在具有很多多边形的情况下如何表现。这立即揭示了这个问题:

dang it...

如果不清楚的话,从左到右运行的是我的老朋友——接缝,但下面似乎是三角形边缘。
所以经过以上所有步骤后,我认为我又回到了最初的问题,即错误的切线。
仍然在寻求任何阅读此文的人的帮助。
编辑4: 好吧,那很快。这个网站http://www.geeks3d.com/20130122/normal-mapping-without-precomputed-tangent-space-vectors/给了我另一种创建切线的方式。虽然代码似乎与我在CPU上所做的有些相似,但它没有产生EDIT 3中产生那些随机方向的切线所产生的边缘。
我现在非常接近了。我仍然有接缝,这种生成切线的其他方法似乎增加了它们的“接缝性”。

enter image description here

编辑 5 我现在尝试修改我的普通地图生成代码。之前的代码是这样的:

for(int i = 0; i < 6; ++i)
{   
    float scale = 15.0;
    std::deque<glm::vec4> normalMap(textureSize*textureSize);
    for(int x = 0; x < textureSize; ++x)
    {
        for(int y = 0; y < textureSize; ++y)
        {
            // center point
            int i11 = utils::math::get_1d_array_index_from_2d(x,y,textureSize);
            float v11 = cubeFacesHeight[i][i11].r;

            // to the left
            int i01 = utils::math::get_1d_array_index_from_2d(std::max(x-1,0),y,textureSize);
            float v01 = cubeFacesHeight[i][i01].r;

            // to the right
            int i21 = utils::math::get_1d_array_index_from_2d(std::min(x+1,textureSize-1),y,textureSize);
            float v21 = cubeFacesHeight[i][i21].r;

            // to the top
            int i10 = utils::math::get_1d_array_index_from_2d(x,std::max(y-1,0),textureSize);
            float v10 = cubeFacesHeight[i][i10].r;

            // and now the bottom
            int i12 = utils::math::get_1d_array_index_from_2d(x,std::min(y+1,textureSize-1),textureSize);
            float v12 = cubeFacesHeight[i][i12].r;

            glm::vec3 S = glm::vec3(1, 0, scale * v21 - scale * v01);
            glm::vec3 T = glm::vec3(0, 1, scale * v12 - scale * v10);

            glm::vec3 N = (glm::vec3(-S.z,-T.z,1) / std::sqrt(S.z*S.z + T.z*T.z + 1));

            N.x = (N.x+1.0)/2.0;
            N.y = (N.y+1.0)/2.0;
            N.z = (N.z+1.0)/2.0;
            normalMap[utils::math::get_1d_array_index_from_2d(x,y,textureSize)] = glm::vec4(N.x,N.y,N.z,v11);
        }
    }
    for(int x = 0; x < textureSize; ++x)
    {
        for(int y = 0; y < textureSize; ++y)
        {
            cubeFacesHeight[i][utils::math::get_1d_array_index_from_2d(x,y,textureSize)] = normalMap[utils::math::get_1d_array_index_from_2d(x,y,textureSize)];
        }
    }
}

cubeFacesHeight 是一个包含 6std::dequeglm::vec4 数组,代表我的立方体贴图的六个面。每个面的颜色都是灰度的,我没有使用浮点数是因为原因无关紧要。

现在我已经对其进行了修改,但警告:这很丑,也很长。

for(int i = 0; i < 6; ++i)
{
    // 0 is negative X
    // 1 is positive X
    // 2 is negative Y
    // 3 is positive Y
    // 4 is negative Z
    // 5 is positive Z

    // +X:  right -Z (left),    left +Z (right),    top -Y (right),     bottom +Y (right)
    // -X:  right +Z (left),    left -Z (right),    top -Y (left),      bottom +Y (left)
    // -Z:  right -X (left),    left +X (right),    top -Y (bottom),    bottom +Y (top)
    // +Z:  right +X (left),    left -X (right),    top -Y (top),       bottom +Y (bottom)
    // -Y:  right +X (top),     left -X (top),      top +Z (top),       bottom -Z (top)
    // +Y:  right +X (bottom),  left -X (bottom),   top -Z (bottom),    bottom +Z (bottom)

    //+Z is towards, -Z is distance
    const int NEGATIVE_X = 0;
    const int NEGATIVE_Y = 2;
    const int NEGATIVE_Z = 4;
    const int POSITIVE_X = 1;
    const int POSITIVE_Y = 3;
    const int POSITIVE_Z = 5;

    float scale = 15.0;
    std::deque<glm::vec4> normalMap(textureSize*textureSize);
    for(int x = 0; x < textureSize; ++x)
    {
        for(int y = 0; y < textureSize; ++y)
        {
            // center point
            int i11 = utils::math::get_1d_array_index_from_2d(x,y,textureSize);
            float v11 = cubeFacesHeight[i][i11].r;

            // to the left
            int i01 = utils::math::get_1d_array_index_from_2d(std::max(x-1,0),y,textureSize);
            float v01 = cubeFacesHeight[i][i01].r;
            if(x-1 < 0)
            {
                if(i == NEGATIVE_X)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(textureSize-1,y,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_Z][i01].r;
                }
                else if(i == POSITIVE_X)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(textureSize-1,y,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_Z][i01].r;
                }
                else if(i == NEGATIVE_Z)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(textureSize-1,y,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_X][i01].r;
                }
                else if(i == POSITIVE_Z)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(textureSize-1,y,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_X][i01].r;
                }
                else if(i == NEGATIVE_Y)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(y,0,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_X][i01].r;
                }
                else if(i == POSITIVE_Y)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(y,textureSize-1,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_X][i01].r;
                }
            }

            // to the right
            int i21 = utils::math::get_1d_array_index_from_2d(std::min(x+1,textureSize-1),y,textureSize);
            float v21 = cubeFacesHeight[i][i21].r;
            if(x+1 > textureSize-1)
            {
                if(i == NEGATIVE_X)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(0,y,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_Z][i01].r;
                }
                else if(i == POSITIVE_X)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(0,y,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_Z][i01].r;
                }
                else if(i == NEGATIVE_Z)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(0,y,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_X][i01].r;
                }
                else if(i == POSITIVE_Z)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(0,y,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_X][i01].r;
                }
                else if(i == NEGATIVE_Y)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(y,0,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_X][i01].r;
                }
                else if(i == POSITIVE_Y)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(y,textureSize-1,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_X][i01].r;
                }
            }

            // to the top
            int i10 = utils::math::get_1d_array_index_from_2d(x,std::max(y-1,0),textureSize);
            float v10 = cubeFacesHeight[i][i10].r;
            if(y-1 < 0)
            {
                if(i == NEGATIVE_X)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(0,x,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_Y][i01].r;
                }
                else if(i == POSITIVE_X)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(textureSize-1,x,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_Y][i01].r;
                }
                else if(i == NEGATIVE_Z)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(x,textureSize-1,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_Y][i01].r;
                }
                else if(i == POSITIVE_Z)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(x,0,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_Y][i01].r;
                }
                else if(i == NEGATIVE_Y)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(x,0,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_Z][i01].r;
                }
                else if(i == POSITIVE_Y)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(x,textureSize-1,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_Z][i01].r;
                }
            }

            // and now the bottom
            int i12 = utils::math::get_1d_array_index_from_2d(x,std::min(y+1,textureSize-1),textureSize);
            float v12 = cubeFacesHeight[i][i12].r;
            if(y+1 > textureSize-1)
            {
                if(i == NEGATIVE_X)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(0,x,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_Y][i01].r;
                }
                else if(i == POSITIVE_X)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(textureSize-1,x,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_Y][i01].r;
                }
                else if(i == NEGATIVE_Z)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(x,0,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_Y][i01].r;
                }
                else if(i == POSITIVE_Z)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(x,textureSize-1,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_Y][i01].r;
                }
                else if(i == NEGATIVE_Y)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(x,0,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_Z][i01].r;
                }
                else if(i == POSITIVE_Y)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(x,textureSize-1,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_Z][i01].r;
                }
            }

            glm::vec3 S = glm::vec3(1, 0, scale * v21 - scale * v01);
            glm::vec3 T = glm::vec3(0, 1, scale * v12 - scale * v10);

            glm::vec3 N = (glm::vec3(-S.z,-T.z,1) / std::sqrt(S.z*S.z + T.z*T.z + 1));

            N.x = (N.x+1.0)/2.0;
            N.y = (N.y+1.0)/2.0;
            N.z = (N.z+1.0)/2.0;

            normalMap[utils::math::get_1d_array_index_from_2d(x,y,textureSize)] = glm::vec4(N.x,N.y,N.z,v11);
        }
    }
    for(int x = 0; x < textureSize; ++x)
    {
        for(int y = 0; y < textureSize; ++y)
        {
            cubeFacesHeight[i][utils::math::get_1d_array_index_from_2d(x,y,textureSize)] = normalMap[utils::math::get_1d_array_index_from_2d(x,y,textureSize)];
        }
    }
}

现在我正在将采样高度的过程“渗透”到相邻的立方体面上,同时生成法线贴图。这实际上增加了接缝的出现。

hmmmm

但是这也引发了一些问题。例如,“为什么影响会增加?”您可以看到现在它是一种类似于斜角的效果。
所以,我相当确定当“渗透”到下一个面时已经正确地匹配了我的立方体面。这让我回到了切线不正确的问题上。
即使我完全混淆了立方体面,也不会产生斜角效果,而是完全不连贯的东西。例如,即使在完全平坦的部分上,即将法线图生成渗入到下一个面中也不会产生任何影响,我仍然看到一个巨大的斜角。

dang it more

这让我想到,如果切线在之前是正确的,法线贴图就像是“匹配”了切线方向?我不知道。 快速编辑 我注意到在原始映射生成过程中,我实际上对面部边缘进行了两次采样。如果我去掉这个双重采样,只使用0作为额外采样,最终会看到同样的大接缝。我不确定这意味着什么... 另一个快速编辑 这张图片显示了一些我认为非常有意义的东西。 enter image description here 我在这里可以看到两个不同的面“指向”相反的方向。这是通过我的片段切线生成得出的。
所以我又回到了我的切线存在问题。

@kfsone 谢谢您的建议,但是您在一开始发表无礼的评论并没有必要。我想赞同数已经告诉我我的问题没有任何问题。不过,我会按照您的建议在gamedev网站上提问(这是个好主意)。如果我的提问方式很不好,请管理员关闭我的问题。 - NeomerArcana
如果您能够以完整的方式将项目发送给我,包括所有源代码和所有资源,那么我查看源代码并进行检查就不会有问题。这是一个很难回答的问题,因为涉及到切线、法线、副切线和双曲线的计算,还涉及到球体的构建方式以及顶点的布局方式以及如何定义纹理坐标。(...) - Francis Cugler
当我开始使用3D渲染并生成基本的3D形状,例如圆柱体,并分配纹理坐标以使其无缝并与光照正常工作时,当定义法线时,我必须考虑到在多个三角面之间共同定义的每个顶点都会生成多个法线。为了解决这个问题,我必须从为特定顶点生成的所有法线中取平均值。我将在答案部分显示一个插图,尽管它不是您问题的答案或解决方案。 - Francis Cugler
也许你想在gamedev.net的图形论坛上发布这样的帖子。 - v.oddou
3个回答

1

法线贴图最好与最初创建法线向量矩阵的配合使用

我认为你的问题与表面上的切线不均匀有关。像这样的问题通常首先要查看UV映射。而将一个球形物体映射到二维图像并不容易(看看各种地球投影拓扑图,你就会明白我的意思)。在某些情况下,你会遇到拉伸、边缘或剪切,很可能是以上所有因素的组合。通常,通过UV映射,我们可以选择在表面上隐藏这些效果的位置。行星的极点经常被选为此目的。

我建议你检查一下重新调整你的切线和副法线,使它们都共享一个公共的全局方向,即切线=北,副法线=东,法线向外(高度)。

如果所有的切线和副法线都统一定向,那么它们的不均匀性会直接影响法线贴图问题中出现的伪影,因为它们可以扭曲该位置的法线贴图效果,如果法线贴图是在假设所有切线和副法线都具有统一方向的情况下生成的。

本质上,法线贴图是通过隐含理解切线和副法线来烘焙/创建的。如果在重新应用法线贴图时,表面的切线和副法线与最初创建法线贴图的隐含理解不一致,则会出现光照和阴影错误。

正交法向量矩阵的好处

这样做的好处是切线和副法线向量通常用于查找2D纹理坐标。如果矩阵非正交,则存在错切、旋转或在倾斜角度下精度损失的风险。


定义统一、正交的法向量矩阵

您可以采用不同的方式来计算法线/切线/副法线,以确保两个因素:

  1. 统一的物体方向,即...所有方向都指向相同的相对方向
  2. 正交向量,这将限制纹理查找扭曲

这将通过将预定义的正交向量矩阵通过两次旋转和一个移动进行变换来实现。为了解释起见,我不会将这三个矩阵操作合并为单个矩阵,但在您的代码中这样做可能会有益。

首先,从已定义的向量矩阵开始

vec3 = [1, 0, 0, 0, 1, 0, 0, 0, 1];enter image description here

其次,在对象空间而非世界空间中执行这些操作

否则,您将不得不将该对象转换回到世界中心,并将其旋转回到其原始方向,然后应用法线变换,最后将对象发送回到其工作位置和方向。

第三步,从vtx[n]到对象中心创建一个向量

这个向量将告诉您在两个方向上旋转您的法向量矩阵的程度:

  • 经度旋转
  • 纬度旋转

enter image description here

第四步,将您的法向量矩阵旋转以对齐。

enter image description here

最后,将您的法向量矩阵移动距离。

enter image description here

洗涤并重复。

enter image description here


如果需要保留不均匀、非正交的UV

您可以基于不一致的UV布局创建法线贴图,使其考虑到该布局并因此适当地应用而不产生影响。但是,您的法线贴图必须从这种固有的不一致性中创建,以便它能够优雅地应用于那些UV。


什么是边缘像素插值?

第三个问题,从普通贴图的边缘弯曲如何跟立方体贴图的形状一致来看,我在思考你是如何插值正常贴图的边缘像素的。


GLSL纹理立方体贴图查找?

此外,也许我只是没有找到你回答中涉及此部分的地方,但您是否考虑使用GLSL立方体贴图查找函数? gvec4 texture( gsamplerCube sampler, vec3 P, [float bias]);


0

这是我在评论中提到的示意图:

Normal Illustration

如你所见,红线是生成的法线,底部的每个顶点都有两个。这会导致光照问题,因为每个三角形的面朝向不同。当我第一次遇到这个问题时,我必须对每个顶点上的两个法线(由黄线表示)取平均值来修复光照计算。

至于你在立方体上看到的接缝 - 凹凸贴图,这可能与你生成球体的顶点定义和应用纹理坐标的方式有关。如果没有看到你整个解决方案或项目并进行实际操作,我无法直接判断。问题可能甚至与切线无关,而是与纹理映射造成的包裹效果有关。

这并不是针对你问题的直接答案,而是提醒你要注意的建议,因为实现这些类型的着色器和渲染方法有很多不同的方式。


我正在生成一个Icosphere作为球体。因此,顶点本身不会对齐在立方体的面上。法线只是从球体中心的方向向量。因此,无需生成每个面与每个顶点的法线或平均值等。它们非常简单明了。我的纹理坐标没有任何问题,因为任何其他的cubemap纹理映射都可以完美地工作。我想接缝是由于我按面生成法线的方式。 - NeomerArcana
今天我实际上得到了一个相当令人满意的结果。从http://www.rorydriscoll.com/2012/01/11/derivative-maps/获取灵感,我仍在努力解密“What Now?”部分的含义,因为那似乎比我现在得到的像素化效果更加平滑。图片链接:http://thelastboundary.com/wp-content/uploads/2015/06/Fleet-Battles-2015-06-01-23-16-45-41.png - NeomerArcana
好的,很高兴听到你的进展。正如我所说,如果没有在我的端上运行项目并使用一些数字和方程式进行工作,我无法确定问题所在。仅从阅读中,我没有看到任何异常之处。我不知道是否有影响,但我注意到你正在使用 GLM,这很好,但你是构建哪个版本? - Francis Cugler
由于使用了偏导数,他们正在讨论如何通过应用链式法则在算法中从一个坐标空间转换到另一个坐标空间。这样做可以使着色器在物体位置靠近视图相机时在导数之间进行插值。如果不使用链式法则来实现这一点,那么就像第一个示例一样,当物体靠近相机时,您会看到块状的块而不是更平滑的渲染。 - Francis Cugler
是的,我有点理解了。我不理解的是“纹素空间”和屏幕空间UV导数。所以我认为我不应该再使用我的float高度值,而是应该使用vec2;但是我该如何计算呢?其次,是屏幕空间uv导数,我完全不理解。 - NeomerArcana
这是一个有些复杂和难以理解的内容,而且很难解释清楚。从阅读页面所得到的信息,它使用了不同的方法,与使用正切相反,在此使用具有向量场的梯度,你使用的是其导数作为你的映射。 - Francis Cugler

0

以前我花了很长时间才理解如何计算切线空间,也许我最终的方法可以帮到你。

你有三个顶点v0、v1、v2。每个顶点都有位置、法线和纹理坐标。我们来计算v0的切线空间。z轴将是v0.normal。我们需要计算x轴和y轴。

三角形上的任意点都可以表示为v0.pos + (v1.pos-v0.pos)*t + (v2.pos-v0.pos)*s。任何纹理坐标都可以表示为v0.uv + (v1.uv - v0.uv)*t + (v2.uv - v0.uv)*s。

在切线空间中,我们需要满足v1.uv - v0.uv = (1,0)和v2.uv-v0.uv = (0,1)。我们可以解出s和t!对于两种情况都是如此!这就是我们的切线和副切线的s和t。只需将它们插入回位置方程,你就可以得到uv=(0,1)和uv=(1,0)时的位置。减去v0.pos,你就得到了x轴和y轴!还要对它们进行归一化。

这就是v0的切空间,一个3x3的矩阵。它不一定正交,但没关系。同时,你需要为每个三角形使用该顶点计算出每个顶点的矩阵。只需对它们求平均值即可。

在渲染时插值这些每个顶点的矩阵,并对每个像素进行归一化处理。

测试的好方法是仅渲染z列-它应该是法线。

对于光照,从光源中减去插值位置并通过“切向矩阵”进行变换。现在你的光源在切空间中,其中(0,0,1)指向光源,法线贴图指向直上方。


如果原始法线贴图是使用正交切线、法线和副法线创建的,则需要确保3D表面具有正交切线、法线和副法线。通常,切线和副法线向量用作u和v查找向量。即...切线=v,副法线=u(或反之亦然)。在法线贴图中,只有两个通道(R&G)与表面照明的方向性相关,蓝色通道与表面法线被R&G通道扭曲的程度相关。 - Andrew

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接