OpenGL.Shader: mapa de luz normal de 9 aprendizajes (calculando la matriz TBN)

OpenGL.Shader: mapa de luz normal de 9 aprendizajes (calculando la matriz TBN)

Este artículo aprende sobre mapas normales. Los mapas normales se utilizan especialmente en el desarrollo de juegos y el desarrollo de sistemas GIS. Su expresividad es particularmente fuerte y el efecto de dibujo es muy cercano a la realidad. El punto más importante es que podemos hacer un modelo muy económico con muy poco costo. Esto es especialmente importante en la obra maestra del juego.

              

Echemos un vistazo al efecto de las dos texturas de cubos anteriores. El lado izquierdo es un mapa de textura normal y el lado derecho es un mapa normal. Los efectos visuales de los dos son enormemente diferentes. Este es el encanto del mapa normal.

Antes del código de presentación, aquí hay una breve descripción del conocimiento relevante del mapa normal.

1. Mapeo normal

Al simular el efecto de iluminación, ¿qué hace que la superficie a iluminar sea una superficie completamente plana? La respuesta es el vector normal de la superficie. Tomando un ladrillo como ejemplo, desde la perspectiva del algoritmo de iluminación, la superficie del ladrillo tiene solo un vector normal, y la superficie se ilumina de manera consistente en base a este vector normal, por lo que la realización del efecto de detalle suele ser relativamente simple. ¿Qué pasa si cada fragmento tiene su propia normal diferente? De esta forma, podemos cambiar el vector normal según los detalles sutiles de la superficie; esto dará como resultado un efecto visual que parece mucho más complicado en la superficie:

Cada fragmento utiliza su propia normal, por lo que podemos hacer una superficie compuesta por muchos planos diminutos (diferentes vectores normales), y los detalles de la superficie de dicho objeto mejorarán enormemente. Esta técnica de usar cada píxel de fragmento para usar su propia normal en lugar de usar la misma normal para todos los fragmentos en una superficie se llama mapeo normal.

Dado que el vector normal es un modelo geométrico de tres elementos, una textura 2D no solo puede almacenar datos de color e iluminación, sino también almacenar el vector normal. Piense en ello, los componentes de color r, g y b en la textura están representados por un modelo matemático de vec3. De manera similar, los elementos del vector normal x, y, z de vec3 también se pueden almacenar en la textura, en lugar de los elementos de color r, gyb para formar una textura normal. De esta manera, podemos muestrear el vector normal de la posición correspondiente de la textura normal en base a un conjunto de datos de posición al mismo tiempo. De esta forma, el mapa normal puede funcionar como un mapa de textura.

2. Espacio tangente

Dado que mapeamos el vector normal (x, y, z) al componente (r, g, b) de una textura, entonces la primera intuición es que el vector normal de cada fragmento debe ser perpendicular a la textura. El plano (es decir, el plano compuesto por coordenadas UV), las proporciones de los tres componentes están todos en el componente z (b), por lo que la textura normal presenta principalmente una apariencia visual azulada. (Como se muestra a continuación) Pero esto tendrá un problema serio. En nuestro ejemplo, hay seis caras de un cubo, solo el vector normal de la cara superior es (0, 0, 1), ¿no pueden las caras en otras direcciones usar este método? ¿Textura de línea?

Piénselo, ¿cómo convertimos las coordenadas de textura / vértice del modelo en coordenadas mundiales? ¿Cómo se sincroniza el vector normal con la operación de cambio del modelo? Todos son a través de la operación matricial del sistema de coordenadas, y aquí se introduce el sistema de coordenadas del espacio tangente . Las coordenadas de textura 2D ordinarias incluyen U y V. La dirección en la que aumenta la coordenada U es la dirección de la tangente en el espacio tangente (eje tangente), y la dirección en la que aumenta la coordenada V es el modelo de dirección de la tangente secundaria (eje bitangente) en el espacio tangente. Las diferentes caras en el, todas tienen un espacio tangente correspondiente, el eje tangente y el eje bitangente están ubicados respectivamente en el plano dibujado, combinados con la dirección normal correspondiente, llamamos eje tangente (T), eje bitangente (B) y eje normal (N ) ¿Es el sistema de coordenadas compuesto por espacio tangente ( TBN ) (como se muestra a continuación)

