此篇博文为个人OpenGL学习笔记,内容参考LearnOpenGL CNGLFW官方文档OpenGL 4 Reference Pages、OpenGL编程指南(原书第9版)

结合所有光照内容创建一个包含六个光源的场景——模拟一个类似太阳的定向光(Directional Light)光源、四个分散在场景中的点光源(Point Light)以及一个手电筒(Flashlight)。

为了在场景中使用多个光源会将光照计算封装到GLSL函数中。

GLSL支持用户自定义函数,同时它也内置了一些函数。用户自定义函数可以在单个着色器对象中定义,然后在多个着色器程序中复用。

GLSL函数声明语法与C语言非常类似,只是变量名需要添加访问修饰符:

returnType functionName([accessModifier] type1 variable1,
                        [accessModifier] type2 variable2,
                        ...)
{
    return returnValue;
}

函数名称可以是任何字符、数字和下划线字符的组合,但是不能使用数字、连续下划线或gl_作为函数的开始。

返回值可以是任何内置的GLSL类型,或者用户自定义的结构体和数组类型。返回值为数组时,必须显式地指定大小。如果一个函数的返回值类型是void,那么它可以没有返回值。

函数的参数也可以是任何类型,包括数组(但是也必须设置数组的大小)。

在使用一个函数之前,必须声明它的原型或者直接给出函数体。GLSL的编译器与C++一致,必须在使用函数之前找到函数的声明,否则会产生错误。如果函数的定义和使用不再同一个着色器对象中,那么必须声明一个函数原型。函数原型只是给出了函数的形式,但是并没有给出具体的实现内容。一个简单的函数原型:

float HornerEvalPolynomial(float coeff[10], float x);

对每个光照都创建一个不同的函数:定向光、点光源和聚光。

当在场景中使用多个光源时通常会需要一个单独的颜色向量代表片段的输出颜色,对于每一个光源它对片段的贡献颜色将会加到片段的输出颜色向量上,所以场景中的每个光源都会计算它们各自对片段的影响并结合为一个最终的输出颜色。

定义一个DirLight的结构体包含一个定向光源最少所需的变量并以此结构体为类型声明一个uniform变量:

struct DirLight {
    vec3 direction;
    
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

uniform DirLight dir_light;

有dir_light结构体和其它两个向量参数的定向光计算函数:

vec3 CalcDirLight(DirLight light, vec3 normal, vec3 view_dir) {
    vec3 light_dir = normalize(-light.direction);

    float diff = max(dot(normal, light_dir), 0.0f);

    vec3 reflect_dir = reflect(-light_dir, normal);
    float spec = pow(max(dot(view_dir, reflect_dir), 0.0f), material.shininess);

    vec3 ambient = light.ambient * vec3(texture(material.diffuse, f_tex_coords));
    vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, f_tex_coords));
    vec3 specular = light.specular * spec * vec3(texture(material.specular, f_tex_coords));
    return (ambient + diffuse + specular);
}

和定向光一样定义一个包含了点光源所需所有变量的结构体并以此结构体为类型声明相应数量的uniform数组:

struct PointLight {
    vec3 position;

    float constant;
    float linear;
    float quadratic;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

#define POINT_LIGHTS_NUMBER 4;
uniform PointLight point_lights[POINT_LIGHTS_NUMBER];

点光源计算函数:

vec3 CalcPointLight(PointLight light, vec3 normal, vec3 f_pos, vec3 view_dir) {
    vec3 light_dir = normalize(light.position - f_pos);

    float diff = max(dot(normal, light_dir), 0.0f);

    vec3 reflect_dir = reflect(-light_dir, normal);
    float spec = pow(max(dot(view_dir, reflect_dir), 0.0f), material.shininess);

    float distance = length(light.position - f_pos);
    float attenuation = 1.0f / (light.constant + light.linear * distance + light.quadratic * (distance * distance));

    vec3 ambient = light.ambient * vec3(texture(material.diffuse, f_tex_coords));
    vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, f_tex_coords));
    vec3 specular = light.specular * spec * vec3(texture(material.specular, f_tex_coords));

    ambient *= attenuation;
    diffuse *= attenuation;
    specular *= attenuation;

    return (ambient + diffuse + specular);
}

