如何使用OpenGL使图像变黑?

问题描述 投票:4回答:5

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

我将解释其工作方式:

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

编辑:我更希望知道这样做的非着色器方式,但是如果不可能,那么我也可以接受着色器方式。

Edit2:既然这里有些混乱,我想告诉我可以通过在整个场景上绘制一个黑色透明四边形来实现这种效果。但是,这不起作用,因为我希望它起作用;像素可以得到的暗度有一个限制,因此它将始终使某些像素“可见”(高于零色值),因为:1 * 0.9 = 0.9->再四舍五入,等等。我可以“修复“这是通过缩短路径的长度来实现的,但是我希望能够尽可能地调整路径的长度,而不是使用双线性(如果是正确的单词)插值,我希望是线性的(因此它将始终从每个r减去-1, g,b值在0-255范围内,而不是使用百分比值)。

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

Edit4:我将使其简单化,我想为此使用OpenGL方法,效果应使用尽可能少的功率,内存或带宽。此效果应该在不清除屏幕像素的情况下起作用,因此,如果我在场景上绘制透明四边形,则先前绘制的像素会变暗等。但是如上所述,它不能很好地工作。最大的缺点是:1)从屏幕上读取像素,在for循环中一一修改它们,然后上传回来。 2)用不同的黑暗度渲染我的对象X次,以模拟拖尾效果。 3)不能将颜色值相乘,因为它不会使像素变黑,它们将以一定的亮度永久保留在屏幕上(请参见上面的说明)。

c++ opengl blending
5个回答
9
投票

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

在解释如何执行此操作之前,我想说的是,您要获得的视觉效果是一种可怕的视觉效果,因此您不应该使用它。从每种RGB颜色中减去一个值将产生不同颜色,而不是同一颜色的较暗版本。如果将RGB颜色(255,128,0)减去1,则将变为(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以来,我们无法做到这一点。相反,我们必须做一些不同的事情。我们必须将映像映射到后缓冲区,然后交换缓冲区。

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);

您已经完成。


6
投票

您可能要使用glBlendFunc(GL_ONE,GL_SRC_ALPHA)绘制从1.0到0.0的alpha值的黑色矩形。

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

您无法通过简单的淡化为黑色操作根据其年龄来淡化单个像素。通常,渲染目标不会“记住”先前帧中绘制的内容。我可以想到一种方法,通过交替渲染到一对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

实际上,缩放比例应为(float)/ 255.0 / 255.0,以使组件均等地淡出(并且没有一个从较低值开始的组件在其他组件之前变为零)。

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

由于您仍然需要重新渲染每个像素,因此只需使用适当的颜色渐变来渲染每个像素:越深,像素越旧。如果您的像素很多,则采用双重FBO方法可能有用。

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


2
投票

一种非着色方法,特别是如果淡化为黑色是在屏幕上唯一发生的事情,就是通过readpixels iirc抓取屏幕内容,将其弹出到纹理中,然后放置一个矩形上带有该纹理的屏幕上,然后您可以将矩形的颜色调制为黑色,以完成您想要的效果。


1
投票

是驱动程序,Windows本身不支持OpenGL或仅支持低版本,我认为是1.5。所有较新的版本均随附ATI或NVIDIA,Intel等的驱动程序。您是否使用其他卡?您正在有效使用哪个版本的OpenGL?


1
投票

是这种情况,所以我不能使用纯OpenGL。我不确定您的项目是否有足够的空间(如果您正在使用其他窗口API则可能没有),或者增加的复杂性是否值得,但是添加像SDL这样的2D库(与OpenGL配合使用)将使您满意直接以合理的方式直接处理显示表面的像素以及一般像素,而OpenGL通常并不容易。

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

无论如何,这都是我可以看到的最简单的解决方案,如果可以在OpenGL中使用其他库。

© www.soinside.com 2019 - 2024. All rights reserved.