Li Jiaheng's blog
1358 字
7 分钟
LearnOpenGL·三·其它·把帧渲染到纹理上
2024-04-23

这是对learnopengl的简单笔记。原教程网址:learnopengl。原教程同时涉及图形学的基本理论与opengl API,本文更多关注API,而简化甚至省略了背后的图形学原理性内容。

一个相机渲染一张纹理#

在unity中看到,相机的渲染结果不一定要放到屏幕上,还可以放到一张纹理里面。这在openGL里面很容易做到。

总体而言,就是

  • 申请一块帧缓冲
  • 申请一些纹理,将这个帧缓冲和这个纹理绑定在一起
  • 调用 glFrameTexture2D 将纹理绑定成帧缓冲的输出“对象”,就可以把相机渲染的帧放到一张纹理图像里*。
// 申请帧缓冲与纹理  
GLuint pingpongFBO, pingpongColorbuffers;  
glGenFramebuffers(1, &pingpongFBO);  
glGenTextures(1, &pingpongColorbuffers);  
// 激活绑定帧缓冲,并将帧缓冲和它的目标绑定在一起.  
glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[i]);  
glBindTexture(GL_TEXTURE_2D, pingpongColorbuffers[i]);  
// 像设置普通纹理贴图那样设置帧缓冲输出到的纹理对象.  
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, frameWidth, frameHeight, 0, GL_RGB, 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); // we clamp to the edge as the blur filter would otherwise sample repeated texture values!  
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);  
// 指定将帧缓冲输出到这个纹理.  
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, pingpongColorbuffers[i], 0);  
// also check if framebuffers are complete (no need for depth buffer)  
// 一些检查  
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)  
	std::cout << "Framebuffer not complete!" << std::endl;  

此后,任何 glBindFramebuffer(pingpongFBO); 后渲染的场景,都会输入到对应纹理 pingpongColorBuffers 中,而不会输入到屏幕。


PS:glBindFrameBuffer(0) 的渲染结果才会直接输出的到屏幕

一个相机渲染到多张纹理里#

相机可以通过在 片段着色器中 设置 layout 的out变量,控制结果输出面元颜色到不同的纹理对象上,具体方法是 glFramebufferTexture2D 中的 GL_COLOR_ATTACHMENT0 参数,它和 layout 布局相对应。

比如:

/* Fragment GLSL */  
#version 330 core  
layout (location = 0) out vec4 FragColor;  
layout (location = 1) out vec4 BrightColor;  
  
in VS_OUT {  
    vec3 FragPos;  
    vec3 Normal;  
    vec2 TexCoords;  
} fs_in;  
  
uniform vec3 lightColor;  
  
void main()  
{  
    FragColor = vec4(lightColor, 1.0);  
    //calculate BrightColor  
    //to do..  
    //就是输出到BrightColor的大于阈值的颜色.  
    float brightness = dot(lightColor, vec3(0.7126, 0.7152, 0.722));  
    if(brightness > 1.0)  
        BrightColor = vec4(lightColor, 1.0);  
    else  
        BrightColor = vec4(0.0,0.0,0.0,1.0);  
}  

这里规定了两个输出“通道” FragColor, BrightColor,分别对应两张要输出的纹理。上面是一个HDR+泛光的渲染的其中一步,最后的得到的两张纹理分别是 16位浮点数的HDR颜色缓冲、光亮超过某一阈值的位置的颜色的缓冲(低于这一阈值的位置直接置0,高于的不变)。这两张纹理分别对应 FragColor, BrightColor.

而绑定帧缓冲与颜色缓冲的过程:

GLuint hdrFBO, colorBuffers[];  
glGenFramebuffers(1, &hdrFBO);  
glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO);  
// create 2 floating point color buffers (1 for normal   endering, other for brightness treshold values)  
glGenTextures(2, colorBuffers);  
for (unsigned int i = 0; i < 2; i++)  
{  
		glBindTexture(GL_TEXTURE_2D, colorBuffers[i]);  
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, frameWidth, frameHeight, 0, GL_RGB, GL_FLOAT, NULL);  
		// 注意申请的是GL_RGB16F纹理,不会自动归一化.  
		// 像普通纹理那样设置属性.  
		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);  // we clamp to the edge as the blur filter would otherwise sample repeated texture values!  
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);  
		// attach texture to framebuffer  
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, colorBuffers[i], 0);  
		注意 GL_COLOR_ATTACHMENT0+i  
}  

