• 友链

  • 首页

  • 文章归档
h u a n b l o g
h u a n b l o g

欢

HI,Friend

05月
22
Shader
C++

OpenGL笔记10-光照贴图

发表于 2024-05-22 • 字数统计 22728 • 被 2,328 人看爆

序言

以glew、glfw库
OpenGL学习网站
glfw官网
OpenGL-API文档
glew官网

光照贴图

前面的OpenGL笔记9-材质教程中我们将一个物体自身作为一个整体为其定义了一个材质,但是现实世界的物体通常不会只有这么一种材质,而是由多种材质组成。想象一辆车:它的外表质地光亮,车窗会部分反射环境,它的轮胎没有specular高光,轮彀却非常闪亮(在洗过之后)。汽车同样有diffuse和ambient颜色,它们在整个车上都不相同;一辆车显示了多种不同的ambient/diffuse颜色。总之,这样一个物体每个部分都有多种材质属性。

所以,前面的材质系统对于除了最简单的模型以外都是不够的,所以我们需要扩展前面的系统,我们要介绍diffuse和specular贴图。它们允许你对一个物体的diffuse(而对于简洁的ambient成分来说,它们几乎总是是一样的)和specular成分能够有更精确的影响。

漫反射贴图

我们希望通过某种方式对每个原始像素独立设置diffuse颜色。有可以让我们基于物体原始像素的位置来获取颜色值的系统吗?

这可能听起来极其相似,坦白来讲我们使用这样的系统已经有一段时间了。听起来很像在一个之前的OpenG笔记4-纹理中谈论的纹理,它基本就是一个纹理。我们其实是使用同一个潜在原则下的不同名称:使用一张图片覆盖住物体,以便我们为每个原始像素索引独立颜色值 。在光照场景中,通过纹理来呈现一个物体的diffuse颜色,这个做法被称做漫反射贴图(Diffuse texture)(因为3D建模师就是这么称呼这个做法的)。

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

在着色器中使用漫反射贴图和纹理教程介绍的一样。这次我们把纹理以sampler2D类型储存在Material结构体中。我们使用diffuse贴图替代早期定义的vec3类型的diffuse颜色。

要记住的是sampler2D也叫做模糊类型,这意味着我们不能以某种类型对它实例化,只能用uniform定义它们。如果我们用结构体而不是uniform实例化(就像函数的参数那样),GLSL会抛出奇怪的错误;这同样也适用于其他模糊类型。

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

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

如果你非把ambient颜色设置为不同的值不可(不同于diffuse值),你可以继续保留ambient的vec3,但是整个物体的ambient颜色会继续保持不变。为了使每个原始像素得到不同ambient值,你需要对ambient值单独使用另一个纹理。

注意,在片段着色器中我们将会再次需要纹理坐标,所以我们声明一个额外输入变量。然后我们简单地从纹理采样,来获得原始像素的diffuse颜色值:

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

同样,不要忘记把ambient材质的颜色设置为diffuse材质的颜色:

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

这就是diffuse贴图的全部内容了。就像你看到的,这不是什么新的东西,但是它却极大提升了视觉品质。为了让它工作,我们需要用到纹理坐标更新顶点数据,把它们作为顶点属性传递到片段着色器,把纹理加载并绑定到合适的纹理单元。

顶点数据现在包括了顶点位置,法线向量和纹理坐标,每个立方体的顶点都有这些属性。让我们更新顶点着色器来接受纹理坐标作为顶点属性,然后发送到片段着色器:

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 texCoords;
...
out vec2 TexCoords;

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

要保证更新的顶点属性指针,不仅是VAO匹配新的顶点数据,也要把箱子图片加载为纹理。在绘制箱子之前,我们希望首选纹理单元被赋为material.diffuse这个uniform采样器,并绑定箱子的纹理到这个纹理单元:

glUniform1i(glGetUniformLocation(lightingShader.Program, "material.diffuse"), 0);
...
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, diffuseMap);

现在,使用一个diffuse贴图,我们在细节上再次获得惊人的提升,这次添加到箱子上的光照开始闪光了(名符其实)。你的箱子现在可能看起来像这样:
漫反射贴图效果.png

完整代码

#include <glew.h>
#include <glfw3.h>
#include <iostream>
#include <SOIL2.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

//顶点着色器
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 position;\n"
"layout (location = 1) in vec3 normal;\n"
"layout (location = 2) in vec2 texCoords;\n"

"out vec3 Normal;\n"            //法向量
"out vec3 FragPos;\n"           //片段位置(如立方体六个面,每个面两个三角形,三角形形成的那个片段)
"out vec2 TexCoords;\n"

"uniform mat4 model;\n"
"uniform mat4 view;\n"
"uniform mat4 projection;\n"

"void main()\n"
"{\n"
"   gl_Position = projection * view * model * vec4(position, 1.0f);\n"       //数学库矩阵转换
"   FragPos = vec3(model * vec4(position, 1.0f));\n"               //片段位置,将片段位置转化为世界坐标
"   Normal = normal;\n"
"   TexCoords = texCoords;\n"
"}\n";

//片段着色器
const char* fragmentShaderSource = "#version 330 core\n"
"in vec3 Normal;\n"
"in vec3 FragPos;\n"
"in vec2 TexCoords;\n"

"out vec4 color;\n"

"uniform vec3 objectColor;\n"
"uniform vec3 lightColor;\n"
"uniform vec3 lightPos;\n"
"uniform vec3 viewPos;\n"           //摄像机位置

