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
Глава 16. Modeler – Игра в Написание Игры

Глава 16. Modeler

Наверное, пора уже подумать, где же брать реальные 3D модели для Проекта. Варианты такие:

  • Web. Широкий выбор моделей разных форматов от профессиональных 3D художников и любителей. Многие из моделей – бесплатные.
  • Самим освоить Blender, Maya, 3Ds Max или еще какой-то 3D редактор. ОГРОМНАЯ задача сама-по-себе. Как-то пока не ощущаю себя готовым для этого.
  • Найти 3Д художника. Проблема со всеми тремя упомянутыми вариантами – а как читать/загружать эти модели в программу и как ими управлять? Неочевидная задача, между прочим. Мы непременно вернемся к этому вопросу попозже. А сейчас я бы предпочел вариант побыстрее.
  • А можно генерировать модели программно. Не факт что они смогут полноценно конкурировать с профессиональными 3D работами, но в любом случае, иметь такую опцию определенно не помешает.

Вобщем, давайте начнем с последнего варианта – генерировать модели программно. Это будет набор инструментов и классов, часть нашего графического движка. Создадим для него каталог.

1. В Windows File Explorer-е в каталоге C:\CPP\engine создадим новый под-каталог modeler.


2. Пройдем в каталог C:\CPP . Сделаем копию каталога a998engine и переименуем ее в a997modeler.


3. Запускаем VS. Открываем C:\CPP\a997modeler\p_windows\p_windows.sln.

Под xEngine (right-click на xEngine ) добавим New Filter, назовем его modeler.


4. Добавим ссылку на новый каталог:

Откроем p_windows project Properties. All Configurations / Active(Win32),

C/C++ -> General -> Additional Include Directories -> Edit, добавим новую строку,

идем в C:\CPP\engine\modeler,

Select Folder, Ok, Apply, Ok.


Идея “modeler-а” – построить массивы вертексов и индексов, и затем перевести их в VBO, EBO, VAO, и в итоге – в DrawJobs.

5. Создадим первый класс, Vertex01. Как обычно, по-файлово:

Под modeler добавим новый header файл Vertex01.h

Location – C:\CPP\engine\modeler

Код:

#pragma once

class Vertex01
{
public:
	int subjN = -1; //game subject number
	int materialN = -1; //material number
	int flag = 0;
	int endOfSequence = 0; //for sequentional (unindexed) primitives (like GL_LINE_STRIP for example)
	int altN = -1; //vertex' position in alternative array
	//atributes
	float aPos[4] = { 0,0,0,0 }; //position x,y,z + 4-th float for matrix operations
	float aNormal[4] = { 0,0,0,0 }; //normal (surface reflection vector) x,y,z + 4-th float for matrix operations
	float aTuv[2] = { 0,0 }; //2D texture coordinates
	float aTuv2[2] = { 0,0 }; //for normal maps
	//tangent space (for normal maps)
	float aTangent[3] = { 0,0,0 };
	float aBinormal[3] = { 0,0,0 };
};

Не обращайте внимание на “лишние” переменные, объясню их попозже, по мере использования. Файл cpp пока не нужен.


Поскольку мы планируем использовать индексы (EBO) для рисования массивов треугольников, то понадобится свой класс и для них.

6. Под modeler добавим новый header файл Triangle01.h

Location – C:\CPP\engine\modeler

Код:

#pragma once

class Triangle01
{
public:
	int subjN = -1; //game subject number
	int materialN = -1; //material number
	int flag = 0;
	int idx[3] = { 0,0,0 }; //3 vertex indices
};

То же самое, не обращайте внимание на “лишние” переменные, обэясню их попозже, по мере использования. Файл cpp тоже пока не нужен.


Еще понадобится понятие групп вершин/треугольников для разных манипуляций.

7, Под modeler добавим новый header файл Group01.h

Location – C:\CPP\engine\modeler

Код:

#pragma once

class Group01
{
public:
	int fromVertexN = 0;
	int fromTriangleN = 0;
};

В основном нам надо будет знать где текущая группа начинается, поэтому переменных “to” тут нет.


Еще понадобится какой-то описатель (descriptor), что (какую форму/shape) мы рисуем/строим. Назовем его VirtualShape. “Virtual” потому что сам-по-себе вершин он строить не будет.

8. Под modeler добавим новый header файл VirtualShape.h

Location – C:\CPP\engine\modeler

Код:

#pragma once
#include <string>

class VirtualShape
{
public:
	std::string type = "box";
	float whl[3] = { 0 }; //width/height/length (x,y,z sizes/dimensions)
};

