{"id":607,"date":"2021-12-07T01:47:18","date_gmt":"2021-12-07T01:47:18","guid":{"rendered":"https:\/\/writingagame.com\/?p=607"},"modified":"2022-01-25T19:15:05","modified_gmt":"2022-01-25T19:15:05","slug":"chapter-19-light-and-phong-shading","status":"publish","type":"post","link":"https:\/\/writingagame.com\/index.php\/2021\/12\/07\/chapter-19-light-and-phong-shading\/","title":{"rendered":"Chapter 19. Light and Phong Shading"},"content":{"rendered":"\n<p>For more realistic image we&#8217;ll need light and shades, which will require new <em>shaders<\/em>. We will use <strong>Phong shading model<\/strong>.<\/p>\n\n\n\n<p>Let&#8217;s start with a uniform color shader.<\/p>\n\n\n\n<p>1. <strong>Vertex shader<\/strong>. <\/p>\n\n\n\n<p>Copy following code in a <strong>Text Editor<\/strong> and save is as a txt file in<\/p>\n\n\n\n<p><em>C:\\CPP\\engine\\dt\\shaders\\<strong>phong_ucolor_v.txt<\/strong><\/em><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#version 320 es\nprecision lowp float;\nuniform mat4 uMVP; \/\/ transform matrix (Model-View-Projection)\nuniform mat3 uMV3x3; \/\/ Model-View matrix (for calculating normals into eye space)\nin vec3 aPos; \/\/ position attribute (x,y,z)\nin vec3 aNormal; \/\/ normal attribute (x,y,z)\nout vec3 vNormal; \/\/ varying normal (to pass to fragment shader)\n\nvoid main(void) { \n\tgl_Position = uMVP * vec4(aPos, 1.0);\t\n\t\/\/ Transform the normal&#039;s orientation into eye space.    \n\tvNormal = uMV3x3 * aNormal;\t\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>2. <strong>Fragment shader<\/strong>. <\/p>\n\n\n\n<p>Copy following code in a <strong>Text Editor<\/strong> and save is as a txt file in<\/p>\n\n\n\n<p><em>C:\\CPP\\engine\\dt\\shaders\\phong_ucolor_<strong>f<\/strong>.txt<\/em><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#version 320 es\nprecision lowp float;\nout vec4 FragColor; \/\/output pixel color\n\nin vec3 vNormal; \/\/normal passed from rasterizer\nuniform vec4 uColor;\n\nuniform float uAlphaFactor; \/\/for semi-transparency\n\nuniform float uAmbient;\nuniform float uSpecularIntencity;\nuniform float uSpecularMinDot;\nuniform float uSpecularPowerOf;\n\nuniform vec3 uVectorToLight;\nuniform vec3 uHalfVector;\n\nvoid main(void) {\n\n\tvec4 outColor = uColor;\n\n\tvec3 vNormalNormal = normalize(vNormal);\n\n\tif(uAmbient&lt;1.0){\n\t\t \/\/ Calculate the dot product of the light vector and vertex normal. If the normal and light vector are\n\t\t \/\/ pointing in the same direction then it will get max illumination.\n\t\t float directionalLightIntencity = dot(vNormalNormal, uVectorToLight);\n\t\t\n\t\t \/\/ count ambient component\n\t\t directionalLightIntencity += uAmbient;\n\t\t if(directionalLightIntencity &lt; uAmbient)\n\t\t\tdirectionalLightIntencity = uAmbient;\n\n\t\t \/\/ Multiply the color by the lightIntencity illumination level to get final output color.\n\t\t outColor = outColor * directionalLightIntencity;\n\t}\n\tif(uSpecularIntencity&gt;0.0){\n\t\t\/\/specular light\n\t\t\/\/ INTENSITY OF THE SPECULAR LIGHT\n\t\t\/\/ DOT PRODUCT OF NORMAL VECTOR AND THE HALF VECTOR TO THE POWER OF THE SPECULAR HARDNESS\n\t\tfloat dotProduct = dot(vNormalNormal, uHalfVector);\n\n\t\tif(dotProduct&gt;uSpecularMinDot){\n\t\t\tfloat specularIntencity = pow(dotProduct, uSpecularPowerOf) * uSpecularIntencity;\t\t\n\t\t\tif(specularIntencity &gt; uSpecularIntencity)\n\t\t\t\tspecularIntencity = uSpecularIntencity;\n\t\t\toutColor += specularIntencity;\n\t\t}\n\t}\n\toutColor.a = uAlphaFactor;\t\t\n\tFragColor = outColor;\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p>Read comments. Hope they are clear enough.<\/p>\n\n\n\n<p>Quite a lot of new variables. Let&#8217;s take care of them.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Windows<\/h2>\n\n\n\n<p>3. Start VS. Open\u00a0<em>C:\\CPP\\<em>a997modeler<\/em>\\p_windows\\p_windows.sln<\/em>.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>First, <em>Shader <\/em>class. We need to add <em>locations <\/em>for all new shader variables, particularly:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>l_aNormal &#8211; vertex normal attribute<\/li><li>l_uVectorToLight &#8211; direction to light, required for light calculation<\/li><li>l_uHalfVector; \/\/required for specular light calculation<\/li><\/ul>\n\n\n\n<p>New <em>Material <\/em>properties related to light:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>l_uAmbient<\/li><li>l_uSpecularIntencity<\/li><li>l_uSpecularMinDot<\/li><li>l_uSpecularPowerOf<\/li><\/ul>\n\n\n\n<ul class=\"wp-block-list\"><li>Plus <em>l_uMV3x3<\/em> &#8211; for Model-View matrix for normals<\/li><li>Plus new shader program number <em>spN_<em>phong<\/em><\/em>_<em>ucolor<\/em>.<\/li><li>Plus new function  <em>fillLocations()<\/em> .<\/li><\/ul>\n\n\n\n<ul class=\"wp-block-list\"><li>All other new variables are reserved for following chapters.<\/li><\/ul>\n\n\n\n<p>4. Open <em>Shader.h<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [16,21,22,23,35,36,37,38,39,40,48,60]; title: ; notranslate\" title=\"\">\n#pragma once\n#include &quot;platform.h&quot;\n#include &lt;string&gt;\n#include &lt;vector&gt;\n\nclass Shader\n{\npublic:\n    \/\/Shader program&#039;s individual descriptor:\n    unsigned int GLid = -1; \/\/ GL shader id\n    \/\/common variables, &quot;l_&quot; for &quot;location&quot;\n    \/\/attributes\n    int l_aPos; \/\/attribute position (3D coordinates)\n    int l_aTuv; \/\/attribute TUV (texture coordinates)\n    int l_aTuv2; \/\/attribute TUV (texture coordinates for normal map)\n    int l_aNormal; \/\/attribute normal (3D vector)\n    int l_aTangent; \/\/for normal map\n    int l_aBinormal; \/\/for normal map\n    \/\/uniforms\n    int l_uMVP; \/\/ transform matrix (Model-View-Projection)\n    int l_uMV3x3; \/\/ Model-View matrix for normals\n    int l_uVectorToLight; \/\/required for light\n    int l_uHalfVector; \/\/required for specular light\n    \/\/material&#039;s properties\n    int l_uColor;\n    int l_uTex0; \/\/texture id\n    int l_uTex1mask; \/\/transparency map\n    int l_uTex2nm; \/\/normal map\n    int l_uTex3; \/\/texture id\n    int l_uTex1alphaChannelN; \/\/alpha channel for mask\n    int l_uTex1alphaNegative; \/\/alpha channel negative\n    int l_uTex0translateChannelN; \/\/translate tex0 to tex3 by channelN.\n    int l_uAlphaFactor; \/\/for semi-transparency\n    int l_uAlphaBlending; \/\/for semi-transparency\n    \/\/light:\n    int l_uAmbient; \/\/ambient light\n    \/\/specular light parameters\n    int l_uSpecularIntencity;\n    int l_uSpecularMinDot;\n    int l_uSpecularPowerOf;\n    \/\/end of descriptor\n\n    \/\/static array (vector) of all loaded shaders\n    static std::vector&lt;Shader*&gt; shaders;\n    \/\/common shader programs (&quot;spN&quot; - shader program number)\n    static int spN_flat_ucolor;\n    static int spN_flat_tex;\n    static int spN_phong_ucolor;\n\npublic:\n    static int loadShaders();\n    static int loadShader(std::string filePathVertexS, std::string filePathFragmentS);\n    static int cleanUp();\n    static unsigned int getGLid(int shN) { return shaders.at(shN)-&gt;GLid; };\n    \n    static int linkShaderProgram(const char* filePathVertexS, const char* filePathFragmentS);\n\tstatic int compileShader(const char* filePath, GLenum shaderType);\n\tstatic int shaderErrorCheck(int shaderId, std::string ref);\n\tstatic int programErrorCheck(int programId, std::string ref);\n    static int fillLocations(Shader* pSh);\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>Now we need to add a new shader program into<em> Shader::loadShader<strong>s<\/strong>()<\/em>. And in <em>Shader::loadShader()-&gt;fillLocations()<\/em> we&#8217;ll fill out shader variables locations.<\/p>\n\n\n\n<p>5. Open <em>Shader.cpp<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [12,17,26,30]; title: ; notranslate\" title=\"\">\n#include &quot;Shader.h&quot;\n#include &quot;platform.h&quot;\n#include &quot;utils.h&quot;\n\nextern std::string filesRoot;\n\n\/\/static array (vector) of all loaded shaders\nstd::vector&lt;Shader*&gt; Shader::shaders;\n\/\/common shader programs (&quot;spN&quot; - shader program number)\nint Shader::spN_flat_ucolor = -1;\nint Shader::spN_flat_tex = -1;\nint Shader::spN_phong_ucolor = -1;\n\nint Shader::loadShaders() {\n    spN_flat_ucolor = loadShader(&quot;\/dt\/shaders\/flat_ucolor_v.txt&quot;, &quot;\/dt\/shaders\/flat_ucolor_f.txt&quot;);\n    spN_flat_tex = loadShader(&quot;\/dt\/shaders\/flat_tex_v.txt&quot;, &quot;\/dt\/shaders\/flat_tex_f.txt&quot;);\n    spN_phong_ucolor = loadShader(&quot;\/dt\/shaders\/phong_ucolor_v.txt&quot;, &quot;\/dt\/shaders\/phong_ucolor_f.txt&quot;);\n    return 1;\n}\nint Shader::loadShader(std::string filePathVertexS, std::string filePathFragmentS) {\n    \/\/create shader object\n    Shader* pSh = new Shader();\n    shaders.push_back(pSh);\n    pSh-&gt;GLid = linkShaderProgram((filesRoot + filePathVertexS).c_str(), (filesRoot + filePathFragmentS).c_str());\n    \/\/common variables. If not presented, = -1;\n    fillLocations(pSh);\n\n    return (shaders.size() - 1);\n}\nint Shader::fillLocations(Shader* pSh) {\n    \/\/common variables. If not presented, = -1;\n    \/\/attributes\n    pSh-&gt;l_aPos = glGetAttribLocation(pSh-&gt;GLid, &quot;aPos&quot;); \/\/attribute position (3D coordinates)\n    pSh-&gt;l_aNormal = glGetAttribLocation(pSh-&gt;GLid, &quot;aNormal&quot;); \/\/attribute normal (3D vector)\n    pSh-&gt;l_aTangent = glGetAttribLocation(pSh-&gt;GLid, &quot;aTangent&quot;); \/\/for normal map\n    pSh-&gt;l_aBinormal = glGetAttribLocation(pSh-&gt;GLid, &quot;aBinormal&quot;); \/\/for normal map\n    pSh-&gt;l_aTuv = glGetAttribLocation(pSh-&gt;GLid, &quot;aTuv&quot;); \/\/attribute TUV (texture coordinates)\n    pSh-&gt;l_aTuv2 = glGetAttribLocation(pSh-&gt;GLid, &quot;aTuv2&quot;); \/\/attribute TUV (texture coordinates)\n    \/\/uniforms\n    pSh-&gt;l_uMVP = glGetUniformLocation(pSh-&gt;GLid, &quot;uMVP&quot;); \/\/ transform matrix (Model-View-Projection)\n    pSh-&gt;l_uMV3x3 = glGetUniformLocation(pSh-&gt;GLid, &quot;uMV3x3&quot;); \/\/ Model-View matrix for normals\n    pSh-&gt;l_uVectorToLight = glGetUniformLocation(pSh-&gt;GLid, &quot;uVectorToLight&quot;); \/\/ \n    pSh-&gt;l_uHalfVector = glGetUniformLocation(pSh-&gt;GLid, &quot;uHalfVector&quot;); \/\/ required for specular light\n    \/\/material&#039;s properties\n    pSh-&gt;l_uColor = glGetUniformLocation(pSh-&gt;GLid, &quot;uColor&quot;);\n    pSh-&gt;l_uTex0 = glGetUniformLocation(pSh-&gt;GLid, &quot;uTex0&quot;); \/\/texture id\n    pSh-&gt;l_uTex1mask = glGetUniformLocation(pSh-&gt;GLid, &quot;uTex1mask&quot;); \/\/texture id\n    pSh-&gt;l_uTex2nm = glGetUniformLocation(pSh-&gt;GLid, &quot;uTex2nm&quot;); \/\/texture id\n    pSh-&gt;l_uTex3 = glGetUniformLocation(pSh-&gt;GLid, &quot;uTex3&quot;); \/\/texture id\n    pSh-&gt;l_uTex1alphaChannelN = glGetUniformLocation(pSh-&gt;GLid, &quot;uTex1alphaChannelN&quot;);\n    pSh-&gt;l_uTex1alphaNegative = glGetUniformLocation(pSh-&gt;GLid, &quot;uTex1alphaNegative&quot;);\n    pSh-&gt;l_uTex0translateChannelN = glGetUniformLocation(pSh-&gt;GLid, &quot;uTex0translateChannelN&quot;);\n    pSh-&gt;l_uAlphaFactor = glGetUniformLocation(pSh-&gt;GLid, &quot;uAlphaFactor&quot;); \/\/ for semi-transparency\n    pSh-&gt;l_uAlphaBlending = glGetUniformLocation(pSh-&gt;GLid, &quot;uAlphaBlending&quot;); \/\/ for semi-transparency\n    pSh-&gt;l_uAmbient = glGetUniformLocation(pSh-&gt;GLid, &quot;uAmbient&quot;); \/\/ ambient light\n    pSh-&gt;l_uSpecularIntencity = glGetUniformLocation(pSh-&gt;GLid, &quot;uSpecularIntencity&quot;); \/\/ \n    pSh-&gt;l_uSpecularMinDot = glGetUniformLocation(pSh-&gt;GLid, &quot;uSpecularMinDot&quot;); \/\/ \n    pSh-&gt;l_uSpecularPowerOf = glGetUniformLocation(pSh-&gt;GLid, &quot;uSpecularPowerOf&quot;); \/\/ \n    return 1;\n}\nint Shader::cleanUp() {\n    int shadersN = shaders.size();\n    if (shadersN &lt; 1)\n        return -1;\n    glUseProgram(0);\n    for (int i = 0; i &lt; shadersN; i++) {\n        Shader* pSh = shaders.at(i);\n        glDeleteProgram(pSh-&gt;GLid);\n        delete pSh;\n    }\n    shaders.clear();\n    return 1;\n}\n\nGLchar infoLog&#91;1024];\nint logLength;\nint Shader::shaderErrorCheck(int shaderId, std::string ref) {\n    \/\/use after glCompileShader()\n    if (checkGLerrors(ref) &gt; 0)\n        return -1;\n    glGetShaderInfoLog(shaderId, 1024, &amp;logLength, infoLog);\n    if (logLength == 0)\n        return 0;\n    mylog(&quot;%s shader infoLog:\\n%s\\n&quot;, ref.c_str(), infoLog);\n    return -1;\n}\nint Shader::programErrorCheck(int programId, std::string ref) {\n    \/\/use after glLinkProgram()\n    if (checkGLerrors(ref) &gt; 0)\n        return -1;\n    glGetProgramInfoLog(programId, 1024, &amp;logLength, infoLog);\n    if (logLength == 0)\n        return 0;\n    mylog(&quot;%s program infoLog:\\n%s\\n&quot;, ref.c_str(), infoLog);\n    return -1;\n}\n\nint Shader::compileShader(const char* filePath, GLenum shaderType) {\n    int shaderId = glCreateShader(shaderType);\n    FILE* pFile;\n    myFopen_s(&amp;pFile, filePath, &quot;rt&quot;);\n    if (pFile != NULL)\n    {\n        \/\/ obtain file size:\n        fseek(pFile, 0, SEEK_END);\n        int fSize = ftell(pFile);\n        rewind(pFile);\n        \/\/ size obtained, create buffer\n        char* shaderSource = new char&#91;fSize + 1];\n        fSize = fread(shaderSource, 1, fSize, pFile);\n        shaderSource&#91;fSize] = 0;\n        fclose(pFile);\n        \/\/ source code loaded, compile\n        glShaderSource(shaderId, 1, (const GLchar**)&amp;shaderSource, NULL);\n        \/\/myglErrorCheck(&quot;glShaderSource&quot;);\n        glCompileShader(shaderId);\n        if (shaderErrorCheck(shaderId, &quot;glCompileShader&quot;) &lt; 0)\n            return -1;\n        delete&#91;] shaderSource;\n    }\n    else {\n        mylog(&quot;ERROR loading %s\\n&quot;, filePath);\n        return -1;\n    }\n    return shaderId;\n}\nint Shader::linkShaderProgram(const char* filePathVertexS, const char* filePathFragmentS) {\n    int vertexShaderId = compileShader(filePathVertexS, GL_VERTEX_SHADER);\n    int fragmentShaderId = compileShader(filePathFragmentS, GL_FRAGMENT_SHADER);\n    int programId = glCreateProgram();\n    glAttachShader(programId, vertexShaderId);\n    glAttachShader(programId, fragmentShaderId);\n    glLinkProgram(programId);\n    if (programErrorCheck(programId, &quot;glLinkProgram&quot;) &lt; 0)\n        return -1;\n    \/\/don&#039;t need shaders any longer - detach and delete them\n    glDetachShader(programId, vertexShaderId);\n    glDetachShader(programId, fragmentShaderId);\n    glDeleteShader(vertexShaderId);\n    glDeleteShader(fragmentShaderId);\n    return programId;\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>DrawJob <\/em>we have a new attributes.<\/p>\n\n\n\n<p>Also <em>executeDrawJob()<\/em> will have new parameters and uniforms related to light\/shade calculations.<\/p>\n\n\n\n<p>6. Open <em>DrawJob.h<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [22,43,44]; 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 aNormal;\n\tAttribRef aTuv;\n\tAttribRef aTuv2; \/\/for normal map\n\tAttribRef aTangent; \/\/for normal map\n\tAttribRef aBinormal; \/\/for normal map\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\tvirtual int setDesirableOffsets(int* pStride, int shaderN, int VBOid) { return setDesirableOffsetsForSingleVBO(this, pStride, shaderN, VBOid); };\n\tstatic int setDesirableOffsetsForSingleVBO(DrawJob* pDJ, int* pStride, int shaderN, int VBOid);\n\n\tint execute(float* uMVP, float* uMV, float* dir2light, float* halfVector, Material* pMt) { return executeDrawJob(this, uMVP, uMV, dir2light, halfVector, pMt); };\n\tstatic int executeDrawJob(DrawJob* pDJ, float* uMVP, float* uMV, float* gir2light, float* halfVector, Material* pMt);\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>New attributes will affect <em>DrawJob::setDesirableOffsetsForSingleVBO(), DrawJob::buildVAOforShader()<\/em> and <em>DrawJob::executeDrawJob()<\/em>.<\/p>\n\n\n\n<p>7. Open <em>DrawJob.cpp<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [41,80,133,134,135,136,137,138,139,140,186,187,188,189,210]; 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_aNormal, 3, &amp;pDJ-&gt;aNormal);\n\tattachAttribute(pShader-&gt;l_aTuv, 2, &amp;pDJ-&gt;aTuv);\n\tattachAttribute(pShader-&gt;l_aTuv2, 2, &amp;pDJ-&gt;aTuv2); \/\/for normal map\n\tattachAttribute(pShader-&gt;l_aTangent, 3, &amp;pDJ-&gt;aTangent); \/\/for normal map\n\tattachAttribute(pShader-&gt;l_aBinormal, 3, &amp;pDJ-&gt;aBinormal); \/\/for normal map\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, float* uMV3x3, float* dir2light, float* halfVector, 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\t\/\/input uniforms\n\tglUniformMatrix4fv(pShader-&gt;l_uMVP, 1, GL_FALSE, (const GLfloat*)uMVP);\n\tglUniformMatrix3fv(pShader-&gt;l_uMV3x3, 1, GL_FALSE, (const GLfloat*)uMV3x3);\n\tif (pShader-&gt;l_uVectorToLight &gt;= 0)\n\t\tglUniform3fv(pShader-&gt;l_uVectorToLight, 1, (const GLfloat*)dir2light);\n\tif (pShader-&gt;l_uHalfVector &gt;= 0)\n\t\tglUniform3fv(pShader-&gt;l_uHalfVector, 1, (const GLfloat*)halfVector);\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\tif (pShader-&gt;l_uTex1mask &gt;= 0) {\n\t\tint textureId = Texture::getGLid(pMt-&gt;uTex1mask);\n\t\t\/\/pass textureId to shader program\n\t\tglActiveTexture(GL_TEXTURE1); \/\/ 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 1.    \n\t\tglUniform1i(pShader-&gt;l_uTex1mask, 1);\n\t}\n\tif (pShader-&gt;l_uTex2nm &gt;= 0) {\n\t\tint textureId = Texture::getGLid(pMt-&gt;uTex2nm);\n\t\t\/\/pass textureId to shader program\n\t\tglActiveTexture(GL_TEXTURE2); \/\/ 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 2.    \n\t\tglUniform1i(pShader-&gt;l_uTex2nm, 2);\n\t}\n\tif (pShader-&gt;l_uTex0translateChannelN &gt;= 0) {\n\t\tglUniform1i(pShader-&gt;l_uTex0translateChannelN, pMt-&gt;uTex0translateChannelN);\n\t\tif (pShader-&gt;l_uTex3 &gt;= 0 &amp;&amp; pMt-&gt;uTex3 &gt;= 0) {\n\t\t\tint textureId = Texture::getGLid(pMt-&gt;uTex3);\n\t\t\t\/\/pass textureId to shader program\n\t\t\tglActiveTexture(GL_TEXTURE3); \/\/ activate the texture unit first before binding texture\n\t\t\tglBindTexture(GL_TEXTURE_2D, textureId);\n\t\t\t\/\/ Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 3.    \n\t\t\tglUniform1i(pShader-&gt;l_uTex3, 3);\n\t\t}\n\t}\n\t\/\/material uniforms\n\tif (pShader-&gt;l_uTex1alphaChannelN &gt;= 0)\n\t\tglUniform1i(pShader-&gt;l_uTex1alphaChannelN, pMt-&gt;uTex1alphaChannelN);\n\tif (pShader-&gt;l_uTex1alphaNegative &gt;= 0)\n\t\tglUniform1i(pShader-&gt;l_uTex1alphaNegative, pMt-&gt;uTex1alphaNegative);\n\tif (pShader-&gt;l_uColor &gt;= 0)\n\t\tglUniform4fv(pShader-&gt;l_uColor, 1, pMt-&gt;uColor.forGL());\n\tif (pShader-&gt;l_uAlphaFactor &gt;= 0)\n\t\tglUniform1f(pShader-&gt;l_uAlphaFactor, pMt-&gt;uAlphaFactor);\n\tif (pShader-&gt;l_uAlphaBlending &gt;= 0)\n\t\tglUniform1i(pShader-&gt;l_uAlphaBlending, pMt-&gt;uAlphaBlending);\n\tif (pShader-&gt;l_uAmbient &gt;= 0)\n\t\tglUniform1f(pShader-&gt;l_uAmbient, pMt-&gt;uAmbient);\n\tif (pShader-&gt;l_uSpecularIntencity &gt;= 0)\n\t\tglUniform1f(pShader-&gt;l_uSpecularIntencity, pMt-&gt;uSpecularIntencity);\n\tif (pShader-&gt;l_uSpecularMinDot &gt;= 0)\n\t\tglUniform1f(pShader-&gt;l_uSpecularMinDot, pMt-&gt;uSpecularMinDot);\n\tif (pShader-&gt;l_uSpecularPowerOf &gt;= 0)\n\t\tglUniform1f(pShader-&gt;l_uSpecularPowerOf, pMt-&gt;uSpecularPowerOf);\n\n\t\/\/adjust render settings\n\tif (pShader-&gt;l_uAlphaBlending &gt;= 0 &amp;&amp; pMt-&gt;uAlphaBlending &gt; 0) {\n\t\tglEnable(GL_BLEND);\n\t\tglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n\t}\n\telse\n\t\tglDisable(GL_BLEND);\n\n\t\/\/execute\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}\nint DrawJob::setDesirableOffsetsForSingleVBO(DrawJob* pDJ, int* pStride, int shaderN, int VBOid) {\n\t\/\/sets desirable offsets and stride according to given shader needs\n\t\/\/assuming that we have 1 single VBO\n\tShader* pSh = Shader::shaders.at(shaderN);\n\tint stride = 0;\n\tpDJ-&gt;aPos.offset = 0; \/\/attribute o_aPos, always 0\n\tstride += sizeof(float) * 3; \/\/aPos size - 3 floats (x,y,z)\n\tif (pSh-&gt;l_aNormal &gt;= 0) { \/\/attribute normal\n\t\tpDJ-&gt;aNormal.offset = stride;\n\t\tstride += sizeof(float) * 3;\n\t}\n\tif (pSh-&gt;l_aTuv &gt;= 0) { \/\/attribute TUV (texture coordinates)\n\t\tpDJ-&gt;aTuv.offset = stride; \/\/attribute TUV (texture coordinates)\n\t\tstride += sizeof(float) * 2;\n\t}\n\tif (pSh-&gt;l_aTuv2 &gt;= 0) { \/\/for normal map\n\t\tpDJ-&gt;aTuv2.offset = stride;\n\t\tstride += sizeof(float) * 2;\n\t}\n\tif (pSh-&gt;l_aTangent &gt;= 0) { \/\/for normal map\n\t\tpDJ-&gt;aTangent.offset = stride;\n\t\tstride += sizeof(float) * 3;\n\t}\n\tif (pSh-&gt;l_aBinormal &gt;= 0) { \/\/for normal map\n\t\tpDJ-&gt;aBinormal.offset = stride;\n\t\tstride += sizeof(float) * 3;\n\t}\n\t*pStride = stride;\n\t\/\/add stride and VBOid to all attributes\n\tAttribRef* pAR = NULL;\n\tpAR = &amp;pDJ-&gt;aPos; pAR-&gt;glVBOid = VBOid; pAR-&gt;stride = stride;\n\tpAR = &amp;pDJ-&gt;aNormal; pAR-&gt;glVBOid = VBOid; pAR-&gt;stride = stride;\n\tpAR = &amp;pDJ-&gt;aTuv; pAR-&gt;glVBOid = VBOid; pAR-&gt;stride = stride;\n\tpAR = &amp;pDJ-&gt;aTuv2; pAR-&gt;glVBOid = VBOid; pAR-&gt;stride = stride;\n\tpAR = &amp;pDJ-&gt;aTangent; pAR-&gt;glVBOid = VBOid; pAR-&gt;stride = stride;\n\tpAR = &amp;pDJ-&gt;aBinormal; pAR-&gt;glVBOid = VBOid; pAR-&gt;stride = stride;\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>Material related parameters\/uniforms we&#8217;ll keep in <em>Material.h<\/em>.<\/p>\n\n\n\n<p>8. Open <em>Material.h<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [20,21,22,23,24]; title: ; notranslate\" title=\"\">\n #pragma once\n#include &quot;MyColor.h&quot;\n\nclass Material\n{\npublic:\n\tint shaderN = -1;\n\tint primitiveType = GL_TRIANGLES;\n\tMyColor uColor;\n\tint uTex0 = -1;\n\tint uTex1mask = -1;\n\tint uTex2nm = -1;\n\tint uTex3 = -1;\n\tint uTex1alphaChannelN = 3; \/\/default - alpha channel for mask\n\tint uTex1alphaNegative = 0; \/\/default - alpha channel not negative\n\tint uTex0translateChannelN = -1; \/\/translate tex0 to tex3 by channelN. Default -1 - don&#039;t translate\n\n\tint uAlphaBlending = 0; \/\/for semi-transparency\n\tfloat uAlphaFactor = 1; \/\/for semi-transparency\n\tfloat uAmbient = 0.4f; \/\/ambient light\n\t\/\/specular light parameters\n\tfloat uSpecularIntencity = 0.8f;\n\tfloat uSpecularMinDot = 0.95f;\n\tfloat uSpecularPowerOf = 20.0f;\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>ModelBuilder::buildSingleDrawJob()<\/em> we need to add new attributes.<\/p>\n\n\n\n<p>9. Open <em>ModelBuilder.cpp<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [321,322]; title: ; notranslate\" title=\"\">\n#include &quot;ModelBuilder.h&quot;\n#include &quot;platform.h&quot;\n#include &quot;utils.h&quot;\n#include &quot;DrawJob.h&quot;\n#include &quot;Shader.h&quot;\n\nextern float degrees2radians;\n\nModelBuilder::~ModelBuilder() {\n\t\/\/clear all vectors\n\tint itemsN = vertices.size();\n\tfor (int i = 0; i &lt; itemsN; i++)\n\t\tdelete vertices.at(i);\n\tvertices.clear();\n\n\titemsN = triangles.size();\n\tfor (int i = 0; i &lt; itemsN; i++)\n\t\tdelete triangles.at(i);\n\ttriangles.clear();\n\n\titemsN = vShapesStack.size();\n\tfor (int i = 0; i &lt; itemsN; i++)\n\t\tdelete vShapesStack.at(i);\n\tvShapesStack.clear();\n\n\titemsN = groupsStack.size();\n\tfor (int i = 0; i &lt; itemsN; i++)\n\t\tdelete groupsStack.at(i);\n\tgroupsStack.clear();\n\n\titemsN = materialsList.size();\n\tfor (int i = 0; i &lt; itemsN; i++)\n\t\tdelete materialsList.at(i);\n\tmaterialsList.clear();\n\n\tsubjNumbersList.clear();\n}\nint ModelBuilder::useSubjN(ModelBuilder* pMB, int subjN) {\n\tpMB-&gt;usingSubjN = subjN;\n\tint itemsN = pMB-&gt;subjNumbersList.size();\n\tbool newN = true;\n\tif (itemsN &gt; 0)\n\t\tfor (int i = 0; i &lt; itemsN; i++)\n\t\t\tif (pMB-&gt;subjNumbersList.at(i) == subjN) {\n\t\t\t\tnewN = false;\n\t\t\t\tbreak;\n\t\t\t}\n\tif (newN)\n\t\tpMB-&gt;subjNumbersList.push_back(subjN);\n\treturn subjN;\n}\nint ModelBuilder::useMaterial(ModelBuilder* pMB, Material* pMT) {\n\tint itemsN = pMB-&gt;materialsList.size();\n\tif (itemsN &gt; 0)\n\t\tfor (int i = 0; i &lt; itemsN; i++)\n\t\t\tif (memcmp(pMB-&gt;materialsList.at(i), pMT, sizeof(Material)) == 0) {\n\t\t\t\tpMB-&gt;usingMaterialN = i;\n\t\t\t\treturn i;\n\t\t\t}\n\t\/\/if here - add new material to the list\n\tpMB-&gt;usingMaterialN = itemsN;\n\t\/\/create a copy of new Material and add to the list\n\tMaterial* pMTnew = new Material(*pMT);\n\tpMB-&gt;materialsList.push_back(pMTnew);\n\treturn itemsN;\n}\nint ModelBuilder::buildBoxFace(ModelBuilder* pMB, std::string applyTo, VirtualShape* pVS) {\n\t\/\/this code is for simple box\n\tVirtualShape vs; \/\/face VS, \n\tmat4x4 transformMatrix = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };\n\t\/\/rotate desirable side to face us. \n\tif (applyTo.find(&quot;front&quot;) == 0) {\n\t\t\/\/Side &lt;front&gt; is facing us as is.\n\t\tvs.whl&#91;0] = pVS-&gt;whl&#91;0];\n\t\tvs.whl&#91;1] = pVS-&gt;whl&#91;1];\n\t\t\/\/define how to move\/place generated face back to the VirtualShape\n\t\t\/\/just shift closer to us by length\/2\n\t\tmat4x4_translate(transformMatrix, 0, 0, pVS-&gt;whl&#91;2] \/ 2);\n\t}\n\telse if (applyTo.find(&quot;back&quot;) == 0) {\n\t\tvs.whl&#91;0] = pVS-&gt;whl&#91;0];\n\t\tvs.whl&#91;1] = pVS-&gt;whl&#91;1];\n\t\t\/\/rotate 180 degrees around Y and shift farther from us by half-length\n\t\tmat4x4_translate(transformMatrix, 0, 0, -pVS-&gt;whl&#91;2] \/ 2);\n\t\tmat4x4_rotate_Y(transformMatrix, transformMatrix, degrees2radians * 180);\n\t}\n\telse if (applyTo.find(&quot;left&quot;) == 0) {\n\t\tvs.whl&#91;0] = pVS-&gt;whl&#91;2]; \/\/width = original length\n\t\tvs.whl&#91;1] = pVS-&gt;whl&#91;1];\n\t\t\/\/rotate -90 degrees around Y (CW) and shift half-width to the left\n\t\tmat4x4_translate(transformMatrix, -pVS-&gt;whl&#91;0] \/ 2, 0, 0);\n\t\tmat4x4_rotate_Y(transformMatrix, transformMatrix, -degrees2radians * 90);\n\t}\n\telse if (applyTo.find(&quot;right&quot;) == 0) {\n\t\tvs.whl&#91;0] = pVS-&gt;whl&#91;2]; \/\/width = original length\n\t\tvs.whl&#91;1] = pVS-&gt;whl&#91;1];\n\t\t\/\/rotate +90 degrees around Y (CCW) and shift half-width to the right\n\t\tmat4x4_translate(transformMatrix, pVS-&gt;whl&#91;0] \/ 2, 0, 0);\n\t\tmat4x4_rotate_Y(transformMatrix, transformMatrix, degrees2radians * 90);\n\t}\n\telse if (applyTo.find(&quot;top&quot;) == 0) {\n\t\tvs.whl&#91;0] = pVS-&gt;whl&#91;0];\n\t\tvs.whl&#91;1] = pVS-&gt;whl&#91;2]; \/\/height = original length\n\t\t\/\/rotate -90 degrees around X (CW) and 180 around Y, and shift half-height up\n\t\tmat4x4_translate(transformMatrix, 0, pVS-&gt;whl&#91;1] \/ 2, 0);\n\t\tmat4x4_rotate_Y(transformMatrix, transformMatrix, -degrees2radians * 180);\n\t\tmat4x4_rotate_X(transformMatrix, transformMatrix, -degrees2radians * 90);\n\t}\n\telse if (applyTo.find(&quot;bottom&quot;) == 0) {\n\t\tvs.whl&#91;0] = pVS-&gt;whl&#91;0];\n\t\tvs.whl&#91;1] = pVS-&gt;whl&#91;2]; \/\/height = original length\n\t\t\/\/rotate 90 around X (CCW) and shift half-height down\n\t\tmat4x4_translate(transformMatrix, 0, -pVS-&gt;whl&#91;1] \/ 2, 0);\n\t\tmat4x4_rotate_X(transformMatrix, transformMatrix, degrees2radians * 90);\n\t}\n\tstartGroup(pMB);\n\t\/\/create vertices\n\tint sectionsX = 1;\n\tint sectionsY = 1;\n\tint pointsX = sectionsX + 1;\n\tint pointsY = sectionsY + 1;\n\tfloat stepX = vs.whl&#91;0] \/ sectionsX;\n\tfloat stepY = vs.whl&#91;1] \/ sectionsY;\n\tfloat kY = vs.whl&#91;1] \/ 2;\n\tfor (int iy = 0; iy &lt; pointsY; iy++) {\n\t\tfloat kX = -vs.whl&#91;0] \/ 2;\n\t\tfor (int ix = 0; ix &lt; pointsX; ix++) {\n\t\t\tint nSE = addVertex(pMB, kX, kY, 0, 0, 0, 1); \/\/vertex number on south-east\n\t\t\tif (iy &gt; 0 &amp;&amp; ix &gt; 0) {\n\t\t\t\t\/\/add 2 triangles\n\t\t\t\tint nSW = nSE - 1; \/\/vertex number south-west\n\t\t\t\tint nNE = nSE - pointsX; \/\/north-east\n\t\t\t\tint nNW = nSW - pointsX; \/\/north-west\n\t\t\t\tadd2triangles(pMB, nNW, nNE, nSW, nSE, iy + ix);\n\t\t\t}\n\t\t\tkX += stepX;\n\t\t}\n\t\tkY -= stepY;\n\t}\n\t\/\/move face to it&#039;s place (apply transform matrix)\n\tint vertsN = pMB-&gt;vertices.size();\n\tfor (int i = pMB-&gt;pCurrentGroup-&gt;fromVertexN; i &lt; vertsN; i++) {\n\t\tVertex01* pVX = pMB-&gt;vertices.at(i);\n\t\tmat4x4_mul_vec4plus(pVX-&gt;aPos, transformMatrix, pVX-&gt;aPos, 1);\n\t\tmat4x4_mul_vec4plus(pVX-&gt;aNormal, transformMatrix, pVX-&gt;aNormal, 0);\n\t}\n\tendGroup(pMB);\n\treturn 1;\n}\nint ModelBuilder::add2triangles(ModelBuilder* pMB, int nNW, int nNE, int nSW, int nSE, int n) {\n\t\/\/indexes: NorthWest, NorthEast, SouthWest,SouthEast\n\tif (n % 2 == 0) { \/\/even number\n\t\taddTriangle(pMB, nNW, nSW, nNE);\n\t\taddTriangle(pMB, nNE, nSW, nSE);\n\t}\n\telse { \/\/odd number\n\t\taddTriangle(pMB, nNW, nSE, nNE);\n\t\taddTriangle(pMB, nNW, nSW, nSE);\n\t}\n\treturn pMB-&gt;triangles.size() - 1;\n}\nint ModelBuilder::addTriangle(ModelBuilder* pMB, int i0, int i1, int i2) {\n\tTriangle01* pTR = new Triangle01();\n\tpMB-&gt;triangles.push_back(pTR);\n\tpTR-&gt;idx&#91;0] = i0;\n\tpTR-&gt;idx&#91;1] = i1;\n\tpTR-&gt;idx&#91;2] = i2;\n\tpTR-&gt;subjN = pMB-&gt;usingSubjN;\n\tpTR-&gt;materialN = pMB-&gt;usingMaterialN;\n\treturn pMB-&gt;triangles.size() - 1;\n}\nvoid ModelBuilder::startGroup(ModelBuilder* pMB) {\n\tpMB-&gt;pCurrentGroup = new Group01();\n\tpMB-&gt;groupsStack.push_back(pMB-&gt;pCurrentGroup);\n\tpMB-&gt;pCurrentGroup-&gt;fromVertexN = pMB-&gt;vertices.size();\n\tpMB-&gt;pCurrentGroup-&gt;fromTriangleN = pMB-&gt;triangles.size();\n}\nvoid ModelBuilder::endGroup(ModelBuilder* pMB) {\n\tdelete pMB-&gt;pCurrentGroup;\n\tpMB-&gt;pCurrentGroup = pMB-&gt;groupsStack.back();\n\tpMB-&gt;groupsStack.pop_back();\n}\nint ModelBuilder::addVertex(ModelBuilder* pMB, float kx, float ky, float kz, float nx, float ny, float nz) {\n\tVertex01* pVX = new Vertex01();\n\tpMB-&gt;vertices.push_back(pVX);\n\tpVX-&gt;aPos&#91;0] = kx;\n\tpVX-&gt;aPos&#91;1] = ky;\n\tpVX-&gt;aPos&#91;2] = kz;\n\t\/\/normal\n\tpVX-&gt;aNormal&#91;0] = nx;\n\tpVX-&gt;aNormal&#91;1] = ny;\n\tpVX-&gt;aNormal&#91;2] = nz;\n\tpVX-&gt;subjN = pMB-&gt;usingSubjN;\n\tpVX-&gt;materialN = pMB-&gt;usingMaterialN;\n\treturn pMB-&gt;vertices.size() - 1;\n}\nint ModelBuilder::buildDrawJobs(ModelBuilder* pMB, std::vector&lt;GameSubj*&gt; gameSubjs) {\n\tint totalSubjsN = pMB-&gt;subjNumbersList.size();\n\tif (totalSubjsN &lt; 1) {\n\t\tpMB-&gt;subjNumbersList.push_back(-1);\n\t\ttotalSubjsN = 1;\n\t}\n\tint totalMaterialsN = pMB-&gt;materialsList.size();\n\tif (totalSubjsN &lt; 2 &amp;&amp; totalMaterialsN &lt; 2) {\n\t\t\/\/simple single DrawJob\n\t\tMaterial* pMT = pMB-&gt;materialsList.at(0);\n\t\tGameSubj* pGS = NULL;\n\t\tint gsN = pMB-&gt;subjNumbersList.at(0);\n\t\tif (gsN &gt;= 0)\n\t\t\tpGS = gameSubjs.at(gsN);\n\t\tif (pGS != NULL)\n\t\t\tpGS-&gt;djStartN = DrawJob::drawJobs.size();\n\t\tbuildSingleDrawJob(pMT, pMB-&gt;vertices, pMB-&gt;triangles);\n\t\tif (pGS != NULL)\n\t\t\tpGS-&gt;djTotalN = DrawJob::drawJobs.size() - pGS-&gt;djStartN;\n\t\treturn 1;\n\t}\n\tint totalVertsN = pMB-&gt;vertices.size();\n\tint totalTrianglesN = pMB-&gt;triangles.size();\n\t\/\/clear flags\n\tfor (int vN = 0; vN &lt; totalVertsN; vN++) {\n\t\tVertex01* pVX = pMB-&gt;vertices.at(vN);\n\t\tpVX-&gt;flag = 0;\n\t}\n\tfor (int tN = 0; tN &lt; totalTrianglesN; tN++) {\n\t\tTriangle01* pTR = pMB-&gt;triangles.at(tN);\n\t\tpTR-&gt;flag = 0;\n\t}\n\tint addedDJs = 0;\n\tfor (int sN = 0; sN &lt; totalSubjsN; sN++) {\n\t\tGameSubj* pGS = NULL;\n\t\tint gsN = pMB-&gt;subjNumbersList.at(sN);\n\t\tif (gsN &gt;= 0)\n\t\t\tpGS = gameSubjs.at(gsN);\n\t\tif (pGS != NULL)\n\t\t\tpGS-&gt;djStartN = DrawJob::drawJobs.size();\n\t\tfor (int mtN = 0; mtN &lt; totalMaterialsN; mtN++) {\n\t\t\tMaterial* pMT = pMB-&gt;materialsList.at(mtN);\n\t\t\tstd::vector&lt;Vertex01*&gt; useVertices;\n\t\t\tstd::vector&lt;Triangle01*&gt; useTriangles;\n\t\t\tfor (int vN = 0; vN &lt; totalVertsN; vN++) {\n\t\t\t\tVertex01* pVX = pMB-&gt;vertices.at(vN);\n\t\t\t\tif (pVX-&gt;flag != 0)\n\t\t\t\t\tcontinue;\n\t\t\t\tif (pVX-&gt;subjN != gsN)\n\t\t\t\t\tcontinue;\n\t\t\t\tif (pVX-&gt;materialN != mtN)\n\t\t\t\t\tcontinue;\n\t\t\t\t\/\/if here - make a copy\n\t\t\t\tVertex01* pVX2 = new Vertex01(*pVX);\n\t\t\t\tuseVertices.push_back(pVX2);\n\t\t\t\tpVX2-&gt;altN = vN;\n\t\t\t\tpVX-&gt;flag = 1;\n\t\t\t\tif (pVX-&gt;endOfSequence &gt; 0) {\n\t\t\t\t\trearrangeArraysForDrawJob(pMB, pMB-&gt;vertices, useVertices, useTriangles);\n\t\t\t\t\tbuildSingleDrawJob(pMT, useVertices, useTriangles);\n\t\t\t\t\taddedDJs++;\n\t\t\t\t\t\/\/clear and proceed to next sequence\n\t\t\t\t\tint useVerticesN = useVertices.size();\n\t\t\t\t\tfor (int i = 0; i &lt; useVerticesN; i++)\n\t\t\t\t\t\tdelete useVertices.at(i);\n\t\t\t\t\tuseVertices.clear();\n\t\t\t\t}\n\t\t\t}\n\t\t\tint useVerticesN = useVertices.size();\n\t\t\tif (useVerticesN &lt; 1)\n\t\t\t\tcontinue; \/\/to next material\n\t\t\t\/\/pick triangles\n\t\t\tfor (int tN = 0; tN &lt; totalTrianglesN; tN++) {\n\t\t\t\tTriangle01* pTR = pMB-&gt;triangles.at(tN);\n\t\t\t\tif (pTR-&gt;flag != 0)\n\t\t\t\t\tcontinue;\n\t\t\t\tif (pTR-&gt;subjN != gsN)\n\t\t\t\t\tcontinue;\n\t\t\t\tif (pTR-&gt;materialN != mtN)\n\t\t\t\t\tcontinue;\n\t\t\t\t\/\/if here - make a copy\n\t\t\t\tTriangle01* pTR2 = new Triangle01(*pTR);\n\t\t\t\tuseTriangles.push_back(pTR2);\n\t\t\t\tpTR-&gt;flag = 1;\n\t\t\t}\n\t\t\trearrangeArraysForDrawJob(pMB, pMB-&gt;vertices, useVertices, useTriangles);\n\t\t\tbuildSingleDrawJob(pMT, useVertices, useTriangles);\n\t\t\taddedDJs++;\n\t\t\t\/\/clear all for next material\n\t\t\tfor (int i = 0; i &lt; useVerticesN; i++)\n\t\t\t\tdelete useVertices.at(i);\n\t\t\tuseVertices.clear();\n\t\t\tint useTrianglesN = useTriangles.size();\n\t\t\tfor (int i = 0; i &lt; useTrianglesN; i++)\n\t\t\t\tdelete useTriangles.at(i);\n\t\t\tuseTriangles.clear();\n\t\t}\n\t\tif (pGS != NULL)\n\t\t\tpGS-&gt;djTotalN = DrawJob::drawJobs.size() - pGS-&gt;djStartN;\n\t}\n\treturn addedDJs;\n}\nint ModelBuilder::buildSingleDrawJob(Material* pMT, std::vector&lt;Vertex01*&gt; useVertices, std::vector&lt;Triangle01*&gt; useTriangles) {\n\tint totalVertsN = useVertices.size();\n\tif (totalVertsN &lt; 1)\n\t\treturn 1;\n\tDrawJob* pDJ = new DrawJob();\n\t\/\/copy material to DJ\n\tmemcpy(&amp;pDJ-&gt;mt, pMT, sizeof(Material));\n\t\/\/calculate VBO element size (stride) and variables offsets in VBO\n\tint VBOid = DrawJob::newBufferId();\n\tint stride = 0;\n\tpDJ-&gt;setDesirableOffsets(&amp;stride, pDJ-&gt;mt.shaderN, VBOid);\n\t\/\/create an array for VBO\n\tint bufferSize = totalVertsN * stride;\n\tfloat* vertsBuffer = new float&#91;bufferSize];\n\t\/\/fill vertsBuffer\n\tShader* pSh = Shader::shaders.at(pDJ-&gt;mt.shaderN);\n\tint floatSize = sizeof(float);\n\tfor (int vN = 0; vN &lt; totalVertsN; vN++) {\n\t\tVertex01* pVX = useVertices.at(vN);\n\t\tint idx = vN * stride \/ floatSize;\n\t\t\/\/pick data from vertex and move to the buffer\n\t\tmemcpy(&amp;vertsBuffer&#91;idx + pDJ-&gt;aPos.offset \/ floatSize], pVX-&gt;aPos, 3 * floatSize);\n\t\tif (pSh-&gt;l_aNormal &gt;= 0) \/\/normal\n\t\t\tmemcpy(&amp;vertsBuffer&#91;idx + pDJ-&gt;aNormal.offset \/ floatSize], pVX-&gt;aNormal, 3 * floatSize);\n\t\tif (pSh-&gt;l_aTuv &gt;= 0) \/\/attribute TUV (texture coordinates)\n\t\t\tmemcpy(&amp;vertsBuffer&#91;idx + pDJ-&gt;aTuv.offset \/ floatSize], pVX-&gt;aTuv, 2 * floatSize);\n\t\tif (pSh-&gt;l_aTuv2 &gt;= 0) \/\/attribute TUV2 (normal maps)\n\t\t\tmemcpy(&amp;vertsBuffer&#91;idx + pDJ-&gt;aTuv2.offset \/ floatSize], pVX-&gt;aTuv2, 2 * floatSize);\n\t\tif (pSh-&gt;l_aTangent &gt;= 0)\n\t\t\tmemcpy(&amp;vertsBuffer&#91;idx + pDJ-&gt;aTangent.offset \/ floatSize], pVX-&gt;aTangent, 3 * floatSize);\n\t\tif (pSh-&gt;l_aBinormal &gt;= 0)\n\t\t\tmemcpy(&amp;vertsBuffer&#91;idx + pDJ-&gt;aBinormal.offset \/ floatSize], pVX-&gt;aBinormal, 3 * floatSize);\n\t}\n\t\/\/buffer is ready, create VBO\n\tglBindBuffer(GL_ARRAY_BUFFER, VBOid);\n\tglBufferData(GL_ARRAY_BUFFER, bufferSize * floatSize, vertsBuffer, GL_STATIC_DRAW);\n\tdelete&#91;] vertsBuffer;\n\tpDJ-&gt;pointsN = totalVertsN;\n\n\tint totalTrianglesN = useTriangles.size();\n\tif (totalTrianglesN &gt; 0) {\n\t\t\/\/create EBO\n\t\tint totalIndexesN = totalTrianglesN * 3;\n\t\t\/\/create buffer\n\t\tGLushort* indexBuffer = new GLushort&#91;totalIndexesN];\n\t\tfor (int tN = 0; tN &lt; totalTrianglesN; tN++) {\n\t\t\tTriangle01* pTR = useTriangles&#91;tN];\n\t\t\tint idx = tN * 3;\n\t\t\tindexBuffer&#91;idx + 0] = (GLushort)pTR-&gt;idx&#91;0];\n\t\t\tindexBuffer&#91;idx + 1] = (GLushort)pTR-&gt;idx&#91;1];\n\t\t\tindexBuffer&#91;idx + 2] = (GLushort)pTR-&gt;idx&#91;2];\n\t\t}\n\t\t\/\/buffer is ready, create IBO\n\t\tpDJ-&gt;glEBOid = DrawJob::newBufferId();\n\t\tglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, pDJ-&gt;glEBOid);\n\t\tglBufferData(GL_ELEMENT_ARRAY_BUFFER, totalIndexesN * sizeof(GLushort), indexBuffer, GL_STATIC_DRAW);\n\t\tdelete&#91;] indexBuffer;\n\t\tpDJ-&gt;pointsN = totalIndexesN;\n\t}\n\t\/\/create and fill vertex attributes array (VAO)\n\tpDJ-&gt;buildVAO();\n\treturn 1;\n}\nint ModelBuilder::rearrangeArraysForDrawJob(ModelBuilder* pMB, std::vector&lt;Vertex01*&gt; allVertices, std::vector&lt;Vertex01*&gt; useVertices, std::vector&lt;Triangle01*&gt; useTriangles) {\n\tint totalTrianglesN = useTriangles.size();\n\tif (totalTrianglesN &lt; 1)\n\t\treturn 0;\n\tint totalVerticesN = useVertices.size();\n\t\/\/save new vertices order in original vertices array\n\t\/\/since triangles indices refer to original vertices order\n\tfor (int i = 0; i &lt; totalVerticesN; i++) {\n\t\tVertex01* pVX1 = useVertices.at(i);\n\t\tVertex01* pVX0 = allVertices.at(pVX1-&gt;altN);\n\t\tpVX0-&gt;altN = i;\n\t}\n\t\/\/replace triangle original indices by new numbers saved in original vertices altN\n\tfor (int tN = 0; tN &lt; totalTrianglesN; tN++) {\n\t\tTriangle01* pTR = useTriangles.at(tN);\n\t\tfor (int i = 0; i &lt; 3; i++) {\n\t\t\tVertex01* pVX0 = allVertices.at(pTR-&gt;idx&#91;i]);\n\t\t\tpTR-&gt;idx&#91;i] = pVX0-&gt;altN;\n\t\t}\n\t}\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.h<\/em> now we have new variable <em>dirToMainLight<\/em>.<\/p>\n\n\n\n<p>10. Open <em>TheGame.h<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [13]; title: ; notranslate\" title=\"\">\n#pragma once\n#include &lt;vector&gt;\n#include &quot;GameSubj.h&quot;\n#include &quot;Camera.h&quot;\n\nclass TheGame\n{\npublic:\n\tint screenSize&#91;2];\n\tfloat screenRatio;\n\tbool bExitGame;\n\tCamera mainCamera;\n\tfloat dirToMainLight&#91;4] = { 1,1,1,0 };\n\n\t\/\/static arrays (vectors) of active GameSubjs\n\tstatic std::vector&lt;GameSubj*&gt; gameSubjs;\npublic:\n\tint run();\n\tint getReady();\n\tint drawFrame();\n\tint cleanUp();\n\tint onScreenResize(int width, int height);\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::getReady()<\/em> we need to initialize Main Light. Directional single-source light will be quite enough for reasonably realistic picture. Let the light source be behind our left shoulder. So, <em>dirToMainLight <\/em>will be normalized {-1,1,1}.<\/p>\n\n\n\n<p>In <em>TheGame::drawFrame()<\/em> we will calculate <em>uHalfVector <\/em>as a direction between <em>dirToMainLight <\/em>and camera view direction.<\/p>\n\n\n\n<p>In the main loop we will calculate now not only <em>mMVP <\/em>(Model-View-Projection) matrix for 3D coordinates, but ALSO <em>mMV3x3 <\/em>(3&#215;3 Model-View) matrix for normals.<\/p>\n\n\n\n<p>And, of course, when building box model, we&#8217;ll use <em>spN_<strong>phong<\/strong>_ucolor<\/em>.<\/p>\n\n\n\n<p>11. Open <em>TheGame.cpp<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [40,72,73,74,84,85,86,87,88,89,90,91,109,110,111,112,113,114,115,119]; title: ; notranslate\" title=\"\">\n#include &quot;TheGame.h&quot;\n#include &quot;platform.h&quot;\n#include &quot;utils.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#include &quot;ModelBuilder.h&quot;\n\nextern std::string filesRoot;\n\nstd::vector&lt;GameSubj*&gt; TheGame::gameSubjs;\n\nint TheGame::getReady() {\n    bExitGame = false;\n    Shader::loadShaders();\n    glEnable(GL_CULL_FACE);\n\n    \/\/=== create box ========================\n    GameSubj* pGS = new GameSubj();\n    gameSubjs.push_back(pGS);\n\n    pGS-&gt;name.assign(&quot;box1&quot;);\n    pGS-&gt;ownCoords.setPosition(0, 0, 0);\n    pGS-&gt;ownCoords.setDegrees(0, 0, 0);\n    pGS-&gt;ownSpeed.setDegrees(0,1,0);\n\n    ModelBuilder* pMB = new ModelBuilder();\n    pMB-&gt;useSubjN(gameSubjs.size() - 1);\n\n    \/\/define VirtualShape\n    VirtualShape vs;\n    vs.type.assign(&quot;box&quot;);\n    vs.whl&#91;0] = 100;\n    vs.whl&#91;1] = 200;\n    vs.whl&#91;2] = 400;\n\n    Material mt;\n    \/\/define material - flat red\n    mt.shaderN = Shader::spN_phong_ucolor;\n    mt.primitiveType = GL_TRIANGLES;\n    mt.uColor.setRGBA(255, 0, 0,255); \/\/red\n    pMB-&gt;useMaterial(&amp;mt);\n\n    pMB-&gt;buildBoxFace(&quot;front&quot;, &amp;vs);\n    pMB-&gt;buildBoxFace(&quot;back&quot;, &amp;vs);\n    pMB-&gt;buildBoxFace(&quot;top&quot;, &amp;vs);\n    pMB-&gt;buildBoxFace(&quot;bottom&quot;, &amp;vs);\n    pMB-&gt;buildBoxFace(&quot;left&quot;, &amp;vs);\n\n    mt.uColor.setRGBA(0, 0, 255,255); pMB-&gt;useMaterial(&amp;mt); \/\/blue\n    pMB-&gt;buildBoxFace(&quot;right&quot;, &amp;vs);\n\n    pMB-&gt;buildDrawJobs(gameSubjs);\n\n    delete pMB;\n\n    \/\/===== set up camera\n    v3set(mainCamera.ownCoords.pos, 0, 200, 1000); \/\/set position\n    float cameraDir&#91;3];\n    v3set(cameraDir, 0, -200, -1000); \/\/set direction vector\n    float cameraYawDg = v3yawDg(cameraDir);\n    float cameraPitchDg = v3pitchDg(cameraDir);\n    \/\/mylog(&quot;cameraYaw=%f, cameraPitch=%f\\n&quot;, cameraYawDg, cameraPitchDg);\n\n    mainCamera.ownCoords.setDegrees(cameraPitchDg, cameraYawDg, 0);\n    float cameraUp&#91;4] = { 0,1,0,0 }; \/\/y - up\n    mat4x4_mul_vec4plus(cameraUp, *mainCamera.ownCoords.getRotationMatrix(), cameraUp, 0);\n\n    mat4x4_look_at(mainCamera.lookAtMatrix, mainCamera.ownCoords.pos, pGS-&gt;ownCoords.pos, cameraUp);\n\n    \/\/===== set up light\n    v3set(dirToMainLight, -1, 1, 1);\n    vec3_norm(dirToMainLight, dirToMainLight);\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\n    \/\/calculate halfVector\n    float dirToCamera&#91;4] = { 0,0,-1,0 }; \/\/-z\n    mat4x4_mul_vec4plus(dirToCamera, *mainCamera.ownCoords.getRotationMatrix(), dirToCamera, 0);\n\n    float uHalfVector&#91;4] = { 0,0,0,0 };\n    for (int i = 0; i &lt; 3; i++)\n        uHalfVector&#91;i] = (dirToCamera&#91;i] + dirToMainLight&#91;i]) \/ 2;\n    vec3_norm(uHalfVector, uHalfVector);\n\n    mat4x4 mProjection, mViewProjection, mMVP, mMV4x4;\n    \/\/mat4x4_ortho(mProjection, -(float)screenSize&#91;0] \/ 2, (float)screenSize&#91;0] \/ 2, -(float)screenSize&#91;1] \/ 2, (float)screenSize&#91;1] \/ 2, 100.f, 500.f);\n    mat4x4_perspective(mProjection, 3.14f \/ 6.0f, (float)screenSize&#91;0] \/ screenSize&#91;1], 700.f, 1300.f);\n    mat4x4_mul(mViewProjection, mProjection, mainCamera.lookAtMatrix);\n    \/\/mViewProjection&#91;1]&#91;3] = 0; \/\/keystone effect\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, mViewProjection, pGS-&gt;ownModelMatrix);\n        \/\/build Model-View (rotation) matrix for normals\n        mat4x4_mul(mMV4x4, mainCamera.lookAtMatrix, (vec4*)pGS-&gt;ownCoords.getRotationMatrix());\n        \/\/convert to 3x3 matrix\n        float mMV3x3&#91;3]&#91;3];\n        for (int y = 0; y &lt; 3; y++)\n            for (int x = 0; x &lt; 3; x++)\n                mMV3x3&#91;y]&#91;x] = mMV4x4&#91;y]&#91;x];\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, *mMV3x3, dirToMainLight, uHalfVector, NULL);\n        }\n    }\n    mySwapBuffers();\n    return 1;\n}\n\nint TheGame::cleanUp() {\n    int itemsN = gameSubjs.size();\n    \/\/delete all UISubjs\n    for (int i = 0; i &lt; itemsN; i++) {\n        GameSubj* pGS = gameSubjs.at(i);\n        delete pGS;\n    }\n    gameSubjs.clear();\n    \/\/clear all other classes\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>12. Build and run.<\/p>\n\n\n\n<p><strong>Before:<\/strong><\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c18\/00.jpg\"><\/p>\n\n\n\n<p><strong><br>After:<\/strong><\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c19\/00.jpg\"><\/p>\n\n\n\n<p><\/p>\n\n\n\n<iframe loading=\"lazy\" width=\"100%\" height=\"400\" src=\"https:\/\/www.youtube.com\/embed\/8X0gZ9buwiQ?controls=0&amp;autoplay=1&amp;loop=1&amp;playlist=8X0gZ9buwiQ\" title=\"Phong shader\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen=\"\"><\/iframe>\n\n\n\n<p><br>Much better !<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Android<\/h2>\n\n\n\n<p>This time EVERYTHING was done on cross-platform side. Just to make sure:<\/p>\n\n\n\n<p>13. Re-start VS. Open  <em>C:\\CPP\\a997modeler\\p_android\\p_android.sln<\/em>.<\/p>\n\n\n\n<p>Switch on, unlock, plug in, allow.<\/p>\n\n\n\n<p>Rebuild solution.<\/p>\n\n\n\n<p>Run. <\/p>\n\n\n\n<p>Perfect!<\/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\">For more realistic image we&#8217;ll need light and shades, which will require new shaders. We will use Phong shading model. Let&#8217;s start with a uniform color shader. 1. Vertex shader. Copy following code in a Text Editor and save is as a txt file in C:\\CPP\\engine\\dt\\shaders\\phong_ucolor_v.txt 2. Fragment shader. Copy following code in a Text [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":624,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-607","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cross-platform-3d"],"_links":{"self":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/607","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=607"}],"version-history":[{"count":33,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/607\/revisions"}],"predecessor-version":[{"id":1353,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/607\/revisions\/1353"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media\/624"}],"wp:attachment":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media?parent=607"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/categories?post=607"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/tags?post=607"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}