//材质属性
"struct Material\n"
"{\n"
"     sampler2D diffuse;\n"     //在漫反射光照下物体的颜色
"     vec3 specular;\n"         //受到镜面反射的颜色
"     float shininess;\n"       //反射半径
"};\n"
"uniform Material material;\n"

//光强度
"struct Light\n"
"{\n"
"     vec3 position;\n"
"     vec3 ambient;\n"          //环境光强度
"     vec3 diffuse;\n"          //漫反射强度
"     vec3 specular;\n"         //镜面反射强度
"};\n"

"uniform Light light;\n"

"void main()\n"
"{\n"

      //环境光
"     vec3 ambient = vec3(texture(material.diffuse, TexCoords)) * light.ambient;\n"       //环境光

      //漫反射
"     vec3 norm = normalize(Normal);\n"         //法向量单位化
"     vec3 lightDir = normalize(lightPos - FragPos);\n"        //计算光的方向,即方向向量  指向光源方向lightPos, 朝向片段方向FragPos
"     float diff = max(dot(norm, lightDir), 0.0);\n"           //计算光对当前片段的实际散射影响
"     vec3 diffuse = diff * vec3(texture(material.diffuse, TexCoords)) * light.diffuse;\n"          //得到散射因子

      //镜面高光
"     vec3 viewDir = normalize(viewPos - FragPos);\n"     //指向摄像机viewPos,朝向FragPos
"     vec3 reflectDir = reflect(-lightDir, norm);\n"      //反射的方向
"     float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);\n"
"     vec3 specular = (material.specular * spec) * light.specular;\n"  //计算镜面分量

      //光照
"     vec3 result = ambient + diffuse + specular;\n"   //冯氏光照模型 = 环境光 + 漫反射 + 镜面反射
"     color = vec4(result, 1.0f) ;\n"
"}\n";


//光源-顶点着色器
const char* lightVertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 position;\n"

"uniform mat4 model;\n"
"uniform mat4 view;\n"
"uniform mat4 projection;\n"

"void main()\n"
"{\n"
"   gl_Position = projection * view * model * vec4(position, 1.0f);\n"       //数学库矩阵转换
"}\n";

//光源-片段着色器
const char* lightFragmentShaderSource = "#version 330 core\n"
"out vec4 color;\n"

"void main()\n"
"{\n"
"     color = vec4(1.0f);\n"
"}\n";

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void do_movement();

const GLuint WIDTH = 800, HEIGHT = 600;

//摄像机信息
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);          //摄像机位置
glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);       //摄像机朝向
glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);           //上向量

GLfloat deltaTime = 0.0f;   //当前帧遇上一帧的时间差
GLfloat lastFrame = 0.0f;   //上一帧的时间

GLfloat lastX = WIDTH / 2, lastY = HEIGHT / 2;       //鼠标上一次位置,默认屏幕中心
GLfloat pitch = 0.0f;       //俯仰角
GLfloat yaw = -90.0f;       //偏航角
GLfloat aspect = 45.0f;     //视角大小
glm::vec3 lightPos(1.0f, 1.0f, 2.0f);

bool keys[1024];

