介绍

现实世界中的光照是极其复杂,难以计算的,因此OpenGL的光照使用的是简化的模型,其中一个模型被称为冯氏光照模型(Phong Lighting Model)。

冯氏光照模型的主要结构由三个分量组成:

  1. 环境(Ambient)光照
  2. 漫反射(Diffuse)光照
  3. 镜面(Specular)光照

环境光照

光的一大特点是,它可以向很多方向发散并反弹,所以现实环境周围通常总会有些光亮,对应的物体通常都会反射些微弱的光。
计算:

用一个很小的分量乘以光的颜色,最后乘以物体的颜色

片段着色器:

void main()
{
    //计算环境光照
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * lightColor;
    //片段最终颜色:乘以物体本身颜色
    vec3 result = ambient * objectColor;   
    FragColor = vec4(result, 1.0);
}

效果
image.png
(完整代码会在最后放出,先只介绍关键代码)

漫反射光照

漫反射光照使物体与光线方向越接近的片段能充光源处获得更多的亮度。

image.png

如果光线垂直于物体表面,这束光对物体的影响会最大化(译注:更亮)。

可以大致理解为

*片段的漫反射光照 = (光源坐标到片段坐标的向量与片段法向量的夹角) 光源颜色**

注意两个向量都要先归一化
为了计算漫反射光照我们需要知道这几个值:

  • 片段所在面的法向量
  • 光源的世界坐标
  • 片段的世界坐标

最后目前片段的颜色为: (环境光照值+漫反射光照值) * 片段自身颜色

光照的计算我们都放在片段着色器中来处理,所以在物体的顶点着色器我们把需要的变量都传递给片段着色器:

#version 330 core
layout(location = 0) in vec3 pos;
layout(location = 1) in vec3 nor;

//光照计算统一传入到片段着色器中计算
out vec3 normal;
out vec4 fragPos;       //片段世界坐标

uniform mat4 modelMat;
uniform mat4 viewMat;
uniform mat4 projectionMat;

void main(){
    gl_Position = projectionMat * viewMat * modelMat * vec4(pos, 1.0f);
    normal = nor;
    fragPos = modelMat * vec4(pos, 1.0);        //顶点坐标乘以模型矩阵转为世界坐标
}

片段着色器部分代码:

//计算漫反射光照
//转为单位向量
vec3 norm = normalize(normal);
vec3 lightDir = normalize(lightPos - vec3(fragPos));
float rad = max(dot(lightDir, norm), 0.0);          //光照方向与片段法向量夹角[0,PI/2]
vec3 diffuse = rad * lightColor;                            //漫反射光照

vec3 result = (ambient + diffuse)  * cubeColor;
fragColor = vec4(result, 1.0);

效果:

image.png

镜面光照

镜面光照决定于表面的反射特性。如果我们把物体表面设想为一面镜子,那么镜面光照最强的地方就是我们看到表面上反射光的地方。如下图:

image.png

为此我们还需要一个观察者坐标(即摄像机)。光的反射向量与观察方向之间夹角越小,镜面光照效果越强。
物体片段着色器部分代码:

//计算镜面光照
vec3 viewDir = normalize(viewPos - vec3(fragPos));          //观察者方向
vec3 reflectDir = reflect(-lightDir, norm);                         //光照经法向量折射方向
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);   //反光度,这里取点乘的32次幂
float specularStrength = 0.5;                                               //镜面强度
vec3 specular = spec * specularStrength * lightColor;       //镜面光照

vec3 result = (ambient + diffuse + specular)  * cubeColor;
fragColor = vec4(result, 1.0);

效果
image.png

距离对光照强度影响

如果我们让光源环绕物体,会看见每个面总是整面同时亮起,除了镜面光照外几乎没有色差:
GIF.gif

这是因为目前光照强度永远为1,与距离无关。
我们可以给光源设定有效距离,且在这个距离内逐渐衰弱。
用片段坐标-光源坐标得到的向量再求模长,就是光源到片段的距离了,再根据这个距离衰减光照强度即可。
物体的片段着色器:

