OpenGL.Shader: 9-learning light-normal map (calcul de la matrice TBN)

OpenGL.Shader: 9-learning light-normal map (calcul de la matrice TBN)

Cet article apprend les cartes normales. Les cartes normales sont particulièrement utilisées dans le développement de jeux et de systèmes SIG. Leur expressivité est particulièrement forte et l'effet de dessin est très proche de la réalité. Le point le plus important est que nous pouvons fabriquer des modèles très économiques à très peu de frais. Ceci est particulièrement important dans le chef-d'œuvre du jeu.

              

Jetons un coup d'œil à l'effet des deux textures de cube ci-dessus. Le côté gauche est une texture normale, et le côté droit est une carte normale. Il y a une énorme différence dans les effets visuels entre les deux. C'est le charme de la carte normale.

Avant le code du spectacle, voici une brève description des connaissances pertinentes de la carte normale.

1. Cartographie normale

Lors de la simulation de l'effet d'éclairage, qu'est-ce qui fait de la surface à éclairer une surface complètement plane? La réponse est le vecteur normal de la surface. En prenant une brique comme exemple, du point de vue de l'algorithme d'éclairage, la surface de la brique n'a qu'un seul vecteur normal et la surface est éclairée de manière cohérente sur la base de ce vecteur normal, de sorte que la réalisation de l'effet de détail est souvent relativement simple. Et si chaque fragment avait sa propre normale différente? De cette façon, nous pouvons changer le vecteur normal en fonction des détails subtils de la surface; cela se traduira par un effet visuel qui semble beaucoup plus compliqué à la surface:

Chaque fragment utilise sa propre normale, et nous pouvons créer une surface composée de nombreux plans minuscules (vecteurs normaux différents), et les détails de la surface d'un tel objet seront grandement améliorés. Cette technique consistant à utiliser chaque pixel de fragment pour utiliser sa propre normale au lieu d'utiliser la même normale pour tous les fragments d'une surface est appelée cartographie normale.

Le vecteur normal étant un modèle géométrique à trois éléments, une texture 2D peut non seulement stocker des données de couleur et d'éclairage, mais également stocker le vecteur normal. Pensez-y, les composants de couleur r, g et b dans la texture sont représentés par un modèle mathématique de vec3. Les éléments vectoriels normaux x, y et z du même vec3 peuvent également être stockés dans la texture, au lieu des éléments couleur r, g et b pour former une texture normale. De cette manière, nous pouvons échantillonner le vecteur normal de la position correspondante à partir de la texture normale sur la base d'un ensemble de données de position en même temps. De cette façon, la carte normale peut fonctionner comme une carte de texture.

2. Espace tangent

Puisque nous mappons le vecteur normal (x, y, z) au composant (r, g, b) d'une texture, alors la première intuition est que le vecteur normal de chaque fragment doit être perpendiculaire à la texture. Le plan (c'est-à-dire le plan composé de coordonnées UV), les proportions des trois composants sont tous sur le composant z (b), de sorte que la texture normale présente principalement un aspect visuel bleuté. (Comme indiqué ci-dessous) Mais cela posera un sérieux problème. Dans notre exemple, il y a six faces d'un cube, seul le vecteur normal de la face supérieure est (0, 0, 1), les faces dans d'autres directions ne peuvent-elles pas utiliser cette méthode? Texture de ligne?

Pensez-y, comment pouvons-nous convertir les coordonnées de sommet / texture du modèle en coordonnées mondiales? Comment le vecteur normal est-il synchronisé avec l'opération de changement du modèle? Tous se font par l'opération matricielle du système de coordonnées, et le système de coordonnées de l' espace tangent est introduit ici . Les coordonnées de texture 2D ordinaires incluent U et V. La direction dans laquelle la coordonnée U augmente est la direction tangente dans l'espace tangent (axe tangent), et la direction dans laquelle la coordonnée V augmente est le modèle de direction tangente secondaire (axe bitangent) dans l'espace tangent. Les différentes faces dans le, ont toutes un espace tangent correspondant, l'axe tangent et l'axe bitangent sont respectivement situés sur le plan dessiné, combinés avec la direction normale correspondante, nous appelons l'axe tangant (T), l'axe bitangent (B) et l'axe normal (N ) Le système de coordonnées est-il composé d'un espace tangent ( TBN ) (comme illustré ci-dessous)

Avec le système de coordonnées d'espace tangent TBN, le vecteur normal extrait de la texture normale est basé sur la valeur de TBN, puis nous effectuons des opérations matricielles mathématiques et le multiplions par une matrice de conversion TBN pour le convertir dans la direction correcte requise par le modèle. Vecteur normal.
(Pour le calcul de la matrice TBN et le principe de conversion mathématique plus approfondie, veuillez vous référer au lien suivant)
https://blog.csdn.net/jxw167/article/details/58671953    
https://blog.csdn.net/yuchenwuhen/article/ détails / 71055602

 