Con el sistema de coordenadas del espacio tangente TBN, el vector normal extraído de la textura normal se basa en el valor de TBN, y luego realizamos operaciones matriciales matemáticas y lo multiplicamos por una matriz de conversión TBN para convertirlo en la dirección correcta requerida por el modelo. Vector normal.
(Para el cálculo de la matriz TBN y el principio de conversión matemática más profunda, consulte el siguiente enlace)
https://blog.csdn.net/jxw167/article/details/58671953    
https://blog.csdn.net/yuchenwuhen/article/ detalles / 71055602

 

3. Uso del código

class CubeTBN {
    struct V3N3UV2 {
        float x, y, z; //位置坐标
        float nx, ny, nz; //法向量
        float u,v; //纹理坐标
    };

    struct V3N3UV2TB6
    {
        float x, y, z;
        float nx, ny, nz;
        float u, v;
        float tx,ty,tz;
        float bx,by,bz;
    };
    // ...
};

Primero, definimos dos estructuras: V3N3UV2 es la estructura de datos estándar que hemos estado usando antes (posición vértice vec3, vector normal vec3, coordenada de textura vec2). Agregue dos vec3s a V3N3UV2, que son la dirección tangente (eje tangente) y la dirección tangente secundaria (eje bitangente). Encuentra el valor específico a través del método convertTBN