//距离影响光照强度,暂时是我自己想的,不保证正确性
float enableDistance = 3.0f;    //有效光照距离
vec3 light2fragVec = lightPos - vec3(fragPos);
float distan = min(sqrt(light2fragVec.x * light2fragVec.x + light2fragVec.y * light2fragVec.y + light2fragVec.z * light2fragVec.z), enableDistance);
float strength = 1 - distan / enableDistance;

//光照远近先不影响环境光照了
vec3 result = ambient * cubeColor + (diffuse + specular)  * cubeColor * 1;
fragColor = vec4(result, 1.0);

现在的效果:
final.gif

完整代码

basicLightingMain.cpp

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include<GL/glew.h>
#include<GLFW/glfw3.h>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#include "../Util/MyOpenGLUtil.h"
#include "../Util/Shader.h"
#include "../Util/Camera.h"

GLFWwindow* window = nullptr;
int width = 800;
int height = 600;

float deltaTime = 0;
float lastTime = 0;
float mouseX = 0.0f, mouseY = 0.0f;         //鼠标坐标
float lastX = 0.0f, lastY = 0.0f;                       //上一帧的位置
float scrollYOffset = 0.0f;                             //滚轮未使用的偏移

glm::vec3 lightPos = glm::vec3(2.0, 0, 0);              //光源坐标
glm::vec3 cameraBasePos = glm::vec3(0.7, 1.0, 5.0);         //摄像机初始坐标

    //创建摄像机
Camera camera(cameraBasePos);

void Work1();           //一个白光光源,一个被照射的物体进行颜色相乘计算出反射颜色
void mouse_callback(GLFWwindow* window, double xPos, double yPos);          //鼠标移动回调函数
void scroll_callback(GLFWwindow* window, double xOffset, double yOffset);       //鼠标滚轮回调函数

int main() {
    Init(&window, &width, &height);
    Work1();

    return 0;
}

