我正在使用具有 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 合成的几篇文章,我希望得到的纹理是
- 矩形外完全透明
- 白色 (1.0, 1.0, 1.0),矩形内不透明度为 50%。
像这样(你不会在 SO 网站上看到这个):
相反,背景颜色 RGB 值,即 (1.0, 0.0, 0.0) 总体上使用 (1 - SourceAlpha) 而不是 (DestAlpha * (1 - SourceAlpha)) 加权。实际结果是这样的:
我已经直接使用 OpenGL、使用 SDL 的包装器 API 和 SFML 的包装器 API 验证了这种行为。使用 SDL 和 SFML,我还将结果保存为图像(带有 alpha 通道),而不是仅仅渲染到屏幕上,以确保最终渲染步骤没有问题。
我需要做什么来产生预期的 SourceOver 结果,无论是使用 SDL、SFML 还是直接使用 OpenGL?
一些资料来源:
W3 关于合成的文章,指定 co = αs x Cs + αb x Cb x (1 – αs),如果 αb 为 0,则 Cb 的权重应为 0,无论如何。
英语 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)(例如这里建议的)。现在我可以告诉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);
}
}