当前位置: 首页 > news >正文

【OpenGL】openGL 法线贴图

参考文章:https://www.opengl-tutorial.org/cn/intermediate-tutorials/tutorial-13-normal-mapping/

一、法线纹理

下图是一张法线纹理:

每个纹素的RGB值实际上表示的是XYZ向量:颜色的分量取值范围为0到1,而向量的分量取值范围是-1到1;可以建立从纹素到法线的简单映射

normal = (2*color)-1 // on each component

由于法线基本都是指向”曲面外侧”的(按照惯例,X轴朝右,Y轴朝上),因此法线纹理整体呈蓝色。

法线纹理的映射方式和漫反射纹理相似。麻烦之处在于如何将法线从各三角形局部空间(切线空间tangent space,亦称图像空间image space)变换到模型空间(着色计算所采用的空间)。

二、切线和副切线(Tangent and Bitangent)

大家对矩阵已经十分熟悉了,应该知道定义一个空间(本例是切线空间)需要三个向量。现在Up向量已经有了,即法线:可用Blender生成,或由一个简单的叉乘计算得到。下图中蓝色箭头代表法线(法线贴图整体颜色也恰好是蓝色)。

然后是切线T:垂直于法线的向量。但这样的切线有很多个:

这么多切线中该选哪个呢?理论上哪一个都行。但我们必须保持连续一致性,以免衔接处出现瑕疵。标准的做法是将切线方向和纹理空间对齐:

定义一组基需要三个向量,因此我们还得计算副切线B(本可以随便选一条切线,但选定垂直于另外两条轴的切线,计算会方便些)。

算法如下:记三角形的两条边为deltaPos1和deltaPos2,deltaUV1和deltaUV2是对应的UV坐标下的差值;则问题可用如下方程表示:

deltaPos1 = deltaUV1.x * T + deltaUV1.y * B
deltaPos2 = deltaUV2.x * T + deltaUV2.y * B

求解T和B就得到了切线和副切线!(代码见下文)

已知T、B、N向量之后,即可得下面这个漂亮的矩阵,完成从切线空间到模型空间的变换:

[TxBxNxTyByNyTzBzNz]\begin{bmatrix} T_x & B_x & N_x \\ T_y & B_y & N_y \\ T_z & B_z & N_z \end{bmatrix}TxTyTzBxByBzNxNyNz

有了TBN矩阵,我们就能把(从法线纹理中获取的)法线变换到模型空间。

可我们需要的却是从切线空间到模型空间的变换,法线则保持不变。所有计算均在切线空间中进行,不会对其他计算产生影响。

只需对上述矩阵求逆即可得逆变换。这个矩阵(正交阵,即各向量相互正交的矩阵,参见下文”延伸阅读”小节)的逆矩阵恰好也就是其转置矩阵,计算十分简单:

invTBN = transpose(TBN)

亦即:

[TxTyTzBxByBzNxNyNz]\begin{bmatrix} T_x & T_y & T_z \\ B_x & B_y & B_z \\ N_x & N_y & N_z \end{bmatrix}TxBxNxTyByNyTzBzNz

三、准备VBO

3.1 计算切线和副切线

我们需要为整个模型计算切线、副切线和法线。我们用一个单独的函数完成这些计算