void Work1() {
    //创建顶点缓冲对象
    unsigned int VBO;
    glGenBuffers(1, &VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);     //绑定缓冲对象,设定缓冲类型
    //将顶点数据复制到缓冲的内存中,并指定显卡如何管理数据,这里指定为GL_STATIC_DRAW
    //使用带法向量的顶点数组(为每个顶点手动配置法向量)
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_cubeHaveNormal), vertices_cubeHaveNormal, GL_STATIC_DRAW);

    //创建VAO顶点数组对象
    unsigned int VAO;
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);
    //顶点
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);       //以顶点属性位置作为参数,启用顶点属性
    //法向量
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(sizeof(float) * 3));
    glEnableVertexAttribArray(1);       //以顶点属性位置作为参数,启用顶点属性

    //创建light的VAO
    unsigned int lightVAO;
    glGenVertexArrays(1, &lightVAO);
    glBindVertexArray(lightVAO);
    //顶点
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);       //以顶点属性位置作为参数,启用顶点属性
    glBindBuffer(GL_ARRAY_BUFFER, VBO);     //绑定缓冲对象,设定缓冲类型

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

    Shader cubeShader("cubew1.vert", "cubew1.frag");
    cubeShader.use();
    //创建模型矩阵,包含物体的位移、旋转、缩放的变换信息
    glm::mat4 modelMat, viewMat, projectionMat;
    //开启深度测试
    glEnable(GL_DEPTH_TEST);
    float xOffset = 0.0f, yOffset = 0.0f;           //鼠标本次偏移


    Shader lightShader("cubew1.vert", "lightw1.frag");
    //隐藏并捕获鼠标,且让鼠标保持在窗口内
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
    //当鼠标移动时,回调函数
    glfwSetCursorPosCallback(window, mouse_callback);
    //注册鼠标滚轮回调函数
    glfwSetScrollCallback(window, scroll_callback);

    while (glfwWindowShouldClose(window) == false) {
        //数值刷新
        float curTime = glfwGetTime();
        deltaTime = curTime - lastTime;
        lastTime = curTime;

        //让光源绕原点旋转
        float distance = 2.0f;
        float lightY = sin(glfwGetTime()) * distance;
        float lightZ = -(cos(glfwGetTime()) * distance);
        float lightX = sin(glfwGetTime()) * distance; 
        //lightPos.x = lightX; lightPos.y = 1; lightPos.z = 1.2;                        //直线移动
        lightPos.x = lightX; lightPos.y = lightY; lightPos.z = lightZ;          //环绕移动


        processInput(window);
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        //清除上一帧的颜色缓冲和深度缓冲
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        cubeShader.use();
        //刷新摄像头
        xOffset = mouseX - lastX; yOffset = lastY - mouseY;
        lastX = mouseX; lastY = mouseY;
        camera.ProcessMouseMovement(xOffset, yOffset);
        camera.ProcessMouseScroll(scrollYOffset);
        scrollYOffset = 0;
        //按键事件
        if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) {
            camera.ProcessKeyboard(FORWARD, deltaTime);
        }
        if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) {
            camera.ProcessKeyboard(BACKWARD, deltaTime);
        }
        if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) {
            camera.ProcessKeyboard(RIGHT, deltaTime);
        }
        if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) {
            camera.ProcessKeyboard(LEFT, deltaTime);
        }

        modelMat = glm::mat4(1.0f);
        viewMat = camera.GetViewMatrix();
        projectionMat = glm::mat4(1.0f);
        projectionMat = glm::perspective(glm::radians(45.0f), width * 1.0f / height, 0.1f, 100.0f);


        unsigned int modelLoc = glGetUniformLocation(cubeShader.ID, "modelMat");
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(modelMat));
        unsigned int viewLoc = glGetUniformLocation(cubeShader.ID, "viewMat");
        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, &viewMat[0][0]);
        unsigned int projectionLoc = glGetUniformLocation(cubeShader.ID, "projectionMat");
        glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projectionMat));
        //设置cube颜色
        cubeShader.set3Float("cubeColor", 1.0f, 0.5f, 0.31f);
        //设置光源颜色
        cubeShader.set3Float("lightColor", 1.0f, 1.0f, 1.0f);
        //设置光源世界坐标
        cubeShader.set3Float("lightPos", lightPos.x, lightPos.y, lightPos.z);
        //glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
        //设置观察者(摄像机)坐标
        cubeShader.set3Float("viewPos", camera.Position.x, camera.Position.y, camera.Position.z);
        //printf("camera.Position: %5.5lf %5.5lf %5.5lf\n", camera.Position.x, camera.Position.y, camera.Position.z);

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

        //光源shader
        lightShader.use();
        modelMat = glm::mat4(1.0f);
        modelMat = glm::translate(modelMat, lightPos);
        modelMat = glm::scale(modelMat, glm::vec3(0.2, 0.2, 0.2));
        viewMat = camera.GetViewMatrix();
        projectionMat = glm::mat4(1.0f);
        projectionMat = glm::perspective(glm::radians(45.0f), width * 1.0f / height, 0.1f, 100.0f);
        modelLoc = glGetUniformLocation(lightShader.ID, "modelMat");
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(modelMat));
        viewLoc = glGetUniformLocation(lightShader.ID, "viewMat");
        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, &viewMat[0][0]);
        projectionLoc = glGetUniformLocation(lightShader.ID, "projectionMat");
        glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projectionMat));

        //设置光源颜色
        lightShader.set3Float("lightColor", 1.0f, 1.0f, 1.0f);
        glBindVertexArray(lightVAO);
        glDrawArrays(GL_TRIANGLES, 0, 36);

        glfwSwapBuffers(window);

        glfwPollEvents();           //获取键盘输入
    }
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glfwTerminate();
}

void mouse_callback(GLFWwindow* window, double xPos, double yPos) {
    static bool firstMouse = true;          //第一次进入窗口,会发生较大移动,这个移动要会忽略掉
    if (firstMouse) {
        lastX = xPos;
        lastY = yPos;
        firstMouse = false;
    }
    ::mouseX = xPos;
    ::mouseY = yPos;
    //printf("mouseX: %5lfmouseY: %5lf\n", xPos, yPos);
}

void scroll_callback(GLFWwindow* window, double xOffset, double yOffset) {
    scrollYOffset += (float)yOffset;
}

MyOpenGLUtil.h

#pragma once
#define GLEW_STATIC
#include<GL/glew.h>
#include<GLFW/glfw3.h>