Главная операция/команда modeler-а будет buildFace() – построить проекцию. Например, скажем, “на левой стороне текущей VirtualShape“. Эта команда создаст 4 уже реальных вертекса на левой стороне виртуальной формы (в этом примере – кубика с размерами whl) и 2 соответствующих треугольника (6 индексов).


Вынесем некоторые сопутствующие вычисления в набор utils.

9. Откроем utils.h и заменим код на:

#pragma once
#include <string>
#include "linmath.h"

int checkGLerrors(std::string ref);
void mat4x4_mul_vec4plus(vec4 vOut, mat4x4 M, vec4 vIn, int v3);


10. Откроем utils.cpp и заменим код на:

#include "utils.h"
#include "platform.h"

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;
}
void mat4x4_mul_vec4plus(vec4 vOut, mat4x4 M, vec4 vIn, int v3) {
    vec4 v2;
    if (vOut == vIn) {
        memcpy(&v2, vIn, sizeof(vec4));
        vIn = v2;
    }
    vIn[3] = (float)v3;
    mat4x4_mul_vec4(vOut, M, vIn);
}

Эта новая функция mat4x4_mul_vec4plus() – умножение вектора на матрицу. Отличие от linmath варианта в том, что избегает запись результатв в исходный вектор, плюс переустанавливает 4-ый элемент в исходном векторе. Если в ноль, то результирующий вектор будет просто развернут, без смещения (используется для расчета нормалей например).


В DrawJob добавлен новый функционал.

11. Откроем DrawJob.h и заменим код на:

#pragma once
#include "Material.h"
#include <vector>

struct AttribRef //attribute reference/description
{
	unsigned int glVBOid = 0; //buffer object id
	int offset = 0; //variable's offset inside of VBO's element
	int stride = 0; //Buffer's element size in bytes
};

class DrawJob
{
public:
	Material mt;
	int pointsN = 0; //N of points to draw
	unsigned int glVAOid = 0; //will hold data stream attributes mapping/positions
	unsigned int glEBOid = 0; //Element Buffer Object (vertex indices)

	//common attributes
	AttribRef aPos;
	AttribRef aTuv;

	//static arrays (vectors) of all loaded DrawJobs, VBO ids
	static std::vector<DrawJob*> drawJobs;
	static std::vector<unsigned int> buffersIds;
public:
	DrawJob();
	virtual ~DrawJob(); //destructor
	static int cleanUp();
	static int newBufferId();
	int buildVAO() { return buildVAOforShader(this, mt.shaderN); };
	static int buildVAOforShader(DrawJob* pDJ, int shaderN);
	static int attachAttribute(int varLocationInShader, int attributeSizeInFloats, AttribRef* pAttribRef);

	virtual int setDesirableOffsets(int* pStride, int shaderN, int VBOid) { return setDesirableOffsetsForSingleVBO(this, pStride, shaderN, VBOid); };
	static int setDesirableOffsetsForSingleVBO(DrawJob* pDJ, int* pStride, int shaderN, int VBOid);

	int execute(float* uMVP, Material* pMt) { return executeDrawJob(this, uMVP, pMt); };
	static int executeDrawJob(DrawJob* pDJ, float* uMVP, Material* pMt);
};

Новая функция setDesirableOffsets(..) заполняет референсы атрибутов в DrawJob для заданного шейдера (AttribRef пока только для aPos и aTuv, позже атрибутов будет больше).


12. Откроем DrawJob.cpp и заменим код на:

#include "DrawJob.h"
#include "platform.h"
#include "utils.h"
#include "Shader.h"
#include "Texture.h"

//static arrays (vectors) of all loaded DrawJobs, VBO ids
std::vector<DrawJob*> DrawJob::drawJobs;
std::vector<unsigned int> DrawJob::buffersIds;

DrawJob::DrawJob() {
	drawJobs.push_back(this);
}
DrawJob::~DrawJob() {
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	if (glVAOid > 0)
		glDeleteVertexArrays(1, &glVAOid);
}
int DrawJob::newBufferId() {
	unsigned int bufferId;
	glGenBuffers(1, &bufferId);
	buffersIds.push_back(bufferId);
	return (int)bufferId;
}
unsigned int activeVBOid;
int DrawJob::buildVAOforShader(DrawJob* pDJ, int shaderN) {
	//delete VAO if exists already
	if (pDJ->glVAOid > 0) {
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		glDeleteVertexArrays(1, &(pDJ->glVAOid));
	}
	glGenVertexArrays(1, &pDJ->glVAOid);
	glBindVertexArray(pDJ->glVAOid);

	//open shader descriptor to access variables locations
	Shader* pShader = Shader::shaders.at(pDJ->mt.shaderN);

	activeVBOid = 0;
	attachAttribute(pShader->l_aPos, 3, &pDJ->aPos);
	attachAttribute(pShader->l_aTuv, 2, &pDJ->aTuv);

	if (pDJ->glEBOid > 0)
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, pDJ->glEBOid);

	glBindVertexArray(0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	return 1;
}

