In our latest samples we used GL_TRIANGLE_STRIP primitive (to draw a rectangle as a sequential array of vertices). But the most useful and most common primitive is GL_TRIANGLES. We used it earlier to draw a triangle (assuming that vertices go in correct sequential order). For more complicated surfaces normal approach is to keep an array of vertices in VBO regardless of their order. And to keep the draw order separately as an array of indexes (vertices numbers) in so called EBO (Element Buffer Object) or IBO (index Buffer Object, which is the same, just alternative name).
Windows
1. Start VS, open C:\CPP\a998engine\p_windows\p_windows.sln.
Need to modify DrawJob class a bit.
2. Open DrawJob.h and replace code by:
#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);
};
Now we have glEBOid property there.
In DrawJob::executeDrawJob(), in case EBO is provided, we will use glDrawElements command instead of glDrawArrays as we always did before. We'll keep using glDrawArrays for sequential (unindexed) vertices arrays (as for GL_TRIANGLE_STRIP for example).
In DrawJob::buildVAOforShader(), when EBO presented, it will be attached to VAO.
3. Open DrawJob.cpp and replace code by:
#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;
}
In TheGame.cpp we'll add frontIndices array: 6 indices for 2 triangles: North-West->South-West->South-East and NW->SE->NE.
In TheGame::getReady(), when creating front DrawJob (with picture), we'll generate EBO and will change pDJ->mt.primitiveType from GL_TRIANGLE_STRIP to GL_TRIANGLES.
Also we'll need to change pDJ->pointsN from 4 (4 sequential vertices) to 6 (2 indexed triangles).
4. Open TheGame.cpp and replace code by:
#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. Build an run (green arrow), same picture, which means the transition from non-indexed GL_TRIANGLE_STRIP to indexed GL_TRIANGLES was successful.