引言
本篇文章主要表述一下帧缓冲和环境贴图,主要以帧缓冲为主。
帧缓冲
帧缓冲是opengl渲染中一个非常重要的组成分布,它进行离屏渲染,可以用来保存数据和进行一些后处理相关的操作,一个帧缓冲,还必须要一个纹理附件来保存得到的信息,和一个渲染缓冲附件,包含一些深度值模板值可能。
js
unsigned int framebuffer; //渲染帧
glGenFramebuffers(1, &framebuffer); //生成一个渲染帧给与framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); //绑定当前的渲染帧
unsigned int textureColorbuffer; //附加的纹理对象
glGenTextures(1, &textureColorbuffer); //生成一个纹理给与textureColorbuffer
glBindTexture(GL_TEXTURE_2D, textureColorbuffer); //指明为2d的纹理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr); //指明纹理的大小,参数等,这个一个RGB的2D等
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); //把当前的纹理的对象给与当前的渲染帧
unsigned int rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo); //绑定好当前的渲染缓冲对象
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, SCR_WIDTH, SCR_HEIGHT); //指明大小和深度和模板值
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo); //把当前的缓冲对象给与帧
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) //检查帧缓冲对象
{
std::cout << "error:framebuffer iis not complete!" << std::endl;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0); //切换到主屏幕,主缓冲
值得注意的是,一个帧缓冲可以携带多个纹理对象,在后面的延迟着色法中会有比较重要的体现: glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorbuffer, 0);
在这里指定了一张纹理附件为GL_COLOR_ATTACHMENT0
,并且如果只有一张纹理的,在输出纹理图片的时候不需要指定layout=0
,当然指定到0也没有什么错误.
js
#version 330 core
out vec4 FragColor; //默认为0输出通道 layout (location = 0) out vec4 FragColor;也可以实现
in vec2 TexCoords;
uniform sampler2D texture1;
void main()
{
FragColor = texture(texture1, TexCoords);
}
同时在输出中可以输出的维度可以和纹理的维度不一致,在本纹理中定义的为RGB的三维的纹理,但是在输出的是一个四维的纹理,也没有什么问题,但是可能一般还是不建议这样。
这里也简单的说明一下多张纹理的定义的格式:
js
unsigned int gBuffer;
glGenFramebuffers(1, &gBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);
unsigned int gPosition, gNormal, gAlbedoSpec; //附加的纹理的图片
//下面不详细处理纹理,只给出大概
glGenTextures(1, &gPosition);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gPosition, 0);
glGenTextures(1, &gNormal);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, gNormal, 0);
glGenTextures(1, &gAlbedoSpec);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); //这张纹理需要RGBA
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, gAlbedoSpec, 0);
unsigned attachments[3] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };
glDrawBuffers(3, attachments);
GLuint rboDepth;
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboDepth);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
std::cout << "Framebuffer not complete!" << std::endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
在这里可以看出,一个缓冲帧可以有多个纹理对象,为了指明输出的需要,需要使用layout
,用unsigned attachments[3] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 }; glDrawBuffers(3, attachments);
指明每一个的输出通道。
并且纹理对象的RGBA的值是有关系,如果缓冲输出的RGBA,但是纹理只有RGB格式,可能有出问题。下面给出输出的片元着色器:
js
#version 330 core
layout (location = 0) out vec3 gPosition;
layout (location = 1) out vec3 gNormal;
layout (location = 2) out vec4 gAlbedoSpec;
in vec2 TexCoords;
in vec3 FragPos;
in vec3 Normal;
uniform sampler2D texture_diffuse1;
uniform sampler2D texture_specular1;
void main()
{
gPosition = FragPos; //保存位置信息,三维,使用RGB纹理
gNormal = normalize(Normal); //保存法线信息,也是三维RGB
gAlbedoSpec.rgb = texture(texture_diffuse1, TexCoords).rgb;
gAlbedoSpec.a = texture(texture_specular1, TexCoords).r; //保存采样的颜色值,四维,同时第四位在纹理的r上
}
在片元着色器中,输出的名字gPosition,gNormal,gAlbedoSpec
和纹理的名字没有关系,只是相当于编程语言中的形参而已。
在渲染中一般需要离屏渲染,需要先绑定该缓冲帧,得到渲染的纹理,再切回主缓冲帧,进行二次处理等:
js
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); //切到缓冲帧
//离屏渲染得到对应的纹理对象或者缓冲数据
glBindFramebuffer(GL_FRAMEBUFFER, 0); //切回主渲染帧
//绑定得到的纹理对象或者缓冲数据
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
//二次渲染
利用帧缓冲技术可以实现不必再一个着色器中进行大量的计算渲染,可以分开进行,为后面的多光源渲染,延迟着色法提供方法,同时也可以实现一些简单的效果:
js
FragColor = vec4(vec3(1.0 - texture(screenTexture, TexCoords)), 1.0); //反相
FragColor = vec4(col, 1.0);
float average = (FragColor.r + FragColor.g + FragColor.b) / 3.0;
FragColor = vec4(average, average, average, 1.0); //灰度
也可以实现一些比较高级的效果,模糊,锐化等,这些大多数都是通过卷积核来实现的:
js
const float offset = 1.0 / 300.0;
void main()
{
vec2 offsets[9] = vec2[](
vec2(-offset, offset), // 左上
vec2( 0.0f, offset), // 正上
vec2( offset, offset), // 右上
vec2(-offset, 0.0f), // 左
vec2( 0.0f, 0.0f), // 中
vec2( offset, 0.0f), // 右
vec2(-offset, -offset), // 左下
vec2( 0.0f, -offset), // 正下
vec2( offset, -offset) // 右下
);
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 = vec3(0.0);
for(int i = 0; i < 9; i++)
col += sampleTex[i] * kernel[i];
FragColor = vec4(col, 1.0);
}
另外可能还有应该会无视的点,渲染到屏幕需要屏幕的纹理坐标和顶点坐标:
js
float quadVertices[] = {
// positions // texCoords //是逆时针的
-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
};
在输入的时候是0-1,不是屏幕的宽度和高度,另外记得,输入点是二维的:
js
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTexCoords;
out vec2 TexCoords;
void main()
{
TexCoords = aTexCoords;
gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
}
关于帧缓冲需要了解基本就是这样,可能后面在做补充。
环境贴图
环境贴图有一点像是天空盒,不知道这么理解是否正确,是由六张纹理组成一个盒子而已:
js
vector<std::string> faces
{
"skybox/right.jpg", //x+
"skybox/left.jpg", //x-
"skybox/top.jpg", //y+
"skybox/bottom.jpg", //y-
"skybox/front.jpg", //z+
"skybox/back.jpg" //z-
};
unsigned int cubemapTexture = loadCubemap(faces);
unsigned int loadCubemap(vector<std::string> faces)
{
unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID); //是一张三维的纹理cube
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); //第一个量是枚举量,在增加的过程中分别为x+ x- y+ y- z+ z-的六张纹理
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;
}
在渲染的过程中,可以在渲染盒子的时候把深度值给予为1,也可以最后渲染,一般使用第二种,可以减少一定的消耗 glDepthFunc(GL_LEQUAL);
小于等于进行渲染,因为前面已经渲染了环境中的物体,在渲染盒子的时候,当深度小于等于其实就是渲染没有深度值的地方,补充为环境的颜色。 在着色器中有:
js
//顶点着色器中
#version 330 core
layout (location = 0) in vec3 aPos;
out vec3 TexCoords;
uniform mat4 projection;
uniform mat4 view;
void main()
{
TexCoords = aPos;
vec4 pos = projection * view * vec4(aPos, 1.0);
gl_Position = pos.xyww; //z一直为1
}
//片元着色器中
#version 330 core
out vec4 FragColor;
in vec3 TexCoords; //纹理坐标是三维的
uniform samplerCube skybox; //三维的纹理对象
void main()
{
FragColor = texture(skybox, TexCoords);
}
在对于环境贴图进行折射和反射的过程中,传入对于贴图就可以:
js
#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)); //计算反射光线I指向片元
FragColor = vec4(texture(skybox, R).rgb, 1.0);
}
//折射
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);
}
计算动态的贴图,需要大量的资源,后面学习。