OpenGL笔记(六)

引用链接Learn OpenGL 作者 Joey de Vries

一. Framebuffer(帧缓冲)

什么是Framebuffer

Framebuffer其实很简单,第一,它是一块buffer,也就是一块内存;第二,它是存储的是一帧的buffer数据。之前学到的ColorBuffer、DepthBuffer和StencilBuffer都是Framebuffer,除此之外,OpenGL还允许用户自定义FrameBuffer,可以类似ColorBuffer、DepthBuffer和StencilBuffer这些的操作,自定义的FrameBuffer可以实现离屏渲染,用FrameBuffer可以将场景渲染成一张贴图

也就是说,Framebuffer分为两种:

  • default framebuffer,用来储存depth buffer、stencil buffer和color buffer,然后展示到屏幕上。
  • 自定义的 framebuffer,可以用来将整个场景或整个物体渲染成一张2D贴图,再把它直接传给default framebuffer

PS:从数据结构的角度上看,其实我们所说的FrameBuffer并不是一个真正意义上的buffer,它类似于C语言中结构体,里面存了一些指针,这些指针分别指向输入的depth buffer、color buffer、stencil buffer和输出的贴图对象texture或rbo。


创建Framebuffer

示例代码如下:

//VBO的写法
unsigned int cubeVBO;
glGenBuffers(1, &cubeVBO);
glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);

//FBO的写法与VBO类似
unsigned int fbo;
glGenFramebuffers(1, &fbo);

//Bind到Framebuffer(即fbo)
glBindFramebuffer(GL_FRAMEBUFFER, fbo);

Read Framebuffer和Write Framebuffer

参考:https://www.khronos.org/opengl/wiki/Framebuffer_Object

默认的framebuffer(The default framebuffer)的各种特殊buffer都有各自的名字,比如GL_FRONT, GL_BACK, GL_AUXi, GL_ACCUM等,之前的glfwSwapBuffers就会把默认framebuffer的GL_FRONTGL_BACK俩buffer交换,而这里自定义的framebuffer则没有这种东西,它应该只有俩buffer,即GL_READ_FRAMEBUFFERGL_DRAW_FRAMEBUFFER,绑定时可以这么写:

// 同时绑定到fbo的Read和Draw Buffer
glBindFramebuffer(GL_FRAMEBUFFER, fbo);

// 只绑定Read
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);

// 只绑定Write
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);

在实际调用里,reading commands(比如glReadPixels)会读当前绑定的Read fbo,而writing commands (all rendering commands)都会用到当前绑定的Draw buffer


填充Framebuffer数据

在创建完基本的FrameBuffer之后,还要为其填充attachment,可以理解为为其填充数据

一个最基本的Framebuffer需要包含四个内容:

  • We have to attach at least one buffer (color, depth or stencil buffer)
  • There should be at least one color attachment
  • All attachments should be complete as well
  • Each buffer should have the same number of samples

关于attachment

为了理解这四个内容,需要先介绍attachment

An attachment is a memory location that can act as a buffer for the framebuffer, think of it as an image. When creating an attachment we have two options to take: textures or renderbuffer objects.

OpenGL定义了两种attachment,texture attachment和render buffer object,二者对应不同的贴图格式。不过老版本的OpenGL只有texture attachment这一种attachment。

把Texture attachment细分后,具体可以分为四种:
在这里插入图片描述
texture attachment 与 renderbuffer object attachment的区别
texture attachment就是一张texture图,可以被采样,可以通过shader进行读取,但rbo attachment,不能被采样,没有mip-maps,不可被shader读取。


理解Framebuffer的四个部分

首先,一个Framebuffer,为了进行渲染,需要两项内容:输入数据和输出数据:

  • 输入:输入的数据,一个完整的FBO需要有输入的数据(at least one buffer
  • 输出:输出的贴图,一个完整的FBO需要有输出的数据(at least one color attachment),这里的attachment就相当于image,作为输出。

完成一个framebuffer的创建和相关attachment的attach操作后,可以用API检测FrameBuffer是否是完整的:

if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)

至于后面两点,就比较好理解了,输出的贴图必须是完整的,而且每一个输入的buffer因为都是渲染一个场景,所以其大小、像素数都必须是一样的。


生成并绑定Texture attachment

生成Texture attachment的方法跟生成texture的方法类似,只是多了个把它attach到Framebuffer的操作。

unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
  
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 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); 

//attach it to the frame buffer, 作为输出的texture
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); 

glFrambebufferTexture2D API:
在这里插入图片描述

生成并绑定Renderbuffer object attachment

Renderbuffer object attachment 是一块真正的buffer,里面直接存储了渲染的data,这些data直接采用了OpenGL内置的渲染格式的,对其进行write操作和copy操作都很快。data是可写不可读的,这里的不可读指的是不可直接从attachment中读取数据,但是可以借助glReadPixels从当前绑定的frame buffer中读取特定区域的片元。
rbo相关的API:

