{"id":535,"date":"2021-12-05T22:36:25","date_gmt":"2021-12-05T22:36:25","guid":{"rendered":"https:\/\/writingagame.com\/?p=535"},"modified":"2021-12-05T22:47:32","modified_gmt":"2021-12-05T22:47:32","slug":"chapter-15-indexes-and-ebo-element-buffer-object","status":"publish","type":"post","link":"https:\/\/writingagame.com\/index.php\/2021\/12\/05\/chapter-15-indexes-and-ebo-element-buffer-object\/","title":{"rendered":"Chapter 15. Indexes and EBO &#8211; Element Buffer Object"},"content":{"rendered":"\n<p>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 <strong>GL_TRIANGLES<\/strong>. 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).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Windows<\/h2>\n\n\n\n<p>1. Start VS, open <em>C:\\CPP\\a998engine\\p_windows\\p_windows.sln<\/em>.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>Need to modify <em>DrawJob <\/em>class a bit.<\/p>\n\n\n\n<p>2. Open <em>DrawJob.h<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [18]; title: ; notranslate\" title=\"\">\n#pragma once\n#include &quot;Material.h&quot;\n#include &lt;vector&gt;\n\nstruct AttribRef \/\/attribute reference\/description\n{\n\tunsigned int glVBOid = 0; \/\/buffer object id\n\tint offset = 0; \/\/variable&#039;s offset inside of VBO&#039;s element\n\tint stride = 0; \/\/Buffer&#039;s element size in bytes\n};\n\nclass DrawJob\n{\npublic:\n\tMaterial mt;\n\tint pointsN = 0; \/\/N of points to draw\n\tunsigned int glVAOid = 0; \/\/will hold data stream attributes mapping\/positions\n\tunsigned int glEBOid = 0; \/\/Element Buffer Object (vertex indices)\n\n\t\/\/common attributes\n\tAttribRef aPos;\n\tAttribRef aTuv;\n\n\t\/\/static arrays (vectors) of all loaded DrawJobs, VBO ids\n\tstatic std::vector&lt;DrawJob*&gt; drawJobs;\n\tstatic std::vector&lt;unsigned int&gt; buffersIds;\npublic:\n\tDrawJob();\n\tvirtual ~DrawJob(); \/\/destructor\n\tstatic int cleanUp();\n\tstatic int newBufferId();\n\tint buildVAO() { return buildVAOforShader(this, mt.shaderN); };\n\tstatic int buildVAOforShader(DrawJob* pDJ, int shaderN);\n\tstatic int attachAttribute(int varLocationInShader, int attributeSizeInFloats, AttribRef* pAttribRef);\n\n\tint execute(float* uMVP, Material* pMt) { return executeDrawJob(this, uMVP, pMt); };\n\tstatic int executeDrawJob(DrawJob* pDJ, float* uMVP, Material* pMt);\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p>Now we have <em>glEBOid <\/em>property there.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>In <em>DrawJob::executeDrawJob()<\/em>, in case EBO is provided, we will use <em>glDrawElements <\/em>command instead of <em>glDrawArrays <\/em>as we always did before. We&#8217;ll keep using <em>glDrawArrays <\/em>for sequential (unindexed) vertices arrays (as for GL_TRIANGLE_STRIP for example).<\/p>\n\n\n\n<p>In <em>DrawJob::buildVAOforShader()<\/em>, when EBO presented, it will be attached to VAO.<\/p>\n\n\n\n<p>3. Open <em>DrawJob.cpp<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [43,44,90,94]; title: ; notranslate\" title=\"\">\n#include &quot;DrawJob.h&quot;\n#include &quot;platform.h&quot;\n#include &quot;utils.h&quot;\n#include &quot;Shader.h&quot;\n#include &quot;Texture.h&quot;\n\n\/\/static arrays (vectors) of all loaded DrawJobs, VBO ids\nstd::vector&lt;DrawJob*&gt; DrawJob::drawJobs;\nstd::vector&lt;unsigned int&gt; DrawJob::buffersIds;\n\nDrawJob::DrawJob() {\n\tdrawJobs.push_back(this);\n}\nDrawJob::~DrawJob() {\n\tglBindBuffer(GL_ARRAY_BUFFER, 0);\n\tglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);\n\tif (glVAOid &gt; 0)\n\t\tglDeleteVertexArrays(1, &amp;glVAOid);\n}\nint DrawJob::newBufferId() {\n\tunsigned int bufferId;\n\tglGenBuffers(1, &amp;bufferId);\n\tbuffersIds.push_back(bufferId);\n\treturn (int)bufferId;\n}\nunsigned int activeVBOid;\nint DrawJob::buildVAOforShader(DrawJob* pDJ, int shaderN) {\n\t\/\/delete VAO if exists already\n\tif (pDJ-&gt;glVAOid &gt; 0) {\n\t\tglBindBuffer(GL_ARRAY_BUFFER, 0);\n\t\tglDeleteVertexArrays(1, &amp;(pDJ-&gt;glVAOid));\n\t}\n\tglGenVertexArrays(1, &amp;pDJ-&gt;glVAOid);\n\tglBindVertexArray(pDJ-&gt;glVAOid);\n\n\t\/\/open shader descriptor to access variables locations\n\tShader* pShader = Shader::shaders.at(pDJ-&gt;mt.shaderN);\n\n\tactiveVBOid = 0;\n\tattachAttribute(pShader-&gt;l_aPos, 3, &amp;pDJ-&gt;aPos);\n\tattachAttribute(pShader-&gt;l_aTuv, 2, &amp;pDJ-&gt;aTuv);\n\n\tif (pDJ-&gt;glEBOid &gt; 0)\n\t\tglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, pDJ-&gt;glEBOid);\n\n\tglBindVertexArray(0);\n\tglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);\n\tglBindBuffer(GL_ARRAY_BUFFER, 0);\n\treturn 1;\n}\n\nint DrawJob::attachAttribute(int varLocationInShader, int attributeSizeInFloats, AttribRef* pAR) {\n\tif (varLocationInShader &lt; 0)\n\t\treturn 0; \/\/not used in this shader\n\tif (pAR-&gt;glVBOid == 0) {\n\t\tmylog(&quot;ERROR in DrawJob::attachAttribute, nk such attribute\/VBO\\n&quot;);\n\t\treturn -1;\n\t}\n\tglEnableVertexAttribArray(varLocationInShader);\n\tif (activeVBOid != pAR-&gt;glVBOid) {\n\t\tactiveVBOid = pAR-&gt;glVBOid;\n\t\t\/\/attach input stream data\n\t\tglBindBuffer(GL_ARRAY_BUFFER, activeVBOid);\n\t}\n\tglVertexAttribPointer(varLocationInShader, attributeSizeInFloats, GL_FLOAT, GL_FALSE, pAR-&gt;stride, (void*)(long)pAR-&gt;offset);\n\treturn 1;\n}\nint DrawJob::executeDrawJob(DrawJob* pDJ, float* uMVP, Material* pMt) {\n\tif (pMt == NULL)\n\t\tpMt = &amp;(pDJ-&gt;mt);\n\tglBindVertexArray(pDJ-&gt;glVAOid);\n\tShader* pShader = Shader::shaders.at(pMt-&gt;shaderN);\n\tglUseProgram(pShader-&gt;GLid);\n\tglUniformMatrix4fv(pShader-&gt;l_uMVP, 1, GL_FALSE, (const GLfloat*)uMVP);\n\n\t\/\/attach textures\n\tif (pShader-&gt;l_uTex0 &gt;= 0) {\n\t\tint textureId = Texture::getGLid(pMt-&gt;uTex0);\n\t\t\/\/pass textureId to shader program\n\t\tglActiveTexture(GL_TEXTURE0); \/\/ activate the texture unit first before binding texture\n\t\tglBindTexture(GL_TEXTURE_2D, textureId);\n\t\t\/\/ Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 0.    \n\t\tglUniform1i(pShader-&gt;l_uTex0, 0);\n\t}\n\t\/\/other uniforms\n\tif (pShader-&gt;l_uColor &gt;= 0) {\n\t\t\/\/float uColor&#91;4] = { 1.f, 0.f, 1.f, 1.f }; \/\/R,G,B, alpha\n\t\tglUniform4fv(pShader-&gt;l_uColor, 1, pMt-&gt;uColor.forGL());\n\t}\n\tif (pDJ-&gt;glEBOid == 0) {\n\t\tglDrawArrays(pMt-&gt;primitiveType, 0, pDJ-&gt;pointsN);\n\t}\n\telse { \/\/use EBO\n\t\tglDrawElements(pMt-&gt;primitiveType, pDJ-&gt;pointsN, GL_UNSIGNED_SHORT, 0);\n\t}\n\tglBindVertexArray(0);\n\treturn 1;\n}\nint DrawJob::cleanUp() {\n\tint itemsN = drawJobs.size();\n\t\/\/delete all drawJobs\n\tfor (int i = 0; i &lt; itemsN; i++) {\n\t\tDrawJob* pDJ = drawJobs.at(i);\n\t\tdelete pDJ;\n\t}\n\tdrawJobs.clear();\n\t\/\/delete Buffers\n\titemsN = buffersIds.size();\n\t\/\/delete all buffers\n\tfor (int i = 0; i &lt; itemsN; i++) {\n\t\tunsigned int id = buffersIds.at(i);\n\t\tglDeleteBuffers(1, &amp;id);\n\t}\n\tbuffersIds.clear();\n\n\treturn 1;\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>In <em>TheGame.cpp<\/em> we&#8217;ll add <em>frontIndices <\/em>array: 6 indices for 2 triangles: North-West->South-West->South-East and NW->SE->NE.<\/p>\n\n\n\n<p>In <em>TheGame::getReady()<\/em>, when creating front <em>DrawJob <\/em>(with picture), we&#8217;ll generate EBO and will change <em>pDJ->mt.primitiveType<\/em> from GL_TRIANGLE_STRIP to GL_TRIANGLES.<\/p>\n\n\n\n<p>Also we&#8217;ll need to change <em>pDJ->pointsN<\/em> from 4 (4 sequential vertices) to <strong>6<\/strong> (2 indexed triangles).<\/p>\n\n\n\n<p>4. Open <em>TheGame.cpp<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [23,48,51,58]; title: ; notranslate\" title=\"\">\n#include &quot;TheGame.h&quot;\n#include &quot;platform.h&quot;\n#include &quot;linmath.h&quot;\n#include &quot;Texture.h&quot;\n#include &quot;Shader.h&quot;\n#include &quot;DrawJob.h&quot;\n\nextern std::string filesRoot;\n\n\/\/static array (vector) for loaded gameSubjs\nstd::vector&lt;GameSubj*&gt; TheGame::gameSubjs;\n\nstatic const struct\n{\n    float x, y, z, tu, tv;\n} frontVertices&#91;4] =\n{\n    { -0.5f,  0.5f, 0.f, 0.f, 0.f }, \/\/top-left\n    { -0.5f, -0.5f, 0.f, 0.f, 1.f }, \/\/bottom-left\n    {  0.5f,  0.5f, 0.f, 1.f, 0.f }, \/\/top-right\n    {  0.5f, -0.5f, 0.f, 1.f, 1.f }  \/\/bottom-right\n};\nGLushort frontIndices&#91;6] = { 0,1,3, 0,3,2 };\n\nint TheGame::getReady() {\n    bExitGame = false;\n    Shader::loadShaders();\n    glEnable(GL_CULL_FACE);\n\n    GameSubj* pGS = new GameSubj();\n    gameSubjs.push_back(pGS);\n    pGS-&gt;djStartN = DrawJob::drawJobs.size();\n\n    pGS-&gt;name.assign(&quot;img1&quot;);\n    \/\/pGS-&gt;ownCoords.setPosition(-50, 50, 0);\n    pGS-&gt;ownSpeed.setDegrees(1, 2, 3);\n    pGS-&gt;scale&#91;0] = 400;\n    pGS-&gt;scale&#91;1] = pGS-&gt;scale&#91;0] \/ 2;\n\n    \/\/face DrawJob\n    \/\/build VBO\n    unsigned int VBOid = DrawJob::newBufferId();\n    glBindBuffer(GL_ARRAY_BUFFER, VBOid);\n    glBufferData(GL_ARRAY_BUFFER, sizeof(frontVertices), frontVertices, GL_STATIC_DRAW);\n    int stride = sizeof(float) * 5;\n    \/\/add DrawJob\n    DrawJob* pDJ = new DrawJob();\n    pDJ-&gt;pointsN = 6; \/\/number of points (vertices or indices in case of EBO)\n    \/\/define material\n    pDJ-&gt;mt.shaderN = Shader::spN_flat_tex;\n    pDJ-&gt;mt.primitiveType = GL_TRIANGLES; \/\/GL_TRIANGLE_STRIP;\n    pDJ-&gt;mt.uTex0 = Texture::loadTexture(filesRoot + &quot;\/dt\/sample_img.png&quot;);\n    \/\/attributes references\n    AttribRef* pAR;\n    pAR = &amp;pDJ-&gt;aPos; pAR-&gt;offset = 0;                 pAR-&gt;glVBOid = VBOid; pAR-&gt;stride = stride;\n    pAR = &amp;pDJ-&gt;aTuv; pAR-&gt;offset = sizeof(float) * 3; pAR-&gt;glVBOid = VBOid; pAR-&gt;stride = stride;\n\n    \/\/build and attrach EBO\n    pDJ-&gt;glEBOid = DrawJob::newBufferId();\n    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, pDJ-&gt;glEBOid);\n    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(frontIndices), frontIndices, GL_STATIC_DRAW);\n\n    \/\/create and fill vertex attributes array (VAO)\n    pDJ-&gt;buildVAO();\n    pGS-&gt;djTotalN = DrawJob::drawJobs.size() - pGS-&gt;djStartN;\n\n    return 1;\n}\nint TheGame::drawFrame() {\n    myPollEvents();\n\n    \/\/glClearColor(0.0, 0.0, 0.5, 1.0);\n    glClear(GL_COLOR_BUFFER_BIT);\n    mat4x4 mProjection, mMVP;\n    mat4x4_ortho(mProjection, -(float)screenSize&#91;0] \/ 2, (float)screenSize&#91;0] \/ 2, -(float)screenSize&#91;1] \/ 2, (float)screenSize&#91;1] \/ 2, 200.f, -200.f);\n\n    \/\/scan subjects\n    int subjsN = gameSubjs.size();\n    for (int subjN = 0; subjN &lt; subjsN; subjN++) {\n        GameSubj* pGS = gameSubjs.at(subjN);\n        \/\/behavior - apply rotation speed\n        pGS-&gt;moveSubj();\n        \/\/prepare subject for rendering\n        pGS-&gt;buildModelMatrix(pGS);\n        \/\/build MVP matrix for given subject\n        mat4x4_mul(mMVP, mProjection, pGS-&gt;ownModelMatrix);\n        \/\/render subject\n        for (int i = 0; i &lt; pGS-&gt;djTotalN; i++) {\n            DrawJob* pDJ = DrawJob::drawJobs.at(pGS-&gt;djStartN + i);\n            pDJ-&gt;execute((float*)mMVP, NULL);\n        }\n    }\n    mySwapBuffers();\n    return 1;\n}\nint TheGame::cleanUp() {\n    Texture::cleanUp();\n    Shader::cleanUp();\n    DrawJob::cleanUp();\n    return 1;\n}\nint TheGame::onScreenResize(int width, int height) {\n    if (screenSize&#91;0] == width &amp;&amp; screenSize&#91;1] == height)\n        return 0;\n    screenSize&#91;0] = width;\n    screenSize&#91;1] = height;\n    screenRatio = (float)width \/ (float)height;\n    glViewport(0, 0, width, height);\n    mylog(&quot; screen size %d x %d\\n&quot;, width, height);\n    return 1;\n}\nint TheGame::run() {\n    getReady();\n    while (!bExitGame) {\n        drawFrame();\n    }\n    cleanUp();\n    return 1;\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>5. Build an run (green arrow), same picture, which means the transition from non-indexed GL_TRIANGLE_STRIP to indexed GL_TRIANGLES was successful.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p class=\"mb-2\">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 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-535","post","type-post","status-publish","format-standard","hentry","category-cross-platform-3d"],"_links":{"self":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/535","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/comments?post=535"}],"version-history":[{"count":8,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/535\/revisions"}],"predecessor-version":[{"id":545,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/535\/revisions\/545"}],"wp:attachment":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media?parent=535"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/categories?post=535"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/tags?post=535"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}