public:
    V3N3UV2TB6       _data[36];
    
    void        init(const CELL::float3 &halfSize)
    {
        // 之前的标准数据,通过传入size确定大小。
        V3N3UV2 verts[] =
        {
                // 前
                {-halfSize.x, +halfSize.y, +halfSize.z, 0.0f,  0.0f,  +1.0f, 0.0f,0.0f},
                {-halfSize.x, -halfSize.y, +halfSize.z, 0.0f,  0.0f,  +1.0f, 1.0f,0.0f},
                {+halfSize.x, +halfSize.y, +halfSize.z, 0.0f,  0.0f,  +1.0f, 0.0f,1.0f},
                {-halfSize.x, -halfSize.y, +halfSize.z, 0.0f,  0.0f,  +1.0f, 1.0f,0.0f},
                {+halfSize.x, -halfSize.y, +halfSize.z, 0.0f,  0.0f,  +1.0f, 1.0f,1.0f},
                {+halfSize.x, +halfSize.y, +halfSize.z, 0.0f,  0.0f,  +1.0f, 0.0f,1.0f},
                // 后
                {+halfSize.x, -halfSize.y, -halfSize.z, 0.0f,  0.0f,  -1.0f, 1.0f,0.0f},
                {-halfSize.x, -halfSize.y, -halfSize.z, 0.0f,  0.0f,  -1.0f, 1.0f,1.0f},
                {+halfSize.x, +halfSize.y, -halfSize.z, 0.0f,  0.0f,  -1.0f, 0.0f,0.0f},
                {-halfSize.x, +halfSize.y, -halfSize.z, 0.0f,  0.0f,  -1.0f, 1.0f,0.0f},
                {+halfSize.x, +halfSize.y, -halfSize.z, 0.0f,  0.0f,  -1.0f, 0.0f,0.0f},
                {-halfSize.x, -halfSize.y, -halfSize.z, 0.0f,  0.0f,  -1.0f, 1.0f,1.0f},
                // 左
                {-halfSize.x, +halfSize.y, +halfSize.z, -1.0f, 0.0f,  0.0f,  0.0f,0.0f},
                {-halfSize.x, -halfSize.y, -halfSize.z, -1.0f, 0.0f,  0.0f,  1.0f,1.0f},
                {-halfSize.x, -halfSize.y, +halfSize.z, -1.0f, 0.0f,  0.0f,  1.0f,0.0f},
                {-halfSize.x, +halfSize.y, -halfSize.z, -1.0f, 0.0f,  0.0f,  0.0f,1.0f},
                {-halfSize.x, -halfSize.y, -halfSize.z, -1.0f, 0.0f,  0.0f,  1.0f,1.0f},
                {-halfSize.x, +halfSize.y, +halfSize.z, -1.0f, 0.0f,  0.0f,  0.0f,0.0f},
                // 右
                {+halfSize.x, +halfSize.y, -halfSize.z, +1.0f, 0.0f,  0.0f,  0.0f,0.0f},
                {+halfSize.x, +halfSize.y, +halfSize.z, +1.0f, 0.0f,  0.0f,  0.0f,1.0f},
                {+halfSize.x, -halfSize.y, +halfSize.z, +1.0f, 0.0f,  0.0f,  1.0f,1.0f},
                {+halfSize.x, -halfSize.y, -halfSize.z, +1.0f, 0.0f,  0.0f,  1.0f,0.0f},
                {+halfSize.x, +halfSize.y, -halfSize.z, +1.0f, 0.0f,  0.0f,  0.0f,0.0f},
                {+halfSize.x, -halfSize.y, +halfSize.z, +1.0f, 0.0f,  0.0f,  1.0f,1.0f},
                // 上
                {-halfSize.x, +halfSize.y, +halfSize.z, 0.0f,  +1.0f, 0.0f,  0.0f,1.0f},
                {+halfSize.x, +halfSize.y, +halfSize.z, 0.0f,  +1.0f, 0.0f,  1.0f,1.0f},
                {+halfSize.x, +halfSize.y, -halfSize.z, 0.0f,  +1.0f, 0.0f,  1.0f,0.0f},
                {-halfSize.x, +halfSize.y, -halfSize.z, 0.0f,  +1.0f, 0.0f,  0.0f,0.0f},
                {-halfSize.x, +halfSize.y, +halfSize.z, 0.0f,  +1.0f, 0.0f,  0.0f,1.0f},
                {+halfSize.x, +halfSize.y, -halfSize.z, 0.0f,  +1.0f, 0.0f,  1.0f,0.0f},
                // 下
                {+halfSize.x, -halfSize.y, -halfSize.z, 0.0f,  -1.0f, 0.0f,  1.0f,1.0f},
                {+halfSize.x, -halfSize.y, +halfSize.z, 0.0f,  -1.0f, 0.0f,  1.0f,0.0f},
                {-halfSize.x, -halfSize.y, -halfSize.z, 0.0f,  -1.0f, 0.0f,  0.0f,1.0f},
                {+halfSize.x, -halfSize.y, +halfSize.z, 0.0f,  -1.0f, 0.0f,  1.0f,0.0f},
                {-halfSize.x, -halfSize.y, +halfSize.z, 0.0f,  -1.0f, 0.0f,  0.0f,0.0f},
                {-halfSize.x, -halfSize.y, -halfSize.z, 0.0f,  -1.0f, 0.0f,  0.0f,1.0f}
        };
        // 根据位置/纹理 -> TBN
        convertTBN(verts, _data);
    }

    void convertTBN(V3N3UV2* vertices,V3N3UV2TB6* nmVerts)
    {
        for (size_t i = 0; i <36; i += 3) // 一次操作一个三角形的三个点
        {
            // copy xyz normal uv
            nmVerts[i + 0].x  = vertices[i + 0].x;
            nmVerts[i + 0].y  = vertices[i + 0].y;
            nmVerts[i + 0].z  = vertices[i + 0].z;
            nmVerts[i + 0].nx = vertices[i + 0].nx;
            nmVerts[i + 0].ny = vertices[i + 0].ny;
            nmVerts[i + 0].nz = vertices[i + 0].nz;
            nmVerts[i + 0].u  = vertices[i + 0].u;
            nmVerts[i + 0].v  = vertices[i + 0].v;

            nmVerts[i + 1].x  = vertices[i + 1].x;
            nmVerts[i + 1].y  = vertices[i + 1].y;
            nmVerts[i + 1].z  = vertices[i + 1].z;
            nmVerts[i + 1].nx = vertices[i + 1].nx;
            nmVerts[i + 1].ny = vertices[i + 1].ny;
            nmVerts[i + 1].nz = vertices[i + 1].nz;
            nmVerts[i + 1].u  = vertices[i + 1].u;
            nmVerts[i + 1].v  = vertices[i + 1].v;

            nmVerts[i + 2].x  = vertices[i + 2].x;
            nmVerts[i + 2].y  = vertices[i + 2].y;
            nmVerts[i + 2].z  = vertices[i + 2].z;
            nmVerts[i + 2].nx = vertices[i + 2].nx;
            nmVerts[i + 2].ny = vertices[i + 2].ny;
            nmVerts[i + 2].nz = vertices[i + 2].nz;
            nmVerts[i + 2].u  = vertices[i + 2].u;
            nmVerts[i + 2].v  = vertices[i + 2].v;

            // Shortcuts for vertices
            CELL::float3  v0  = CELL::float3(vertices[i + 0].x,vertices[i + 0].y,vertices[i + 0].z);
            CELL::float3  v1  = CELL::float3(vertices[i + 1].x,vertices[i + 1].y,vertices[i + 1].z);
            CELL::float3  v2  = CELL::float3(vertices[i + 2].x,vertices[i + 2].y,vertices[i + 2].z);
            CELL::float2  uv0 = CELL::float2(vertices[i + 0].u, vertices[i + 0].v);
            CELL::float2  uv1 = CELL::float2(vertices[i + 1].u, vertices[i + 1].v);
            CELL::float2  uv2 = CELL::float2(vertices[i + 2].u, vertices[i + 2].v);
            // 构建triangle平面的方向向量 (position delta, δ)
            CELL::float3  deltaPos1 = v1 - v0;
            CELL::float3  deltaPos2 = v2 - v0;
            // 构建UV平面的两个方向的向量 (uv delta, δ)
            CELL::float2 deltaUV1   = uv1 - uv0;
            CELL::float2 deltaUV2   = uv2 - uv0;

            float   r  = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);  // uv叉积作底
            CELL::float3 tangent    = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y)*r; // 得出切线
            CELL::float3 binormal   = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x)*r; // 得出副切线

            // 赋值到t b
            nmVerts[i + 0].tx = tangent.x;  nmVerts[i + 0].bx = binormal.x;
            nmVerts[i + 0].ty = tangent.y;  nmVerts[i + 0].by = binormal.y;
            nmVerts[i + 0].tz = tangent.z;  nmVerts[i + 0].bz = binormal.z;

            nmVerts[i + 1].tx = tangent.x;  nmVerts[i + 1].bx = binormal.x;
            nmVerts[i + 1].ty = tangent.y;  nmVerts[i + 1].by = binormal.y;
            nmVerts[i + 1].tz = tangent.z;  nmVerts[i + 1].bz = binormal.z;

            nmVerts[i + 2].tx = tangent.x;  nmVerts[i + 2].bx = binormal.x;
            nmVerts[i + 2].ty = tangent.y;  nmVerts[i + 2].by = binormal.y;
            nmVerts[i + 2].tz = tangent.z;  nmVerts[i + 2].bz = binormal.z;
        }
    }