在主函数中遍历整个点光源数组对每个光源调用此函数即可计算多个点光源的光照。

同样为聚光添加相关结构体变量和光照计算函数:

struct SpotLight {
    vec3 position;
    vec3 direction;
    float cutoff;
    float outer_cutoff;

    float constant;
    float linear;
    float quadratic;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

uniform SpotLight spot_light;

vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 f_pos, vec3 view_dir) {
    vec3 light_dir = normalize(light.position - f_pos);
    
    float diff = max(dot(normal, light_dir), 0.0f);

    vec3 reflect_dir = reflect(-light_dir, normal);
    float spec = pow(max(dot(view_dir, reflect_dir), 0.0f), material.shininess);

    float distance = length(light.position - f_pos);
    float attenuation = 1.0f / (light.constant + light.linear * distance + light.quadratic * (distance * distance));

    float theta = dot(light_dir, normalize(-light.direction));
    float epsilon = light.cutoff - light.outer_cutoff;
    float intensity = clamp((theta - light.outer_cutoff) / epsilon, 0.0f, 1.0f);

    vec3 ambient = light.ambient * vec3(texture(material.diffuse, f_tex_coords));
    vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, f_tex_coords));
    vec3 specular = light.specular * spec * vec3(texture(material.specular, f_tex_coords));

    ambient *= attenuation;
    diffuse *= attenuation;
    specular *= attenuation;

    return (ambient + diffuse + specular);
}

在主函数中调用(使用)定向光的计算函数、点光源的计算函数和聚光的计算函数进行光照计算:

void main() {
    vec3 norm = normalize(f_normal);
    vec3 light_dir = normalize(light.position - f_pos);

    vec3 result = CalcDirLight(dir_light, norm, view_dir);
    for (int i = 0; i < POINT_LIGHTS_NUMBER; ++i) {
        result += CalcPointLight(point_lights[i], norm, f_pos, view_dir);
    }
    result += CalcSpotLight(spot_light, norm, f_pos, view_dir);

    color = vec4(result, 1.0f);
}

每个光源类型都将它们的贡献加到了最终的输出颜色上直到所有的光源全部处理完毕。最终的颜色包含了场景中所有光源的颜色影响所合并的结果。

在程序中对所有光源相关uniform变量进行赋值:

light_shader.SetVec3("dir_light.direction", -0.2f, -1.0f, -0.3f);
light_shader.SetVec3("dir_light.ambient", 0.05f, 0.05f, 0.05f);
light_shader.SetVec3("dir_light.diffuse", 0.4f, 0.4f, 0.4f);
light_shader.SetVec3("dir_light.specular", 0.5f, 0.5f, 0.5f);

light_shader.SetVec3("point_lights[0].position", lamp_positions[0]);
light_shader.SetVec3("point_lights[0].ambient", 0.05f, 0.05f, 0.05f);
light_shader.SetVec3("point_lights[0].diffuse", 0.8f, 0.8f, 0.8f);
light_shader.SetVec3("point_lights[0].specular", 1.0f, 1.0f, 1.0f);
light_shader.SetFloat("point_lights[0].constant", 1.0f);
light_shader.SetFloat("point_lights[0].linear", 0.09f);
light_shader.SetFloat("point_lights[0].quadratic", 0.032f);

light_shader.SetVec3("point_lights[1].position", lamp_positions[1]);
light_shader.SetVec3("point_lights[1].ambient", 0.05f, 0.05f, 0.05f);
light_shader.SetVec3("point_lights[1].diffuse", 0.8f, 0.8f, 0.8f);
light_shader.SetVec3("point_lights[1].specular", 1.0f, 1.0f, 1.0f);
light_shader.SetFloat("point_lights[1].constant", 1.0f);
light_shader.SetFloat("point_lights[1].linear", 0.09f);
light_shader.SetFloat("point_lights[1].quadratic", 0.032f);

