Этот урок — нечто вроде Hello World для GLSL. Минимальный шейдер, осуществляющий большинство базовых вещей: трансформирование вершин и рендер примитивов с единым цветом.

Рассмотрим эти шейдеры, вершинный и пикселный.

Вершинный шейдер.

Как и описывалось раньше, вершинный шейдер применяется для трансформации вершин. Здесь будет показано, как трансформировать вершины, следуя формулам базового функционала.

Базовый функционал обеспечивает трансформацию вершин матрицами modelview и projection с помощью следующей формулы: vTrans = projection * modelview * incomingVertex

Чтобы написать такой же функционал с GLSL, необходимо получить доступ к OpenGL, чтобы получить обе матрицы. Как говорилось раньше, часть фунуционала OpenGL доступна в GLSL, а именно — эти матрицы.

Они объявлены как уже описанные uniform-переменные:

uniform mat4 gl_ModelViewMatrix;
uniform mat4 gl_ProjectionMatrix;

Необходима ещё одна вещь: доступ ко входящим вершинам. Эти вершины — каждая из них — доступны вершинному шейдеру через объявленные ранее attribute-переменные: attribute vec4 gl_Vertex;

Чтобы вывести трансформированные вершины, шейдер ДОЛЖЕН производить запись в также заранее объявленную переменную gl_Position, объявленную как vec4.

Имея всё перечисленное, становится возможным написать простой вершинный шейдер, делающий ничто иное, как трансформацию вершин. Учтите, что весь остальной базовый функционал будет утерян. Это значит, например, что не будет произведена обработка освещения.

Вершинный шейдер должен иметь функцию main(). Следующий код демонстрирует это:


void main()
{

gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;
}


В этом коде матрица проекции умножается на матрицу моделей для каждой вершины, что является пустой тратой времени, так как эти матрицы не изменяются от вершины к вершине. Матрицы — это uniform-переменные.

GLSL предоставляет некие обьединённые матрицы, gl_ModelViewProjectionMatrix, которые являются результатом умножения матриц projection и modelview. Таким образом этот вершинный шейдер может быть переписан следующим образом:


void main()
{

gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}


Конечный результат, естественно, будет таким же. Гарантирует ли это такой же результат, как при использовании фиксированного функционала? Ну, теоретически — да, но на практике процесс трансформации вершин может идти не в том же порядке, что и здесь. Обычно это высоко оптимизированный на уровне видеокатры процесс, и в GLSL есть специальная функция, чтобы мы могли использовать эту оптимизацию в наших целях. Ещё одна причина использования этой функции — недостаточный предел точности типа данных float. Когда трансформация происходит по нашей инициативе в другом порядке, именно из-за этой недостаточной точности могут быть получены разные результаты. К счастью, GLSL предоставляет нам функцию, которая гарантирует, что будет получена не только максимальная оптимизация, но и тот же результат, что и при использовании базового функционала.
Вот она, эта магическая функция:

vec4 ftransform(void);

Эта функция возвращает результат трансформации исходных вершин, следуя тем же этапам, что и базовый
функционал. Таким образом, шейдер может быть переписан вот так:


void main()
{

gl_Position = ftransform();
}


Пикселный шейдер

Пикселный шейдер также имеет заранее обьявленную переменную для записи цвета пиксела: gl_FragColor.
Так же как и вершинный шейдер, пикселный должен иметь функцию main(). Следующий код отрисовывает все пикселы с синим цветом:


void main()
{
gl_FragColor = vec4(0.4,0.4,0.8,1.0);
}


Первая программа

Теперь давайте наконец полностью напишем простую программу, использующую простые шейдеры.

Для начала давайте напишем шейдеры. Они действительно просты =)

Вершинный шейдер. Файл minimal.vert:

void main()
{// Следующие три линии возвращают один и тот же результат//  gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;
//  gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
gl_Position = ftransform();
}

 

Пикселный шейдер. Файл minimum.frag:

void main()
{
gl_FragColor = vec4(0.4,0.4,0.8,1.0);
}

 

Действительно очень простые шейдеры 😉

Теперь напишем пару простых вспомогательных файлов — «модуль», реализующай чтение и запись в файл.

Файл textfile.h:

char *textFileRead(char *fn);
int textFileWrite(char *fn, char *s);// И всё, всего два прототипа

 

Файл textfile.cpp:

// textfile.cpp
//
// Простое чтение/запись текстовых файлов
//
//////////////////////////////////////////////////////////////////////#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *textFileRead(char *fn) {

FILE *fp;
char *content = NULL;

int count=0;

