Lekcja 46. Pełnoekranowy AntyAliasing
Autor: Kacper 'Kacperz1' Zygnarowski
Oryginał: AntiAliasing (Colt 'MainRoach' McAnlis)
Źródła: http://nehe.gamedev.net/data/lessons/vc/lesson46.zip

Cześć wszystkim. The Friendly Neighborhood Roach ze swoim interesującym tutorialem pomoże Wam polepszyć swoje aplikacje. W rzeczywistości programy korzystające z OpenGL wyglądają lepiej niż normalne aplikacje, tylko że wielkim problemem z którym się nieraz spotkamy to aliasing. O tym własnie będzie ten tutorial. Kwadrat zawiera "kreseczki" które są umieszczone na przekątnych liniach w stosunku do pikseli kwadrata które widzisz na ekranie. Rozwiązanie dla tego zdarzenia nazywane jest dla tego anty aliasingiem. Antyaliasing zasłania "kreseczki" tak, aby obiekt wyglądał znacznie lepiej. Proces w którym używany jest anty aliasing nazywany jest wielowzorowością.

Pełnoekranowy Anty Aliasing ma dużo zalet. Przy nowszym sprzęcie jesteśmy zdolni uzyskać ten sam efekt. Rozszerzenie ARB_MULTISAMPLE pozwala nam na to. Każdy piksel jest wzorowany na sąsiedzie aby przedstawiac optymalny anty alias. To zatem prowadzi do spowolnienia programu.

Vid_mem = sizeof(Front_buffer) + sizeof(Back_buffer) + num_samples * (sizeof(Front_buffer) +sizeof(ZS_buffer))

Po więcej informacji zajrzyjcie pod poniższe linki:

GDC2002 -- OpenGL Wielowzorcowosc

OpenGL Formaty Fixeli i Wielowzorowosc Antialiasingu

W przeciwieństwie do innych rozszerzeń, które są związane z renderowaniem w OpenGL, rozszerzenie ARB_MULTISAMPLE musi być rozpatrywany podczas całego procesu renderowania okna. Ten proces wygląda tak:

  1. Stwórz normalne okno
  2. Podaj wartość wielowzorowego piksela (InitMultisample)
  3. Jeżeli wielodobieranie jest dostępne to zniszcz okno i odnów je naszym nowym formatem pikseli
  4. Dla anty aliasingu musimy wywołać glEnable(GL_ARB_MULTISAMPLE);

Zacznijmy omawiać kod pliku arbMultiSample.cpp. Zacznijmy od dodania bibliotek gl.h, glu.h i windows.h. O arb_multisample.h porozmawiamy później.

#include <windows.h>
#include <gl/gl.h>
#include <gl/glu.h>
#include "arb_multisample.h"

Druga i trzecia linia kodu określają zakres dostępu do łańcucha WGL (WGL string). Użymy je do zwiększenia cech formatu piksela do testowania naszego formatu wzorca. Dwie inne zmienne są używane do dostępu do danych.

#define WGL_SAMPLE_BUFFERS_ARB 0x2041
#define WGL_SAMPLES_ARB 0x2042
bool arbMultisampleSupported = false;
int arbMultisampleFormat = 0;

Następna funkcja o której będziemy mówić to WGLisExtensionSupported. Używana jest do uzyskania listy rozszerzeń WGL do weryfikacji jeżeli podany format jest wspierany przez system. Będziemy opisywać kod gdy do niego dojdziemy ponieważ jest to łatwiejszy sposób na wytłumaczenie poszczególnych funkcji.

NOTATKA:Kod poniżej był przepisany przez Henry Goffin. Jego aktualna wersja zawiera lepsze parsery wspierające rozszerzenia GL.

bool WGLisExtensionSupported(const char *extension)
{
    const size_t extlen = strlen(extension);
    const char *supported = NULL;
        // Spróbuj użyć wglGetExtensionStringARB w DC, jeżeli to możliwePROC wglGetExtString =
    wglGetProcAddress("wglGetExtensionsStringARB");
    if (wglGetExtString) supported = ((char*(__stdcall*)(HDC))wglGetExtString)(wglGetCurrentDC());
        // Jeżeli nie jest to możliwe, spróbuj użyć standardowych łańcuchów rozszerzeń Opengl
    if (supported == NULL) supported = (char*)glGetString(GL_EXTENSIONS);
        // Jeżeli to też nie możliwe, musi wtedy zostać wspierany przez żadne z rozszerzeń
    if (supported ==NULL) return false;
        //Zacznij kontrolę na początku stringu, przestań przy pierwszym błędzie
    for (const char* p = supported; ; p++)
    {         // Przesuń p w zwyrz do nastęnego możliwego odpowiednika
        p = strstr(p, extension);
        if (p == NULL)
            return false;         // Żadnych odpowiedników         
        // Upewnij się że odpowiednik jest na początku stringu lub
        // Poprzedni znak to spacja, albo zrobimy przypadkowy
        // Odpowiednik "wglFunkywglExtension" z "wglExtension"
        // Również upewnij się że następny znak to spacja lub NULL
        if ((p==supported || p[-1]==' ') && (p[extlen]=='\0' || p[extlen]==''))
            return true;
    }
}

