Learn OpenGL with Qt——光照贴图

光照贴图

在上一节中,我们讨论了让每个物体都拥有自己独特的材质从而对光照做出不同的反应的方法。这样子能够很容易在一个光照的场景中给每个物体一个独特的外观,但是这仍不能对一个物体的视觉输出提供足够多的灵活性。

在上一节中,我们将整个物体的材质定义为一个整体,但现实世界中的物体通常并不只包含有一种材质,而是由多种材质所组成。想想一辆汽车:它的外壳非常有光泽,车窗会部分反射周围的环境,轮胎不会那么有光泽,所以它没有镜面高光,轮毂非常闪亮(如果你洗车了的话)。汽车同样会有漫反射和环境光颜色,它们在整个物体上也不会是一样的,汽车有着许多种不同的环境光/漫反射颜色。总之,这样的物体在不同的部件上都有不同的材质属性。

所以,上一节中的那个材质系统是肯定不够的,它只是一个最简单的模型,所以我们需要拓展之前的系统,引入漫反射镜面光贴图(Map)。这允许我们对物体的漫反射分量(以及间接地对环境光分量,它们几乎总是一样的)和镜面光分量有着更精确的控制。

我们希望通过某种方式对物体的每个片段单独设置漫反射颜色。有能够让我们根据片段在物体上的位置来获取颜色值得系统吗?

这可能听起来很熟悉,而且事实上这个系统我们已经使用很长时间了。这听起来很像在之前教程中详细讨论过的纹理,而这基本就是这样:一个纹理。我们仅仅是对同样的原理使用了不同的名字:其实都是使用一张覆盖物体的图像,让我们能够逐片段索引其独立的颜色值。在光照场景中,它通常叫做一个漫反射贴图(Diffuse Map)(3D艺术家通常都这么叫它),它是一个表现了物体所有的漫反射颜色的纹理图像。

为了演示漫反射贴图,我们将会使用下面的图片,它是一个有钢边框的木箱:

在着色器中使用漫反射贴图的方法和纹理教程中是完全一样的。但这次我们会将纹理储存为Material结构体中的一个sampler2D。我们将之前定义的vec3漫反射颜色向量替换为漫反射贴图。

我们也移除amibient材质颜色向量,因为ambient颜色绝大多数情况等于diffuse颜色,所以不需要分别去储存它:

struct Material
{
    sampler2D diffuse;
    vec3 specular;
    float shininess;
};
...
in vec2 TexCoords;

注意我们将在片段着色器中再次需要纹理坐标,所以我们声明一个额外的输入变量。接下来我们只需要从纹理中采样片段的漫反射颜色值即可:

 

vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));

不要忘记将环境光得材质颜色设置为漫反射材质颜色同样的值。

vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));

这就是使用漫反射贴图的全部步骤了。你可以看到,这并不是什么新的东西,但这能够极大地提高视觉品质。为了让它正常工作,我们还需要使用纹理坐标更新顶点数据,将它们作为顶点属性传递到片段着色器,加载材质并绑定材质到合适的纹理单元。

更新后的顶点数据在这里:

vertices = {
         //位置               //法向量               //纹理坐标
        -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
    };

顶点数据现在包含了顶点位置、法向量和立方体顶点处的纹理坐标。让我们更新顶点着色器来以顶点属性的形式接受纹理坐标,并将它们传递到片段着色器中:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
...
out vec2 TexCoords;

void main()
{
    ...
    TexCoords = aTexCoords;
}

记得去更新两个VAO的顶点属性指针来匹配新的顶点数据,并加载箱子图像为一个纹理。在绘制箱子之前,我们希望将要用的纹理单元赋值到material.diffuse这个uniform采样器,并绑定箱子的纹理到这个纹理单元:

    , tex_diffuse(QOpenGLTexture::Target2D)
    tex_diffuse.setData(QImage(":/container_diffuse.png"));
    shaderProgram.setAttributeBuffer("aPos", GL_FLOAT, 0, 3, sizeof(GLfloat) * 8);
    shaderProgram.enableAttributeArray("aPos");
    shaderProgram.setAttributeBuffer("aNormal", GL_FLOAT, sizeof(GLfloat) * 3, 3, sizeof(GLfloat) * 8);
    shaderProgram.enableAttributeArray("aNormal");
    shaderProgram.setAttributeBuffer("aTexCoords", GL_FLOAT, sizeof(GLfloat) * 6, 2, sizeof(GLfloat) * 8);
    shaderProgram.enableAttributeArray("aTexCoords");
    //...
    lampShader.setAttributeBuffer("aPos", GL_FLOAT, 0, 3, sizeof(GLfloat) * 8);
    lampShader.enableAttributeArray("aPos");
    tex_diffuse.bind(0);
    shaderProgram.setUniformValue("material.diffuse",0);

