raysan5 / raylib

A simple and easy-to-use library to enjoy videogames programming

Home Page:http://www.raylib.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[examples] FXAA anti-alising example for RenderTexture

Lucrecious opened this issue · comments

Issue description

I'm trying to get RenderTexture to be DPI aware. I'm using rlImGui (raylib DearImGui backend), and its very pixel-y no matter how big I make the underlying texture.

Regarding #65 which was posted almost 10 years ago, I tried the given shader solution and looked all over for examples of its usage in the repository, but none were found. The shader does not work as intended.

I think it would be nice to have a working solution for RenderTexture with hiDPI and antialiasing, whether there's a compile time option to let RenderTextures enable MSAA, or a runtime flag, a shader that gets most of the way there, or a hack/workaround within the raylib examples that works. I feel like this should be documented somewhere, no?

Issue Screenshot

Here's the render texture as an imgui image element:
image
^ This looks the same even without putting it an imgui element and rendering it directly on the main frame buffer. This is not DPI aware, but even if I scale it up, it still looks jagged and pixel-y.

Here's the same draw commands but within the main framebuffer:
image

The render texture is rendered at 1280 by 720, while hiDPI is enabled, I don't think it should look this low-quality... so I'm assuming this is a filtering issue?

I looked this up and found that RenderTexture does not use MSAA for performance reasons.

Is this why one looks pixelated and the other looks fine?

How do I get the best looking image for the RenderTexture?

Code Example

Here's the fragment shader I tried using:

#version 330

#define FXAA_REDUCE_MIN (1.0/128.0)
#define FXAA_REDUCE_MUL (1.0/8.0)
#define FXAA_SPAN_MAX 8.0

in vec2 fragTexCoord;
in vec4 fragColor;

out vec4 finalColor;

uniform sampler2D texture0;
uniform vec2 resolution;
 
void main()
{
    vec2 inverse_resolution = vec2(1.0/resolution.x,1.0/resolution.y);
    
    vec3 rgbNW = texture2D(texture0, fragTexCoord.xy + (vec2(-1.0,-1.0)) * inverse_resolution).xyz;
    vec3 rgbNE = texture2D(texture0, fragTexCoord.xy + (vec2(1.0,-1.0)) * inverse_resolution).xyz;
    vec3 rgbSW = texture2D(texture0, fragTexCoord.xy + (vec2(-1.0,1.0)) * inverse_resolution).xyz;
    vec3 rgbSE = texture2D(texture0, fragTexCoord.xy + (vec2(1.0,1.0)) * inverse_resolution).xyz;
    
    vec3 rgbM  = texture2D(texture0,  fragTexCoord.xy).xyz;
    
    vec3 luma = vec3(0.299, 0.587, 0.114);

    float lumaNW = dot(rgbNW, luma);
    float lumaNE = dot(rgbNE, luma);
    float lumaSW = dot(rgbSW, luma);
    float lumaSE = dot(rgbSE, luma);
    float lumaM  = dot(rgbM,  luma);
    float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
    float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); 

    vec2 dir;
    dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
    dir.y =  ((lumaNW + lumaSW) - (lumaNE + lumaSE));

    float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL),FXAA_REDUCE_MIN);
    float rcpDirMin = 1.0/(min(abs(dir.x), abs(dir.y)) + dirReduce);

    dir = min(vec2( FXAA_SPAN_MAX,  FXAA_SPAN_MAX),max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),dir * rcpDirMin)) * inverse_resolution;

    vec3 rgbA = 0.5 * (texture2D(texture0,   fragTexCoord.xy   + dir * (1.0/3.0 - 0.5)).xyz + texture2D(texture0,   fragTexCoord.xy   + dir * (2.0/3.0 - 0.5)).xyz);
    vec3 rgbB = rgbA * 0.5 + 0.25 * (texture2D(texture0,  fragTexCoord.xy   + dir *  - 0.5).xyz + texture2D(texture0,  fragTexCoord.xy   + dir * 0.5).xyz);
    
    float lumaB = dot(rgbB, luma);

    if((lumaB < lumaMin) || (lumaB > lumaMax)) 
    {
        finalColor = vec4(rgbA, 1.0);
    } 
    else 
    {
        finalColor = vec4(rgbB,1.0);
    }
}

Here's an example: it's the core_3d_camera_free example modified to be DPI aware and rendered to a render texture. Looks no different compared to rendering directly to a back-buffer.

#include "raylib.h"