Hasta el momento se ha calculado la matriz TBN de cada punto de referencia. Una vez que tenemos los datos, podemos empezar a aprender a escribir programas de sombreado.

Primero miramos la parte del sombreador de vértices:

#version 320 es
en vec3 _position; // entrada externa
en vec3 _normal;
en vec2 _uv;
en vec3 _tagent;
en vec3 _biTagent;
uniform mat4 _mvp; // mvp matrix
uniform mat3 _normalMatrix; // matriz normal
uniform mat4 _matModel; / / Matriz de conversión de modelo
out vec2 _outUV;
out vec3 _outPos;
out mat3 _TBN;
void main ()
{         _outUV = _uv; Coordenadas de textura de salida al sombreador de fragmentos para extraer mapas de textura y mapas normales         vec4 pos = _matModel * vec4 ( _position, 1);         _outPos = pos.xyz; Muestra la posición del vértice del modelo para garantizar que cada fragmento tenga una dirección de luz refinada         vec3 normal = normalize (_normalMatrix * _normal); // Multiplica por la matriz normal para asegurar la transformación del modelo Consistencia después de la operación.




        vec3 tagent = normalize (_normalMatrix * _tagent);
        vec3 biTagent = normalize (_normalMatrix * _biTagent);
        _TBN = mat3x3 (tagent, biTagent, normal); // Construya la matriz TBN y
        envíela al sombreador de fragmentos gl_Position = _mvp * vec4 (_position, 1.0); // Muestra las coordenadas finales del vértice dibujadas
}

em ... Se han añadido los comentarios. ¿En cuanto a por qué los vértices (del sistema de coordenadas mundial) después de la operación de modelado deben enviarse al sombreador de fragmentos? Calcular el atributo principal de la intensidad de la luz: la dirección de la luz. Solíamos hacerlo directamente en el sombreador de vértices. Esto se debe a que antes no dominamos el contenido clave del mapa normal y no pudimos refinar el vector normal para cada fragmento. entre. Una vez que los datos de la posición del vértice se envían desde el sombreador de vértices al sombreador de fragmentos, el sombreador de fragmentos realizará operaciones de interpolación. Después de la interpolación, cada fragmento tiene los datos de posición del vértice interpolados, por lo que es necesario volver a calcular la dirección de luz más detallada para que coincida con el mapa normal para obtener mejores resultados de cálculo.

