如何用OpenGL制作淡入黑的效果?

发布于 2024-11-26 01:22:34 字数 1007 浏览 4 评论 0 原文

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

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

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

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

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

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

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

Im trying to achieve fade-to-black effect, but i dont know how to do it. I tried several things but they fail due to how opengl works

I will explain how it would work:

If i draw 1 white pixel and move it around each frame for one pixel to some direction, each frame the screen pixels will get one R/G/B value less (of range 0-255), thus after 255 frames the white pixel will be fully black. So if i move the white pixel around, i would see a gradient trail going from white to black evenly 1 color value difference compared to previous pixel color.

Edit: I would prefer to know non-shader way of doing this, but if its not possible then i can accept shader-way too.

Edit2: Since there is some confusion around here, I would like to tell that i can do this kind of effect already by drawing a black transparent quad over my whole scene. BUT, this does not work as i want it to work; there is a limit on the darkness the pixels can get, so it will always leave some of the pixels "visible" (above zero color value) because: 1*0.9 = 0.9 -> rounded to 1 again, etc. I can "fix" this by making the trail shorter, but i want to be able to adjust the trail lenght as much as possible and instead of bilinear (if thats the right word) interpolation i want linear (so it would always reduce -1 from each r,g,b value in 0-255 scale, instead of using a percent value).

Edit3: Still some confusion left, so lets be clear: i want to improve the effect that is done by disabling GL_COLOR_BUFFER_BIT from glClear(), i dont want to see the pixels on my screen FOREVER, so i want to make them darker in time, by drawing a quad over my scene that will reduce each of the pixels color value by 1 (in 0-255 scale).

Edit4: I'll make it simple, i want OpenGL method for this, the effect should use as little power, memory or bandwidth as possible. this effect is supposed to work without clearing the screen pixels, so if i draw a transparent quad over my scene, the previous pixels drawn will get darker etc. But as explained above few times, its not working very well. The big NO's are: 1) reading pixels from screen, modifying them one by one in a for loop and then uploading back. 2) rendering my objects X times with different darknesses to emulate the trail effect. 3) multiplying the color values is not an option since it wont make the pixels into black, they will stay on the screen forever at certain brightness (see explanation somewhere above).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

嗫嚅 2024-12-03 01:22:34

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

在解释如何执行此操作之前,我想说您想要的视觉效果是一种糟糕的视觉效果,您不应该使用它。从每种 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);

如果您不知道如何使用着色器,那么您应该学习。但如果您不想,那么您可以使用 学习着色器;从长远来看,这要容易得多。

正常绘制东西

现在,您只需正常渲染所有内容即可。只是不要解绑FBO;我们仍然想渲染它。

在屏幕上显示渲染图像

通常,您会使用交换缓冲区调用来显示渲染结果。但由于我们渲染的是 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);

您就完成了。

If i draw 1 white pixel and move it around each frame for one pixel to some direction, each frame the screen pixels will get one R/G/B value less (of range 0-255), thus after 255 frames the white pixel will be fully black. So if i move the white pixel around, i would see a gradient trail going from white to black evenly 1 color value difference compared to previous pixel color.

Before I explain how to do this, I would like to say that the visual effect you're going for is a terrible visual effect and you should not use it. Subtracting a value from each of the RGB colors will produce a different color, not a darker version of the same color. The RGB color (255,128,0), if you subtract 1 from it 128 times, will become (128, 0, 0). The first color is brown, the second is a dark red. These are not the same.

Now, since you haven't really explained this very well, I have to make some guesses. I am assuming that there are no "objects" in what you are rendering. There is no state. You're simply drawing stuff at arbitrary locations, and you don't remember what you drew where, nor do you want to remember what was drawn where.

To do what you want, you need two off-screen buffers. I recommend using FBOs and screen-sized textures for these. The basic algorithm is simple. You render the previous frame's image to the current image, using a blend mode that "subtracts 1" from the colors you write. Then you render the new stuff you want to the current image. Then you display that image. After that, you switch which image is previous and which is current, and do the process all over again.

Note: The following code will assume OpenGL 3.3 functionality.

Initialization

So first, during initialization (after OpenGL is initialized), you must create your screen-sized textures. You also need two screen-sized depth buffers.

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

Drawing Previous Frame

The next step will be drawing the previous frame's image to the current image.

To do this, we need to have the concept of a previous and current FBO. This is done by having two variables: currIndex and prevIndex. These values are indices into our GLuint arrays for textures, renderbuffers, and FBOs. They should be initialized (during initialization, not for each frame) as follows:

