{"id":772,"date":"2021-12-09T03:35:25","date_gmt":"2021-12-09T03:35:25","guid":{"rendered":"https:\/\/writingagame.com\/?p=772"},"modified":"2026-03-20T23:14:32","modified_gmt":"2026-03-20T23:14:32","slug":"chapter-26-render-to-and-read-from-gl-texture","status":"publish","type":"post","link":"https:\/\/writingagame.com\/index.php\/2021\/12\/09\/chapter-26-render-to-and-read-from-gl-texture\/","title":{"rendered":"Chapter 26. Rendering to texture (RTT)"},"content":{"rendered":"\n<p>Here I want to render a bit more complicated white noise images. I want bigger dots with a soft transition between them. The task splits in following steps:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Generate initial black\/white image (as we did in previous chapter)<\/li><li>Generate a GL texture out of it<\/li><li>Make it renderable (associate a render buffer with it)<\/li><li>Force GL to render to new render buffer<\/li><li>Render random black and white bigger dots into it<\/li><li>Read this surface back from GPU<\/li><li>Blur received image<\/li><li>And save it<\/li><\/ul>\n\n\n\n<p>It will require a few new variables and functions in <em>Texture <\/em>class.<\/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>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: [30,31,32,33,34,35,36,37]; 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;\n\npublic:\n    static int loadTexture(std::string filePath, int glRepeatH = GL_MIRRORED_REPEAT, int glRepeatV = GL_MIRRORED_REPEAT);\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\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\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.cpp<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; 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\n\/\/static array (vector) of all loaded textures\nstd::vector&lt;Texture*&gt; Texture::textures;\n\nint Texture::loadTexture(std::string filePath, 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    \/\/ 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}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p>Hope the code is readable enough.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>Following code renders and saves 3 images with different &#8220;blur depths&#8221;:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nint TheGame::run() {\n    \/*\n    getReady();\n    while (!bExitGame) {\n        drawFrame();\n    }\n    cleanUp();\n    *\/\n    Shader::loadShaders();\n\n    int wh&#91;2] = { 64,64 };\n    int bytesPerPixel = 4;\n    unsigned char* imgData = new unsigned char&#91;wh&#91;1] * wh&#91;0] * 4];\n    for (int y = 0; y &lt; wh&#91;1]; y++)\n        for (int x = 0; x &lt; wh&#91;0]; x++) {\n            int idx = (y * wh&#91;1] + x) * bytesPerPixel;\n            for (int i = 0; i &lt; 4; i++)\n                imgData&#91;idx + i] = getRandom(0, 1) * 255;\n        }\n    \/\/background generated, generate texture:\n    int texN = Texture::generateTexture(&quot;&quot;, wh&#91;0], wh&#91;1], imgData);\n    Texture::attachRenderBuffer(texN);\n    Texture::setRenderToTexture(texN);\n\n    mat4x4 mProjection, mMVP;\n    mat4x4_ortho(mProjection, -wh&#91;0]  \/ 2, wh&#91;0] \/ 2, -wh&#91;1] \/ 2, wh&#91;1] \/ 2, 1.f, -1.f);\n    unsigned char RGBA&#91;4];\n\n    \/\/create a simple 1x1 ucolor square\n    GameSubj* pSquare = new GameSubj();\n    gameSubjs.push_back(pSquare);\n    ModelBuilder* pMB = new ModelBuilder();\n    pMB-&gt;useSubjN(gameSubjs.size() - 1);\n    \/\/define VirtualShape\n    VirtualShape vs;\n    vs.setShapeType(&quot;box&quot;);\n    v3set(vs.whl, 1, 1, 0);\n    Material mt;\n    \/\/define material - flat red\n    mt.shaderN = Shader::spN_flat_ucolor;\n    mt.primitiveType = GL_TRIANGLES;\n    mt.uColor.setRGBA(255, 0, 0, 255); \/\/red\n    pMB-&gt;useMaterial(&amp;mt);\n    pMB-&gt;buildBoxFace(pMB, &quot;front&quot;, &amp;vs);\n    pMB-&gt;buildDrawJobs(gameSubjs);\n    delete pMB;\n    \/\/copy mt to pAltMaterial\n    pSquare-&gt;pAltMaterial = new Material(mt);\n    \/\/--end of pSquare\n    DrawJob* pDJ = DrawJob::drawJobs.back();\n\n    \/\/-------------blurLevel 3\n    int blurLevel = 3;\n    std::string fileName = &quot;wn64_blur3&quot;;\n    int dotSize = blurLevel * 2;\n    v3set(pSquare-&gt;scale, dotSize, dotSize, 1);\n\n    for (int i = 0; i &lt; 500; i++) {\n        pSquare-&gt;ownCoords.pos&#91;0] = getRandom(-wh&#91;0] \/ 2, wh&#91;0] \/ 2);\n        pSquare-&gt;ownCoords.pos&#91;1] = getRandom(-wh&#91;1] \/ 2, wh&#91;1] \/ 2);\n        pSquare-&gt;ownCoords.setDegrees(0,0,getRandom(0,90));\n        for (int ch = 0; ch &lt; 4; ch++)\n            RGBA&#91;ch] = (unsigned char)(getRandom(0, 1) * 255);\n        pSquare-&gt;pAltMaterial-&gt;uColor.setRGBA(RGBA);\n\n        \/\/prepare subject for rendering\n        pSquare-&gt;buildModelMatrix(pSquare);\n        \/\/build MVP matrix for given subject\n        mat4x4_mul(mMVP, mProjection, pSquare-&gt;ownModelMatrix);\n        \/\/render subject\n        pDJ-&gt;execute((float*)mMVP, NULL, NULL, NULL, pSquare-&gt;pAltMaterial);\n    }\n    Texture::getImageFromTexture(texN, imgData);\n    Texture::blurRGBA(imgData, wh&#91;0], wh&#91;1], blurLevel);\n\n    Texture::saveBMP(&quot;\/dt\/out\/&quot; + fileName + &quot;.bmp&quot;, imgData, wh&#91;0], wh&#91;1]);\n    \/\/-------------blurLevel 2\n    blurLevel = 2;\n    fileName = &quot;wn64_blur2&quot;;\n    dotSize = blurLevel * 2;\n    v3set(pSquare-&gt;scale, dotSize, dotSize, 1);\n\n    for (int i = 0; i &lt; 500; i++) {\n        pSquare-&gt;ownCoords.pos&#91;0] = getRandom(-wh&#91;0] \/ 2, wh&#91;0] \/ 2);\n        pSquare-&gt;ownCoords.pos&#91;1] = getRandom(-wh&#91;1] \/ 2, wh&#91;1] \/ 2);\n        pSquare-&gt;ownCoords.setDegrees(0, 0, getRandom(0, 90));\n        for (int ch = 0; ch &lt; 4; ch++)\n            RGBA&#91;ch] = (unsigned char)(getRandom(0, 1) * 255);\n        pSquare-&gt;pAltMaterial-&gt;uColor.setRGBA(RGBA);\n\n        \/\/prepare subject for rendering\n        pSquare-&gt;buildModelMatrix(pSquare);\n        \/\/build MVP matrix for given subject\n        mat4x4_mul(mMVP, mProjection, pSquare-&gt;ownModelMatrix);\n        \/\/render subject\n        pDJ-&gt;execute((float*)mMVP, NULL, NULL, NULL, pSquare-&gt;pAltMaterial);\n    }\n    Texture::getImageFromTexture(texN, imgData);\n    Texture::blurRGBA(imgData, wh&#91;0], wh&#91;1], blurLevel);\n\n    Texture::saveBMP(&quot;\/dt\/out\/&quot; + fileName + &quot;.bmp&quot;, imgData, wh&#91;0], wh&#91;1]);\n    \/\/-------------blurLevel 1\n    blurLevel = 1;\n    fileName = &quot;wn64_blur1&quot;;\n    dotSize = blurLevel * 2;\n    v3set(pSquare-&gt;scale, dotSize, dotSize, 1);\n\n    for (int i = 0; i &lt; 500; i++) {\n        pSquare-&gt;ownCoords.pos&#91;0] = getRandom(-wh&#91;0] \/ 2, wh&#91;0] \/ 2);\n        pSquare-&gt;ownCoords.pos&#91;1] = getRandom(-wh&#91;1] \/ 2, wh&#91;1] \/ 2);\n        pSquare-&gt;ownCoords.setDegrees(0, 0, getRandom(0, 90));\n        for (int ch = 0; ch &lt; 4; ch++)\n            RGBA&#91;ch] = (unsigned char)(getRandom(0, 1) * 255);\n        pSquare-&gt;pAltMaterial-&gt;uColor.setRGBA(RGBA);\n\n        \/\/prepare subject for rendering\n        pSquare-&gt;buildModelMatrix(pSquare);\n        \/\/build MVP matrix for given subject\n        mat4x4_mul(mMVP, mProjection, pSquare-&gt;ownModelMatrix);\n        \/\/render subject\n        pDJ-&gt;execute((float*)mMVP, NULL, NULL, NULL, pSquare-&gt;pAltMaterial);\n    }\n    Texture::getImageFromTexture(texN, imgData);\n    Texture::blurRGBA(imgData, wh&#91;0], wh&#91;1], blurLevel);\n\n    Texture::saveBMP(&quot;\/dt\/out\/&quot; + fileName + &quot;.bmp&quot;, imgData, wh&#91;0], wh&#91;1]);\n\n    delete&#91;] imgData;\n    mylog(&quot;Ready\\n&quot;);\n\n    return 1;\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p>4. Open <em>TheGame.cpp<\/em> and replace <em>TheGame::<strong>run()<\/strong><\/em> function by the code above.<\/p>\n\n\n\n<p>Details:<\/p>\n\n\n\n<p>a) Line 13: we are creating 64&#215;64 RGBA buffer.<\/p>\n\n\n\n<p>b) Lines 14-19: filling this buffer out by a simple background (as in previous chapter). Each of 4 channels will look like<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c26\/01.jpg\"><\/p>\n\n\n\n<p>Combined channels image:<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c26\/02.jpg\"><\/p>\n\n\n\n<p>c) Lines 21-23: generating a texture, render buffer and setting rendering to created render buffer\/texture.<\/p>\n\n\n\n<p>d) Lines 29-49: creating a flat 1&#215;1 square object which we will draw to generated texture.<\/p>\n\n\n\n<p>e) Line 55: For the first image dots(squares) size will be 6&#215;6.<\/p>\n\n\n\n<p>f) Lines 58-72: Rendering these squares in random colors in random coordinates.<\/p>\n\n\n\n<p>g) Line 73: Retrieving resulting image back from GPU:<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c26\/04.jpg\"><\/p>\n\n\n\n<p>where each channel looks like<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c26\/03.jpg\"><\/p>\n\n\n\n<p>h) Line 74: Blur:<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c26\/06.jpg\"><\/p>\n\n\n\n<p>where each channel looks like<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c26\/05.jpg\"><\/p>\n\n\n\n<p>i) Line 78: Saving in file.<\/p>\n\n\n\n<p>j) Lines 77 to 126: the same for blur depth 2<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c26\/08.jpg\"><\/p>\n\n\n\n<p>and 1<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c26\/10.jpg\"><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p><\/p>\n\n\n\n<p>5. Build and run. New images are generated and saved in<\/p>\n\n\n\n<p><em>C:\\CPP\\a997modeler\\p_windows\\Debug\\dt\\<strong>out<\/strong><\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>6. In <strong>Windows File Explorer<\/strong> copy all newly generated files from <\/p>\n\n\n\n<p> <em>C:\\CPP\\a997modeler\\p_windows\\Debug\\dt\\<strong>out<\/strong><\/em> <\/p>\n\n\n\n<p>to&nbsp;<em>C:\\CPP\\engine\\dt\\common\\img\\whitenoise<\/em><\/p>\n\n\n\n<p>So, now we have 5 BMP files in this folder.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>7. Ok to delete folder  <em>C:\\CPP\\a997modeler\\p_windows\\Debug\\<strong>dt<\/strong><\/em> .<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>These pretty pictures are not so precious by themselves, but their black\/white channels &#8211; ARE. We&#8217;ll need them soon.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\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 render a bit more complicated white noise images. I want bigger dots with a soft transition between them. The task splits in following steps: Generate initial black\/white image (as we did in previous chapter) Generate a GL texture out of it Make it renderable (associate a render buffer with it) Force [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":774,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-772","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\/772","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=772"}],"version-history":[{"count":18,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/772\/revisions"}],"predecessor-version":[{"id":3515,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/772\/revisions\/3515"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media\/774"}],"wp:attachment":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media?parent=772"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/categories?post=772"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/tags?post=772"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}