使用了漫反射贴图之后,细节再一次得到惊人的提升,这次箱子有了光照开始闪闪发光(字面意思也是)了。你的箱子看起来可能像这样:

代码节点

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include "camera.h"

#include <QOpenGLWidget>
#include <QOpenGLExtraFunctions>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLTexture>
#include <QTimer>
#include <QTime>
#include <QtMath>
#include <QKeyEvent>
class Widget : public QOpenGLWidget,public QOpenGLExtraFunctions
{
    Q_OBJECT

public:
    Widget(QWidget *parent = 0);
    ~Widget();
protected:
    virtual void initializeGL() override;
    virtual void resizeGL(int w,int h) override;
    virtual void paintGL() override;

    virtual bool event(QEvent *e) override;

private:
    QVector<float> vertices;
    QVector<QVector3D> cubePositions;
    QOpenGLShaderProgram shaderProgram;
    QOpenGLShaderProgram lampShader;
    QOpenGLBuffer VBO;
    QOpenGLVertexArrayObject VAO;
    QOpenGLVertexArrayObject lightVAO;
    QTimer timer;
    QOpenGLTexture tex_diffuse;
    Camera camera;
};

#endif // WIDGET_H

 widget.cpp

#include "widget.h"
#include <QtMath>

Widget::Widget(QWidget *parent)
    : QOpenGLWidget(parent)
    , VBO(QOpenGLBuffer::VertexBuffer)
    , tex_diffuse(QOpenGLTexture::Target2D)
    , camera(this)

{

    vertices = {
         //位置               //法向量               //纹理坐标
        -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
    };

    timer.setInterval(18);
    connect(&timer,&QTimer::timeout,this,static_cast<void (Widget::*)()>(&Widget::update));
    timer.start();

    QSurfaceFormat format;
    format.setSamples(20);
    setFormat(format);

}

Widget::~Widget()
{
    makeCurrent();
    tex_diffuse.destroy();
    doneCurrent();
}

void Widget::initializeGL()
{
    this->initializeOpenGLFunctions();        //初始化opengl函数
    if(!shaderProgram.addShaderFromSourceFile(QOpenGLShader::Vertex,":/triangle.vert")){     //添加并编译顶点着色器
        qDebug()<<"ERROR:"<<shaderProgram.log();    //如果编译出错,打印报错信息
    }
    if(!shaderProgram.addShaderFromSourceFile(QOpenGLShader::Fragment,":/triangle.frag")){   //添加并编译片段着色器
        qDebug()<<"ERROR:"<<shaderProgram.log();    //如果编译出错,打印报错信息
    }
    if(!shaderProgram.link()){                      //链接着色器
        qDebug()<<"ERROR:"<<shaderProgram.log();    //如果链接出错,打印报错信息
    }

    tex_diffuse.setData(QImage(":/container_diffuse.png"));
    QOpenGLVertexArrayObject::Binder{&VAO};

    VBO.create();       //生成VBO对象
    VBO.bind();         //将VBO绑定到当前的顶点缓冲对象(QOpenGLBuffer::VertexBuffer)中

    //将顶点数据分配到VBO中,第一个参数为数据指针,第二个参数为数据的字节长度
    VBO.allocate(vertices.data(),sizeof(float)*vertices.size());



    //设置顶点解析格式,并启用顶点
    shaderProgram.setAttributeBuffer("aPos", GL_FLOAT, 0, 3, sizeof(GLfloat) * 8);
    shaderProgram.enableAttributeArray("aPos");
    shaderProgram.setAttributeBuffer("aNormal", GL_FLOAT, sizeof(GLfloat) * 3, 3, sizeof(GLfloat) * 8);
    shaderProgram.enableAttributeArray("aNormal");
    shaderProgram.setAttributeBuffer("aTexCoords", GL_FLOAT, sizeof(GLfloat) * 6, 2, sizeof(GLfloat) * 8);
    shaderProgram.enableAttributeArray("aTexCoords");

    if(!lampShader.addShaderFromSourceFile(QOpenGLShader::Vertex,":/triangle.vert")){     //添加并编译顶点着色器
        qDebug()<<"ERROR:"<<shaderProgram.log();    //如果编译出错,打印报错信息
    }
    if(!lampShader.addShaderFromSourceFile(QOpenGLShader::Fragment,":/lamp.frag")){   //添加并编译片段着色器
        qDebug()<<"ERROR:"<<shaderProgram.log();    //如果编译出错,打印报错信息
    }
    if(!lampShader.link()){                      //链接着色器
        qDebug()<<"ERROR:"<<shaderProgram.log();    //如果链接出错,打印报错信息
    }

    QOpenGLVertexArrayObject::Binder{&lightVAO};
    VBO.bind(); //只需要绑定VBO不用再次设置VBO的数据,因为箱子的VBO数据中已经包含了正确的立方体顶点数据
    lampShader.setAttributeBuffer("aPos", GL_FLOAT, 0, 3, sizeof(GLfloat) * 8);
    lampShader.enableAttributeArray("aPos");


    this->glEnable(GL_DEPTH_TEST);

    camera.init();

}