currIndex = 0;
prevIndex = 1;

In your drawing routine, the first step is to draw the previous frame, subtracting one (again, I strongly suggest using a real blend here).

This won't be full code; there will be pseudo-code that I expect you to fill in.

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

The RenderFullscreenQuadWithTexture function does exactly what it says: renders a quad the size of the screen, using the currently bound texture. The program object BlenderProgramObject is a GLSL shader that does our blend operation. It fetches from the texture and does the blend. Again, I'm assuming you know how to set up a shader and so forth.

The fragment shader would have a main function that looks something like this:

shaderOutput = texture(prevImage, texCoord) - (1.0/255.0);

Again, I strongly advise this:

shaderOutput = texture(prevImage, texCoord) * (0.05);

If you don't know how to use shaders, then you should learn. But if you don't want to, then you can get the same effect using a glTexEnv function. And if you don't know what those are, I suggest learning shaders; it's so much easier in the long run.

Draw Stuff As Normal

Now, you just render everything you would as normal. Just don't unbind the FBO; we still want to render to it.

Display the Rendered Image on Screen

Normally, you would use a swapbuffer call to display the results of your rendering. But since we rendered to an FBO, we can't do that. Instead, we have to do something different. We must blit our image to the backbuffer and then swap buffers.

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

Switch Images

Now we need to do one more thing: switch the images that we're using. The previous image becomes current and vice versa:

std::swap(currIndex, prevIndex);

And you're done.

请恋爱 2024-12-03 01:22:34

您可能想要使用 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 方法
可能会起作用。

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

You may want to render a black rectangle with alpha going from 1.0 to 0.0 using glBlendFunc (GL_ONE, GL_SRC_ALPHA).

Edit in response to your comment (reply doesn't fit in a comment):

You cannot fade single pixels depending on their age with a simple fade-to-black operation. Usually a render target does not "remember" what has drawn to it in previous frames. I could think of a way to do this by alternatingly rendering to one of a pair of FBOs and using their alpha channel for it, but you needed a shader there. So what you would do is first render the FBO containing the pixels at their previous positions, decreasing their alpha value by one, dropping them when alpha == 0, otherwise darkening them whenever their alpha decreases, then render the pixels at their current positions with alpha == 255.

If you only have moving pixels:

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

If you want to modify some scene (i.e. have a scene and moving pixels in it):

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

Actually the scale should be (float) / 255.0 / 255.0 to make the components equally fade away (and not one that started at a lower value become zero before the others do).

If you only have a few moving pixels, you could re-render the pixel at all previous positions up to 255 "ticks" back.

Since you need to re-render each of the pixels anyway, just render each one with the proper color gradient: Darker, the older the pixel is. If you have a real lot of pixels, the dual FBO approach
might work.

I am writing ticks, and not frames, because frames can take a varying amount of time depending on renderer and hardware, but you probably want to have the pixel trail fade away within a constant time. That means you need to dim each pixel only after so-and-so many milliseconds, keeping their color for the frames in between.

我三岁 2024-12-03 01:22:34

一种非着色器的方法是通过 readpixels iirc 获取屏幕内容,将其弹出到纹理中,特别是当屏幕上唯一发生的事情是淡入黑色时,并在屏幕上放置一个具有该纹理的矩形,然后您可以将矩形的颜色调制为黑色,以实现您想要实现的效果。

One non-shader way of doing this, especially if the fade to black is the only thing that is going on the screen is to grab the contents of the screen via readpixels iirc, pop those into a texture, and put a rectangle up onto the screen with that texture, then you can modulate the color of the rectangle to towards black to do the efect that you want to accomplish.

失与倦" 2024-12-03 01:22:34

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

It is the drivers, Windows itself does not support OpenGL or only a low Version, I think 1.5. All newer versions come with drivers from ATI or NVIDIA, Intel etc.
Are you using different cards?
What version of OpenGL are you effectivly using?

靖瑶 2024-12-03 01:22:34

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

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

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

It's situations like this that make it so I cannot use pure OpenGL. I am not sure if your project has room for it (which it may not if you're using another windowing API), or if the added complexity would be worth it, but adding a 2D library like SDL which works with OpenGL would allow you to directly work with the display surface's pixels in a reasonable fashion, as well as just pixels in general, which OpenGL generally doesn't make easy.

Then all you would need to do is run through the display surface's pixels before OpenGL renders it's geometry, and subtract 1 from each RGB component.

That's the easiest solution I can see anyway, if using additional libraries with OpenGL is an option.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文