void computeTangentBasis(// inputsstd::vector & vertices,std::vector & uvs,std::vector & normals,// outputsstd::vector & tangents,std::vector & bitangents
){

为每个三角形计算边(deltaPos)和deltaUV

for ( int i=0; i<vertices.size(); i+=3){// Shortcuts for verticesglm::vec3 & v0 = vertices[i+0];glm::vec3 & v1 = vertices[i+1];glm::vec3 & v2 = vertices[i+2];// Shortcuts for UVsglm::vec2 & uv0 = uvs[i+0];glm::vec2 & uv1 = uvs[i+1];glm::vec2 & uv2 = uvs[i+2];// Edges of the triangle : postion deltaglm::vec3 deltaPos1 = v1-v0;glm::vec3 deltaPos2 = v2-v0;// UV deltaglm::vec2 deltaUV1 = uv1-uv0;glm::vec2 deltaUV2 = uv2-uv0;

现在用公式来算切线和副切线:

float r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
glm::vec3 tangent = (deltaPos1 * deltaUV2.y   - deltaPos2 * deltaUV1.y)*r;
glm::vec3 bitangent = (deltaPos2 * deltaUV1.x   - deltaPos1 * deltaUV2.x)*r;

最后,把这些_切线_和_副切线_缓存起来。记住,我们还没为这些缓存的数据生成索引,因此每个顶点都有一份拷贝

// Set the same tangent for all three vertices of the triangle.
// They will be merged later, in vboindexer.cpp
tangents.push_back(tangent);
tangents.push_back(tangent);
tangents.push_back(tangent);// Same thing for binormals
bitangents.push_back(bitangent);
bitangents.push_back(bitangent);
bitangents.push_back(bitangent);}

3.2 索引

索引VBO的方法和之前类似,仅有些许不同。

找到相似顶点(相同的坐标、法线、纹理坐标)后,我们不直接用它的切线、副法线,而是取其均值。因此,只需把老代码修改一下:

// Try to find a similar vertex in out_XXXX
unsigned int index;
bool found = getSimilarVertexIndex(in_vertices[i], in_uvs[i], in_normals[i],     out_vertices, out_uvs, out_normals, index);if ( found ){ // A similar vertex is already in the VBO, use it instead !out_indices.push_back( index );// Average the tangents and the bitangentsout_tangents[index] += in_tangents[i];out_bitangents[index] += in_bitangents[i];
}else{ // If not, it needs to be added in the output data.// Do as usual[...]
}

注意,这里没有对结果归一化。这种做法十分便利。由于小三角形的切线、副切线向量较小;相对于大三角形来说,对模型外观的影响程度较小。

四、着色器

4.1 新增缓冲和uniform变量

我们需要再加两个缓冲,分别存储切线和副切线:

GLuint tangentbuffer;
glGenBuffers(1, &tangentbuffer);
glBindBuffer(GL_ARRAY_BUFFER, tangentbuffer);
glBufferData(GL_ARRAY_BUFFER, indexed_tangents.size() * sizeof(glm::vec3), &indexed_tangents[0], GL_STATIC_DRAW);GLuint bitangentbuffer;
glGenBuffers(1, &bitangentbuffer);
glBindBuffer(GL_ARRAY_BUFFER, bitangentbuffer);
glBufferData(GL_ARRAY_BUFFER, indexed_bitangents.size() * sizeof(glm::vec3), &indexed_bitangents[0], GL_STATIC_DRAW);

还需要一个uniform变量存储新增的法线纹理:

[...]
GLuint NormalTexture = loadTGA_glfw("normal.tga");
[...]
GLuint NormalTextureID  = glGetUniformLocation(programID, "NormalTextureSampler");

另外一个uniform变量存储3x3的模型视图矩阵。严格地讲,这个矩阵可有可无,它仅仅是让计算更方便罢了;详见后文。由于仅仅计算旋转,不需要平移,因此只需矩阵左上角3x3的部分。

GLuint ModelView3x3MatrixID = glGetUniformLocation(programID, "MV3x3");

完整的绘制代码如下:

// Clear the screen
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Use our shader
glUseProgram(programID);// Compute the MVP matrix from keyboard and mouse input
computeMatricesFromInputs();
glm::mat4 ProjectionMatrix = getProjectionMatrix();
glm::mat4 ViewMatrix = getViewMatrix();
glm::mat4 ModelMatrix = glm::mat4(1.0);
glm::mat4 ModelViewMatrix = ViewMatrix * ModelMatrix;
glm::mat3 ModelView3x3Matrix = glm::mat3(ModelViewMatrix); // Take the upper-left part of ModelViewMatrix
glm::mat4 MVP = ProjectionMatrix * ViewMatrix * ModelMatrix;// Send our transformation to the currently bound shader,
// in the "MVP" uniform
glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);
glUniformMatrix4fv(ModelMatrixID, 1, GL_FALSE, &ModelMatrix[0][0]);
glUniformMatrix4fv(ViewMatrixID, 1, GL_FALSE, &ViewMatrix[0][0]);
glUniformMatrix4fv(ViewMatrixID, 1, GL_FALSE, &ViewMatrix[0][0]);
glUniformMatrix3fv(ModelView3x3MatrixID, 1, GL_FALSE, &ModelView3x3Matrix[0][0]);glm::vec3 lightPos = glm::vec3(0,0,4);
glUniform3f(LightID, lightPos.x, lightPos.y, lightPos.z);// Bind our diffuse texture in Texture Unit 0
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, DiffuseTexture);
// Set our "DiffuseTextureSampler" sampler to user Texture Unit 0
glUniform1i(DiffuseTextureID, 0);// Bind our normal texture in Texture Unit 1
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, NormalTexture);
// Set our "Normal    TextureSampler" sampler to user Texture Unit 0
glUniform1i(NormalTextureID, 1);// 1rst attribute buffer : vertices
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glVertexAttribPointer(0,                  // attribute3,                  // sizeGL_FLOAT,           // typeGL_FALSE,           // normalized?0,                  // stride(void*)0            // array buffer offset
);// 2nd attribute buffer : UVs
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
glVertexAttribPointer(1,                                // attribute2,                                // sizeGL_FLOAT,                         // typeGL_FALSE,                         // normalized?0,                                // stride(void*)0                          // array buffer offset
);// 3rd attribute buffer : normals
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
glVertexAttribPointer(2,                                // attribute3,                                // sizeGL_FLOAT,                         // typeGL_FALSE,                         // normalized?0,                                // stride(void*)0                          // array buffer offset
);// 4th attribute buffer : tangents
glEnableVertexAttribArray(3);
glBindBuffer(GL_ARRAY_BUFFER, tangentbuffer);
glVertexAttribPointer(3,                                // attribute3,                                // sizeGL_FLOAT,                         // typeGL_FALSE,                         // normalized?0,                                // stride(void*)0                          // array buffer offset
);// 5th attribute buffer : bitangents
glEnableVertexAttribArray(4);
glBindBuffer(GL_ARRAY_BUFFER, bitangentbuffer);
glVertexAttribPointer(4,                                // attribute3,                                // sizeGL_FLOAT,                         // typeGL_FALSE,                         // normalized?0,                                // stride(void*)0                          // array buffer offset
);// Index buffer
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);// Draw the triangles !
glDrawElements(GL_TRIANGLES,      // modeindices.size(),    // countGL_UNSIGNED_INT,   // type(void*)0           // element array buffer offset
);glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
glDisableVertexAttribArray(3);
glDisableVertexAttribArray(4);// Swap buffers
glfwSwapBuffers();

4.2 顶点着色器

如前所述,所有计算都摄像机空间中做,因为在这一空间中更容易获取片段坐标。这就是为什么要用模型视图矩阵乘T、B、N向量。

vertexNormal_cameraspace = MV3x3 * normalize(vertexNormal_modelspace);
vertexTangent_cameraspace = MV3x3 * normalize(vertexTangent_modelspace);
vertexBitangent_cameraspace = MV3x3 * normalize(vertexBitangent_modelspace);

这三个向量确定了TBN矩阵,其创建方式如下:

mat3 TBN = transpose(mat3(vertexTangent_cameraspace,vertexBitangent_cameraspace,vertexNormal_cameraspace
)); // You can use dot products instead of building this matrix and transposing it. See References for details.

此矩阵是从摄像机空间到切线空间的变换(若矩阵名为XXX_modelspace,则是从模型空间到切线空间的变换)。我们可以利用它计算切线空间中的光线方向和视线方向。

LightDirection_tangentspace = TBN * LightDirection_cameraspace;
EyeDirection_tangentspace =  TBN * EyeDirection_cameraspace;

4.3 片段着色器

切线空间中的法线很容易获取–就在纹理中:

    // Local normal, in tangent spacevec3 TextureNormal_tangentspace = normalize(texture( NormalTextureSampler, UV ).rgb*2.0 - 1.0);

一切准备就绪。漫反射光的值由切线空间中的n和l计算得来(在哪个空间中计算并不重要,关键是n和l必须位于同一空间中),并用_clamp( dot( n,l ), 0,1 )_截取。镜面光用_clamp( dot( E,R ), 0,1 )_截取,E和R也必须位于同一空间中。大功告成!

五、结果

这是目前得到的结果,您可以看到:

  • 砖块看上去凹凸不平,这是因为砖块表面法线变化比较剧烈
  • 水泥部分看上去很平整,这是因为这部分的法线纹理全都是蓝色

在这里插入图片描述

六、延伸阅读

6.1 正交化(Orthogonalization)

顶点着色器中,为了计算速度,我们没有进行矩阵求逆,而是进行了转置。这只有当矩阵表示的空间正交时才成立,而这个矩阵还不是正交的。好在这个问题很容易解决:只需在computeTangentBasis()末尾让切线与法线垂直。

t = glm::normalize(t - n * glm::dot(n, t));

这个公式有点难理解,来看看图:

n和t差不多是相互垂直的,只要把t沿-n方向稍微”推”一下,幅度是dot(n,t)。

这里有一个applet也演示得很清楚(仅含两个向量)。

6.2 左手系还是右手系?

一般不必担心这个问题。但在某些情况下,比如使用对称模型时,UV坐标方向会出错,导致切线T方向错误。

判断是否需要翻转坐标系很容易:TBN必须形成一个右手坐标系–向量cross(n,t)应该和b同向。

用数学术语讲,”向量A和向量B同向”则有”dot(A,B)>0”;故只需检查dot( cross(n,t) , b )是否大于0。

