SDL_RenderCopy-ing 到另一个纹理会导致图像略微变暗

SDL_RenderCopy-ing to another texture results in subtly darker image

我一直在追查这个视觉错误,我得出的结论是,从一个纹理渲染复制到另一个纹理的过程会导致图像稍微变暗。

#include "basics.h"

#include <SDL.h>
#include <SDL_ttf.h>

#ifdef WIN32
#define _WIN32_WINNT 0x0500
#include <windows.h>
#endif



//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~O~~~~~~~~~~| M A I N |~~~~~~~~~~~O~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int main(int argc, char *argv[]){

    SDL_Window *window;
    SDL_Renderer *renderer;
    int width = 640;
    int height = 480;
    int loop = 1;
    int mouseX, mouseY, pmouseX, pmouseY;


    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s", SDL_GetError());
        return 3;
    }
    if (SDL_CreateWindowAndRenderer(width, height, SDL_WINDOW_RESIZABLE | SDL_WINDOW_MAXIMIZED, &window, &renderer)) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window and renderer: %s", SDL_GetError());
        return 3;
    }

    SDL_GetWindowSize( window, &width, &height );


    const SDL_Color black = {0, 0, 0, 255};
    const SDL_Color white = {255, 255, 255, 255};

    char font_filename [] = "fonts/FreeSerif.ttf";//AveriaLibre-Regular
    int size = 20;

    if(TTF_Init() == -1){
        printf("TTF_Init: %s\n", TTF_GetError());
    }
    TTF_Font *font = TTF_OpenFont( font_filename, size );
    
    char str [] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
    
    SDL_Rect dst1 = (SDL_Rect){ 100, 100, 400, 200 };
    SDL_Rect dst2 = (SDL_Rect){ 515, 100, 400, 200 };

    
    SDL_Texture *buffer = SDL_CreateTexture( renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, width, height );
    SDL_SetTextureBlendMode( buffer, SDL_BLENDMODE_BLEND );

    SDL_SetRenderDrawColor( renderer, 0, 0, 0, 255);
    SDL_RenderClear( renderer );

    SDL_Surface *surf = TTF_RenderText_Blended_Wrapped( font, str, white, dst1.w );
    dst1.w = surf->w; dst1.h = surf->h;
    SDL_Texture *texture = SDL_CreateTextureFromSurface( renderer, surf );
    SDL_RenderCopy( renderer, texture, NULL, &dst1 );


    SDL_SetRenderTarget( renderer, buffer );
    dst2.w = surf->w; dst2.h = surf->h;
    SDL_RenderCopy( renderer, texture, NULL, &dst2 );
    SDL_SetRenderTarget( renderer, NULL );
    SDL_RenderCopy( renderer, buffer, NULL, &(SDL_Rect){0, 0, width, height} );
    

    SDL_FreeSurface( surf );
    SDL_DestroyTexture( texture );

    char ascii [] = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
    
    surf = TTF_RenderText_Blended( font, ascii, white );
    texture = SDL_CreateTextureFromSurface( renderer, surf );
    SDL_RenderCopy( renderer, texture, NULL, &(SDL_Rect){100, 500, surf->w, surf->h} );

    
    SDL_SetRenderTarget( renderer, buffer );
    SDL_RenderCopy( renderer, texture, NULL, &(SDL_Rect){100, 530, surf->w, surf->h} );
    SDL_SetRenderTarget( renderer, NULL );
    SDL_RenderCopy( renderer, buffer, NULL, &(SDL_Rect){0, 0, width, height} );


    SDL_RenderPresent(renderer);

    while( 1 ) {
        SDL_Event event;
        while( SDL_PollEvent(&event) ){
            if(event.type == SDL_QUIT) {
                goto exit;
            }
        }
    }
    exit:

    SDL_DestroyTexture( buffer );
    SDL_DestroyTexture( texture );
    SDL_FreeSurface( surf );
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);

    SDL_Quit();

    return 0;
}