int main()
{
    glfwInit();     //必须要将glfw初始化
    //告诉GLFW使用OpenGL版本
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);  //主版本号
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); //次版本号
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);  //使用的是OpenGL核心模式
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);   //不允许调整窗口大小

    //创建窗口
    GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", nullptr, nullptr);
    if (window == nullptr) {
        std::cout << "Failed to create GLFW Window" << std::endl;

        glfwTerminate();    //销毁窗口与数据

        return -1;
    }

    glfwMakeContextCurrent(window);     //将OpenGL指向为当前窗口

    glewExperimental = GL_TRUE;     //用于告知GLEW使用现化OpenGL技术

    //glew初始化
    if (glewInit() != GLEW_OK) {
        std::cout << "Failed to initialize GLEW" << std::endl;

        return -1;
    }

    //视口
    int width = 800, height = 600;
    glfwGetFramebufferSize(window, &width, &height);        //设置OpenGL渲染窗口的尺寸
    glViewport(0, 0, width, height);    //设置窗口的维度 前两个参数控制窗口左下角的位置, 第三、四个参数控制渲染窗口的宽度和高度
    glfwSetKeyCallback(window, key_callback);       //注册按键回调事件
    glfwSetCursorPosCallback(window, mouse_callback);       //注册鼠标回调事件
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);        //隐藏鼠标,并捕获他
    glfwSetScrollCallback(window, scroll_callback);                 //鼠标滚动回调事件

    glEnable(GL_DEPTH_TEST);            //深度缓存区

    //顶点着色器
    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr);
    glCompileShader(vertexShader);

    GLint vertexSuccess;
    GLchar vertexInfoLog[512];

    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &vertexSuccess);
    if (!vertexSuccess) {
        glGetShaderInfoLog(vertexShader, 512, nullptr, vertexInfoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << vertexInfoLog << std::endl;
    }

    //片段着色器
    GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);
    glCompileShader(fragmentShader);

    GLint fragmentSuccess;
    GLchar fragmentInfoLog[512];

    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &fragmentSuccess);
    if (!fragmentSuccess) {
        glGetShaderInfoLog(fragmentShader, 512, nullptr, fragmentInfoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << fragmentInfoLog << std::endl;
    }


    //着色器链接程序
    GLuint shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);

    GLint programSuccess;
    GLchar programInfoLog[512];
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &programSuccess);
    if (!programSuccess) {
        glGetProgramInfoLog(shaderProgram, 512, nullptr, programInfoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << programInfoLog << std::endl;
    }

    //光源-顶点着色器
    GLuint lightVertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(lightVertexShader, 1, &lightVertexShaderSource, nullptr);
    glCompileShader(lightVertexShader);

    GLint lightVertexSuccess;
    GLchar lightVertexInfoLog[512];

    glGetShaderiv(lightVertexShader, GL_COMPILE_STATUS, &lightVertexSuccess);
    if (!vertexSuccess) {
        glGetShaderInfoLog(lightVertexShader, 512, nullptr, lightVertexInfoLog);
        std::cout << "ERROR::SHADER::LIGHT::VERTEX::COMPILATION_FAILED\n" << lightVertexInfoLog << std::endl;
    }

    //片段着色器
    GLuint lightFragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(lightFragmentShader, 1, &lightFragmentShaderSource, nullptr);
    glCompileShader(lightFragmentShader);

    GLint lightFragmentSuccess;
    GLchar lightFragmentInfoLog[512];

    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &lightFragmentSuccess);
    if (!fragmentSuccess) {
        glGetShaderInfoLog(fragmentShader, 512, nullptr, lightFragmentInfoLog);
        std::cout << "ERROR::SHADER::LIGHT::FRAGMENT::COMPILATION_FAILED\n" << lightFragmentInfoLog << std::endl;
    }


    //光源着色器链接程序
    GLuint lightShaderProgram = glCreateProgram();
    glAttachShader(lightShaderProgram, lightVertexShader);
    glAttachShader(lightShaderProgram, lightFragmentShader);
    glLinkProgram(lightShaderProgram);

    GLint lightProgramSuccess;
    GLchar lightProgramInfoLog[512];
    glGetProgramiv(lightShaderProgram, GL_LINK_STATUS, &lightProgramSuccess);
    if (!programSuccess) {
        glGetProgramInfoLog(lightShaderProgram, 512, nullptr, lightProgramInfoLog);
        std::cout << "ERROR::SHADER::LIGHT::PROGRAM::LINKING_FAILED\n" << lightProgramInfoLog << std::endl;
    }


    GLfloat 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
    };

    //顶点数据
    GLuint VBO, VAO;
    glGenBuffers(1, &VBO);      //顶点缓冲对象
    glGenVertexArrays(1, &VAO); //顶点数组对象

    glBindVertexArray(VAO);

    //顶点数据
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    //顶点属性
    //顶点坐标
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (GLvoid*)0);
    glEnableVertexAttribArray(0);

    //法向量
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (GLvoid*)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(1);

    //纹理坐标
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (GLvoid*)(6 * sizeof(GLfloat)));
    glEnableVertexAttribArray(2);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    //光源
    GLuint lightVAO;
    glGenVertexArrays(1, &lightVAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBindVertexArray(lightVAO);

    //顶点属性
    //顶点坐标
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (GLvoid*)0);
    glEnableVertexAttribArray(0);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    //纹理
    GLuint texture;
    int textureWidht, textureHeight;
    //加载纹理
    unsigned char* image = SOIL_load_image("container2.png", &textureWidht, &textureHeight, 0, SOIL_LOAD_RGB);
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);

    //环绕方式-WRAP,默认环绕方式-重复纹理图形GL_REPEAT
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);       //对应X轴  
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);       //对应Y轴

    //过滤方式。缩小(GL_TEXTURE_MIN_FILTER)和放大(GL_TEXTURE_MAG_FILTER)都采用线性过滤(GL_LINEAR)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);       //缩小
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);       //放大

    //生成纹理
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, textureWidht, textureWidht, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
    glGenerateMipmap(GL_TEXTURE_2D);    //生成所有需要的多级渐远纹理

    //解绑
    SOIL_free_image_data(image);        //释放图像内存
    glBindTexture(GL_TEXTURE_2D, 0);


    while (!glfwWindowShouldClose(window)) {
        //检查GLFW是否退出,即窗口是否关闭了,true代表结束了

        glfwPollEvents();       //检查有没有事件发生(键盘输入、鼠标移动),如发生调用对应的回调函数  键盘事件:glfwSetKeyCallback(window, key_callback);  key_callback即设定的回调函数
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);       //清空屏幕所用的颜色,即清除颜色缓冲之后,整个颜色缓冲都会被填充为glClearColor里所设置的颜色。
        //glClear(GL_COLOR_BUFFER_BIT);       //清空屏幕缓冲,这里是颜色缓冲
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        do_movement();
        //渲染指令
        glUseProgram(shaderProgram);

        glm::mat4 model;        //模型矩阵
        glm::mat4 view;         //观察矩阵
        glm::mat4 projection;   //投影矩阵-透视投影
        view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
        projection = glm::perspective(aspect, (GLfloat)WIDTH / (GLfloat)HEIGHT, 0.1f, 100.0f);  //缩放效果

        //物体颜色和光颜色
        GLint objectColorLoc = glGetUniformLocation(shaderProgram, "objectColor");
        GLint lightColorLoc = glGetUniformLocation(shaderProgram, "lightColor");
        glUniform3f(objectColorLoc, 1.0f, 0.5f, 0.31f);// 我们所熟悉的珊瑚红
        glUniform3f(lightColorLoc, 1.0f, 1.0f, 1.0f); // 依旧把光源设置为白色

        //材质
        GLint matDiffuseLoc = glGetUniformLocation(shaderProgram, "material.diffuse");
        GLint matSpecularLoc = glGetUniformLocation(shaderProgram, "material.specular");
        GLint matShineLoc = glGetUniformLocation(shaderProgram, "material.shininess");

        glUniform1i(matDiffuseLoc, 0);
        glUniform3f(matSpecularLoc, 0.5f, 0.5f, 0.5f);
        glUniform1f(matShineLoc, 64.0f);

        //光强度
        GLint lightAmbientLoc = glGetUniformLocation(shaderProgram, "light.ambient");
        GLint lightDiffuseLoc = glGetUniformLocation(shaderProgram, "light.diffuse");
        GLint lightSpecularLoc = glGetUniformLocation(shaderProgram, "light.specular");

       /* glm::vec3 lightColor; lightColor.x = sin(glfwGetTime() * 2.0f);
        lightColor.y = sin(glfwGetTime() * 0.7f);
        lightColor.z = sin(glfwGetTime() * 1.3f);

        glm::vec3 diffuseColor = lightColor * glm::vec3(0.5f);
        glm::vec3 ambientColor = diffuseColor * glm::vec3(0.2f);

        glUniform3f(lightAmbientLoc, ambientColor.x, ambientColor.y, ambientColor.z);
        glUniform3f(lightDiffuseLoc, diffuseColor.x, diffuseColor.y, diffuseColor.z);*/

        glUniform3f(lightAmbientLoc, 0.2f, 0.2f, 0.2f);
        glUniform3f(lightDiffuseLoc, 0.5f, 0.5f, 0.5f);     //让我们把这个光调暗一点,这样会看起来更自然
        glUniform3f(lightSpecularLoc, 1.0f, 1.0f, 1.0f);

        GLint modelLoc = glGetUniformLocation(shaderProgram, "model");
        GLint viewLoc = glGetUniformLocation(shaderProgram, "view");
        GLint projLoc = glGetUniformLocation(shaderProgram, "projection");
        GLint lightPosLoc = glGetUniformLocation(shaderProgram, "lightPos");
        GLint viewPosLoc = glGetUniformLocation(shaderProgram, "viewPos");

        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));
        glUniform3f(lightPosLoc, lightPos.x, lightPos.y, lightPos.z);
        glUniform3f(viewPosLoc, cameraPos.x, cameraPos.y, cameraPos.z);

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture);

        glBindVertexArray(VAO);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);

        //光源
        glUseProgram(lightShaderProgram);       //光源
        modelLoc = glGetUniformLocation(lightShaderProgram, "model");
        viewLoc = glGetUniformLocation(lightShaderProgram, "view");
        projLoc = glGetUniformLocation(lightShaderProgram, "projection");
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));

        glBindVertexArray(lightVAO);
        model = glm::translate(model, lightPos);
        model = glm::scale(model, glm::vec3(0.2f));
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);

        glfwSwapBuffers(window);  //交换颜色缓冲,用来绘制,输出显示在屏幕上
    }

    glDeleteBuffers(1, &VBO);
    glDeleteVertexArrays(1, &VAO);

    glfwTerminate();
    return 0;
}