若dot( cross(n,t) , b ) < 0,就要翻转t:

if (glm::dot(glm::cross(n, t), b) < 0.0f){t = t * -1.0f;}

在computeTangentBasis()末对每个顶点都做这个操作。

6.3 镜面纹理(Specular texture)

为了增强趣味性,我在代码里加上了镜面纹理;取代了原先作为镜面颜色的灰色vec3(0.3,0.3,0.3)。镜面纹理看起来像这样:

在这里插入图片描述

请注意,由于如上镜面纹理中没有镜面分量,水泥部分均呈黑色。

七、完整代码

my_glwidget.h

//  
// Created by liuhang on 2025/9/16.  
//  #ifndef OPENGL_LEARNING_MY_GLWIDGET_H  
#define OPENGL_LEARNING_MY_GLWIDGET_H  #include<QOpenGLWidget>  
#include<QOpenGLFunctions>  
#include<QMatrix4x4>  
#include<QTimer>  
#include "objloader.hpp"  class MyGLWidget : public QOpenGLWidget,protected QOpenGLFunctions  
{  Q_OBJECT  
public:  explicit MyGLWidget(QWidget* parent = nullptr);  ~MyGLWidget() override;  protected:  void initializeGL() override;  void paintGL() override;  void resizeGL(int w,int h) override;  void doMVP();  
private:  void loadShader(std::string const& vertex_shader_path,std::string const& fragment_shader_path);  void loadModel();  void loadEbo();  GLint loadBmpTexture(QString const& bmp_path);  void computeTangentBasis(  // inputs  std::vector<QVector3D> & vertices,  std::vector<QVector2D> & uvs,  std::vector<QVector3D> & normals,  // outputs  std::vector<QVector3D> & tangents,  std::vector<QVector3D> & bitangents  );  private:  GLuint vertex_array_id;  GLuint vertex_buffer_id;  GLuint element_buffer_id;  GLuint uv_buffer_id;  GLuint normal_buffer_id;  GLuint vertex_shader_id;  GLuint fragment_shader_id;  GLuint shader_program_id;  GLint uniform_model_matrix_location;  GLint uniform_view_matrix_location;  GLint uniform_projection_matrix_location;  GLint uniform_mvp_matrix_location;  GLint uniform_diffuse_texture_sampler_location;  GLint uniform_specular_texture_sampler_location;  GLint uniform_normal_texture_sampler_location;  GLint uniform_MV3x3_location;  GLint uniform_LightPosition_worldspace;  GLuint diffuse_texture_id;  GLuint specular_texture_id;  GLuint normal_texture_id;  GLuint tangent_buffer_id;  GLuint bitangent_buffer_id;  //MVP矩阵  QVector3D camera_pos = {4,4,-0};  QVector3D look_dir = {0,0,0};  QVector3D up_dir = {0,1,0};  float angle = 45;  float aspect;  //距离相机的位置,渲染范围:0.1-100  float near_plane = 0.1f;  float far_plane = 100.f;  QtOBJLoader objLoader;  std::vector<QVector3D> model_vertices;  std::vector<QVector2D> model_uvs;  std::vector<QVector3D> model_normals;  std::vector<QVector3D> model_tangent;  std::vector<QVector3D> model_bitangent;  std::vector<QVector3D>model_index_vertices;  std::vector<QVector2D>model_index_uvs;  std::vector<QVector3D>model_index_normals;  std::vector<QVector3D> model_index_tangent;  std::vector<QVector3D> model_index_bitangent;  std::vector<unsigned short>model_index;  
};  #endif //OPENGL_LEARNING_MY_GLWIDGET_H

my_glwidget.cpp

//  
// Created by liuhang on 2025/9/16.  
//  #include "my_glwidget.h"  
#include<string>  
#include<fstream>  
#include<sstream>  
#include<iostream>  
#include<filesystem>  
#include<QThread>  
#include<QImage>  
#include"vboindex.hpp"  
#include"my_texture.hpp"  MyGLWidget::MyGLWidget(QWidget *parent): QOpenGLWidget(parent)  
{  
#if 0  timer.setInterval(1);  connect(&timer,&QTimer::timeout,this,[this](){        static float count = 0;        count+= 0.01f;        global_i = std::fabs(sin(count));  this->update();  timer.setInterval(1);        timer.start();    });  
#endif  #if 0  timer.setInterval(1);  connect(&timer,&QTimer::timeout,this,[this](){        global_i+= 1;        if(fabs(global_i - 100) < 0.1f){            global_i = 0;        }        this->update();  timer.setInterval(1);        timer.start();    });  timer.start();#endif  aspect = this->width() * 1.0f / this->height();  
}  MyGLWidget::~MyGLWidget()  {  makeCurrent();  //vao  glDeleteVertexArrays(1,&vertex_array_id);  //坐标顶点vbo  glDeleteBuffers(1,&vertex_buffer_id);  //纹理顶点vbo  glDeleteBuffers(1,&uv_buffer_id);  //法线顶点vbo  glDeleteBuffers(1,&normal_buffer_id);  //切线顶点vbo  glDeleteBuffers(1,&tangent_buffer_id);  //副切线顶点vbo  glDeleteBuffers(1,&bitangent_buffer_id);  //顶点索引ebo  glDeleteBuffers(1,&element_buffer_id);  //shader_program  glDeleteShader(shader_program_id);  //纹理单元  glDeleteTextures(0,&diffuse_texture_id);  glDeleteTextures(1,&specular_texture_id);  glDeleteTextures(2,&normal_texture_id);  doneCurrent();  
}  void MyGLWidget::initializeGL() {  initializeOpenGLFunctions();  glClearColor(0.0f,0.0f,0.0f,1.0f);  //启动深度测试  glEnable(GL_DEPTH_TEST);  // Accept fragment if it closer to the camera than the former one  glDepthFunc(GL_LESS);  //启用混合  glEnable(GL_BLEND);  glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); //(1-src)权重  //关闭背面剔除  glDisable(GL_CULL_FACE);  //加载着色器  loadShader("/Users/liuhang/CLionProjects/opengl-learning/opengl2-13-normal-mapping/shader/shader.vert",  "/Users/liuhang/CLionProjects/opengl-learning/opengl2-13-normal-mapping/shader/shader.frag");  //加载模型  loadModel();  //加载索引  loadEbo();  //===============vao===============  glGenVertexArrays(1,&vertex_array_id);  glBindVertexArray(vertex_array_id);  //===============模型坐标数据vbo===============  glGenBuffers(1,&vertex_buffer_id);  glBindBuffer(GL_ARRAY_BUFFER,vertex_buffer_id);  glBufferData(GL_ARRAY_BUFFER, sizeof(QVector3D) * model_index_vertices.size(),model_index_vertices.data(),GL_STATIC_DRAW);  //===============顶点纹理数据vbo===============  glGenBuffers(1,&uv_buffer_id);  glBindBuffer(GL_ARRAY_BUFFER,uv_buffer_id);  glBufferData(GL_ARRAY_BUFFER,sizeof(QVector3D) * model_index_uvs.size(),model_index_uvs.data(),GL_STATIC_DRAW);  //===============加载纹理===============  diffuse_texture_id = loadDDS("/Users/liuhang/CLionProjects/opengl-learning/resource/diffuse.DDS");  specular_texture_id = loadDDS("/Users/liuhang/CLionProjects/opengl-learning/resource/specular.DDS");  normal_texture_id = loadBmpTexture("/Users/liuhang/CLionProjects/opengl-learning/resource/normal.bmp");  //设置纹理uniform  uniform_diffuse_texture_sampler_location = glGetUniformLocation(shader_program_id,"diffuse_texture_sampler");  uniform_specular_texture_sampler_location = glGetUniformLocation(shader_program_id,"specular_texture_sampler");  uniform_normal_texture_sampler_location = glGetUniformLocation(shader_program_id,"normal_texture_sampler");  //===============顶点法线数据vbo===============  glGenBuffers(1,&normal_buffer_id);  glBindBuffer(GL_ARRAY_BUFFER,normal_buffer_id);  glBufferData(GL_ARRAY_BUFFER,sizeof(QVector3D) * model_index_normals.size(),model_index_normals.data(),GL_STATIC_DRAW);  //===============切线数据vbo===============  glGenBuffers(1,&tangent_buffer_id);  glBindBuffer(GL_ARRAY_BUFFER,tangent_buffer_id);  glBufferData(GL_ARRAY_BUFFER,sizeof(QVector3D) * model_tangent.size(),model_index_tangent.data(),GL_STATIC_DRAW);  //===============副切线数据vbo===============  glGenBuffers(1,&bitangent_buffer_id);  glBindBuffer(GL_ARRAY_BUFFER,bitangent_buffer_id);  glBufferData(GL_ARRAY_BUFFER,sizeof(QVector3D) * model_bitangent.size(),model_index_bitangent.data(),GL_STATIC_DRAW);  //===============顶点索引ebo===============  glGenBuffers(1,&element_buffer_id);  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,element_buffer_id);  glBufferData(GL_ELEMENT_ARRAY_BUFFER,model_index.size()*sizeof(unsigned int),model_index.data(),GL_STATIC_DRAW);  //获取MVP矩阵的uniform  uniform_model_matrix_location = glGetUniformLocation(shader_program_id,"model_matrix");  uniform_view_matrix_location = glGetUniformLocation(shader_program_id,"view_matrix");  uniform_projection_matrix_location = glGetUniformLocation(shader_program_id,"projection_matrix");  uniform_mvp_matrix_location = glGetUniformLocation(shader_program_id,"mvp_matrix");  uniform_MV3x3_location = glGetUniformLocation(shader_program_id,"MV3x3");  //获取LightPosition_worldspace的location  uniform_LightPosition_worldspace = glGetUniformLocation(shader_program_id,"LightPosition_worldspace");  //解绑vao  glBindVertexArray(0);  
}  void MyGLWidget::paintGL() {  //清除颜色缓存和深度缓存  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  glBindVertexArray(vertex_array_id);  //使用着色器程序  glUseProgram(shader_program_id);  //上传MVP矩阵  doMVP();  //绑定vao  glBindVertexArray(vertex_array_id);  //加载layout = 0 : vertexs  glEnableVertexAttribArray(0);  glBindBuffer(GL_ARRAY_BUFFER,vertex_buffer_id);  glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,nullptr);  //加载layout = 1 : uvs  glEnableVertexAttribArray(1);  glBindBuffer(GL_ARRAY_BUFFER,uv_buffer_id);  glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,0,nullptr);  //加载layout = 2 : normals  glEnableVertexAttribArray(2);  glBindBuffer(GL_ARRAY_BUFFER,normal_buffer_id);  glVertexAttribPointer(2,3,GL_FLOAT,GL_FALSE,0,nullptr);  //加载layout = 3 : tangents  glEnableVertexAttribArray(3);  glBindBuffer(GL_ARRAY_BUFFER,tangent_buffer_id);  glVertexAttribPointer(3,3,GL_FLOAT,GL_FALSE,0,nullptr);  //加载layout = 4 : bitangents  glEnableVertexAttribArray(4);  glBindBuffer(GL_ARRAY_BUFFER,bitangent_buffer_id);  glVertexAttribPointer(4,3,GL_FLOAT,GL_FALSE,0,nullptr);  //绑定纹理单元0:diffuse  glActiveTexture(GL_TEXTURE0);  glBindTexture(GL_TEXTURE_2D,diffuse_texture_id);  glUniform1i(uniform_diffuse_texture_sampler_location, 0);  //绑定纹理单元1:specular  glActiveTexture(GL_TEXTURE1);  glBindTexture(GL_TEXTURE_2D,specular_texture_id);  glUniform1i(uniform_specular_texture_sampler_location,1);  //绑定纹理单元2:normal  glActiveTexture(GL_TEXTURE2);  glBindTexture(GL_TEXTURE_2D,normal_texture_id);  glUniform1i(uniform_normal_texture_sampler_location,2);  //上传光照位置 uniform    QVector3D light_pos = {4.0f,4.0f,4.0f} ;  glUniform3f(uniform_LightPosition_worldspace,light_pos.x(),light_pos.y(),light_pos.z());  //绑定EBO  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,element_buffer_id);  //使用EBO绘制  glDrawElements(GL_TRIANGLES,model_index.size(),GL_UNSIGNED_SHORT,nullptr);  //解绑layout  glDisableVertexAttribArray(0);  glDisableVertexAttribArray(1);  glDisableVertexAttribArray(2);  glDisableVertexAttribArray(3);  glDisableVertexAttribArray(4);  //解绑着色器  glUseProgram(0);  
}  void MyGLWidget::resizeGL(int w, int h) {  glViewport(0, 0, w, h);  
}  void MyGLWidget::loadShader(std::string const& vertex_shader_path,std::string const& fragment_shader_path) {  //创建顶点着色器  vertex_shader_id = glCreateShader(GL_VERTEX_SHADER);  //读取shader.vert  std::string vertex_shader_code;  std::ifstream vertex_shader_stream(vertex_shader_path,std::ios::in);  if(vertex_shader_stream.is_open()){  std::stringstream ss;  ss << vertex_shader_stream.rdbuf();  vertex_shader_code = ss.str();  vertex_shader_stream.close();  }  else{  std::cout << "current path = " << std::filesystem::current_path() << std::endl;  if (!std::filesystem::exists(vertex_shader_path)) {  std::cerr << "File not found: " << vertex_shader_path << std::endl;  }  std::cerr << "read vertex_shader fail!" << std::endl;  return;  }  //加载vertex着色器程序代码  const char* vertex_shader_code_pointer = vertex_shader_code.c_str();  glShaderSource(vertex_shader_id,1,&vertex_shader_code_pointer,nullptr);  //编译vertex shader  glCompileShader(vertex_shader_id);  //查看vertex编译结果  GLint Result = GL_FALSE;  int infoLogLength;  glGetShaderiv(vertex_shader_id,GL_COMPILE_STATUS,&Result);  glGetShaderiv(vertex_shader_id,GL_INFO_LOG_LENGTH,&infoLogLength);  if(infoLogLength > 0){  std::vector<char> vertex_shader_error_message(infoLogLength+1);  glGetShaderInfoLog(vertex_shader_id, infoLogLength, nullptr, &vertex_shader_error_message[0]);  std::cout << "vertex shader:" << &vertex_shader_error_message[0] << std::endl;  }  //创建片段着色器  fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER);  //读取shader.frag  std::string fragment_shader_code;  std::ifstream fragment_shader_stream(fragment_shader_path,std::ios::in);  if(fragment_shader_stream.is_open()){  std::stringstream ss;  ss << fragment_shader_stream.rdbuf();  fragment_shader_code = ss.str();  fragment_shader_stream.close();  }  else{  std::cout << "current path = " << std::filesystem::current_path() << std::endl;  if (!std::filesystem::exists(vertex_shader_path)) {  std::cerr << "File not found: " << vertex_shader_path << std::endl;  }  std::cerr << "read fragment_shader fail!" << std::endl;  }  //加载fragment着色器程序代码  const char* fragment_shader_code_pointer = fragment_shader_code.c_str();  glShaderSource(fragment_shader_id,1,&fragment_shader_code_pointer,nullptr);  //编译fragment shader  glCompileShader(fragment_shader_id);  //查看fragment编译结果  glGetShaderiv(fragment_shader_id,GL_COMPILE_STATUS,&Result);  glGetShaderiv(fragment_shader_id,GL_INFO_LOG_LENGTH,&infoLogLength);  if(infoLogLength > 0){  std::vector<char> fragment_shader_error_message(infoLogLength+1);  glGetShaderInfoLog(fragment_shader_id, infoLogLength, nullptr, &fragment_shader_error_message[0]);  std::cout << "fragment shader compile error:" << &fragment_shader_error_message[0] << std::endl;  }  //创建着色器程序  shader_program_id = glCreateProgram();  //附加着色器到程序  glAttachShader(shader_program_id,vertex_shader_id);  glAttachShader(shader_program_id,fragment_shader_id);  //链接shader  glLinkProgram(shader_program_id);  //检查程序链接结果  glGetProgramiv(shader_program_id,GL_LINK_STATUS,&Result);  glGetProgramiv(shader_program_id,GL_INFO_LOG_LENGTH,&infoLogLength);  if(infoLogLength > 0){  std::vector<char>program_error_message(infoLogLength + 1);  glGetProgramInfoLog(shader_program_id,infoLogLength,nullptr,&program_error_message[0]);  std::cout << "program link:" << &program_error_message[0] << std::endl;  }  //删除着色器编译结果  glDeleteShader(vertex_shader_id);  glDeleteShader(fragment_shader_id);  
}  void MyGLWidget::doMVP()  
{  //模型矩阵  QMatrix4x4 model_matrix;  model_matrix.setToIdentity();  //lookAt  QMatrix4x4 view_matrix;  view_matrix.lookAt(camera_pos,look_dir,up_dir);  //投影矩阵  QMatrix4x4 projection_matrix;  projection_matrix.perspective(angle,aspect,near_plane,far_plane);  //计算mvp矩阵  QMatrix4x4 mvp_matrix = projection_matrix * view_matrix * model_matrix;  QMatrix3x3 MV3x3_matrix = (view_matrix * model_matrix).toGenericMatrix<3,3>();  //矩阵传入shader  glUniformMatrix4fv(uniform_model_matrix_location,1,GL_FALSE,model_matrix.data());  glUniformMatrix4fv(uniform_view_matrix_location,1,GL_FALSE,view_matrix.data());  glUniformMatrix4fv(uniform_projection_matrix_location,1,GL_FALSE,projection_matrix.data());  glUniformMatrix4fv(uniform_mvp_matrix_location,1,GL_FALSE,mvp_matrix.data());  glUniformMatrix3fv(uniform_MV3x3_location,1,GL_FALSE,MV3x3_matrix.data());  }  void MyGLWidget::loadModel()  
{  //读取模型  bool ret = objLoader.loadOBJ("/Users/liuhang/CLionProjects/opengl-learning/resource/cylinder.obj");  if(!ret){  std::cerr << "读取模型错误" << std::endl;  return;  }  //获取属性  model_vertices = objLoader.vertices();  model_uvs = objLoader.uvs();  model_normals = objLoader.normals();  
}  void MyGLWidget::loadEbo()  
{  //计算切线和副切线  computeTangentBasis(model_vertices,model_uvs,model_normals,model_tangent,model_bitangent);  //加载使用索引后的顶点数据  indexVBO_TBN(model_vertices,model_uvs,model_normals,model_tangent,model_bitangent,  model_index,model_index_vertices,model_index_uvs,model_index_normals,model_index_tangent,model_index_bitangent);  
}  void MyGLWidget::computeTangentBasis(  std::vector<QVector3D> &vertices,  std::vector<QVector2D> &uvs,  std::vector<QVector3D> &normals,  std::vector<QVector3D> &tangents,  std::vector<QVector3D> &bitangents  
) {  for (int i = 0; i < vertices.size(); i += 3) {  // 使用Qt的向量类  QVector3D v0 = vertices[i];  QVector3D v1 = vertices[i+1];  QVector3D v2 = vertices[i+2];  QVector2D uv0 = uvs[i];  QVector2D uv1 = uvs[i+1];  QVector2D uv2 = uvs[i+2];  // 计算边向量和UV差  QVector3D deltaPos1 = v1 - v0;  QVector3D deltaPos2 = v2 - v0;  QVector2D deltaUV1 = uv1 - uv0;  QVector2D deltaUV2 = uv2 - uv0;  // 计算切线/副切线  float r = 1.0f / (deltaUV1.x() * deltaUV2.y() - deltaUV1.y() * deltaUV2.x());  QVector3D tangent = (deltaPos1 * deltaUV2.y() - deltaPos2 * deltaUV1.y()) * r;  QVector3D bitangent = (deltaPos2 * deltaUV1.x() - deltaPos1 * deltaUV2.x()) * r;  // 正交化处理(可选)  QVector3D normal = normals[i];  tangent = (tangent - normal * QVector3D::dotProduct(normal, tangent)).normalized();  bitangent = QVector3D::crossProduct(normal, tangent).normalized();  // 存储结果  tangents.push_back(tangent);  tangents.push_back(tangent);  tangents.push_back(tangent);  bitangents.push_back(bitangent);  bitangents.push_back(bitangent);  bitangents.push_back(bitangent);  }  
}  GLint MyGLWidget::loadBmpTexture(QString const& bmp_path) {  QImage texture_image(bmp_path);  if (texture_image.isNull()) {  std::cerr << bmp_path .toStdString() << "路径错误或文件损坏!" << std::endl;  return 0;  }  // 转换为RGB格式并垂直翻转(OpenGL坐标原点在左下角)  QImage gl_image = texture_image.convertToFormat(QImage::Format_RGB888);  GLuint textureID;  glGenTextures(1, &textureID);  glBindTexture(GL_TEXTURE_2D, textureID);  // 上传纹理数据  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, gl_image.width(), gl_image.height(), 0,  GL_RGB, GL_UNSIGNED_BYTE, gl_image.bits());  // 设置纹理参数  //===============设置纹理过滤===============  #if 0  //GL_NEAREST:最近像素点采样  
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);  
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);  
#endif  #if 0  //GL_LINEAR:线性过滤  
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);  
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);  
#endif  #if 1  //GL_MIPMAP_NEAREST: mipmap  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_NEAREST);  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);  //生成mipmap,多加这一行  glGenerateMipmap(GL_TEXTURE_2D);  
#endif  //解绑  glBindTexture(GL_TEXTURE_2D, 0); // 解绑  return textureID;  
}