这个程序的结果是: text rendering output

就像我说的,这是一个微妙的效果,您可能需要放大,但就我的目的而言,它真的很糟糕。任何人都知道可能是什么原因,或者一些解决方法?谢谢。

您对同一数据多次递归应用混合操作。

您的渲染纹理与 window 的大小相同。你没有清除它,所以我们假设它默认为零,包括 alpha(它可能是 implementation-dependant,我没有看到任何文档说明;明确清除所需的颜色可能是个好主意)。

SDL_ttf 为您提供带 alpha 通道的表面。然后使用 blendmode=blend 将第一个文本片段渲染到该纹理,将其混合在零之上。如果您查看 formulas for blend operations - 渲染目标的 alpha 值会被文本纹理覆盖(dstA = srcA + (dstA * (1-srcA)),而 dstA 为 0,因此它只是 srcA),但是颜色值乘以您的源 alpha,所以 srcRGB = srcRGB * srcA.

然后你在你的屏幕顶部混合这个目标纹理,它被清除为 (0, 0, 0, 255),再次应用相同的混合操作,导致由相同的 srcA 进行另一次混合,所以你的颜色现在 srcRGB = srcRGB * srcA * srcA。这显然是不正确的。

第二个文本片段的情况变得更糟,因为您再次执行了所有操作,但从未清除渲染目标。因此,当您再次使用渲染目标时,您将对整个纹理进行另一次渲染,包括在第一个文本片段中渲染的内容,并在其上应用另一次混合。

您只需混合一次。有多种方法可以做到这一点,您必须决定最适合您的情况。我不知道你想如何使用你的渲染纹理,所以假设你想保留 alpha 值,你需要在最后阶段进行混合。这可以通过使用 blendmode=none 绘制到渲染目标,然后将渲染纹理 blit 到屏幕上来实现,但 仅一次 。例如:

SDL_Texture *buffer = SDL_CreateTexture( renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, width, height );
SDL_SetTextureBlendMode( buffer, SDL_BLENDMODE_BLEND );

SDL_SetRenderDrawColor( renderer, 0, 0, 0, 255);
SDL_RenderClear( renderer );

// first text fragment, rendering directly to screen
SDL_Surface *surf = TTF_RenderText_Blended_Wrapped( font, str, white, dst1.w );
dst1.w = surf->w; dst1.h = surf->h;
SDL_Texture *texture = SDL_CreateTextureFromSurface( renderer, surf );
SDL_RenderCopy( renderer, texture, NULL, &dst1 );

// first text fragment, rendering to render texture. blendmode=none to avoid
// pre-blending
SDL_SetRenderTarget( renderer, buffer );
dst2.w = surf->w; dst2.h = surf->h;
SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_NONE);
SDL_RenderCopy( renderer, texture, NULL, &dst2 );
SDL_SetRenderTarget( renderer, NULL );
// NOT copying render target to screen. It is not finished yet, need to do
// that only once

SDL_FreeSurface( surf );
SDL_DestroyTexture( texture );

char ascii [] = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~";

// second text fragment, rendering directly to screen
surf = TTF_RenderText_Blended( font, ascii, white );
texture = SDL_CreateTextureFromSurface( renderer, surf );
SDL_RenderCopy( renderer, texture, NULL, &(SDL_Rect){100, 500, surf->w, surf->h} );

// second text fragment, rendering to render texture, blendmode=none
SDL_SetRenderTarget( renderer, buffer );
SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_NONE);
SDL_RenderCopy( renderer, texture, NULL, &(SDL_Rect){100, 530, surf->w, surf->h} );
SDL_SetRenderTarget( renderer, NULL );

// render texture finished, now it is time to blend it with screen contents
SDL_RenderCopy( renderer, buffer, NULL, &(SDL_Rect){0, 0, width, height} );

SDL_RenderPresent(renderer);