如何使用OpenGL实现淡入淡出效果?

时间:2011-07-25 00:03:05

标签: c++ opengl blending

我试图实现淡入淡出效果,但我不知道该怎么做。我尝试了几件事,但由于opengl的工作原理而失败了

我将解释它是如何工作的:

如果我绘制1个白色像素并将其围绕每个帧移动一个像素到某个方向,则每个帧的屏幕像素将获得一个较小的R / G / B值(范围0-255),因此在255帧之后白色像素将完全变黑。因此,如果我移动白色像素,我会看到一条渐变轨迹从白色到黑色均匀分布1个颜色值与之前的像素颜色相比。

编辑:我更愿意知道非阴影方式,但如果不可能,那么我也可以接受着色器方式。

Edit2:由于这里有一些混乱,我想告诉我,我可以通过在整个场景上绘制黑色透明四边形来做到这种效果。但是,这不起作用,因为我希望它工作;像素可以获得的暗度是有限制的,因此它总是会使一些像素“可见”(高于零色值),因为:1 * 0.9 = 0.9 - >再次舍入到1,等等。我可以通过缩短尾迹来“修复”这个,但我希望能够尽可能多地调整轨迹长度而不是双线性(如果那是正确的词)插值我想要线性(所以它总是从0-255比例的每个r,g,b值减少-1,而不是使用百分比值。)

编辑3:还有一些混乱,所以我们要明确:我想通过从glClear()禁用GL_COLOR_BUFFER_BIT来改善效果,我不想看到屏幕上的像素FOREVER ,所以我希望通过在我的场景上绘制一个四边形,使每个像素的颜色值减少1(在0-255比例范围内),使它们在时间上更暗。

编辑4:我会简单一点,我想要OpenGL方法,效果应尽量少用电,内存或带宽。这个效果应该可以在不清除屏幕像素的情况下工作,所以如果我在我的场景上绘制一个透明的四边形,之前绘制的像素会变暗等。但如上所述几次,它不能很好地工作。大的NO是:1)从屏幕读取像素,在for循环中逐个修改它们然后上传回来。 2)使用不同的暗度渲染我的物体X次以模拟轨迹效果。 3)乘以颜色值不是一个选项,因为它不会使像素变成黑色,它们将永远保持在屏幕上一定的亮度(见上面某处的解释)。

5 个答案:

答案 0 :(得分:9)

  

如果我绘制1个白色像素并将其围绕每个帧移动一个像素到某个方向,则每个帧的屏幕像素将获得一个较小的R / G / B值(范围0-255),因此在255帧之后白色像素将完全变黑。因此,如果我移动白色像素,我会看到一条渐变轨迹从白色到黑色均匀分布1个颜色值与之前的像素颜色相比。

在我解释如何做之前,我想说你想要的视觉效果是一种可怕的视觉效果,你不应该使用它。从每种RGB颜色中减去一个值将产生不同的颜色,而不是相同颜色的较暗版本。 RGB颜色(255,128,0),如果从中减去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的概念。这是通过使用两个变量来完成的:currIndexprevIndex。这些值是纹理,渲染缓冲区和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函数获得相同的效果。如果你不知道那些是什么,我建议learning shaders;从长远来看,它更容易。

正常绘制东西

现在,您只需正常渲染一切。只是不要解开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);

你已经完成了。

答案 1 :(得分:6)

你可能想要使用glBlendFunc(GL_ONE,GL_SRC_ALPHA)渲染一个alpha从1.0到0.0的黑色矩形。

编辑以回复您的评论(回复不适合评论):

通过简单的淡入淡出操作,您无法根据年龄淡化单个像素。通常,渲染目标不会“记住”在先前帧中绘制的内容。我可以想办法通过交替渲染到一对FBO中的一个并使用它们的alpha通道来实现这一点,但是你需要一个着色器。那么你要做的是首先渲染包含前一个位置像素的FBO,将alpha值减一,在alpha == 0时删除它们,否则在alpha减小时使它们变暗,然后将像素渲染到当前位置alpha == 255。

如果您只有移动像素:

render FBO 2 to FBO 1, darkening each pixel in it by a scale (skip during first pass)
render moving pixels to FBO 1
render FBO 1 to FBO 2 (FBO 2 is the "age" buffer)
render FBO 2 to screen

如果你想修改一些场景(即有场景和移动像素):

set glBlendFunc (GL_ONE, GL_ZERO)
render FBO 2 to FBO 1, reducing each alpha > 0.0 in it by a scale (skip during first pass)
render moving pixels to FBO 1
render FBO 1 to FBO 2 (FBO 2 is the "age" buffer)
render the scene to screen
set glBlendFunc (GL_ONE, GL_SRC_ALPHA)
render FBO 2 to screen

实际上,比例应该是(浮点)/ 255.0 / 255.0,以使组件同样消失(并且在其他组件之前没有一个以较低值开始的组件变为零)。

如果您只有几个移动像素,则可以在之前的所有位置重新渲染像素,最多返回255“滴答”。

由于您无论如何都需要重新渲染每个像素,只需使用正确的颜色渐变渲染每个像素:颜色越深,像素越旧。如果你有很多像素,那就是双FBO方法 可能有用。

我正在编写刻度线,而不是帧,因为取决于渲染器和硬件,帧可能需要不同的时间,但您可能希望像素轨迹在恒定时间内消失。这意味着你需要在这么多毫秒后调暗每个像素,保持它们之间的帧颜色。

答案 2 :(得分:2)

这样做的一种非着色方式,特别是如果淡出到黑色是屏幕上唯一的东西是通过readpixels iirc抓取屏幕内容,将它们弹出到纹理中,并使用该纹理将一个矩形放在屏幕上,然后您可以将矩形的颜色调制为黑色以完成您想要完成的效果。

答案 3 :(得分:1)

它是驱动程序,Windows本身不支持OpenGL或只支持低版本,我认为1.5。所有新版本都附带ATI或NVIDIA,Intel等驱动程序。 你在使用不同的卡吗? 你有效使用什么版本的OpenGL?

答案 4 :(得分:1)

这样的情况使得我无法使用纯OpenGL。我不确定你的项目是否有空间(如果你正在使用另一个窗口API,它可能不会),或者增加的复杂性是值得的,但是添加一个像OpenGL一样的SDL的2D库可以让你以合理的方式直接使用显示表面的像素,以及一般的像素,OpenGL通常不容易。

然后,您需要做的就是在OpenGL渲染几何体之前运行显示表面的像素,并从每个RGB分量中减去1。

这是我能看到的最简单的解决方案,如果在OpenGL中使用其他库是一种选择。