OpenGL with QtWidgets:材质、光照贴图
(本文是LearnOpenGL的学习笔记, 教程中文翻译地址https://learnopengl-cn.github.io/(备用地址https://learnopengl-cn.readthedocs.io/zh/latest/),写于 2021-9-20)
0.前言
上一节学习了基础光照(https://mp.csdn.net/mp_blog/creation/editor/106005766),教程接下来讲了材质和光照贴图。
1.知识点
如果我们想要在OpenGL中模拟多种类型的物体,我们必须为每个物体分别定义一个材质(Material)属性。可以用这三个分量来定义一个材质颜色(Material Color):环境光照(Ambient Lighting)、漫反射光照(Diffuse Lighting)和镜面光照(Specular Lighting)。通过为每个分量指定一个颜色,我们就能够对物体的颜色输出有着精细的控制了。再添加反光度(Shininess)这个分量到上述的三个颜色中,这就有我们需要的所有材质属性了:
struct Material {
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
uniform Material material;
ambient材质向量定义了在环境光照下这个物体反射得是什么颜色,通常这是和物体颜色相同的颜色。diffuse材质向量定义了在漫反射光照下物体的颜色。(和环境光照一样)漫反射颜色也要设置为我们需要的物体颜色。specular材质向量设置的是镜面光照对物体的颜色影响(或者甚至可能反射一个物体特定的镜面高光颜色)。最后,shininess影响镜面高光的散射/半径。
现实世界中的物体通常并不只包含有一种材质,而是由多种材质所组成。引入漫反射和镜面光贴图(Map)。这允许我们对物体的漫反射分量(以及间接地对环境光分量,它们几乎总是一样的)和镜面光分量有着更精确的控制。
struct Material {
sampler2D diffuse; //环境光和漫反射取同一个纹理
sampler2D specular;
float shininess;
};
2.实现代码
(项目git链接:https://github.com/gongjianbo/OpenGLwithQtWidgets.git)
为了简化代码,我把 Camrea 观察矩阵给固定了不能旋转和移动
材质效果:
光照贴图效果:
光照贴图部分的代码(GLLightingMap类)如下:
#pragma once
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLTexture>
#include <QVector3D>
#include <QMatrix4x4>
#include <QQuaternion>
#include <QTimer>
//光照贴图
//QOpenGLWidget窗口上下文
//QOpenGLFunctions访问OpenGL接口,可以不继承作为成员变量使用
class GLLightingMap
: public QOpenGLWidget
, protected QOpenGLFunctions_3_3_Core
{
Q_OBJECT
public:
explicit GLLightingMap(QWidget *parent = nullptr);
~GLLightingMap();
protected:
//【】继承QOpenGLWidget后重写这三个虚函数
//设置OpenGL资源和状态。在第一次调用resizeGL或paintGL之前被调用一次
void initializeGL() override;
//渲染OpenGL场景,每当需要更新小部件时使用
void paintGL() override;
//设置OpenGL视口、投影等,每当尺寸大小改变时调用
void resizeGL(int width, int height) override;
private:
void initShader();
QOpenGLTexture *initTexture(const QString &imgpath);
private:
//着色器程序
QOpenGLShaderProgram lightingShader,lampShader;
//顶点数组对象
QOpenGLVertexArrayObject lightingVao,lampVao;
//顶点缓冲
QOpenGLBuffer vbo;
//纹理
QOpenGLTexture *diffuseMap{ nullptr };
QOpenGLTexture *specularMap{ nullptr };
//
QTimer timer;
int rotate{ 0 };
};
#include "GLLightingMap.h"
#include <cmath>
#include <QtMath>
#include <QDebug>
GLLightingMap::GLLightingMap(QWidget *parent)
: QOpenGLWidget(parent)
{
connect(&timer,&QTimer::timeout,this,[this](){
rotate+=1;
if(isVisible()){
update();
}
});
timer.setInterval(50);
}
GLLightingMap::~GLLightingMap()
{
//initializeGL在显示时才调用,释放未初始化的会异常
if(!isValid())
return;
//QOpenGLWidget
//三个虚函数不需要makeCurrent,对应的操作已由框架完成
//但是释放时需要设置当前上下文
makeCurrent();
vbo.destroy();
lightingVao.destroy();
lampVao.destroy();
delete diffuseMap;
delete specularMap;
doneCurrent();
}
void GLLightingMap::initializeGL()
{
//为当前上下文初始化OpenGL函数解析
initializeOpenGLFunctions();
initShader();
//方块的顶点、法向量、纹理坐标
float vertices[] = {
// positions // normals // texture coords
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f
};
vbo=QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
vbo.create();
//light vao
lightingVao.create();
lightingVao.bind();
vbo.bind();
vbo.allocate(vertices,sizeof(vertices));
//setAttributeBuffer(int location, GLenum type, int offset, int tupleSize, int stride = 0)
lightingShader.setAttributeBuffer(0, GL_FLOAT, sizeof(GLfloat) * 0, 3, sizeof(GLfloat) * 8);
lightingShader.enableAttributeArray(0);
lightingShader.setAttributeBuffer(1, GL_FLOAT, sizeof(GLfloat) * 3, 3, sizeof(GLfloat) * 8);
lightingShader.enableAttributeArray(1);
lightingShader.setAttributeBuffer(2, GL_FLOAT, sizeof(GLfloat) * 6, 2, sizeof(GLfloat) * 8);
lightingShader.enableAttributeArray(2);
vbo.release();
lightingVao.release();
//lamp vao
lampVao.create();
lampVao.bind();
vbo.bind();
//setAttributeBuffer(int location, GLenum type, int offset, int tupleSize, int stride = 0)
lampShader.setAttributeBuffer(0, GL_FLOAT, 0, 3, sizeof(GLfloat) * 8);
lampShader.enableAttributeArray(0);
vbo.release();
lampVao.release();
//纹理
diffuseMap = initTexture(":/container2.png");
specularMap = initTexture(":/container2_specular.png");
//shader configuration
lightingShader.bind();
lightingShader.setUniformValue("material.diffuse", 0);
lightingShader.setUniformValue("material.specular", 1);
lightingShader.release();
timer.start();
}
void GLLightingMap::paintGL()
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
//清除深度缓冲
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//Z缓冲(Z-buffer),深度缓冲(Depth Buffer)。
glEnable(GL_DEPTH_TEST);
//draw lighting
lightingShader.bind();
QMatrix4x4 view; //观察矩阵
view.translate(0.0f, 0.0f, -5.0f);
view.rotate(45, QVector3D(1.0f, 0.8f, 0.0f));
lightingShader.setUniformValue("view", view);
QMatrix4x4 projection; //透视投影
projection.perspective(45.0f, 1.0f * width() / height(), 0.1f, 100.0f);
lightingShader.setUniformValue("projection", projection);
QMatrix4x4 model;//模型矩阵
lightingShader.setUniformValue("model", model);
//因为要获取灯的位置,所以提前算灯的model矩阵
model = QMatrix4x4();
model.translate(QVector3D(1.0f, 1.0f, -1.0f));
model.scale(0.3f);
QVector3D light_pos = model.map(QVector3D(0.0f, 0.0f, 0.0f));
QMatrix4x4 vv = view.inverted(); //逆矩阵求观察点位置
QVector3D view_pos = vv.map(QVector3D(0.0f, 0.0f, 0.0f));
lightingShader.setUniformValue("light.position", light_pos);
lightingShader.setUniformValue("viewPos", view_pos);
//光照-light properties
QVector3D light_color = QVector3D(1.0f, 1.0f, 1.0f);
QVector3D diffuse_color = light_color * 0.5f; // decrease the influence
QVector3D ambient_color = diffuse_color * 0.2f; // low influence
lightingShader.setUniformValue("light.ambient", ambient_color);
lightingShader.setUniformValue("light.diffuse", diffuse_color);
lightingShader.setUniformValue("light.specular", QVector3D(1.0f, 1.0f, 1.0f));
//材质-material properties
//shininess影响镜面高光的散射/半径
lightingShader.setUniformValue("material.shininess", 64.0f);
lightingVao.bind();
//绑定2d纹理
//bind diffuse map
glActiveTexture(GL_TEXTURE0);
diffuseMap->bind();
//bind specular map
glActiveTexture(GL_TEXTURE1);
specularMap->bind();
glDrawArrays(GL_TRIANGLES, 0, 36);
lightingVao.release();
lightingShader.release();
//draw lamp
lampShader.bind();
lampShader.setUniformValue("view", view);
lampShader.setUniformValue("projection", projection);
lampShader.setUniformValue("model", model);
lampVao.bind();
glDrawArrays(GL_TRIANGLES, 0, 36);
lampVao.release();
lampShader.release();
}
void GLLightingMap::resizeGL(int width, int height)
{
glViewport(0, 0, width, height);
}
void GLLightingMap::initShader()
{
//lingting shader
//in输入,out输出,uniform从cpu向gpu发送
const char *lighting_vertex=R"(#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
FragPos = vec3(model * vec4(aPos, 1.0));
Normal = mat3(transpose(inverse(model))) * aNormal;
TexCoords = aTexCoords;
gl_Position = projection * view * vec4(FragPos, 1.0);
})";
const char *lighting_fragment=R"(#version 330 core
out vec4 FragColor;
struct Material {
sampler2D diffuse;
sampler2D specular;
float shininess;
};
struct Light {
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;
uniform vec3 viewPos;
uniform Material material;
uniform Light light;
void main()
{
// ambient
vec3 ambient = light.ambient * texture(material.diffuse, TexCoords).rgb;
// diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(light.position - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = light.diffuse * diff * texture(material.diffuse, TexCoords).rgb;
// specular
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = light.specular * spec * texture(material.specular, TexCoords).rgb;
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
})";
//将source编译为指定类型的着色器,并添加到此着色器程序
if(!lightingShader.addCacheableShaderFromSourceCode(
QOpenGLShader::Vertex,lighting_vertex)){
qDebug()<<"compiler vertex error"<<lightingShader.log();
}
if(!lightingShader.addCacheableShaderFromSourceCode(
QOpenGLShader::Fragment,lighting_fragment)){
qDebug()<<"compiler fragment error"<<lightingShader.log();
}
//使用addShader()将添加到该程序的着色器链接在一起。
if(!lightingShader.link()){
qDebug()<<"link shaderprogram error"<<lightingShader.log();
}
//lamp shader
const char *lamp_vertex=R"(#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0f);
})";
const char *lamp_fragment=R"(#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0);
})"; // set alle 4 vector values to 1.0
if(!lampShader.addCacheableShaderFromSourceCode(
QOpenGLShader::Vertex,lamp_vertex)){
qDebug()<<"compiler vertex error"<<lampShader.log();
}
if(!lampShader.addCacheableShaderFromSourceCode(
QOpenGLShader::Fragment,lamp_fragment)){
qDebug()<<"compiler fragment error"<<lampShader.log();
}
if(!lampShader.link()){
qDebug()<<"link shaderprogram error"<<lampShader.log();
}
}
QOpenGLTexture *GLLightingMap::initTexture(const QString &imgpath)
{
QOpenGLTexture *texture = new QOpenGLTexture(QImage(imgpath), QOpenGLTexture::GenerateMipMaps);
if(!texture->isCreated()){
qDebug() << "Failed to create texture";
}
//set the texture wrapping parameters
//等于glTexParameteri(GLtexture_2D, GLtexture_WRAP_S, GL_REPEAT);
texture->setWrapMode(QOpenGLTexture::DirectionS, QOpenGLTexture::Repeat);
texture->setWrapMode(QOpenGLTexture::DirectionT, QOpenGLTexture::Repeat);//
//set texture filtering parameters
//等价于glTexParameteri(GLtexture_2D, GLtexture_MIN_FILTER, GL_LINEAR);
texture->setMinificationFilter(QOpenGLTexture::Linear);
texture->setMagnificationFilter(QOpenGLTexture::Linear);
return texture;
}
3.参考
LearnOpenGL:https://learnopengl-cn.github.io/02%20Lighting/04%20Lighting%20maps/
博客(Qt+OpenGL):https://www.jianshu.com/p/855ce8d81849
博客(Qt+OpenGL):https://blog.csdn.net/z136411501/article/details/80149125
博客(Qt+OpenGL):https://blog.csdn.net/qq_40946921/article/details/106200378
还没有评论,来说两句吧...