{"id":466,"date":"2021-12-04T19:18:41","date_gmt":"2021-12-04T19:18:41","guid":{"rendered":"https:\/\/writingagame.com\/?p=466"},"modified":"2023-06-06T18:03:45","modified_gmt":"2023-06-06T18:03:45","slug":"chapter-11-textures","status":"publish","type":"post","link":"https:\/\/writingagame.com\/index.php\/2021\/12\/04\/chapter-11-textures\/","title":{"rendered":"Chapter 11. Textures"},"content":{"rendered":"\n<p>1. To use textures, we first need to load them into the application. Using an image-loading library that supports several popular formats looks like a good solution. I like <em>stb_image.h<\/em> by <a href=\"https:\/\/github.com\/nothings\" target=\"_blank\" rel=\"noreferrer noopener\">Sean Barrett<\/a>. Especially I love that it&#8217;s a single h-file, not a lib or dll.<\/p>\n\n\n\nYou can download it <a href=\"https:\/\/writingagame.com\/img\/b01\/c11\/stb_image.h\" download=\"\">here<\/a>.\n\n\n\n<p><br>Save it in <em>C:\\CPP\\engine<\/em> folder (the same place where we saved <em>linmath.h<\/em> earlier).<\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity\"\/>\n\n\n\n<p>2. Then we&#8217;ll need an image itself.<\/p>\n\n\n\n<p>Create a folder for it: <strong>\\dt<\/strong> under <em>C:\\CPP\\a999hello<\/em> (this folder should belong to the <strong>project<\/strong>, NOT to engine).<\/p>\n\n\n\n<p><strong>IMPORTANT<\/strong>: the image sizes MUST be powers of 2.<br>The following <em>sample_img.png<\/em> is 512&#215;256:<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c11\/sample_img.png\"><\/p>\n\n\n\n<p><\/p>\n\n\n\nYou can download it <a href=\"https:\/\/writingagame.com\/img\/b01\/c11\/sample_img.png\" download=\"\">here<\/a>.\n\n\n\n<p><br>Save it in <em>C:\\CPP\\a999hello\\dt<\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Windows<\/h2>\n\n\n\n<p>3. Start Visual Studio. Open&nbsp;<em>C:\\CPP\\a999hello\\p_windows\\p_windows.sln<\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity\"\/>\n\n\n\n<p>4. Add new <strong>\/dt<\/strong> folder to Post-Build <strong>xcopy <\/strong>instruction:<\/p>\n\n\n\n<p>Open  <em>p_windows<\/em>  project Properties, All Configurations \/ x86, <em>Build events -&gt; Post-Build Event -&gt; Command Line -&gt; Edit<\/em>.<\/p>\n\n\n\n<p>Add new line<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>xcopy \"..\\dt\\*.*\" \"$(TargetDir)dt\\\" \/E \/R \/D \/y<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>so now we have 2 xcopy commands.<\/p>\n\n\n\n<p>Ok, Apply, Ok.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity\"\/>\n\n\n\n<p>5. Open <em>TheGame.cpp<\/em>. We&#8217;ll need to change it a bit:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>We need a reference to <em>stb_image.h<\/em>.<\/li>\n\n\n\n<li>A bit different variables set.<\/li>\n\n\n\n<li>Different vertex structure: (3D coordinates + 2D texture coordinates) instead of just coordinates.<\/li>\n\n\n\n<li>We will use 4 vertices instead of 3 and GL_TRIANGLE_STRIP primitive instead of GL_TRIANGLES<\/li>\n\n\n\n<li>Also we need to implement reading\/loading an image and converting it into the texture.<\/li>\n\n\n\n<li>And pass texture id and per-vertex texture coordinates to a new shader program.<\/li>\n<\/ul>\n\n\n\n<p>So, replace<em>TheGame.cpp<\/em> code by following:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74]; title: ; notranslate\" title=\"\">\n#include &quot;TheGame.h&quot;\n#include &quot;platform.h&quot;\n#include &quot;linmath.h&quot;\n#include &quot;utils.h&quot;\n#include &quot;Shader.h&quot;\n\nextern std::string filesRoot;\n\n#define STB_IMAGE_IMPLEMENTATION  \/\/required by stb_image.h\n#include &quot;stb_image.h&quot;\n\nstatic const struct\n{\n    float x, y, z, tu, tv;\n} vertices&#91;4] =\n{\n    { -0.5f,  0.5f, 0.f, 0.f, 0.f }, \/\/top-left\n    { -0.5f, -0.5f, 0.f, 0.f, 1.f }, \/\/bottom-left\n    {  0.5f,  0.5f, 0.f, 1.f, 0.f }, \/\/top-right\n    {  0.5f, -0.5f, 0.f, 1.f, 1.f }  \/\/bottom-right\n};\nunsigned int vao_buffer, vertex_buffer, shaderProgramId, textureId;\nint umvp_location, apos_location, atuv_location;\nfloat angle_z = 0;\n\nint TheGame::getReady() {\n    bExitGame = false;\n\n    glGenVertexArrays(1, &amp;vao_buffer);\n    glBindVertexArray(vao_buffer);\n\n    glGenBuffers(1, &amp;vertex_buffer);\n    glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);\n    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);\n\n    shaderProgramId = Shader::linkShaderProgram((filesRoot + &quot;\/dt\/shaders\/flat_tex_v.txt&quot;).c_str(), (filesRoot + &quot;\/dt\/shaders\/flat_tex_f.txt&quot;).c_str());\n\n    umvp_location = glGetUniformLocation(shaderProgramId, &quot;uMVP&quot;);\n\n    apos_location = glGetAttribLocation(shaderProgramId, &quot;aPos&quot;);\n    glEnableVertexAttribArray(apos_location);\n    glVertexAttribPointer(apos_location, 3, GL_FLOAT, GL_FALSE,\n        sizeof(vertices&#91;0]), (void*)0);\n\n    atuv_location = glGetAttribLocation(shaderProgramId, &quot;aTuv&quot;);\n    glEnableVertexAttribArray(atuv_location);\n    glVertexAttribPointer(atuv_location, 2, GL_FLOAT, GL_FALSE,\n        sizeof(vertices&#91;0]), (void*)(sizeof(float) * 3));\n\n    \/\/ loading an image\n    int imgWidth, imgHeight, nrChannels;\n    unsigned char* imgData = stbi_load((filesRoot + &quot;\/dt\/sample_img.png&quot;).c_str(),\n        &amp;imgWidth, &amp;imgHeight, &amp;nrChannels, 4); \/\/&quot;4&quot;-convert to 4 channels -RGBA\n    if (imgData == NULL) {\n        mylog(&quot;ERROR loading image\\n&quot;);\n    }\n    \/\/ generate texture\n    unsigned int textureId;\n    glGenTextures(1, &amp;textureId);\n    glBindTexture(GL_TEXTURE_2D, textureId);\n    \/\/ set the texture wrapping\/filtering options (on the currently bound texture object)\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n    \/\/ attach\/load image data\n    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, imgWidth, imgHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, imgData);\n    glGenerateMipmap(GL_TEXTURE_2D);\n    \/\/ release image data\n    stbi_image_free(imgData);\n\n    \/\/pass textureId to shader program\n    glActiveTexture(GL_TEXTURE0); \/\/ activate the texture unit first before binding texture\n    glBindTexture(GL_TEXTURE_2D, textureId);\n\n    return 1;\n}\nint TheGame::drawFrame() {\n    myPollEvents();\n\n    mat4x4 m, p, mvp;\n\n    \/\/glClearColor(0.0, 0.0, 0.5, 1.0);\n    glClear(GL_COLOR_BUFFER_BIT);\n    angle_z += 0.01f;\n    mat4x4_identity(m);\n    mat4x4_rotate_Z(m, m, angle_z);\n    mat4x4_scale_aniso(m, m, 2.0, 1.0, 1.0);\n    mat4x4_ortho(p, -screenRatio, screenRatio, -1.f, 1.f, 1.f, -1.f);\n    mat4x4_mul(mvp, p, m);\n\n    glUseProgram(shaderProgramId);\n    glUniformMatrix4fv(umvp_location, 1, GL_FALSE, (const GLfloat*)mvp);\n\n    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n\n    mySwapBuffers();\n    return 1;\n}\nint TheGame::cleanUp() {\n    return 1;\n}\nint TheGame::onScreenResize(int width, int height) {\n    if (screenSize&#91;0] == width &amp;&amp; screenSize&#91;1] == height)\n        return 0;\n    screenSize&#91;0] = width;\n    screenSize&#91;1] = height;\n    screenRatio = (float)width \/ (float)height;\n    glViewport(0, 0, width, height);\n    mylog(&quot; screen size %d x %d\\n&quot;, width, height);\n    return 1;\n}\nint TheGame::run() {\n    getReady();\n    while (!bExitGame) {\n        drawFrame();\n    }\n    cleanUp();\n    return 1;\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity\"\/>\n\n\n\n<ol class=\"wp-block-list\" start=\"6\">\n<li>And, of course, new shaders:<\/li>\n<\/ol>\n\n\n\n<p><strong>Vertex shader<\/strong>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#version 320 es\nprecision lowp float;\nuniform mat4 uMVP; \/\/ transform matrix (Model-View-Projection)\nin vec3 aPos; \/\/attribute position (3D coordinates)\nin vec2 aTuv; \/\/attribute TUV (texture coordinates)\nout vec2 vTuv; \/\/varying TUV (pass to fragment shader)\nvoid main(){\n  gl_Position = uMVP * vec4(aPos, 1.0);\n  vTuv = aTuv;\n}\n\n<\/pre><\/div>\n\n\n<p>Copy-paste this code to a <strong>text editor<\/strong> and save it as a txt file in<\/p>\n\n\n\n<p><em>C:\\CPP\\engine\\dt\\shaders\\flat_tex_v.txt<\/em><\/p>\n\n\n\n<p><strong>Fragment shader<\/strong>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#version 320 es\nprecision lowp float;\nuniform sampler2D uTex0;  \/\/texture id\nin vec2 vTuv; \/\/varying TUV (passed from vertex shader)\nout vec4 FragColor; \/\/ output pixel color\nvoid main(){\n  FragColor = texture(uTex0, vTuv);\n}\n\n<\/pre><\/div>\n\n\n<p>Copy-paste this code to a <strong>text editor<\/strong> and save it as a txt file in<\/p>\n\n\n\n<p>C:\\CPP\\engine\\dt\\shaders\\flat_tex_f.txt<\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity\"\/>\n\n\n\n<p>7. Build and run. Result:<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c11\/01.jpg\"><\/p>\n\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity\"\/>\n\n\n\n<p>Now &#8211; on<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Android<\/h2>\n\n\n\n<p>This time ALL code changes were on a cross-platform side, so we don&#8217;t even need to change anything\u2026 Except <em>stb_image.h<\/em> and <em>xcopy<\/em> post-build instruction.<\/p>\n\n\n\n<p>8. Re-start Visual Studio. Open <em>C:\\CPP\\a999hello\\p_android\\p_android.sln<\/em>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity\"\/>\n\n\n\n<p>9. Go to  <em>p_android.NativeActivity<\/em> project Properties, All Configurations \/ ARM64, <em>Build Events -&gt; Post-Build Event -&gt; Command Line -&gt; Edit<\/em><\/p>\n\n\n\n<p>Add new line:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>xcopy \"..\\..\\dt\\*.*\" \"..\\$(RootNamespace).Packaging\\assets\\dt\\\" \/E \/R \/D \/y<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>Ok, Apply, Ok.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity\"\/>\n\n\n\n<p>9. Now, right-click on <em>xEngine<\/em>, Add -&gt; <strong>Existing <\/strong>Item,<\/p>\n\n\n\n<p><em>C:\\CPP\\engine\\stb_image.h<\/em><\/p>\n\n\n\n<p><strong>Add<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity\"\/>\n\n\n\n<p>10. Plug in Android (as usual). <\/p>\n\n\n\n<p>Re-build and run. <\/p>\n\n\n\n<p>Nice, just as expected.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity\"\/>\n\n\n\n<p>BTW, if you are a newbie to programming, now you can modestly indicate in your resume that you are fluent in:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>C++<\/li>\n\n\n\n<li>graphics<\/li>\n\n\n\n<li>3D<\/li>\n\n\n\n<li>Visual Studio<\/li>\n\n\n\n<li>Cross-platform<\/li>\n\n\n\n<li>Android<\/li>\n\n\n\n<li>Windows<\/li>\n\n\n\n<li>OpenGL ES<\/li>\n\n\n\n<li>GLSL<\/li>\n<\/ul>\n\n\n\n<p>Well, maybe &#8220;fluent&#8221; is still an exaggeration at this point, but everything else is a <em>pure truth<\/em>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity\"\/>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p class=\"mb-2\">1. To use textures, we first need to load them into the application. Using an image-loading library that supports several popular formats looks like a good solution. I like stb_image.h by Sean Barrett. Especially I love that it&#8217;s a single h-file, not a lib or dll. You can download it here. Save it in C:\\CPP\\engine [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":475,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-466","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\/466","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=466"}],"version-history":[{"count":13,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/466\/revisions"}],"predecessor-version":[{"id":2124,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/466\/revisions\/2124"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media\/475"}],"wp:attachment":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media?parent=466"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/categories?post=466"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/tags?post=466"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}