Наконец, шейдеры! Начнем с самого простого, просто заливка сплошным цветом, который мы передадим шейдеру в качестве инпута вместе с данными повехности (в нашем примере – треугольника).
Шейдеры пишутся на GLSL (OpenGL Shading Language с синтаксисом похожим на C). GLSL исполняется на граф-карте.
Исполняемая шейдер-программа состоит из 2-х шейдеров: Vertex Shader, который обрабатывает данные вешин (включая координаты) и преобразует их в экранные координаты. Выходные данные идут в rasterizer, который вызывает Fragment (Pixel) Shader для каждого вовлеченного пикселя.
- Vertex Shader:
#version 320 es
precision lowp float;
uniform mat4 uMVP; // transform matrix (Model-View-Projection)
in vec3 aPos; // position attribute (x,y,z)
void main(){
gl_Position = uMVP * vec4(aPos, 1.0);
}
Скопируем этот код в Текстовый Редактор и сохраним как txt файл в
C:\CPP\engine\dt\shaders\flat_ucolor_v.txt
Надеюсь, объяснять его не надо, все вроде достаточно очевидно. Ну или можно обратиться к GLSL документации на Вебе.
- Файл test0.txt можно оттуда удалить. Больше он не понадобится.
2. Fragment (Pixel) Shader:
#version 320 es
precision lowp float;
uniform vec4 uColor;
out vec4 FragColor; //output pixel color
void main(){
FragColor = uColor;
}
Сохраним этот код в качестве txt файла в
C:\CPP\engine\dt\shaders\flat_ucolor_f.txt
Теперь давайте их опробуем.
Windows
3. Запускаем VS. Открываем C:\CPP\a999hello\p_windows\p_windows.sln.
Во-первых, понадобится функция проверки на предмет GL ошибок. Поместим ее в отдельный набор utils.
4. Под xEngine добавим New Item
- Header File (.h)
- Name: utils.h
- Location: C:\CPP\engine\
Код:
#pragma once
#include <string>
int checkGLerrors(std::string ref);
5. Под xEngine добавим New Item
- C++ File (.cpp)
- Name: utils.cpp
- Location: C:\CPP\engine\
Код:
#include "platform.h"
#include <string>
int checkGLerrors(std::string ref) {
//can be used after any GL call
int res = glGetError();
if (res == 0)
return 0;
std::string errCode;
switch (res) {
//case GL_NO_ERROR: errCode = "GL_NO_ERROR"; break;
case GL_INVALID_ENUM: errCode = "GL_INVALID_ENUM"; break;
case GL_INVALID_VALUE: errCode = "GL_INVALID_VALUE"; break;
case GL_INVALID_OPERATION: errCode = "GL_INVALID_OPERATION"; break;
case GL_INVALID_FRAMEBUFFER_OPERATION: errCode = "GL_INVALID_FRAMEBUFFER_OPERATION"; break;
case GL_OUT_OF_MEMORY: errCode = "GL_OUT_OF_MEMORY"; break;
default: errCode = "??"; break;
}
mylog("GL ERROR %d-%s in %s\n", res, errCode.c_str(), ref.c_str());
return -1;
}
Shaders-related код поместим в отдельном классе Shaders.
6. Под xEngine добавим New Item
- Header File (.h)
- Name: Shader.h
- Location: C:\CPP\engine\
Код:
#pragma once
#include "platform.h"
#include <string>
class Shader
{
public:
static int linkShaderProgram(const char* filePathVertexS, const char* filePathFragmentS);
static int compileShader(const char* filePath, GLenum shaderType);
static int shaderErrorCheck(int shaderId, std::string ref);
static int programErrorCheck(int programId, std::string ref);
};
7. Под xEngine добавим New Item
- C++ File (.cpp)
- Name: Shader.cpp
- Location: C:\CPP\engine\
Код:
#include "Shader.h"
#include "platform.h"
#include "utils.h"
GLchar infoLog[1024];
int logLength;
int Shader::shaderErrorCheck(int shaderId, std::string ref) {
//use after glCompileShader()
if (checkGLerrors(ref) > 0)
return -1;
glGetShaderInfoLog(shaderId, 1024, &logLength, infoLog);
if (logLength == 0)
return 0;
mylog("%s shader infoLog:\n%s\n", ref.c_str(), infoLog);
return -1;
}
int Shader::programErrorCheck(int programId, std::string ref) {
//use after glLinkProgram()
if (checkGLerrors(ref) > 0)
return -1;
glGetProgramInfoLog(programId, 1024, &logLength, infoLog);
if (logLength == 0)
return 0;
mylog("%s program infoLog:\n%s\n", ref.c_str(), infoLog);
return -1;
}
int Shader::compileShader(const char* filePath, GLenum shaderType) {
int shaderId = glCreateShader(shaderType);
FILE* pFile;
myFopen_s(&pFile, filePath, "rt");
if (pFile != NULL)
{
// obtain file size:
fseek(pFile, 0, SEEK_END);
int fSize = ftell(pFile);
rewind(pFile);
// size obtained, create buffer
char* shaderSource = new char[fSize + 1];
fSize = fread(shaderSource, 1, fSize, pFile);
shaderSource[fSize] = 0;
fclose(pFile);
// source code loaded, compile
glShaderSource(shaderId, 1, (const GLchar**)&shaderSource, NULL);
//myglErrorCheck("glShaderSource");
glCompileShader(shaderId);
if (shaderErrorCheck(shaderId, "glCompileShader") < 0)
return -1;
delete[] shaderSource;
}
else {
mylog("ERROR loading %s\n", filePath);
return -1;
}
return shaderId;
}
int Shader::linkShaderProgram(const char* filePathVertexS, const char* filePathFragmentS) {
int vertexShaderId = compileShader(filePathVertexS, GL_VERTEX_SHADER);
int fragmentShaderId = compileShader(filePathFragmentS, GL_FRAGMENT_SHADER);
int programId = glCreateProgram();
glAttachShader(programId, vertexShaderId);
glAttachShader(programId, fragmentShaderId);
glLinkProgram(programId);
if (programErrorCheck(programId, "glLinkProgram") < 0)
return -1;
//don't need shaders any longer - detach and delete them
glDetachShader(programId, vertexShaderId);
glDetachShader(programId, fragmentShaderId);
glDeleteShader(vertexShaderId);
glDeleteShader(fragmentShaderId);
return programId;
}
Заметим, что в функции compileShader(..) для открытия файла мы использовали собственную функцию myFopen_s (строка 31, тот же синтакс как fopen_s). Причина в том, что в Windows-е привычная fopen объявлена устаревшей, надо использовать fopen_s, в то время как на Андроиде fopen_s еще не имплементирована, и надо использовать fopen. Вобщем, нужны 2 platform-specific имплементации.
8. Открываем platform.h и заменяем код следующим:
#pragma once
#include <glad/glad.h>
#include <stdio.h>
void mylog(const char* _Format, ...);
void mySwapBuffers();
void myPollEvents();
int myFopen_s(FILE** pFile, const char* filePath, const char* mode);
9. Открываем platform.cpp и заменяем код следующим:
#include <stdarg.h>
#include <stdio.h>
#include <GLFW/glfw3.h>
#include "platform.h"
#include "TheGame.h"
extern GLFWwindow* myMainWindow;
extern TheGame theGame;
void mylog(const char* _Format, ...) {
#ifdef _DEBUG
va_list _ArgList;
va_start(_ArgList, _Format);
vprintf(_Format, _ArgList);
va_end(_ArgList);
#endif
};
void mySwapBuffers() {
glfwSwapBuffers(myMainWindow);
}
void myPollEvents() {
glfwPollEvents();
//check if closing the window
theGame.bExitGame = glfwWindowShouldClose(myMainWindow);
//check screen size
int width, height;
glfwGetFramebufferSize(myMainWindow, &width, &height);
theGame.onScreenResize(width, height);
}
int myFopen_s(FILE** pFile, const char* filePath, const char* mode) {
return fopen_s(pFile, filePath, mode);
}
10. Теперь откроем TheGame.cpp и заменим код на:
#include "TheGame.h"
#include "platform.h"
#include "linmath.h"
#include "Shader.h"
extern std::string filesRoot;
static const struct
{
float x, y, z;
} vertices[3] =
{
{ -0.6f, -0.4f, 0.f },
{ 0.6f, -0.4f, 0.f },
{ 0.f, 0.6f, 0.f }
};
unsigned int vao_buffer, vertex_buffer, shaderProgramId;
int umvp_location, apos_location, ucol_location;
float angle_z = 0;
int TheGame::getReady() {
bExitGame = false;
glGenVertexArrays(1, &vao_buffer);
glBindVertexArray(vao_buffer);
glGenBuffers(1, &vertex_buffer);
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
shaderProgramId = Shader::linkShaderProgram((filesRoot + "/dt/shaders/flat_ucolor_v.txt").c_str(), (filesRoot + "/dt/shaders/flat_ucolor_f.txt").c_str());
umvp_location = glGetUniformLocation(shaderProgramId, "uMVP");
apos_location = glGetAttribLocation(shaderProgramId, "aPos");
glEnableVertexAttribArray(apos_location);
glVertexAttribPointer(apos_location, 3, GL_FLOAT, GL_FALSE,
sizeof(vertices[0]), (void*)0);
ucol_location = glGetUniformLocation(shaderProgramId, "uColor");
glUseProgram(shaderProgramId);
float uColor[4] = { 1.f, 0.f, 1.f, 1.f }; //R,G,B, alpha
glUniform4fv(ucol_location, 1, uColor);
return 1;
}
int TheGame::drawFrame() {
myPollEvents();
mat4x4 m, p, mvp;
//glClearColor(0.0, 0.0, 0.5, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
angle_z += 0.01f;
mat4x4_identity(m);
mat4x4_rotate_Z(m, m, angle_z);
mat4x4_ortho(p, -screenRatio, screenRatio, -1.f, 1.f, 1.f, -1.f);
mat4x4_mul(mvp, p, m);
glUseProgram(shaderProgramId);
glUniformMatrix4fv(umvp_location, 1, GL_FALSE, (const GLfloat*)mvp);
glDrawArrays(GL_TRIANGLES, 0, 3);
mySwapBuffers();
return 1;
}
int TheGame::cleanUp() {
return 1;
}
int TheGame::onScreenResize(int width, int height) {
if (screenSize[0] == width && screenSize[1] == height)
return 0;
screenSize[0] = width;
screenSize[1] = height;
screenRatio = (float)width / (float)height;
glViewport(0, 0, width, height);
mylog(" screen size %d x %d\n", width, height);
return 1;
}
int TheGame::run() {
getReady();
while (!bExitGame) {
drawFrame();
}
cleanUp();
return 1;
}
Теперь он чуть-ли не вдвое короче.
Важные изменения:
- Встроенных в код шейдеров vertex_shader_text и fragment_shader_text больше нет.
- Переменные vertex_shader и fragment_shader тоже больше не нужны.
- Структура данных треугольника теперь другая. Вместо 2D координат + цвет теперь просто 3D координаты, без цвета.
- Некоторые переменные переименованы для единообразия.
11. Компиляция и запуск:
Отлично.
Теперь на
Андроиде
12. Закрываем и перезапускаем Visual Studio, открываем C:\CPP\a999hello\p_android\p_android.sln
13. Под xEngine добавляем Existing Item
Идем в C:\CPP\engine, выбираем
- Shader.cpp
- Shader.h
- utils.cpp
- utils.h
Add
Нужно еще добавить myFopen_s(..) для Андроида.
14. Откроем platform.h и заменим код на:
#pragma once
void mylog(const char* _Format, ...);
void mySwapBuffers();
void myPollEvents();
int myFopen_s(FILE** pFile, const char* filePath, const char* mode);
15. Откроем platform.cpp и заменим код на:
#include <android/log.h>
#include "stdio.h"
#include "TheGame.h"
extern struct android_app* androidApp;
extern const ASensor* accelerometerSensor;
extern ASensorEventQueue* sensorEventQueue;
extern EGLDisplay androidDisplay;
extern EGLSurface androidSurface;
extern TheGame theGame;
void mylog(const char* _Format, ...) {
#ifdef _DEBUG
char outStr[1024];
va_list _ArgList;
va_start(_ArgList, _Format);
vsprintf(outStr, _Format, _ArgList);
__android_log_print(ANDROID_LOG_INFO, "mylog", outStr, NULL);
va_end(_ArgList);
#endif
};
void mySwapBuffers() {
eglSwapBuffers(androidDisplay, androidSurface);
}
void myPollEvents() {
// Read all pending events.
int ident;
int events;
struct android_poll_source* source;
// If not animating, we will block forever waiting for events.
// If animating, we loop until all events are read, then continue
// to draw the next frame of animation.
while ((ident = ALooper_pollAll(0, NULL, &events,
(void**)&source)) >= 0) {
// Process this event.
if (source != NULL) {
source->process(androidApp, source);
}
// If a sensor has data, process it now.
if (ident == LOOPER_ID_USER) {
if (accelerometerSensor != NULL) {
ASensorEvent event;
while (ASensorEventQueue_getEvents(sensorEventQueue,
&event, 1) > 0) {
//LOGI("accelerometer: x=%f y=%f z=%f",
// event.acceleration.x, event.acceleration.y,
// event.acceleration.z);
}
}
}
// Check if we are exiting.
if (androidApp->destroyRequested != 0) {
theGame.bExitGame = true;
break;
}
}
}
int myFopen_s(FILE** pFile, const char* filePath, const char* mode) {
*pFile = fopen(filePath, mode);
if (*pFile == NULL) {
mylog("ERROR: can't open file %s\n", filePath);
return -1;
}
return 1;
}
16. Включаем, разблокируем, подключаем, разрешаем.
Компиляция и запуск.
Норм.