LearnOpenGL学习笔记8:光照基础

Posted 键盘春秋

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LearnOpenGL学习笔记8:光照基础相关的知识,希望对你有一定的参考价值。

一、 颜色

当我们把光源的颜色与物体的颜色相乘,所得到的就是这个物体所反射该光源的颜色(也就是我们感知到的颜色)。

二、 光照基础

光照模型都是基于我们对光的物理特性的理解。其中一个模型被称为冯氏光照模型(Phong Lighting Model)。冯氏光照模型的主要结构由3个元素组成:环境(Ambient)、漫反射(Diffuse)和镜面(Specular)光照。这些光照元素看起来像下面这样:

  • 环境光照(Ambient
    Lighting):即使在黑暗的情况下,世界上也仍然有一些光亮(月亮、一个来自远处的光),所以物体永远不会是完全黑暗的。我们使用环境光照来模拟这种情况,也就是无论如何永远都给物体一些颜色。
  • 漫反射光照(Diffuse Lighting):模拟一个发光物对物体的方向性影响(Directional
    Impact)。它是冯氏光照模型最显著的组成部分。面向光源的一面比其他面会更亮。
  • 镜面光照(Specular Lighting):模拟有光泽物体上面出现的亮点。镜面光照的颜色,相比于物体的颜色更倾向于光的颜色。

1. 环境光照

把环境光照添加到场景里非常简单。我们用光的颜色乘以一个(数值)很小常量环境因子,再乘以物体的颜色,然后使用它作为片段的颜色:

void main()

    float ambientStrength = 0.1f;
    vec3 ambient = ambientStrength * lightColor;
    vec3 result = ambient * objectColor;
    color = vec4(result, 1.0f);

2. 漫反射光照

漫反射光使物体上与光线排布越近的片段越能从光源处获得更多的亮度。

如果光线垂直于物体表面,这束光对物体的影响会最大化(译注:更亮)。为了测量光线和片段的角度,我们使用一个叫做法向量(Normal Vector)的东西,它是垂直于片段表面的一种向量(这里以黄色箭头表示)。两个向量之间的角度就能够根据点乘计算出来。
注意,我们使用的是单位向量(Unit Vector,长度是1的向量)取得两个向量夹角的余弦值,所以我们需要确保所有的向量都被标准化,否则点乘返回的值就不仅仅是余弦值了。
所以,我们需要些什么来计算漫反射光照?

  • 法向量:一个垂直于顶点表面的向量。
  • 定向的光线:作为光的位置和片段的位置之间的向量差的方向向量。为了计算这个光线,我们需要光的位置向量和片段的位置向量。

法向量

法向量(Normal Vector)是垂直于顶点表面的(单位)向量。由于顶点自身并没有表面(它只是空间中一个独立的点),我们利用顶点周围的顶点计算出这个顶点的表面。
因为我们向顶点数组添加了额外的数据,所以我们应该更新光照的顶点着色器:

#version 330 core
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;

我们不会更新发光物的着色器或者属性配置,但是我们必须至少修改一下顶点属性指针来适应新的顶点数组的大小:

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid *)0);
glEnableVertexAttribArray(0);

所有光照的计算需要在片段着色器里进行,所以我们需要把法向量由顶点着色器传递到片段着色器。我们这么做:

out vec3 Normal;
void main()

    gl_Position = projection * view * model * vec4(position, 1.0f);
    Normal = normal;

剩下要做的事情是,在片段着色器中定义相应的输入值:

in vec3 Normal;

计算漫反射光照

每个顶点现在都有了法向量,但是我们仍然需要光的位置向量和片段的位置向量。

uniform vec3 lightPos;

然后再游戏循环中(外面也可以,因为它不会变)更新uniform。我们使用在前面教程中声明的lightPos向量作为光源位置:

GLint lightPosLoc = glGetUniformLocation(lightingShader.Program, "lightPos");
glUniform3f(lightPosLoc, lightPos.x, lightPos.y, lightPos.z);