/*
* 按键回调事件
* @param window 窗口
* @param key 按键
* @param scancode 扫描码
* @param action 表示这个按键是被按下还是释放
* @param mode 是否有Ctrl、Shift、Alt、Super等按钮的操作
*/
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode) {

    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
        //esc键按下,关闭窗口
        glfwSetWindowShouldClose(window, GL_TRUE);
    }

    if (action == GLFW_PRESS)   //按下
        keys[key] = true;
    else if (action == GLFW_RELEASE)    //松开
        keys[key] = false;

}

void do_movement() {
    GLfloat currentFrame = glfwGetTime();
    deltaTime = currentFrame - lastFrame;
    lastFrame = currentFrame;
    GLfloat cameraSpeed = 5.0f * deltaTime;    //速度

    if (keys[GLFW_KEY_W])
        cameraPos += cameraSpeed * cameraFront;
    if (keys[GLFW_KEY_S])
        cameraPos -= cameraSpeed * cameraFront;
    if (keys[GLFW_KEY_A])
        cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
    if (keys[GLFW_KEY_D])
        cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
    if (keys[GLFW_KEY_SPACE])       //向上
        cameraPos += cameraSpeed * cameraUp;
}

bool firstMouse = true;     //除了第一次鼠标移动外,其他不能移动
/*
* 鼠标回调事件
* @param window 窗口
* @param xpos 鼠标x坐标
* @param ypos 鼠标y坐标
*/
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    GLfloat xoffset = xpos - lastX;
    GLfloat yoffset = lastY - ypos;
    lastX = xpos;
    lastY = ypos;

    GLfloat sensitivity = 0.05;         //鼠标转向速度
    xoffset *= sensitivity;
    yoffset *= sensitivity;

    yaw += xoffset;
    pitch += yoffset;

    if (pitch > 89.0f)
        pitch = 89.0f;
    if (pitch < -89.0f)
        pitch = -89.0f;

    glm::vec3 front;
    front.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
    front.y = sin(glm::radians(pitch));
    front.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
    cameraFront = glm::normalize(front);
}

/*
* 鼠标滚轮回调事件
*/
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    if (aspect >= 1.0f && aspect <= 45.0f)
        aspect -= yoffset;
    if (aspect <= 1.0f)
        aspect = 1.0f;
    if (aspect >= 45.0f)
        aspect = 45.0f;
}

镜面贴图

你可能注意到,specular高光看起来不怎么样,由于我们的物体是个箱子,大部分是木头,我们知道木头是不应该有镜面高光的。我们通过把物体设置specular材质设置为vec3(0.0f)来修正它。但是这样意味着铁边会不再显示镜面高光,我们知道钢铁是会显示一些镜面高光的。我们会想要控制物体部分地显示镜面高光,它带有修改了的亮度。这个问题看起来和diffuse贴图的讨论一样。这是巧合吗?我想不是。