objloader.hpp

#ifndef QT_OBJLOADER_H  
#define QT_OBJLOADER_H  #include <QVector3D>  
#include <QVector2D>  
#include <QMatrix4x4>  
#include <vector>  
#include <QFile>  
#include <QTextStream>  class QtOBJLoader {  
public:  // 加载OBJ文件,返回是否成功  bool loadOBJ(const QString& path);  // 获取解析后的数据  const std::vector<QVector3D>& vertices() const { return m_vertices; }  const std::vector<QVector2D>& uvs() const { return m_uvs; }  const std::vector<QVector3D>& normals() const { return m_normals; }  private:  std::vector<QVector3D> m_vertices;  // 顶点坐标  std::vector<QVector2D> m_uvs;       // 纹理坐标  std::vector<QVector3D> m_normals;   // 法线向量  
};  #endif // QT_OBJLOADER_H

objloader.cpp

#include "objloader.hpp"  
#include <QDebug>  bool QtOBJLoader::loadOBJ(const QString& path) {  QFile file(path);  if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {  qWarning() << "Failed to open file:" << path;  return false;  }  std::vector<QVector3D> temp_vertices;  std::vector<QVector2D> temp_uvs;  std::vector<QVector3D> temp_normals;  std::vector<unsigned int> vertexIndices, uvIndices, normalIndices;  QTextStream in(&file);  while (!in.atEnd()) {  QString line = in.readLine().trimmed();  if (line.isEmpty() || line.startsWith("#")) continue;  QStringList parts = line.split(" ", Qt::SkipEmptyParts);  if (parts.isEmpty()) continue;  QString type = parts[0];  if (type == "v") {  // 顶点坐标  if (parts.size() < 4) continue;  QVector3D vertex(parts[1].toFloat(), parts[2].toFloat(), parts[3].toFloat());  temp_vertices.push_back(vertex);  }  else if (type == "vt") {  // 纹理坐标  if (parts.size() < 3) continue;  QVector2D uv(parts[1].toFloat(), -parts[2].toFloat());  // 反转V坐标适配DDS纹理  temp_uvs.push_back(uv);  }  else if (type == "vn") {  // 法线向量  if (parts.size() < 4) continue;  QVector3D normal(parts[1].toFloat(), parts[2].toFloat(), parts[3].toFloat());  temp_normals.push_back(normal);  }  else if (type == "f") {  // 面数据  for (int i = 1; i < parts.size(); ++i) {  QStringList indices = parts[i].split("/");  if (indices.size() >= 1) vertexIndices.push_back(indices[0].toUInt() - 1);  if (indices.size() >= 2) uvIndices.push_back(indices[1].toUInt() - 1);  if (indices.size() >= 3) normalIndices.push_back(indices[2].toUInt() - 1);  }  }  }  file.close();  // 根据索引填充输出数据  for (size_t i = 0; i < vertexIndices.size(); ++i) {  if (vertexIndices[i] < temp_vertices.size())  m_vertices.push_back(temp_vertices[vertexIndices[i]]);  if (uvIndices.size() > i && uvIndices[i] < temp_uvs.size())  m_uvs.push_back(temp_uvs[uvIndices[i]]);  if (normalIndices.size() > i && normalIndices[i] < temp_normals.size())  m_normals.push_back(temp_normals[normalIndices[i]]);  }  return !m_vertices.empty();  
}

