如果我画一个白色像素,并在每一帧中将其向某个方向移动一个像素,那么每一帧屏幕像素的R/G/B值都会减少一个(范围为0-255),因此经过255帧后,白色像素将完全变黑。因此,如果我移动白色像素,我将看到一个渐变轨迹,从白色到黑色,每个像素颜色与前一个像素颜色相比均匀减少1个颜色值。
在我解释如何做到这一点之前,我想说,你要实现的视觉效果是一个可怕的视觉效果,你不应该使用它。从RGB颜色中减去一个值将产生一个不同的颜色,而不是同一颜色的较暗版本。如果你从RGB颜色(255,128,0)中减去128次1,则会变成(128,0,0)。第一个颜色是棕色,第二个颜色是深红色。这些颜色不相同。
现在,由于你没有很好地解释这个问题,我必须猜测一些东西。我假设您正在渲染的内容中没有“对象”。没有状态。您只是在任意位置绘制东西,您不记得在哪里绘制了什么,也不想记住在哪里绘制了什么。
为了实现您想要的效果,您需要两个离屏缓冲区。我建议使用
FBO和屏幕大小的纹理来完成这些操作。基本算法很简单。您将上一帧的图像渲染到当前图像中,使用“减 1”混合模式从您写入的颜色中减去1。然后您将新的内容渲染到当前图像中。然后您显示该图像。之后,您切换哪个图像是前一个,哪个是当前的,并重新执行整个过程。
注意:以下代码将假定OpenGL 3.3功能可用。
初始化
因此,在初始化期间(在OpenGL初始化之后),您必须创建屏幕大小的纹理。您还需要两个屏幕大小的深度缓冲区。
GLuint screenTextures[2];
GLuint screenDepthbuffers[2];
GLuint fbos[2];
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) {
}
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,所以我们不能这样做。相反,我们必须执行不同的操作。我们必须将图像复制到后台缓冲区,然后交换缓冲区。
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);
完成。