void Widget::resizeGL(int w, int h)
{
    this->glViewport(0,0,w,h);                  //定义视口区域
}

void Widget::paintGL()
{
    this->glClearColor(0.0f,0.0f,0.0f,1.0f);                       //设置清屏颜色
    this->glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT);       //清除颜色缓存和深度缓存
    float time=QTime::currentTime().msecsSinceStartOfDay()/1000.0;
    shaderProgram.bind();

    QVector3D lightColor(qSin(time*2.0f),qSin(time*0.7f),qSin(time*1.3f));
    QVector3D objectColor(1.0f,0.5f,0.31f);
    QVector3D lightPos(-4.0f,0.0f,0.5f);

    shaderProgram.setUniformValue("objectColor",objectColor);
    shaderProgram.setUniformValue("lightColor",lightColor);

    QMatrix4x4 model;
    shaderProgram.setUniformValue("model",model);
    shaderProgram.setUniformValue("view",camera.getView());

    shaderProgram.setUniformValue("viewPos", camera.getCameraPos());
 
    tex_diffuse.bind(0);
    shaderProgram.setUniformValue("material.diffuse",0);
    shaderProgram.setUniformValue("material.specular",0.5f,0.5f,0.5f);
    shaderProgram.setUniformValue("material.shininess", 32.0f);
 
    shaderProgram.setUniformValue("light.position", lightPos);
    shaderProgram.setUniformValue("light.ambient", QVector3D(0.2f, 0.2f, 0.2f));
    shaderProgram.setUniformValue("light.diffuse", QVector3D(0.5f, 0.5f, 0.5f));    // 将光照调暗了一些以搭配场景
    shaderProgram.setUniformValue("light.specular",1.0f, 1.0f, 1.0f);
 

    QMatrix4x4 projection;
    projection.perspective(45.0f,width()/(float)height(),0.1f,100.0f);
    shaderProgram.setUniformValue("projection",projection);
    QOpenGLVertexArrayObject::Binder{&VAO};
    this->glDrawArrays(GL_TRIANGLES, 0, 36);

    lampShader.bind();

    model.translate(lightPos);
    model.scale(0.2);

    lampShader.setUniformValue("model",model);
    lampShader.setUniformValue("view",camera.getView());
    lampShader.setUniformValue("projection",projection);

    QOpenGLVertexArrayObject::Binder{&lightVAO};
    this->glDrawArrays(GL_TRIANGLES, 0, 36);
}

bool Widget::event(QEvent *e)
{
    camera.handle(e);
    return QWidget::event(e);   //调用父类的事件分发函数
}

 

triangle.vert 

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    FragPos = vec3(model * vec4(aPos, 1.0));
    Normal = mat3(transpose(inverse(model))) * aNormal;
    TexCoords = aTexCoords;
}

triangle.frag

#version 330 core

out vec4 FragColor;

uniform vec3 objectColor;
uniform vec3 lightColor;

in vec3 FragPos;
in vec3 Normal;