vboindex.hpp

#ifndef VBOINDEXER_HPP  
#define VBOINDEXER_HPP  #include <QVector>  
#include <QMap>  
#include <QVector3D>  
#include <QVector2D>  void indexVBO(  std::vector<QVector3D> &in_vertices,  std::vector<QVector2D> &in_uvs,  std::vector<QVector3D> &in_normals,  std::vector<unsigned short> &out_indices,  std::vector<QVector3D> &out_vertices,  std::vector<QVector2D> &out_uvs,  std::vector<QVector3D> &out_normals  
);  void indexVBO_TBN(  std::vector<QVector3D> &in_vertices,  std::vector<QVector2D> &in_uvs,  std::vector<QVector3D> &in_normals,  std::vector<QVector3D> &in_tangents,  std::vector<QVector3D> &in_bitangents,  std::vector<unsigned short> &out_indices,  std::vector<QVector3D> &out_vertices,  std::vector<QVector2D> &out_uvs,  std::vector<QVector3D> &out_normals,  std::vector<QVector3D> &out_tangents,  std::vector<QVector3D> &out_bitangents  
);  
#endif

vboindex.cpp

#include "vboindex.hpp"  
#include <cmath>  
#include <cstring>  // 判断浮点数是否近似相等  
bool is_near(float v1, float v2) {  return fabs(v1 - v2) < 0.01f;  
}  // 线性搜索相似顶点(慢速版)  
bool getSimilarVertexIndex(  const QVector3D &in_vertex,  const QVector2D &in_uv,  const QVector3D &in_normal,  const std::vector<QVector3D> &out_vertices,  const std::vector<QVector2D> &out_uvs,  const std::vector<QVector3D> &out_normals,  unsigned short &result  
) {  for (int i = 0; i < out_vertices.size(); i++) {  if (  is_near(in_vertex.x(), out_vertices[i].x()) &&  is_near(in_vertex.y(), out_vertices[i].y()) &&  is_near(in_vertex.z(), out_vertices[i].z()) &&  is_near(in_uv.x(), out_uvs[i].x()) &&  is_near(in_uv.y(), out_uvs[i].y()) &&  is_near(in_normal.x(), out_normals[i].x()) &&  is_near(in_normal.y(), out_normals[i].y()) &&  is_near(in_normal.z(), out_normals[i].z())  ) {  result = i;  return true;  }  }  return false;  
}  // 快速搜索相似顶点(基于哈希表)  
struct PackedVertex {  QVector3D position;  QVector2D uv;  QVector3D normal;  bool operator<(const PackedVertex &that) const {  return memcmp(this, &that, sizeof(PackedVertex)) > 0;  }  
};  bool getSimilarVertexIndex_fast(  const PackedVertex &packed,  QMap<PackedVertex, unsigned short> &VertexToOutIndex,  unsigned short &result  
) {  auto it = VertexToOutIndex.find(packed);  if (it == VertexToOutIndex.end()) {  return false;  } else {  result = it.value();  return true;  }  
}  // 主函数:索引化VBO(优化版)  
void indexVBO(  std::vector<QVector3D> &in_vertices,  std::vector<QVector2D> &in_uvs,  std::vector<QVector3D> &in_normals,  std::vector<unsigned short> &out_indices,  std::vector<QVector3D> &out_vertices,  std::vector<QVector2D> &out_uvs,  std::vector<QVector3D> &out_normals  
) {  QMap<PackedVertex, unsigned short> VertexToOutIndex;  for (int i = 0; i < in_vertices.size(); i++) {  PackedVertex packed = {in_vertices[i], in_uvs[i], in_normals[i]};  unsigned short index;  if (getSimilarVertexIndex_fast(packed, VertexToOutIndex, index)) {  out_indices.push_back(index);  } else {  out_vertices.push_back(in_vertices[i]);  out_uvs.push_back(in_uvs[i]);  out_normals.push_back(in_normals[i]);  unsigned short newindex = out_vertices.size() - 1;  out_indices.push_back(newindex);  VertexToOutIndex[packed] = newindex;  }  }  
}  // 支持切线空间的版本  
void indexVBO_TBN(  std::vector<QVector3D> &in_vertices,  std::vector<QVector2D> &in_uvs,  std::vector<QVector3D> &in_normals,  std::vector<QVector3D> &in_tangents,  std::vector<QVector3D> &in_bitangents,  std::vector<unsigned short> &out_indices,  std::vector<QVector3D> &out_vertices,  std::vector<QVector2D> &out_uvs,  std::vector<QVector3D> &out_normals,  std::vector<QVector3D> &out_tangents,  std::vector<QVector3D> &out_bitangents  
) {  QMap<PackedVertex, unsigned short> VertexToOutIndex;  for (int i = 0; i < in_vertices.size(); i++) {  PackedVertex packed = {in_vertices[i], in_uvs[i], in_normals[i]};  unsigned short index;  if (getSimilarVertexIndex_fast(packed, VertexToOutIndex, index)) {  out_indices.push_back(index);  out_tangents[index] += in_tangents[i];  out_bitangents[index] += in_bitangents[i];  } else {  out_vertices.push_back(in_vertices[i]);  out_uvs.push_back(in_uvs[i]);  out_normals.push_back(in_normals[i]);  out_tangents.push_back(in_tangents[i]);  out_bitangents.push_back(in_bitangents[i]);  unsigned short newindex = out_vertices.size() - 1;  out_indices.push_back(newindex);  VertexToOutIndex[packed] = newindex;  }  }  
}

