Notice: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the antispam-bee domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Translations should be loaded at the init action or later. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /home/ruwritingagame/public_html/wp-includes/functions.php on line 6121
Глава 10. Простой шейдер – Игра в Написание Игры

Глава 10. Простой шейдер

Наконец, шейдеры! Начнем с самого простого, просто заливка сплошным цветом, который мы передадим шейдеру в качестве инпута вместе с данными повехности (в нашем примере – треугольника).

Шейдеры пишутся на GLSL (OpenGL Shading Language с синтаксисом похожим на C). GLSL исполняется на граф-карте.

Исполняемая шейдер-программа состоит из 2-х шейдеров: Vertex Shader, который обрабатывает данные вешин (включая координаты) и преобразует их в экранные координаты. Выходные данные идут в rasterizer, который вызывает Fragment (Pixel) Shader для каждого вовлеченного пикселя.

  1. 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. Включаем, разблокируем, подключаем, разрешаем.

Компиляция и запуск.

Норм.


Leave a Reply

Your email address will not be published. Required fields are marked *