Programowanie w języku C/C++ » Artykuły

Ładowanie tekstury do OpenGL pod Windows

Podczas programowania grafiki w OpenGL dość szybko pojawia się problem: jak załadować teksturę. Sama biblioteka OpenGL nie udostępnia żadnych funkcji służących do ładowania plików graficznych.
W tutorialach spotyka się różne rozwiązania: prymitywne ładowanie pliku .bmp, ładowanie poprzez funkcje WinAPI (również tylko .bmp) albo użycie zewnętrznej biblioteki.

Jest jeszcze jeden sposób — użycie wbudowanej w Windows biblioteki WIC (Windows Imaging Component). Obsługuje wiele formatów, w tym .jpg i .png.
Nie trzeba do programu dodawać zewnętrznych bibliotek, nic instalować.
Poniższy kod jest przeznaczony dla Visual C++ 2010 lub nowszego (może być wersja Express). Zamieszczam tylko kod ładujący teksturę - bez rysowania ani inicjalizacji OpenGL. Do wykorzystania we własnym programie.

Funkcja LoadTexture() ładuje teksturę z podanego pliku i zwraca identyfikator tekstury do wykorzystania w bibliotece OpenGL.
Wymagana wersja OpenGL to 1.2 lub nowsza.

#define _WIN32_WINNT 0x501
 
#include <Windows.h>
#include <wincodec.h>
 
#include <comdef.h>
 
#include <gl\GL.h>
#include <gl\GLU.h>
 
#include <memory>
 
// definicja smartpointerów COM. później zamiast np. IWICBitmap* używamy IWICBitmapPtr
_COM_SMARTPTR_TYPEDEF(IWICImagingFactory, __uuidof(IWICImagingFactory));
_COM_SMARTPTR_TYPEDEF(IWICBitmapDecoder, __uuidof(IWICBitmapDecoder));
_COM_SMARTPTR_TYPEDEF(IWICBitmap, __uuidof(IWICBitmap));
_COM_SMARTPTR_TYPEDEF(IWICBitmapFrameDecode, __uuidof(IWICBitmapFrameDecode));
_COM_SMARTPTR_TYPEDEF(IWICBitmapSource, __uuidof(IWICBitmapSource));
 
// potrzebne libki
#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glu32.lib")
#pragma comment(lib, "windowscodecs.lib")
#ifdef NDEBUG
#  pragma comment(lib, "comsuppw.lib")
#else
#  pragma comment(lib, "comsuppwd.lib")
#endif
 
// brakująca stała
#define GL_BGRA 0x80E1
 
// w funkcji main/WinMain trzeba pamiętać o CoInitialize/CoUninitialize
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
    CoInitialize(NULL);
 
    // ...
 
    CoUninitialize();
    return msg.wParam;
}
 
// uff. po tym przydługim wstępie oto funkcja ładująca teksturę:
GLuint LoadTexture(const wchar_t *fileName)
{
    HRESULT hr;
 
    // główny obiekt WIC. można by go wyciągnąć na zewnątrz, żeby nie inicjować biblioteki na nowo za każdym razem...
    IWICImagingFactoryPtr factory;
    hr = factory.CreateInstance(CLSID_WICImagingFactory);
    if (FAILED(hr)) return 0;
 
    // ładujemy plik
    IWICBitmapDecoderPtr decoder;
    hr = factory->CreateDecoderFromFilename(fileName, NULL, GENERIC_READ, WICDecodeMetadataCacheOnDemand, &decoder);
    if (FAILED(hr)) return 0;
 
    // teoretycznie plik może zawierać więcej niż jeden obrazek, nas interesuje ten o indeksie 0
    IWICBitmapFrameDecodePtr frame;
    hr = decoder->GetFrame(0, &frame);
    if (FAILED(hr)) return 0;
 
    // konwertujemy bitmapę do 32-bitowego formatu BGRA, na wypadek gdyby była w innym.
    // można by też format sprawdzać i konwertować warunkowo, ale dla prostoty kodu sprawdzanie pominiemy.
    IWICBitmapSourcePtr source;
    hr = WICConvertBitmapSource(GUID_WICPixelFormat32bppBGRA, frame, &source);
    if (FAILED(hr)) return 0;
 
    // kopiujemy obrazek do bitmapy nieskompresowanej, dla przyspieszenia kopiowania jej wiersz po wierszu.
    // w tym miejscu dopiero następuje dekodowanie bitmapy z pliku (np. jpg)
    IWICBitmapPtr bitmap;
    hr = factory->CreateBitmapFromSource(source, WICBitmapCacheOnDemand, &bitmap);
    if (FAILED(hr)) return 0;
 
    UINT w, h;
    hr = bitmap->GetSize(&w, &h);
    if (FAILED(hr)) return 0;
 
    // alokujemy bufor i kopiujemy bitmapę jeszcze raz, zamieniając kolejność wierszy, tak jak OpenGL lubi.
    std::unique_ptr<unsigned char> texture(new unsigned char[w*h*4]);
    for (unsigned int y = 0; y<h; y++)
    {
        WICRect rect = {0, h-1-y, w, 1}; // waski pasek na jeden piksel (jeden wiersz bitmapy)
        hr = bitmap->CopyPixels(&rect, w*4, w*4, texture.get()+y*w*4);
        if (FAILED(hr)) return 0;
    }
 
    // koniec zabawy z WIC, ładujemy teksturę do OpenGL. tu można dodawać parametry wedle potrzeby i upodobania.
    GLuint texture_id;
    glGenTextures(1, &texture_id);
    glBindTexture(GL_TEXTURE_2D, texture_id);
    gluBuild2DMipmaps(GL_TEXTURE_2D, 4, w, h, GL_BGRA, GL_UNSIGNED_BYTE, texture.get());
    // kluczowe jest użycie "4" i "GL_BGRA", identycznego formatu jakiego użyliśmy powyżej w WIC
 
    // zwracamy ID tekstury do wykorzystania w OpenGL.
    return texture_id;
}

1 komentarz

fasadin 2014-09-02 13:30

Brakuje jednej. BARDZO istnotnej rzeczy. Wspomniecia ze tekstury powinny byc kwadratowe i powinny byc potega dwojki czyli np 512x512 czy 2048x2048. Niektore karty graficzne z nieregularnymi ksztaltami (ktore nie spelniaja wymagan powyzej) nie beda wyswietlac tekstur (przyklad moze potwierdziec nawet @dampe).