使用OpenGL / GLSL实现法线贴图

时间:2015-03-13 22:13:45

标签: opengl glsl fragment-shader vertex-shader

我正在学习GLSL并试图实现一些照明和制图技巧。我正在使用ShaderDesigner工具。编码法线贴图后,我发现我的模型照明看起来不真实。这是我的代码和一些图片。如果有可能告诉我我的问题是什么。

顶点着色器

#define MAX_LIGHTS 1

struct LightProps
{
    vec3 direction[MAX_LIGHTS];
};

attribute vec3 tangent;
attribute vec3 bitangent;

varying LightProps lights;

void main()
{
    vec3 N = normalize(gl_NormalMatrix*gl_Normal);
    vec3 T = normalize(gl_NormalMatrix*tangent);
    vec3 B = normalize(gl_NormalMatrix*bitangent);

    mat3 TBNMatrix = mat3(T,B,N);

    vec4 vertex = gl_ModelViewMatrix*gl_Vertex;
    for(int i = 0; i < MAX_LIGHTS; i++)
    {
        vec4 lightPos = gl_LightSource[i].position;
        lights.direction[i] = vec3(lightPos.w > 0 ? lightPos-vertex : lightPos);
        lights.direction[i] *= TBNMatrix;
    }

    gl_TexCoord[0] = gl_MultiTexCoord0;
    gl_Position = gl_ModelViewProjectionMatrix*gl_Vertex;
} 

片段着色器

#define MAX_LIGHTS 1
struct LightProps
{
    vec3 direction[MAX_LIGHTS];
};

uniform sampler2D textureUnit;
uniform sampler2D normalTextureUnit;
uniform vec4 TexColor;

varying LightProps lights;

void main()
{
    vec3 N = normalize(texture2D(normalTextureUnit,gl_TexCoord[0].st).rgb*2.0-1.0);

    vec4 color = vec4(0,0,0,0);
    for(int i = 0; i < MAX_LIGHTS; i++)
    {
        vec3 L = lights.direction[i];
        float dist = length(L);
        L = normalize(L);

        float NdotL = max(dot(N,L),0.0);

        if(NdotL > 0)
        {
            float att = 1.0;
            if(gl_LightSource[i].position.w > 0)
            {
                att = 1.0/ (gl_LightSource[i].constantAttenuation +
                gl_LightSource[i].linearAttenuation * dist +
                gl_LightSource[i].quadraticAttenuation * dist * dist);
            }

            vec4 ambient = gl_FrontLightProduct[i].ambient;
            vec4 diffuse = clamp(att*NdotL*gl_FrontLightProduct[i].diffuse,0,1);

            color += att*(ambient+diffuse);
        }
    }

    vec4 textureColor = texture2D(textureUnit, vec2(gl_TexCoord[0]));
    gl_FragColor = TexColor*textureColor + gl_FrontLightModelProduct.sceneColor + color;
}

我将TexColor设置为(0.3,0.3,0.3,1.0)并截取屏幕截图:

截图

Screenshot

当我将相机和光线向左旋转时,几乎没有光线照射, 但当我向右旋转时,飞机被完全照亮。我认为有一些错误,因为没有法线贴图平面从侧面看起来相同。这是正常的纹理。提前谢谢。

法线贴图:

Normal Texture

1 个答案:

答案 0 :(得分:4)

该法线贴图位于切线空间中,但您将其视为对象空间。

除了法线之外,您还需要每个顶点的双切线和/或切线矢量,以便形成进入和切出切线空间的转换的基础。该矩阵通常简称为TBN

您有两个选择:

  1. 将所有光照方向矢量转换为切线空间

    • 用于前向着色,可以在顶点着色器中完成
  2. 将法线贴图从切线空间转换回视图空间

    • 延迟着色要求,必须在片段着色器
    • 中完成
  3. 这两个选项都需要构建TBN矩阵,如果您的切线空间基础是正交的(可以配置Assimp等建模软件为您执行此操作),您可以转置TBN矩阵来执行任一操作

    您正在实施前向着色,因此解决方案#1是您应采取的方法。


    以下是解决方案#1的必要步骤的概述。通常,您会在顶点着色器中计算光照方向向量,以获得更好的性能。

    将光照矢量转换为切线空间的顶点着色器:

    attribute vec3 tangent;
    attribute vec3 bitangent;
    
    varying vec3 N;
    varying vec3 V;
    varying vec3 E;
    
    varying vec3 T;
    varying vec3 B;
    
    void main()
    {
        N = normalize(gl_NormalMatrix*gl_Normal);
        V = vec3(gl_ModelViewMatrix*gl_Vertex);
        E = normalize(-V);
    
        T = normalize(gl_NormalMatrix*tangent);
        B = normalize(gl_NormalMatrix*bitangent);
    
        gl_TexCoord[0] = gl_MultiTexCoord0;
        gl_Position = gl_ModelViewProjectionMatrix*gl_Vertex;
    }
    

    将光照矢量转换为切线空间的片段着色器:

    varying vec3 N;
    varying vec3 V;
    varying vec3 E;
    
    varying vec3 B;
    varying vec3 T;
    
    uniform sampler2D textureUnit;
    uniform sampler2D normalTextureUnit;
    uniform vec4 TexColor;
    
    #define MAX_LIGHTS 1
    
    void main()
    {
        // Construct Tangent Space Basis
        mat3 TBN = mat3 (T, B, N);
    
        vec3 normal = normalize (texture2D(normalTextureUnit,gl_TexCoord[0].st).xyz*2.0 - 1.0);
    
        vec4 color = vec4(0,0,0,0);
        for(int i = 0; i < MAX_LIGHTS; i++)
        {
            vec4 lightPos = gl_LightSource[i].position;
            vec3 L = lightPos.w > 0 ? lightPos.xyz - V : lightPos;
    
            L *= TBN; // Transform into tangent-space
    
            float dist = length(L);
            L = normalize(L);
    
            float NdotL = max(dot(L,N),0.0);
            if(NdotL > 0)
            {
                float att = 1.0;
                if(lightPos.w > 0)
                {
                    att = 1.0/ (gl_LightSource[i].constantAttenuation +
                    gl_LightSource[i].linearAttenuation * dist +
                    gl_LightSource[i].quadraticAttenuation * dist * dist);
                }
    
                vec4 diffuse =  clamp(att*NdotL*gl_FrontLightProduct[i].diffuse,0,1);
                color += att*gl_FrontLightProduct[i].ambient + diffuse;
            }
        }
    
        vec4 textureColor = texture2D(textureUnit, vec2(gl_TexCoord[0]));
        gl_FragColor = TexColor*textureColor + gl_FrontLightModelProduct.sceneColor + color;
    }
    

    有一个教程here应该填补空白并解释如何计算切线和比特。