最后,我们还需要片段的位置(Position)。我们会在世界空间中进行所有的光照计算,因此我们需要一个在世界空间中的顶点位置。我们可以通过把顶点位置属性乘以模型矩阵(Model Matrix, 只用模型矩阵不需要用观察和投影矩阵)来把它变换到世界空间坐标。这个在顶点着色器中很容易完成,所以让我们就声明一个输出(out)变量,然后计算它的世界空间坐标:

out vec3 FragPos;
out vec3 Normal;

void main()

    gl_Position = projection * view * model * vec4(position, 1.0f);
    FragPos = vec3(model * vec4(position, 1.0f));
    Normal = normal;

最后,在片段着色器中添加相应的输入变量。

in vec3 FragPos;

现在,所有需要的变量都设置好了,我们可以在片段着色器中开始光照的计算了。

我们需要做的第一件事是计算光源和片段位置之间的方向向量。前面提到,光的方向向量是光的位置向量与片段的位置向量之间的向量差。你可能记得,在变换教程中,我们简单的通过两个向量相减的方式计算向量差。我们同样希望确保所有相关向量最后都转换为单位向量,所以我们把法线和方向向量这个结果都进行标准化:

vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);

下一步,我们对norm和lightDir向量进行点乘,来计算光对当前片段的实际的散射影响。结果值再乘以光的颜色,得到散射因子。两个向量之间的角度越大,散射因子就会越小:

float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;

如果两个向量之间的角度大于90度,点乘的结果就会变成负数,这样会导致散射因子变为负数。为此,我们使用max函数返回两个参数之间较大的参数,从而保证散射因子不会变成负数。负数的颜色是没有实际定义的,所以最好避免它,除非你是那种古怪的艺术家。

既然我们有了一个环境光照颜色和一个散射光颜色,我们把它们相加,然后把结果乘以物体的颜色,来获得片段最后的输出颜色。

vec3 result = (ambient + diffuse) * objectColor;
color = vec4(result, 1.0f);

法向量补充说明

首先,法向量只是一个方向向量,不能表达空间中的特定位置。同时,法向量没有齐次坐标(顶点位置中的w分量)。这意味着,平移不应该影响到法向量。因此,如果我们打算把法向量乘以一个模型矩阵,我们就要把模型矩阵左上角的3×3矩阵从模型矩阵中移除(译注:所谓移除就是设置为0),它是模型矩阵的平移部分(注意,我们也可以把法向量的w分量设置为0,再乘以4×4矩阵;同样可以移除平移)。对于法向量,我们只能对它应用缩放(Scale)和旋转(Rotation)变换。
其次,如果模型矩阵执行了不等比缩放,法向量就不再垂直于表面了,顶点就会以这种方式被改变了。因此,我们不能用这样的模型矩阵去乘以法向量。下面的图展示了应用了不等比缩放的矩阵对法向量的影响:

修复这个行为的诀窍是使用另一个为法向量专门定制的模型矩阵。这个矩阵称之为正规矩阵(Normal Matrix),正规矩阵被定义为“模型矩阵左上角的逆矩阵的转置矩阵”。
在顶点着色器中,我们可以使用inverse和transpose函数自己生成正规矩阵,inverse和transpose函数对所有类型矩阵都有效。注意,我们也要把这个被处理过的矩阵强制转换为3×3矩阵,这是为了保证它失去了平移属性,之后它才能乘以法向量。

Normal = mat3(transpose(inverse(model))) * normal;

对于着色器来说,逆矩阵也是一种开销比较大的操作,因此,无论何时,在着色器中只要可能就应该尽量避免逆操作,因为它们必须为你场景中的每个顶点进行这样的处理。以学习的目的这样做很好,但是对于一个对于效率有要求的应用来说,在绘制之前,你最好用CPU计算出正规矩阵,然后通过uniform把值传递给着色器(和模型矩阵一样)。

3. 镜面光照

如果我们想象物体表面像一面镜子一样,那么,无论我们从哪里去看那个表面所反射的光,镜面光照都会达到最大化。你可以从下面的图片看到效果:

观察向量是镜面光照的一个附加变量,我们可以使用观察者世界空间位置(Viewer’s World Space Position)和片段的位置来计算。之后,我们计算镜面光亮度,用它乘以光的颜色,在用它加上作为之前计算的光照颜色。