light_shader.SetVec3("point_lights[2].position", lamp_positions[2]);
light_shader.SetVec3("point_lights[2].ambient", 0.05f, 0.05f, 0.05f);
light_shader.SetVec3("point_lights[2].diffuse", 0.8f, 0.8f, 0.8f);
light_shader.SetVec3("point_lights[2].specular", 1.0f, 1.0f, 1.0f);
light_shader.SetFloat("point_lights[2].constant", 1.0f);
light_shader.SetFloat("point_lights[2].linear", 0.09f);
light_shader.SetFloat("point_lights[2].quadratic", 0.032f);

light_shader.SetVec3("point_lights[3].position", lamp_positions[3]);
light_shader.SetVec3("point_lights[3].ambient", 0.05f, 0.05f, 0.05f);
light_shader.SetVec3("point_lights[3].diffuse", 0.8f, 0.8f, 0.8f);
light_shader.SetVec3("point_lights[3].specular", 1.0f, 1.0f, 1.0f);
light_shader.SetFloat("point_lights[3].constant", 1.0f);
light_shader.SetFloat("point_lights[3].linear", 0.09f);
light_shader.SetFloat("point_lights[3].quadratic", 0.032f);

light_shader.SetVec3("spot_light.position", camera.position);
light_shader.SetVec3("spot_light.direction", camera.front);
light_shader.SetVec3("spot_light.ambient", 0.0f, 0.0f, 0.0f);
light_shader.SetVec3("spot_light.diffuse", 1.0f, 1.0f, 1.0f);
light_shader.SetVec3("spot_light.specular", 1.0f, 1.0f, 1.0f);
light_shader.SetFloat("spot_light.constant", 1.0f);
light_shader.SetFloat("spot_light.linear", 0.09f);
light_shader.SetFloat("spot_light.quadratic", 0.032f);
light_shader.SetFloat("spot_light.cutoff", glm::cos(glm::radians(12.5f)));
light_shader.SetFloat("spot_light.outer_cutoff", glm::cos(glm::radians(15.0f)));

为每个点光源定义一个位置向量:

glm::vec3 lamp_positions[] = {
    glm::vec3(0.7f, 0.2f, 2.0f),
    glm::vec3(2.3f, -3.3f, -4.0f),
    glm::vec3(-4.0f, 2.0f, -12.0f),
    glm::vec3(0.0f, 0.0f, -3.0f)
};

完整代码:

camera.h:

#pragma once

#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>

enum Camera_Movement {
    FORWARD,
    BACKWARD,
    LEFT,
    RIGHT
};

const float yaw_init = -90.0f, pitch_init = 0.0f;
const float speed_init = 2.5f, sensitivity_init = 0.1f, fov_init = 45.0f;

class Camera {
public:
    glm::vec3 position, front, up, right, world_up;
    float yaw, pitch, movement_speed, mouse_sensitivity, fov;

    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_init, float _pitch = pitch_init) {
        front = glm::vec3(0.0f, 0.0f, -1.0f);
        movement_speed = speed_init;
        mouse_sensitivity = sensitivity_init;
        fov = fov_init;
        position = _position;
        world_up = _up;
        yaw = _yaw;
        pitch = _pitch;
        UpdateCameraVectors();
    }

    Camera(float pos_x, float pos_y, float pos_z, float up_x, float up_y, float up_z, float _yaw, float _pitch) {
        front = glm::vec3(0.0f, 0.0f, -1.0f);
        movement_speed = speed_init;
        mouse_sensitivity = sensitivity_init;
        fov = fov_init;
        position = glm::vec3(pos_x, pos_y, pos_z);
        world_up = glm::vec3(up_x, up_y, up_z);
        yaw = _yaw;
        pitch = _pitch;
        UpdateCameraVectors();
    }

    glm::mat4 GetViewMatrix() {
        return glm::lookAt(position, position + front, up);
    }

    void ProcessKeyboard(Camera_Movement direction, float delta_time) {
        float velocity = movement_speed * delta_time;
        if (direction == FORWARD) {
            position += front * velocity;
        }
        if (direction == BACKWARD) {
            position -= front * velocity;
        }
        if (direction == LEFT) {
            position -= right * velocity;
        }
        if (direction == RIGHT) {
            position += right * velocity;
        }
    }

    void ProcessMouseMovement(float x_offset, float y_offset, GLboolean constrain_pitch = true) {
        x_offset *= mouse_sensitivity;
        y_offset *= mouse_sensitivity;
        yaw += x_offset;
        pitch += y_offset;
        if (constrain_pitch) {
            if (pitch > 89.0f) {
                pitch = 89.0f;
            }
            if (pitch < -89.0f) {
                pitch = -89.0f;
            }
        }
        UpdateCameraVectors();
    }

    void ProcessMouseScroll(float y_offset) {
        if (fov >= 1.0f && fov <= 45.0f) {
            fov -= y_offset;
        }
        if (fov <= 1.0f) {
            fov = 1.0f;
        }
        if (fov >= 45.0f) {
            fov = 45.0f;
        }
    }

