{"id":884,"date":"2021-12-12T18:02:23","date_gmt":"2021-12-12T18:02:23","guid":{"rendered":"https:\/\/writingagame.com\/?p=884"},"modified":"2021-12-14T00:19:27","modified_gmt":"2021-12-14T00:19:27","slug":"chapter-29-chroma-key","status":"publish","type":"post","link":"https:\/\/writingagame.com\/index.php\/2021\/12\/12\/chapter-29-chroma-key\/","title":{"rendered":"Chapter 29. Chroma key"},"content":{"rendered":"\n<p><strong>Chroma keying<\/strong>, obviously, is a way of treating certain color as transparent one. In our case &#8211; green:<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c28\/marlboro03small.png\"><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p> Of course, after loading an image, before pushing it to a texture, we can easily scan it for green pixels and set their Alpha-channels to zero. But when GL rasterizer picks <em>tUV <\/em>coordinates near the green areas, resulting screen pixel will be <em>partially <\/em>&#8220;transparent&#8221; and <em>partially <\/em>&#8211; <strong>green<\/strong>. BTW, regardless of transparency. In our sample it looks like a greenish thin lines on projection borders:<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c29\/01.jpg\"><\/p>\n\n\n\n<p>Well, it <strong>is<\/strong> important what colors surround your images.<\/p>\n\n\n\n<p><strong>The cure<\/strong>: when scanning for green pixels, we can set their Alpha-channels to zero AND <strong>also <\/strong>RGB components to nearest non-transparent pixels values. So, resulting picture will be:<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c29\/02.jpg\"><\/p>\n\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Former green areas are <strong>transparent <\/strong>now, <strong>plus <\/strong>their RGB components won&#8217;t affect neighboring pixels any more.<\/li><\/ul>\n\n\n\n<p><strong>Implementation<\/strong>:<\/p>\n\n\n\n<p>1. Start VS, open&nbsp;<em>C:\\CPP\\a997modeler\\p_windows\\p_windows.sln<\/em>.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>In <strong>Texture <\/strong>class we&#8217;ll have a new function, <em>applyCkey(..)<\/em>, and new parameter in <em>loadTexture(..)<\/em>.<\/p>\n\n\n\n<p>2. Open <em>Texture.h<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [21,22]; title: ; notranslate\" title=\"\">\n#pragma once\n#include &lt;string&gt;\n#include &lt;vector&gt;\n#include &quot;platform.h&quot;\n\nclass Texture\n{\npublic:\n    \/\/texture&#039;s individual descriptor:\n    unsigned int GLid = -1; \/\/ GL texture id\n    int size&#91;2] = { 0,0 };  \/\/ image size\n    std::string source; \/\/file name\n    \/\/if renderable ?\n    unsigned int frameBufferId = 0;\n    unsigned int depthBufferId = 0;\n    \/\/end of descriptor\n\n    \/\/static array (vector) of all loaded textures\n    static std::vector&lt;Texture*&gt; textures;\npublic:\n    static int loadTexture(std::string filePath, unsigned int ckey, int glRepeatH = GL_MIRRORED_REPEAT, int glRepeatV = GL_MIRRORED_REPEAT);\n    static int applyCkey(unsigned char* imgData, int w, int h, unsigned int ckey);\n    static int findTexture(std::string filePath);\n    static int cleanUp();\n    static unsigned int getGLid(int texN) { return textures.at(texN)-&gt;GLid; };\n    static int saveBMP(std::string filePath, unsigned char* buff, int w, int h, int bytesPerPixel=4);\n    static int saveTGA(std::string filePath, unsigned char* buff, int w, int h, int bytesPerPixel=4);\n    static int generateTexture(std::string imgID, int w, int h, unsigned char* imgData, int glRepeatH = GL_MIRRORED_REPEAT, int glRepeatV = GL_MIRRORED_REPEAT);\n    static int detachRenderBuffer(Texture* pTex);\n    static int attachRenderBuffer(int texN, bool zBuffer = false) { return attachRenderBuffer(textures.at(texN), zBuffer); };\n    static int attachRenderBuffer(Texture* pTex, bool zBuffer = false);\n    static int setRenderToTexture(int texN) { return setRenderToTexture(textures.at(texN)); };\n    static int setRenderToTexture(Texture* pTex);\n    static int getImageFromTexture(int texN, unsigned char* imgData);\n    static int blurRGBA(unsigned char* imgData, int w, int h, int blurLevel);\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>Texture.<\/em><strong><em>cpp<\/em> <\/strong>and replace code by: <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [11,23,24,303]; title: ; notranslate\" title=\"\">\n#include &quot;Texture.h&quot;\n#define STB_IMAGE_IMPLEMENTATION  \/\/required by stb_image.h\n#include &quot;stb_image.h&quot;\n#include &quot;platform.h&quot;\n#include &quot;utils.h&quot;\n#include &quot;MyColor.h&quot;\n\n\/\/static array (vector) of all loaded textures\nstd::vector&lt;Texture*&gt; Texture::textures;\n\nint Texture::loadTexture(std::string filePath, unsigned int ckey, int glRepeatH, int glRepeatV) {\n    int texN = findTexture(filePath);\n    if (texN &gt;= 0)\n        return texN;\n    \/\/if here - texture wasn&#039;t loaded\n    \/\/ load an image\n    int nrChannels, w, h;\n    unsigned char* imgData = stbi_load(filePath.c_str(),\n        &amp;w, &amp;h, &amp;nrChannels, 4); \/\/&quot;4&quot;-convert to 4 channels -RGBA\n    if (imgData == NULL) {\n        mylog(&quot;ERROR in Texture::loadTexture loading image %s\\n&quot;, filePath.c_str());\n    }\n    if (ckey != 0)\n        applyCkey(imgData, w, h, ckey);\n    \/\/ generate texture\n    generateTexture(filePath, w, h, imgData, glRepeatH, glRepeatV);\n    \/\/ release image data\n    stbi_image_free(imgData);\n\n    return (textures.size() - 1);\n}\nint Texture::findTexture(std::string filePath) {\n    int texturesN = textures.size();\n    if (texturesN &lt; 1)\n        return -1;\n    for (int i = 0; i &lt; texturesN; i++) {\n        Texture* pTex = textures.at(i);\n        if (pTex-&gt;source.compare(filePath) == 0)\n            return i;\n    }\n    return -1;\n}\nint Texture::cleanUp() {\n    int texturesN = textures.size();\n    if (texturesN &lt; 1)\n        return -1;\n    \/\/detach all textures\n    glActiveTexture(GL_TEXTURE0); \/\/ activate the texture unit first before binding texture\n    glBindTexture(GL_TEXTURE_2D, 0);\n    glActiveTexture(GL_TEXTURE1);\n    glBindTexture(GL_TEXTURE_2D, 0);\n    glActiveTexture(GL_TEXTURE2);\n    glBindTexture(GL_TEXTURE_2D, 0);\n    glActiveTexture(GL_TEXTURE3);\n    glBindTexture(GL_TEXTURE_2D, 0);\n    \/\/release all textures\n    for (int i = 0; i &lt; texturesN; i++) {\n        Texture* pTex = textures.at(i);\n        detachRenderBuffer(pTex);\n        glDeleteTextures(1, (GLuint*)&amp;pTex-&gt;GLid);\n        pTex-&gt;GLid = 0;\n        delete pTex;\n    }\n    textures.clear();\n    return 1;\n}\nint Texture::saveBMP(std::string filePath, unsigned char* buff, int w, int h, int bytesPerPixel) {\n    std::string fullPath = getFullPath(filePath);\n    std::string inAppPath = getInAppPath(fullPath);\n    makeDirs(inAppPath);\n    FILE* outFile;\n    myFopen_s(&amp;outFile, fullPath.c_str(), &quot;wb&quot;);\n    if (outFile == NULL) {\n        mylog(&quot;ERROR in Texture::saveBMP: Can&#039;t create file %s\\n&quot;, filePath.c_str());\n        return -1;\n    }\n    struct {\n        char chars2skip&#91;2]; \/\/\n            \/\/BMP Header\n        char bm&#91;2] = { 0x42, 0x4D }; \/\/\t&quot;BM&quot;\n        myUint32 fileSize = 0; \/\/ Size of the BMP file, little-endian\n        myUint32 unused = 0;\n        myUint32 dataOffset = 0; \/\/ Offset where the pixel array (bitmap data) can be found, little-endian\n        \/\/DIB Header\n        myUint32 dibHeaderSize = 0; \/\/ Number of bytes in the DIB header, little-endian\n        myUint32 imgW = 0; \/\/ Width of the bitmap in pixels, little-endian\n        myUint32 imgH = 0; \/\/ Height of the bitmap in pixels, little-endian\n        char colorPlainsN&#91;2] = { 1,0 };\n        char bitsPerPixel&#91;2] = { 32,0 };\n        myUint32 compression = 0; \/\/0-BI_RGB\n        myUint32 dataSize = 0; \/\/ Size of the raw bitmap data (including padding), little-endian\n        myUint32 printResution&#91;2] = { 2835 ,2835 }; \/\/ Print resolution of the image,\n                \/\/72 DPI \u00d7 39.3701 inches per metre yields 2834.6472, little-endian\n        myUint32 paletteColors = 0; \/\/ Number of colors in the palette\n        myUint32 importantColors = 0; \/\/0 means all colors are important\n    } bmpHeader;\n    int rowSize = w * bytesPerPixel;\n    int rowPadding = (4 - rowSize % 4) % 4;\n    int rowSizeWithPadding = rowSize + rowPadding;\n    int dataSize = rowSizeWithPadding * h;\n    int headerSize = sizeof(bmpHeader) - 2; \/\/-chars2skip\n    bmpHeader.fileSize = dataSize + headerSize;\n    bmpHeader.dataOffset = headerSize;\n    bmpHeader.dibHeaderSize = headerSize - 14; \/\/-BMP Header size\n    bmpHeader.imgW = w;\n    bmpHeader.imgH = h;\n    if (bytesPerPixel != 4)\n        bmpHeader.bitsPerPixel&#91;0] = bytesPerPixel * 8;\n    bmpHeader.dataSize = dataSize;\n    fwrite(&amp;bmpHeader.bm, 1, headerSize, outFile);\n    \/\/data, from bottom to top\n    unsigned char zero&#91;4] = { 0,0,0,0 };\n    unsigned char bgra&#91;4];\n    for (int y = h - 1; y &gt;= 0; y--) {\n        for (int x = 0; x &lt; w; x++) {\n            int pixelOffset = y * rowSize + x * 4;\n            bgra&#91;0] = buff&#91;pixelOffset + 2];\n            bgra&#91;1] = buff&#91;pixelOffset + 1];\n            bgra&#91;2] = buff&#91;pixelOffset + 0];\n            bgra&#91;3] = buff&#91;pixelOffset + 3];\n            fwrite(bgra, 1, bytesPerPixel, outFile);\n        }\n        if (rowPadding != 0)\n            fwrite(zero, 1, rowPadding, outFile);\n    }\n    fflush(outFile);\n    fclose(outFile);\n\n    return 1;\n}\n\nint Texture::saveTGA(std::string filePath, unsigned char* buff, int w, int h, int bytesPerPixel) {\n    std::string fullPath = getFullPath(filePath);\n    std::string inAppPath = getInAppPath(fullPath);\n    makeDirs(inAppPath);\n    FILE* outFile;\n    myFopen_s(&amp;outFile, fullPath.c_str(), &quot;wb&quot;);\n    if (outFile == NULL) {\n        mylog(&quot;ERROR in Texture::saveBMP: Can&#039;t create file %s\\n&quot;, filePath.c_str());\n        return -1;\n    }\n    unsigned char tgaHeader&#91;18] = { 0,0,2,0,0,0,0,0,0,0,0,0, (unsigned char)(w % 256), (unsigned char)(w \/ 256),\n        (unsigned char)(h % 256), (unsigned char)(h \/ 256), (unsigned char)(bytesPerPixel * 8), 0x20 };\n    fwrite(tgaHeader, 1, 18, outFile);\n    \/\/data\n    unsigned char bgra&#91;4];\n    for (int i = 0; i &lt; w * h; i++) {\n        int pixelOffset = i * 4;\n        bgra&#91;0] = buff&#91;pixelOffset + 2];\n        bgra&#91;1] = buff&#91;pixelOffset + 1];\n        bgra&#91;2] = buff&#91;pixelOffset + 0];\n        bgra&#91;3] = buff&#91;pixelOffset + 3];\n        fwrite(bgra, 1, bytesPerPixel, outFile);\n    }\n    fflush(outFile);\n    fclose(outFile);\n\n    return 1;\n}\nint Texture::generateTexture(std::string imgID, int w, int h, unsigned char* imgData, int glRepeatH, int glRepeatV) {\n    \/\/glRepeat options: GL_REPEAT, GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT\n    if (!imgID.empty()) {\n        int texN = findTexture(imgID);\n        if (texN &gt;= 0)\n            return texN;\n    }\n    \/\/if here - texture wasn&#039;t generated\n    \/\/create Texture object\n    Texture* pTex = new Texture();\n    textures.push_back(pTex);\n    pTex-&gt;size&#91;0] = w;\n    pTex-&gt;size&#91;1] = h;\n    pTex-&gt;source.assign(imgID);\n    \/\/ generate texture\n    glGenTextures(1, (GLuint*)&amp;pTex-&gt;GLid);\n    glBindTexture(GL_TEXTURE_2D, pTex-&gt;GLid);\n    \/\/ set the texture wrapping\/filtering options (on the currently bound texture object)\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, glRepeatH);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, glRepeatV);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);\/\/ GL_LINEAR);  \/\/\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n    \/\/ attach image data (if provided)\n    if (imgData != NULL) {\n        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pTex-&gt;size&#91;0], pTex-&gt;size&#91;1], 0, GL_RGBA, GL_UNSIGNED_BYTE, imgData);\n        glGenerateMipmap(GL_TEXTURE_2D);\n    }\n    return (textures.size() - 1);\n}\nint Texture::detachRenderBuffer(Texture* pTex) {\n    if (pTex-&gt;frameBufferId == 0)\n        return 0;\n    if (pTex-&gt;depthBufferId &gt; 0) {\n        glDeleteRenderbuffers(1, (GLuint*)&amp;pTex-&gt;depthBufferId);\n        pTex-&gt;depthBufferId = 0;\n    }\n    glDeleteFramebuffers(1, (GLuint*)&amp;pTex-&gt;frameBufferId);\n    pTex-&gt;frameBufferId = 0;\n    return 1;\n}\n\nint Texture::attachRenderBuffer(Texture* pTex, bool zBuffer) {\n    if (pTex-&gt;frameBufferId &gt; 0)\n        return 0; \/\/attached already\n    \/\/generate frame buffer\n    glGenFramebuffers(1, (GLuint*)&amp;pTex-&gt;frameBufferId);\n    if (zBuffer) {\n        \/\/generate depth buffer\n        glGenRenderbuffers(1, (GLuint*)&amp;pTex-&gt;depthBufferId);\n        \/\/ create render buffer and bind 16-bit depth buffer\n        glBindRenderbuffer(GL_RENDERBUFFER, pTex-&gt;depthBufferId);\n        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, pTex-&gt;size&#91;0], pTex-&gt;size&#91;1]);\n        glBindRenderbuffer(GL_RENDERBUFFER, 0); \/\/release\n    }\n    return 1;\n}\nint Texture::setRenderToTexture(Texture* pTex) {\n    if (pTex-&gt;frameBufferId == 0) {\n        mylog(&quot;ERROR in Texture::setRenderToTexture: %s not renderable&quot;, pTex-&gt;source.c_str());\n        return -1;\n    }\n    \/\/ Bind the framebuffer\n    glBindFramebuffer(GL_FRAMEBUFFER, pTex-&gt;frameBufferId);\n    \/\/ specify texture as color attachment\n    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, pTex-&gt;GLid, 0);\n    \/\/ attach render buffer as depth buffer\n    if (pTex-&gt;depthBufferId &gt; 0) {\n        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, pTex-&gt;depthBufferId);\n        glClear(GL_DEPTH_BUFFER_BIT);\n    }\n    \/\/ check status\n    int status = glCheckFramebufferStatus(GL_FRAMEBUFFER);\n    if (status != GL_FRAMEBUFFER_COMPLETE) {\n        std::string str;\n        if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)\n            str.assign(&quot;GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT&quot;);\n        else if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)\n            str.assign(&quot;GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT&quot;);\n        else if (status == GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE)\n            str.assign(&quot;GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE&quot;);\n        else if (status == GL_FRAMEBUFFER_UNSUPPORTED)\n            str.assign(&quot;GL_FRAMEBUFFER_UNSUPPORTED&quot;);\n        else\n            str.assign(&quot;hz&quot;);\n        mylog(&quot;Modeler.setRenderToTextureBind to texture %s failed: %s\\n&quot;, pTex-&gt;source.c_str(), str.c_str());\n        return -1;\n    }\n    glViewport(0, 0, pTex-&gt;size&#91;0], pTex-&gt;size&#91;1]);\n    return 1;\n}\nint Texture::getImageFromTexture(int texN, unsigned char* imgData) {\n    Texture* pTex = textures.at(texN);\n    glBindTexture(GL_TEXTURE_2D, pTex-&gt;GLid);\n    glBindFramebuffer(GL_FRAMEBUFFER, pTex-&gt;frameBufferId);\n\n    glReadPixels(0, 0, pTex-&gt;size&#91;0], pTex-&gt;size&#91;1], GL_RGBA, GL_UNSIGNED_BYTE, imgData);\n    return 1;\n}\nint Texture::blurRGBA(unsigned char* imgData, int w0, int h0, int blurLevel) {\n    unsigned char* imgTemp = new unsigned char&#91;w0 * h0 * 4];\n    int w00 = blurLevel * 2 + 1;\n    for (int y0 = 0; y0 &lt; h0; y0++) {\n        int y1 = y0 - blurLevel;\n        int h1 = w00;\n        if (y1 &lt; 0) {\n            int d = -y1;\n            y1 += d;\n            h1 -= d;\n        }\n        else if (y1 &gt; h0 - w00) {\n            int d = y1 - (h0 - w00);\n            h1 -= d;\n        }\n        for (int x0 = 0; x0 &lt; w0; x0++) {\n            int x1 = x0 - blurLevel;\n            int w1 = w00;\n            if (x1 &lt; 0) {\n                int d = -x1;\n                x1 += d;\n                w1 -= d;\n            }\n            else if (x1 &gt; w0 - w00) {\n                int d = x1 - (w0 - w00);\n                w1 -= d;\n            }\n            int sum&#91;4] = { 0,0,0,0 };\n            for (int y = y1; y &lt; y1 + h1; y++) {\n                for (int x = x1; x &lt; x1 + w1; x++) {\n                    int idx = (y * w0 + x) * 4;\n                    for (int ch = 0; ch &lt; 4; ch++)\n                        sum&#91;ch] += imgData&#91;idx + ch];\n                }\n            }\n            int n = w1 * h1;\n            int idx = (y0 * w0 + x0) * 4;\n            for (int ch = 0; ch &lt; 4; ch++)\n                imgTemp&#91;idx + ch] = (unsigned char)(sum&#91;ch] \/ n);\n        }\n    }\n    memcpy(imgData, imgTemp, w0 * h0 * 4);\n    delete&#91;] imgTemp;\n    return 1;\n}\nint Texture::applyCkey(unsigned char* imgData, int w0, int h0, unsigned int ckey) {\n    if (ckey == 0)\n        return 0;\n    MyColor transparentPixel;\n    transparentPixel.setRGBA(127, 127, 127, 0);\n    unsigned int transparentValue = transparentPixel.getUint32();\n    unsigned int* pIntData = (unsigned int*)imgData;\n    int dataIntsN = w0 * h0;\n    int transparentPixelsN = 0;\n    for (int i = 0; i &lt; dataIntsN; i++) {\n        if (pIntData&#91;i] != ckey)\n            continue;\n        \/\/here - have ckey pixel\n        transparentPixelsN++;\n        pIntData&#91;i] = transparentValue;\n    }\n    if (transparentPixelsN == 0)\n        return 0;\n    \/\/re-calculate transparent RGBs\n    int dataCharsN = dataIntsN * 4;\n    \/\/duplicate image\n    unsigned char* imgTemp = new unsigned char&#91;dataCharsN];\n    memcpy(imgTemp, imgData, dataCharsN);\n    \/\/scan\n    int blurLevel = 1;\n    int w00 = blurLevel * 2 + 1;\n    for (int y0 = 0; y0 &lt; h0; y0++) {\n        int y1 = y0 - blurLevel;\n        int h1 = w00;\n        if (y1 &lt; 0) {\n            int d = -y1;\n            y1 += d;\n            h1 -= d;\n        }\n        else if (y1 &gt; h0 - w00) {\n            int d = y1 - (h0 - w00);\n            h1 -= d;\n        }\n        for (int x0 = 0; x0 &lt; w0; x0++) {\n            if (imgData&#91;(y0 * w0 + x0) * 4 + 3] != 0) \/\/check current pixel&#039;s alpha component\n                continue; \/\/non-transparent pixel\n            int x1 = x0 - blurLevel;\n            int w1 = w00;\n            if (x1 &lt; 0) {\n                int d = -x1;\n                x1 += d;\n                w1 -= d;\n            }\n            else if (x1 &gt; w0 - w00) {\n                int d = x1 - (w0 - w00);\n                w1 -= d;\n            }\n            int sum&#91;3] = { 0,0,0 };\n            int opaquePixelsN = 0;\n            for (int y = y1; y &lt; y1 + h1; y++) {\n                for (int x = x1; x &lt; x1 + w1; x++) {\n                    int idx = (y * w0 + x) * 4;\n                    if (imgTemp&#91;idx + 3] == 0) \/\/check alpha channel\n                        continue; \/\/transparent pixel\n                    opaquePixelsN++;\n                    for (int ch = 0; ch &lt; 3; ch++)\n                        sum&#91;ch] += imgTemp&#91;idx + ch];\n                }\n            }\n            if (opaquePixelsN == 0)\n                continue;\n            int idx = (y0 * w0 + x0) * 4;\n            for (int ch = 0; ch &lt; 3; ch++)\n                imgData&#91;idx + ch] = (unsigned char)(sum&#91;ch] \/ opaquePixelsN);\n        }\n    }\n    delete&#91;] imgTemp;\n    saveTGA(&quot;\/dt\/02.tga&quot;, imgData, w0, h0, 3);\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><em>ModelLoader <\/em>already has the code that will read and execute <em>chroma key<\/em> for a texture. Need to add it to model descriptor.<\/p>\n\n\n\n<p>4. In a <strong>Text Editor<\/strong> open<\/p>\n\n\n\n<p><em>C:\\CPP\\<strong>a997modeler<\/strong>\\dt\\models\\misc\\marlboro01\\<strong>root01.txt<\/strong><\/em><\/p>\n\n\n\n<p>and replace by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: xml; highlight: [1]; title: ; notranslate\" title=\"\">\n&lt;texture_as=&quot;tx0&quot; src=&quot;marlboro03small.png&quot; ckey=&quot;#00ff00&quot;\/&gt;\n&lt;mt_type=&quot;phong&quot; uTex0_use=&quot;tx0&quot; \/&gt;\n&lt;vs=&quot;box_tank&quot; whl=&quot;53,83,21&quot; ext=1 sectR=1 \/&gt;\n&lt;a=&quot;front v,back v&quot; xywh=&quot;2,1,323,495&quot;\/&gt;\n&lt;a=&quot;right all&quot; xywh=&quot;327,1,128,495&quot;\/&gt;\n&lt;a=&quot;left all&quot; xywh=&quot;457,1,128,495&quot;\/&gt;\n&lt;a=&quot;top&quot; xywh=&quot;588,1,323,133&quot;\/&gt;\n&lt;a=&quot;bottom&quot; xywh=&quot;587,136,324,134&quot;\/&gt;\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Please note: new variable\/property <em>ckey=&#8221;#00ff00&#8243;<\/em>.<\/li><li>It is set in HTML HEX format. Alternatively it can be set as decimal integers array <em>ckey=&#8221;0,255,0&#8243;<\/em>.<\/li><\/ul>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>5. Build and run.<\/p>\n\n\n\n<p>Now &#8211; with no greenish lines!<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>Checked on Android too &#8211; works fine.<\/p>\n\n\n\n<p>Our next goal &#8211; to make it even more realistic than in reality!<\/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\">Chroma keying, obviously, is a way of treating certain color as transparent one. In our case &#8211; green: Of course, after loading an image, before pushing it to a texture, we can easily scan it for green pixels and set their Alpha-channels to zero. But when GL rasterizer picks tUV coordinates near the green areas, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-884","post","type-post","status-publish","format-standard","hentry","category-cross-platform-3d"],"_links":{"self":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/884","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=884"}],"version-history":[{"count":20,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/884\/revisions"}],"predecessor-version":[{"id":957,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/884\/revisions\/957"}],"wp:attachment":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media?parent=884"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/categories?post=884"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/tags?post=884"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}