OpenGL基础51:泛光

 

一、泛光(Bloom)

如果一个物体的颜色值接近1.0,怎么确定它是否是光源呢?毕竟一张白纸它的颜色也可以是1.0

在此之前,分辨光源往往都是靠的经验,例如你知道这个物体它是个路灯,又或者说根据周围的光照来判断光的中心等等,其实只要使用的模型,都没什么大的问题,而很多游戏中为了体现出更真实的光源,都会给光源加上泛光,以体现出光源向四周发散的光芒,提升光照的效果:

泛光效果只是一个渲染技巧,本质上就是一个模糊处理,它并不能反映出真实的物理世界,但尽管如此,一个完美的算法依旧可以大幅提升画面的效果,泛光的实现可能相对于之前要复杂一些,但是整体难度并没有太高,大致的流程如下:

  1. 走正常渲染流程,将渲染得到的结果放入颜色缓冲A中,并同时提取亮色(光源)放入颜色缓冲B中
  2. 对于从颜色缓冲B得到的亮色纹理,多次进行模糊处理,最终可以得到一个只有光源且带了泛光效果的纹理C
  3. 混合纹理C和纹理A

很多地方在讲泛光时都会提到HDR,是因为在HDR场景中能够更精准的提取亮色,但是不依赖HDR也可以,例如你可以直接提取所有的放射光贴图

minecraft的光影材质包

 

二、多渲染目标(MRT、Multiple Render Targets)

之前是将渲染的结果存储在一张纹理中,然后紧接着对着整张纹理进行二次处理,例如HDR和模糊之类的,但要实现泛光就需要渲染两张纹理,一张为正常的渲染结果,另一张为提取的亮色

确实可以渲染两次,但是有个叫做多渲染目标的小技巧,可以直接一次渲染出两个结果:

  1. 为什么一定要筛出亮色:为了后面进行模糊采样时,只对亮色进行采样
  2. 为什么需要特意将亮色处理到一张纹理中:后面在计算模糊时,需要对这张只有亮色的纹理进行多次的着色处理,如果不分开,每次在处理时都需要重新计算一次光照以筛出亮色,不然上一次模糊的结果会混到场景中

和顶点着色器的位置值一样,片段着色器的输出也可以设定位置值:

layout (location = 0) out vec4 fragColor;
layout (location = 1) out vec4 lightColor;
void main()
{
    //……

    fragColor = vec4(…);        //渲染到第一张纹理
    lightColor = vec4(…);       //渲染到第二张纹理
}

对于FBO,只要对应绑定两个颜色缓冲就可以了,当把一个纹理链接到帧缓冲的颜色缓冲上时,可以指定一个颜色附件,之前一直都是GL_COLOR_ATTACHMENT0,这次分别指定两个颜色附件,就要用到GL_COLOR_ATTACHMENT1了

使用多个像素着色器输出的必要条件是:有多个颜色缓冲附加到了当前绑定的帧缓冲对象上,因此别忘了使用glDrawBuffers指定要绘制到的颜色缓冲区的列表

void main()
{
    //……
    GLuint FBO, RBO;
    glGenFramebuffers(1, &FBO);
    glBindFramebuffer(GL_FRAMEBUFFER, FBO);

    GLuint texNum = 2;
    GLuint* textureColorBuffer = getAttachmentTexture(texNum);
    glGenRenderbuffers(1, &RBO);
    glBindRenderbuffer(GL_RENDERBUFFER, RBO);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, WIDTH, HEIGHT);
    glBindRenderbuffer(GL_RENDERBUFFER, 0);

    for (GLuint i = 0; i < texNum; i++)
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, textureColorBuffer[i], 0);
    GLuint attachments[5] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3, GL_COLOR_ATTACHMENT4 };
    glDrawBuffers(texNum, attachments);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, RBO);
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << endl;
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    //……
}

GLuint* getAttachmentTexture(GLuint num)
{
    GLuint *textureID;
    textureID = new GLuint[num];
    glGenTextures(num, textureID);
    for (GLuint i = 0; i < num; i++)
    {
        glBindTexture(GL_TEXTURE_2D, textureID[i]);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, WIDTH, HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    }
    glBindTexture(GL_TEXTURE_2D, 0);
    return textureID;
}