我们同样用一个纹理贴图,来获得镜面高光。这意味着我们需要生成一个黑白(或者你喜欢的颜色)纹理来定义specular亮度,把它应用到物体的每个部分。下面是一个镜面贴图(Specular Map)的例子:
container2_specular.png

一个specular高光的亮度可以通过图片中每个纹理的亮度来获得。specular贴图的每个像素可以显示为一个颜色向量,比如:在那里黑色代表颜色向量vec3(0.0f),灰色是vec3(0.5f)。在片段着色器中,我们采样相应的颜色值,把它乘以光的specular亮度。像素越“白”,乘积的结果越大,物体的specualr部分越亮。

由于箱子几乎是由木头组成,木头作为一个材质不会有镜面高光,整个木头部分的diffuse纹理被用黑色覆盖:黑色部分不会包含任何specular高光。箱子的铁边有一个修改的specular亮度,它自身更容易受到镜面高光影响,木纹部分则不会。

从技术上来讲,木头也有镜面高光,尽管这个闪亮值很小(更多的光被散射),影响很小,但是为了学习目的,我们可以假装木头不会有任何specular光反射。

使用Photoshop或Gimp之类的工具,通过将图片进行裁剪,将某部分调整成黑白图样,并调整亮度/对比度的做法,可以非常容易将一个diffuse纹理贴图处理为specular贴图。

镜面贴图采样

一个specular贴图和其他纹理一样,所以代码和diffuse贴图的代码也相似。确保合理的加载了图片,生成一个纹理对象。由于我们在同样的片段着色器中使用另一个纹理采样器,我们必须为specular贴图使用一个不同的纹理单元(参见OpenGL笔记4-纹理),所以在渲染前让我们把它绑定到合适的纹理单元:

glUniform1i(glGetUniformLocation(lightingShader.Program, "material.specular"), 1);
...
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, specularMap);

然后更新片段着色器材质属性,接受一个sampler2D作为这个specular部分的类型,而不是vec3:

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

最后我们希望采样这个specular贴图,来获取原始像素相应的specular亮度:

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));
color = vec4(ambient + diffuse + specular, 1.0f);

通过使用一个specular贴图我们可以定义极为精细的细节,物体的这个部分会获得闪亮的属性,我们可以设置它们相应的亮度。specular贴图给我们一个附加的高于diffuse贴图的控制权限。

如果你不想成为主流,你可以在specular贴图里使用颜色,不单单为每个原始像素设置specular亮度,同时也设置specular高光的颜色。从真实角度来说,specular的颜色基本是由光源自身决定的,所以它不会生成真实的图像(这就是为什么图片通常是黑色和白色的:我们只关心亮度)。

如果你现在运行应用,你可以清晰地看到箱子的材质现在非常类似真实的铁边的木头箱子了:
镜面反射贴图效果.png

完整代码

#include <glew.h>
#include <glfw3.h>
#include <iostream>
#include <SOIL2.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

//顶点着色器
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 position;\n"
"layout (location = 1) in vec3 normal;\n"
"layout (location = 2) in vec2 texCoords;\n"

"out vec3 Normal;\n"            //法向量
"out vec3 FragPos;\n"           //片段位置(如立方体六个面,每个面两个三角形,三角形形成的那个片段)
"out vec2 TexCoords;\n"

"uniform mat4 model;\n"
"uniform mat4 view;\n"
"uniform mat4 projection;\n"

"void main()\n"
"{\n"
"   gl_Position = projection * view * model * vec4(position, 1.0f);\n"       //数学库矩阵转换
"   FragPos = vec3(model * vec4(position, 1.0f));\n"               //片段位置,将片段位置转化为世界坐标
"   Normal = normal;\n"
"   TexCoords = texCoords;\n"
"}\n";

//片段着色器
const char* fragmentShaderSource = "#version 330 core\n"
"in vec3 Normal;\n"
"in vec3 FragPos;\n"
"in vec2 TexCoords;\n"

"out vec4 color;\n"

"uniform vec3 objectColor;\n"
"uniform vec3 lightColor;\n"
"uniform vec3 lightPos;\n"
"uniform vec3 viewPos;\n"           //摄像机位置

//材质属性
"struct Material\n"
"{\n"
"     sampler2D diffuse;\n"     //在漫反射光照下物体的颜色
"     sampler2D specular;\n"    //受到镜面反射的颜色
"     float shininess;\n"       //反射半径
"};\n"
"uniform Material material;\n"

//光强度
"struct Light\n"
"{\n"
"     vec3 position;\n"
"     vec3 ambient;\n"          //环境光强度
"     vec3 diffuse;\n"          //漫反射强度
"     vec3 specular;\n"         //镜面反射强度
"};\n"

"uniform Light light;\n"

"void main()\n"
"{\n"

      //环境光
"     vec3 ambient = vec3(texture(material.diffuse, TexCoords)) * light.ambient;\n"       //环境光

      //漫反射
"     vec3 norm = normalize(Normal);\n"         //法向量单位化
"     vec3 lightDir = normalize(lightPos - FragPos);\n"        //计算光的方向,即方向向量  指向光源方向lightPos, 朝向片段方向FragPos
"     float diff = max(dot(norm, lightDir), 0.0);\n"           //计算光对当前片段的实际散射影响
"     vec3 diffuse = diff * vec3(texture(material.diffuse, TexCoords)) * light.diffuse;\n"          //得到散射因子

      //镜面高光
