光源(投光物)
将光投射(Cast)到物体的光源叫做投光物(Light Caster)。 主要的分类:
平行光,点光源,聚光
平行光 > 当一个光源处于很远的地方时,来自光源的每条光线就会近似于互相平行。不论物体和者观察者的位置, > 看起来好像所有的光都来自于同一个方向。当我们使用一个假设光源处于无限远处的模型时, > 它就被称为定向光,因为它的所有光线都有着相同的方向,它与光源的位置是没有关系的
struct Light { // vec3 position; // 因为要使用的是方向,就不需要位置来计算方向 vec3 direction; vec3 ambient; vec3 diffuse; vec3 specular; }; ... void main() { vec3 lightDir = normalize(-light.direction); ... }点光源
定向光对于照亮整个场景的全局光源是非常棒的,但除了定向光之外我们也需要一些分散在场景中的点光源(Point Light) 。点光源是处于世界中某一个位置的光源,它会朝着所有方向发光,但光线会随着距离逐渐衰减。想象作为投光物的灯泡和火把,它们都是点光源
衰减 > 为了实现趋向真实的效果, 衰减公式 $ F_{att} = , d 代表到光源距离,K_c、K_l、K_q 可查表。 $
距离 常数项 一次项 二次项 7 1.0 0.7 13 1.0 0.35 0.44 20 1.0 0.22 0.20 32 1.0 0.14 0.07 50 1.0 0.09 0.032 65 1.0 0.07 0.017 100 1.0 0.045 0.0075 160 1.0 0.027 0.0028 200 1.0 0.022 0.0019 325 1.0 0.014 0.0007 600 1.0 0.007 0.0002 3250 1.0 0.0014 0.000007 struct Light { vec3 position; vec3 ambient; vec3 diffuse; vec3 specular; float constant; //K_c float linear; //K_l float quadratic;//K_q };代码中设置衰减参数
cpp lightingShader.setFloat("light.constant", 1.0f); lightingShader.setFloat("light.linear", 0.09f); lightingShader.setFloat("light.quadratic", 0.032f);计算光照
GLSL内建的length函数计算距离float distance = length(light.position - FragPos); float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance)); //。。。。。 //应用衰减参数 ambient *= attenuation; diffuse *= attenuation; specular *= attenuation;
聚光灯(Spotlight) > OpenGL中聚光是用一个世界空间位置、一个方向和一个切光角(Cutoff Angle)来表示的,切光角指定了聚光的半径(译注:是圆锥的半径不是距光源距离那个半径)。 > 对于每个片段,我们会计算片段是否位于聚光的切光方向之间(也就是在锥形内),如果是的话,我们就会相应地照亮片段。
LightDir:从片段指向光源的向量。 SpotDir:聚光所指向的方向。
Phi\(\phi\):指定了聚光半径的切光角。落在这个角度之外的物体都不会被这个聚光所照亮。theta\(\theta\):LightDir向量和SpotDir向量之间的夹角。在聚光内部的话θ值应该比ϕ值小。- 结构定义
struct Light { vec3 position; vec3 direction; float cutOff; ... };- 设置参数
lightingShader.setVec3("light.position", camera.Position); lightingShader.setVec3("light.direction", camera.Front); lightingShader.setFloat("light.cutOff", glm::cos(glm::radians(12.5f)));- 区域判定, 使用
clamp 函数
float theta = dot(lightDir, normalize(-light.direction)); if(theta > light.cutOff) { // 执行光照计算 } else // 否则,使用环境光,让场景在聚光之外时不至于完全黑暗 color = vec4(light.ambient * vec3(texture(material.diffuse, TexCoords)), 1.0);- 边缘软化 >
种看起来边缘平滑的聚光,我们需要模拟聚光有一个内圆锥(Inner
Cone)和一个外圆锥(Outer
Cone)。我们可以将内圆锥设置为上一部分中的那个圆锥,但我们也需要一个外圆锥,来让光从内圆锥逐渐减暗,直到外圆锥的边界。
- 衰减公式 $ I= , 这里ϵ(Epsilon)是内(ϕ)和外圆锥(γ)之间的余弦值差(ϵ=ϕ−γ)。最终的I值就是在当前片段聚光的强度。 $
θ θ(角度) ϕ(内光切) ϕ(角度) γ(外光切) γ(角度) ϵ I 0.87 30 0.91 25 0.82 35 0.91 - 0.82 = 0.09 0.87 - 0.82 / 0.09 = 0.56 0.9 26 0.91 25 0.82 35 0.91 - 0.82 = 0.09 0.9 - 0.82 / 0.09 = 0.89 0.97 14 0.91 25 0.82 35 0.91 - 0.82 = 0.09 0.97 - 0.82 / 0.09 = 1.67 0.83 34 0.91 25 0.82 35 0.91 - 0.82 = 0.09 0.83 - 0.82 / 0.09 = 0.11 0.64 50 0.91 25 0.82 35 0.91 - 0.82 = 0.09 0.64 - 0.82 / 0.09 = -2.0 0.966 15 0.9978 12.5 0.953 17.5 0.966 - 0.953 = 0.0448 0.966 - 0.953 / 0.0448 = 0.29
完整代码
#include <glad/glad.h> #include <GLFW/glfw3.h> #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/type_ptr.hpp> #include <shader_m.h> #include <camera.h> #include <iostream> using namespace std; void framebuffer_size_callback(GLFWwindow* window, int width, int height); void mouse_callback(GLFWwindow* window, double xpos, double ypos); void scroll_callback(GLFWwindow* window, double xoffset, double yoffset); void processInput(GLFWwindow *window); unsigned int loadTexture(const char *path); // settings const int wWidth = 800, wHeight = 600; const char* wName = "LearnOpenGL"; // camera Camera camera(glm::vec3(0.0f, 0.0f, 3.0f)); float lastX = wWidth / 2.0f; float lastY = wHeight / 2.0f; bool firstMouse = true; // timing float deltaTime = 0.0f; float lastFrame = 0.0f; // lighting glm::vec3 lightPos(0.f, 0.4f, 1.0f); int main() { //1. 初始化 if (glfwInit() == GLFW_FALSE) { cout << "init glfw fail"; return 1; } //2. 基本设置 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); #if __APPLE__ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); #endif //3. 创建窗口 GLFWwindow *window = glfwCreateWindow(wWidth, wHeight, wName, nullptr, nullptr); if (window == nullptr) { cout << "Failed to create GLFW window"; goto TERMINATE; //return 1; } //4. 当前主线程上下文窗口 glfwMakeContextCurrent(window); //5. 使用glad管理OpenGL指针。 if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout << "Failed to initialize GLAD" << std::endl; goto TERMINATE; //return 1; } //6. 设置视口 glViewport(0, 0, wWidth, wHeight); //7. 设置resize回调,对应刷新视口 glfwMakeContextCurrent(window); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); glfwSetCursorPosCallback(window, mouse_callback); glfwSetScrollCallback(window, scroll_callback); // build and compile our shader zprogram // ------------------------------------ Shader lightingShader("res/shaders/light_maps.vs", "res/shaders/light_maps.fs"); Shader textureShader("res/shaders/texture.vs", "res/shaders/texture1.fs"); Shader lightCubeShader("res/shaders/cube.vs", "res/shaders/white.fs"); float vertices[] = { // positions // normals // texture coords -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, -0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f }; unsigned int VBO, cubeVAO; glGenVertexArrays(1, &cubeVAO); glGenBuffers(1, &VBO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glBindVertexArray(cubeVAO); 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); // second, configure the light's VAO (VBO stays the same; the vertices are the same for the light object which is also a 3D cube) unsigned int lightCubeVAO; glGenVertexArrays(1, &lightCubeVAO); glBindVertexArray(lightCubeVAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); // note that we update the lamp's position attribute's stride to reflect the updated buffer data glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); // load textures (we now use a utility function to keep the code more organized) // ----------------------------------------------------------------------------- unsigned int diffuseMap = loadTexture("./res/textures/container2.png"); unsigned int specularMap = loadTexture("./res/textures/container2_specular.png"); float verticesMMap[] = { //位置 // 基础颜色 // 贴图uv 0.6f, 0.6f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top right 0.6f, 0.9f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom right 0.9f, 0.9f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom left 0.9f, 0.6f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // top left }; unsigned int indicesMMap[] = { 0, 1, 3, // 第一个三角形 1, 2, 3 // 第二个三角形 }; unsigned int mmapVBO, mmapVAO, mmapEBO; glGenVertexArrays(1, &mmapVAO); glBindVertexArray(mmapVAO); glGenBuffers(1, &mmapVBO); glBindBuffer(GL_ARRAY_BUFFER, mmapVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(verticesMMap), verticesMMap, GL_STATIC_DRAW); glGenBuffers(1, &mmapEBO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mmapEBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indicesMMap), indicesMMap, GL_STATIC_DRAW); //1. 指定顶点的顶点属性的结构,顶点 3个float,从0开始,整体每一组数据8个。 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); //2. 指定颜色的顶点属性的结构,三色 3个float,从3开始,整体每一组数据8个。 glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); //3. 指定uv的顶点属性的结构,uv 2个float,从6开始,整体每一组数据8个。 glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float))); glEnableVertexAttribArray(2); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); //指定采样器贴图储存的位置 textureShader.use(); textureShader.setInt("texture1", 0); lightingShader.use(); lightingShader.setInt("material.diffuse", 0); lightingShader.setInt("material.specular", 1); glEnable(GL_DEPTH_TEST); while (!glfwWindowShouldClose(window)) { // per-frame time logic // -------------------- float currentFrame = glfwGetTime(); deltaTime = currentFrame - lastFrame; lastFrame = currentFrame; // input // ----- processInput(window); glClearColor(0.1f, 0.1f, 0.1f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // be sure to activate shader when setting uniforms/drawing objects lightingShader.use(); lightingShader.setVec3("light.position", lightPos); lightingShader.setVec3("viewPos", camera.Position); // light properties lightingShader.setVec3("light.ambient", 0.3f, 0.3f, 0.3f); lightingShader.setVec3("light.diffuse", 0.5f, 0.5f, 0.5f); lightingShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f); // material properties lightingShader.setFloat("material.shininess", 64.0f); // view/projection transformations glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)wWidth / (float)wHeight, 0.1f, 100.0f); glm::mat4 view = camera.GetViewMatrix(); lightingShader.setMat4("projection", projection); lightingShader.setMat4("view", view); // world transformation glm::mat4 model = glm::mat4(1.0f); lightingShader.setMat4("model", model); // bind diffuse map glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, diffuseMap); // bind diffuse map glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, specularMap); // render the cube glBindVertexArray(cubeVAO); glDrawArrays(GL_TRIANGLES, 0, 36); // also draw the lamp object lightCubeShader.use(); lightCubeShader.setMat4("projection", projection); lightCubeShader.setMat4("view", view); model = glm::mat4(1.0f); model = glm::translate(model, lightPos); model = glm::scale(model, glm::vec3(0.2f)); // a smaller cube lightCubeShader.setMat4("model", model); glBindVertexArray(lightCubeVAO); glDrawArrays(GL_TRIANGLES, 0, 36); // bind Texture1 /*glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, diffuseMap);*/ textureShader.use(); glBindVertexArray(mmapVAO); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); glfwSwapBuffers(window); glfwPollEvents(); } glDeleteVertexArrays(1, &cubeVAO); glDeleteVertexArrays(1, &lightCubeVAO); glDeleteBuffers(1, &VBO); glDeleteVertexArrays(1, &mmapVAO); glDeleteBuffers(1, &mmapVBO); glDeleteBuffers(1, &mmapEBO); TERMINATE: glfwTerminate(); return 0; } // process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly // --------------------------------------------------------------------------------------------------------- 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, deltaTime); if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) camera.ProcessKeyboard(BACKWARD, deltaTime); if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) camera.ProcessKeyboard(LEFT, deltaTime); if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) camera.ProcessKeyboard(RIGHT, deltaTime); } // glfw: whenever the window size changed (by OS or user resize) this callback function executes // --------------------------------------------------------------------------------------------- void framebuffer_size_callback(GLFWwindow* window, int width, int 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); } // glfw: whenever the mouse moves, this callback is called // ------------------------------------------------------- void mouse_callback(GLFWwindow* window, double xpos, double ypos) { if (firstMouse) { lastX = xpos; lastY = ypos; firstMouse = false; } float xoffset = xpos - lastX; float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to top lastX = xpos; lastY = ypos; camera.ProcessMouseMovement(xoffset, yoffset); } // glfw: whenever the mouse scroll wheel scrolls, this callback is called // ---------------------------------------------------------------------- void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { camera.ProcessMouseScroll(yoffset); } // utility function for loading a 2D texture from file // --------------------------------------------------- unsigned int loadTexture(char const * path) { unsigned int textureID; glGenTextures(1, &textureID); int width, height, nrComponents; unsigned char *data = stbi_load(path, &width, &height, &nrComponents, 0); if (data) { GLenum format; if (nrComponents == 1) format = GL_RED; else if (nrComponents == 3) format = GL_RGB; else if (nrComponents == 4) format = GL_RGBA; glBindTexture(GL_TEXTURE_2D, textureID); glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); 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_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); stbi_image_free(data); } else { std::cout << "Texture failed to load at path: " << path << std::endl; stbi_image_free(data); } return textureID; }