OpenGL高级-帧缓冲

效果展示

在这里插入图片描述

知识点

 颜色缓冲记录帧的颜色值,深度缓冲记录深度信息,模板缓冲允许我们基于一些条件丢弃指定片段。这几种缓冲结合起来叫做帧缓冲(FrameBuffer),它被储存于内存中。
 OpenGL给了我们自己定义帧缓冲的自由,我们可以选择性的定义自己的颜色缓冲、深度和模板缓冲。
 定义自己的帧缓冲好处就是:我们现在可以自由的获取已经渲染场景中的任何像素,然后把它当作一个纹理图像了。我们可以在片段着色器中创建一些有意思的效果。所有这些有意思的效果统称为后处理特效。
 我们目前所做的渲染操作都是是在默认的帧缓冲之上进行的。当你创建了你的窗口的时候默认帧缓冲就被创建和配置好了(GLFW为我们做了这件事)。通过创建我们自己的帧缓冲我们能够获得一种额外的渲染方式。
 后续所有渲染操作将渲染到当前绑定的帧缓冲的附加缓冲中,由于我们的帧缓冲不是默认的帧缓冲,渲染命令对窗口的视频输出不会产生任何影响。出于这个原因,它被称为离屏渲染(off-screen rendering),就是渲染到一个另外的缓冲中。为了让所有的渲染操作对主窗口产生影响我们必须通过绑定为0来使默认帧缓冲被激活。
 我们需要把一个或更多的附件附加到帧缓冲上。一个附件就是一个内存地址,这个内存地址里面包含一个为帧缓冲准备的缓冲,它可以是个图像。当创建一个附件的时候我们有两种方式可以采用:纹理或渲染缓冲(renderbuffer)对象。
 当把一个纹理附加到帧缓冲上的时候,所有渲染命令会写入到纹理上,就像它是一个普通的颜色/深度或者模板缓冲一样。使用纹理的好处是,所有渲染操作的结果都会被储存为一个纹理图像,这样我们就可以简单的在着色器中使用了。
 如果你打算把整个屏幕渲染到一个或大或小的纹理上,你需要用新的纹理的尺寸作为参数再次调用glViewport(要在渲染到你的帧缓冲之前做好),否则只有一小部分纹理或屏幕能够绘制到纹理上。
 除颜色附件以外,我们还可以附加一个深度和一个模板纹理到帧缓冲对象上。
 。和纹理图像一样,渲染缓冲对象也是一个缓冲,它可以是一堆字节、整数、像素或者其他东西。渲染缓冲对象的一大优点是,它以OpenGL原生渲染格式储存它的数据,因此在离屏渲染到帧缓冲的时候,这些数据就相当于被优化过的了。
 然而,渲染缓冲对象通常是只写的,不能修改它们(就像获取纹理,不能写入纹理一样)。可以用glReadPixels函数去读取,函数返回一个当前绑定的帧缓冲的特定像素区域,而不是直接返回附件本身。
 我们在每个渲染迭代末尾使用的那个glfwSwapBuffers函数,同样以渲染缓冲对象实现:我们简单地写入到一个渲染缓冲图像,最后交换到另一个里。渲染缓冲对象对于这种操作来说很完美。
 由于渲染缓冲对象通常是只写的,它们经常作为深度和模板附件来使用,由于大多数时候,我们不需要从深度和模板缓冲中读取数据,但仍关心深度和模板测试。我们就需要有深度和模板值提供给测试,但不需要对这些值进行采样(sample),所以深度缓冲对象是完全符合的。当我们不去从这些缓冲中采样的时候,渲染缓冲对象通常很合适,因为它们等于是被优化过的。
 在帧缓冲项目中,渲染缓冲对象可以提供一些优化,但更重要的是知道何时使用渲染缓冲对象,何时使用纹理。通常的规则是,如果你永远都不需要从特定的缓冲中进行采样,渲染缓冲对象对特定缓冲是更明智的选择。如果哪天需要从比如颜色或深度值这样的特定缓冲采样数据的话,你最好还是使用纹理附件。从执行效率角度考虑,它不会对效率有太大影响。
 我们把它的内部给事设置为GL_DEPTH24_STENCIL8,对于我们的目的来说这个精确度已经足够了。
&esmp;还要保证解绑帧缓冲,这样我们才不会意外渲染到错误的帧缓冲上。
 现在帧缓冲做好了,我们要做的全部就是渲染到帧缓冲上,而不是绑定到帧缓冲对象的默认缓冲。余下所有命令会影响到当前绑定的帧缓冲上。所有深度和模板操作同样会从当前绑定的帧缓冲的深度和模板附件中读取,当然,得是在它们可用的情况下。如果你遗漏了比如深度缓冲,所有深度测试就不会工作,因为当前绑定的帧缓冲里没有深度缓冲。
 然而这有什么好处呢?好处就是我们现在可以自由的获取已经渲染场景中的任何像素,然后把它当作一个纹理图像了,我们可以在片段着色器中创建一些有意思的效果。所有这些有意思的效果统称为后处理特效。

源代码

渲染场景的顶点着色器:

#version 330 core
// 传入局部坐标下的顶点坐标
layout( location = 0 ) in vec3 position;
layout (location = 1) in vec2 texCoords;

// 传入变换矩阵
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

out vec2 TexCoords;

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

渲染场景的片段着色器:

#version 330 core
in vec2 TexCoords;
out vec4 color;

uniform sampler2D screenTexture;

void main()
{
    color = texture(screenTexture, TexCoords);
}

渲染屏幕的顶点着色器:

#version 330 core
layout (location = 0) in vec2 position;
layout (location = 1) in vec2 texCoords;

out vec2 TexCoords;

void main()
{
    gl_Position = vec4(position.x, position.y, 0.0f, 1.0f);
    TexCoords = texCoords;
}

渲染屏幕的片段着色器:

#version 330 core
in vec2 TexCoords;
out vec4 color;

uniform sampler2D screenTexture;

void main()
{
    color = texture(screenTexture, TexCoords);
}

主程序:

// Std. Includes
#include <string>
#include <algorithm>
using namespace std;

// GLEW
#define GLEW_STATIC
#include <GL/glew.h>

