@SiemkaElkoTszyPiencZerko: widzę że masz tak wielką awersję do programów typu hello-world że nie możesz pozbyć się tych klas, a zaciemniają niestety sytuację...
Pozwól że pomogę. Bo dobrze jest najpierw napisać absolutne minimum, które może nawet nie jest pięknym kodem, ale przynajmniej działa.
Użyłem Visual Studio 2010 pod Windows XP (awaryjnie bo dysk z Win 10 padł i nie kupiłem jeszcze nowego).
Z wyjątkiem nagłówka glext.h
program nie używa żadnych innych zewnętrznych bibliotek które trzeba byłoby doinstalowywać do VS2010: nie ma GLM (bo nie ma przekształceń macierzowych), nie ma GLUT-a, GLFW ani innych GL-fuj, GL-pluj ani SDL-i. Po prostu WinAPI.
Rysowany jest statyczny obrazek. Nie ma nawet "pętli rysującej". Po prostu rysuje na starcie, a potem gdy Windowsowi się zechce odświeżyć okienko.
Samo okienko wygląda tak:
#include <Windows.h>
#include <GL/GL.h>
#include "glext.h"
PFNGLGENVERTEXARRAYSPROC glGenVertexArrays;
PFNGLBINDVERTEXARRAYPROC glBindVertexArray;
PFNGLGENBUFFERSPROC glGenBuffers;
PFNGLBINDBUFFERPROC glBindBuffer;
PFNGLCREATESHADERPROC glCreateShader;
PFNGLSHADERSOURCEPROC glShaderSource;
PFNGLCOMPILESHADERPROC glCompileShader;
PFNGLCREATEPROGRAMPROC glCreateProgram;
PFNGLATTACHSHADERPROC glAttachShader;
PFNGLLINKPROGRAMPROC glLinkProgram;
PFNGLUSEPROGRAMPROC glUseProgram;
PFNGLBUFFERDATAPROC glBufferData;
PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray;
PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer;
void init_scene()
{
glClearColor(1, 1, 1, 0);
}
void render_scene()
{
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
}
void init_opengl(HWND hwnd)
{
PIXELFORMATDESCRIPTOR pfd = { sizeof(pfd), 1 };
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
pfd.cColorBits = 32;
pfd.cDepthBits = 24;
HDC dc = GetDC(hwnd);
SetPixelFormat(dc, ChoosePixelFormat(dc, &pfd), NULL);
HGLRC rc = wglCreateContext(dc);
wglMakeCurrent(dc, rc);
glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)wglGetProcAddress("glGenVertexArrays");
glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)wglGetProcAddress("glBindVertexArray");
glGenBuffers = (PFNGLGENBUFFERSPROC)wglGetProcAddress("glGenBuffers");
glBindBuffer = (PFNGLBINDBUFFERPROC)wglGetProcAddress("glBindBuffer");
glCreateShader = (PFNGLCREATESHADERPROC)wglGetProcAddress("glCreateShader");
glShaderSource = (PFNGLSHADERSOURCEPROC)wglGetProcAddress("glShaderSource");
glCompileShader = (PFNGLCOMPILESHADERPROC)wglGetProcAddress("glCompileShader");
glCreateProgram = (PFNGLCREATEPROGRAMPROC)wglGetProcAddress("glCreateProgram");
glAttachShader = (PFNGLATTACHSHADERPROC)wglGetProcAddress("glAttachShader");
glLinkProgram = (PFNGLLINKPROGRAMPROC)wglGetProcAddress("glLinkProgram");
glUseProgram = (PFNGLUSEPROGRAMPROC)wglGetProcAddress("glUseProgram");
glBufferData = (PFNGLBUFFERDATAPROC)wglGetProcAddress("glBufferData");
glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)wglGetProcAddress("glEnableVertexAttribArray");
glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)wglGetProcAddress("glVertexAttribPointer");
}
LRESULT __stdcall WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE:
init_opengl(hwnd);
init_scene();
break;
case WM_PAINT:
render_scene();
ValidateRect(hwnd, NULL);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
CoInitialize(NULL);
WNDCLASS wc = {};
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.lpszClassName = L"blablabla";
ATOM atom = RegisterClass(&wc);
HWND hwnd = CreateWindow(
MAKEINTATOM(atom),
L"Test OpenGL",
WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
CW_USEDEFAULT, CW_USEDEFAULT,
500, 500,
(HWND)NULL, (HMENU)NULL, hInstance, (LPVOID)NULL
);
ShowWindow(hwnd, nCmdShow);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
OK. To nam daje białe okno. Interesują nas dwie funkcje: init_scene()
i render_scene()
. Reszta to WinAPI-owe „śmieci”. W init_scene mamy ustawienie koloru na biały, a w render_scene czyszczenie okna.
Linie z PFNGL-itd. są potrzebne bo nie mamy żadnej gotowej ładowarki do OpenGL-a, przez co (pod Windows) większość funkcji trzeba załadować przez wglGetProcAddress.
Typy wskaźnikowe (PFNGL...) są zdefiniowane w glext.h.
W podany wyżej sposób powstaje nam kontekst OpenGL “compatibility”, ale tylko dlatego że kod inicjalizujący “core” byłby sporo dłuższy, a chciałem żeby kod był jak najkrótszy. Mimo to w dalszej części postaram się trzymać wymagań “core”.
Kontekst OpenGL “core” wymaga użycia VAO i VBO. Niech im tam będzie:
void init_scene()
{
...
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
}
Proszsz. Te sześć linijek załatwia sprawę. Jest VAO, jest VBO i nie będziemy mówić o nich więcej.
Okej. W dalszej kolejności idą shadery. Absolutnie najprostsze, najpierw bez teksturowania:
void init_scene()
{
...
GLuint vs = glCreateShader(GL_VERTEX_SHADER);
const char *vs_source =
"#version 330\n "
"layout (location = 0) in vec2 a_position; "
"void main() "
"{ "
" gl_Position = a_position; "
"} ";
glShaderSource(vs, 1, (GLchar**)&vs_source, NULL);
glCompileShader(vs);
GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
const char *fs_source =
"#version 330\n "
"layout (location = 0) out vec4 o_color; "
"void main() "
"{ "
" o_color = vec4(1.0, 0.0, 0.0, 1.0); "
"} ";
glShaderSource(fs, 1, (GLchar**)&fs_source, NULL);
glCompileShader(fs);
GLuint prog = glCreateProgram();
glAttachShader(prog, vs);
glAttachShader(prog, fs);
glLinkProgram(prog);
glUseProgram(prog);
}
Vertex shader nie robi nic (przepycha pozycję dalej…) a fragment shader rysuje na czerwono.
No to narysujmy coś:
struct vertex_t
{
float x, y;
};
void init_scene()
{
....
const vertex_t vertices[] = { { -0.5, 0.5 }, { -0.5, -0.5 }, { 0.5, -0.5 }, { -0.5, 0.5 }, { 0.5, -0.5 }, { 0.5, 0.5 } };
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(vertex_t), 0);
}
void render_scene()
{
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, 6);
glFlush();
}
Ponieważ rysujemy statyczny obrazek, do render_scene trafia tylko samo rysowanie glDrawArrays, wszystko inne ustawiane jest tylko raz.
Efekt:
Tadaam.
Teraz tekstura.
#include "texloader.h"
Kod texloader.h/texloader.cpp w załączniku. Nie będziemy zaśmiecać posta.
Zmienia nam się struktura wierzchołka:
struct vertex_t
{
struct { float x, y; } position;
struct { float u, v; } texCoord;
};
W funkcji init_scene zmieniamy kod shaderów:
const char *vs_source =
"#version 330\n "
"layout (location = 0) in vec2 a_position; "
"layout (location = 1) in vec2 a_texCoord; "
"out vec2 v_texCoord; "
"void main() "
"{ "
" gl_Position = vec4(a_position, 0.0, 1.0); "
" v_texCoord = a_texCoord; "
"} ";
const char *fs_source =
"#version 330\n "
"in vec2 v_texCoord; "
"uniform sampler2D u_texture0; "
"layout (location = 0) out vec4 o_color; "
"void main() "
"{ "
" o_color = texture(u_texture0, v_texCoord); "
"} ";
i na końcu same dane które idą do bufora:
const vertex_t vertices[] =
{ { { -0.5, 0.5 }, { 0, 1 } }, { { -0.5, -0.5 }, { 0, 0 } }, { { 0.5, -0.5 }, { 1, 0 } },
{ { -0.5, 0.5 }, { 0, 1 } }, { { 0.5, -0.5 }, { 1, 0 } }, { { 0.5, 0.5 }, { 1, 1 } } };
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (void*)offsetof(vertex_t, position));
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (void*)offsetof(vertex_t, texCoord));
GLuint texture = LoadTexture(L"tex.png");
glBindTexture(GL_TEXTURE_2D, texture);
Funkcja LoadTexture (z texloader.h) przyjmuje nazwę pliku (może być jpg, png, i inne obsługiwane przez Windows formaty). Sam plik powinien być w tym samym katalogu co exek.