3. Utilisation du code

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;
    };
    // ...
};

Tout d'abord, nous définissons deux structures: V3N3UV2 est la structure de données standard que nous utilisions auparavant (position vertex vec3, vecteur normal vec3, coordonnée de texture vec2). Ajoutez deux vec3 à V3N3UV2, qui sont la direction tangente (axe tangent) et la direction tangente secondaire (axe bitangent). Trouvez la valeur spécifique via la méthode 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;
        }
    }

Jusqu'à présent, la matrice TBN de chaque point de référence a été calculée. Une fois que nous avons les données, nous pouvons commencer à apprendre à écrire des programmes de shader.

Nous examinons d'abord la partie vertex shader:

#version 320 es
in vec3 _position; // entrée externe
dans vec3 _normal;
in vec2 _uv;
in vec3 _tagent;
in vec3 _biTagent;
uniform mat4 _mvp; // mvp matrix
uniform mat3 _normalMatrix; // normal matrix
uniform mat4 _matModel; / / Matrice de conversion de modèle
out vec2 _outUV;
out vec3 _outPos;
out mat3 _TBN;
void main ()
{         _outUV = _uv; Sortie des coordonnées de texture vers le fragment shader pour extraire les textures et les normales maps         vec4 pos = _matModel * vec4 ( _position, 1);         _outPos = pos.xyz; Affiche la position du sommet du modèle pour s'assurer que chaque fragment a une direction de lumière raffinée         vec3 normal = normaliser (_normalMatrix * _normal); // Multiplier par la matrice normale pour assurer la transformation du modèle Cohérence après opération.




        vec3 tagent = normalize (_normalMatrix * _tagent);
        vec3 biTagent = normalize (_normalMatrix * _biTagent);
        _TBN = mat3x3 (tagent, biTagent, normal); // Construisez la matrice TBN et
        envoyez- la au fragment shader gl_Position = _mvp * vec4 (_position, 1.0); // Affiche les coordonnées des sommets dessinés finaux
}

em ... Les commentaires ont été ajoutés. Quant à savoir pourquoi les sommets (du système de coordonnées du monde) après l'opération de modélisation devraient être envoyés au shader de fragment? Calcul de l'attribut principal de l'intensité lumineuse (direction de l'éclairage). Nous le faisions directement dans le vertex shader. Cela est dû au fait que nous ne maîtrisions pas le contenu clé de la texture normale auparavant et que nous ne parvenions pas à affiner le vecteur normal de chaque fragment. parmi. Une fois que les données de position de vertex sont sorties du vertex shader vers le fragment shader, le fragment shader effectuera des opérations d'interpolation. Après interpolation, chaque fragment a les données de position de sommet interpolées, il est donc nécessaire de recalculer la direction de la lumière plus détaillée pour correspondre à la carte normale pour de meilleurs résultats de calcul.

ES 320. #Version
Precision mediump un flotteur,
en vec2 _outUV,
dans vec3 _outPos,
dans MAT3 _TBN;
Uniform vec3 _lightColor;
Uniform vec3 _lightDiffuse;
_texture uniforme sampler2D;
Uniform sampler2D _texNormal;
Uniform vec3 _lightPos;
Uniform vec3 _cameraPos;
OUT vec3 _fragColor;
void main ()
{         vec3 lightDir = normalize (_lightPos-_outPos); // Calcule la direction de la lumière de chaque fragment         vec3 normal = normalize (_TBN * (texture (_texNormal, _outUV) .xyz * 2.0-1.0));         // Extraire le vecteur normal de la carte normale pour normaliser l'intervalle [-1,1] et le transformer en vecteur normal final via la matrice TBN



        vec4 materialColor = texture (_texture, _outUV); 
        float lightStrength = max (dot (normal, lightDir), 0.0); // Calculer l'intensité lumineuse
        vec4 lightColor = vec4 (_lightColor * lightStrength + _lightDiffuse, 1); // Intensité lumineuse * couleur Lumière + lumière diffuse
        _fragColor.rgb = materialColor.rgb * 0,2 + 0,8 * lightColor.rgb; // Effet d'entrée mixte.

Les coordonnées de texture _uv extraient la valeur de couleur de la carte de texture et peuvent extraire la valeur vectorielle normale de la carte normale. Il convient de noter que la valeur de plage de texture (_texNormal, _outUV) .xyz est [0,255], et la normalisation est [0,1]. Mais ce dont notre vecteur normal a besoin est [-1, 1], et nous devons le convertir nous-mêmes. L'effet de sortie mixte final n'est pas fixe. Ajustez simplement l'effet selon vos besoins.

 

Enfin, ajoutez la méthode 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();
}

Code source de la démonstration du projet: fichier de référence CubeTBN.hpp CubeTbnProgram.hpp

https://github.com/MrZhaozhirong/NativeCppApp      

Je suppose que tu aimes

Origine blog.csdn.net/a360940265a/article/details/94719015
conseillé
Classement