【问题标题】:Implementing Normal Mapping using OpenGL/GLSL使用 OpenGL/GLSL 实现法线贴图
【发布时间】:2015-03-13 22:13:45
【问题描述】:

我正在学习 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) 并截图:

截图

  

当我向左旋转相机和灯光时,有一点点灯光, 但是当我向右旋转时,飞机完全被照亮了。我认为有问题,因为没有法线贴图,飞机从两侧看起来都是一样的。这是正常的纹理。提前致谢。

法线贴图:

  

【问题讨论】:

  • 我认为每个片段的这些法线,我只需要将这些法线转换到世界空间以与光的方向相乘以获得漫反射强度
  • 不,可以正常工作的法线存储在对象空间中,但事实并非如此。这些法线在切线空间中,您可以看出这一点,因为主要颜色是蓝色。您有两个选择:1. 将所有照明方向向量转换为切线空间或 2. 将法线贴图从切线空间反向转换回视图空间.两者都需要构建 TBN 矩阵。
  • 好的,谢谢。我会试试的
  • 我尝试了您建议的解决方案,结果如下i1300.photobucket.com/albums/ag94/haksist/…。是真的吗?
  • 看起来差不多,如果不知道灯的确切位置很难说。颜色被洗掉了,但在您的原始屏幕截图中已经是这种情况了。

标签: opengl glsl fragment-shader vertex-shader


【解决方案1】:

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

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

这里有两种选择:

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

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

    • 延迟着色需要,必须在片段着色器中完成

这两个选项都需要构建 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 应该填补空白并解释如何计算正切和双切。

【讨论】:

  • 什么是lightPos.w,我如何获得它?
猜你喜欢
  • 2013-08-14
  • 2014-04-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-22
  • 2012-01-12
相关资源
最近更新 更多