private:
    void UpdateCameraVectors() {
        glm::vec3 front_buffer;
        front_buffer.x = std::cos(glm::radians(yaw)) * std::cos(glm::radians(pitch));
        front_buffer.y = std::sin(glm::radians(pitch));
        front_buffer.z = std::sin(glm::radians(yaw)) * std::cos(glm::radians(pitch));
        front = glm::normalize(front_buffer);
        right = glm::normalize(glm::cross(front, world_up));
        up = glm::normalize(glm::cross(right, front));
    }
};

shader.h:

#pragma once

#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>

class Shader {
public:
    unsigned int id;

    Shader(const GLchar* vertex_path, const GLchar* fragment_path) {
        std::string vertex_source, fragment_source;
        std::ifstream vertex_shader_file, fragment_shader_file;
        vertex_shader_file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        fragment_shader_file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        try {
            vertex_shader_file.open(vertex_path);
            fragment_shader_file.open(fragment_path);
            std::stringstream vertex_shader_stream, fragment_shader_stream;
            vertex_shader_stream << vertex_shader_file.rdbuf();
            fragment_shader_stream << fragment_shader_file.rdbuf();
            vertex_shader_file.close();
            fragment_shader_file.close();
            vertex_source = vertex_shader_stream.str();
            fragment_source = fragment_shader_stream.str();
        }
        catch (std::ifstream::failure e) {
            std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
        }
        const char* vertex_shader_source = vertex_source.c_str();
        const char* fragment_shader_source = fragment_source.c_str();
        unsigned int vertex_shader, fragment_shader;
        vertex_shader = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertex_shader, 1, &vertex_shader_source, NULL);
        glCompileShader(vertex_shader);
        checkCompileErrors(vertex_shader, "VERTEX");
        fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragment_shader, 1, &fragment_shader_source, NULL);
        glCompileShader(fragment_shader);
        checkCompileErrors(fragment_shader, "FRAGMENT");
        this->id = glCreateProgram();
        glAttachShader(this->id, vertex_shader);
        glAttachShader(this->id, fragment_shader);
        glLinkProgram(this->id);
        checkCompileErrors(id, "PROGRAM");
        glDeleteShader(vertex_shader);
        glDeleteShader(fragment_shader);
    }

    void Use() {
        glUseProgram(this->id);
    }

    void SetBool(const std::string& name, bool value) const {
        glUniform1i(glGetUniformLocation(this->id, name.c_str()), (int)value);
    }

    void SetInt(const std::string& name, int value) const {
        glUniform1i(glGetUniformLocation(this->id, name.c_str()), value);
    }

    void SetFloat(const std::string& name, float value) const {
        glUniform1f(glGetUniformLocation(this->id, name.c_str()), value);
    }

    void SetVec2(const std::string& name, const glm::vec2& value) const {
        glUniform2fv(glGetUniformLocation(this->id, name.c_str()), 1, glm::value_ptr(value));
    }

    void SetVec2(const std::string& name, float x, float y) const {
        glUniform2f(glGetUniformLocation(this->id, name.c_str()), x, y);
    }

    void SetVec3(const std::string& name, const glm::vec3& value) const {
        glUniform3fv(glGetUniformLocation(this->id, name.c_str()), 1, glm::value_ptr(value));
    }

    void SetVec3(const std::string& name, float x, float y, float z) const {
        glUniform3f(glGetUniformLocation(this->id, name.c_str()), x, y, z);
    }

    void SetVec4(const std::string& name, const glm::vec4& value) const {
        glUniform4fv(glGetUniformLocation(this->id, name.c_str()), 1, glm::value_ptr(value));
    }

    void SetVec4(const std::string& name, float x, float y, float z, float w) {
        glUniform4f(glGetUniformLocation(this->id, name.c_str()), x, y, z, w);
    }

    void SetMat2(const std::string& name, const glm::mat2& mat) const {
        glUniformMatrix2fv(glGetUniformLocation(this->id, name.c_str()), 1, GL_FALSE, glm::value_ptr(mat));
    }

    void SetMat3(const std::string& name, const glm::mat3& mat) const {
        glUniformMatrix3fv(glGetUniformLocation(this->id, name.c_str()), 1, GL_FALSE, glm::value_ptr(mat));
    }

    void SetMat4(const std::string& name, const glm::mat4& mat) const {
        glUniformMatrix4fv(glGetUniformLocation(this->id, name.c_str()), 1, GL_FALSE, glm::value_ptr(mat));
    }