static int* widthPtr = nullptr, * heightPtr = nullptr;

void processInput(GLFWwindow* window);          //按键事件处理
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void Init(GLFWwindow** window, int width = 800, int height = 600);                                                              //初始化
void Init(GLFWwindow** window, int* width, int* height);
unsigned int CreateShader(const int shaderType, const char* shaderSource);
unsigned int CreateShaderProgram(unsigned int vertexShader, unsigned int fragmentShader);

static float vertices_cubeHaveTexture[] = {
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
         0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
         0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
         0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
         0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
         0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
};

static float vertices_cubeHaveNormal[] = {
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
     0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
     0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
     0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
    -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,

    -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
     0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
    -0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,

    -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
    -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
    -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
    -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
    -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
    -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,

     0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
     0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
     0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
     0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
     0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
     0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,

    -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
     0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
     0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
     0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,

    -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
     0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
     0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
     0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
    -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
    -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f
};

MyOpenGLUtil.cpp

#include<iostream>
#include "MyOpenGLUtil.h"
using namespace std;

void processInput(GLFWwindow* window) {
    //当按下指定键时,关闭窗口
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
        glfwSetWindowShouldClose(window, true);
    }
}

//初始化
void Init(GLFWwindow** mainWindow, int width, int height) {
    glfwInit();             //初始化
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);                  //使用主版本号3
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);                  //使用次版本号3
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);          //使用核心模式配置
#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
    //Open GLFW Window  开一个窗口
    * mainWindow = glfwCreateWindow(width, height, "My OpenGL Game", NULL, NULL);
    if (*mainWindow == NULL) {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();                //终止
        exit(-1);
    }
    glfwMakeContextCurrent(*mainWindow);
    glfwSetFramebufferSizeCallback(*mainWindow, framebuffer_size_callback);

    //glad使用这个加载函数
    // glad: load all OpenGL function pointers
    //if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    //{
    //  std::cout << "Failed to initialize GLAD" << std::endl;
    //  exit(-1);
    //}

    //Init GLFW
    glewExperimental = true;
    if (glewInit() != GLEW_OK) {
        cout << "Init GLFW failed." << endl;
        glfwTerminate();
        exit(1);
    }
}

void Init(GLFWwindow** mainWindow, int* width, int* height) {
    widthPtr = width; heightPtr = height;
    Init(mainWindow, *widthPtr, *heightPtr);
}

