使用 GLSL 和 C++ 在 Vulkan 中实现切线接缝

问题描述 投票:0回答:1

我正在使用tinygltf 编写一个小型Vulkan 玩具渲染器来加载我的模型。 在过去的两周里,我一直在尝试在渲染器中实现法线贴图,但遇到了问题。

我想我将其范围缩小到了切线/TBN 矩阵计算,但此时它可以是任何东西。 我从 glTF 编辑器上的示例模型中检索了所有模型(由于信誉太低而无法链接)。正如您在附图中看到的,当使用采样和 TBN 转换后的法线时,模型中间有一条大接缝。 为了确保这不是模型本身的问题,我尝试了以下操作:

  1. 我将模型导入到 Blender 中查看是否可以看到相同的接缝,但事实并非如此。在 Blender 中一切看起来都很好。
  2. 我尝试了几种不同的型号(Sponza、损坏的头盔、紧身胸衣),但它们都有相同的问题,到处都是奇怪的切线接缝。

有些模型没有切线,所以我进入 Blender 并导出带有切线的模型,但接缝仍然存在。

然后,我在导入模型时尝试使用此处提供的说明自行计算切线和副切线(第 5 页,清单 7.4)。但即使这样也没有解决我的问题,导入的切线和计算的切线是完全相同的。

我尝试过的事情:

  1. 将光线和视图方向从世界方向转换为顶点着色器内的切线空间,如学习 OpenGL 文章中所示,但无济于事。

顶点着色器:

void main() 
{
    vec4 worldPos = primitive.model * vec4(inPosition, 1.0f);
    gl_Position = ubo.viewProjection * worldPos;

    mat3 modelM3 = transpose(inverse(mat3(primitive.model)));
    vec3 T = normalize(modelM3 * inTangent.xyz);
    vec3 N = normalize(modelM3 * inNormal);
    vec3 B = normalize(cross(N, T)) * inTangent.w;

    // Transposed matrix to transform light and view vectors from world to tangent space.
    mat3 TBN = mat3(
    T.x, B.x, N.x,
    T.y, B.y, N.y,
    T.z, B.z, N.z
    );

    vs_out.normal = N;
    vs_out.tangent = vec4(T, inTangent.w);
    vs_out.bitangent = normalize(modelM3 * inBitangent);
    vs_out.texcoord = inTexcoord;

    vs_out.lightDir = ubo.globalLightDirection.xyz * TBN;
    vs_out.viewDir = (worldPos.xyz - ubo.cameraPos.xyz) * TBN;
}

片段着色器:

vec3 calculate_diffuse(vec3 color, vec3 normal)
{
    float diffuse_strength = dot(normal, -normalize(fs_in.lightDir));

    // HALF-LAMBERT
    diffuse_strength = diffuse_strength * 0.5f + 0.5f;
    diffuse_strength = clamp(diffuse_strength, 0.f, 1.f);

    return color * diffuse_strength;
}

void main()
{
    vec4 color = texture(samplerColorMap, fs_in.texcoord);

    if (ALPHA_MASK) {
        if (color.a < ALPHA_MASK_CUTOFF) {
            discard;
        }
    }

    vec3 localNormal = 2.f * texture(samplerNormalMap, fs_in.texcoord).rgb - 1.f;
    vec3 normal = normalize(localNormal);

    vec3 diffuse = calculate_diffuse(color.rgb, normal);

    outFragColor = vec4(diffuse, color.a);
}
  1. 使用顶点着色器内部计算的 TBN 矩阵,将采样法线从片段着色器内的切线空间转换到世界空间,也没有成功。

顶点着色器:

void main() 
{
    vec4 worldPos = primitive.model * vec4(inPosition, 1.0f);
    gl_Position = ubo.viewProjection * worldPos;

    vs_out.normal = inNormal;
    vs_out.tangent = inTangent;
    vs_out.bitangent = inBitangent;
    vs_out.texcoord = inTexcoord;

    vs_out.lightDir = ubo.globalLightDirection.xyz;
    vs_out.viewDir = (worldPos.xyz - ubo.cameraPos.xyz);

    vec3 T = normalize(mat3(primitive.model) * inTangent.xyz);
    vec3 B = normalize(mat3(primitive.model) * inBitangent);
    vec3 N = normalize(mat3(primitive.model) * inNormal);

    vs_out.TBN = mat3(T, B, N);
}

片段着色器:

vec3 calculate_diffuse(vec3 color, vec3 normal)
{
    float diffuse_strength = dot(normal, -normalize(fs_in.lightDir));

    // HALF-LAMBERT
    diffuse_strength = diffuse_strength * 0.5f + 0.5f;
    diffuse_strength = clamp(diffuse_strength, 0.f, 1.f);

    return color * diffuse_strength;
}


void main()
{
    vec4 color = texture(samplerColorMap, fs_in.texcoord);

    if (ALPHA_MASK) {
        if (color.a < ALPHA_MASK_CUTOFF) {
            discard;
        }
    }

    vec3 localNormal = 2.f * texture(samplerNormalMap, fs_in.texcoord).rgb - 1.f;
    vec3 normal = normalize(localNormal * fs_in.TBN);

    vec3 diffuse = calculate_diffuse(color.rgb, normal);

    outFragColor = vec4(diffuse, color.a);
}
  1. 我使用了 Sascha Willems 的着色器代码,但这导致了相同的接缝。 Sascha Willems 的 glTF 场景渲染示例 Sascha Willems 的顶点和片段着色器与上面的示例一起使用

我的 github 存储库可以在这里找到 导入网格并在 coral_mesh::Builder::load_from_gltf() 中计算切线,并且可以在着色器文件夹中找到着色器

提前非常感谢!

图片:

Helmet diffuse lighting with seam running across the middle

Sponza diffuse lighting with seams in the tangents

c++ graphics glsl vulkan normals
1个回答
0
投票

所以,问题在于普通纹理没有我想象的格式。普通纹理应该具有 VkFormat“VK_FORMAT_R8G8B8_UNORM”,这是我提供给纹理抽象构造函数的。但是,构造函数从未使用此提供的值,而是使用“VK_FORMAT_R8G8B8_SRGB”初始化所有纹理。 解决这个问题,修复所有接缝。 这是经典的图像视图格式问题困扰着我和许多其他图形程序员。

© www.soinside.com 2019 - 2024. All rights reserved.