我们选择在世界空间(World Space)进行光照计算,但是大多数人趋向于在观察空间(View Space)进行光照计算。在观察空间计算的好处是,观察者的位置总是(0, 0, 0),所以这样你直接就获得了观察者位置。可是,我发现出于学习的目的,在世界空间计算光照更符合直觉。如果你仍然希望在视野空间计算光照的话,那就使用观察矩阵应用到所有相关的需要变换的向量(不要忘记,也要改变正规矩阵)。
为了得到观察者的世界空间坐标,我们简单地使用摄像机对象的位置坐标代替(它就是观察者)。所以我们把另一个uniform添加到片段着色器,把相应的摄像机位置坐标传给片段着色器:

uniform vec3 viewPos;

GLint viewPosLoc = glGetUniformLocation(lightingShader.Program, "viewPos");
glUniform3f(viewPosLoc, camera.Position.x, camera.Position.y, camera.Position.z);

现在我们已经获得所有需要的变量,可以计算高光亮度了。首先,我们定义一个镜面强度(Specular Intensity)变量specularStrength,给镜面高光一个中等亮度颜色,这样就不会产生过度的影响了。

float specularStrength = 0.5f;

如果我们把它设置为1.0f,我们会得到一个对于珊瑚色立方体来说过度明亮的镜面亮度因子。下一节教程,我们会讨论所有这些光照亮度的合理设置,以及它们是如何影响物体的。下一步,我们计算视线方向坐标,和沿法线轴的对应的反射坐标:

vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);

需要注意的是我们使用了lightDir向量的相反数。reflect函数要求的第一个是从光源指向片段位置的向量,但是lightDir当前是从片段指向光源的向量(由先前我们计算lightDir向量时,(减数和被减数)减法的顺序决定)。为了保证我们得到正确的reflect坐标,我们通过lightDir向量的相反数获得它的方向的反向。第二个参数要求是一个法向量,所以我们提供的是已标准化的norm向量。

剩下要做的是计算镜面亮度分量。下面的代码完成了这件事:

float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor;

我们先计算视线方向与反射方向的点乘(确保它不是负值),然后得到它的32次幂。这个32是高光的发光值(Shininess)。一个物体的发光值越高,反射光的能力越强,散射得越少,高光点越小。在下面的图片里,你会看到不同发光值对视觉(效果)的影响:

我们不希望镜面成分过于显眼,所以我们把指数设置为32。剩下的最后一件事情是把它添加到环境光颜色和散射光颜色里,然后再乘以物体颜色:

vec3 result = (ambient + diffuse + specular) * objectColor;
color = vec4(result, 1.0f);

转载请注明出处:http://blog.csdn.net/ylbs110/article/details/52506098

三、 示例

代码:

#include <iostream>
using namespace std;
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
// SOIL
#include <SOIL\\SOIL.h>

#include <glm\\glm.hpp>
#include <glm\\gtc\\matrix_transform.hpp>
#include <glm\\gtc\\type_ptr.hpp>

#include "Shader.h"
#include "Camera.h"

const GLuint WIDTH = 800, HEIGHT = 600;

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
GLuint loadTexture(string fileName, GLint REPEAT, GLint FILTER);
void do_movement();
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
// Shaders
const GLchar* vertexShaderSource = "#version 330 core\\n"
"layout (location = 0) in vec3 position;\\n"//顶点数据传入的坐标
"layout (location = 1) in vec3 normal;\\n"//顶点数据传入的颜色
"layout (location = 2) in vec2 texCoord;\\n"//顶点数据传入的纹理坐标
"uniform vec4 offset;\\n"
"uniform float mixPar;\\n"
"uniform mat4 model;\\n"
"uniform mat4 view;\\n"
"uniform mat4 projection;\\n"
"out vec3 FragPos;\\n"
"out vec3 Normal;\\n"
"out vec2 TexCoord;\\n"
"out float MixPar;\\n"
"void main()\\n"
"\\n"
"gl_Position =projection * view * model* vec4(position.x, position.y, position.z, 1.0)+offset;\\n"
"FragPos = vec3(model * vec4(position, 1.0f));\\n"
"Normal=normal;\\n"
"TexCoord=texCoord;\\n"
"MixPar=mixPar;\\n"
"\\0";