当渲染到这个帧缓冲的时候,只要着色器对布局使用location修饰符,fragment就会写入对应的颜色缓冲

提取亮色的逻辑如下,关于需要注意的细节:

  • 在判断一个点亮度是否大于某个值时,最好用对应的灰度值,灰度值并不是简单的RGB三维加在一起除以3,而是对RGB使用了3个不同的比例参数以进行计算,这个特殊值(0.2126, 0.7152, 0.0722)是根据人眼根据光线的感知差得出的
  • 别忘了考虑纯色光源,(0, 0, 1)会是一个纯蓝色的光源,而(0.8, 0.8, 0.8)却有可能只是被照亮的物体表面,当然还有放射光贴图,很多模型上的光源都是用它来展现的
fragColor = vec4(result, 1.0f);
float brightness = dot(fragColor.rgb, vec3(0.2126, 0.7152, 0.0722));
if(brightness > 1.0 || vec3(texture(material.texture_emission1, texCoords)) != vec3(0.0))
    lightColor = vec4(fragColor.rgb, 1.0);

 

三、高斯模糊

之前用Kernel矩阵实现过一个非常简单的模糊效果,也就是取图像中所有周围像素的均值,现在可以尝试用一个更好的模糊算法:高斯模糊

高斯模糊的采样曲线如下,片段与当前采样点的距离越小,采样比重就越大,如果考虑精度,整体采样的范围大概是 32 x 32,也就是每一个片段的最终颜色,都是从周围 32 x 32 范围内的纹理中采样决定

这和定义一个 32 x 32 的Kernel矩阵一样,但是这一步计算复杂度貌似太高了,因为每个纹理点都需要采样 1024 次,整体复杂度是  O(pr^2),其中p为分辨率、r为高斯模糊采样半径,因此需要找一个优化的方法:

考虑到高斯模糊最是一种图像滤波器,它是用正态分布计算模糊模板,并使用该模板与原图像做卷积运算达到模糊图像的目的的,而其中的正态分布正是高斯函数,这也正对应了上面那张“钟形函数”的图片,对应的函数公式为:

G(x, y)=\frac{1}{2 \pi \sigma^{2}} e^{-\frac{x^{2}+y^{2}}{2 \sigma^{2}}}

其中,σ 是正态分布的标准差,σ 值越大,图像越模糊,x 和 y 决定了模糊半径,这个二维函数有个很好的性质:它可以转换为两个一维函数的乘积:

G(x)=\frac{1}{\sqrt{2 \pi \sigma^{2}}} e^{-\frac{x^{2}}{2 \sigma^{2}}}

这就意味着,高斯模糊可以水平垂直交替分开处理,具体来说:我们准备两个帧缓冲C和D专门来做这个算法,先在C中进行垂直采样,处理完毕放入D缓冲,紧接着在D中进行横向采样

关于具体的算法:

因为 σ 可以给定,所以可以直接确定每个采样比例的常数值,着色器代码如下:

#version 330 core
out vec4 color;
in vec2 texIn;
uniform sampler2D gaussTexture;
uniform bool horizontal;
uniform float weight[5] = float[](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
void main()
{             
    vec2 tex_offset = 1.0 / textureSize(gaussTexture, 0);
    vec3 result = texture(gaussTexture, texIn).rgb * weight[0]; 
    if(horizontal)
    {
        for(int i = 1; i < 5; ++i)
        {
            result += texture(gaussTexture, texIn + vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
            result += texture(gaussTexture, texIn - vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
        }
    }
    else
    {
        for(int i = 1; i < 5; ++i)
        {
            result += texture(gaussTexture, texIn + vec2(0.0, tex_offset.y * i)).rgb * weight[i];
            result += texture(gaussTexture, texIn - vec2(0.0, tex_offset.y * i)).rgb * weight[i];
        }
    }
    color = vec4(result, 1.0);
}

然后就是主代码,想要进行多次高斯模糊,就要在两个帧缓冲之间来回倒腾,这种感觉有点像某些需要两个相同容器的算法一样,一个容器中计算的结果放入另一个容器,然后另一个容器继续相同的算法,结果再放入第一个容器……

例子中,先是用提取出来的亮区纹理所在的帧缓冲作为第一个帧缓冲,后面每次循环再根据打算渲染的是水平还是垂直来绑定两个缓冲其中之一,并将另一个绑定为纹理进行模糊,代码中总共模糊的次数是5次(=垂直水平各5次)

在使用相同的高斯权重的情况下,进行高斯模糊的次数越多,模糊的强度就越大

GLuint gaussFBO[2];
GLuint* gaussColorbuffers = getAttachmentTexture(2);
glGenFramebuffers(2, gaussFBO);
for (GLuint i = 0; i < 2; i++)
{
    glBindFramebuffer(GL_FRAMEBUFFER, gaussFBO[i]);
    glBindTexture(GL_TEXTURE_2D, gaussColorbuffers[i]);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gaussColorbuffers[i], 0);
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << endl;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);

//……
while (!glfwWindowShouldClose(window))
{
    //……
    GLuint amount = 10;
    bool horizontal = true;
    bool first_iteration = true;
    shaderBlur.Use();
    for (GLuint i = 0; i < amount; i++)
    {
        glBindFramebuffer(GL_FRAMEBUFFER, gaussFBO[horizontal]);
        glUniform1i(glGetUniformLocation(shaderBlur.Program, "horizontal"), horizontal);
        glActiveTexture(GL_TEXTURE1);
        glUniform1i(glGetUniformLocation(shaderBlur.Program, "gaussTexture"), 1);
        glBindTexture(GL_TEXTURE_2D, first_iteration ? textureColorBuffer[1] : gaussColorbuffers[!horizontal]);
        renderQuad();
        horizontal = !horizontal;
        if (first_iteration)
            first_iteration = false;
    }
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    //……
}

好了,这下复杂度就由 32 * 32 降到了 32 + 32,也就是 O(pr),考虑到傅里叶变换的过程,如果采样半径足够大,还可以FFT优化,复杂度再降到 O(p\log_{2}r),不过这就不属于这篇文章的范畴了

 

四、纹理混合

搞定了高斯模糊后,最后就可以得到两个最终需要的纹理:标准渲染结果,以及进行高斯模糊处理过的亮色纹理

混合就可以了,这是最后一步显示的着色器:

#version 330 core
in vec2 texIn;
out vec4 color;
uniform sampler2D screenTexture;
uniform sampler2D bloomTexture;
const float offset = 1.0 / 500;
void main()
{
    vec3 bloomColor = texture(bloomTexture, texIn).rgb;
    vec3 result = col + bloomColor;
    result = vec3(1.0) - exp(-result);
    result = pow(result, vec3(1.0 / 2.2));
    color = vec4(result, 1.0);
}

放下完整的主代码和效果吧:

#include<iostream>
#include<opengl/glew.h>
#define GLEW_STATIC
#include<GLFW/glfw3.h>
#include"Shader.h"
#include"Camera.h"
#include"Light.h"
#include<glm/glm.hpp>
#include<glm/gtc/matrix_transform.hpp>
#include<glm/gtc/type_ptr.hpp>
#include"Mesh.h"
#include"Model.h"
#include<opengl/freeglut.h>
#include<SOIL.h>

bool keys[1024];
Camera camera;
GLfloat lastX, lastY;
bool firstMouse = true;
bool openSpotLight = true;
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
GLuint loadCubemap(vector<const GLchar*> faces);
GLuint* getAttachmentTexture(GLuint num);
GLuint getMultiSampleTexture(GLuint samples);           //MSAA几重采样
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void cameraMove();
void renderQuad();
const GLuint WIDTH = 800, HEIGHT = 600;

int main()
{
    //------------------------------------------------------初始化---------------------------------------------------------
    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);
    glfwMakeContextCurrent(window);
    glfwSetKeyCallback(window, key_callback);
    glfwSetCursorPosCallback(window, mouse_callback);
    glfwSetScrollCallback(window, scroll_callback);

    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
    glewExperimental = GL_TRUE;
    glewInit();

    int width, height;
    glfwGetFramebufferSize(window, &width, &height);
    glViewport(0, 0, width, height);

    Shader shaderObj("ObjVShader.vert", "ObjFShader.frag", "ObjGShader.geom");
    Shader shaderObjToDebug("ObjNormalVShader.vert", "ObjNormalFShader.frag", "ObjNormalGShader.geom");
    Shader shaderObjWithoutTex("ObjVShaderWithoutTex.vert", "ObjFShaderWithoutTex.frag");
    Shader shaderLight("LightVShader.vert", "LightFShader.frag");
    Shader shaderSkyBox("SkyboxVShader.vert", "SkyboxFShader.frag");
    Shader shaderScreen("ScreenVShader.vert", "ScreenFShader.frag");
    Shader shaderBlur("GaussVShader.vert", "GaussFShader.frag");

    //------------------------------------------------------数据定义---------------------------------------------------------
    glm::vec3 positions[] = 
    {
        glm::vec3(0.0f, 0.0f, 0.0f),
        glm::vec3(0.0f, 0.89f, 0.0f),
        glm::vec3(0.0f, 1.78f, 0.0f),
        glm::vec3(-2.0f, 0.0f, 0.0f),
        glm::vec3(-2.0f, 0.89f, 0.0f),
        glm::vec3(-3.0f, 0.0f, 0.0f),
        glm::vec3(-2.0f, 0.0f, 1.0f),
        glm::vec3(-1.0f, 0.0f, 4.0f),
    };
    glm::vec3 wallPositions[] =
    {
        glm::vec3(-1, 0.3, -0.802),
        glm::vec3(-5.07, 0.3, -0.8),
        glm::vec3(3.07, 0.3, -0.8),
    };
    glm::vec3 pointLightPositions[] = 
    {
       glm::vec3(-4.0f, 2.6f, -0.25f),
       glm::vec3(1000000.0f, 0.8f, 2.0f),
       glm::vec3(1000000.0f, 0.8f, 1.0f),
    };
    GLfloat skyboxVertices[] = 
    {
        -1.0f,  1.0f, -1.0f,
        -1.0f, -1.0f, -1.0f,
         1.0f, -1.0f, -1.0f,
         1.0f, -1.0f, -1.0f,
         1.0f,  1.0f, -1.0f,
        -1.0f,  1.0f, -1.0f,

        -1.0f, -1.0f,  1.0f,
        -1.0f, -1.0f, -1.0f,
        -1.0f,  1.0f, -1.0f,
        -1.0f,  1.0f, -1.0f,
        -1.0f,  1.0f,  1.0f,
        -1.0f, -1.0f,  1.0f,

         1.0f, -1.0f, -1.0f,
         1.0f, -1.0f,  1.0f,
         1.0f,  1.0f,  1.0f,
         1.0f,  1.0f,  1.0f,
         1.0f,  1.0f, -1.0f,
         1.0f, -1.0f, -1.0f,

        -1.0f, -1.0f,  1.0f,
        -1.0f,  1.0f,  1.0f,
         1.0f,  1.0f,  1.0f,
         1.0f,  1.0f,  1.0f,
         1.0f, -1.0f,  1.0f,
        -1.0f, -1.0f,  1.0f,

        -1.0f,  1.0f, -1.0f,
         1.0f,  1.0f, -1.0f,
         1.0f,  1.0f,  1.0f,
         1.0f,  1.0f,  1.0f,
        -1.0f,  1.0f,  1.0f,
        -1.0f,  1.0f, -1.0f,

        -1.0f, -1.0f, -1.0f,
        -1.0f, -1.0f,  1.0f,
         1.0f, -1.0f, -1.0f,
         1.0f, -1.0f, -1.0f,
        -1.0f, -1.0f,  1.0f,
         1.0f, -1.0f,  1.0f
    };

    //------------------------------------------------------VAO & VBO---------------------------------------------------------

    GLuint skyboxVAO, skyboxVBO;
    glGenVertexArrays(1, &skyboxVAO);
    glGenBuffers(1, &skyboxVBO);
    glBindVertexArray(skyboxVAO);
    glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), &skyboxVertices, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);

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

    //------------------------------------------------------UBO---------------------------------------------------------
    GLuint UBO;
    glGenBuffers(1, &UBO);
    glBindBuffer(GL_UNIFORM_BUFFER, UBO);
    glBufferData(GL_UNIFORM_BUFFER, 2 * sizeof(glm::mat4), NULL, GL_STATIC_DRAW);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);
    glBindBufferRange(GL_UNIFORM_BUFFER, 0, UBO, 0, 2 * sizeof(glm::mat4));

    //------------------------------------------------------帧缓冲---------------------------------------------------------
    GLuint FBO, RBO;
    glGenFramebuffers(1, &FBO);
    glBindFramebuffer(GL_FRAMEBUFFER, FBO);

    GLuint texNum = 2;
    GLuint* textureColorBuffer = getAttachmentTexture(texNum);
    glGenRenderbuffers(1, &RBO);
    glBindRenderbuffer(GL_RENDERBUFFER, RBO);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, WIDTH, HEIGHT);
    glBindRenderbuffer(GL_RENDERBUFFER, 0);

    for (GLuint i = 0; i < texNum; i++)
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, textureColorBuffer[i], 0);
    GLuint attachments[5] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3, GL_COLOR_ATTACHMENT4 };
    glDrawBuffers(texNum, attachments);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, RBO);
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << endl;
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    GLuint gaussFBO[2];
    GLuint* gaussColorbuffers = getAttachmentTexture(2);
    glGenFramebuffers(2, gaussFBO);
    for (GLuint i = 0; i < 2; i++)
    {
        glBindFramebuffer(GL_FRAMEBUFFER, gaussFBO[i]);
        glBindTexture(GL_TEXTURE_2D, gaussColorbuffers[i]);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gaussColorbuffers[i], 0);
        if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
            cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << endl;
    }
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    //------------------------------------------------------设置状态、加载模型和天空盒---------------------------------------------------------
    glEnable(GL_CULL_FACE);
    glEnable(GL_MULTISAMPLE);
    glEnable(GL_STENCIL_TEST);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_PROGRAM_POINT_SIZE);
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);             //模板测试和深度测试都成功时,将对应像素的模板值设置为用glStencilFunc函数设置的ref值
    glEnable(GL_BLEND);
    glEnable(GL_PROGRAM_POINT_SIZE);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    Model wood("Object/wood/file.fbx", "Object/wood/file.fbx");
    Model ground("Object/ground/ground.fbx", "Object/ground");
    Model wall("Object/wall/stonewall.fbx", "Object/wall");
    Model lightObj("Object/light/file.fbx", "Object/light");

    vector<const GLchar*> faces;
    faces.push_back("Texture/Skybox/StarryNight1024/Right.jpg");
    faces.push_back("Texture/Skybox/StarryNight1024/Left.jpg");
    faces.push_back("Texture/Skybox/StarryNight1024/Up.jpg");
    faces.push_back("Texture/Skybox/StarryNight1024/Down.jpg");
    faces.push_back("Texture/Skybox/StarryNight1024/Back.jpg");
    faces.push_back("Texture/Skybox/StarryNight1024/Front.jpg");
    GLuint cubemapTexture = loadCubemap(faces);

    //------------------------------------------------------实例化---------------------------------------------------------
    glm::mat4* modelMatrices;
    modelMatrices = new glm::mat4[1000];
    glm::mat4 model = glm::mat4(1.0f);
    for (GLuint i = 0; i <= 7; i++)
    {
        model = glm::translate(glm::mat4(1.0f), positions[i]);
        model = glm::scale(model, glm::vec3(0.01f));
        modelMatrices[i] = model;
    }
    wood.UpdateModelMatrix(modelMatrices, 8);

    GLint groundIndex = 0;
    for (int i = -1; i <= 2; i++)
    {
        for (int j = 0; j <= 2; j++)
        {
            model = glm::translate(glm::mat4(1.0f), glm::vec3(i * 3.52f, -0.05f, j * 3.72f));
            model = glm::scale(model, glm::vec3(0.1f));
            model = glm::rotate(model, glm::radians(90.0f), glm::vec3(-1.0f, 0.0f, 0.0f));
            modelMatrices[groundIndex++] = model;
        }
    }
    ground.UpdateModelMatrix(modelMatrices, groundIndex);

    for (GLuint i = 0; i <= 2; i++)
    {
        model = glm::translate(glm::mat4(1.0f), wallPositions[i]);
        model = glm::scale(model, glm::vec3(0.15, 0.3, 0.15));
        model = glm::rotate(model, glm::radians(90.0f), glm::vec3(-1.0f, 0.0f, 0.0f));
        modelMatrices[i] = model;
    }
    wall.UpdateModelMatrix(modelMatrices, 3);

    model = glm::translate(glm::mat4(1.0f), glm::vec3(-4.0f, 2.3f, -0.25f));
    model = glm::scale(model, glm::vec3(0.015, 0.015, 0.015));
    model = glm::rotate(model, glm::radians(90.0f), glm::vec3(-1.0f, 0.0f, 0.0f));
    modelMatrices[0] = model;
    lightObj.UpdateModelMatrix(modelMatrices, 1);
    delete[] modelMatrices;

    Light light;
    light.AddSunLight(glm::vec3(-0.2f, -1.0f, -0.3f), glm::vec3(0.03f, 0.03f, 0.03f), glm::vec3(0.036f, 0.036f, 0.036f));
    for (int i = 0; i <= 2; i++)
        light.AddPointLight(glm::vec3(pointLightPositions[i].x, pointLightPositions[i].y, pointLightPositions[i].z), glm::vec3(0.6, 0.6, 0.6), glm::vec3(0.45, 0.45, 0.45));
    light.AddSpotLight(glm::vec3(camera.Position.x, camera.Position.y, camera.Position.z), glm::vec3(camera.Front.x, camera.Front.y, camera.Front.z), 
        glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f));

    //------------------------------------------------------渲染ing---------------------------------------------------------
    while (!glfwWindowShouldClose(window))
    {
        glfwPollEvents();
        glBindFramebuffer(GL_FRAMEBUFFER, FBO);
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glStencilMask(0xFF);                  //设置模板缓冲区可写入,如果设置为不可写入之后清空模板缓冲区,将会清空失败!毕竟不可写入了
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        cameraMove();

        glm::mat4 view = camera.GetViewMatrix();
        glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (GLfloat)WIDTH / (GLfloat)HEIGHT, 0.1f, 100.0f);
        glBindBuffer(GL_UNIFORM_BUFFER, UBO);
        glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), glm::value_ptr(view));
        glBufferSubData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(projection));
        glBindBuffer(GL_UNIFORM_BUFFER, 0);
        
        glStencilFunc(GL_ALWAYS, 0xFF, 0xFF);
        shaderObj.Use();
        light.spotLight.clear();
        if (openSpotLight)
        {
            light.AddSpotLight(glm::vec3(camera.Position.x, camera.Position.y, camera.Position.z), glm::vec3(camera.Front.x, camera.Front.y, camera.Front.z),
                glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f));
        }
        light.AppAllLightToShader(shaderObj.Program);
        glUniform3f(glGetUniformLocation(shaderObj.Program, "viewPos"), camera.Position.x, camera.Position.y, camera.Position.z);
        wall.Draw(shaderObj, 3);
        wood.Draw(shaderObj, 8);
        ground.Draw(shaderObj, groundIndex + 1);
        lightObj.Draw(shaderObj, 1);

        //--------------------------------------------------天空盒-----------------------------------------------------
        shaderSkyBox.Use();
        glDepthFunc(GL_LEQUAL);
        view = glm::mat4(glm::mat3(camera.GetViewMatrix()));
        glUniformMatrix4fv(glGetUniformLocation(shaderSkyBox.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(glGetUniformLocation(shaderSkyBox.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
        glBindVertexArray(skyboxVAO);
        glActiveTexture(GL_TEXTURE1);
        glUniform1i(glGetUniformLocation(shaderSkyBox.Program, "skybox"), 1);
        glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);
        glDepthFunc(GL_LESS);
        
        //--------------------------------------------------高斯模糊-----------------------------------------------------
        GLuint amount = 10;
        bool horizontal = true;
        bool first_iteration = true;
        shaderBlur.Use();
        for (GLuint i = 0; i < amount; i++)
        {
            glBindFramebuffer(GL_FRAMEBUFFER, gaussFBO[horizontal]);
            glUniform1i(glGetUniformLocation(shaderBlur.Program, "horizontal"), horizontal);
            glActiveTexture(GL_TEXTURE1);
            glUniform1i(glGetUniformLocation(shaderBlur.Program, "gaussTexture"), 1);
            glBindTexture(GL_TEXTURE_2D, first_iteration ? textureColorBuffer[1] : gaussColorbuffers[!horizontal]);
            renderQuad();
            horizontal = !horizontal;
            if (first_iteration)
                first_iteration = false;
        }
        glBindFramebuffer(GL_FRAMEBUFFER, 0);

        //--------------------------------------------------默认帧缓冲--------------------------------------------------
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        shaderScreen.Use();
        glActiveTexture(GL_TEXTURE1);
        glUniform1i(glGetUniformLocation(shaderScreen.Program, "screenTexture"), 1);
        glBindTexture(GL_TEXTURE_2D, textureColorBuffer[0]);
        glActiveTexture(GL_TEXTURE2);
        glUniform1i(glGetUniformLocation(shaderScreen.Program, "bloomTexture"), 2);
        glBindTexture(GL_TEXTURE_2D, gaussColorbuffers[!horizontal]);
        renderQuad();

        glfwSwapBuffers(window);
    }

    //------------------------------------------------------解绑---------------------------------------------------------
    glDeleteFramebuffers(1, &FBO);
    glfwTerminate();
    return 0;
}

GLuint quadVAO = 0;
GLuint quadVBO;
void renderQuad()
{
    if (quadVAO == 0)
    {
        GLfloat quadVertices[] =
        {
            -1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
            -1.0f, -1.0f, 0.0f, 0.0f, 0.0f,
             1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
             1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
        };
        glGenVertexArrays(1, &quadVAO);
        glGenBuffers(1, &quadVBO);
        glBindVertexArray(quadVAO);
        glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
        glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
        glEnableVertexAttribArray(1);

        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindVertexArray(0);
    }
    glBindVertexArray(quadVAO);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glBindVertexArray(0);
}

GLuint getMultiSampleTexture(GLuint samples)
{
    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, texture);
    glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGB, WIDTH, HEIGHT, GL_TRUE);
    glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);
    return texture;
}