int DrawJob::attachAttribute(int varLocationInShader, int attributeSizeInFloats, AttribRef* pAR) {
	if (varLocationInShader < 0)
		return 0; //not used in this shader
	if (pAR->glVBOid == 0) {
		mylog("ERROR in DrawJob::attachAttribute, nk such attribute/VBO\n");
		return -1;
	}
	glEnableVertexAttribArray(varLocationInShader);
	if (activeVBOid != pAR->glVBOid) {
		activeVBOid = pAR->glVBOid;
		//attach input stream data
		glBindBuffer(GL_ARRAY_BUFFER, activeVBOid);
	}
	glVertexAttribPointer(varLocationInShader, attributeSizeInFloats, GL_FLOAT, GL_FALSE, pAR->stride, (void*)(long)pAR->offset);
	return 1;
}
int DrawJob::executeDrawJob(DrawJob* pDJ, float* uMVP, Material* pMt) {
	if (pMt == NULL)
		pMt = &(pDJ->mt);
	glBindVertexArray(pDJ->glVAOid);
	Shader* pShader = Shader::shaders.at(pMt->shaderN);
	glUseProgram(pShader->GLid);
	glUniformMatrix4fv(pShader->l_uMVP, 1, GL_FALSE, (const GLfloat*)uMVP);

	//attach textures
	if (pShader->l_uTex0 >= 0) {
		int textureId = Texture::getGLid(pMt->uTex0);
		//pass textureId to shader program
		glActiveTexture(GL_TEXTURE0); // activate the texture unit first before binding texture
		glBindTexture(GL_TEXTURE_2D, textureId);
		// Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 0.    
		glUniform1i(pShader->l_uTex0, 0);
	}
	//other uniforms
	if (pShader->l_uColor >= 0) {
		//float uColor[4] = { 1.f, 0.f, 1.f, 1.f }; //R,G,B, alpha
		glUniform4fv(pShader->l_uColor, 1, pMt->uColor.forGL());
	}
	if (pDJ->glEBOid == 0) {
		glDrawArrays(pMt->primitiveType, 0, pDJ->pointsN);
	}
	else { //use EBO
		glDrawElements(pMt->primitiveType, pDJ->pointsN, GL_UNSIGNED_SHORT, 0);
	}
	glBindVertexArray(0);
	return 1;
}
int DrawJob::cleanUp() {
	int itemsN = drawJobs.size();
	//delete all drawJobs
	for (int i = 0; i < itemsN; i++) {
		DrawJob* pDJ = drawJobs.at(i);
		delete pDJ;
	}
	drawJobs.clear();
	//delete Buffers
	itemsN = buffersIds.size();
	//delete all buffers
	for (int i = 0; i < itemsN; i++) {
		unsigned int id = buffersIds.at(i);
		glDeleteBuffers(1, &id);
	}
	buffersIds.clear();

	return 1;
}
int DrawJob::setDesirableOffsetsForSingleVBO(DrawJob* pDJ, int* pStride, int shaderN, int VBOid) {
	//sets desirable offsets and stride according to given shader needs
	//assuming that we have 1 single VBO
	Shader* pSh = Shader::shaders.at(shaderN);
	int stride = 0;
	pDJ->aPos.offset = 0; //attribute o_aPos, always 0
	stride += sizeof(float) * 3; //aPos size - 3 floats (x,y,z)
	if (pSh->l_aTuv >= 0) { //attribute TUV (texture coordinates)
		pDJ->aTuv.offset = stride; //attribute TUV (texture coordinates)
		stride += sizeof(float) * 2;
	}
	*pStride = stride;
	//add stride and VBOid to all attributes
	AttribRef* pAR = NULL;
	pAR = &pDJ->aPos; pAR->glVBOid = VBOid; pAR->stride = stride;
	pAR = &pDJ->aTuv; pAR->glVBOid = VBOid; pAR->stride = stride;

	return 1;
}

Продолжение следует…


Leave a Reply

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