В наших последних примерах мы использовали примитив GL_TRIANGLE_STRIP для рисования прямоугольника как последовательного массива вершин. Но наиболее полезный и часто-используемый примитив – это все-же GL_TRIANGLES. Мы использовали его раньше для рисования треугольника как последовательности вершин. Для более сложных форм / поверхностей стандартный подход – это хранить вертексы в VBO безотносительно их порядка, а последовательность рисования держать отдельно как массив индексов (номеров вершин) в так называемых EBO (Element Buffer Object) или IBO (index Buffer Object, что вобщем-то одно и то же).
Windows
1. Запускаем VS, открываем C:\CPP\a998engine\p_windows\p_windows.sln.
Надо слегка поменять класс DrawJob.
2. Откроем 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);
int execute(float* uMVP, Material* pMt) { return executeDrawJob(this, uMVP, pMt); };
static int executeDrawJob(DrawJob* pDJ, float* uMVP, Material* pMt);
};
Теперь у нас там есть переменная glEBOid.
В функции DrawJob::executeDrawJob(), если EBO представлен, мы будем использовать команду glDrawElements вместо glDrawArrays как было раньше. Мы продолжим изпользовать glDrawArrays для последовательных (НЕиндексированных) массивов вершин (как для GL_TRIANGLE_STRIP например).
В функции DrawJob::buildVAOforShader(), если EBO представлен, то он будет включен в VAO.
3. Откроем 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;
}
В TheGame.cpp добавлен массив frontIndices: 6 индексов для 2-х треугольников: North-West->South-West->South-East and NW->SE->NE.
В TheGame::getReady(), при создании DrawJob (с картинкой), мы сгенерируем EBO и поменяем pDJ->mt.primitiveType с GL_TRIANGLE_STRIP на GL_TRIANGLES.
Также надо поменять pDJ->pointsN с 4 (4 последовательные вершины) на 6 (2 индексированных треугольника).
4. Откроем TheGame.cpp и заменим код на:
#include "TheGame.h"
#include "platform.h"
#include "linmath.h"
#include "Texture.h"
#include "Shader.h"
#include "DrawJob.h"
extern std::string filesRoot;
//static array (vector) for loaded gameSubjs
std::vector<GameSubj*> TheGame::gameSubjs;
static const struct
{
float x, y, z, tu, tv;
} frontVertices[4] =
{
{ -0.5f, 0.5f, 0.f, 0.f, 0.f }, //top-left
{ -0.5f, -0.5f, 0.f, 0.f, 1.f }, //bottom-left
{ 0.5f, 0.5f, 0.f, 1.f, 0.f }, //top-right
{ 0.5f, -0.5f, 0.f, 1.f, 1.f } //bottom-right
};
GLushort frontIndices[6] = { 0,1,3, 0,3,2 };
int TheGame::getReady() {
bExitGame = false;
Shader::loadShaders();
glEnable(GL_CULL_FACE);
GameSubj* pGS = new GameSubj();
gameSubjs.push_back(pGS);
pGS->djStartN = DrawJob::drawJobs.size();
pGS->name.assign("img1");
//pGS->ownCoords.setPosition(-50, 50, 0);
pGS->ownSpeed.setDegrees(1, 2, 3);
pGS->scale[0] = 400;
pGS->scale[1] = pGS->scale[0] / 2;
//face DrawJob
//build VBO
unsigned int VBOid = DrawJob::newBufferId();
glBindBuffer(GL_ARRAY_BUFFER, VBOid);
glBufferData(GL_ARRAY_BUFFER, sizeof(frontVertices), frontVertices, GL_STATIC_DRAW);
int stride = sizeof(float) * 5;
//add DrawJob
DrawJob* pDJ = new DrawJob();
pDJ->pointsN = 6; //number of points (vertices or indices in case of EBO)
//define material
pDJ->mt.shaderN = Shader::spN_flat_tex;
pDJ->mt.primitiveType = GL_TRIANGLES; //GL_TRIANGLE_STRIP;
pDJ->mt.uTex0 = Texture::loadTexture(filesRoot + "/dt/sample_img.png");
//attributes references
AttribRef* pAR;
pAR = &pDJ->aPos; pAR->offset = 0; pAR->glVBOid = VBOid; pAR->stride = stride;
pAR = &pDJ->aTuv; pAR->offset = sizeof(float) * 3; pAR->glVBOid = VBOid; pAR->stride = stride;
//build and attrach EBO
pDJ->glEBOid = DrawJob::newBufferId();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, pDJ->glEBOid);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(frontIndices), frontIndices, GL_STATIC_DRAW);
//create and fill vertex attributes array (VAO)
pDJ->buildVAO();
pGS->djTotalN = DrawJob::drawJobs.size() - pGS->djStartN;
return 1;
}
int TheGame::drawFrame() {
myPollEvents();
//glClearColor(0.0, 0.0, 0.5, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
mat4x4 mProjection, mMVP;
mat4x4_ortho(mProjection, -(float)screenSize[0] / 2, (float)screenSize[0] / 2, -(float)screenSize[1] / 2, (float)screenSize[1] / 2, 200.f, -200.f);
//scan subjects
int subjsN = gameSubjs.size();
for (int subjN = 0; subjN < subjsN; subjN++) {
GameSubj* pGS = gameSubjs.at(subjN);
//behavior - apply rotation speed
pGS->moveSubj();
//prepare subject for rendering
pGS->buildModelMatrix(pGS);
//build MVP matrix for given subject
mat4x4_mul(mMVP, mProjection, pGS->ownModelMatrix);
//render subject
for (int i = 0; i < pGS->djTotalN; i++) {
DrawJob* pDJ = DrawJob::drawJobs.at(pGS->djStartN + i);
pDJ->execute((float*)mMVP, NULL);
}
}
mySwapBuffers();
return 1;
}
int TheGame::cleanUp() {
Texture::cleanUp();
Shader::cleanUp();
DrawJob::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;
}
5. Компиляция и запуск (зеленая стрелка), картинка та же, а значит, переход с не-индексированного GL_TRIANGLE_STRIP на индексированный GL_TRIANGLES успешно состоялся.