// GLFW
#include <GLFW/glfw3.h>

// GL includes
#include "Shader.h"
#include "Camera.h"

// GLM Mathemtics
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

// Other Libs
#include <SOIL.h>

// Properties
GLuint screenWidth = 800, screenHeight = 600;

// Function prototypes
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void Do_Movement();
GLuint loadTexture(const GLchar* path, GLboolean alpha = false);
GLuint generateAttachmentTexture(GLboolean depth, GLboolean stencil);

// Camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
bool keys[1024];
GLfloat lastX = 400, lastY = 300;
bool firstMouse = true;

GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;

// The MAIN function, from here we start our application and run our Game loop
int main()
{
#pragma region "Init_Set_GLFW_GLEW" 
    // Init 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(screenWidth, screenHeight, "LearnOpenGL", nullptr, nullptr); // Windowed
    glfwMakeContextCurrent(window);

    // Set the required callback functions
    glfwSetKeyCallback(window, key_callback);
    glfwSetCursorPosCallback(window, mouse_callback);
    glfwSetScrollCallback(window, scroll_callback);

    // Options
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    // Initialize GLEW to setup the OpenGL Function pointers
    glewExperimental = GL_TRUE;
    glewInit();

    // Define the viewport dimensions
    glViewport(0, 0, screenWidth, screenHeight);

    // Setup some OpenGL options
    glDepthFunc(GL_LESS);

   #pragma endregion

    // 两个着色器
    Shader shader("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\lightVertexShader.txt", "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\lightFragmentShader.txt");
    Shader screenShader("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\vertexShader.txt", "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\fragmentShader.txt");

#pragma region "object_initialization"
    // 立方体顶点数据
    GLfloat cubeVertices[] = {
         // 位置              // 纹理
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
         0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

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

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

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

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

        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
    };
    // 地面顶点数据
    GLfloat floorVertices[] = {
        // 位置                //  纹理坐标(请注意,我们将其设置为高于1,与GL_REPEAT一起作为纹理包裹模式将导致地板纹理重复)
        5.0f,  -0.5f,  5.0f,  2.0f, 0.0f,
        -5.0f, -0.5f,  5.0f,  0.0f, 0.0f,
        -5.0f, -0.5f, -5.0f,  0.0f, 2.0f,

        5.0f,  -0.5f,  5.0f,  2.0f, 0.0f,
        -5.0f, -0.5f, -5.0f,  0.0f, 2.0f,
        5.0f,  -0.5f, -5.0f,  2.0f, 2.0f
    };
    // 四边形顶点数据
    GLfloat quadVertices[] = {   // 在“规格化设备坐标”中填充整个屏幕的四边形的顶点属性。
        // 位置        // 纹理
        -1.0f,  1.0f,  0.0f, 1.0f,
        -1.0f, -1.0f,  0.0f, 0.0f,
         1.0f, -1.0f,  1.0f, 0.0f,

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

    // 设置立方体VAO
    GLuint cubeVAO, cubeVBO;
    glGenVertexArrays(1, &cubeVAO);
    glGenBuffers(1, &cubeVBO);
    glBindVertexArray(cubeVAO);
    glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), &cubeVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glBindVertexArray(0);
    // 设置地面VAO
    GLuint floorVAO, floorVBO;
    glGenVertexArrays(1, &floorVAO);
    glGenBuffers(1, &floorVBO);
    glBindVertexArray(floorVAO);
    glBindBuffer(GL_ARRAY_BUFFER, floorVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(floorVertices), &floorVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glBindVertexArray(0);
    // 设置四边形VAO
    GLuint quadVAO, quadVBO;
    glGenVertexArrays(1, &quadVAO);
    glGenBuffers(1, &quadVBO);
    glBindVertexArray(quadVAO);
    glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)(2 * sizeof(GLfloat)));
    glBindVertexArray(0);

    // 加载纹理(立方体和地面)
    GLuint cubeTexture = loadTexture("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Resource\\container.jpg",false);
    GLuint floorTexture = loadTexture("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Resource\\hsys.jpg",false);