//------------------------------------------------------------------------------------
// Program main entry point
//------------------------------------------------------------------------------------
int main(void)
{
    // Initialization
    //--------------------------------------------------------------------------------------
    const int screenWidth = 800;
    const int screenHeight = 450;

    SetConfigFlags(FLAG_WINDOW_HIGHDPI);

    InitWindow(screenWidth, screenHeight, "raylib [core] example - 3d camera free");

    // Define the camera to look into our 3d world
    Camera3D camera = { 0 };
    camera.position = (Vector3){ 10.0f, 10.0f, 10.0f }; // Camera position
    camera.target = (Vector3){ 0.0f, 0.0f, 0.0f };      // Camera looking at point
    camera.up = (Vector3){ 0.0f, 1.0f, 0.0f };          // Camera up vector (rotation towards target)
    camera.fovy = 45.0f;                                // Camera field-of-view Y
    camera.projection = CAMERA_PERSPECTIVE;             // Camera projection type

    Vector3 cubePosition = { 0.0f, 0.0f, 0.0f };

    DisableCursor();                    // Limit cursor to relative movement inside the window

    SetTargetFPS(60);                   // Set our game to run at 60 frames-per-second
    //--------------------------------------------------------------------------------------

    int width = GetRenderWidth();
    int height = GetRenderHeight();
    RenderTexture2D renderTexture = LoadRenderTexture(width, height);

    // Main game loop
    while (!WindowShouldClose())        // Detect window close button or ESC key
    {
        //Since our render texture is tied to our render resolution we need to reload it when window size changes
        //That can happen when changing the window size directly, when changing scaling factor of our current display or dragging the window to a display with a different scaling factor
        if (IsWindowResized()) {
            UnloadRenderTexture(renderTexture);
            int width = GetRenderWidth();
            int height = GetRenderHeight();
            renderTexture = LoadRenderTexture(width, height);
        }

        // Update
        //----------------------------------------------------------------------------------
        UpdateCamera(&camera, CAMERA_FREE);

        if (IsKeyPressed('Z')) camera.target = (Vector3){ 0.0f, 0.0f, 0.0f };
        //----------------------------------------------------------------------------------

        // Draw
        //----------------------------------------------------------------------------------
        BeginDrawing();
            
        BeginTextureMode(renderTexture);
            ClearBackground(RAYWHITE);

            BeginMode3D(camera);

                DrawCube(cubePosition, 2.0f, 2.0f, 2.0f, RED);
                DrawCubeWires(cubePosition, 2.0f, 2.0f, 2.0f, MAROON);

                DrawGrid(10, 1.0f);

            EndMode3D();

            DrawRectangle( 10, 10, 320, 93, Fade(SKYBLUE, 0.5f));
            DrawRectangleLines( 10, 10, 320, 93, BLUE);

            DrawText("Free camera default controls:", 20, 20, 10, BLACK);
            DrawText("- Mouse Wheel to Zoom in-out", 40, 40, 10, DARKGRAY);
            DrawText("- Mouse Wheel Pressed to Pan", 40, 60, 10, DARKGRAY);
            DrawText("- Z to zoom to (0, 0, 0)", 40, 80, 10, DARKGRAY);

        EndTextureMode();

        DrawTexturePro(renderTexture.texture, (Rectangle) { 0, 0, renderTexture.texture.width, -renderTexture.texture.height }, (Rectangle) { 0, 0, screenWidth, screenHeight }, (Vector2) {0.0, 0.0}, 0, WHITE);

        EndDrawing();
        //----------------------------------------------------------------------------------
    }

    // De-Initialization
    //--------------------------------------------------------------------------------------
    CloseWindow();        // Close window and OpenGL context
    //--------------------------------------------------------------------------------------

    return 0;
}

No, this wouldn't work. Even when the resolution is matched to the render width and height, the lines are still jagged, they don't seem to have any filter applied.

I'm not sure if #65 is still accurate today (almost 10 years later) in that RenderTexture still doesn't use MSAA for performance reasons. However, that FXAA shader that helps with antialiasing in that issue seems outdated as well somehow.

I'm trying to get RenderTexture to be DPI aware.

That's literally the first sentence in your message. I've provided an example on how to do it.

If your issue is aliasing - then dpi awareness has nothing to do with it. There's obviously going to be regular rasterization aliasing, but there's additional aliasing I see on your screenshot that (I guess) is caused by stretching that texture. My example doesn't have this issue.

So, let's be clear: this issue is about anti-aliasing for render textures? ( Either MSAA or FXAA)

Yes, I can see why that would be confusing, but I was mostly saying that to provide context. I think the rest of my issue makes it pretty clear though that I'm trying to get a smoother image using some type of anti-aliasing on the render texture.

I would be very glad if anti-aliasing on rendertextures were possible, I often use them to make videos by converting a rendertexture to an image and piping that to ffmpeg. It's a bit of a pity that the video isn't anti-aliased then.

I would be very glad if anti-aliasing on rendertextures were possible, I often use them to make videos by converting a rendertexture to an image and piping that to ffmpeg. It's a bit of a pity that the video isn't anti-aliased then.

A quick and easy way to get 4x SSAA is to make render texture 4x times bigger (2x on each axis) and then draw that texture to a regular size render texture ( don't forget to enable bilinear texture filtering on a big render texture). I assume additional overhead is not that important for an offline workflow that you have.

A quick and easy way to get 4x SSAA is to make render texture 4x times bigger (2x on each axis) and then draw that texture to a regular size render texture ( don't forget to enable bilinear texture filtering on a big render texture). I assume additional overhead is not that important for an offline workflow that you have.

Yes, thanks, I was thinking of something like that, but now I found an even better (for this usecase at least) way: grab the actual (anti-aliased) framebuffer from the main window, similar to what TakeScreenshot does internally:

#include "rlgl.h"
unsigned char *imgData = rlReadScreenPixels(screenWidth, screenHeight);
Image img = { imgData, screenWidth, screenHeight, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 };

(apologies for going a bit off-topic)

commented

MSAA on RenderTextures is cumbersome to implement on OpenGL and it's not supported by raylib, it's not as easy as enabling a flag, like it is for main frmaebuffer.

About FXAA, it's just a shader and it can be used with RenderTextures BUT note that it is applied to all the RenderTexture buffer and not specific parts of the drawing, it could generate not expected results. Probably in many cases a small blur shader would give better results.

Drawing everything on a 4x RenderTexture is a good solution but it increases the render cost A LOT (not done by hardware like MSAA).

I'm closing this issue because it seems more a discussion about a topic than an issue with raylib library.