ES 320. # Versión
Precision mediump un flotador;
en vec2 _outUV;
en vec3 _outPos;
en MAT3 _TBN;
Uniforme vec3 _lightColor;
Uniforme vec3 _lightDiffuse;
Uniforme _texture sampler2D;
Uniforme sampler2D _texNormal;
Uniforme vec3 _lightPos;
Uniforme vec3 _cameraPos;
; OUT vec3 _fragColor
principal void ()
{         vec3 lightDir = normalize (_lightPos-_outPos); // Calcula la dirección de la luz de cada fragmento         vec3 normal = normalize (_TBN * (texture (_texNormal, _outUV) .xyz * 2.0-1.0));         // Extraiga el vector normal del mapa normal para normalizar el intervalo [-1,1] y transformarlo en el vector normal final a través de la matriz TBN



        vec4 materialColor = texture (_texture, _outUV); 
        float lightStrength = max (dot (normal, lightDir), 0.0); // Calcular la intensidad de la luz
        vec4 lightColor = vec4 (_lightColor * lightStrength + _lightDiffuse, 1); // Intensidad de la luz * color Luz + luz difusa
        _fragColor.rgb = materialColor.rgb * 0.2 + 0.8 * lightColor.rgb; // Efecto de entrada mixto.

Las coordenadas de textura _uv extraen el valor de color del mapa de textura y pueden extraer el valor de vector normal del mapa normal. Cabe señalar que el valor del rango de textura (_texNormal, _outUV) .xyz es [0,255], y la normalización es [0,1]. Pero lo que nuestro vector normal necesita es [-1, 1], y necesitamos convertirlo nosotros mismos. El efecto final de salida mixta no es fijo. Simplemente ajuste el efecto según sea necesario.

 

Finalmente, agregue el método CubeTBN.render.

void        render(Camera3D& camera)
{
    _program.begin();
    static  float   angle = 0;
    angle += 0.1f;
    CELL::matrix4   matRot;
    matRot.rotateYXZ(angle, 0.0f, 0.0f);
    CELL::matrix4   model   =   _modelMatrix * matRot;
    glUniformMatrix4fv(_program._matModel, 1, GL_FALSE, model.data());
    CELL::matrix4   vp = camera.getProject() * camera.getView();
    CELL::matrix4   mvp = (vp * model);
    glUniformMatrix4fv(_program._mvp, 1, GL_FALSE, mvp.data());
    CELL::matrix3   matNormal = mat4_to_mat3(model)._inverse()._transpose();
    glUniformMatrix3fv(_program._normalMatrix, 1, GL_FALSE, matNormal.data());

    glUniform3f(_program._lightDiffuse, 0.1f, 0.1f, 0.1f); // 漫反射 环境光
    glUniform3f(_program._lightColor, 1.0f, 1.0f, 1.0f);  // 定向光源的颜色
    glUniform3f(_program._lightPos, camera._eye.x, camera._eye.y, camera._eye.z);//光源位置

    glActiveTexture(GL_TEXTURE0);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D,  _texMaterial); 
    glUniform1i(_program._texture, 0); // 加载纹理贴图
    glActiveTexture(GL_TEXTURE1);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D,  _texNormal);
    glUniform1i(_program._texNormal, 1); // 加载法线贴图

    glVertexAttribPointer(_program._position, 3, GL_FLOAT, GL_FALSE, sizeof(V3N3UV2TB6), _data);
    glVertexAttribPointer(_program._normal, 3, GL_FLOAT, GL_FALSE, sizeof(V3N3UV2TB6), &_data[0].nx);
    glVertexAttribPointer(_program._uv, 2, GL_FLOAT, GL_FALSE, sizeof(V3N3UV2TB6), &_data[0].u);
    glVertexAttribPointer(_program._tagent, 3, GL_FLOAT, GL_FALSE, sizeof(V3N3UV2TB6), &_data[0].tx);
    glVertexAttribPointer(_program._biTagent, 3, GL_FLOAT, GL_FALSE, sizeof(V3N3UV2TB6), &_data[0].bx);
    glDrawArrays(GL_TRIANGLES, 0, 36);
    _program.end();
}

Código fuente de la demostración del proyecto: archivo de referencia CubeTBN.hpp CubeTbnProgram.hpp

https://github.com/MrZhaozhirong/NativeCppApp      

Supongo que te gusta

Origin blog.csdn.net/a360940265a/article/details/94719015
Recomendado
Clasificación