#pragma endregion

    // 创建帧缓冲对象framebuffer
    GLuint framebuffer;
    glGenFramebuffers(1, &framebuffer);
    // 将帧缓冲对象绑定到GL_FRAMEBUFFER上
    // 接下来所有的读、写帧缓冲的操作都会影响到当前绑定的帧缓冲
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

    // 创建一个适合于帧缓冲区的纹理附件
    GLuint textureColorbuffer = generateAttachmentTexture(false, false);
    // 将纹理附件附加到帧缓冲上
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorbuffer, 0);

    // 为深度和模板附件创建一个渲染缓冲对象(因为我们不会对这些进行采样)
    GLuint rbo;
    glGenRenderbuffers(1, &rbo);
    // 把渲染缓冲对象绑定,这样所有后续渲染缓冲操作都会影响到当前的渲染缓冲对象
    glBindRenderbuffer(GL_RENDERBUFFER, rbo);
    // 创建一个深度和模板缓冲对象(这个对象是专门被设计用于图像的),将单个renderbuffer对象用于深度和模具缓冲区
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, screenWidth, screenHeight); 
    // 为渲染缓冲对象分配了足够的内存空间后,解绑渲染缓冲
    glBindRenderbuffer(GL_RENDERBUFFER, 0);
    // 将渲染缓冲对象附加到帧缓冲的深度和模板附件上
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo); 
    
    // 现在我们实际创建了帧缓冲区并添加了所有附件,检查它现在是否真的完成了
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << endl;
    glBindFramebuffer(GL_FRAMEBUFFER, 0);


    // 显示物体的所有面,以线段方式,多边形用轮廓显示
    // glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    // 游戏循环
    while (!glfwWindowShouldClose(window))
    {
        // 设置帧时间
        GLfloat currentFrame = glfwGetTime();
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        // 检查和调用事件
        glfwPollEvents();
        Do_Movement();

        // 将帧缓冲对象绑定到GL_FRAMEBUFFER上
        // 接下来所有的读、写帧缓冲的操作都会影响到当前绑定的帧缓冲
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

 
        // 就像我们通常会做的那样。        
        // 清除所有连接的缓冲区        
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 我们没有使用模具缓冲区,为什么要麻烦清理呢?

        // 启用深度测试(当有的渲染不用深度测试时,我们需要保证深度测试默认是关闭的,只有在需要的时候才打开)
        glEnable(GL_DEPTH_TEST);
        // 设置 uniforms(使用Shader着色器真实渲染)
        shader.Use();
        glm::mat4 model = glm::mat4(1.0f);
        glm::mat4 view = camera.GetViewMatrix();
        glm::mat4 projection = glm::perspective(camera.Zoom, (float)screenWidth / (float)screenHeight, 0.1f, 100.0f);
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));

        // 渲染地面(绑定对应VAO、传入模型矩阵和纹理、执行渲染)
        glBindVertexArray(floorVAO);
        glBindTexture(GL_TEXTURE_2D, floorTexture);
        model = glm::mat4(1.0f);
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glBindVertexArray(0);
        // 渲染立方体(两个)
        glBindVertexArray(cubeVAO);
        glBindTexture(GL_TEXTURE_2D, cubeTexture);
        model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 36);
        model = glm::mat4(1.0f);
        model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f));
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);


        // 绑定到默认帧缓冲区
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        // 清除所有相关缓冲区
        glClearColor(1.0f, 1.0f, 1.0f, 1.0f); // 将透明颜色设置为白色(实际上没有必要,因为我们无论如何都看不到四边形后面)
        glClear(GL_COLOR_BUFFER_BIT);         //清除默认缓冲区中的颜色缓冲
        glDisable(GL_DEPTH_TEST);             //渲染单个四边形时,我们不关心深度信息

        // 绘制屏幕(具有attched屏幕纹理的四边形平面)
        screenShader.Use();
        // 绑定四边形的VAO
        glBindVertexArray(quadVAO);
        // 使用颜色附加纹理作为四边形的纹理
        glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
        // 绘制
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glBindVertexArray(0);

        // 交换缓冲区
        glfwSwapBuffers(window);
    }

    // Clean up
    glDeleteFramebuffers(1, &framebuffer);

    glfwTerminate();
    return 0;
}

// This function loads a texture from file. Note: texture loading functions like these are usually 
// managed by a 'Resource Manager' that manages all resources (like textures, models, audio). 
// For learning purposes we'll just define it as a utility function.
GLuint loadTexture(const GLchar* path, GLboolean alpha)
{
    //Generate texture ID and load texture data 
    GLuint textureID;
    glGenTextures(1, &textureID);
    int width, height;
    unsigned char* image = SOIL_load_image(path, &width, &height, 0, alpha ? SOIL_LOAD_RGBA : SOIL_LOAD_RGB);
    // 检测加载图片是否成功
    if (image == NULL)
        std::cout << "Image load error" << ", path:" << path;
    // Assign texture to ID
    glBindTexture(GL_TEXTURE_2D, textureID);
    glTexImage2D(GL_TEXTURE_2D, 0, alpha ? GL_RGBA : GL_RGB, width, height, 0, alpha ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, image);
    glGenerateMipmap(GL_TEXTURE_2D);

    // Parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, alpha ? GL_CLAMP_TO_EDGE : GL_REPEAT);	// Use GL_MIRRORED_REPEAT to prevent white borders. Due to interpolation it takes value from next repeat 
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, alpha ? GL_CLAMP_TO_EDGE : GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glBindTexture(GL_TEXTURE_2D, 0);
    SOIL_free_image_data(image);
    return textureID;
}

// 生成适合于帧缓冲区附件的纹理
GLuint generateAttachmentTexture(GLboolean depth, GLboolean stencil)
{
    // 纹理附件的类型
    GLenum attachment_type;
    if (!depth && !stencil)
        attachment_type = GL_RGB;   // 颜色附件
    else if (depth && !stencil)
        attachment_type = GL_DEPTH_COMPONENT;   // 深度附件
    else if (!depth && stencil)
        attachment_type = GL_STENCIL_INDEX;     // 模板附件

    //创建纹理ID并且加载纹理 
    GLuint textureID;
    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_2D, textureID);
    if (!depth && !stencil)
        glTexImage2D(GL_TEXTURE_2D, 0, attachment_type, screenWidth, screenHeight, 0, attachment_type, GL_UNSIGNED_BYTE, NULL);
    else // 同时使用模具和深度测试,需要特殊的格式参数(因此写个if else)
        glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, screenWidth, screenHeight, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL);
    // 设置纹理过滤方式(不用关心环绕方式或者Mipmap,因为在大多数时候都不需要它们)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glBindTexture(GL_TEXTURE_2D, 0);

    // 返回纹理ID
    return textureID;
}

#pragma region "User input"

// Moves/alters the camera positions based on user input
void Do_Movement()
{
    // Camera controls
    if (keys[GLFW_KEY_W])
        camera.ProcessKeyboard(FORWARD, deltaTime);
    if (keys[GLFW_KEY_S])
        camera.ProcessKeyboard(BACKWARD, deltaTime);
    if (keys[GLFW_KEY_A])
        camera.ProcessKeyboard(LEFT, deltaTime);
    if (keys[GLFW_KEY_D])
        camera.ProcessKeyboard(RIGHT, deltaTime);
}

// Is called whenever a key is pressed/released via GLFW
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 (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;

    lastX = xpos;
    lastY = ypos;

    camera.ProcessMouseMovement(xoffset, yoffset);
}

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

#pragma endregion

后期效果

 现在,整个场景渲染到了一个单独的纹理上,我们可以创建一些有趣的效果,只要简单操纵纹理数据就能做到。这部分,我们会向你展示一些流行的后期处理(Post-processing)特效,以及怎样添加一些创造性去创建出你自己的特效。

反相

 我们已经取得了渲染输出的每个颜色,所以在片段着色器里返回这些颜色的反色(Inversion)并不难。我们得到屏幕纹理的颜色,然后用1.0减去它:

void main()
{
    color = vec4(vec3(1.0 - texture(screenTexture, TexCoords)), 1.0);
}

 运行效果:
在这里插入图片描述

