{"id":789,"date":"2021-12-10T02:56:40","date_gmt":"2021-12-10T02:56:40","guid":{"rendered":"https:\/\/writingagame.com\/?p=789"},"modified":"2021-12-10T03:27:55","modified_gmt":"2021-12-10T03:27:55","slug":"chapter-27-shader-groups","status":"publish","type":"post","link":"https:\/\/writingagame.com\/index.php\/2021\/12\/10\/chapter-27-shader-groups\/","title":{"rendered":"Chapter 27. Shader groups"},"content":{"rendered":"\n<p>Here I want to add &#8220;reflection&#8221; and &#8220;over mask&#8221; shaders, each of which will double number of shader-programs. Correspondingly, we&#8217;ll need a function which will load a <strong>group <\/strong>of shaders in 1 shot (for example &#8211; <em>Phong <\/em>or <em>flat <\/em>for different data feeds). Also, we&#8217;ll need a function that will pick particular shader number depending on shader type and provided Material (for example &#8211; <em>flat, ucolor<\/em>, no textures).<\/p>\n\n\n\n<p>The call for loading a shaders group will look like this:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; first-line: 15; title: ; notranslate\" title=\"\">\nloadShadersGroup(&quot;phong&quot;, &quot;PHONG; COLOR | TEXTURE; NONE | OVERMASK&quot;, pFLvertex-&gt;pData, pFLfragment-&gt;pData);\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p>This function supposed to build 4 shader programs with following defines combinations:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>PHONG + COLOR<\/li><li>PHONG + COLOR + OVERMASK<\/li><li>PHONG + TEXTURE<\/li><li>PHONG + TEXTURE + OVERMASK<\/li><\/ul>\n\n\n\n<p><strong>Implementation<\/strong>:<\/p>\n\n\n\n<p>1. Start VS, open&nbsp;<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>It will require new variables and functions in <strong>Shader <\/strong>class.<\/p>\n\n\n\n<p>2. Open <em>Shader.h<\/em> and replace code by<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [11,63]; 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    char shaderType&#91;20] = &quot;&quot;;\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\npublic:\n    static int loadShaders();\n    static int cleanUp();\n    static unsigned int getGLid(int shN) { return shaders.at(shN)-&gt;GLid; };\n    static int shaderErrorCheck(int shaderId, std::string ref);\n    static int programErrorCheck(int programId, std::string ref);\n    static int fillLocations(Shader* pSh);\n\n    static int buildShaderObjectFromFiles(std::string filePathVertexS, std::string filePathFragmentS);\n    static int linkShaderProgramFromFiles(const char* filePathVertexS, const char* filePathFragmentS);\n\tstatic int compileShaderFromFile(const char* filePath, GLenum shaderType);\n\n    static int buildShaderObjectWithDefines(std::string shaderType, std::string definesString, char* sourceVertex, char* sourceFragment);\n    static int linkShaderProgramWithDefines(std::string definesString, char* sourceVertex, char* sourceFragment);\n    static int compileShaderWithDefines(std::string definesString, char* shaderSource, GLenum shaderType);\n\n    static int loadShadersGroup(std::string shaderType, std::string optionsString, char* sourceVertex, char* sourceFragment);\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>3. Open <em>Shader.cpp<\/em> and replace code by <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [14,15,16,222]; title: ; notranslate\" title=\"\">\n#include &quot;Shader.h&quot;\n#include &quot;platform.h&quot;\n#include &quot;utils.h&quot;\n#include &quot;FileLoader.h&quot;\n\nextern std::string filesRoot;\n\n\/\/static array (vector) of all loaded shaders\nstd::vector&lt;Shader*&gt; Shader::shaders;\n\nint Shader::loadShaders() {\n    FileLoader* pFLvertex = new FileLoader(&quot;\/dt\/shaders\/phong_v.txt&quot;);\n    FileLoader* pFLfragment = new FileLoader(&quot;\/dt\/shaders\/phong_f.txt&quot;);\n    loadShadersGroup(&quot;flat&quot;, &quot;FLAT; COLOR | TEXTURE; NONE | OVERMASK&quot;, pFLvertex-&gt;pData, pFLfragment-&gt;pData);\n    loadShadersGroup(&quot;phong&quot;, &quot;PHONG; COLOR | TEXTURE; NONE | OVERMASK&quot;, pFLvertex-&gt;pData, pFLfragment-&gt;pData);\n    loadShadersGroup(&quot;mirror&quot;, &quot;PHONG;MIRROR; NONE | OVERMASK&quot;, pFLvertex-&gt;pData, pFLfragment-&gt;pData);\n    delete pFLvertex;\n    delete pFLfragment;\n    return 1;\n}\nint Shader::buildShaderObjectFromFiles(std::string filePathVertexS, std::string filePathFragmentS) {\n    \/\/create shader object\n    Shader* pSh = new Shader();\n    shaders.push_back(pSh);\n    pSh-&gt;GLid = linkShaderProgramFromFiles((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::compileShaderFromFile(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::linkShaderProgramFromFiles(const char* filePathVertexS, const char* filePathFragmentS) {\n    int vertexShaderId = compileShaderFromFile(filePathVertexS, GL_VERTEX_SHADER);\n    int fragmentShaderId = compileShaderFromFile(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\nint Shader::buildShaderObjectWithDefines(std::string shaderType, std::string definesString, char* sourceVertex, char* sourceFragment) {\n    \/\/create shader object\n    Shader* pSh = new Shader();\n    shaders.push_back(pSh);\n    myStrcpy_s(pSh-&gt;shaderType, 20, shaderType.c_str());\n\n    pSh-&gt;GLid = linkShaderProgramWithDefines(definesString, sourceVertex, sourceFragment);\n    \/\/common variables. If not presented, = -1;\n    fillLocations(pSh);\n\n    return (shaders.size() - 1);\n}\nint Shader::linkShaderProgramWithDefines(std::string definesString00, char* sourceVertex, char* sourceFragment) {\n    \/\/build extended definesString\n    bool bUSE_NORMALS = false;\n    bool bUSE_TEX0 = false;\n    bool bUSE_TUV0 = false;\n    if (definesString00.find(&quot; PHONG\\n&quot;) != std::string::npos)\n        bUSE_NORMALS = true;\n    if (definesString00.find(&quot; TEXTURE\\n&quot;) != std::string::npos) {\n        bUSE_TEX0 = true;\n        bUSE_TUV0 = true;\n    }\n    if (definesString00.find(&quot; MIRROR\\n&quot;) != std::string::npos) {\n        bUSE_NORMALS = true;\n        bUSE_TEX0 = true;\n    }\n    if (definesString00.find(&quot; OVERMASK\\n&quot;) != std::string::npos) {\n        bUSE_TUV0 = true;\n    }\n    std::string definesString;\n    definesString.assign(&quot;#version 320 es\\n&quot;);\n    definesString.append(definesString00);\n    if (bUSE_NORMALS)\n        definesString.append(&quot;#define USE_NORMALS\\n&quot;);\n    if (bUSE_TEX0)\n        definesString.append(&quot;#define USE_TEX0\\n&quot;);\n    if (bUSE_TUV0)\n        definesString.append(&quot;#define USE_TUV0\\n&quot;);\n\n    int vertexShaderId = compileShaderWithDefines(definesString, sourceVertex, GL_VERTEX_SHADER);\n    int fragmentShaderId = compileShaderWithDefines(definesString, sourceFragment, GL_FRAGMENT_SHADER);\n\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    \/\/mylog(&quot;linking program\\n%s\\n&quot;, definesString.c_str());\n    return programId;\n}\nint Shader::compileShaderWithDefines(std::string definesString, char* shaderSource, GLenum shaderType) {\n    int shaderId = glCreateShader(shaderType);\n    if (definesString.empty())\n        glShaderSource(shaderId, 1, (const GLchar**)&amp;shaderSource, NULL);\n    else { \/\/2 strings\n        const char* sourceStrings&#91;2];\n        sourceStrings&#91;0] = definesString.c_str();\n        sourceStrings&#91;1] = shaderSource;\n        \/\/ source code loaded, compile\n        glShaderSource(shaderId, 2, (const GLchar**)sourceStrings, NULL);\n    }\n    \/\/myglErrorCheck(&quot;glShaderSource&quot;);\n    glCompileShader(shaderId);\n    if (shaderErrorCheck(shaderId, &quot;glCompileShader&quot;) &lt; 0) {\n        mylog(&quot;ERROR in compileShader,\\n%s\\n%s\\n&quot;, definesString.c_str(), shaderSource);\n        return -1;\n    }\n    return shaderId;\n}\n\nint Shader::loadShadersGroup(std::string shaderType, std::string optionsString, char* sourceVertex, char* sourceFragment) {\n    struct Terms {\n        std::vector&lt;std::string&gt; terms;\n        int totalN = 0;\n        int currentN = 0;\n    };\n    std::vector&lt;Terms*&gt; terms;\n    std::vector&lt;std::string&gt; termGroups = splitString(optionsString, &quot;;&quot;);\n    int groupsN = termGroups.size();\n    for (int groupN = 0; groupN &lt; groupsN; groupN++) {\n        Terms* pTerms = new Terms();\n        terms.push_back(pTerms);\n        pTerms-&gt;terms = splitString(termGroups.at(groupN), &quot;|&quot;);\n        pTerms-&gt;totalN = pTerms-&gt;terms.size();\n    }\n    while (1) {\n        std::string definesString = &quot;&quot;;\n        for (int groupN = 0; groupN &lt; groupsN; groupN++) {\n            Terms* pTerms = terms.at(groupN);\n            std::string term = pTerms-&gt;terms.at(pTerms-&gt;currentN);\n            if (term.compare(&quot;NONE&quot;) != 0) {\n                definesString.append(&quot;#define &quot;);\n                definesString.append(term);\n                definesString.append(&quot;\\n&quot;);\n            }\n        }\n        int shaderObjN = buildShaderObjectWithDefines(shaderType, definesString, sourceVertex, sourceFragment);\n        \/\/go to next terms combo\n        bool noMoreOptions = false;\n        for (int groupN = groupsN - 1; groupN &gt;= 0; groupN--) {\n            Terms* pTerms = terms.at(groupN);\n            if (pTerms-&gt;currentN &lt; pTerms-&gt;totalN - 1) {\n                pTerms-&gt;currentN++;\n                break;\n            }\n            else { \/\/ the level exhausted\n                pTerms-&gt;currentN = 0;\n                \/\/proceed to upper level\n                if (groupN == 0) {\n                    noMoreOptions = true;\n                    break;\n                }\n            }\n        }\n        if (noMoreOptions)\n            break;\n    }\n    return 1;\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Please note: in 3 calls we are generating TEN shader programs out of just ONE single shaders pair.<\/li><\/ul>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>Now &#8211; shaders themselves.<\/p>\n\n\n\n<p>4. <strong>Vertex shader<\/strong>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [28,29,30,31]; 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)\n\nin vec3 aPos; \/\/ position attribute (x,y,z)\n#if defined(USE_NORMALS)\n\tin vec3 aNormal; \/\/ normal attribute (x,y,z)\n\tout vec3 vNormal; \/\/ varying normal (to pass to fragment shader)\n#endif\n#if defined(USE_TUV0)\n\tin vec2 aTuv; \/\/attribute TUV (texture coordinates)\n\tout vec2 vTuv; \/\/varying TUV (pass to fragment shader)\n#endif\n#if defined(MIRROR)\n\tout vec2 vTuvMirror; \/\/varying TUV (pass to fragment shader)\n#endif\n\nvoid main(void) { \n\tgl_Position = uMVP * vec4(aPos, 1.0);\n#if defined(USE_NORMALS)\t\n\t\/\/ Transform the normal&#039;s orientation into eye space. \n\tvNormal = uMV3x3 * aNormal;\t\n#endif\n#if defined(USE_TUV0)\n\tvTuv = aTuv;\n#endif\n#if defined(MIRROR)\n\tvTuvMirror&#91;0] =  (gl_Position&#91;0]\/gl_Position&#91;3]*0.1+vNormal&#91;0]*0.4)+0.5;\n\tvTuvMirror&#91;1] = -(gl_Position&#91;1]\/gl_Position&#91;3]*0.1+vNormal&#91;1]*0.4)+0.5;\n#endif\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p>Copy this code to a <strong>Text Editor <\/strong>and save it as (overwrite)<\/p>\n\n\n\n<p> <em>C:\\CPP\\engine\\dt\\shaders\\phong_<strong>v<\/strong>.txt <\/em><\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Just in case: reflection (lines 28 to 31): we&#8217;ll take a white noise image (from previous chapter) as a texture, then will calculate texture tUV coordinates from vertex screen position and normal&#8217;s orientation. Construction &#8220;gl_Position[0]\/gl_Position[3]&#8221; will give us vertex coordinates in GL -1 to 1 format. Construction in brackets will give a position + orientation combo in -0.5 to 0.5 range. So, <em>vTuvMirror <\/em>&#8211; is a conversion of vertex&#8217;  position\/orientation into texture tUV coordinates.<\/li><\/ul>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c27\/02.jpg\"><\/p>\n\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>5. <strong>Fragment shader<\/strong>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [45,67,68,69,70,71,72]; title: ; notranslate\" title=\"\">\n\/\/#version 320 es\nprecision lowp float;\nout vec4 FragColor; \/\/output pixel color\nuniform float uAlphaFactor; \/\/for semi-transparency\nuniform int uAlphaBlending; \/\/for semi-transparency\n\n#if defined(USE_NORMALS)\n\tin vec3 vNormal; \/\/normal passed from rasterizer\n#endif\n#if defined(USE_TEX0)\n\tuniform sampler2D uTex0;  \/\/texture id\n\tuniform sampler2D uTex3;  \/\/translate texture id\n\tuniform int uTex0translateChannelN;\n#else\n\tuniform vec4 uColor;\n#endif\n#if defined(USE_TUV0)\n\tin vec2 vTuv; \/\/varying TUV (passed from vertex shader)\n#endif\n#if defined(MIRROR)\n\tin vec2 vTuvMirror; \/\/varying TUV (passed from vertex shader)\n#endif\n#if defined(OVERMASK)\n\tuniform sampler2D uTex1mask;  \/\/texture id\n\tuniform int uTex1alphaChannelN;\n\tuniform int uTex1alphaNegative;\n#endif\n\n#if defined(PHONG)\n\tuniform float uAmbient;\n\tuniform float uSpecularIntencity;\n\tuniform float uSpecularMinDot;\n\tuniform float uSpecularPowerOf;\n\n\tuniform vec3 uVectorToLight;\n\tuniform vec3 uHalfVector;\n#endif\n\nvoid main(void) {\n\n\tvec4 outColor;\n\tfloat alpha = 1.0;\n#if defined(OVERMASK)\n\toutColor = texture(uTex1mask, vTuv);\n\talpha = outColor&#91;uTex1alphaChannelN];\n\tif(uTex1alphaNegative &gt; 0)\n\t\talpha = 1.0 - alpha;\n\tif(alpha &lt; 0.5){\n\t\tif(uAlphaBlending &gt; 0){\n\t\t\tif(alpha == 0.0){\n\t\t\t\tdiscard;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\telse{ \/\/no AlphaBlending\n\t\t\tdiscard;\n\t\t\treturn;\n\t\t}\n\t}\n#endif\n#if defined(USE_TEX0)\n\t#if defined(MIRROR)\n\t\toutColor = texture(uTex0, vTuvMirror);\n\t#else\n\t\toutColor = texture(uTex0, vTuv);\n\t#endif\n\tif(uTex0translateChannelN &gt;= 0){ \/\/translate channel\n\t\tvec2 tuv3;\n\t\ttuv3&#91;0] = outColor&#91;uTex0translateChannelN];\n\t\ttuv3&#91;1] = 0.0;\n\t\toutColor = texture(uTex3, tuv3);\n\t}\n\tFragColor = outColor;\n#else\n\tFragColor = uColor;\n#endif\n\tif(FragColor.a != 1.0){\n\t\talpha *= FragColor.a;\n\t\tif(alpha &lt; 0.5){\n\t\t\tif(uAlphaBlending &gt; 0){\n\t\t\t\tif(alpha == 0.0){\n\t\t\t\t\tdiscard;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse{ \/\/no AlphaBlending\n\t\t\t\tdiscard;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n#if defined(USE_NORMALS)\n\tvec3 vNormalNormal = normalize(vNormal);\n#endif\n\n#if defined(PHONG)\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 FragColor *= 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\tFragColor += specularIntencity;\n\t\t}\n\t}\n#endif\n\tif(uAlphaFactor != 1.0)\n\t\talpha *= uAlphaFactor;\t\n\tFragColor.a = alpha;\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p>Copy this code to a <strong>Text Editor <\/strong>and save it as (overwrite)<\/p>\n\n\n\n<p> <em>C:\\CPP\\engine\\dt\\shaders\\phong_<strong>f<\/strong>.txt <\/em><\/p>\n\n\n\n<p><strong>Comments<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Line 45. For transparency map default  <em>uTex1alphaChannelN <\/em>is 3 (Alpha-channel), but can be set to any of 4 RGBA channels.<\/li><li>Lines 67-72. If  <em>uTex0translateChannelN <\/em>is set (>=0), then we&#8217;ll &#8220;translate&#8221; black-and-white channel&#8217;s value to colors defined in 1-dimensional <em>uTex3 <\/em>texture.<\/li><\/ul>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>As for &#8220;translation&#8221; to 1-dimensional\u00a0texture: <\/p>\n\n\n\n<p>For example, gold. In our case it will be 4&#215;1 texture:<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c27\/01.jpg\"><\/p>\n\n\n\n<p>6. In <strong>Windows File Explorer<\/strong> create new folder  <em>C:\\CPP\\engine\\dt\\common\\img<strong>\\materials<\/strong><\/em> <\/p>\n\n\n\nThen <a href=\"https:\/\/writingagame.com\/img\/b01\/c27\/gold01.bmp\" download=\"\">download <b>gold01.bmp<\/b> here<\/a>\n\n\n\n<p><\/p>\n\n\n\n<p>and save it to <em>C:\\CPP\\engine\\dt\\common\\img<strong>\\materials\\gold01.bmp<\/strong><\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>In <em>Material <\/em>class new variable <em>shaderType <\/em>and functions<\/p>\n\n\n\n<p>7. Open <em>Material.h<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [8,29,30,31,32,33,34,35,36]; title: ; notranslate\" title=\"\">\n#pragma once\n#include &quot;MyColor.h&quot;\n#include &lt;string&gt;\n\nclass Material\n{\npublic:\n\tchar shaderType&#91;20] = &quot;&quot;;\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\npublic:\n\tint pickShaderNumber() { return pickShaderNumber(this); };\n\tstatic int pickShaderNumber(Material* pMT);\n\tvoid setShaderType(std::string needType) { setShaderType(this, needType); };\n\tstatic void setShaderType(Material* pMT, std::string needType) { myStrcpy_s(pMT-&gt;shaderType, 20, (char*)needType.c_str()); };\n\tvoid clear() { clear(this); };\n\tstatic void clear(Material* pMT);\n\tint assignShader(std::string needType) { return assignShader(this, needType); };\n\tstatic int assignShader(Material* pMT, std::string needType);\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>8.  Open our empty <em>Material.cpp<\/em> and replace by: <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#include &quot;Material.h&quot;\n#include &quot;Shader.h&quot;\n#include &quot;platform.h&quot;\n\nint Material::pickShaderNumber(Material* pMT) {\n    int shadersN = Shader::shaders.size();\n    for (int i = 0; i &lt; shadersN; i++) {\n        Shader* pSH = Shader::shaders.at(i);\n        if (strcmp(pMT-&gt;shaderType, pSH-&gt;shaderType) != 0)\n            continue;\n        if (pMT-&gt;uColor.isZero() != (pSH-&gt;l_uColor &lt; 0))\n            continue;\n        if ((pMT-&gt;uTex0 &lt; 0) != (pSH-&gt;l_uTex0 &lt; 0))\n            continue;\n        if ((pMT-&gt;uTex1mask &lt; 0) != (pSH-&gt;l_uTex1mask &lt; 0))\n            continue;\n        if ((pMT-&gt;uTex2nm &lt; 0) != (pSH-&gt;l_uTex2nm &lt; 0))\n            continue;\n        pMT-&gt;shaderN = i;\n        return i;\n    }\n    mylog(&quot;ERROR in Material::pickShaderNumber:\\n&quot;);\n    mylog(&quot;Can&#039;t find &#039;%s&#039; shader for uColor=%08x uTex0=%d uTex1mask=%d uTex2nm=%d\\n&quot;,\n        pMT-&gt;shaderType, pMT-&gt;uColor.getUint32(), pMT-&gt;uTex0, pMT-&gt;uTex1mask, pMT-&gt;uTex2nm);\n    return -1;\n}\n\nvoid Material::clear(Material* pMT) {\n    Material mtZero;\n    memcpy(pMT, &amp;mtZero, sizeof(Material));\n}\nint Material::assignShader(Material* pMT, std::string shaderType) {\n    setShaderType(pMT, shaderType);\n    return pickShaderNumber(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><em>ModelBuilder1base.cpp<\/em> now, when building <em>DrawJobs<\/em>, picks shaders programmatically (line 228).<\/p>\n\n\n\n<p>9. Open  <em>ModelBuilder1base.cpp<\/em> and replace code by<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [228]; title: ; notranslate\" title=\"\">\n#include &quot;ModelBuilder1base.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\nModelBuilder1base::~ModelBuilder1base() {\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 ModelBuilder1base::useSubjN(ModelBuilder1base* 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 ModelBuilder1base::useMaterial(ModelBuilder1base* 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 ModelBuilder1base::add2triangles(ModelBuilder1base* 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 ModelBuilder1base::addTriangle(ModelBuilder1base* 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 ModelBuilder1base::lockGroup(ModelBuilder1base* pMB) {\n\tif (pMB-&gt;pCurrentGroup != NULL)\n\t\tpMB-&gt;groupsStack.push_back(pMB-&gt;pCurrentGroup);\n\tpMB-&gt;pCurrentGroup = new Group01();\n\tpMB-&gt;pCurrentGroup-&gt;fromVertexN = pMB-&gt;vertices.size();\n\tpMB-&gt;pCurrentGroup-&gt;fromTriangleN = pMB-&gt;triangles.size();\n}\nvoid ModelBuilder1base::releaseGroup(ModelBuilder1base* pMB) {\n\tdelete pMB-&gt;pCurrentGroup;\n\tif (pMB-&gt;groupsStack.size() &gt; 0) {\n\t\tpMB-&gt;pCurrentGroup = pMB-&gt;groupsStack.back();\n\t\tpMB-&gt;groupsStack.pop_back();\n\t}\n\telse\n\t\tpMB-&gt;pCurrentGroup = NULL;\n}\nint ModelBuilder1base::addVertex(ModelBuilder1base* 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 ModelBuilder1base::buildDrawJobs(ModelBuilder1base* 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}\n\nint ModelBuilder1base::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 0;\n\t\/\/if (pMT-&gt;uTex2nm &gt;= 0)\n\t\/\/\tcalculateTangentSpace(useVertices, useTriangles);\n\tpMT-&gt;pickShaderNumber();\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] = (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}\n\nint ModelBuilder1base::rearrangeArraysForDrawJob(ModelBuilder1base* 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\nint ModelBuilder1base::moveGroupDg(ModelBuilder1base* pMB, float aX, float aY, float aZ, float kX, float kY, float kZ) {\n\t\/\/moves and rotates vertex group\n\t\/\/rotation angles are set in degrees\n\tmat4x4 transformMatrix = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };\n\tmat4x4_translate(transformMatrix, kX, kY, kZ);\n\t\/\/rotation order: Z-X-Y\n\tif (aY != 0) mat4x4_rotate_Y(transformMatrix, transformMatrix, degrees2radians * aY);\n\tif (aX != 0) mat4x4_rotate_X(transformMatrix, transformMatrix, degrees2radians * aX);\n\tif (aZ != 0) mat4x4_rotate_Z(transformMatrix, transformMatrix, degrees2radians * aZ);\n\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\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>And, finally, <em>TheGame.cpp<\/em><\/p>\n\n\n\n<p>10. Open  <em>TheGame.cpp<\/em>  and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [46,47,48,49,50,60,62,63,64]; 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#include &quot;TexCoords.h&quot;\n\nextern std::string filesRoot;\nextern float degrees2radians;\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,3,0);\n\n    ModelBuilder* pMB = new ModelBuilder();\n    pMB-&gt;useSubjN(gameSubjs.size() - 1);\n\n    \/\/define VirtualShape\n    VirtualShape vs;\n    vs.setShapeType(&quot;box-tank&quot;);\n    vs.whl&#91;0] = 60;\n    vs.whl&#91;1] = 160;\n    vs.whl&#91;2] = 390;\n    vs.setExt(20);\n    vs.extD = 0;\n    vs.extF = 0; \/\/to make front face &quot;flat&quot;\n    vs.sectionsR = 2;\n\n    Material mt;\n    \/\/define material - gold\n    mt.setShaderType(&quot;mirror&quot;);\n    mt.uTex0 = Texture::loadTexture(filesRoot + &quot;\/dt\/common\/img\/whitenoise\/wn64_blur3.bmp&quot;); \/\/white noise\n    mt.uTex0translateChannelN = 0;\n    mt.uTex3 = Texture::loadTexture(filesRoot + &quot;\/dt\/common\/img\/materials\/gold01.bmp&quot;);\n    pMB-&gt;useMaterial(&amp;mt);\n\n    pMB-&gt;buildBoxFace(pMB,&quot;front v&quot;, &amp;vs);\n    pMB-&gt;buildBoxFace(pMB, &quot;back v&quot;, &amp;vs);\n    pMB-&gt;buildBoxFace(pMB, &quot;top&quot;, &amp;vs);\n    pMB-&gt;buildBoxFace(pMB, &quot;bottom&quot;, &amp;vs);\n    pMB-&gt;buildBoxFace(pMB, &quot;left all&quot;, &amp;vs);\n\n    \/\/textured surface\n    mt.clear(); \/\/ set to zero;\n    mt.setShaderType(&quot;phong&quot;);\n    mt.uTex0 = Texture::loadTexture(filesRoot + &quot;\/dt\/sample_img.png&quot;); \/\/sample img\n    \/\/transparency map\n    mt.uTex1mask = Texture::loadTexture(filesRoot + &quot;\/dt\/common\/img\/whitenoise\/wn64_blur1.bmp&quot;); \/\/white noise\n    mt.uTex1alphaChannelN = 0;\n    pMB-&gt;useMaterial(&amp;mt);\n    TexCoords tc;\n    tc.set(mt.uTex0, 11, 12, 256, 128, &quot;h&quot;); \/\/flip horizontally\n    pMB-&gt;buildBoxFace(pMB, &quot;right all&quot;, &amp;vs, &amp;tc);\n\n    pMB-&gt;buildDrawJobs(gameSubjs);\n\n    delete pMB;\n\n    \/\/===== set up camera\n    mainCamera.ownCoords.setDegrees(15, 180, 0); \/\/set camera angles\/orientation\n    mainCamera.viewRangeDg = 30;\n    mainCamera.stageSize&#91;0] = 500;\n    mainCamera.stageSize&#91;1] = 375;\n    memcpy(mainCamera.lookAtPoint, pGS-&gt;ownCoords.pos, sizeof(float) * 3);\n    mainCamera.onScreenResize();\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    float nearClip = mainCamera.focusDistance - 250;\n    float farClip = mainCamera.focusDistance + 250;\n    mat4x4_perspective(mProjection, mainCamera.viewRangeDg * degrees2radians, screenAspectRatio, nearClip, farClip);\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    \/\/synchronization\n    while (1) {\n        long long int currentMillis = getSystemMillis();\n        long long int millisSinceLastFrame = currentMillis - lastFrameMillis;\n        if (millisSinceLastFrame &gt;= millisPerFrame) {\n            lastFrameMillis = currentMillis;\n            break;\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    screenAspectRatio = (float)width \/ height;\n    glViewport(0, 0, width, height);\n    mainCamera.onScreenResize();\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<p>Please note: now we set <em>Materials <\/em>a bit differently:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Instead of dealing with shaders numbers, now we are assigning desirable shader type (lines 46 and 60). Specific shader number will be picked later automatically, while building <em>DrawJob<\/em>.<\/li><li>Defining &#8220;gold&#8221; material, lines 46-50: The main texture <em>uTex0 <\/em>&#8211; is a white noise. We&#8217;ll take black-and-white channel #0 and will translate retrieved 0-to-1 <strong>values to colors<\/strong> from 4&#215;1 <em>uTex3<\/em>, which is set to <em>gold01.bmp<\/em>.<\/li><li>Transparency mask, lines 62-64: We are setting  <em>uTex1mask<\/em>  to a white noise image. Setting <em>uTex1alphaChannelN<\/em> is optional. Default value is 3 &#8211; Alpha-channel.<\/li><li>And, of course, we are back to normal <em>run()<\/em>.<\/li><\/ul>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>11. Build and run:<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c27\/00.jpg\"><\/p>\n\n\n\n<iframe loading=\"lazy\" width=\"100%\" height=\"400\" src=\"https:\/\/www.youtube.com\/embed\/eZ5UKOL3plY?controls=0&amp;autoplay=1&amp;loop=1&amp;playlist=eZ5UKOL3plY\" title=\"Shader groups\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen=\"\"><\/iframe>\n\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Black holes on textured side are from <em>Transparency Mask<\/em>.<\/li><\/ul>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>Verified on Android too.<\/p>\n\n\n\n<p>Direct fit!<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p class=\"mb-2\">Here I want to add &#8220;reflection&#8221; and &#8220;over mask&#8221; shaders, each of which will double number of shader-programs. Correspondingly, we&#8217;ll need a function which will load a group of shaders in 1 shot (for example &#8211; Phong or flat for different data feeds). Also, we&#8217;ll need a function that will pick particular shader number depending [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":812,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-789","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\/789","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=789"}],"version-history":[{"count":28,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/789\/revisions"}],"predecessor-version":[{"id":831,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/789\/revisions\/831"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media\/812"}],"wp:attachment":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media?parent=789"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/categories?post=789"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/tags?post=789"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}