unsigned int rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo); 

由于renderbuffer的write-only的特性,rbo常常用作于depth和stencil的Attachment

glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);  //24位的depth,8位的stencil

通过设置texture类型的framebuffer,可以把整个场景渲染成一张贴图。
在这里插入图片描述

OpenGL默认的四个framebuffer

  • FRONT_LEFT 展示给屏幕的fbo
  • FRONT_RIGHT
  • BACK_LEFT 在后面偷偷绘制的fbo,用于swapbuffer用,是双缓冲机制
  • BACK_RIGHT
    需要四个fbo,以适用特殊用途,比如VR,Left是左眼,RIght是右眼

具体使用FBO的三个步骤

  • 绑定到自己创建的FBO,按照正常操作进行绘制
  • 切换到默认的framebuffer
  • 画正常场景,把刚刚生成的贴图贴上去

Framebuffer里使用MSAA的贴图

我在用Framebuffer把场景渲染成一张贴图绘制出来的过程中,发现绘制出的图案有锯齿,这里打算提高Framebuffer输出贴图的采样率,即MSAA,参考Anti Aliasing,里面介绍的有两种在OpenGL里使用MSAA的方法:

  • 一种是开启窗口的MSAA,即整个窗口在绘制时启用MSAA技术
  • 一种是使用MSAA的贴图

A Multisample Texture is a Texture that can have multiple samples per pixel, thereby allowing it to be used in multisampled rendering.

第一种很简单,也是我之前学习OpenGL时使用的方法:

// 在glfwCreateWindow之前调用此函数
glfwWindowHint(GLFW_SAMPLES, 4);

...

// 设置OpenGL的contex
glEnable(GL_MULTISAMPLE); 

看上去这么简单,其实是因为GLFW把默认窗口的Framebuffer里相关MSAA的内容都设置好了,而这里的游戏引擎是把场景渲染为贴图输出到viewport子窗口给imgui绘制的,所以不适合第一种方法,对于自己创建的framebuffer而言,MSAA本质上是提高了buffer的大小,4x的MSAA对应了原本4倍的buffer大小,具体分为两类:

  • Multisampled texture attachments
  • Multisampled renderbuffer objects

MSAA贴图的写法如下,跟创建Texture2D很像:

// GL_TEXTURE_2D和GL_TEXTURE_2D_MULTISAMPLE是两种GLenum
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex);
// 用glTexImage2DMultisample取代glTexImage2D函数
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGB, width, height, GL_TRUE);
// Bind完后绑定还原到空槽位
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);  

// 把MultiSample的Texture绑定到Framebuffer上
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex, 0);

OpenGL定义了两种attachment,texture attachment和render buffer object,二者对应不同的贴图格式。texture attachment就是一张texture图,可以被采样,可以通过shader进行读取,但rbo attachment,不能被采样,没有mip-maps,不可被shader读取,但是可以借助glReadPixels从当前绑定的frame buffer中读取特定区域的片元,由于renderbuffer的write-only的特性,rbo常常用作于depth和stencil的Attachment,其进行write操作和copy操作都很快

MSAA的贴图设置前面介绍过了,MSAA的rbo设置如下所示:

// ===================== 原本的RBO设置 ============================

// 创建用于depth和stencil attachment的rbo(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); 
// attach到当前绑定的fbo上
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo); 


// ===================== 现在的RBO设置 ============================
unsigned int rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);

// 改下API而已, 其实只改了这一行
glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, SCR_WIDTH, SCR_HEIGHT);

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

最后还需要相关framebuffer的设置,即Render to multisampled framebuffer,因为multisampled buffer有点特殊,不能直接用shader直接使用此buffer,此时需要对其输出的贴图进行downsclae操作(也叫resolve),此时需要借助glBlitFramebuffer,从此MSAA buffer里复制得到一份新的buffer,相当于针对MSAA的贴图,每帧Copy出一份新的普通Image,代码如下:

// 当使用glBindFramebuffer(GL_FRAMEBUFFER, fbo)时, 其实是同时绑定到了GL_READ_FRAMEBUFFER和GL_DRAW_FRAMEBUFFER上
// 此部分代码需要每帧调用

// 把绑定了MSAA的texture attachment和rbo attachment的frame buffer绑定到ReadBuffer上
glBindFramebuffer(GL_READ_FRAMEBUFFER, multisampledFBO);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

// 使用BlitFramebuffer把read buffer里的东西Copy到draw buffer上
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); 

glBlitFramebuffer, glBlitNamedFramebuffer — copy a block of pixels from one framebuffer object to another