灰度

 另一个有意思的效果是移除所有除了黑白灰以外的颜色作用,是整个图像成为黑白的。实现它的简单的方式是获得所有颜色元素,然后将它们平均化:

void main()
{
    color = texture(screenTexture, TexCoords);
    float average = (color.r + color.g + color.b) / 3.0;
    color = vec4(average, average, average, 1.0);
}

 运行效果:
在这里插入图片描述
 这已经创造出很赞的效果了,但是人眼趋向于对绿色更敏感,对蓝色感知比较弱,所以为了获得更精确的符合人体物理的结果,我们需要使用加权通道:

void main()
{
    color = texture(screenTexture, TexCoords);
    float average = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
    color = vec4(average, average, average, 1.0);
}

 运行效果:
在这里插入图片描述

Kernel effects

 在单独纹理图像上进行后处理的另一个好处是我们可以从纹理的其他部分进行采样。比如我们可以从当前纹理值的周围采样多个纹理值。创造性地把它们结合起来就能创造出有趣的效果了。
 kernel是一个长得有点像一个小矩阵的数值数组,它中间的值中心可以映射到一个像素上,这个像素和这个像素周围的值再乘以kernel,最后再把结果相加就能得到一个值。所以,我们基本上就是给当前纹理坐标加上一个它四周的偏移量,然后基于kernel把它们结合起来。下面是一个kernel的例子:
在这里插入图片描述
 kernel对于后处理来说非常管用,因为用起来简单。网上能找到有很多实例,为了能用上kernel我们还得改改片段着色器。这里假设每个kernel都是3×3(实际上大多数都是3×3):

const float offset = 1.0 / 300;  

void main()
{
    vec2 offsets[9] = vec2[](
        vec2(-offset, offset),  // top-left
        vec2(0.0f,    offset),  // top-center
        vec2(offset,  offset),  // top-right
        vec2(-offset, 0.0f),    // center-left
        vec2(0.0f,    0.0f),    // center-center
        vec2(offset,  0.0f),    // center-right
        vec2(-offset, -offset), // bottom-left
        vec2(0.0f,    -offset), // bottom-center
        vec2(offset,  -offset)  // bottom-right
    );

    float kernel[9] = float[](
        -1, -1, -1,
        -1,  9, -1,
        -1, -1, -1
    );

    vec3 sampleTex[9];
    for(int i = 0; i < 9; i++)
    {
        sampleTex[i] = vec3(texture(screenTexture, TexCoords.st + offsets[i]));
    }
    vec3 col;
    for(int i = 0; i < 9; i++)
        col += sampleTex[i] * kernel[i];

    color = vec4(col, 1.0);
}

 运行效果:
在这里插入图片描述
 这个锐化的kernel创建的有趣的效果就好像你的玩家吞了某种麻醉剂产生的幻觉一样。

模糊

在这里插入图片描述
 运行效果:
在这里插入图片描述
 这样的模糊效果具有创建许多有趣效果的潜力.例如,我们可以随着时间的变化改变模糊量,创建出类似于某人喝醉酒的效果,或者,当我们的主角摘掉眼镜的时候增加模糊.模糊也能为我们在后面的教程中提供都颜色值进行平滑处理的能力。
 你可以看到我们一旦拥有了这个kernel的实现以后,创建一个后处理特效就不再是一件难事.最后,我们再来讨论一个流行的特效,以结束本节内容.。

边检测

在这里插入图片描述
 运行效果:
在这里插入图片描述
 在一些像Photoshop这样的软件中使用这些kernel作为图像操作工具/过滤器一点都不奇怪.因为掀开可以具有很强的平行处理能力,我们以实时进行针对每个像素的图像操作便相对容易,图像编辑工具因而更经常使用显卡来进行图像处理。

练习

 你可以使用帧缓冲来创建一个后视镜吗?做到它,你必须绘制场景两次:一次正常绘制,另一次摄像机旋转180度后绘制.尝试在你的显示器顶端创建一个小四边形,在上面应用后视镜的镜面纹理。
 实现效果:
在这里插入图片描述
 可以看到,图中后视镜中看到了另一个立方体,原本大屏幕中摄像机视角向上看,而后视镜中向下看。
 实现思路很简单:使用两个帧缓冲对象,一个默认帧缓冲对象渲染出原来的图形,一个我们自定义的帧缓冲对象(一个位于屏幕顶部的小四方体)。先使用自定义帧缓冲对象渲染出后视镜中的场景,然后再使用默认帧渲染出原本的场景,再使用默认帧绑定自定义帧的纹理附件,渲染出屏幕上方的小四方体。
 教程中对于这个练习给出的代码得到的是一模一样的图形,没有反转。因为它对偏航角和俯仰角都旋转了180°,相当于让你站着,先转向身后(偏航角旋转180°),然后再头往后仰直到看到后面 (俯仰角旋转180°)。因此看到的内容是一样的。
 我自己实现时,我发现只需要把偏航角即camaer.Yaw旋转180°即可看到后面,但是我发现如果我向上看,后视镜也是向上看的,我还想实现我向上看后视镜向下看。如果将俯仰角旋转180°,会发现和教程中一样没有效果了,我尝试将WorldUp定义为(0,-1,0),这时整个世界上下颠倒,明显不是我们想要的效果。我再尝试将上向量Up定义为(0,-1,0),结果发现没有用。
 最后我想到一个很简单的实现,就是将摄像机的Front朝向反转,即变成-Front,教程中通过调用鼠标检测函数ProcessMouseMovement来间接的调用摄像机的updateCameraVectors更新函数,然后再调用摄像机GetViewMatrix函数得到观察矩阵。如果先转换Front为-Front,然后再调用ProcessMouseMovement,由于摄像机的偏航角等一系列参数没有被更改,所以被调用的updateCameraVectors会将摄像机朝向调回Front,因此我们不调用ProcessMouseMovement。
 核心代码如下:

		shader.Use();
        glm::mat4 model = glm::mat4(1.0f);
        // 将摄像机的朝向反向
        camera.Front = -camera.Front;
        // 获取后视镜下的观察矩阵(最重要的就是观察矩阵)
        glm::mat4 view = camera.GetViewMatrix();
        // 重置回摄像机的朝向
        camera.Front = -camera.Front;
        // 这时可以更新摄像机的朝向了
        camera.ProcessMouseMovement(0, 0, false); // Pitch constraint boolean is set to true as default.
        glm::mat4 projection = glm::perspective(camera.Zoom, (float)screenWidth / (float)screenHeight, 0.1f, 100.0f);
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));

 完整代码:

// Std. Includes
#include <string>
#include <algorithm>
using namespace std;

// GLEW
#define GLEW_STATIC
#include <GL/glew.h>

// GLFW
#include <GLFW/glfw3.h>

// GL includes
#include "Shader.h"
#include "Camera.h"

// GLM Mathemtics
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

// Other Libs
#include <SOIL.h>

// Properties
GLuint screenWidth = 800, screenHeight = 600;

// Function prototypes
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void Do_Movement();
GLuint loadTexture(const GLchar* path, GLboolean alpha = false);
GLuint generateAttachmentTexture(GLboolean depth, GLboolean stencil);

// Camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
bool keys[1024];
GLfloat lastX = 400, lastY = 300;
bool firstMouse = true;

GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;

// The MAIN function, from here we start our application and run our Game loop
int main()
{
#pragma region "Init_Set_GLFW_GLEW" 
    // Init 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(screenWidth, screenHeight, "LearnOpenGL", nullptr, nullptr); // Windowed
    glfwMakeContextCurrent(window);

    // Set the required callback functions
    glfwSetKeyCallback(window, key_callback);
    glfwSetCursorPosCallback(window, mouse_callback);
    glfwSetScrollCallback(window, scroll_callback);

    // Options
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    // Initialize GLEW to setup the OpenGL Function pointers
    glewExperimental = GL_TRUE;
    glewInit();

    // Define the viewport dimensions
    glViewport(0, 0, screenWidth, screenHeight);

    // Setup some OpenGL options
    glDepthFunc(GL_LESS);

   #pragma endregion

    // 两个着色器
    Shader shader("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\lightVertexShader.txt", "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\lightFragmentShader.txt");
    Shader screenShader("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\vertexShader.txt", "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\fragmentShader.txt");

#pragma region "object_initialization"
    // 立方体顶点数据
    GLfloat cubeVertices[] = {
         // 位置              // 纹理
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
         0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

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

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

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

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

        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
    };
    // 地面顶点数据
    GLfloat floorVertices[] = {
        // 位置                //  纹理坐标(请注意,我们将其设置为高于1,与GL_REPEAT一起作为纹理包裹模式将导致地板纹理重复)
        5.0f,  -0.5f,  5.0f,  2.0f, 0.0f,
        -5.0f, -0.5f,  5.0f,  0.0f, 0.0f,
        -5.0f, -0.5f, -5.0f,  0.0f, 2.0f,

        5.0f,  -0.5f,  5.0f,  2.0f, 0.0f,
        -5.0f, -0.5f, -5.0f,  0.0f, 2.0f,
        5.0f,  -0.5f, -5.0f,  2.0f, 2.0f
    };
    // 四边形顶点数据
    GLfloat quadVertices[] = {   // Vertex attributes for a quad that in Normalized Device Coordinates. NOTE that this plane is now much smaller and at the top of the screen
       // Positions   // TexCoords
       -0.3f,  1.0f,  0.0f, 1.0f,
       -0.3f,  0.7f,  0.0f, 0.0f,
        0.3f,  0.7f,  1.0f, 0.0f,

       -0.3f,  1.0f,  0.0f, 1.0f,
        0.3f,  0.7f,  1.0f, 0.0f,
        0.3f,  1.0f,  1.0f, 1.0f
    };

    // 设置立方体VAO
    GLuint cubeVAO, cubeVBO;
    glGenVertexArrays(1, &cubeVAO);
    glGenBuffers(1, &cubeVBO);
    glBindVertexArray(cubeVAO);
    glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), &cubeVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glBindVertexArray(0);
    // 设置地面VAO
    GLuint floorVAO, floorVBO;
    glGenVertexArrays(1, &floorVAO);
    glGenBuffers(1, &floorVBO);
    glBindVertexArray(floorVAO);
    glBindBuffer(GL_ARRAY_BUFFER, floorVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(floorVertices), &floorVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glBindVertexArray(0);
    // 设置四边形VAO
    GLuint quadVAO, quadVBO;
    glGenVertexArrays(1, &quadVAO);
    glGenBuffers(1, &quadVBO);
    glBindVertexArray(quadVAO);
    glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)(2 * sizeof(GLfloat)));
    glBindVertexArray(0);

    // 加载纹理(立方体和地面)
    GLuint cubeTexture = loadTexture("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Resource\\container.jpg",false);
    GLuint floorTexture = loadTexture("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Resource\\hsys.jpg",false);