关键在于最后的 glFramebufferTexture2D,里面的 GL_COLOR_ATTACHMENTx 和片段着色器中 (layout = x) out 的输出通道对应。
GL_COLOR_ATTACHMENTx 的定义时连续的,所以 GL_COLOR_ATTACHMENT0 + i = GL_COLOR_ATTACHMENTi

将相机渲染的纹理又展示回屏幕#

相机渲染出来的纹理一般是平铺满整个屏幕的,所以只需要将纹理“采样”贴到屏幕上就可,所以方法是:画一个铺满窗口的平面矩形,直接在这个矩形上用相机渲染的纹理采样表示颜色,即可

着色器:

顶点着色器直接传递平面坐标,平面坐标就是纹理坐标,也直接当成世界坐标.  
#version 330 core  
layout (location = 0) in vec3 aPos;  
layout (location = 1) in vec2 aTexCoords;  
  
out vec2 TexCoords;  
  
void main()  
{  
    TexCoords = aTexCoords;  
    gl_Position = vec4(aPos, 1.0);  
}  
-------------------------------------------  
-------------------------------------------  
片段着色器直接在纹理上采样,并处理一些Gamma矫正、曝光等。  
#version 330 core  
out vec4 FragColor;  
  
in vec2 TexCoords;  
  
uniform sampler2D scene;      //colorBuffer[0](hdr颜色值)  
uniform sampler2D bloomBlur;  //pingpongColorBuffer(模糊后的高亮.)  
uniform bool bloom;  
uniform float exposure;  
  
void main()  
{  
    const float gamma = 2.2;  
    vec3 hdrColor = texture(scene, TexCoords).rgb;  
    vec3 bloomColor = texture(bloomBlur, TexCoords).rgb;  
  
    //calculate the final FragColor by blending hdrColor and bloomColor, then exposure tone mapping.  
    // to do  
    if(bloom)  
        hdrColor += bloomColor;   //直接相加..  
    //曝光度:1-e^(-hdrColor * exposure)  
    hdrColor = vec3(1.0) - exp(-hdrColor * exposure);  
    //Gamma矫正.  
    hdrColor = pow(hdrColor, vec3(1.0/gamma));  
  
    FragColor = vec4(hdrColor, 1.0);  
}  
这是Gamma+HDR+泛光的片段着色器,注意连个sampler2D就是要融合展示的、由相机渲染的纹理。  

在渲染的时候,设置视口,绑定帧缓冲为0指定输出屏幕,然后激活、绑定纹理为将要展示的、由相机渲染的纹理,设置着色器后,画一个大矩形renderQuad 铺满屏幕即可。

lViewport(0, 0, windowWidth, windowHeight);  
glBindFramebuffer(0);  
glActiveTexture(GL_TEXTURE0);  
glBindTexture(GL_TEXTURE_2D, HDRcolor);  //这个纹理对应第一个sampler2D  
glActivateTexture(1);  
glBindTexture(GL_TEXTURE_2D, BLOOMcolor);  //对应第二个sampler2D  
// ... 设置着色器 ...  
renderQuad();  
glViewport(0, 0, frameWidth, frameHeight);  

这样就把纹理处理后整个贴到屏幕上了。

下面是画那个大矩形,只用定义四角。

void renderQuad()  
{  
	if (quadVAO == 0)  
	{  
		float quadVertices[] = {  
			// positions        // texture Coords  
			-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,  
		};  
		// setup plane VAO  
		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, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);  
		glEnableVertexAttribArray(1);  
		glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));  
	}  
	glBindVertexArray(quadVAO);  
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);  
	glBindVertexArray(0);  
}  
LearnOpenGL·三·其它·把帧渲染到纹理上
https://namisntimpot.github.io/posts/cg/opengl/三其它把帧渲染到纹理上/
作者
Li Jiaheng
发布于
2024-04-23
许可协议
CC BY-NC-SA 4.0