"     vec3 viewDir = normalize(viewPos - FragPos);\n"     //指向摄像机viewPos,朝向FragPos
"     vec3 reflectDir = reflect(-lightDir, norm);\n"      //反射的方向
"     float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);\n"
"     vec3 specular = vec3(texture(material.specular, TexCoords)) * spec * light.specular;\n"  //计算镜面分量

      //光照
"     vec3 result = ambient + diffuse + specular;\n"   //冯氏光照模型 = 环境光 + 漫反射 + 镜面反射
"     color = vec4(result, 1.0f) ;\n"
"}\n";


//光源-顶点着色器
const char* lightVertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 position;\n"

"uniform mat4 model;\n"
"uniform mat4 view;\n"
"uniform mat4 projection;\n"

"void main()\n"
"{\n"
"   gl_Position = projection * view * model * vec4(position, 1.0f);\n"       //数学库矩阵转换
"}\n";

//光源-片段着色器
const char* lightFragmentShaderSource = "#version 330 core\n"
"out vec4 color;\n"

"void main()\n"
"{\n"
"     color = vec4(1.0f);\n"
"}\n";

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void do_movement();

const GLuint WIDTH = 800, HEIGHT = 600;

//摄像机信息
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);          //摄像机位置
glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);       //摄像机朝向
glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);           //上向量

GLfloat deltaTime = 0.0f;   //当前帧遇上一帧的时间差
GLfloat lastFrame = 0.0f;   //上一帧的时间

GLfloat lastX = WIDTH / 2, lastY = HEIGHT / 2;       //鼠标上一次位置,默认屏幕中心
GLfloat pitch = 0.0f;       //俯仰角
GLfloat yaw = -90.0f;       //偏航角
GLfloat aspect = 45.0f;     //视角大小
glm::vec3 lightPos(1.0f, 1.0f, 2.0f);

bool keys[1024];