const GLchar* fragmentShaderSource = "#version 330 core\\n"
"out vec4 color;\\n"
"in vec3 FragPos;\\n"
"in vec3 Normal;\\n"
"in vec2 TexCoord;\\n"
"in float MixPar;\\n"
"uniform sampler2D ourTexture1;\\n"
"uniform sampler2D ourTexture2;\\n"
"uniform vec3 objectColor;\\n"
"uniform vec3 lightColor;\\n"
"uniform vec3 lightPos;\\n"
"uniform vec3 viewPos;\\n"
"float specularStrength = 0.5f;\\n"
"void main()\\n"
"\\n"
"float ambientStrength = 0.1f;\\n"
"vec3 ambient = ambientStrength * lightColor;\\n"

"vec3 norm = normalize(Normal);\\n"
"vec3 lightDir = normalize(lightPos - FragPos);\\n"
"float diff = max(dot(norm, lightDir), 0.0);\\n"
"vec3 diffuse = diff * lightColor;\\n"

"vec3 viewDir = normalize(viewPos - FragPos);\\n"
"vec3 reflectDir = reflect(-lightDir, norm);\\n"
"float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);\\n"
"vec3 specular = specularStrength * spec * lightColor;\\n"

"vec3 result = (ambient + diffuse+specular) * objectColor;\\n"
"color = vec4(result, 1.0f);\\n"
//"color =result*mix(texture(ourTexture1, TexCoord),texture(ourTexture2, vec2(TexCoord.x,1-TexCoord.y)),MixPar)+vec4(lightColor * objectColor, 1.0f);\\n"
"\\n\\0";
const GLchar* fragmentShaderSourceLight = "#version 330 core\\n"
"out vec4 color;\\n"
"void main()\\n"
"\\n"
"color = vec4(1.0f);\\n"
"\\n\\0";
Camera mainCamera;
Shader shader ,lightintShader;//shader
GLuint texContainer, texAwesomeface;//纹理id

float key_UD = 0.5f;//混合比例
GLuint VBO, lightVAO,VAO;

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

bool keys[1024];

GLfloat lastX = 400, lastY = 300;
GLfloat scrollSpeed = 0.05f;
bool firstMouse = true;

glm::vec3 lightPos(1.2f, 1.0f, 2.0f);
glm::mat4 model;
glm::mat4 view;
glm::mat4 projection;
GLint modelLoc;
GLint viewLoc;
GLint projectionLoc;

void shaderInit() 
    shader = Shader(vertexShaderSource, fragmentShaderSource);
    lightintShader = Shader(vertexShaderSource, fragmentShaderSourceLight);

void textureInit() 
    texContainer = loadTexture("container.jpg", GL_CLAMP_TO_EDGE, GL_LINEAR);
    texAwesomeface = loadTexture("awesomeface.png", GL_MIRRORED_REPEAT, GL_NEAREST);

GLuint loadTexture(string fileName,GLint REPEAT, GLint FILTER) 
    //创建纹理
    GLuint texture;
    glGenTextures(1, &texture);
    //绑定纹理
    glBindTexture(GL_TEXTURE_2D, texture);
    // 为当前绑定的纹理对象设置环绕、过滤方式

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, REPEAT);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, FILTER);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, FILTER);

    //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // 加载纹理
    int width, height;
    unsigned char* image = SOIL_load_image(fileName.c_str(), &width, &height, 0, SOIL_LOAD_RGB);
    // 生成纹理
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
    glGenerateMipmap(GL_TEXTURE_2D);
    //释放图像的内存并解绑纹理对象
    SOIL_free_image_data(image);
    glBindTexture(GL_TEXTURE_2D, 0);

    return texture;