private:
    void checkCompileErrors(GLuint shader, std::string type) {
        GLint success;
        char info_log[1024];
        if (type != "PROGRAM") {
            glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
            if (!success) {
                glGetShaderInfoLog(shader, 1024, NULL, info_log);
                std::cout << "EEROR::SHADER_COMPILATINO_ERROR of type: " << type << std::endl;
                std::cout << info_log << std::endl << " -- --------------------------------------------------- -- " << std::endl;
            }
        }
        else {
            glGetProgramiv(shader, GL_LINK_STATUS, &success);
            if (!success) {
                glGetProgramInfoLog(shader, 1024, NULL, info_log);
                std::cout << "EEROR::PROGRAM_COMPILATINO_ERROR of type: " << type << std::endl;
                std::cout << info_log << std::endl << " -- --------------------------------------------------- -- " << std::endl;
            }
        }
    }
};

light_shader.vs:

#version 460 core
layout (location = 0) in vec3 v_pos;
layout (location = 1) in vec3 v_normal;
layout (location = 2) in vec2 v_tex_coords;

out vec3 f_pos;
out vec3 f_normal;
out vec2 f_tex_coords;

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

void main() {
    f_pos = vec3(model * vec4(v_pos, 1.0));
    f_normal = mat3(transpose(inverse(model))) * v_normal;
    f_tex_coords = v_tex_coords;
    gl_Position = projection * view * model * vec4(v_pos, 1.0f);
}

light_shader.fs:

#version 460 core
out vec4 color;

in vec3 f_normal;
in vec3 f_pos;
in vec2 f_tex_coords;

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

uniform Material material;

struct DirLight {
    vec3 direction;
    
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

uniform DirLight dir_light;

vec3 CalcDirLight(DirLight light, vec3 normal, vec3 view_dir) {
    vec3 light_dir = normalize(-light.direction);

    float diff = max(dot(normal, light_dir), 0.0f);

    vec3 reflect_dir = reflect(-light_dir, normal);
    float spec = pow(max(dot(view_dir, reflect_dir), 0.0f), material.shininess);

    vec3 ambient = light.ambient * vec3(texture(material.diffuse, f_tex_coords));
    vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, f_tex_coords));
    vec3 specular = light.specular * spec * vec3(texture(material.specular, f_tex_coords));
    return (ambient + diffuse + specular);
}

struct PointLight {
    vec3 position;

    float constant;
    float linear;
    float quadratic;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

#define POINT_LIGHTS_NUMBER 4
uniform PointLight point_lights[POINT_LIGHTS_NUMBER];

vec3 CalcPointLight(PointLight light, vec3 normal, vec3 f_pos, vec3 view_dir) {
    vec3 light_dir = normalize(light.position - f_pos);

    float diff = max(dot(normal, light_dir), 0.0f);

    vec3 reflect_dir = reflect(-light_dir, normal);
    float spec = pow(max(dot(view_dir, reflect_dir), 0.0f), material.shininess);

    float distance = length(light.position - f_pos);
    float attenuation = 1.0f / (light.constant + light.linear * distance + light.quadratic * (distance * distance));

    vec3 ambient = light.ambient * vec3(texture(material.diffuse, f_tex_coords));
    vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, f_tex_coords));
    vec3 specular = light.specular * spec * vec3(texture(material.specular, f_tex_coords));

    ambient *= attenuation;
    diffuse *= attenuation;
    specular *= attenuation;

    return (ambient + diffuse + specular);
}