uniform vec3 viewPos;

struct Material {
    sampler2D diffuse;
    vec3      specular;
    float     shininess;
};

in vec2 TexCoords;

uniform Material material;

struct Light {
    vec3 position;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

uniform Light light;

void main()
{
    // 环境光
    vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));

    // 漫反射
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(light.position - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));

    // 镜面光
    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 * material.specular);

    vec3 result = ambient + diffuse + specular;
    FragColor = vec4(result, 1.0);

}

镜面光贴图

你可能会注意到,镜面高光看起来有些奇怪,因为我们的物体大部分都是木头,我们知道木头不应该有这么强的镜面高光的。我们可以将物体的镜面光材质设置为vec3(0.0)来解决这个问题,但这也意味着箱子钢制的边框将不再能够显示镜面高光了,我们知道钢铁应该是有一些镜面高光的。所以,我们想要让物体的某些部分以不同的强度显示镜面高光。这个问题看起来和漫反射贴图非常相似。是巧合吗?我想不是。

我们同样可以使用一个专门用于镜面高光的纹理贴图。这也就意味着我们需要生成一个黑白的(如果你想得话也可以是彩色的)纹理,来定义物体每部分的镜面光强度。下面是一个镜面光贴图(Specular Map)的例子:

镜面高光的强度可以通过图像每个像素的亮度来获取。镜面光贴图上的每个像素都可以由一个颜色向量来表示,比如说黑色代表颜色向量vec3(0.0),灰色代表颜色向量vec3(0.5)。在片段着色器中,我们接下来会取样对应的颜色值并将它乘以光源的镜面强度。一个像素越「白」,乘积就会越大,物体的镜面光分量就会越亮。

由于箱子大部分都由木头所组成,而且木头材质应该没有镜面高光,所以漫反射纹理的整个木头部分全部都转换成了黑色。箱子钢制边框的镜面光强度是有细微变化的,钢铁本身会比较容易受到镜面高光的影响,而裂缝则不会。

 使用PhotoshopGimp之类的工具,将漫反射纹理转换为镜面光纹理还是比较容易的,只需要剪切掉一些部分,将图像转换为黑白的,并增加亮度/对比度就好了。

采样镜面光贴图

镜面光贴图和其它的纹理非常类似,所以代码也和漫反射贴图的代码很类似。记得要保证正确地加载图像并生成一个纹理对象。由于我们正在同一个片段着色器中使用另一个纹理采样器,我们必须要对镜面光贴图使用一个不同的纹理单元,所以我们在渲染之前先把它绑定到合适的纹理单元上。

我们的流程一般是:

创建纹理对象——构造函数中初始化——initializeGL中设置纹理的数据来源以及一些属性设置——paintGL中将纹理绑定到某个纹理单元——设置纹理采样器的数据来源——在析构函数中销毁纹理对象。

接下来更新片段着色器的材质属性,让其接受一个sampler2D而不是vec3作为镜面光分量:

struct Material {
    sampler2D diffuse;
    sampler2D specular;
    float     shininess;
};

最后我们希望采样镜面光贴图,来获取片段所对应的镜面光强度:

vec3 ambient  = light.ambient  * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse  = light.diffuse  * diff * vec3(texture(material.diffuse, TexCoords));  
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
FragColor = vec4(ambient + diffuse + specular, 1.0);

通过使用镜面光贴图我们可以可以对物体设置大量的细节,比如物体的哪些部分需要有闪闪发光的属性,我们甚至可以设置它们对应的强度。镜面光贴图能够在漫反射贴图之上给予我们更高一层的控制。

如果你现在运行程序的话,你可以清楚地看到箱子的材质现在和真实的钢制边框箱子非常类似了:

代码节点

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include "camera.h"

#include <QOpenGLWidget>
#include <QOpenGLExtraFunctions>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLTexture>
#include <QTimer>
#include <QTime>
#include <QtMath>
#include <QKeyEvent>
class Widget : public QOpenGLWidget,public QOpenGLExtraFunctions
{
    Q_OBJECT

public:
    Widget(QWidget *parent = 0);
    ~Widget();
protected:
    virtual void initializeGL() override;
    virtual void resizeGL(int w,int h) override;
    virtual void paintGL() override;