Następna funkcja to w istocie główny kod programu. Podstawowo, sprawdzimy czy nasze rozszerzenia arb są wspierane przez system. Potem, przejdziemy do sprawdzania naszego planu kontekstu aby znaleźć zakres naszej wielowzorcowości. Popatrzmy w kod.

bool InitMultisample(HINSTANCE hInstance, HWND hWnd,PIXELFORMATDESCRIPTOR pfd)
{
        // Sprawdz czy string istnieje w WGL!
    if (!WGLisExtensionSupported("WGL_ARB_multisample"))
    {
        arbMultisampleSupported = false;
        return false;
    }
        // Zdobądz swój własny format pikseli
    PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB =
        (PFNWGLCHOOSEPIXELFORMATARBPROC) wglGetProcAddress("wglChoosePixelFormatARB");
    if (!wglChoosePixelFormatARB)
    {
        // Nie znaleźliśmy wsparcia dla Wielowzorowości, Ustaw naszą flagę i wyjdź z niej
        arbMultisampleSupported=false;
        return false;
    }
    
        // Zdobądź nasz aktualny kontekst. Potrzebujemy to w porządku żeby zdobyć informacje na temat atrybutów okien OpenGL
    HDC hDC = GetDC(hWnd);
    int pixelFormat;
    bool valid;
    UINT numFormats;
    float fAttributes[] = {0,0};
    int iAttributes[] = { WGL_DRAW_TO_WINDOW_ARB,GL_TRUE,
            WGL_SUPPORT_OPENGL_ARB,GL_TRUE,
            WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB,
            WGL_COLOR_BITS_ARB,24,
            WGL_ALPHA_BITS_ARB,8,
            WGL_DEPTH_BITS_ARB,16,
            WGL_STENCIL_BITS_ARB,0,
            WGL_DOUBLE_BUFFER_ARB,GL_TRUE,
            WGL_SAMPLE_BUFFERS_ARB,GL_TRUE,
            WGL_SAMPLES_ARB, 4,         // Sprawdź za 4x Wielowzorowość
            0,0 };
        // Najpierw zobaczymy czy możemy uzyskać format pikseli dla 4 wzorów
    valid = wglChoosePixelFormatARB(hDC,iAttributes,fAttributes,1,&pixelFormat,&numFormats);
        // Jeżeli zwrócimy True i nasz format więcej niż 1
    if (valid && numFormats >= 1)
    {
        arbMultisampleSupported=true;
        arbMultisampleFormat=pixelFormat;
        return arbMultisampleSupported;
    }
        // Nasz format pikseli z 4 nieprawidłowymi (ang. Failed) wzorami, Testowany na 2 wzorach
    iAttributes[19] = 2;
    valid = wglChoosePixelFormatARB(hDC,iAttributes,fAttributes,1,&pixelFormat,&numFormats);
    if(valid && numFormats >= 1)
    {
        arbMultisampleSupported=true;
        arbMultisampleFormat=pixelFormat;
        return arbMultisampleSupported;
    }
        // Zwróć prawidłowy format
    return arbMultisampleSupported;
}

Teraz, kiedy skończyliśmy pisać nasz kod, musimy go tak skonfigurować aby wiedział co nasze kreacje w oknie będą robić. Najpierw musimy dołączyć nasz arb_multisample.f, a na koniec przesunąć prototypy destroywindow i createwindow na samą górę.

#include <windows.h>
#include <gl/gl.h>         // Biblioteka OpenGL32
#include <gl/glu.h>         // Biblioteka GLu32
#include "NeHeGL.h"         // Biblioteka The NeHeGL Basecode
        // ROACH
#include "ARB_MULTISAMPLE.h"
BOOL DestroyWindowGL (GL_Window* window);
BOOL CreateWindowGL (GL_Window* window);
        // ENDROACH