struct SpotLight {
    vec3 position;
    vec3 direction;
    float cutoff;
    float outer_cutoff;

    float constant;
    float linear;
    float quadratic;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

uniform SpotLight spot_light;

vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 f_pos, vec3 view_dir) {
    vec3 light_dir = normalize(light.position - f_pos);
    
    float diff = max(dot(normal, light_dir), 0.0f);

    vec3 reflect_dir = reflect(-light_dir, normal);
    float spec = pow(max(dot(view_dir, reflect_dir), 0.0f), material.shininess);

    float distance = length(light.position - f_pos);
    float attenuation = 1.0f / (light.constant + light.linear * distance + light.quadratic * (distance * distance));

    float theta = dot(light_dir, normalize(-light.direction));
    float epsilon = light.cutoff - light.outer_cutoff;
    float intensity = clamp((theta - light.outer_cutoff) / epsilon, 0.0f, 1.0f);

    vec3 ambient = light.ambient * vec3(texture(material.diffuse, f_tex_coords));
    vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, f_tex_coords));
    vec3 specular = light.specular * spec * vec3(texture(material.specular, f_tex_coords));

    ambient *= attenuation * intensity;
    diffuse *= attenuation * intensity;
    specular *= attenuation * intensity;

    return (ambient + diffuse + specular);
}

uniform vec3 view_pos;

void main() {
    vec3 norm = normalize(f_normal);
    vec3 view_dir = normalize(view_pos - f_pos);

    vec3 result = CalcDirLight(dir_light, norm, view_dir);
    for (int i = 0; i < POINT_LIGHTS_NUMBER; ++i) {
        result += CalcPointLight(point_lights[i], norm, f_pos, view_dir);
    }
    result += CalcSpotLight(spot_light, norm, f_pos, view_dir);

    color = vec4(result, 1.0f);
}

lamp_shader.vs:

#version 460 core
layout (location = 0) in vec3 v_pos;

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

void main() {
    gl_Position = projection * view * model * vec4(v_pos, 1.0f);
}

lamp_shader.fs:

#version 460 core
out vec4 color;

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

main.cpp:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include "shader.h"
#include "camera.h"

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

const unsigned int window_width = 800;
const unsigned int window_height = 600;

Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float mouse_last_x = window_width / 2.0f;
float mouse_last_y = window_height / 2.0f;
bool first_mouse = true;

float delta_time = 0.0f;
float last_frame = 0.0f;

void FramebufferSizeCallback(GLFWwindow* window, int width, int height) {
    glViewport(0, 0, width, height);
}

void ProcessInput(GLFWwindow* window) {
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
        glfwSetWindowShouldClose(window, true);
    }
    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) {
        camera.ProcessKeyboard(FORWARD, delta_time);
    }
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) {
        camera.ProcessKeyboard(BACKWARD, delta_time);
    }
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) {
        camera.ProcessKeyboard(LEFT, delta_time);
    }
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) {
        camera.ProcessKeyboard(RIGHT, delta_time);
    }
}

void MouseCallback(GLFWwindow* window, double x_pos, double y_pos) {
    if (first_mouse) {
        mouse_last_x = x_pos;
        mouse_last_y = y_pos;
        first_mouse = false;
    }
    float x_offset = x_pos - mouse_last_x;
    float y_offset = mouse_last_y - y_pos;
    mouse_last_x = x_pos;
    mouse_last_y = y_pos;
    camera.ProcessMouseMovement(x_offset, y_offset);
}

void ScrollCallback(GLFWwindow* window, double x_offset, double y_offset) {
    camera.ProcessMouseScroll(y_offset);
}

unsigned int LoadTexture(char const* path) {
    unsigned int texture_id;
    glGenTextures(1, &texture_id);
    glBindTexture(GL_TEXTURE_2D, texture_id);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_REPEAT);
    int width, height, channels;
    unsigned char* data = stbi_load(path, &width, &height, &channels, 0);
    if (data) {
        GLenum format;
        if (channels == 1) {
            format = GL_RED;
        }
        else if (channels == 3) {
            format = GL_RGB;
        }
        else if (channels == 4) {
            format = GL_RGBA;
        }
        glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    else {
        std::cout << "Failed to load texture at path: " << path << std::endl;
    }
    stbi_image_free(data);
    return texture_id;
}

