如果我绘制 1 个白色像素并将其围绕每个帧移动一个像素到某个方向,则每帧屏幕像素将减少一个 R/G/B 值(范围为 0-255),因此在 255 帧后白色像素将全黑。因此,如果我移动白色像素,我会看到从白色到黑色的渐变轨迹,与之前的像素颜色相比,颜色值差异均匀。
在我解释如何做到这一点之前,我想说你想要的视觉效果是一个糟糕的视觉效果,你不应该使用它。从每种 RGB 颜色中减去一个值将产生不同的颜色,而不是相同颜色的较暗版本。RGB 颜色 (255,128,0) 减去 1 128 次,将变为 (128, 0, 0)。第一种颜色是棕色,第二种是深红色。这些不一样。
现在,由于您还没有很好地解释这一点,我必须做出一些猜测。我假设您正在渲染的内容中没有“对象”。没有状态。你只是在任意位置画东西,你不记得你在哪里画了什么,你也不想记住在哪里画了什么。
为了做你想做的事,你需要两个屏幕外缓冲区。我建议为这些使用FBO和屏幕大小的纹理。基本算法很简单。您使用从您编写的颜色中“减去 1”的混合模式将前一帧的图像渲染到当前图像。然后你将你想要的新东西渲染到当前图像。然后显示该图像。之后,您切换哪个图像是以前的图像,哪个是当前图像,然后重新执行该过程。
注意:以下代码将假定 OpenGL 3.3 功能。
初始化
因此,首先,在初始化期间(在 OpenGL 初始化之后),您必须创建屏幕大小的纹理。您还需要两个屏幕大小的深度缓冲区。
GLuint screenTextures[2];
GLuint screenDepthbuffers[2];
GLuint fbos[2]; //Put these definitions somewhere useful.
glGenTextures(2, screenTextures);
glGenRenderbuffers(2, screenDepthbuffers);
glGenFramebuffers(2, fbos);
for(int i = 0; i < 2; ++i)
{
glBindTexture(GL_TEXTURE_2D, screenTextures[i]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, SCREEN_WIDTH, SCREEN_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_2D, 0);
glBindRenderbuffer(GL_RENDERBUFFER, screenDepthBuffers[i]);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, SCREEN_WIDTH, SCREEN_HEIGHT);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[i]);
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, screenTextures[i], 0);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, screenDepthBuffers[i]);
if(glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
//Error out here.
}
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
绘制上一帧
下一步是将前一帧的图像绘制到当前图像。
为此,我们需要有以前和当前 FBO 的概念。这是通过有两个变量来完成的:currIndex
和prevIndex
。这些值是纹理、渲染缓冲区和 FBO 的 GLuint 数组的索引。它们应该被初始化(在初始化期间,而不是针对每一帧),如下所示:
currIndex = 0;
prevIndex = 1;
在您的绘图程序中,第一步是绘制前一帧,减去一个(再次,我强烈建议在这里使用真正的混合)。
这不是完整的代码;我希望你填写一些伪代码。
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[currIndex]);
glClearColor(...);
glClearDepth(...);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0 + 0);
glBindTexture(GL_TEXTURE_2D, screenTextures[prevIndex]);
glUseProgram(BlenderProgramObject); //The shader will be talked about later.
RenderFullscreenQuadWithTexture();
glUseProgram(0);
glBindTexture(GL_TEXTURE_2D, 0);
该RenderFullscreenQuadWithTexture
函数完全按照它所说的进行:使用当前绑定的纹理渲染屏幕大小的四边形。程序对象BlenderProgramObject
是一个执行混合操作的 GLSL 着色器。它从纹理中提取并进行混合。同样,我假设您知道如何设置着色器等等。
片段着色器将有一个看起来像这样的主函数:
shaderOutput = texture(prevImage, texCoord) - (1.0/255.0);
再次,我强烈建议:
shaderOutput = texture(prevImage, texCoord) * (0.05);
如果您不知道如何使用着色器,那么您应该学习。但是,如果您不想这样做,则可以使用glTexEnv函数获得相同的效果。如果你不知道那些是什么,我建议学习着色器;从长远来看,这要容易得多。
正常绘制东西
现在,您只需像平常一样渲染所有内容。只是不要取消绑定 FBO;我们仍然想渲染它。
在屏幕上显示渲染图像
通常,您会使用 swapbuffer 调用来显示渲染结果。但由于我们渲染到 FBO,我们不能这样做。相反,我们必须做一些不同的事情。我们必须将我们的图像 blit 到后台缓冲区,然后交换缓冲区。
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[currIndex]);
glBlitFramebuffer(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, SCREEN_WDITH, SCREEN_HEIGHT, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
//Do OpenGL swap buffers as normal
切换图像
现在我们还需要做一件事:切换我们正在使用的图像。之前的图像变为当前图像,反之亦然:
std::swap(currIndex, prevIndex);
你完成了。