Następny bit musi być dodany do funkcji CreateWindowsGL i kod musi zostać jeszcze trochę zmodyfikowany. Nie możemy używać formatów pikseli dopóki mamy stworzone okno, ale za to niemożemy używac obrazu FSAA dopóki nie wiemy jaki format pikseli będzie wspomagał go. Zrobimy okno, uzyskamy format pikseli, potem zniszczymy i zrobimy okno na nowo jeżeli wielowzorcowość jest wspomagana.

Proste...

    window->hDC = GetDC (window->hWnd);
        // Chwyć kontekst dla okna
    if (window->hDC == 0)         // Zdobyliśmy kontekst?
        // Failed
        DestroyWindow(window->hWnd);         // Zniszcz okno
        window->hWnd = 0;         // Wyzeruj uchwyt okna
        return FALSE;         // Zwróć False
    }
        //ROACH    
        // Wielowzorowość nie została jeszcze zrobiona,wieć stworzymy normalne okno
        // Jeżeli jest wspomagany, użyjemy "Second Pass"
        // To znaczy że użyjemy naszego własnego formatu pikseli do wzorowości
        // Więc ustaw format pikseli do arbMultiSampleforma
    if(!arbMultisampleSupported)
    {
        PixelFormat= ChoosePixelFormat (window->hDC, &pfd);
        // Znajdź kompatybilny format pikseli
        if (PixelFormat == 0)         // Znaleźliśmy kompatybilny format?
        //Failed
            ReleaseDC(window->hWnd, window->hDC);
        // Uwolnij kontekst
            window->hDC = 0;         // Wyzeruj kontekst
            DestroyWindow(window->hWnd);         // Zniszcz okno
            window->hWnd = 0;         // Zero jest uchwytem okna
            return FALSE;         // Zwróć wartość False
        }
    }
    else
    {
        PixelFormat = arbMultisampleFormat;
    }
        //ENDROACH
    if (SetPixelFormat(window->hDC, PixelFormat, &pfd) == FALSE)
        // Spróbuj ustawić format pikseli
    {         //Failed
        ReleaseDC(window->hWnd, window->hDC);
        // Uwolnij kontekst
        window->hDC = 0;         // Wyzeruj kontekst
        DestroyWindow(window->hWnd);
        // Zniszcz okno
        window->hWnd = 0;         // Zero jest uchwytem okna
        return FALSE;         // Zwróć wartość False
    }    

Okno zostalo zrobione, wiec mamy juz potrzebny uchwyt dla Wielowzorcowego wsparcia. Jezeli znajdziemy wsparcie, zniszczymy okno, i wywolamy funkcje CreateWindowGL od nowa , tylko ze z nowym formatem pikseli.

        // Zrob z kontekstu renderowania nasz główny kontekst renderowania
    if (wglMakeCurrent (window->hDC, window->hRC) == FALSE)
    {         //Failed
        wglDeleteContext(window->hRC);         // Usuń kontekst renderowania
        window->hRC = 0;         // Daj wartość Zero kontekście renderowania
        ReleaseDC(window->hWnd, window->hDC);
        // Uwolnij kontekst
        window->hDC = 0;         // Wyzeruj kontekst
        DestroyWindow(window->hWnd);         // Zniszcz okno
        window->hWnd = 0;         // Zero jest uchwytem okna
        return FALSE;         // Zwróć wartość False
    }
        // ROACH
        // Wywołamy nasze okno (InitMultiSample)
        // Jeżeli uzyskamy aktualną wartość, zniszczymy nasze aktualne okno
        // I zrobimy nowy używając nową wielowzorowośćif(!arbMultisampleSupported
    if(!arbMultisampleSupported && CHECK_FOR_MULTISAMPLE)
    {
        if(InitMultisample(window->init.application->hInstance,window->hWnd,pfd))
        {
            DestroyWindowGL (window);
            return CreateWindowGL(window);
        }
    }
        // ENDROACH
    ShowWindow (window->hWnd, SW_NORMAL);         // Pokaż nasze okno
    window->isVisible = TRUE;

OK, konfiguracja zakończona pomyślnie! Skonfigurowaliśmy nasz ekran tak, że teraz zezwala na odpowiednią głębie dla wielowzorowości. Teraz najlepsza część! Renderowanie sceny. Na szczęście ARB zdecydował zrobić dynamiczną wielowzorowość więc to pozwala na włączenie i wyłączenie tego przy pomocy funkcji glEnable / glDisable

    glEnable(GL_MULTISAMPLE_ARB);
        // Renderowanie sceny
    glDisable(GL_MULTISAMPLE_ARB);

Był to najłatwiejszy efekt rotacji do pokazania jaki znam.