【问题标题】:Wrong Normal Mapping错误的法线贴图
【发布时间】:2019-04-11 04:54:09
【问题描述】:

我正在 OpenGL 上创建一个模型加载程序。我处理了光和镜面反射,但我坚持使用法线贴图。我想我在法线贴图计算中犯了一个错误。

正常图像是:

当我应用法线贴图效果时,它看起来是这样的,这是屏幕截图:

我的顶点着色器:

#version 430 core

layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;
layout(location = 2) in vec2 uvw;
layout(location = 3) in vec3 tangent;
layout(location = 4) in vec3 biTangent;

uniform mat4 M;
uniform mat4 MVP;
uniform mat3 N;

out block
{
    vec4 position;
    vec3 normal;
    vec2 uvw;
    vec3 tangent;
    vec3 biTangent;
    mat3 TBN;
} Out;

void main()
{
    Out.position = M * vec4(position, 1.0);
    Out.normal = normalize(N * normal);
    Out.uvw = uvw;
    Out.tangent = (M * vec4(tangent, 0.0)).xyz;
    Out.TBN = mat3(tangent, biTangent, normal); 

    gl_Position = MVP * vec4(position, 1.0);
}

片段着色器是:

#version 430 core
#define M_PI        3.14159265358979323846

layout(binding = 0) uniform sampler2D dts;
layout(binding = 1) uniform sampler2D sts;
layout(binding = 2) uniform sampler2D nts;

struct Light {
    vec3 position;
    vec3 filterColor;
    float multiplier;
};

struct Material {
    vec3 baseColor;
    float baseColorMultiplier;
    float roughness;
    float ior;
};

uniform Light light;
uniform Material material;

in block
{
    vec4 position;
    vec3 normal;
    vec2 uvw;
    vec3 tangent;
    vec3 biTangent;
    mat3 TBN;
} In;

out vec4 color;


vec3 Le(Light light, vec4 position, vec3 wi) {
    vec3 Le;
    float dist = length(wi);
    Le = light.filterColor / (dist * dist);

    return Le;
}

vec3 Fresnel(vec3 spec, vec3 normal, vec3 wi)
{
    return spec + (1 - spec) * pow((1 - max(0.0, dot(wi, normal))), 5);
}

vec3 Normal()
{
    vec4 norm = texture2D(nts, In.uvw);

    return vec3(norm);
}

vec3 Diffuse(Material material, vec2 uvw) {
    vec4 diff = texture2D(dts, uvw);
    return vec3(diff) / M_PI;
}

vec3 Reflection(Material material, vec3 wi, vec3 normal, vec2 uvw)
{
    vec3 f;
    float cosTheta = dot(normal, wi);

    vec4 spec = texture2D(sts, uvw);
    f += vec3(1.0) * vec3(spec) * pow(max(0.0, abs(cosTheta)), material.roughness);

    f += Fresnel(vec3(spec), normal, wi);
    return f;
}

vec3 BRDF(Light light, Material material, vec3 wo, vec3 wi, vec4 position, vec3 normal, vec2 uvw) {
    vec3 L;

    // Evaluate emitted light
    vec3 Li = Le(light, position, wi);

    // Diffuse
    vec3 f = Diffuse(material, uvw);    

    // Reflection
    float cosThetaI = max(0.0, dot(wi, normal));
    if(cosThetaI > 0.0) {
        f += Reflection(material, wi, normal, uvw);
    }

    // BRDF function
    L += f * Li * max(0.0, dot(wi, normal));

    return L;
}

void main(void)
{   
    vec3 L;
    vec3 wi, wo;

    // Evaluate incoming and outgoing light direction
    wi = normalize(light.position - vec3(In.position));
    wo = reflect(-wi, In.normal);

    // Evaluate normal map
    vec4 normal = texture2D(nts, In.uvw);
    normal = normalize(normal * 2.0 -1.0);
    normal = vec4(normalize(In.TBN * normal.xyz), 0.0);

    L += BRDF(light, material, wo, wi, In.position, normal.xyz, In.uvw);
    color = vec4(L, 1.0);
}

我的正切和副切计算代码是:

void CGLPrimitive::CalculateTangentBiTangent()
{
    for (unsigned int i = 0; i < positions.size(); i+=3) {
        CVector3<float> p1 = positions[i];
        CVector3<float> p2 = positions[i + 1];
        CVector3<float> p3 = positions[i + 2];

        CVector2<float> uv1 = uv[i];
        CVector2<float> uv2 = uv[i+1];
        CVector2<float> uv3 = uv[i+2];


        CVector3<float> e1 = p2 - p1;
        CVector3<float> e2 = p3 - p1;

        CVector2<float> deltaUV1 = uv2 - uv1;
        CVector2<float> deltaUV2 = uv3 - uv1;

        float f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);


        CVector3<float> t;
        t.x = f * (deltaUV2.y * e1.x - deltaUV1.y * e2.x);
        t.y = f * (deltaUV2.y * e1.y - deltaUV1.y * e2.y);
        t.z = f * (deltaUV2.y * e1.z - deltaUV1.y * e2.z);
        t = Normalize(t);
        tangents.push_back(t);
        tangents.push_back(t);
        tangents.push_back(t);

        CVector3<float> b;
        b.x = f * (-deltaUV2.x * e1.x + deltaUV1.x * e2.x);
        b.y = f * (-deltaUV2.x * e1.y + deltaUV1.x * e2.y);
        b.z = f * (-deltaUV2.x * e1.z + deltaUV1.x * e2.z);
        b = Normalize(b);
        biTangents.push_back(b);
        biTangents.push_back(b);
        biTangents.push_back(b);
    }


}

我想知道我在哪里犯了错误。谢谢。

【问题讨论】:

  • 图片和你想要的有什么不同?
  • @user463035818:对不起。我添加了一个法线输出图像和法线贴图。
  • tangentbiTangent 必须通过普通矩阵 Out.tangent = normalize(N * tangent); Out.biTangent = normalize(N * biTangent); 进行转换

标签: c++ opengl glsl


【解决方案1】:

tangentbiTangent 必须像使用 normal 向量一样由法线矩阵转换:

Out.tangent   = normalize(N * tangent); 
Out.biTangent = normalize(N * biTangent);
Out.normal    = normalize(N * normal);

在片段着色器中,计算是在世界空间中完成的。为此,您必须将法线贴图的法线向量转换为世界空间。

mat3(tangent, biTangent, normal)是从纹理空间转换到模型空间的矩阵。

从纹理空间转换到世界空间的矩阵是:

Out.TBN = N * mat3(tangent, biTangent, normal);  

Out.TBN = mat3(Out.tangent, Out.biTangent, Out.normal);       

使用上述切线空间矩阵的计算来解决您的问题。

【讨论】:

  • 它没有解决我的问题。谢谢您的回答。也许切线-双切线插值存在问题。你认为我应该提出一个新问题吗?还是我应该在这里添加?
  • @ADesignersEncyclopedia 没有任何变化吗?在此处添加以完成您的问题。
【解决方案2】:

几件事:

1) 正如 Rabbid76 所说,您必须将切线和双切线相乘。您可以使用模型视图矩阵,因为这些向量不会受到非均匀比例引起的任何失真。

2) 我们不知道您在 GPU 中的纹理格式。如果它的 alpha 通道设置为 1.0,然后您对整个向量进行归一化:

vec4 normal = texture2D(nts, In.uvw);
normal = normalize(normal * 2.0 -1.0); 

归一化向量可能在 4D 中归一化,但在 3D 中可能不归一化。只需从纹理中提取 rgb 并使用它。

vec3 normal = texture2D(nts, In.uvw).rgb;

3) 这是干什么用的? normal = normal * 0.5 + 0.5; 我相信这是您问题的根源,因为您以错误的方式平滑法线。示例:

normal (1,0,0) 将变为 (1,0.5,0.5)

但是

normal (-1,0,0) 将变为 (0,0.5,0.5)

我还建议您通过绘制法线、切线和双切线(例如使用几何着色器)来调试它们,以确保它们是正确的。

【讨论】:

  • 非常感谢。我修复了代码。通过在插值中添加 uv 坐标解决了问题。
猜你喜欢
  • 1970-01-01
  • 2015-04-16
  • 2017-05-16
  • 2019-01-25
  • 2018-02-02
  • 1970-01-01
  • 2013-08-14
  • 1970-01-01
  • 2020-04-15
相关资源
最近更新 更多