    virtual bool event(QEvent *e) override;

private:
    QVector<float> vertices;
    QVector<QVector3D> cubePositions;
    QOpenGLShaderProgram shaderProgram;
    QOpenGLShaderProgram lampShader;
    QOpenGLBuffer VBO;
    QOpenGLVertexArrayObject VAO;
    QOpenGLVertexArrayObject lightVAO;
    QTimer timer;
    QOpenGLTexture tex_diffuse;
    QOpenGLTexture tex_specular;
    Camera camera;
};

#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include <QtMath>

Widget::Widget(QWidget *parent)
    : QOpenGLWidget(parent)
    , VBO(QOpenGLBuffer::VertexBuffer)
    , tex_diffuse(QOpenGLTexture::Target2D)
    , tex_specular(QOpenGLTexture::Target2D)
    , camera(this)

{

    vertices = {
         //位置               //法向量               //纹理坐标
        -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
    };

    timer.setInterval(18);
    connect(&timer,&QTimer::timeout,this,static_cast<void (Widget::*)()>(&Widget::update));
    timer.start();

    QSurfaceFormat format;
    format.setSamples(20);
    setFormat(format);

}

Widget::~Widget()
{
    makeCurrent();
    tex_diffuse.destroy();
    tex_specular.destroy();
    doneCurrent();
}

void Widget::initializeGL()
{
    this->initializeOpenGLFunctions();        //初始化opengl函数
    if(!shaderProgram.addShaderFromSourceFile(QOpenGLShader::Vertex,":/triangle.vert")){     //添加并编译顶点着色器
        qDebug()<<"ERROR:"<<shaderProgram.log();    //如果编译出错,打印报错信息
    }
    if(!shaderProgram.addShaderFromSourceFile(QOpenGLShader::Fragment,":/triangle.frag")){   //添加并编译片段着色器
        qDebug()<<"ERROR:"<<shaderProgram.log();    //如果编译出错,打印报错信息
    }
    if(!shaderProgram.link()){                      //链接着色器
        qDebug()<<"ERROR:"<<shaderProgram.log();    //如果链接出错,打印报错信息
    }

    tex_diffuse.setData(QImage(":/container_diffuse.png"));
    tex_specular.setData(QImage(":/container_specular.png"));
    QOpenGLVertexArrayObject::Binder{&VAO};

    VBO.create();       //生成VBO对象
    VBO.bind();         //将VBO绑定到当前的顶点缓冲对象(QOpenGLBuffer::VertexBuffer)中

    //将顶点数据分配到VBO中,第一个参数为数据指针,第二个参数为数据的字节长度
    VBO.allocate(vertices.data(),sizeof(float)*vertices.size());



    //设置顶点解析格式,并启用顶点
    shaderProgram.setAttributeBuffer("aPos", GL_FLOAT, 0, 3, sizeof(GLfloat) * 8);
    shaderProgram.enableAttributeArray("aPos");
    shaderProgram.setAttributeBuffer("aNormal", GL_FLOAT, sizeof(GLfloat) * 3, 3, sizeof(GLfloat) * 8);
    shaderProgram.enableAttributeArray("aNormal");
    shaderProgram.setAttributeBuffer("aTexCoords", GL_FLOAT, sizeof(GLfloat) * 6, 2, sizeof(GLfloat) * 8);
    shaderProgram.enableAttributeArray("aTexCoords");

    if(!lampShader.addShaderFromSourceFile(QOpenGLShader::Vertex,":/triangle.vert")){     //添加并编译顶点着色器
        qDebug()<<"ERROR:"<<shaderProgram.log();    //如果编译出错,打印报错信息
    }
    if(!lampShader.addShaderFromSourceFile(QOpenGLShader::Fragment,":/lamp.frag")){   //添加并编译片段着色器
        qDebug()<<"ERROR:"<<shaderProgram.log();    //如果编译出错,打印报错信息
    }
    if(!lampShader.link()){                      //链接着色器
        qDebug()<<"ERROR:"<<shaderProgram.log();    //如果链接出错,打印报错信息
    }

    QOpenGLVertexArrayObject::Binder{&lightVAO};
    VBO.bind(); //只需要绑定VBO不用再次设置VBO的数据,因为箱子的VBO数据中已经包含了正确的立方体顶点数据
    lampShader.setAttributeBuffer("aPos", GL_FLOAT, 0, 3, sizeof(GLfloat) * 8);
    lampShader.enableAttributeArray("aPos");


    this->glEnable(GL_DEPTH_TEST);

    camera.init();

}

