如何使用OpenGL在SDL中获得正确的SourceOver alpha合成

如何使用OpenGL在SDL中获得正确的SourceOver alpha合成,opengl,rendering,sdl-2,alphablending,compositing,Opengl,Rendering,Sdl 2,Alphablending,Compositing,我使用的FBO(或“渲染纹理”)具有alpha通道(32bpp ARGB),并使用不完全不透明的颜色清除该通道,例如(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网

我使用的FBO(或“渲染纹理”)具有alpha通道(32bpp ARGB),并使用不完全不透明的颜色清除该通道,例如(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通道),而不仅仅是渲染到屏幕上,以确保最终渲染步骤没有问题

使用SDL、SFML或直接使用OpenGL,我需要做什么才能生成预期的SourceOver结果

一些资料来源:

,指定co=αs x Cs+αb x Cb x(1–αs),如果αb为0,则Cb的权重应为0,无论怎样

显示根据αB(以及间接的αs)加权的目的地(“B”)

显示50%透明度示例,透明背景的原始RGB值显然不会干扰绿色或品红光源,还显示交点明显不对称,有利于“在顶部”的元素

还有几个问题,乍一看似乎可以解决这个问题,但我找不到任何关于这个具体问题的讨论。人们建议使用不同的OpenGL混合函数,但普遍的共识似乎是
glBlendFuncSeparate(GL_SRC_ALPHA,GL_ONE_减去SRC_ALPHA,GL_ONE,GL_ONE_减去SRC_ALPHA)
,这是SDL和SFML默认使用的。我也尝试过不同的组合,但没有成功

另一个建议是将颜色与目标alpha进行预乘,因为OpenGL只能有1个因子,但需要2个因子才能获得正确的SourceOver。然而,我完全无法理解这一点。如果我将(1,0,0)与目标alpha值(比如,(0.1)进行预乘,我得到(0.1,0,0)(例如建议的)。现在我可以告诉OpenGL这个因子
GL\u ONE\u减去\u SRC\u ALPHA
(源代码是
GL\u SRC\u ALPHA
),但我实际上是在混合黑色,这是不正确的。虽然我不是这方面的专家,但我花了相当多的精力去理解(至少我已经成功地为每种合成模式编写了一个工作的纯软件实现)。我的理解是,将0.1“通过预乘”的alpha值应用于(1.0,0.0,0.0)与将alpha值正确地视为第四个颜色分量完全不同

下面是一个使用SDL的最小且完整的示例。需要SDL2本身进行编译,如果要另存为PNG,则可以选择SDL2_图像

// 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);
    }
}
//定义将结果图像保存为PNG(需要SDL2_图像),取消定义将其显示在窗口中
#定义将图像保存为PNG
#包括
#包括
#ifdef将图像另存为PNG
#包括
#恩迪夫
int main(int argc,字符**argv)
{
如果(SDL_Init(SDL_Init_视频)!=0)
{
printf(“初始化失败%s\n”,SDL_GetError());
返回1;
}
#ifdef将图像另存为PNG
if(IMG_Init(IMG_Init_PNG)==0)
{
printf(“IMG init失败%s\n”,IMG_GetError());
返回1;
}
#恩迪夫
SDL_Window*Window=SDL_CreateWindow(“测试”,SDL_WINDOWPOS_居中,SDL_WINDOWPOS_居中,800600,SDL_Window_OPENGL|SDL_Window_显示);
如果(窗口==NULL)
{
printf(“窗口失败%s\n”,SDL_GetError());
返回1;
}
SDL_Renderer*Renderer=SDL_CreateRenderer(窗口,1,SDL_Renderer_ACCELERATED | SDL_Renderer_TARGETTEXTURE);
if(渲染器==NULL)
{
printf(“呈现程序失败%s\n”,SDL_GetError());
返回1;
}
//这是我们渲染的纹理
SDL_纹理*render_纹理=SDL_CreateTexture(渲染器,SDL_像素格式_RGBA8888,SDL_TEXTUREACCESS_目标,300,200);
如果(渲染纹理==NULL)
{
printf(“rendertexture失败%s\n”,SDL_GetError());
返回1;
}
SDL_SetTextureBlendMode(渲染纹理,SDL_BLENDMODE_混合);
SDL_SetRenderDrawBlendMode(渲染器,SDL_BLENDMODE_混合);
printf(“init ok\n”);
#ifdef将图像另存为PNG
uint8_t*像素=新的uint8_t[300*200*4];
#恩迪夫
而(1)
{
SDL_事件;
while(SDL_事件和事件))
{
if(event.type==SDL\u QUIT)
{
返回0;
}
}
SDL_Rect;
rect.x=1;
矩形y=0;
矩形w=150;
矩形h=120;
SDL_SetRenderTarget(渲染器、渲染纹理);
SDL_SetRenderDrawColor(渲染器,255,0,0,0);
SDL_渲染器(渲染器);
SDL_SetRenderDrawColor(渲染器,255、255、255、127);
SDL_RenderFillRect(渲染器,&rect);
#ifdef将图像另存为PNG
SDL_渲染器像素(渲染器,NULL,SDL_像素格式,argb888,像素,4*300);
//希望遮罩适合您的系统。可能需要随机更改周围的ff部件。
SDL_Surface*tmp_Surface=SDL_CreateRGBSurfaceFrom(像素,300、200、32、4*300、0xff0000、0xff00、0xff、0xff000000);
如果(tmp_曲面==NULL)
{
printf(“表面错误%s\n”,SDL\u GetError())
// Clear with the premultiplied version of the real background color - the texture (which is always the destination in all blending operations) now complies with the "destination must always have premultiplied alpha" convention.
glClearColor(0.1f, 0.0f, 0.0f, 0.1f); 
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

//
// Option 1 - source either already has premultiplied alpha for whatever reason, or we can easily ensure that it has
//
{
    // Set the drawing color to the premultiplied version of the real drawing color.
    glColor4f(0.5f, 0.5f, 0.5f, 0.5f);

    // Set the blending equation according to "blending source with premultiplied alpha".
    glEnable(GL_BLEND);
    glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    glBlendEquationSeparate(GL_ADD, GL_ADD);
}

//
// Option 2 - source does not have premultiplied alpha
// 
{
    // Set the drawing color to the original version of the real drawing color.
    glColor4f(1.0f, 1.0f, 1.0f, 0.5f);

    // Set the blending equation according to "blending source with premultiplied alpha".
    glEnable(GL_BLEND);
    glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    glBlendEquationSeparate(GL_ADD, GL_ADD);
}

// --- draw the thing ---

glDisable(GL_BLEND);
// The back buffer has 100 % alpha.
glClearColor(0.0f, 0.0f, 0.5f, 1.0f); 
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// The color with which the texture is drawn - the modulating color's RGB values also need premultiplied alpha
glColor4f(0.0f, 0.0f, 0.8f, 0.8f);

// Set the blending equation according to "blending source with premultiplied alpha".
glEnable(GL_BLEND);
glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glBlendEquationSeparate(GL_ADD, GL_ADD);

// --- draw the texture ---

glDisable(GL_BLEND);
renderTexture.clear(sf::Color(25, 0, 0, 25));

sf::RectangleShape rect;
sf::RenderStates rs;
// Assuming the object has premultiplied alpha - or we can easily make sure that it has
{
    rs.blendMode = sf::BlendMode(sf::BlendMode::One, sf::BlendMode::OneMinusSrcAlpha);
    rect.setFillColor(sf::Color(127, 127, 127, 127));
}

// Assuming the object does not have premultiplied alpha
{
    rs.blendMode = sf::BlendAlpha; // This is a shortcut for the constructor with the correct blending parameters for this type
    rect.setFillColor(sf::Color(255, 255, 255, 127));
}

// --- align the rect ---

renderTexture.draw(rect, rs);
// premultiplied modulation color
renderTexture_sprite.setColor(sf::Color(0, 0, 204, 204));
window.clear(sf::Color(0, 0, 127, 255));
sf::RenderStates rs;
rs.blendMode = sf::BlendMode(sf::BlendMode::One, sf::BlendMode::OneMinusSrcAlpha);
window.draw(renderTexture_sprite, rs);