int main()
{
    glfwInit();     //必须要将glfw初始化
    //告诉GLFW使用OpenGL版本
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);  //主版本号
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); //次版本号
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);  //使用的是OpenGL核心模式
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);   //不允许调整窗口大小

    //创建窗口
    GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", nullptr, nullptr);
    if (window == nullptr) {
        std::cout << "Failed to create GLFW Window" << std::endl;

        glfwTerminate();    //销毁窗口与数据

        return -1;
    }

    glfwMakeContextCurrent(window);     //将OpenGL指向为当前窗口

    glewExperimental = GL_TRUE;     //用于告知GLEW使用现化OpenGL技术

    //glew初始化
    if (glewInit() != GLEW_OK) {
        std::cout << "Failed to initialize GLEW" << std::endl;

        return -1;
    }

    //视口
    int width = 800, height = 600;
    glfwGetFramebufferSize(window, &width, &height);        //设置OpenGL渲染窗口的尺寸
    glViewport(0, 0, width, height);    //设置窗口的维度 前两个参数控制窗口左下角的位置, 第三、四个参数控制渲染窗口的宽度和高度
    glfwSetKeyCallback(window, key_callback);       //注册按键回调事件
    glfwSetCursorPosCallback(window, mouse_callback);       //注册鼠标回调事件
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);        //隐藏鼠标,并捕获他
    glfwSetScrollCallback(window, scroll_callback);                 //鼠标滚动回调事件

    glEnable(GL_DEPTH_TEST);            //深度缓存区

    //顶点着色器
    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr);
    glCompileShader(vertexShader);

    GLint vertexSuccess;
    GLchar vertexInfoLog[512];

    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &vertexSuccess);
    if (!vertexSuccess) {
        glGetShaderInfoLog(vertexShader, 512, nullptr, vertexInfoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << vertexInfoLog << std::endl;
    }

    //片段着色器
    GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);
    glCompileShader(fragmentShader);

    GLint fragmentSuccess;
    GLchar fragmentInfoLog[512];

    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &fragmentSuccess);
    if (!fragmentSuccess) {
        glGetShaderInfoLog(fragmentShader, 512, nullptr, fragmentInfoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << fragmentInfoLog << std::endl;
    }


    //着色器链接程序
    GLuint shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);

    GLint programSuccess;
    GLchar programInfoLog[512];
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &programSuccess);
    if (!programSuccess) {
        glGetProgramInfoLog(shaderProgram, 512, nullptr, programInfoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << programInfoLog << std::endl;
    }

    //光源-顶点着色器
    GLuint lightVertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(lightVertexShader, 1, &lightVertexShaderSource, nullptr);
    glCompileShader(lightVertexShader);

    GLint lightVertexSuccess;
    GLchar lightVertexInfoLog[512];

    glGetShaderiv(lightVertexShader, GL_COMPILE_STATUS, &lightVertexSuccess);
    if (!vertexSuccess) {
        glGetShaderInfoLog(lightVertexShader, 512, nullptr, lightVertexInfoLog);
        std::cout << "ERROR::SHADER::LIGHT::VERTEX::COMPILATION_FAILED\n" << lightVertexInfoLog << std::endl;
    }

    //片段着色器
    GLuint lightFragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(lightFragmentShader, 1, &lightFragmentShaderSource, nullptr);
    glCompileShader(lightFragmentShader);

    GLint lightFragmentSuccess;
    GLchar lightFragmentInfoLog[512];

    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &lightFragmentSuccess);
    if (!fragmentSuccess) {
        glGetShaderInfoLog(fragmentShader, 512, nullptr, lightFragmentInfoLog);
        std::cout << "ERROR::SHADER::LIGHT::FRAGMENT::COMPILATION_FAILED\n" << lightFragmentInfoLog << std::endl;
    }


    //光源着色器链接程序
    GLuint lightShaderProgram = glCreateProgram();
    glAttachShader(lightShaderProgram, lightVertexShader);
    glAttachShader(lightShaderProgram, lightFragmentShader);
    glLinkProgram(lightShaderProgram);

    GLint lightProgramSuccess;
    GLchar lightProgramInfoLog[512];
    glGetProgramiv(lightShaderProgram, GL_LINK_STATUS, &lightProgramSuccess);
    if (!programSuccess) {
        glGetProgramInfoLog(lightShaderProgram, 512, nullptr, lightProgramInfoLog);
        std::cout << "ERROR::SHADER::LIGHT::PROGRAM::LINKING_FAILED\n" << lightProgramInfoLog << std::endl;
    }


    GLfloat 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
    };

    //顶点数据
    GLuint VBO, VAO;
    glGenBuffers(1, &VBO);      //顶点缓冲对象
    glGenVertexArrays(1, &VAO); //顶点数组对象

    glBindVertexArray(VAO);

    //顶点数据
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    //顶点属性
    //顶点坐标
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (GLvoid*)0);
    glEnableVertexAttribArray(0);

    //法向量
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (GLvoid*)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(1);

    //纹理坐标
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (GLvoid*)(6 * sizeof(GLfloat)));
    glEnableVertexAttribArray(2);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    //光源
    GLuint lightVAO;
    glGenVertexArrays(1, &lightVAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBindVertexArray(lightVAO);

    //顶点属性
    //顶点坐标
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (GLvoid*)0);
    glEnableVertexAttribArray(0);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    //纹理
    GLuint texture;
    int textureWidht, textureHeight;
    //加载纹理
    unsigned char* image = SOIL_load_image("container2.png", &textureWidht, &textureHeight, 0, SOIL_LOAD_RGB);
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);

    //环绕方式-WRAP,默认环绕方式-重复纹理图形GL_REPEAT
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);       //对应X轴  
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);       //对应Y轴

    //过滤方式。缩小(GL_TEXTURE_MIN_FILTER)和放大(GL_TEXTURE_MAG_FILTER)都采用线性过滤(GL_LINEAR)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);       //缩小
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);       //放大

    //生成纹理
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, textureWidht, textureWidht, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
    glGenerateMipmap(GL_TEXTURE_2D);    //生成所有需要的多级渐远纹理

    //解绑
    SOIL_free_image_data(image);        //释放图像内存
    glBindTexture(GL_TEXTURE_2D, 0);

    GLuint texture2;
    //加载纹理
    image = SOIL_load_image("container2_specular.png", &textureWidht, &textureHeight, 0, SOIL_LOAD_RGB);
    glGenTextures(1, &texture2);
    glBindTexture(GL_TEXTURE_2D, texture2);

    //环绕方式-WRAP,默认环绕方式-重复纹理图形GL_REPEAT
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);       //对应X轴  
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);       //对应Y轴

    //过滤方式。缩小(GL_TEXTURE_MIN_FILTER)和放大(GL_TEXTURE_MAG_FILTER)都采用线性过滤(GL_LINEAR)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);       //缩小
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);       //放大

    //生成纹理
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, textureWidht, textureWidht, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
    glGenerateMipmap(GL_TEXTURE_2D);    //生成所有需要的多级渐远纹理

    //解绑
    SOIL_free_image_data(image);        //释放图像内存
    glBindTexture(GL_TEXTURE_2D, 0);


    while (!glfwWindowShouldClose(window)) {
        //检查GLFW是否退出,即窗口是否关闭了,true代表结束了

        glfwPollEvents();       //检查有没有事件发生(键盘输入、鼠标移动),如发生调用对应的回调函数  键盘事件:glfwSetKeyCallback(window, key_callback);  key_callback即设定的回调函数
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);       //清空屏幕所用的颜色,即清除颜色缓冲之后,整个颜色缓冲都会被填充为glClearColor里所设置的颜色。
        //glClear(GL_COLOR_BUFFER_BIT);       //清空屏幕缓冲,这里是颜色缓冲
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        do_movement();
        //渲染指令
        glUseProgram(shaderProgram);

        glm::mat4 model;        //模型矩阵
        glm::mat4 view;         //观察矩阵
        glm::mat4 projection;   //投影矩阵-透视投影
        view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
        projection = glm::perspective(aspect, (GLfloat)WIDTH / (GLfloat)HEIGHT, 0.1f, 100.0f);  //缩放效果

        //物体颜色和光颜色
        GLint objectColorLoc = glGetUniformLocation(shaderProgram, "objectColor");
        GLint lightColorLoc = glGetUniformLocation(shaderProgram, "lightColor");
        glUniform3f(objectColorLoc, 1.0f, 0.5f, 0.31f);// 我们所熟悉的珊瑚红
        glUniform3f(lightColorLoc, 1.0f, 1.0f, 1.0f); // 依旧把光源设置为白色

        //材质
        GLint matDiffuseLoc = glGetUniformLocation(shaderProgram, "material.diffuse");
        GLint matSpecularLoc = glGetUniformLocation(shaderProgram, "material.specular");
        GLint matShineLoc = glGetUniformLocation(shaderProgram, "material.shininess");

        glUniform1i(matDiffuseLoc, 0);
        glUniform1i(matSpecularLoc, 1);
        glUniform1f(matShineLoc, 64.0f);

        //光强度
        GLint lightAmbientLoc = glGetUniformLocation(shaderProgram, "light.ambient");
        GLint lightDiffuseLoc = glGetUniformLocation(shaderProgram, "light.diffuse");
        GLint lightSpecularLoc = glGetUniformLocation(shaderProgram, "light.specular");

       /* glm::vec3 lightColor; lightColor.x = sin(glfwGetTime() * 2.0f);
        lightColor.y = sin(glfwGetTime() * 0.7f);
        lightColor.z = sin(glfwGetTime() * 1.3f);

        glm::vec3 diffuseColor = lightColor * glm::vec3(0.5f);
        glm::vec3 ambientColor = diffuseColor * glm::vec3(0.2f);

        glUniform3f(lightAmbientLoc, ambientColor.x, ambientColor.y, ambientColor.z);
        glUniform3f(lightDiffuseLoc, diffuseColor.x, diffuseColor.y, diffuseColor.z);*/

        glUniform3f(lightAmbientLoc, 0.2f, 0.2f, 0.2f);
        glUniform3f(lightDiffuseLoc, 0.5f, 0.5f, 0.5f);     //让我们把这个光调暗一点,这样会看起来更自然
        glUniform3f(lightSpecularLoc, 1.0f, 1.0f, 1.0f);

        GLint modelLoc = glGetUniformLocation(shaderProgram, "model");
        GLint viewLoc = glGetUniformLocation(shaderProgram, "view");
        GLint projLoc = glGetUniformLocation(shaderProgram, "projection");
        GLint lightPosLoc = glGetUniformLocation(shaderProgram, "lightPos");
        GLint viewPosLoc = glGetUniformLocation(shaderProgram, "viewPos");

        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));
        glUniform3f(lightPosLoc, lightPos.x, lightPos.y, lightPos.z);
        glUniform3f(viewPosLoc, cameraPos.x, cameraPos.y, cameraPos.z);

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture2);

        glBindVertexArray(VAO);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);

        //光源
        glUseProgram(lightShaderProgram);       //光源
        modelLoc = glGetUniformLocation(lightShaderProgram, "model");
        viewLoc = glGetUniformLocation(lightShaderProgram, "view");
        projLoc = glGetUniformLocation(lightShaderProgram, "projection");
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));

        glBindVertexArray(lightVAO);
        model = glm::translate(model, lightPos);
        model = glm::scale(model, glm::vec3(0.2f));
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);

        glfwSwapBuffers(window);  //交换颜色缓冲,用来绘制,输出显示在屏幕上
    }

    glDeleteBuffers(1, &VBO);
    glDeleteVertexArrays(1, &VAO);

    glfwTerminate();
    return 0;
}