void vertexObjectInit() 
    //不使用索引缓冲对象用两个三角形绘制一个梯形
    // 设置顶点缓存和属性指针
    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
    ;
    //创建索引缓冲对象
    glGenBuffers(1, &VBO);

    // 把顶点数组复制到缓冲中供OpenGL使用
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glGenVertexArrays(1, &VAO);

    glBindVertexArray(VAO);
    // 位置属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);
    // 颜色属性
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(1);

    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
    glEnableVertexAttribArray(2);

    //glBindBuffer(GL_ARRAY_BUFFER, 0);// 这个方法将顶点属性指针注册到VBO作为当前绑定顶点对象,然后我们就可以安全的解绑
    glBindVertexArray(0);// 解绑 VAO (这通常是一个很好的用来解绑任何缓存/数组并防止奇怪错误的方法)



    glGenVertexArrays(1, &lightVAO);
    glBindVertexArray(lightVAO);
    // 只需要绑定VBO不用再次设置VBO的数据,因为容器(物体)的VBO数据中已经包含了正确的立方体顶点数据
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    // 设置灯立方体的顶点属性指针(仅设置灯的顶点数据)
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);
    // 颜色属性
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(1);

    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
    glEnableVertexAttribArray(2);
    glBindVertexArray(0);


int main()

    //初始化GLFW
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    //创建窗口对象
    GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
    if (window == nullptr)
    
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    
    glfwMakeContextCurrent(window);
    //注册键盘回调
    glfwSetKeyCallback(window, key_callback);
    //注册鼠标回调
    glfwSetCursorPosCallback(window, mouse_callback);
    //注册鼠标滚轮回到
    glfwSetScrollCallback(window, scroll_callback);
    //设置光标隐藏并捕获
    //glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
    //初始化GLEW
    glewExperimental = GL_TRUE;
    if (glewInit() != GLEW_OK)
    
        std::cout << "Failed to initialize GLEW" << std::endl;
        return -1;
    
    //告诉OpenGL渲染窗口尺寸大小
    int width, height;
    glfwGetFramebufferSize(window, &width, &height);

    glViewport(0, 0, width, height);

    glEnable(GL_DEPTH_TEST);
    //初始化并绑定shaders
    shaderInit();
    //初始化textures
    textureInit();
    //初始化顶点对象数据
    vertexObjectInit();

    mainCamera = Camera();
    //让窗口接受输入并保持运行
    while (!glfwWindowShouldClose(window))
    
        GLfloat currentFrame = glfwGetTime();
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;
        //检查事件
        glfwPollEvents();
        do_movement();

        //渲染指令
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        //GLfloat timeValue = glfwGetTime();
        //lightPos.x = lightPos.x+ sin(timeValue);
        //lightPos.y = lightPos.y + cos(timeValue);

        //绘制光源
        model = glm::mat4();
        model = glm::translate(model, lightPos);
        model = glm::scale(model, glm::vec3(0.2f));
        lightintShader.Use();
        //GLint vertexColorLocation = glGetUniformLocation(lightintShader.Program, "offset");
        //glUniform4f(vertexColorLocation, offsetx, offsety, 0.0f, 1.0f);
        // 设置模型、视图和投影矩阵uniform
        modelLoc = glGetUniformLocation(lightintShader.Program, "model");
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        viewLoc = glGetUniformLocation(lightintShader.Program, "view");
        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
        projectionLoc = glGetUniformLocation(lightintShader.Program, "projection");
        glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
            // 绘制灯立方体对象
        glBindVertexArray(lightVAO);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);



        //绘制箱子      
        shader.Use();
        //绑定两张贴图
        //glActiveTexture(GL_TEXTURE0);
        //glBindTexture(GL_TEXTURE_2D, texContainer);
        //glUniform1i(glGetUniformLocation(shader.Program, "ourTexture1"), 0);
        //glActiveTexture(GL_TEXTURE1);
        //glBindTexture(GL_TEXTURE_2D, texAwesomeface);
        //glUniform1i(glGetUniformLocation(shader.Program, "ourTexture2"), 1);

        // 更新uniform值
        //设置混合比例
        GLint mixPar = glGetUniformLocation(shader.Program, "mixPar");
        glUniform1f(mixPar, key_UD);
        model = glm::rotate(model, (GLfloat)glfwGetTime() * 5.0f, glm::vec3(0.5f, 1.0f, 0.0f));     
        view = mainCamera.GetViewMatrix();      
        projection = glm::perspective(mainCamera.Zoom*scrollSpeed, (float)(WIDTH / HEIGHT), 0.1f, 100.0f);

        modelLoc = glGetUniformLocation(shader.Program, "model");
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        viewLoc = glGetUniformLocation(shader.Program, "view");
        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
        projectionLoc = glGetUniformLocation(shader.Program, "projection");
        glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));


        // 在此之前不要忘记首先'使用'对应的着色器程序(来设定uniform)
        GLint objectColorLoc = glGetUniformLocation(shader.Program, "objectColor");
        GLint lightColorLoc = glGetUniformLocation(shader.Program, "lightColor");
        glUniform3f(objectColorLoc, 1.0f, 0.5f, 0.31f);// 我们所熟悉的珊瑚红
        glUniform3f(lightColorLoc, 1.0f, 1.0f, 1.0f); // 依旧把光源设置为白色
        GLint lightPosLoc = glGetUniformLocation(shader.Program, "lightPos");
        glUniform3f(lightPosLoc, lightPos.x, lightPos.y, lightPos.z);
        GLint viewPosLoc = glGetUniformLocation(shader.Program, "viewPos");
        glUniform3f(viewPosLoc, mainCamera.Position.x, mainCamera.Position.y, mainCamera.Position.z);


        glm::vec3 cubePositions[] = 
            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)
        ;

        glBindVertexArray(VAO);
        for (GLuint i = 0; i < 10; i++)
        
            glm::mat4 model;
            model = glm::translate(model, cubePositions[i]);
            if (i < 4)                 
                GLfloat angle = (GLfloat)glfwGetTime()*5.0f * i;
                model = glm::rotate(model, angle, glm::vec3(1.0f, 0.3f, 0.5f));
                glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
            
            else
            
                glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
            
            glDrawArrays(GL_TRIANGLES, 0, 36);
        
        glBindVertexArray(0);

        //交换缓冲
        glfwSwapBuffers(window);
    
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);

    //释放资源
    glfwTerminate();
    return 0;

