LearnOpenGL-笔记-其七

Face Culling

简单地说,我们的面剔除就是负责得到立方体的哪些面是被摄像机所观察到的而哪些没有被观察到,对于没有被摄像机观察到的面,我们没有必要去浪费时间渲染。

现在的问题是:我们要如何得知我们哪些面是摄像机能看到的哪些是不能被看到的呢?

是的,本质上来说其实我们就是利用我们定义三角形时的顶点索引顺序来确定这个面是正面还是反面,从而确定哪些面可以被剔除而哪些面不可以被剔除。

OpenGL已经为我们封装好了面剔除方法,我们只需要去开启即可。

FrameBuffers

虽然我们早在第一篇笔记中就提到了帧缓冲的概念,但是现在我们要更加深入地学习这个概念。

在之前的学习中,我们已经学习了颜色缓冲、深度缓冲以及模板缓冲,这些缓冲组成了我们的帧缓冲。

到目前为止,我们已经使用了很多屏幕缓冲了:用于写入颜色值的颜色缓冲、用于写入深度信息的深度缓冲和允许我们根据一些条件丢弃特定片段的模板缓冲。这些缓冲结合起来叫做帧缓冲(Framebuffer),它被储存在GPU内存中的某处。OpenGL允许我们定义我们自己的帧缓冲,也就是说我们能够定义我们自己的颜色缓冲,甚至是深度缓冲和模板缓冲。

我们目前所做的所有操作都是在默认帧缓冲的渲染缓冲上进行的。默认的帧缓冲是在你创建窗口的时候生成和配置的(GLFW帮我们做了这些)。通过创建我们自己的帧缓冲,我们可以获得额外的渲染目标(target)。

你可能不能很快理解帧缓冲的应用,但渲染你的场景到不同的帧缓冲能够让我们在场景中加入类似镜子的东西,或者做出很酷的后期处理效果。首先我们会讨论它是如何工作的,之后我们将来实现这些炫酷的后期处理效果。

我们自定义的帧缓冲的代码如下:

    // framebuffer configuration
    // -------------------------
    unsigned int framebuffer;
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    // create a color attachment texture
    unsigned int textureColorbuffer;
    glGenTextures(1, &textureColorbuffer);
    glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorbuffer, 0);
    // create a renderbuffer object for depth and stencil attachment (we won't be sampling these)
    unsigned int rbo;
    glGenRenderbuffers(1, &rbo);
    glBindRenderbuffer(GL_RENDERBUFFER, rbo);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, SCR_WIDTH, SCR_HEIGHT); // use a single renderbuffer object for both a depth AND stencil buffer.
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo); // now actually attach it
    // now that we actually created the framebuffer and added all attachments we want to check if it is actually complete now
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << endl;
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

和之前创建的缓冲文件一样,我们先定义一个int变量代表ID,然后生成,绑定;之后我们分别新创建一个空的纹理作为颜色缓冲以及一个新的空纹理作为渲染缓冲(深度或模板),之后我们检查创建过程是否有问题,最后我们将FBO进行解绑。

这里就可以看到创建帧缓冲文件的优越处:我们创建帧缓冲文件之后,就可以把之前使用的颜色缓冲、深度缓冲、模板缓冲作为附件统一管理。

效果图如图所示:

在OpenGL中,​默认帧缓冲直接关联显示窗口,渲染结果会立即输出到屏幕且无法保留中间状态。这导致我们无法对当前帧的渲染结果(如颜色、深度等数据)进行二次加工。

关键优势:将"渲染结果"转化为"可编程数据",突破了实时渲染的单通道限制。

以下是一些效果展示:

Cubemaps

立方体贴图本质上就是六个2D纹理贴在一起,其他的处理方式与正常的纹理也并没有太多不同,我们接下来通过一个天空盒的实现过程展示整个代码。

unsigned int loadCubemap(vector<std::string> faces)
{
    unsigned int textureID;
    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);

    int width, height, nrChannels;
    for (unsigned int i = 0; i < faces.size(); i++)
    {
        unsigned char *data = stbi_load(faces[i].c_str(), &width, &height, &nrChannels, 0);
        if (data)
        {
            glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 
                         0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data
            );
            stbi_image_free(data);
        }
        else
        {
            std::cout << "Cubemap texture failed to load at path: " << faces[i] << std::endl;
            stbi_image_free(data);
        }
    }
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_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);

    return textureID;
}

这段代码里展现了如何加载立方体纹理的六个面,我们接收一个faces类型的vector数组,可以看到每一个面的处理方式本质上和我们之前处理纹理时别无二致,我们先生成一个立方体纹理ID,然后生成纹理,绑定。然后我们逐个地去读取faces中的面的数据,设置纹理属性与参数。

#version 330 core
layout (location = 0) in vec3 aPos;

out vec3 TexCoords;

uniform mat4 projection;
uniform mat4 view;

void main()
{
    TexCoords = aPos;
    gl_Position = projection * view * vec4(aPos, 1.0);
}

顶点着色器的代码就是很正常的mvp矩阵(没有model矩阵是因为我们直接生成了点的位置来得到图形的位置),然后把位置作为纹理坐标给出,然后片元着色器就可以根据纹理坐标来具体渲染。

#version 330 core
out vec4 FragColor;

in vec3 TexCoords;

uniform samplerCube skybox;

void main()
{    
    FragColor = texture(skybox, TexCoords);
}

注意这里的samplerCube类型,显然就是专门针对立方体纹理的类型,我们直接用FragColor = texture(skybox, TexCoords);语句就可以将这个立方体纹理放置在我们在顶点着色器中传出的纹理坐标上。

glDepthMask(GL_FALSE);
skyboxShader.use();
// ... 设置观察和投影矩阵
glBindVertexArray(skyboxVAO);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
glDrawArrays(GL_TRIANGLES, 0, 36);
glDepthMask(GL_TRUE);
// ... 绘制剩下的场景

这里是有关天空盒的绘制部分:注意:我们需要将天空盒的深度写入关闭,这样我们的天空盒才会永远位于其他物体后部,渲染完天空盒之后我们再开启深度写入。

效果如图:

现在我们还可以加一点更炫的:我们加入环境的反射效果。

总的来说,我们的反射原理图是这样的:

在GLSL中我们内部封装了这个反射函数reflect,我们修改顶点着色器和片元着色器即可:

#version 330 core
out vec4 FragColor;

in vec3 Normal;
in vec3 Position;

uniform vec3 cameraPos;
uniform samplerCube skybox;

void main()
{             
    vec3 I = normalize(Position - cameraPos);
    vec3 R = reflect(I, normalize(Normal));
    FragColor = vec4(texture(skybox, R).rgb, 1.0);
}

这是片元着色器的部分,我们获取平面的法线和视线之后通过reflect得到反射向量,然后利用texture函数来从R方向获取天空盒采样颜色。

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

out vec3 Normal;
out vec3 Position;

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

void main()
{
    Normal = mat3(transpose(inverse(model))) * aNormal;
    Position = vec3(model * vec4(aPos, 1.0));
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}

这是顶点着色器,正常的MVP变换。

效果如图:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值