unsigned int CreateShader(const int shaderType, const char* shaderSource) {
    unsigned int shader;
    shader = glCreateShader(shaderType);
    glShaderSource(shader, 1, &shaderSource, NULL);
    glCompileShader(shader);
    int success;
    char infoLog[512];
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
    if (!success) {
        glGetShaderInfoLog(shader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    return shader;
}

unsigned int CreateShaderProgram(unsigned int vertexShader, unsigned int fragmentShader) {
    unsigned int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    int success;
    char infoLog[512];
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADERPROGRAM::LINK_FAILED\n" << infoLog << std::endl;
    }
    return shaderProgram;
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    cout << width << "   " << height << endl;
    if (widthPtr && heightPtr) {
        *widthPtr = width; *heightPtr = height;
    }
    // make sure the viewport matches the new window dimensions; note that width and 
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}

Shader.h

#pragma once
#ifndef SHADER
#define SHADER

#define GLEW_STATIC
#include <GL/glew.h>; // 包含glew来获取所有的必须OpenGL头文件
#include <GLFW/glfw3.h>;
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>

class Shader
{
public:
    // 程序ID
    unsigned int ID;

    // 构造器读取并构建着色器
    Shader(const char* vertexPath, const char* fragmentPath);
    // 使用/激活程序
    void use();
    // uniform工具函数
    void setBool(const std::string& name, bool value) const;
    void setInt(const std::string& name, int value) const;
    void setFloat(const std::string& name, float value) const;
    void set3Float(const std::string& name, float f1, float f2, float f3) const;
    void set4Float(const std::string& name, float f1, float f2, float f3, float f4) const;
};
#endif // !SHADER

Shader.cpp

#include"Shader.h"

Shader::Shader(const char* vertexPath, const char* fragmentPath) {
    std::ifstream vFile(vertexPath), fFile(fragmentPath);
    std::stringstream vss, fss;
    std::string vertexShaderCodeStr, fragmentShaderCodeStr;

    //保证ifstream对象可以抛出异常
    vFile.exceptions(std::istream::failbit | std::istream::badbit);
    fFile.exceptions(std::istream::failbit | std::istream::badbit);
    try {
        if (vFile && fFile) {
            vss << vFile.rdbuf();
            fss << fFile.rdbuf();
            vertexShaderCodeStr = vss.str();
            fragmentShaderCodeStr = fss.str();
            vFile.close();
            fFile.close();
        }
    }
    catch (std::ifstream::failure e) {
        std::cerr << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
    }
    const char* vertexShaderCode = vertexShaderCodeStr.c_str(), * fragmentShaderCode = fragmentShaderCodeStr.c_str();

    //创建顶点着色器
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderCode, NULL);
    glCompileShader(vertexShader);
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success) {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::VERTEX_SHADER::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    //创建片段着色器
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderCode, NULL);
    glCompileShader(fragmentShader);
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success) {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "ERROR::FRAGMENT_SHADER::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    //创建Program
    unsigned int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADERPROGRAM::LINK_FAILED\n" << infoLog << std::endl;
    }
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    ID = shaderProgram;
}
// 使用/激活程序
void Shader::use() {
    glUseProgram(ID);
}
// uniform工具函数
void Shader::setBool(const std::string& name, bool value) const {
    glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
}
void Shader::setInt(const std::string& name, int value) const {
    glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
}
void Shader::setFloat(const std::string& name, float value) const {
    glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
}
void Shader::set3Float(const std::string& name, float f1, float f2, float f3) const {
    glUniform3f(glGetUniformLocation(ID, name.c_str()), f1, f2, f3);
}
void Shader::set4Float(const std::string& name, float f1, float f2, float f3, float f4) const {
    glUniform4f(glGetUniformLocation(ID, name.c_str()), f1, f2, f3, f4);
}

Camera.h

#ifndef CAMERA_H
#define CAMERA_H

#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

#include <vector>

// Defines several possible options for camera movement. Used as abstraction to stay away from window-system specific input methods
enum Camera_Movement {
    FORWARD,
    BACKWARD,
    LEFT,
    RIGHT
};

// Default camera values
const float YAW = -90.0f;
const float PITCH = 0.0f;
const float SPEED = 2.5f;
const float SENSITIVITY = 0.1f;
const float ZOOM = 45.0f;


// An abstract camera class that processes input and calculates the corresponding Euler Angles, Vectors and Matrices for use in OpenGL
class Camera
{
public:
    // camera Attributes
    glm::vec3 Position;
    glm::vec3 Front;
    glm::vec3 Up;
    glm::vec3 Right;
    glm::vec3 WorldUp;
    // euler Angles
    float Yaw;
    float Pitch;
    // camera options
    float MovementSpeed;
    float MouseSensitivity;
    float Zoom;

    // constructor with vectors
    Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float yaw = YAW, float pitch = PITCH) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM)
    {
        Position = position;
        WorldUp = up;
        Yaw = yaw;
        Pitch = pitch;
        updateCameraVectors();
    }
    // constructor with scalar values
    Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM)
    {
        Position = glm::vec3(posX, posY, posZ);
        WorldUp = glm::vec3(upX, upY, upZ);
        Yaw = yaw;
        Pitch = pitch;
        updateCameraVectors();
    }

    // returns the view matrix calculated using Euler Angles and the LookAt Matrix
    glm::mat4 GetViewMatrix()
    {
        return glm::lookAt(Position, Position + Front, Up);
    }

    // processes input received from any keyboard-like input system. Accepts input parameter in the form of camera defined ENUM (to abstract it from windowing systems)
    void ProcessKeyboard(Camera_Movement direction, float deltaTime)
    {
        float velocity = MovementSpeed * deltaTime;
        if (direction == FORWARD)
            Position += Front * velocity;
        if (direction == BACKWARD)
            Position -= Front * velocity;
        if (direction == LEFT)
            Position -= Right * velocity;
        if (direction == RIGHT)
            Position += Right * velocity;
    }

    // processes input received from a mouse input system. Expects the offset value in both the x and y direction.
    void ProcessMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true)
    {
        xoffset *= MouseSensitivity;
        yoffset *= MouseSensitivity;

        Yaw += xoffset;
        Pitch += yoffset;

        // make sure that when pitch is out of bounds, screen doesn't get flipped
        if (constrainPitch)
        {
            if (Pitch > 89.0f)
                Pitch = 89.0f;
            if (Pitch < -89.0f)
                Pitch = -89.0f;
        }

        // update Front, Right and Up Vectors using the updated Euler angles
        updateCameraVectors();
    }

    // processes input received from a mouse scroll-wheel event. Only requires input on the vertical wheel-axis
    void ProcessMouseScroll(float yoffset)
    {
        Zoom -= (float)yoffset;
        if (Zoom < 1.0f)
            Zoom = 1.0f;
        if (Zoom > 45.0f)
            Zoom = 45.0f;
    }