my_texture.hpp

#pragma once  #include<QOpenGLFunctions>  #define FOURCC_DXT1 0x31545844 // Equivalent to "DXT1" in ASCII  
#define FOURCC_DXT3 0x33545844 // Equivalent to "DXT3" in ASCII  
#define FOURCC_DXT5 0x35545844 // Equivalent to "DXT5" in ASCII  GLuint loadDDS(const char * imagepath);

my_texture.cpp

  
#include"my_texture.hpp"  GLuint loadDDS(const char * imagepath){  unsigned char header[124];  FILE *fp;  /* try to open the file */  fp = fopen(imagepath, "rb");  if (fp == NULL){  printf("%s could not be opened. Are you in the right directory ? Don't forget to read the FAQ !\n", imagepath); getchar();  return 0;  }  /* verify the type of file */  char filecode[4];  fread(filecode, 1, 4, fp);  if (strncmp(filecode, "DDS ", 4) != 0) {  fclose(fp);  return 0;  }  /* get the surface desc */  fread(&header, 124, 1, fp);  unsigned int height      = *(unsigned int*)&(header[8 ]);  unsigned int width       = *(unsigned int*)&(header[12]);  unsigned int linearSize  = *(unsigned int*)&(header[16]);  unsigned int mipMapCount = *(unsigned int*)&(header[24]);  unsigned int fourCC      = *(unsigned int*)&(header[80]);  unsigned char * buffer;  unsigned int bufsize;  /* how big is it going to be including all mipmaps? */  bufsize = mipMapCount > 1 ? linearSize * 2 : linearSize;  buffer = (unsigned char*)malloc(bufsize * sizeof(unsigned char));  fread(buffer, 1, bufsize, fp);  /* close the file pointer */  fclose(fp);  unsigned int components  = (fourCC == FOURCC_DXT1) ? 3 : 4;  unsigned int format;  switch(fourCC)  {  case FOURCC_DXT1:  format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;  break;  case FOURCC_DXT3:  format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;  break;  case FOURCC_DXT5:  format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;  break;  default:  free(buffer);  return 0;  }  // Create one OpenGL texture  GLuint textureID;  glGenTextures(1, &textureID);  // "Bind" the newly created texture : all future texture functions will modify this texture  glBindTexture(GL_TEXTURE_2D, textureID);  glPixelStorei(GL_UNPACK_ALIGNMENT,1);  unsigned int blockSize = (format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) ? 8 : 16;  unsigned int offset = 0;  /* load the mipmaps */  for (unsigned int level = 0; level < mipMapCount && (width || height); ++level)  {  unsigned int size = ((width+3)/4)*((height+3)/4)*blockSize;  glCompressedTexImage2D(GL_TEXTURE_2D, level, format, width, height,  0, size, buffer + offset);  offset += size;  width  /= 2;  height /= 2;  // Deal with Non-Power-Of-Two textures. This code is not included in the webpage to reduce clutter.  if(width < 1) width = 1;  if(height < 1) height = 1;  }  free(buffer);  return textureID;  }

