sunLoadingImage
whowedImag
decoration left 1
decoration left 2
transhome
transprojects
transgallery
transarticles
decoration rigth
English
Українська
Show/Hide search bar
black cat logo variable logo
[18 Січ 2013]

Компіляція GLSL шейдерів в OpenGL

Шейдери це програми для графічних карт, які дозволяють запрограмувати частини конвеєра рендерінгу. До введення шейдерів налаштування рендерінгу було обмежене змінами станів конвеєра рендерінгу. В GLSL шейдер це відкомпільований код GLSL. Сам шейдер не може напряму бути використаний в рендерінгу, так як обєкт "шейдер" є тільки одиним етапом шейдігу. Серед етапів шейдінгу: вершинний шейдер, шейдер геометрії, шейдер контролю теселяції, шейдер виконання теселяції та фрагментний шейдер. Для того щоб використати шейдер при рендерінгу необхідно обєднати декілька шейдерів в шейдерну програму. Шейдерна програма обовязково має містити в собі вершинний і фрагментний шейдер. Інші типи шейдерів є необовязковими. Основними кроками для створення нової шейдерної програмами є: завантаження і компіляція окремих шейдерів, додавання необхідних шейдерів в шейдерну програму, лінкування шейдерної програми, а також перевірка на помилки під час компіляції та лінковки. Після цього шейдерна програма готова до використання в графічному конвеєрі. Рендерінг без вибраної шейдерної програми призведе до помилок.
Наступний приклад показує як створити шейдерну програму з вершинного та фрагментного шейдера.
Створення шейдерної програми:
GLuint loadShaderProgram(const QString & vertexShPath, const QString & fragmentShPath)
{
   GLuint shaderProgram(0), vertexShader(0), fragmentShader(0);
   
   // Компіляція вершинного шейдера
   if(!compileShader(GL_VERTEX_SHADER, vertexShader, vertexShPath))
   {
      return -1;
   }

   // Компіляція фрагментнго шейдера
   if(!compileShader(GL_FRAGMENT_SHADER, fragmentShader, fragmentShPath))
   {
      return -1;
   }

   // Створення нової шейдерної програми
   shaderProgram = glCreateProgram();
   // Привязка шейдерів до шейдерної програми
   glAttachShader(shaderProgram, vertexShader);
   glAttachShader(shaderProgram, fragmentShader);
   // Лінковка шейдерної програми
   glLinkProgram(shaderProgram);
   // Відєднати шейдери від шейдерної програми
   glDetachShader?(shaderProgram?, vertexShader);
   glDetachShader?(shaderProgram, fragmentShader?);
   // Видалення шейдерів, так як вони більше не потрібні
   glDeleteShader(vertexShader);
   glDeleteShader(fragmentShader);

   // Перевірка, чи лінковка пройшла успішно
   GLint testVal;
   glGetProgramiv(shaderProgram, GL_LINK_STATUS, &testVal);
   if(testVal == GL_FALSE)
   {
      char infolog[1024];
      glGetProgramInfoLog(shaderProgram, 1024, NULL, infolog);
      // тут можна вивести infolog
      glDeleteProgram(shaderProgram);
      return -1;
   }

   return shaderProgram;
}
Функція для компіляції шейдера:
bool compileShader(GLenum shaderType, GLuint & shader, const QString & path)
{
   QString source;

   // завантажити код шейдера з файла
   if(!loadSourceFromFile(path,source))
   {
      return false;
   }
   QByteArray byteArray = source.toUtf8();
   const char* vertCString = byteArray.constData();

   // створити новий шейдер з заданим типом
   shader = glCreateShader(shaderType);
   // задати код шейдера
   glShaderSource(shader, 1, &vertCString, nullptr);
   // компіляція шейдера
   glCompileShader(shader);
   // перевірити чи компіляція пройшла успішно
   if(!self->checkShaderStatus(shader, GL_COMPILE_STATUS))
   {
      glDeleteShader(shader);
      return false;
   }

   return true;
}
Функція для завантаження коду GLSL з файлу:
bool loadSourceFromFile(const QString & path, QString &source)
{
   QFile fl(path);
   // Завантаження вмісту файла
   if(!fl.open(QIODevice::ReadOnly))
   {
      return false;
   }
   QTextStream in(&fl);
   source = in.readAll();
   fl.close();

   return true;
}
Функція для перевірки правильності компіляції шейдера:
bool checkShaderStatus(GLuint shader, GLenum status)
{
   GLint testVal = 0;
   // дізнатися статус шейдера від OpenGL
   glGetShaderiv(shader, status, &testVal);
   if(testVal == GL_FALSE)
   {
      char infolog[1024];
      // дістати опис помилки
      glGetShaderInfoLog(shader, 1024, NULL, infolog);
      // тут можна вивести infolog
      return false;
   }
   return true;
}
Після лінковки шейдерної програми можна дістати інформацію про атрибути (attributes) та константні змінні (uniforms). Це може знадобитися для виведення інформації про всі атрибути та константні змінні, чи для збереження індексів які є спільними для багатьох шейдерів. Наступний приклад показує як це зробити:
// шейдерна програма повинна бути активною для того щоб зробити запит glUseProgram(shaderProgram);