/*
* 按键回调事件
* @param window 窗口
* @param key 按键
* @param scancode 扫描码
* @param action 表示这个按键是被按下还是释放
* @param mode 是否有Ctrl、Shift、Alt、Super等按钮的操作
*/
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode) {

    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
        //esc键按下,关闭窗口
        glfwSetWindowShouldClose(window, GL_TRUE);
    }

    if (action == GLFW_PRESS)   //按下
        keys[key] = true;
    else if (action == GLFW_RELEASE)    //松开
        keys[key] = false;

}

void do_movement() {
    GLfloat currentFrame = glfwGetTime();
    deltaTime = currentFrame - lastFrame;
    lastFrame = currentFrame;
    GLfloat cameraSpeed = 5.0f * deltaTime;    //速度

    if (keys[GLFW_KEY_W])
        cameraPos += cameraSpeed * cameraFront;
    if (keys[GLFW_KEY_S])
        cameraPos -= cameraSpeed * cameraFront;
    if (keys[GLFW_KEY_A])
        cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
    if (keys[GLFW_KEY_D])
        cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
    if (keys[GLFW_KEY_SPACE])       //向上
        cameraPos += cameraSpeed * cameraUp;
}

bool firstMouse = true;     //除了第一次鼠标移动外,其他不能移动
/*
* 鼠标回调事件
* @param window 窗口
* @param xpos 鼠标x坐标
* @param ypos 鼠标y坐标
*/
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    GLfloat xoffset = xpos - lastX;
    GLfloat yoffset = lastY - ypos;
    lastX = xpos;
    lastY = ypos;

    GLfloat sensitivity = 0.05;         //鼠标转向速度
    xoffset *= sensitivity;
    yoffset *= sensitivity;

    yaw += xoffset;
    pitch += yoffset;

    if (pitch > 89.0f)
        pitch = 89.0f;
    if (pitch < -89.0f)
        pitch = -89.0f;

    glm::vec3 front;
    front.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
    front.y = sin(glm::radians(pitch));
    front.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
    cameraFront = glm::normalize(front);
}

/*
* 鼠标滚轮回调事件
*/
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    if (aspect >= 1.0f && aspect <= 45.0f)
        aspect -= yoffset;
    if (aspect <= 1.0f)
        aspect = 1.0f;
    if (aspect >= 45.0f)
        aspect = 45.0f;
}

原教程

光照贴图原教程

该教程源码

git地址

分享到:
OpenGL笔记11-投光物
OpenGL笔记9-材质
  • 文章目录
  • 站点概览
欢

网红 欢

你能抓到我么?

Email RSS
看爆 Top5
  • mac系统版本与Xcode版本有冲突 4,080次看爆
  • JAVA_HOME环境配置问题 3,730次看爆
  • AssetBundle使用 3,499次看爆
  • VSCode配置C++开发环境 3,257次看爆
  • Lua反射 3,133次看爆

Copyright © 2025 欢 粤ICP备2020105803号-1

由 Halo 强力驱动 · Theme by Sagiri · 站点地图