if (fn != NULL) {
fp = fopen(fn,«rt»);

if (fp != NULL) {

fseek(fp, 0, SEEK_END);
count = ftell(fp);
rewind(fp);

if (count > 0) {
content = (char *)malloc(sizeof(char) * (count+1));
count = fread(content,sizeof(char),count,fp);
content[count] = ;
}
fclose(fp);
}
}
return content;
}

int textFileWrite(char *fn, char *s) {

FILE *fp;
int status = 0;

if (fn != NULL) {
fp = fopen(fn,«w»);

if (fp != NULL) {

if (fwrite(s,sizeof(char),strlen(s),fp) == strlen(s))
status = 1;
fclose(fp);
}
}
return(status);
}

 

Наконец, переходим к коду. Исходный код я взял из [a=https://masandilov.ru/opengl/begin:a]первого урока OpenGL, но вы можете легко адаптировать куда угодно приведенные фрагменты кода.

Файл main.cpp:

// Для начала добавим библиотеку и хидер glaux. Это нужно для рисования «чайника», для наглядности
#pragma comment(lib, «glaux.lib»)
#include <glglaux.h>// Добавим три глобальных переменных:
GLuint v,f,f2,p;        // Дескрипторы программ и шейдеров
float lpos[4] = {1,0.5,1,0};    // Позиция источника света
float a = 0;            // Хранит угол вращения

//////////////////////////////////////////////////////////////////////////
//
//  Следующая новая функция инициализирует и активирует шейдеры:
//

void setShaders() {

// Обьявление массивов строк для чтения из файлов
char *vs = NULL,*fs = NULL,*fs2 = NULL;

// Создание шейдеров
v = glCreateShader(GL_VERTEX_SHADER);
f = glCreateShader(GL_FRAGMENT_SHADER);
f2 = glCreateShader(GL_FRAGMENT_SHADER);

// Чтение исходного кода шейдеров
vs = textFileRead(«minimal.vert»);
fs = textFileRead(«minimal.frag»);

// Копируем код шейдеров
const char * vv = vs;
const char * ff = fs;

// Передаём шейдерам их исходный код
glShaderSource(v, 1, &vv,NULL);
glShaderSource(f, 1, &ff,NULL);

// Освобождаем память под ненужные теперь массивы
free(vs);free(fs);

// Компилируем шейдеры
glCompileShader(v);
glCompileShader(f);

// Создаём программу
p = glCreateProgram();
// Присоединяем к программе шейдеры
glAttachShader(p,v);
glAttachShader(p,f);

// Линкуем программу
glLinkProgram(p);

// И используем её
glUseProgram(p);
}

/////////////////////////////////////////////////////////////////////////////
//
// В самый конец функции Init() допишите
//     а) инициализацию glew и проверку поддержки OpenGL 2.0
//     б) вызов новой функции:

void Init(HWND hWnd)
{
g_hWnd = hWnd;
GetClientRect(g_hWnd, &g_rRect);
InitializeOpenGL(g_rRect.right, g_rRect.bottom);

// Инициализируем библиотеку glew
glewInit();

// Проверяем, есть ли поддержка OpenGL 2.0
if (!glewIsSupported(«GL_VERSION_2_0»))
{
MessageBox(NULL,«OpenGL 2.0 not supported»,«Error»,MB_OK);
exit(1);
}

// Устанавливаем шейдеры
setShaders();
}

////////////////////////////////////////////////////////////////////////
//
//    И наконец изменяем функцию RenderScene():
//
void RenderScene()
{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();

// Устанавливаем взгляд
gluLookAt(0.0,0.0,5.0,
0.0,0.0,-1.0,
0.0f,1.0f,0.0f);

// Устанавливаем источник света
glLightfv(GL_LIGHT0, GL_POSITION, lpos);

// Вращаем на угол ‘a’
glRotatef(a,0,1,1);

// Рисуем чайник (для этого и подключили glaux)
auxSolidTeapot(1);

// Увеличиваем угол вращения
a+=0.1;

SwapBuffers(g_hDC);
}

 

Вот и всё. Шейдеры инициализируются в функции Init() и используются дальше без отключения.
Пикселный шейдер окрашивает все пикселы чайника в синий цвет. Вы можете изменить его в minimal.vert и посмотреть на результат. Или, например, убрать вызов setShaders(); из функции Init(), Тогда  изменённый базовый функционал не будет окрашивать пикселы, и цвет останется белым =)

Если вы не поняли что-либо в этом коде, я настаиваю, чтобы вы прочитали предыдущие разделы.

Исходные коды к уроку