#pragma endregion

    // 创建帧缓冲对象framebuffer
    GLuint framebuffer;
    glGenFramebuffers(1, &framebuffer);
    // 将帧缓冲对象绑定到GL_FRAMEBUFFER上
    // 接下来所有的读、写帧缓冲的操作都会影响到当前绑定的帧缓冲
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

    // 创建一个适合于帧缓冲区的纹理附件
    GLuint textureColorbuffer = generateAttachmentTexture(false, false);
    // 将纹理附件附加到帧缓冲上
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorbuffer, 0);

    // 为深度和模板附件创建一个渲染缓冲对象(因为我们不会对这些进行采样)
    GLuint rbo;
    glGenRenderbuffers(1, &rbo);
    // 把渲染缓冲对象绑定,这样所有后续渲染缓冲操作都会影响到当前的渲染缓冲对象
    glBindRenderbuffer(GL_RENDERBUFFER, rbo);
    // 创建一个深度和模板缓冲对象(这个对象是专门被设计用于图像的),将单个renderbuffer对象用于深度和模具缓冲区
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, screenWidth, screenHeight); 
    // 为渲染缓冲对象分配了足够的内存空间后,解绑渲染缓冲
    glBindRenderbuffer(GL_RENDERBUFFER, 0);
    // 将渲染缓冲对象附加到帧缓冲的深度和模板附件上
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo); 
    
    // 现在我们实际创建了帧缓冲区并添加了所有附件,检查它现在是否真的完成了
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << endl;
    glBindFramebuffer(GL_FRAMEBUFFER, 0);


    // 显示物体的所有面,以线段方式,多边形用轮廓显示
    // glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    // 游戏循环
    while (!glfwWindowShouldClose(window))
    {
        // 设置帧时间
        GLfloat currentFrame = glfwGetTime();
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        // 检查和调用事件
        glfwPollEvents();
        Do_Movement();

        //
        // First render pass: Mirror texture...
        // Bind to framebuffer and draw to color texture as 
        // we normally would, but with the view camera 
        // reversed.
        // //
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        // Clear all attached buffers        
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // We're not using stencil buffer so why bother with clearing?

        glEnable(GL_DEPTH_TEST);
        // Set uniforms
        shader.Use();
        glm::mat4 model = glm::mat4(1.0f);
        camera.Front = -camera.Front;
        glm::mat4 view = camera.GetViewMatrix();
        camera.Front = -camera.Front;
        camera.ProcessMouseMovement(0, 0, false); // Pitch constraint boolean is set to true as default.
        glm::mat4 projection = glm::perspective(camera.Zoom, (float)screenWidth / (float)screenHeight, 0.1f, 100.0f);
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));

        // Floor
        glBindVertexArray(floorVAO);
        glBindTexture(GL_TEXTURE_2D, floorTexture);
        model = glm::mat4(1.0f);
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glBindVertexArray(0);
        // Cubes
        glBindVertexArray(cubeVAO);
        glBindTexture(GL_TEXTURE_2D, cubeTexture);
        model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 36);
        model = glm::mat4(1.0f);
        model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f));
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);

        /
        // Second render pass: Draw as normal
        // //
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        // Clear all attached buffers        
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // We're not using stencil buffer so why bother with clearing?

        // Reset the camera uniform to its normal orientation
        view = camera.GetViewMatrix();
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));

        // Floor
        glBindVertexArray(floorVAO);
        glBindTexture(GL_TEXTURE_2D, floorTexture);
        model = glm::mat4(1.0f);
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glBindVertexArray(0);
        // Cubes
        glBindVertexArray(cubeVAO);
        glBindTexture(GL_TEXTURE_2D, cubeTexture);
        model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 36);
        model = glm::mat4(1.0f);
        model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f));
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);

        /
        // Now also draw the mirror quad with screen texture
        // //
        glDisable(GL_DEPTH_TEST); // We disable depth information so the mirror quad is always rendered on top
        // Draw mirror
        screenShader.Use();
        glBindVertexArray(quadVAO);
        glBindTexture(GL_TEXTURE_2D, textureColorbuffer);	// Use the color attachment texture as the texture of the quad plane
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glBindVertexArray(0);


        // Swap the buffers
        glfwSwapBuffers(window);
    }

    // Clean up
    glDeleteFramebuffers(1, &framebuffer);

    glfwTerminate();
    return 0;
}

// This function loads a texture from file. Note: texture loading functions like these are usually 
// managed by a 'Resource Manager' that manages all resources (like textures, models, audio). 
// For learning purposes we'll just define it as a utility function.
GLuint loadTexture(const GLchar* path, GLboolean alpha)
{
    //Generate texture ID and load texture data 
    GLuint textureID;
    glGenTextures(1, &textureID);
    int width, height;
    unsigned char* image = SOIL_load_image(path, &width, &height, 0, alpha ? SOIL_LOAD_RGBA : SOIL_LOAD_RGB);
    // 检测加载图片是否成功
    if (image == NULL)
        std::cout << "Image load error" << ", path:" << path;
    // Assign texture to ID
    glBindTexture(GL_TEXTURE_2D, textureID);
    glTexImage2D(GL_TEXTURE_2D, 0, alpha ? GL_RGBA : GL_RGB, width, height, 0, alpha ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, image);
    glGenerateMipmap(GL_TEXTURE_2D);

    // Parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, alpha ? GL_CLAMP_TO_EDGE : GL_REPEAT);	// Use GL_MIRRORED_REPEAT to prevent white borders. Due to interpolation it takes value from next repeat 
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, alpha ? GL_CLAMP_TO_EDGE : GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glBindTexture(GL_TEXTURE_2D, 0);
    SOIL_free_image_data(image);
    return textureID;
}

// 生成适合于帧缓冲区附件的纹理
GLuint generateAttachmentTexture(GLboolean depth, GLboolean stencil)
{
    // 纹理附件的类型
    GLenum attachment_type;
    if (!depth && !stencil)
        attachment_type = GL_RGB;   // 颜色附件
    else if (depth && !stencil)
        attachment_type = GL_DEPTH_COMPONENT;   // 深度附件
    else if (!depth && stencil)
        attachment_type = GL_STENCIL_INDEX;     // 模板附件

    //创建纹理ID并且加载纹理 
    GLuint textureID;
    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_2D, textureID);
    if (!depth && !stencil)
        glTexImage2D(GL_TEXTURE_2D, 0, attachment_type, screenWidth, screenHeight, 0, attachment_type, GL_UNSIGNED_BYTE, NULL);
    else // 同时使用模具和深度测试,需要特殊的格式参数(因此写个if else)
        glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, screenWidth, screenHeight, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL);
    // 设置纹理过滤方式(不用关心环绕方式或者Mipmap,因为在大多数时候都不需要它们)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glBindTexture(GL_TEXTURE_2D, 0);

    // 返回纹理ID
    return textureID;
}

#pragma region "User input"

// Moves/alters the camera positions based on user input
void Do_Movement()
{
    // Camera controls
    if (keys[GLFW_KEY_W])
        camera.ProcessKeyboard(FORWARD, deltaTime);
    if (keys[GLFW_KEY_S])
        camera.ProcessKeyboard(BACKWARD, deltaTime);
    if (keys[GLFW_KEY_A])
        camera.ProcessKeyboard(LEFT, deltaTime);
    if (keys[GLFW_KEY_D])
        camera.ProcessKeyboard(RIGHT, deltaTime);
}