// дістати кількість константних значень з шейдерної програми
GLint numUniforms = 0;
glGetProgramiv(shaderProgram, GL_ACTIVE_UNIFORMS, &numUniforms);
for(GLuint i=0; i<numUniforms; ++i)
{
   int name_len=-1, unifsize=-1;
   GLenum type = GL_ZERO;
   char name[100];
   // дістати назву константної змінної під індексом i
   glGetActiveUniform(shaderProgram, i, sizeof(name)-1,
                  &name_len, &unifsize, &type, name );
   name[name_len] = 0;
   // дістати індекс розміщення константної змінної з імям name
   GLuint location = glGetUniformLocation(shaderProgram, name);
   
   // тут можна використати location і name
}

// дістати кількість атрибутів у шейдерній програмі (вершинний шейдер)
GLint numAttributes=0;
glGetProgramiv(shaderProgram, GL_ACTIVE_ATTRIBUTES, &numAttributes);
for(GLuint i=0; i<numAttributes; ++i)
{
   int name_len=-1, unifsize=-1;
   GLenum type = GL_ZERO;
   char name[100];
   // дістати назву атрибута під індексом i
   glGetActiveAttrib(shaderProgram, i, sizeof(name)-1,
                  &name_len, &unifsize, &type, name);
   name[name_len] = 0;
   // дістати індекс розміщення атрибута з назвою name
   GLuint location = glGetAttribLocation(shaderProgram, name);
   
   // тут можна використати location і name
}
Якщо в шейдері немає атрибута чи константної змінної з шуканою назвою, то запит індекса розміщення повертає -1. Наступна функція може знадобися при розробці, щоб перевірити, що усі встановлення константних значень та атрибутів є правильними:
GLint getUniLocation(QString uni)
{
   // дістати індекс розміщення змінної, і вивести
   попередження, якщо такої змінної немає

   GLint loc = glGetUniformLocation(shaderProgram, uni.toLatin1());
   if(loc == -1)
   {
      Log::instance().log("Failed to get location of uniform : " + uni);
   }
   return loc;

   // аналогічно для атрибутів
}
Наступний код дозволяє перевірити чи шейдерна програма може справно функціонувати при поточному стані контексту OpenGL. Корисна тільки під час розробки. Може вивести інформацію про проблеми з виконанням шейдерної програми чи швидкістю виконання. Повідомлення залежать від драйвера.
glUseProgram(shaderProgram);
glValidateProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_VALIDATE_STATUS, &testVal);
if(testVal == GL_FALSE)
{
   char infolog[1024];
   glGetProgramInfoLog(shaderProgram, 1024, NULL, infolog);
   // тут можна вивести infolog
   glDeleteProgram(shaderProgram);
   return;
}
Перед лінковкою шейдерної програми можна встановити індекси, які мають використовуватися атрибутами чи даними, які виходять з фрагментного шейдера. Також перед лінковкою можна встановити, які змінні повинні зберігатися під час Transform Feedback з допомогою функції glTransformFeedbackVaryings().
// атрибут з назвою attributeName? має мати індекс 0
glBindAttribLocation(shaderProgram, 0, attributeName);
// вихідне значення з фрагментного шейдера outColor? має мати індекс 0
glBindFragDataLocation(shaderProgram, 0, outColor);
// тепер необхідно знову відлінкувати шейдерну програму
glLinkProgram(shaderProgram);
Не забудьте видалити шейдерну програму після використання:
glDeleteProgram(shaderProgram);


Sun and Black Cat- Ігор Дихта (igor dykhta email) © 2007-2014