GLuint loadCubemap(vector<const GLchar*> faces)
{
    GLuint textureID;
    glGenTextures(1, &textureID);

    int width, height;
    unsigned char* image;
    glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
    for (GLuint i = 0; i < faces.size(); i++)
    {
        image = SOIL_load_image(faces[i], &width, &height, 0, SOIL_LOAD_RGB);
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_SRGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
        SOIL_free_image_data(image);
    }
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
    glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
    return textureID;
}

GLuint* getAttachmentTexture(GLuint num)
{
    GLuint *textureID;
    textureID = new GLuint[num];
    glGenTextures(num, textureID);
    for (GLuint i = 0; i < num; i++)
    {
        glBindTexture(GL_TEXTURE_2D, textureID[i]);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, WIDTH, HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    }
    glBindTexture(GL_TEXTURE_2D, 0);
    return textureID;
}

GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;
void cameraMove()
{
    GLfloat currentFrame = glfwGetTime();
    deltaTime = currentFrame - lastFrame;
    lastFrame = currentFrame;

    GLfloat cameraSpeed = 1.0f * deltaTime;
    if (keys[GLFW_KEY_W])
        camera.ProcessKeyboard(Camera_Movement(FORWARD), deltaTime);
    if (keys[GLFW_KEY_S])
        camera.ProcessKeyboard(Camera_Movement(BACKWARD), deltaTime);
    if (keys[GLFW_KEY_A])
        camera.ProcessKeyboard(Camera_Movement(LEFT), deltaTime);
    if (keys[GLFW_KEY_D])
        camera.ProcessKeyboard(Camera_Movement(RIGHT), deltaTime);
}

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);
    if (key == GLFW_KEY_TAB && action == GLFW_PRESS)
        openSpotLight = !openSpotLight;
    if (action == GLFW_PRESS)           //如果当前是按下操作
        keys[key] = true;
    else if (action == GLFW_RELEASE)            //松开键盘
        keys[key] = false;
}

void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    camera.ProcessMouseScroll(yoffset);
}

void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }
    GLfloat xoffset = xpos - lastX;
    GLfloat yoffset = lastY - ypos;
    lastX = xpos;
    lastY = ypos;
    
    GLfloat sensitivity = 0.05;
    xoffset *= sensitivity;
    yoffset *= sensitivity;
    
    camera.ProcessMouseMovement(xoffset, yoffset);
}

这只是最简单的高斯模糊,后面还可以讨论如何通过优化高斯模糊来更显著提升泛光效果

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值