Ładowanie tekstury do OpenGL pod Windows
Azarien
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;
}
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).