再强调一下这里的glBlitFramebuffer函数,blit可以理解为移动、传送,这里左边是MSAA输出的贴图的四个顶点的像素坐标,右边也是输出贴图的坐标,其他细节如下图所示:
![在这里插入图片描述](https://img-blog.csdnimg.cn/0032f54ec6c5479aa55f49c4c5b6d1f2.png


二. 通过post-processing 的操作,实现某些特殊effects

当把整个场景渲染成一张贴图后,可以对贴图进行以下后处理操作

(1)曝光效果: 把贴图颜色进行inverse操作即可
在这里插入图片描述

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

在这里插入图片描述

(2)Grayscale: 灰白效果

在这里插入图片描述
可达到这种效果
在这里插入图片描述
(3)Kenel Effect
Kener Effect是利用卷积矩阵,把点在texture上的颜色与周围点的颜色做了一个采样,也就是说,texture上的每个点,都会收到周围点的颜色影响。
比如这样的矩阵:

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

这种矩阵和都为1,若大于1,则整体偏亮,小于1 整体偏暗
可以模拟出这种喝醉酒的感觉:
在这里插入图片描述

(4)模糊效果: 同样是采用与周围片元混合的方式,但混合的矩阵有点不同:

float kernel[9] = float[](
    1.0 / 16, 2.0 / 16, 1.0 / 16,
    2.0 / 16, 4.0 / 16, 2.0 / 16,
    1.0 / 16, 2.0 / 16, 1.0 / 16  
);

通过这样的矩阵,可以实现模糊效果
在这里插入图片描述
模糊效果大致如下,可以用于摘掉眼镜、喝醉酒等情况
在这里插入图片描述

(5)Edge detection(亮化边缘): 同样是用kernel矩阵
在这里插入图片描述
我觉得是因为边缘的矩阵周围有0,所以加起来其color的大小会比1大,所以显得更亮
效果图如下:
在这里插入图片描述


三. 正方体贴图

正方体贴图由六个面组成,正方体贴图的贴图坐标由三个坐标组成,对于下图所示的正方体,可以发现,正好对于一个点,其坐标点的坐标和其贴图坐标是相同的,这样就不用单独再去给贴图传入贴图UV坐标了。
在这里插入图片描述
创建cubemap的相关API如下:

unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
//调用6次glTexImage2D,对正方体六个面进行贴图
glTexImage2D(
        GL_TEXTURE_CUBE_MAP_POSITIVE_X , //right side of the cube
        0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data
    );

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);  //三维的需要多加一个R方向

//shader中的写法
in vec3 textureDir; // direction vector representing a 3D texture coordinate
uniform samplerCube cubemap; // cubemap texture sampler

void main()
{             
    FragColor = texture(cubemap, textureDir);
}  

在绘制Skybox的时候,如果按照绘制普通物体的画法,Skybox其实就是一个正方体而已,如图所示:
在这里插入图片描述
为了实现在天空盒里面的视角,在绘制天空盒的时候,需要做以下特殊处理:

  • 将绘制天空盒的view矩阵的第四行和第四列元素置为0。
  • 在绘制时,为了保证Skybox的深度最大,在片元着色器里,将其pos的z分量置为1
    具体操作如下:
glm::mat4 view = glm::mat4(glm::mat3(myCamera.GetViewMatrix())); 
void main()
{
    TexCoords = aPos;
    vec4 pos = projection * view * vec4(aPos, 1.0);
    gl_Position = pos.xyww;  //MVP转换之后,再把深度值转换为1
} 

还有一个具体的小细节,在绘制场景时,需要把默认的深度测试的模式从GL_LESS换为GL_LEQUAL,后者是包含深度相等的清空,也就是如果深度都为1,仍然绘制场景,画完skybox之后,可再把深度测试模式还原。


四. 环境映射(Environment mapping)

立方体贴图不仅仅只用于skybox,还可以用来给物体带来反射和折射特性。

反射特性
反射特性可以如下图所示,反射出环境光
在这里插入图片描述
为了突出反射光的效果,直接将反射的光,不进行衰减的,原路返回进行输出(我理解的是把所有反射的光,都照射在垂直角度的skybox上),片元着色器这么写:

#version 330 core
in vec3 Normal;
in vec3 Pos;
uniform samplerCube skybox;
uniform vec3 cameraPos;
out vec4 fragColor;

void main()
{
	vec3 dir =  normalize(Pos - cameraPos);
	vec3 target = reflect(dir,normalize(Normal));
	//fragColor = texture(skybox,target);
	fragColor = vec4(texture(skybox,target).rgb,1.0);
}

最后得到的效果,用的贴图完全是从skybox上反射回来的:
在这里插入图片描述

折射
折射也是类似的,只不过多了个折射系数而已,折射的API是refract()
下面是常用介质的折射系数
在这里插入图片描述
片元着色器可以这么写:

void main()
{             
    float ratio = 1.00 / 1.52;
    vec3 I = normalize(Position - cameraPos);
    vec3 R = refract(I, normalize(Normal), ratio);
    FragColor = vec4(texture(skybox, R).rgb, 1.0);
}  

因为进行了光线衰减 ,所有可以实现折射的透明效果:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值