private:
    // calculates the front vector from the Camera's (updated) Euler Angles
    void updateCameraVectors()
    {
        // calculate the new Front vector
        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));
        Front = glm::normalize(front);
        // also re-calculate the Right and Up vector
        Right = glm::normalize(glm::cross(Front, WorldUp));  // normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement.
        Up = glm::normalize(glm::cross(Right, Front));
    }
};
#endif

其他没有的头文件是官网下的。

cubew1.vert (物体和光源共同使用该顶点着色器)

#version 330 core
layout(location = 0) in vec3 pos;
layout(location = 1) in vec3 nor;

//光照计算统一传入到片段着色器中计算
out vec3 normal;
out vec4 fragPos;       //片段世界坐标

uniform mat4 modelMat;
uniform mat4 viewMat;
uniform mat4 projectionMat;

void main(){
    gl_Position = projectionMat * viewMat * modelMat * vec4(pos, 1.0f);
    normal = nor;
    fragPos = modelMat * vec4(pos, 1.0);        //顶点坐标乘以模型矩阵转为世界坐标
}

lightw1.frag

#version 330 core

out vec4 fragColor;

uniform vec3 lightColor;

void main(){
    fragColor = vec4(lightColor, 1.0);
}

cubew1.frag

#version 330 core
in vec3 normal;
in vec4 fragPos;                        //片段世界坐标

out vec4 fragColor;

uniform vec3 cubeColor;
uniform vec3 lightColor;
uniform vec3 lightPos;          //光照世界坐标
uniform vec3 viewPos;           //观察者世界坐标

void main(){
    //计算环境光照
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * lightColor;        //环境光照

    //计算漫反射光照
    //转为单位向量
    vec3 norm = normalize(normal);
    vec3 lightDir = normalize(lightPos - vec3(fragPos));
    float rad = max(dot(lightDir, norm), 0.0);          //光照方向与片段法向量夹角[0,PI/2]
    vec3 diffuse = rad * lightColor;                            //漫反射光照

    //计算镜面光照
    vec3 viewDir = normalize(viewPos - vec3(fragPos));          //观察者方向
    vec3 reflectDir = reflect(-lightDir, norm);                         //光照经法向量折射方向
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);   //反光度,这里取点乘的32次幂
    float specularStrength = 0.5;                                               //镜面强度
    vec3 specular = spec * specularStrength * lightColor;       //镜面光照

    //距离影响光照强度,暂时是我自己想的
    float enableDistance = 4.0f;    //有效光照距离
    vec3 light2fragVec = lightPos - vec3(fragPos);
    float distan = min(sqrt(light2fragVec.x * light2fragVec.x + light2fragVec.y * light2fragVec.y + light2fragVec.z * light2fragVec.z), enableDistance);
    float strength = 1 - distan / enableDistance;

    //光照远近先不影响环境光照了
    vec3 result = ambient * cubeColor + (diffuse + specular)  * cubeColor * strength;
    fragColor = vec4(result, 1.0);
}

学习自:https://learnopengl-cn.github.io/02%20Lighting/02%20Basic%20Lighting/

最后修改:2022 年 12 月 18 日
如果觉得我的文章对你有用,请随意赞赏