void Widget::resizeGL(int w, int h)
{
    this->glViewport(0,0,w,h);                  //定义视口区域
}

void Widget::paintGL()
{
    this->glClearColor(0.0f,0.0f,0.0f,1.0f);                       //设置清屏颜色
    this->glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT);       //清除颜色缓存和深度缓存
    float time=QTime::currentTime().msecsSinceStartOfDay()/1000.0;
    shaderProgram.bind();

    QVector3D lightColor(qSin(time*2.0f),qSin(time*0.7f),qSin(time*1.3f));
    QVector3D objectColor(1.0f,0.5f,0.31f);
    QVector3D lightPos(-4.0f,0.0f,0.5f);

    shaderProgram.setUniformValue("objectColor",objectColor);
    shaderProgram.setUniformValue("lightColor",lightColor);

    QMatrix4x4 model;
    shaderProgram.setUniformValue("model",model);
    shaderProgram.setUniformValue("view",camera.getView());

    shaderProgram.setUniformValue("viewPos", camera.getCameraPos());
 
    tex_diffuse.bind(0);
    shaderProgram.setUniformValue("material.diffuse",0);
    shaderProgram.setUniformValue("material.specular",0.5f,0.5f,0.5f);
    shaderProgram.setUniformValue("material.shininess", 32.0f);
 
    shaderProgram.setUniformValue("light.position", lightPos);
    shaderProgram.setUniformValue("light.ambient", QVector3D(0.2f, 0.2f, 0.2f));
    shaderProgram.setUniformValue("light.diffuse", QVector3D(0.5f, 0.5f, 0.5f));    // 将光照调暗了一些以搭配场景
    shaderProgram.setUniformValue("light.specular",1.0f, 1.0f, 1.0f);
 
 


    QMatrix4x4 projection;
    projection.perspective(45.0f,width()/(float)height(),0.1f,100.0f);
    shaderProgram.setUniformValue("projection",projection);
    QOpenGLVertexArrayObject::Binder{&VAO};
    this->glDrawArrays(GL_TRIANGLES, 0, 36);

    lampShader.bind();

    model.translate(lightPos);
    model.scale(0.2);

    lampShader.setUniformValue("model",model);
    lampShader.setUniformValue("view",camera.getView());
    lampShader.setUniformValue("projection",projection);

    QOpenGLVertexArrayObject::Binder{&lightVAO};
    this->glDrawArrays(GL_TRIANGLES, 0, 36);
}

bool Widget::event(QEvent *e)
{
    camera.handle(e);
    return QWidget::event(e);   //调用父类的事件分发函数
}

triangle.vert

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    FragPos = vec3(model * vec4(aPos, 1.0));
    Normal = mat3(transpose(inverse(model))) * aNormal;
    TexCoords = aTexCoords;
}

triangle.frag

#version 330 core

out vec4 FragColor;

uniform vec3 objectColor;
uniform vec3 lightColor;

in vec3 FragPos;
in vec3 Normal;

uniform vec3 viewPos;

struct Material {
    sampler2D diffuse;
    sampler2D specular;
    float     shininess;
};

in vec2 TexCoords;

uniform Material material;

struct Light {
    vec3 position;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

uniform Light light;

void main()
{
    // 环境光
    vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));

    // 漫反射
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(light.position - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse  = light.diffuse  * diff * vec3(texture(material.diffuse, TexCoords));

    // 镜面光
    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 * vec3(texture(material.specular, TexCoords));

    vec3 result = ambient + diffuse + specular;
    FragColor = vec4(result, 1.0);

}

lamp.frag

#version 330 core
out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0); // 将向量的四个分量全部设置为1.0
}

