5

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

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

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

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

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

Edit3:仍然有些混乱,所以让我们明确一点:我想通过从 glClear() 禁用 GL_COLOR_BUFFER_BIT 来改善效果,我不想永远看到屏幕上的像素,所以我想让它们及时变暗,通过在我的场景上绘制一个四边形,将每个像素的颜色值减少 1(在 0-255 范围内)。

Edit4:我会简单点,我想要 OpenGL 方法,效果应该使用尽可能少的功率、内存或带宽。这种效果应该在不清除屏幕像素的情况下工作,所以如果我在我的场景上绘制一个透明的四边形,之前绘制的像素会变得更暗等。但正如上面几次解释的那样,它不能很好地工作。最大的NO是:1)从屏幕读取像素,在for循环中一一修改,然后上传回来。2) 用不同的暗度渲染我的对象 X 次以模拟轨迹效果。3)乘以颜色值不是一种选择,因为它不会使像素变成黑色,它们会以一定的亮度永远留在屏幕上(参见上面的解释)。

4

5 回答 5

9

如果我绘制 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 的概念。这是通过有两个变量来完成的: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函数获得相同的效果。如果你不知道那些是什么,我建议学习着色器;从长远来看,这要容易得多。

正常绘制东西

现在,您只需像平常一样渲染所有内容。只是不要取消绑定 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);

你完成了。

于 2011-08-22T00:11:09.533 回答
6

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

根据您的评论进行编辑(回复不适合评论):

您不能通过简单的淡入黑操作根据其年龄淡化单个像素。通常,渲染目标不会“记住”在之前的帧中绘制到它的内容。我可以想办法通过交替渲染到一对 FBO 中的一个并使用它们的 alpha 通道来实现这一点,但是你需要一个着色器。因此,您要做的是首先渲染包含先前位置的像素的 FBO,将其 alpha 值减一,当 alpha == 0 时将其丢弃,否则每当其 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

实际上,比例应该是 (float) / 255.0 / 255.0 以使组件同样消失(而不是从较低值开始的组件在其他组件之前变为零)。

如果您只有几个移动像素,您可以重新渲染所有先前位置的像素,最多 255 个“记号”。

由于无论如何您都需要重新渲染每个像素,只需使用适当的颜色渐变渲染每个像素:越暗,像素越旧。如果您有很多像素,则双 FBO 方法可能会起作用。

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

于 2011-08-21T12:37:52.933 回答
2

这样做的一种非着色器方法,特别是如果淡入黑色是屏幕上唯一发生的事情是通过readpixelsiirc 抓取屏幕的内容,将它们弹出到纹理中,然后在屏幕上放置一个矩形使用该纹理,您可以将矩形的颜色调制为黑色,以实现您想要完成的效果。

于 2011-08-21T13:45:27.683 回答
1

就是驱动,windows本身不支持OpenGL或者只有低版本,我觉得是1.5。所有较新的版本都带有来自 ATI 或 NVIDIA、Intel 等的驱动程序。您使用不同的卡吗?您有效使用的是哪个版本的 OpenGL?

于 2011-07-25T00:11:40.833 回答
1

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

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

无论如何,这是我能看到的最简单的解决方案,如果可以选择使用带有 OpenGL 的其他库。

于 2011-08-24T05:14:55.177 回答