void do_movement()

    // 摄像机控制
    GLfloat cameraSpeed = 5.0f* deltaTime;
    if (keys[GLFW_KEY_W])
        mainCamera.ProcessKeyboard(FORWARD, deltaTime);
    if (keys[GLFW_KEY_S])
        mainCamera.ProcessKeyboard(BACKWARD, deltaTime);
    if (keys[GLFW_KEY_A])
        mainCamera.ProcessKeyboard(LEFT, deltaTime);
    if (keys[GLFW_KEY_D])
        mainCamera.ProcessKeyboard(RIGHT, deltaTime);

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)

    // 当用户按下ESC键,我们设置window窗口的WindowShouldClose属性为true
    // 关闭应用程序
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);
    if (key == GLFW_KEY_UP&& action == GLFW_PRESS)//按下UP键增加混合比例
        key_UD = key_UD + 0.1f;
    if (key == GLFW_KEY_DOWN&& action == GLFW_PRESS)//按下DOWN减小混合比例
        key_UD = key_UD - 0.1f;

    if (action == GLFW_PRESS)
        keys[key] = true;
    else if (action == GLFW_RELEASE)
        keys[key] = false;

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;  // Reversed since y-coordinates go from bottom to left 

    lastX = xpos;
    lastY = ypos;

    mainCamera.ProcessMouseMovement(xoffset, yoffset);


void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)

    cout << yoffset << endl;
    mainCamera.ProcessMouseScroll(yoffset);

结果:

以上是关于LearnOpenGL学习笔记8:光照基础的主要内容,如果未能解决你的问题,请参考以下文章

Unity Shader入门精要学习笔记 - 第6章 开始 Unity 中的基础光照

OpenGL光照2:材质

OpenGL光照3:光源

LearnOpenGL学习笔记9:材质

Unity Shader 光照模型(基础公式和代码实现)

Unity Shaders学习笔记之为创建Half Lambert光照模型