【发布时间】:2018-01-28 14:49:51
【问题描述】:
我正在使用具有 Alpha 通道 (32bpp ARGB) 的 FBO(或“渲染纹理”)并使用不完全不透明的颜色清除该通道,例如(R=1、G=0、B=0 , A=0)(即完全透明)。然后我正在渲染一个半透明的对象,例如一个带有颜色(R=1,G=1,B=1,A=0.5)的矩形,在此之上。 (所有值从 0 归一化到 1)
按照常识,还有GIMP和Photoshop等成像软件,以及Porter-Duff合成的几篇文章,我希望得到的纹理是
- 矩形外完全透明
- 矩形内不透明度为 50% 的白色(1.0、1.0、1.0)。
像这样(你不会在 SO 网站上看到这个):
相反,背景颜色 RGB 值 (1.0, 0.0, 0.0) 使用 (1 - SourceAlpha) 而不是 (DestAlpha * (1 - SourceAlpha)) 进行整体加权。实际结果是这样的:
我已经直接使用 OpenGL、使用 SDL 的包装器 API 和 SFML 的包装器 API 验证了这种行为。使用 SDL 和 SFML,我还将结果保存为图像(带有 alpha 通道),而不是仅仅渲染到屏幕上,以确保最终渲染步骤没有问题。
使用 SDL、SFML 或直接使用 OpenGL,我需要做什么才能产生预期的 SourceOver 结果?
一些来源:
W3 article on compositing,指定co = αs x Cs + αb x Cb x (1 – αs),如果αb为0,Cb的权重应该为0,无论如何。
English Wiki 显示目的地(“B”)根据 αb(以及 αs,间接地)加权。
German Wiki 显示 50% 透明度示例,透明背景的原始 RGB 值显然不会干扰绿色或洋红色源,还表明交叉点明显不对称,有利于“顶部”元素.
还有几个关于 SO 的问题乍一看似乎涉及到这个问题,但我找不到任何与这个特定问题相关的内容。人们建议使用不同的 OpenGL 混合函数,但普遍的共识似乎是 glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA),这是 SDL 和 SFML 默认使用的。我也尝试了不同的组合,但没有成功。
另一个建议是将颜色与目标 alpha 进行预乘,因为 OpenGL 只能有 1 个因子,但正确的 SourceOver 需要 2 个因子。但是,我完全无法理解这一点。如果我将 (1, 0, 0) 与目标 alpha 值(例如 (0.1))进行预乘,我会得到 (0.1, 0, 0)(例如建议 here)。现在我可以告诉 OpenGL 因子 GL_ONE_MINUS_SRC_ALPHA (并且只有 GL_SRC_ALPHA 的源),但随后我有效地与黑色混合,这是不正确的。虽然我不是该主题的专家,但我付出了相当多的努力来试图理解(并且至少达到了我设法编写了每种合成模式的工作纯软件实现的地步)。我的理解是,将 0.1 的 alpha 值“通过预乘”应用于 (1.0, 0.0, 0.0) 与将 alpha 值正确地视为第四个颜色分量完全不同。
这是一个使用 SDL 的最小且完整的示例。需要 SDL2 本身来编译,如果你想保存为 PNG,可以选择 SDL2_image。
// Define to save the result image as PNG (requires SDL2_image), undefine to instead display it in a window
#define SAVE_IMAGE_AS_PNG
#include <SDL.h>
#include <stdio.h>
#ifdef SAVE_IMAGE_AS_PNG
#include <SDL_image.h>
#endif
int main(int argc, char **argv)
{
if (SDL_Init(SDL_INIT_VIDEO) != 0)
{
printf("init failed %s\n", SDL_GetError());
return 1;
}
#ifdef SAVE_IMAGE_AS_PNG
if (IMG_Init(IMG_INIT_PNG) == 0)
{
printf("IMG init failed %s\n", IMG_GetError());
return 1;
}
#endif
SDL_Window *window = SDL_CreateWindow("test", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);
if (window == NULL)
{
printf("window failed %s\n", SDL_GetError());
return 1;
}
SDL_Renderer *renderer = SDL_CreateRenderer(window, 1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
if (renderer == NULL)
{
printf("renderer failed %s\n", SDL_GetError());
return 1;
}
// This is the texture that we render on
SDL_Texture *render_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 300, 200);
if (render_texture == NULL)
{
printf("rendertexture failed %s\n", SDL_GetError());
return 1;
}
SDL_SetTextureBlendMode(render_texture, SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
printf("init ok\n");
#ifdef SAVE_IMAGE_AS_PNG
uint8_t *pixels = new uint8_t[300 * 200 * 4];
#endif
while (1)
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
if (event.type == SDL_QUIT)
{
return 0;
}
}
SDL_Rect rect;
rect.x = 1;
rect.y = 0;
rect.w = 150;
rect.h = 120;
SDL_SetRenderTarget(renderer, render_texture);
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 0);
SDL_RenderClear(renderer);
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 127);
SDL_RenderFillRect(renderer, &rect);
#ifdef SAVE_IMAGE_AS_PNG
SDL_RenderReadPixels(renderer, NULL, SDL_PIXELFORMAT_ARGB8888, pixels, 4 * 300);
// Hopefully the masks are fine for your system. Might need to randomly change those ff parts around.
SDL_Surface *tmp_surface = SDL_CreateRGBSurfaceFrom(pixels, 300, 200, 32, 4 * 300, 0xff0000, 0xff00, 0xff, 0xff000000);
if (tmp_surface == NULL)
{
printf("surface error %s\n", SDL_GetError());
return 1;
}
if (IMG_SavePNG(tmp_surface, "t:\\sdltest.png") != 0)
{
printf("save image error %s\n", IMG_GetError());
return 1;
}
printf("image saved successfully\n");
return 0;
#endif
SDL_SetRenderTarget(renderer, NULL);
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, render_texture, NULL, NULL);
SDL_RenderPresent(renderer);
SDL_Delay(10);
}
}
【问题讨论】:
-
你是说
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA)不适合你吗?请注意,生成的图像将具有预乘 alpha,这可能会让您感到困惑。 -
@HolyBlackCat 是的,在渲染四边形时,该函数正在传递给 OpenGL(通过 SDL 库)。结果是我发布的微红图像。如果生成的图像“具有预乘 alpha”(如果这确实是问题所在),那么您能否澄清一下 实际上 的含义以及如何将其转换为预期结果?
-
我想我知道出了什么问题。可能目标图像也必须具有预乘 alpha,这对于您的红色矩形而言并非如此。 “这实际上意味着什么以及如何将其转换为预期结果” 这意味着结果看起来就像是应用了
color.rgb *= color.a。要将其转换回来,您可以使用color.rgb =/ color.a。但请注意,通常只有在保存到文件时才需要将其转换回来。 -
如果您使用该技术渲染到屏幕上,生成的图像将看起来就好像它是在黑色背景上以适当的混合方式渲染的(而不是忽略生成的 alpha照常)。这不是问题,因为通常屏幕背景是不透明的,因此不会有任何视觉差异。
-
@HolyBlackCat “可能目标图像也必须具有预乘 alpha” - 您是指源图像吗?无论如何,如果我要这样做,然后将纹理渲染到不透明的背景上,那么只有当背景是统一的黑色时,结果才可能是正确的。所有其他背景颜色都会有可见的伪影。在这种情况下,首先使用 alpha 混合的全部好处就没有了,我还不如渲染到不透明的黑色背景上,而不是完全透明的背景上。
标签: opengl rendering sdl-2 alphablending compositing