shader.vert

#version 330 core  layout(location = 0) in vec3 vertexPosition_modelspace;  
layout(location = 1) in vec2 vertexUV;  
layout(location = 2) in vec3 vertexNormal_modelspace;  
layout(location = 3) in vec3 vertexTangent_modelspace;  
layout(location = 4) in vec3 vertexBitangent_modelspace;  out vec2 UV;  
out vec3 Position_worldspace;  
out vec3 EyeDirection_cameraspace;  
out vec3 LightDirection_cameraspace;  out vec3 LightDirection_tangentspace;  
out vec3 EyeDirection_tangentspace;  uniform mat4 model_matrix;  
uniform mat4 view_matrix;  
uniform mat4 projection_matrix;  
uniform mat4 mvp_matrix;  
uniform mat3 MV3x3;  uniform vec3 LightPosition_worldspace;  void main()  
{  //裁剪坐标系  gl_Position = mvp_matrix * vec4(vertexPosition_modelspace,1.0f);  //顶点(世界坐标系)  Position_worldspace = (model_matrix * vec4(vertexPosition_modelspace,1)).xyz;  //顶点(摄像机坐标系)  vec3 vertexPosition_cameraspace = (view_matrix * model_matrix * vec4(vertexPosition_modelspace,1)).xyz;  //视线方向(摄像机坐标系),原点指向摄像机,假设摄像机看向原点  EyeDirection_cameraspace = vec3(0,0,0) - vertexPosition_cameraspace;  //光源所在位置(摄像机坐标系)  vec3 LightPosition_cameraspace = (view_matrix * vec4(LightPosition_worldspace,1)).xyz;  //光源方向(摄像机坐标系)顶点指向光源,假设摄像机看向原点  LightDirection_cameraspace = LightPosition_cameraspace + EyeDirection_cameraspace;  //uv赋值  UV = vertexUV;  //法线、切线、副切线从模型坐标系->摄像机坐标系 (MV3x3实际上是view_matrix * model_matrix)  vec3 vertexTangent_cameraspace = MV3x3 * vertexTangent_modelspace;  vec3 vertexBitangent_cameraspace = MV3x3 * vertexBitangent_modelspace;  vec3 vertexNormal_cameraspace = MV3x3 * vertexNormal_modelspace;  //使用法线、切线、副切线计算出TBN矩阵,transpose是求转置  mat3 TBN = transpose(mat3(  vertexTangent_cameraspace,  vertexBitangent_cameraspace,  vertexNormal_cameraspace  ));  //光源方向(切线坐标系): TBN * 摄像机坐标系  LightDirection_tangentspace = TBN * LightDirection_cameraspace;  //视觉方向(切线坐标系): TBN * 摄像机坐标系  EyeDirection_tangentspace =  TBN * EyeDirection_cameraspace;  
}