// Is called whenever a key is pressed/released via GLFW
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 (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;

    lastX = xpos;
    lastY = ypos;

    camera.ProcessMouseMovement(xoffset, yoffset);
}

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

#pragma endregion
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
译 者 序 近年来,随着计算机技术的进步,我们跨入了一个三维时代,各种扣人心弦的三维游戏、能数字化地显示天气变化的气象服务、震撼人心的3D数字化特殊效果,无不使我们体验到三维世界的全新感觉。可视化、计算机动画、虚拟现实是当今图形学领域的三大热门话题,它们的技术核心都是三维图形。 1992年7月,SGI公司首次发布了作为三维图形编程接口的OpenGL。目前它已成为国际上通用的开放式三维图形标准。一方面,OpenGL规范由ARB(OpenGL Architecture Review Board,OpenGL结构评审委员会)负责管理,充分保证了它的独立性、开放性、前瞻性和跨平台性。它可被集成到Unix、Windows NT 4.0、Windows 98、x窗口等窗口系统中。另一方面,Compaq 、IBM、 Intel、Microsoft等在计算机界具有主导作用的公司纷纷采用OpenGL图形的国际标准。各种游戏加速卡、专用加速部件都能不同程度地提高OpenGL程序的运行性能。这些都推动了OpenGL的发展,并迅速成为三维图形的国际标准。再者,SGI公司不断推出以OpenGL为基础的高级开发工具,以满足对图形工具性能日益增长的需求。这一切使得OpenGL成为最流行的三维图形开发工具。目前它已被广泛应用于CAD/CAM/CAE、地质、航空、医学图像处理、广告、艺术造型、电影后期制作等领域。 OpenGL由大量功能强大的图形函数组成,它集成了所有曲面造型、图形变换、光照、材质、纹理、像素操作、融合、反选择、雾化等复杂的计算机图形学算法。开发人员可以利用这些函数对整个三维图形轻松进行渲染,从而达到数字化现实生活景象的目的。 本书是OpenGL 参考手册的第3版,对OpenGL的函数进行了详细而简洁的说明,是程序员利用OpenGL进行程序开发的不可缺少的工具书。本书第1章是OpenGL入门,第2章对命令和例程进行了简介,第3章集中介绍了各种命令和例程,第4章介绍了定义的常量和相关命令,第5章是OpenGL参考说明,第6章是GLU的参考说明,第7章是GLX的参考说明。 本书在翻译过程中得到了国家863计划项目(863-511-942-016)的支持。参加翻译的人员还有王火亮、许宇荣、杨勤、杨颖、余牛、周贵仔、蒋丽、李岳梅,在此感谢他们的辛勤劳动。 由于计算机软件行业的飞速发展,加之时间仓促,翻译中难免会有不妥之处,如能得到您的及时指正将不胜感激。 我们的电子邮箱地址为[email protected]. 译 者 2000年9月于求是园 目 录 译者序 前言 第1章 OpenGL简介 1 1.1 OpenGL基础 1 1.1.1 OpenGL图元及命令 1 1.1.2 OpenGL是一种过程语言 1 1.1.3 OpenGL的执行模式 2 1.2 基本OpenGL操作 2 第2章 命令和例程概述 4 2.1 OpenGL处理流程 4 2.1.1 顶点 4 2.1.2 ARB绘图子集 8 2.1.3 片断 9 2.2 其他OpenGL命令 11 2.2.1 使用求值器 11 2.2.2 执行选择和反馈 11 2.2.3 显示列表的使用 12 2.2.4 模式和运行的管理 12 2.2.5 获取状态信息 12 2.3 OpenGL实用库 13 2.3.1 生成纹理操作所需的图形 13 2.3.2 坐标转换 13 2.3.3 多边形的镶嵌分块 14 2.3.4 绘制球体、圆柱和圆盘 14 2.3.5 NURBS曲线和曲面 14 2.3.6 错误处理 15 2.4 对X窗口系统的OpenGL扩展 15 2.4.1 初始化 15 2.4.2 控制绘制操作 15 第3章 命令和例程一览 18 3.1 注释 18 3.2 OpenGL命令 19 3.2.1 图元 19 3.2.2 顶点数组 19 3.2.3 坐标转换 20 3.2.4 着色与光照 20 3.2.5 剪切 21 3.2.6 光栅化 21 3.2.7 像素操作 22 3.2.8 纹理 22 3.2.9 雾 23 3.2.10 缓冲区操作 24 3.2.11 求值器 24 3.2.12 选择与反馈 25 3.2.13 显示列表 25 3.2.14 模式与执行 25 3.2.15 状态查询 26 3.3 ARB扩展 26 3.3.1 多重纹理 26 3.3.2 绘图子集 26 3.4 GLU例程 28 3.4.1 纹
前言 第1章OpenGL简介 1.1什么是OpenGL? 1.2一段简单的OpenGL代码 1.3OpenGL函数的语法 1.4OpenGL是个状态机 1.5OpenGL渲染管线 1.5.1显示列表 1.5.2求值器 1.5.3基于顶点的操作 1.5.4图元装配 1.5.5像素操作 1.5.6纹理装配 1.5.7光栅化 1.5.8片断操作 1.6与OpenGL相关的函数库 1.6.1包含文件 1.6.2GLUT,OpenGL实用工具包 1.7动画 1.7.1暂停刷新 1.7.2动画=重绘+交换 第2章状态管理和绘制几何物体 2.1绘图工具箱 2.1.1清除窗口 2.1.2指定颜色 2.1.3强制完成绘图操作 2.1.4坐标系统工具箱 2.2描述点、直线和多边形 2.2.1什么是点、直线和多边形? 2.2.2指定顶点 2.2.3OpenGL几何图元 2.3基本状态管理 2.4显示点、直线和多边形 2.4.1点的细节 2.4.2直线的细节 2.4.3多边形的细节 2.5法线向量 2.6顶点数组 2.6.1步骤1:启用数组 2.6.2步骤2:指定数组的数据 2.6.3步骤3:解引用和渲染 2.7缓冲区对象 2.7.1创建缓冲区对象 2.7.2激活缓冲区对象 2.7.3用数据分配和初始化缓冲区对象 2.7.4更新缓冲区对象的数据值 2.7.5清除缓冲区对象 2.7.6使用缓冲区对象存储顶点数据数据 2.8属性组 2.9关于创建多边形表面模型的一些提示 2.9.1例子:创建一个二十面体 第3章视图 3.1简介:照相机比喻 3.2.1一个简单的例子:绘制立方体 3.1.2通用的变换函数 3.2视图和模型变换 3.2.1对变换进行思考 3.2.2模型变换 3.2.3视图变换 3.3投影变换 3.3.1透视投影 …… 第4章颜色 第5章光照 第6章混合、抗锯齿、雾和多边形偏移 第7章显示列表 第8章绘制像素、位图、字体和图像 第9章纹理贴图 第10章缓冲区 第11章分格化和二次方程表面 第12章求值器和NURBS 第13章选择和反馈 第14章OpenGL高级技巧 第15章OpenGL着色语言 附录A操作顺序 附录B状态变量 附录COpenGL和窗口系统 附录DGLUT(OpenGL实用工具库)基础知识 附录E计算法线向量 附录F齐次坐标和变换矩阵 附录G编程提示 附录HOpenGL的不变性规则 附录IOpenGL着色语言内置的变量和函数 术语表
OpenGL ES 3.0 英文版 第1章——OpenGL ES 3.0简介   第1章简单介绍OpenGL ES,概述了OpenGL ES 3.0图形管线,讨论了OpenGL ES 3.0的设计理念和限制,最后介绍了OpenGL ES 3.0中使用的一些约定和类型。   第2章——你好,三角形:一个OpenGL ES 3.0示例   第2章介绍绘制三角形的一个简单OpenGL ES 3.0示例。我们的目的是说明OpenGL ES 3.0程序的样子,向读者介绍一些API概念,并说明如何构建和运行OpenGL ES 3.0示例程序。   第3章——EGL简介   第3章介绍EGL——为OpenGL ES 3.0创建表面和渲染上下文的API。我们说明与原生窗口系统通信、选择配置和创建EGL渲染上下文及表面的方法,传授足够多的EGL知识,你可以了解到启动OpenGL ES 3.0进行渲染所需的所有知识。   第4章——着色器和程序   着色器对象和程序对象是OpenGL ES 3.0中最基本的对象。第4章介绍创建着色器对象、编译着色器和检查编译错误的方法。这一章还说明如何创建程序对象、将着色器对象连接到程序对象以及链接最终程序对象的方法。我们讨论如何查询程序对象的信息以及加载统一变量(uniform)的方法。此外,你将学习有关源着色器和程序二进制代码之间的差别以及它们的使用方法。   第5章——OpenGL ES着色语言   第5章介绍编写着色器所需的着色语言的基础知识。这些着色语言基础知识包括变量和类型、构造器、结构、数组、统一变量、统一变量块(uniform block)和输入/输出变量。该章还描述着色语言的某些更细微的部分,例如精度限定符和不变性。   第6章——顶点属性、顶点数组和缓冲区对象   从第6章开始(到第11章为止),我们将详细介绍管线,教授设置和编程图形管线各个部分的方法。这一旅程从介绍几何形状输入图形管线的方法开始,包含了对顶点属性、顶点数组和缓冲区对象的讨论。   第7章——图元装配和光栅化   在前一章讨论几何形状输入图形管线的方法之后,第7章将讨论几何形状如何装配成图元,介绍OpenGL ES 3.0中所有可用的图元类型,包括点精灵、直线、三角形、三角形条带和三角扇形。此外,我们还说明了在顶点上进行坐标变换的方法,并简单介绍了OpenGL ES 3.0管线的光栅化阶段。   第8章——顶点着色器   我们所介绍的管线的下一部分是顶点着色器。第8章概述了顶点着色器如何融入管线以及OpenGL ES 着色语言中可用于顶点着色器的特殊变量,介绍了多个顶点着色器的示例,包括逐像素照明和蒙皮(skinning)。我们还给出了用顶点着色器实现OpenGL ES 1.0(和1.1)固定功能管线的示例。   第9章——纹理   第9章开始介绍片段着色器,描述OpenGL ES 3.0中所有可用的纹理功能。该章提供了创建纹理、加载纹理数据以及纹理渲染的细节,描述了纹理包装模式、纹理过滤、纹理格式、压缩纹理、采样器对象、不可变纹理、像素解包缓冲区对象和Mip贴图。该章介绍了OpenGL ES 3.0支持的所有纹理类型:2D纹理、立方图、2D纹理数组和3D纹理。   第10章——片段着色器   第9章的重点是如何在片段着色器中使用纹理,第10章介绍编写片段着色器所需知道的其他知识。该章概述了片段着色器和所有可用的特殊内建变量,还演示了用片段着色器实现OpenGL ES 1.1中所有固定功能技术的方法。多重纹理、雾化、Alpha测试和用户裁剪平面的例子都使用片段着色器实现。   第11章——片段操作   第11章讨论可以适用于整个缓冲区或者在OpenGL ES 3.0片段管线中执行片段着色器后适用于单个片段的操作。这些操作包括剪裁测试、模板测试、深度测试、多重采样、混合和抖动。本章介绍OpenGL ES 3.0图形管线的最后阶段。   第12章——缓冲区对象   第12章讨论使用缓冲区对象渲染屏幕外表面。缓冲区对象有多种用法,最常见的是渲染到一个纹理。本章提供API缓冲区对象部分的完整概述。理解缓冲区对象对于实现许多高级特效(如反射、阴影贴图和后处理)至关重要。   第13章——同步对象和栅栏   第13章概述同步对象和栅栏,它们是在OpenGL ES 3.0主机应用和GPU执行中同步的有效图元。我们讨论同步对象和栅栏的使用方法,并以一个示例作为结束。   第14章——OpenGL ES 3.0高级编程   第14章是核心章节,将本书介绍的许多主题串联在一起。我们已经选择了高级渲染技术的一个样本,并展示了实现这些功能的示例。该章包含使用法线贴图的逐像素照明、环境贴图、粒子系统、图像后处理、程序纹理、阴影贴图、地形渲染

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

仰望—星空

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值