Ł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;
}

1 komentarz

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).