OpenGLopenGL-法线贴图
【OpenGL】openGL 法线贴图
参考文章:
一、法线纹理
下图是一张法线纹理:
每个纹素的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向量之后,即可得下面这个漂亮的矩阵,完成从切线空间到模型空间的变换:
[ T x B x N x T y B y N y T z B z N z ] \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)
亦即:
[ T x T y T z B x B y B z N x N y N z ] \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(
// inputs
std::vector & vertices,
std::vector & uvs,
std::vector & normals,
// outputs
std::vector & tangents,
std::vector & bitangents
){
为每个三角形计算边(deltaPos)和deltaUV
for ( int i=0; i<vertices.size(); i+=3){
// Shortcuts for vertices
glm::vec3 & v0 = vertices[i+0];
glm::vec3 & v1 = vertices[i+1];
glm::vec3 & v2 = vertices[i+2];
// Shortcuts for UVs
glm::vec2 & uv0 = uvs[i+0];
glm::vec2 & uv1 = uvs[i+1];
glm::vec2 & uv2 = uvs[i+2];
// Edges of the triangle : postion delta
glm::vec3 deltaPos1 = v1-v0;
glm::vec3 deltaPos2 = v2-v0;
// UV delta
glm::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 bitangents
out_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, // attribute
3, // size
GL_FLOAT, // type
GL_FALSE, // normalized?
0, // stride
(void*)0 // array buffer offset
);
// 2nd attribute buffer : UVs
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
glVertexAttribPointer(
1, // attribute
2, // size
GL_FLOAT, // type
GL_FALSE, // normalized?
0, // stride
(void*)0 // array buffer offset
);
// 3rd attribute buffer : normals
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
glVertexAttribPointer(
2, // attribute
3, // size
GL_FLOAT, // type
GL_FALSE, // normalized?
0, // stride
(void*)0 // array buffer offset
);
// 4th attribute buffer : tangents
glEnableVertexAttribArray(3);
glBindBuffer(GL_ARRAY_BUFFER, tangentbuffer);
glVertexAttribPointer(
3, // attribute
3, // size
GL_FLOAT, // type
GL_FALSE, // normalized?
0, // stride
(void*)0 // array buffer offset
);
// 5th attribute buffer : bitangents
glEnableVertexAttribArray(4);
glBindBuffer(GL_ARRAY_BUFFER, bitangentbuffer);
glVertexAttribPointer(
4, // attribute
3, // size
GL_FLOAT, // type
GL_FALSE, // normalized?
0, // stride
(void*)0 // array buffer offset
);
// Index buffer
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);
// Draw the triangles !
glDrawElements(
GL_TRIANGLES, // mode
indices.size(), // count
GL_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 space
vec3 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;
}