shader.frag

#version 330 core  
in vec2 UV;  
in vec3 Position_worldspace;  
in vec3 EyeDirection_cameraspace;  
in vec3 LightDirection_cameraspace;  
out vec4 color;  
in vec3 LightDirection_tangentspace;  
in vec3 EyeDirection_tangentspace;  uniform sampler2D diffuse_texture_sampler;  
uniform sampler2D specular_texture_sampler;  
uniform sampler2D normal_texture_sampler;  uniform mat4 model_matrix;  
uniform mat4 view_matrix;  
uniform mat4 mvp_matrix;  
uniform mat3 MV3x3;  uniform vec3 LightPosition_worldspace;  void main(){  //光源颜色:白光  vec3 LightColor = vec3(1.0,1.0,1.0);  //环境光强度  float LightPower = 120.0;  //材质属性:反射光  vec3 MaterialDiffuseColor = texture(diffuse_texture_sampler, UV ).rgb;  //材质属性:环境光  vec3 MaterialAmbientColor = vec3(0.5,0.5,0.5) * MaterialDiffuseColor;  //材质属性:镜面光  vec3 MaterialSpecularColor = texture(specular_texture_sampler, UV ).rgb * 0.3;  //法线贴图的值需要转换:normal = 2.0 * color - 1.0  vec3 TextureNormal_tangentspace = normalize(texture(normal_texture_sampler, vec2(UV.x,UV.y)).rgb*2.0 - 1.0);  //离光源的距离  float distance = length( LightPosition_worldspace - Position_worldspace );  //法线方向  vec3 n = TextureNormal_tangentspace;  //光源方向  vec3 l = normalize(LightDirection_tangentspace);  //法线和光源的夹角  float cosTheta = clamp( dot( n,l ), 0,1 );  //视线方向  vec3 E = normalize(EyeDirection_tangentspace);  //反射光方向  vec3 R = reflect(-l,n);  //视线和反射光的夹角  float cosAlpha = clamp( dot( E,R ), 0,1 );  //镜面光强度  float shiness = pow(cosAlpha,5);  color.xyz =  //环境光  MaterialAmbientColor +  //反射光  MaterialDiffuseColor * LightColor * LightPower * cosTheta / (distance*distance)+  //镜面光  MaterialSpecularColor * LightColor * LightPower * shiness / (distance*distance);  color.a = 1.0;  
}
http://www.dtcms.com/a/394221.html

相关文章:

  • 科普:通配符表达式(Wildcard)与正则表达式(Regular Expression)
  • 【ROS2】Beginner: Client libraries - 使用 colcon 构建功能包
  • 记一次投影连接网络存储
  • 计算机视觉(opencv)实战二十九——图像风格迁移
  • Python数据挖掘之基础分类模型_K最近邻分类器(KNN)_决策树
  • 23种设计模式之【外观模式】-核心原理与 Java实践
  • 第4章:构建自己的物料解决方案
  • 华为昇腾 950 系列芯片深度解析
  • 2025华为杯 C题围岩裂隙精准识别与三维模型重构保姆级教程思路分析【国奖版】
  • 搭建Electron桌面项目
  • Linux 线程之pthread库
  • 内存泄漏、内存溢出与内存访问越界
  • C++初阶(11)string类的模拟实现
  • Python快速入门专业版(三十九):Python集合:去重与集合运算(交集、并集、差集)
  • pytorch 中meshgrid()函数详解
  • 深度探秘GAIA:一个为下一代AI量身打造的挑战性基准
  • 今日分享C++ ---继承
  • TableGPT:浙江大学发布的表格大模型
  • Linux 概述
  • 领码学堂·定时任务新思维[二]——七大替代方案总览:场景、优缺点与快速选型
  • NLP:详解FastText
  • 【力扣】hot100系列(一)哈希部分解析(多解法+时间复杂度分析)
  • 用AI开发HTML双语阅读工具助力英语阅读
  • AI论文速读 | 当大语言模型遇上时间序列:大语言模型能否执行多步时间序列推理与推断
  • 如何使用升腾C92主机搭建本地Linux编译服务器并通过Windows映射访问共享目录
  • 测试DuckDB-rs项目中的示例程序
  • 分布式协议与算法实战-实战篇
  • 【硬件-笔试面试题-105】硬件/电子工程师,笔试面试题(知识点:详细讲讲什么是链表和数组)
  • 【获取地址栏的搜索关键字】功能-总结
  • 关于__sync_bool_compare_and_swap的使用及在多核多线程下使用时的思考