container_diffuse.png

 container_specular.png

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 《使用PyTorch和scikit-learn进行机器学习》是一本以PyTorch和scikit-learn为基础的机器学习教程,旨在帮助读者学习并应用这两个流行的机器学习工具。该教程深入介绍了PyTorch和scikit-learn的基本概念和用法,让读者能够通过实际的编程实例掌握这些工具的使用。 PyTorch是一个基于Python的机器学习框架,它提供了丰富的工具和函数库,用于构建和训练神经网络模型。这本教程通过一系列的章节,从最基本的张量操作开始,逐步引导读者了解如何创建和训练神经网络。同时,它还介绍了PyTorch的自动微分功能,以及在实际项目中如何应用PyTorch。 scikit-learn是一个功能强大的Python机器学习库,提供了大量的机器学习算法和工具,用于数据处理、特征选择、模型训练和评估等。这本教程还介绍了scikit-learn的常用函数和工具,包括数据预处理、特征提取、模型选择和评估等。读者可以通过实践项目,学习如何使用scikit-learn解决实际的机器学习问题。 总之,《使用PyTorch和scikit-learn进行机器学习》是一本实用、易懂的教程,适合想要学习和应用PyTorch和scikit-learn的机器学习爱好者和从业者。阅读本教程,你将能够了解和掌握这两个强大的机器学习工具,为实际项目应用提供坚实的基础。 ### 回答2: "Machine Learning with PyTorch and Scikit-Learn PDF" 是一本探讨使用PyTorch和Scikit-Learn进行机器学习的书籍,这本书以PDF形式提供。 PyTorch 是一个开源的深度学习框架,它提供了丰富的工具和库来进行神经网络的训练和推理。Scikit-Learn 是一个强大的机器学习库,提供了各种机器学习算法的实现和工具。 这本书将通过结合PyTorch和Scikit-Learn,帮助读者了解如何使用这两个工具进行机器学习任务。它从介绍机器学习的基本概念和算法开始,然后介绍了PyTorch和Scikit-Learn的基本用法和功能。 在书的早期,读者将学习如何使用Scikit-Learn完成一些常见的机器学习任务,例如分类、回归和聚类等。然后,书籍将介绍如何使用PyTorch来构建和训练深度神经网络,并将其用于解决机器学习问题。 书的重点是教授读者如何使用PyTorch和Scikit-Learn来实现机器学习算法和模型,以及如何评估和优化它们的性能。它将提供一些示例代码和实际项目,帮助读者更好地理解和应用所学知识。 总的来说,"Machine Learning with PyTorch and Scikit-Learn PDF" 是一本适合初学者和有经验的机器学习从业者的书籍,它将帮助读者掌握使用PyTorch和Scikit-Learn进行机器学习的基本原理和技巧。 ### 回答3: Machine Learning with PyTorch and Scikit-Learn PDF 是一本介绍使用 PyTorch 和 Scikit-Learn 进行机器学习的书籍。 PyTorch 是一个基于 Python 的开源机器学习库,具有很强的灵活性和高性能。它提供了丰富的工具和函数,用于构建神经网络和深度学习模型。通过PyTorch,我们可以轻松地搭建、训练和部署各种复杂的机器学习模型。 Scikit-Learn 是另一个流行的 Python 机器学习库,它提供了大量的机器学习算法和便捷的工具,以简化机器学习任务的开发过程。Scikit-Learn 中包含了许多常用的机器学习模型,例如分类器、回归模型、聚类算法等。通过Scikit-Learn,我们可以快速地创建、训练和评估各种机器学习模型。 "Machine Learning with PyTorch and Scikit-Learn PDF" 这本书会教读者如何使用 PyTorch 和 Scikit-Learn 这两个库进行机器学习任务。这本书可能会包含以下内容: 1. 介绍 PyTorch 和 Scikit-Learn 的基本概念和用法。读者可以了解如何安装和配置这两个库,并学习如何使用它们的基本功能。 2. 介绍机器学习的基本概念和原理。读者可以了解常见的机器学习算法和技术,以及如何在 PyTorch 和 Scikit-Learn 中应用它们。 3. 提供示例代码和案例研究。读者可以学习如何使用 PyTorch 和 Scikit-Learn 解决实际的机器学习问题,比如图像分类、文本分类、回归分析等。 4. 讨论如何有效地训练和优化机器学习模型。读者可以了解如何选择合适的数据预处理方法、模型选择、超参数调优等技巧,以提高模型的性能和准确性。 总而言之,"Machine Learning with PyTorch and Scikit-Learn PDF" 这本书将帮助读者掌握使用 PyTorch 和 Scikit-Learn 进行机器学习任务的基本知识和技能。无论是初学者还是有一定经验的开发者,都可以通过这本书更好地理解和应用机器学习。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值