int main() {
    if (!glfwInit()) {
        std::cout << "Failed to initialize GLFW" << std::endl;
        return -1;
    }
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    GLFWwindow* window = glfwCreateWindow(window_width, window_height, "Hello OpenGL", NULL, NULL);
    if (window == NULL) {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, FramebufferSizeCallback);
    glfwSetCursorPosCallback(window, MouseCallback);
    glfwSetScrollCallback(window, ScrollCallback);
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }
    glEnable(GL_DEPTH_TEST);

    Shader light_shader("light_shader.vs", "light_shader.fs");
    Shader lamp_shader("lamp_shader.vs", "lamp_shader.fs");

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

    glm::vec3 cube_positions[] = {
        glm::vec3(0.0f,  0.0f,  0.0f),
        glm::vec3(2.0f,  5.0f, -15.0f),
        glm::vec3(-1.5f, -2.2f, -2.5f),
        glm::vec3(-3.8f, -2.0f, -12.3f),
        glm::vec3(2.4f, -0.4f, -3.5f),
        glm::vec3(-1.7f,  3.0f, -7.5f),
        glm::vec3(1.3f, -2.0f, -2.5f),
        glm::vec3(1.5f,  2.0f, -2.5f),
        glm::vec3(1.5f,  0.2f, -1.5f),
        glm::vec3(-1.3f,  1.0f, -1.5f)
    };

    glm::vec3 lamp_positions[] = {
        glm::vec3(0.7f, 0.2f, 2.0f),
        glm::vec3(2.3f, -3.3f, -4.0f),
        glm::vec3(-4.0f, 2.0f, -12.0f),
        glm::vec3(0.0f, 0.0f, -3.0f)
    };

    unsigned int vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    unsigned int cube_vao;
    glGenVertexArrays(1, &cube_vao);
    glBindVertexArray(cube_vao);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);

    unsigned int light_vao;
    glGenVertexArrays(1, &light_vao);
    glBindVertexArray(light_vao);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    unsigned int diffuse_map = LoadTexture("src/container2.png");
    unsigned int specular_map = LoadTexture("src/container2_specular.png");
    light_shader.Use();
    light_shader.SetInt("material.diffuse", 0);
    light_shader.SetInt("material.specular", 1);

    while (!glfwWindowShouldClose(window)) {
        float current_frame = glfwGetTime();
        delta_time = current_frame - last_frame;
        last_frame = current_frame;

        ProcessInput(window);

        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        light_shader.Use();
        light_shader.SetVec3("view_pos", camera.position);
        light_shader.SetFloat("material.shininess", 32.0f);

        light_shader.SetVec3("dir_light.direction", -0.2f, -1.0f, -0.3f);
        light_shader.SetVec3("dir_light.ambient", 0.05f, 0.05f, 0.05f);
        light_shader.SetVec3("dir_light.diffuse", 0.4f, 0.4f, 0.4f);
        light_shader.SetVec3("dir_light.specular", 0.5f, 0.5f, 0.5f);

        light_shader.SetVec3("point_lights[0].position", lamp_positions[0]);
        light_shader.SetVec3("point_lights[0].ambient", 0.05f, 0.05f, 0.05f);
        light_shader.SetVec3("point_lights[0].diffuse", 0.8f, 0.8f, 0.8f);
        light_shader.SetVec3("point_lights[0].specular", 1.0f, 1.0f, 1.0f);
        light_shader.SetFloat("point_lights[0].constant", 1.0f);
        light_shader.SetFloat("point_lights[0].linear", 0.09f);
        light_shader.SetFloat("point_lights[0].quadratic", 0.032f);

        light_shader.SetVec3("point_lights[1].position", lamp_positions[1]);
        light_shader.SetVec3("point_lights[1].ambient", 0.05f, 0.05f, 0.05f);
        light_shader.SetVec3("point_lights[1].diffuse", 0.8f, 0.8f, 0.8f);
        light_shader.SetVec3("point_lights[1].specular", 1.0f, 1.0f, 1.0f);
        light_shader.SetFloat("point_lights[1].constant", 1.0f);
        light_shader.SetFloat("point_lights[1].linear", 0.09f);
        light_shader.SetFloat("point_lights[1].quadratic", 0.032f);

        light_shader.SetVec3("point_lights[2].position", lamp_positions[2]);
        light_shader.SetVec3("point_lights[2].ambient", 0.05f, 0.05f, 0.05f);
        light_shader.SetVec3("point_lights[2].diffuse", 0.8f, 0.8f, 0.8f);
        light_shader.SetVec3("point_lights[2].specular", 1.0f, 1.0f, 1.0f);
        light_shader.SetFloat("point_lights[2].constant", 1.0f);
        light_shader.SetFloat("point_lights[2].linear", 0.09f);
        light_shader.SetFloat("point_lights[2].quadratic", 0.032f);

        light_shader.SetVec3("point_lights[3].position", lamp_positions[3]);
        light_shader.SetVec3("point_lights[3].ambient", 0.05f, 0.05f, 0.05f);
        light_shader.SetVec3("point_lights[3].diffuse", 0.8f, 0.8f, 0.8f);
        light_shader.SetVec3("point_lights[3].specular", 1.0f, 1.0f, 1.0f);
        light_shader.SetFloat("point_lights[3].constant", 1.0f);
        light_shader.SetFloat("point_lights[3].linear", 0.09f);
        light_shader.SetFloat("point_lights[3].quadratic", 0.032f);

        light_shader.SetVec3("spot_light.position", camera.position);
        light_shader.SetVec3("spot_light.direction", camera.front);
        light_shader.SetVec3("spot_light.ambient", 0.0f, 0.0f, 0.0f);
        light_shader.SetVec3("spot_light.diffuse", 1.0f, 1.0f, 1.0f);
        light_shader.SetVec3("spot_light.specular", 1.0f, 1.0f, 1.0f);
        light_shader.SetFloat("spot_light.constant", 1.0f);
        light_shader.SetFloat("spot_light.linear", 0.09f);
        light_shader.SetFloat("spot_light.quadratic", 0.032f);
        light_shader.SetFloat("spot_light.cutoff", glm::cos(glm::radians(12.5f)));
        light_shader.SetFloat("spot_light.outer_cutoff", glm::cos(glm::radians(15.0f)));

        glm::mat4 projection = glm::perspective(glm::radians(camera.fov), (float)window_width / (float)window_height, 0.1f, 100.0f);
        glm::mat4 view = camera.GetViewMatrix();
        light_shader.SetMat4("projection", projection);
        light_shader.SetMat4("view", view);
        glm::mat4 model = glm::mat4(1.0f);
        light_shader.SetMat4("model", model);

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, diffuse_map);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, specular_map);

        glBindVertexArray(cube_vao);
        for (unsigned int i = 0; i < 10; ++i) {
            glm::mat4 model = glm::mat4(1.0f);
            model = glm::translate(model, cube_positions[i]);
            float angle = 20.0f * i;
            model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
            light_shader.SetMat4("model", model);
            glDrawArrays(GL_TRIANGLES, 0, 36);
        }

        lamp_shader.Use();
        lamp_shader.SetMat4("projection", projection);
        lamp_shader.SetMat4("view", view);

        glBindVertexArray(light_vao);
        for (unsigned int i = 0; i < 4; ++i) {
            model = glm::mat4(1.0f);
            model = glm::translate(model, lamp_positions[i]);
            model = glm::scale(model, glm::vec3(0.2f));
            lamp_shader.SetMat4("model", model);
            glDrawArrays(GL_TRIANGLES, 0, 36);
        }

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glDeleteVertexArrays(1, &cube_vao);
    glDeleteVertexArrays(1, &light_vao);
    glDeleteBuffers(1, &vbo);

    glfwTerminate();
    return 0;
}

运行效果:

运行效果

调整光源属性即可获得其它视觉效果:

其它视觉效果

Last modification:March 21st, 2020 at 04:25 pm
如果觉得我的文章对你有用,请随意赞赏