{"id":15468149,"url":"https://github.com/lesnitsky/webgl-month","last_synced_at":"2025-04-09T21:20:14.569Z","repository":{"id":85541865,"uuid":"194667033","full_name":"lesnitsky/webgl-month","owner":"lesnitsky","description":"🎓 Daily WebGL tutorials","archived":false,"fork":false,"pushed_at":"2024-08-28T07:21:47.000Z","size":944,"stargazers_count":224,"open_issues_count":4,"forks_count":14,"subscribers_count":10,"default_branch":"dev","last_synced_at":"2025-04-02T13:58:06.389Z","etag":null,"topics":["3d-rendering","beginner","code-samples","javascript","tutorials","webgl"],"latest_commit_sha":null,"homepage":"https://dev.to/lesnitsky","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lesnitsky.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-07-01T12:18:08.000Z","updated_at":"2024-12-16T20:30:04.000Z","dependencies_parsed_at":null,"dependency_job_id":"c70dc012-c40d-47db-ac92-42046358f74d","html_url":"https://github.com/lesnitsky/webgl-month","commit_stats":{"total_commits":39,"total_committers":1,"mean_commits":39.0,"dds":0.0,"last_synced_commit":"71bc84d4c78482ce9eb475cf65123943303cdd06"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lesnitsky%2Fwebgl-month","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lesnitsky%2Fwebgl-month/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lesnitsky%2Fwebgl-month/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lesnitsky%2Fwebgl-month/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lesnitsky","download_url":"https://codeload.github.com/lesnitsky/webgl-month/tar.gz/refs/heads/dev","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248112226,"owners_count":21049620,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["3d-rendering","beginner","code-samples","javascript","tutorials","webgl"],"created_at":"2024-10-02T01:40:18.149Z","updated_at":"2025-04-09T21:20:14.539Z","avatar_url":"https://github.com/lesnitsky.png","language":"JavaScript","funding_links":[],"categories":["WebGL"],"sub_categories":["Blog Series"],"readme":"# WebGL month\n\nHi 👋 My name is Andrei. I have some fun experience with WebGL and I want to share it. I'm starting a month of WebGL, each day I will post a WebGL related tutorial. Not Three.js, not pixi.js, WebGL API itself.\n\n[Follow me on twitter](https://twitter.com/lesnitsky_a) to get WebGL month updates or [join WebGL month mailing list](http://eepurl.com/gwiSeH)\n\n\n## Day 1. Intro\n\nThis is a series of blog posts related to WebGL. New post will be available every day\n\n[Subscribe](https://twitter.com/lesnitsky_a) for updates or [join mailing list](http://eepurl.com/gwiSeH)\n\n[Soruce code available here](https://github.com/lesnitsky/webgl-month)\n\n![GitHub stars](https://img.shields.io/github/stars/lesnitsky/webgl-month.svg?style=social)\n\n\u003e Built with [GitTutor](https://github.com/lesnitsky/git-tutor)\n\nWelcome to day 1 of WebGL month. In this article we'll get into high level concepts of rendering which are improtant to understand before approaching actual WebGL API.\n\nWebGL API is often treated as 3D rendering API, which is a wrong assumption. So what WebGL does?\nTo answer this question let's try to render smth with canvas 2d.\n\n\nWe'll need simple html\n\n📄 index.html\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n  \u003chead\u003e\n    \u003cmeta charset=\"UTF-8\" /\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /\u003e\n    \u003cmeta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" /\u003e\n    \u003ctitle\u003eWebGL Month\u003c/title\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\u003c/body\u003e\n\u003c/html\u003e\n\n```\nand canvas\n\n📄 index.html\n```diff\n      \u003cmeta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" /\u003e\n      \u003ctitle\u003eWebGL Month\u003c/title\u003e\n    \u003c/head\u003e\n-   \u003cbody\u003e\u003c/body\u003e\n+   \u003cbody\u003e\n+     \u003ccanvas\u003e\u003c/canvas\u003e\n+   \u003c/body\u003e\n  \u003c/html\u003e\n\n```\nDon't forget beloved JS\n\n📄 index.html\n```diff\n    \u003c/head\u003e\n    \u003cbody\u003e\n      \u003ccanvas\u003e\u003c/canvas\u003e\n+     \u003cscript src=\"./src/canvas2d.js\"\u003e\u003c/script\u003e\n    \u003c/body\u003e\n  \u003c/html\u003e\n\n```\n📄 src/canvas2d.js\n```js\nconsole.log('Hello WebGL month');\n```\nLet's grab a reference to canvas and get 2d context\n\n📄 src/canvas2d.js\n```diff\n- console.log('Hello WebGL month');+ console.log('Hello WebGL month');\n+ \n+ const canvas = document.querySelector('canvas');\n+ const ctx = canvas.getContext('2d');\n\n```\nand do smth pretty simple, like drawing a black rectangle\n\n📄 src/canvas2d.js\n```diff\n  \n  const canvas = document.querySelector('canvas');\n  const ctx = canvas.getContext('2d');\n+ \n+ ctx.fillRect(0, 0, 100, 50);\n\n```\nOk, this is pretty simple right?\nBut let's think about what this signle line of code actually did.\nIt filled every pixel inside of rectangle with black color.\n\nAre there any ways to do the same but w/o `fillRect`?\nThe answer is yes\n\n\nLet's implement our own version of\n\n📄 src/canvas2d.js\n```diff\n  const canvas = document.querySelector('canvas');\n  const ctx = canvas.getContext('2d');\n  \n- ctx.fillRect(0, 0, 100, 50);\n+ function fillRect(top, left, width, height) {\n+ \n+ }\n\n```\nSo basically each pixel is just a color encoded in 4 integers. R, G, B channel and Alpha.\nTo store info about each pixel of canvas we'll need a `Uint8ClampedArray`.\nThe size of this array is `canvas.width * canvas.height` (pixels count) `* 4` (each pixel has 4 channels).\n\n📄 src/canvas2d.js\n```diff\n  const ctx = canvas.getContext('2d');\n  \n  function fillRect(top, left, width, height) {\n- \n+     const pixelStore = new Uint8ClampedArray(canvas.width * canvas.height * 4);\n  }\n\n```\nNow we can fill each pixel storage with colors. Note that alpha component is also in  range unlike CSS\n\n📄 src/canvas2d.js\n```diff\n  \n  function fillRect(top, left, width, height) {\n      const pixelStore = new Uint8ClampedArray(canvas.width * canvas.height * 4);\n+ \n+     for (let i = 0; i \u003c pixelStore.length; i += 4) {\n+         pixelStore[i] = 0; // r\n+         pixelStore[i + 1] = 0; // g\n+         pixelStore[i + 2] = 0; // b\n+         pixelStore[i + 3] = 255; // alpha\n+     }\n  }\n\n```\nBut how do we render this pixels? There is a special canvas renderable class\n\n📄 src/canvas2d.js\n```diff\n          pixelStore[i + 2] = 0; // b\n          pixelStore[i + 3] = 255; // alpha\n      }\n+ \n+     const imageData = new ImageData(pixelStore, canvas.width, canvas.height);\n+     ctx.putImageData(imageData, 0, 0);\n  }\n+ \n+ fillRect();\n\n```\nWhoa 🎉 We filled canvas with a color manually iterating over each pixel! But we're not taking into account passed arguments, let's fix it.\n\n\nCalculate pixel indices inside rectangle\n\n📄 src/canvas2d.js\n```diff\n  const canvas = document.querySelector('canvas');\n  const ctx = canvas.getContext('2d');\n  \n+ function calculatePixelIndices(top, left, width, height) {\n+     const pixelIndices = [];\n+ \n+     for (let x = left; x \u003c left + width; x++) {\n+         for (let y = top; y \u003c top + height; y++) {\n+             const i =\n+                 y * canvas.width * 4 + // pixels to skip from top\n+                 x * 4; // pixels to skip from left\n+ \n+             pixelIndices.push(i);\n+         }\n+     }\n+ \n+     return pixelIndices;\n+ }\n+ \n  function fillRect(top, left, width, height) {\n      const pixelStore = new Uint8ClampedArray(canvas.width * canvas.height * 4);\n  \n\n```\nand iterate over these pixels instead of the whole canvas\n\n📄 src/canvas2d.js\n```diff\n  \n  function fillRect(top, left, width, height) {\n      const pixelStore = new Uint8ClampedArray(canvas.width * canvas.height * 4);\n+     \n+     const pixelIndices = calculatePixelIndices(top, left, width, height);\n  \n-     for (let i = 0; i \u003c pixelStore.length; i += 4) {\n+     pixelIndices.forEach((i) =\u003e {\n          pixelStore[i] = 0; // r\n          pixelStore[i + 1] = 0; // g\n          pixelStore[i + 2] = 0; // b\n          pixelStore[i + 3] = 255; // alpha\n-     }\n+     });\n  \n      const imageData = new ImageData(pixelStore, canvas.width, canvas.height);\n      ctx.putImageData(imageData, 0, 0);\n  }\n  \n- fillRect();\n+ fillRect(10, 10, 100, 50);\n\n```\nCool 😎 We've just reimplemented `fillRect`! But what does it have in common with WebGL?\n\n![Everything](https://git-tutor-assets.s3.eu-west-2.amazonaws.com/thanos-everyhting.jpg)\n\nThat's exactly what WebGL API does – _it calculates color of each pixel and fills it with calculated color_\n\n### What's next?\n\nIn next article we'll start working with WebGL API and render a WebGL \"Hello world\". See you tomorrow\n\n[Subscribe](https://twitter.com/lesnitsky_a) for updates or [join mailing list](eepurl.com/gwiSeH)\n\n[Soruce code available here](https://github.com/lesnitsky/webgl-month)\n\n![GitHub stars](https://img.shields.io/github/stars/lesnitsky/webgl-month.svg?style=social)\n\n\u003e Built with [GitTutor](https://github.com/lesnitsky/git-tutor)\n\n### Homework\n\nExtend custom `fillRect` to support custom colors\n\n\n## Day 2. Simple shader and triangle\n\nThis is a series of blog posts related to WebGL. New post will be available every day\n\n[Subscribe](https://twitter.com/lesnitsky_a) for updates or [join mailing list](http://eepurl.com/gwiSeH)\n\n[Soruce code available here](https://github.com/lesnitsky/webgl-month)\n\n![GitHub stars](https://img.shields.io/github/stars/lesnitsky/webgl-month.svg?style=social)\n\n\u003e Built with [GitTutor](https://github.com/lesnitsky/git-tutor)\n\n---\n\n[Yesterday](https://dev.to/lesnitsky/webgl-month-day-1-19ha) we've learned what WebGL does – calculates each pixel color inside renderable area. But how does it actually do that?\n\n\nWebGL is an API which works with your GPU to render stuff. While JavaScript is executed by v8 on a CPU, GPU can't execute JavaScript, but it is still programmable\n\nOne of the languages GPU \"understands\" is [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language), so we'll famialarize ourselves not only with WebGL API, but also with this new language.\n\nGLSL is a C like programming language, so it is easy to learn and write for JavaScript developers.\n\nBut where do we write glsl code? How to pass it to GPU in order to execute?\n\nLet's write some code\n\n\nLet's create a new js file and get a reference to WebGL rendering context\n\n📄 index.html\n```diff\n    \u003c/head\u003e\n    \u003cbody\u003e\n      \u003ccanvas\u003e\u003c/canvas\u003e\n-     \u003cscript src=\"./src/canvas2d.js\"\u003e\u003c/script\u003e\n+     \u003cscript src=\"./src/webgl-hello-world.js\"\u003e\u003c/script\u003e\n    \u003c/body\u003e\n  \u003c/html\u003e\n\n```\n📄 src/webgl-hello-world.js\n```js\nconst canvas = document.querySelector('canvas');\nconst gl = canvas.getContext('webgl');\n\n```\nThe program executable by GPU is created by  method of WebGL rendering context\n\n📄 src/webgl-hello-world.js\n```diff\n  const canvas = document.querySelector('canvas');\n  const gl = canvas.getContext('webgl');\n+ \n+ const program = gl.createProgram();\n\n```\nGPU program consists of two \"functions\"\nThese functions are called `shaders`\nWebGL supports several types of shaders\n\nIn this example we'll work with `vertex` and `fragment` shaders.\nBoth could be created with `createShader` method\n\n📄 src/webgl-hello-world.js\n```diff\n  const gl = canvas.getContext('webgl');\n  \n  const program = gl.createProgram();\n+ \n+ const vertexShader = gl.createShader(gl.VERTEX_SHADER);\n+ const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);\n\n```\nNow let's write the simpliest possible shader\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  const vertexShader = gl.createShader(gl.VERTEX_SHADER);\n  const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);\n+ \n+ const vShaderSource = `\n+ void main() {\n+     \n+ }\n+ `;\n\n```\nThis should look pretty familiar to those who has some C/C++ experience\n\n\nUnlike C or C++ `main` doesn't return anyhting, it assignes a value to a global variable `gl_Position` instead\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  const vShaderSource = `\n  void main() {\n-     \n+     gl_Position = vec4(0, 0, 0, 1);\n  }\n  `;\n\n```\nNow let's take a closer look to what is being assigned.\n\nThere is a bunch of functions available in shaders.\n\n`vec4` function creates a vector of 4 components.\n\n`gl_Position = vec4(0, 0, 0, 1);`\n\nLooks weird.. We live in 3-dimensional world, what on earth is the 4th component? Is it `time`? 😕\n\nNot really\n\n[Quote from MDN](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_model_view_projection#Homogeneous_Coordinates)\n\n\u003e It turns out that this addition allows for lots of nice techniques for manipulating 3D data.\n\u003e A three dimensional point is defined in a typical Cartesian coordinate system. The added 4th dimension changes this point into a homogeneous coordinate. It still represents a point in 3D space and it can easily be demonstrated how to construct this type of coordinate through a pair of simple functions.\n\nFor now we can just ingore the 4th component and set it to `1.0` just because\n\n\nAlright, we have a shader variable, shader source in another variable. How do we connect these two? With\n\n📄 src/webgl-hello-world.js\n```diff\n      gl_Position = vec4(0, 0, 0, 1);\n  }\n  `;\n+ \n+ gl.shaderSource(vertexShader, vShaderSource);\n\n```\nGLSL shader should be compiled in order to be executed\n\n📄 src/webgl-hello-world.js\n```diff\n  `;\n  \n  gl.shaderSource(vertexShader, vShaderSource);\n+ gl.compileShader(vertexShader);\n\n```\nCompilation result could be retreived by . This method returns a \"compiler\" output. If it is an empty string – everyhting is good\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  gl.shaderSource(vertexShader, vShaderSource);\n  gl.compileShader(vertexShader);\n+ \n+ console.log(gl.getShaderInfoLog(vertexShader));\n\n```\nWe'll need to do the same with fragment shader, so let's implement a helper function which we'll use for fragment shader as well\n\n📄 src/webgl-hello-world.js\n```diff\n  }\n  `;\n  \n- gl.shaderSource(vertexShader, vShaderSource);\n- gl.compileShader(vertexShader);\n+ function compileShader(shader, source) {\n+     gl.shaderSource(shader, source);\n+     gl.compileShader(shader);\n  \n- console.log(gl.getShaderInfoLog(vertexShader));\n+     const log = gl.getShaderInfoLog(shader);\n+ \n+     if (log) {\n+         throw new Error(log);\n+     }\n+ }\n+ \n+ compileShader(vertexShader, vShaderSource);\n\n```\nHow does the simpliest fragment shader looks like? Exactly the same\n\n📄 src/webgl-hello-world.js\n```diff\n  }\n  `;\n  \n+ const fShaderSource = `\n+     void main() {\n+         \n+     }\n+ `;\n+ \n  function compileShader(shader, source) {\n      gl.shaderSource(shader, source);\n      gl.compileShader(shader);\n\n```\nComputation result of a fragment shader is a color, which is also a vector of 4 components (r, g, b, a). Unlike CSS, values are in range of `[0..1]` instead of `[0..255]`. Fragment shader computation result should be assigned to the variable `gl_FragColor`\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  const fShaderSource = `\n      void main() {\n-         \n+         gl_FragColor = vec4(1, 0, 0, 1);\n      }\n  `;\n  \n  }\n  \n  compileShader(vertexShader, vShaderSource);\n+ compileShader(fragmentShader, fShaderSource);\n\n```\nNow we should connect `program` with our shaders\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  compileShader(vertexShader, vShaderSource);\n  compileShader(fragmentShader, fShaderSource);\n+ \n+ gl.attachShader(program, vertexShader);\n+ gl.attachShader(program, fragmentShader);\n\n```\nNext step – link program. This phase is required to verify if vertex and fragment shaders are compatible with each other (we'll get to more details later)\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  gl.attachShader(program, vertexShader);\n  gl.attachShader(program, fragmentShader);\n+ \n+ gl.linkProgram(program);\n\n```\nOur application could have several programs, so we should tell gpu which program we want to use before issuing a draw call\n\n📄 src/webgl-hello-world.js\n```diff\n  gl.attachShader(program, fragmentShader);\n  \n  gl.linkProgram(program);\n+ \n+ gl.useProgram(program);\n\n```\nOk, we're ready to draw something\n\n📄 src/webgl-hello-world.js\n```diff\n  gl.linkProgram(program);\n  \n  gl.useProgram(program);\n+ \n+ gl.drawArrays();\n\n```\nWebGL can render several types of \"primitives\"\n\n-   Points\n-   Lines\n-   Triangels\n\nWe should pass a primitive type we want to render\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  gl.useProgram(program);\n  \n- gl.drawArrays();\n+ gl.drawArrays(gl.POINTS);\n\n```\nThere is a way to pass input data containing info about positions of our primitives to vertex shader, so we need to pass the index of the first primitive we want to render\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  gl.useProgram(program);\n  \n- gl.drawArrays(gl.POINTS);\n+ gl.drawArrays(gl.POINTS, 0);\n\n```\nand primitives count\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  gl.useProgram(program);\n  \n- gl.drawArrays(gl.POINTS, 0);\n+ gl.drawArrays(gl.POINTS, 0, 1);\n\n```\nNothing rendered 😢\nWhat is wrong?\n\nActually to render point, we should also specify a point size inside vertex shader\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  const vShaderSource = `\n  void main() {\n+     gl_PointSize = 20.0;\n      gl_Position = vec4(0, 0, 0, 1);\n  }\n  `;\n\n```\nWhoa 🎉 We have a point!\n\n![WebGL Point](https://git-tutor-assets.s3.eu-west-2.amazonaws.com/webgl-point.png)\n\nIt is rendered in the center of the canvas because `gl_Position` is `vec4(0, 0, 0, 1)` =\u003e `x == 0` and `y == 0`\nWebGL coordinate system is different from `canvas2d`\n\n`canvas2d`\n\n```\n0.0\n-----------------------→ width (px)\n|\n|\n|\n↓\nheight (px)\n```\n\n`webgl`\n\n```\n                    (0, 1)\n                      ↑\n                      |\n                      |\n                      |\n(-1, 0) ------ (0, 0)-·---------\u003e (1, 0)\n                      |\n                      |\n                      |\n                      |\n                    (0, -1)\n```\n\n\nNow let's pass point coordinate from JS instead of hardcoding it inside shader\n\nInput data of vertex shader is called `attribute`\nLet's define `position` attribute\n\n📄 src/webgl-hello-world.js\n```diff\n  const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);\n  \n  const vShaderSource = `\n+ attribute vec2 position;\n+ \n  void main() {\n      gl_PointSize = 20.0;\n-     gl_Position = vec4(0, 0, 0, 1);\n+     gl_Position = vec4(position.x, position.y, 0, 1);\n  }\n  `;\n  \n\n```\nIn order to fill attribute with data we need to get attribute location. Think of it as of unique identifier of attribute in javascript world\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  gl.useProgram(program);\n  \n+ const positionPointer = gl.getAttribLocation(program, 'position');\n+ \n  gl.drawArrays(gl.POINTS, 0, 1);\n\n```\nGPU accepts only typed arrays as input, so let's define a `Float32Array` as a storage of our point position\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  const positionPointer = gl.getAttribLocation(program, 'position');\n  \n+ const positionData = new Float32Array([0, 0]);\n+ \n  gl.drawArrays(gl.POINTS, 0, 1);\n\n```\nBut this array couldn't be passed to GPU as-is, GPU should have it's own buffer.\nThere are different kinds of \"buffers\" in GPU world, in this case we need `ARRAY_BUFFER`\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  const positionData = new Float32Array([0, 0]);\n  \n+ const positionBuffer = gl.createBuffer(gl.ARRAY_BUFFER);\n+ \n  gl.drawArrays(gl.POINTS, 0, 1);\n\n```\nTo make any changes to GPU buffers, we need to \"bind\" it. After buffer is bound, it is treated as \"current\", and any buffer modification operation will be performed on \"current\" buffer.\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  const positionBuffer = gl.createBuffer(gl.ARRAY_BUFFER);\n  \n+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);\n+ \n  gl.drawArrays(gl.POINTS, 0, 1);\n\n```\nTo fill buffer with some data, we need to call `bufferData` method\n\n📄 src/webgl-hello-world.js\n```diff\n  const positionBuffer = gl.createBuffer(gl.ARRAY_BUFFER);\n  \n  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);\n+ gl.bufferData(gl.ARRAY_BUFFER, positionData);\n  \n  gl.drawArrays(gl.POINTS, 0, 1);\n\n```\nTo optimize buffer operations (memory management) on GPU side, we should pass a \"hint\" to GPU indicating how this buffer will be used. [There are several ways to use buffers](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bufferData#Parameters)\n\n-   `gl.STATIC_DRAW`: Contents of the buffer are likely to be used often and not change often. Contents are written to the buffer, but not read.\n-   `gl.DYNAMIC_DRAW`: Contents of the buffer are likely to be used often and change often. Contents are written to the buffer, but not read.\n-   `gl.STREAM_DRAW`: Contents of the buffer are likely to not be used often. Contents are written to the buffer, but not read.\n\n    When using a WebGL 2 context, the following values are available additionally:\n\n-   `gl.STATIC_READ`: Contents of the buffer are likely to be used often and not change often. Contents are read from the buffer, but not written.\n-   `gl.DYNAMIC_READ`: Contents of the buffer are likely to be used often and change often. Contents are read from the buffer, but not written.\n-   `gl.STREAM_READ`: Contents of the buffer are likely to not be used often. Contents are read from the buffer, but not written.\n-   `gl.STATIC_COPY`: Contents of the buffer are likely to be used often and not change often. Contents are neither written or read by the user.\n-   `gl.DYNAMIC_COPY`: Contents of the buffer are likely to be used often and change often. Contents are neither written or read by the user.\n-   `gl.STREAM_COPY`: Contents of the buffer are likely to be used often and not change often. Contents are neither written or read by the user.\n\n📄 src/webgl-hello-world.js\n```diff\n  const positionBuffer = gl.createBuffer(gl.ARRAY_BUFFER);\n  \n  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);\n- gl.bufferData(gl.ARRAY_BUFFER, positionData);\n+ gl.bufferData(gl.ARRAY_BUFFER, positionData, gl.STATIC_DRAW);\n  \n  gl.drawArrays(gl.POINTS, 0, 1);\n\n```\nNow we need to tell GPU how it should read the data from our buffer\n\nRequired info:\n\nAttribute size (2 in case of `vec2`, 3 in case of `vec3` etc)\n\n📄 src/webgl-hello-world.js\n```diff\n  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);\n  gl.bufferData(gl.ARRAY_BUFFER, positionData, gl.STATIC_DRAW);\n  \n+ const attributeSize = 2;\n+ \n  gl.drawArrays(gl.POINTS, 0, 1);\n\n```\ntype of data in buffer\n\n📄 src/webgl-hello-world.js\n```diff\n  gl.bufferData(gl.ARRAY_BUFFER, positionData, gl.STATIC_DRAW);\n  \n  const attributeSize = 2;\n+ const type = gl.FLOAT;\n  \n  gl.drawArrays(gl.POINTS, 0, 1);\n\n```\nnormalized – indicates if data values should be clamped to a certain range\n\nfor `gl.BYTE` and `gl.SHORT`, clamps the values to `[-1, 1]` if true\n\nfor `gl.UNSIGNED_BYTE` and `gl.UNSIGNED_SHORT`, clamps the values to `[0, 1]` if true\n\nfor types `gl.FLOAT` and `gl.HALF_FLOAT`, this parameter has no effect.\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  const attributeSize = 2;\n  const type = gl.FLOAT;\n+ const nomralized = false;\n  \n  gl.drawArrays(gl.POINTS, 0, 1);\n\n```\nWe'll talk about these two later 😉\n\n📄 src/webgl-hello-world.js\n```diff\n  const attributeSize = 2;\n  const type = gl.FLOAT;\n  const nomralized = false;\n+ const stride = 0;\n+ const offset = 0;\n  \n  gl.drawArrays(gl.POINTS, 0, 1);\n\n```\nNow we need to call `vertexAttribPointer` to setup our `position` attribute\n\n📄 src/webgl-hello-world.js\n```diff\n  const stride = 0;\n  const offset = 0;\n  \n+ gl.vertexAttribPointer(positionPointer, attributeSize, type, nomralized, stride, offset);\n+ \n  gl.drawArrays(gl.POINTS, 0, 1);\n\n```\nLet's try to change a position of the point\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  const positionPointer = gl.getAttribLocation(program, 'position');\n  \n- const positionData = new Float32Array([0, 0]);\n+ const positionData = new Float32Array([1.0, 0.0]);\n  \n  const positionBuffer = gl.createBuffer(gl.ARRAY_BUFFER);\n  \n\n```\nNothing changed 😢 But why?\n\nTurns out – all attributes are disabled by default (filled with 0), so we need to `enable` our position attrbiute\n\n📄 src/webgl-hello-world.js\n```diff\n  const stride = 0;\n  const offset = 0;\n  \n+ gl.enableVertexAttribArray(positionPointer);\n  gl.vertexAttribPointer(positionPointer, attributeSize, type, nomralized, stride, offset);\n  \n  gl.drawArrays(gl.POINTS, 0, 1);\n\n```\nNow we can render more points!\nLet's mark every corner of a canvas with a point\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  const positionPointer = gl.getAttribLocation(program, 'position');\n  \n- const positionData = new Float32Array([1.0, 0.0]);\n+ const positionData = new Float32Array([\n+     -1.0, // point 1 x\n+     -1.0, // point 1 y\n+ \n+     1.0, // point 2 x\n+     1.0, // point 2 y\n+ \n+     -1.0, // point 3 x\n+     1.0, // point 3 y\n+ \n+     1.0, // point 4 x\n+     -1.0, // point 4 y\n+ ]);\n  \n  const positionBuffer = gl.createBuffer(gl.ARRAY_BUFFER);\n  \n  gl.enableVertexAttribArray(positionPointer);\n  gl.vertexAttribPointer(positionPointer, attributeSize, type, nomralized, stride, offset);\n  \n- gl.drawArrays(gl.POINTS, 0, 1);\n+ gl.drawArrays(gl.POINTS, 0, positionData.length / 2);\n\n```\nLet's get back to our shader\n\nWe don't necessarily need to explicitly pass `position.x` and `position.y` to a `vec4` constructor, there is a `vec4(vec2, float, float)` override\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  void main() {\n      gl_PointSize = 20.0;\n-     gl_Position = vec4(position.x, position.y, 0, 1);\n+     gl_Position = vec4(position, 0, 1);\n  }\n  `;\n  \n  const positionPointer = gl.getAttribLocation(program, 'position');\n  \n  const positionData = new Float32Array([\n-     -1.0, // point 1 x\n-     -1.0, // point 1 y\n+     -1.0, // top left x\n+     -1.0, // top left y\n  \n      1.0, // point 2 x\n      1.0, // point 2 y\n\n```\nNow let's move all points closer to the center by dividing each position by 2.0\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  void main() {\n      gl_PointSize = 20.0;\n-     gl_Position = vec4(position, 0, 1);\n+     gl_Position = vec4(position / 2.0, 0, 1);\n  }\n  `;\n  \n\n```\nResult:\n\n![Result](https://git-tutor-assets.s3.eu-west-2.amazonaws.com/4points.png)\n\n### Conclusion\n\nWe now have a better understanding of how does GPU and WebGL work and can render something very basic\nWe'll explore more primitive types tomorrow!\n\n### Homework\n\nRender a `Math.cos` graph with dots\nHint: all you need is fill `positionData` with valid values\n\n\n[Subscribe](https://twitter.com/lesnitsky_a) for updates or [join mailing list](http://eepurl.com/gwiSeH)\n\n[Soruce code available here](https://github.com/lesnitsky/webgl-month)\n\n![GitHub stars](https://img.shields.io/github/stars/lesnitsky/webgl-month.svg?style=social)\n\n\u003e Built with [GitTutor](https://github.com/lesnitsky/git-tutor)\n\n\n## Day 3. Shader uniforms, lines and triangles\n\nThis is a series of blog posts related to WebGL. New post will be available every day\n\n[Subscribe](https://twitter.com/lesnitsky_a) for updates or [join mailing list](http://eepurl.com/gwiSeH)\n\n[Soruce code available here](https://github.com/lesnitsky/webgl-month)\n\n![GitHub stars](https://img.shields.io/github/stars/lesnitsky/webgl-month.svg?style=social)\n\n\u003e Built with [GitTutor](https://github.com/lesnitsky/git-tutor)\n\n[Yesterday](https://dev.to/lesnitsky/shaders-and-points-3h2c) we draw the simplies primitive possible – point. Let's first solve the \"homework\"\n\n\nWe need to remove hardcoded points data\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  const positionPointer = gl.getAttribLocation(program, 'position');\n  \n- const positionData = new Float32Array([\n-     -1.0, // top left x\n-     -1.0, // top left y\n- \n-     1.0, // point 2 x\n-     1.0, // point 2 y\n- \n-     -1.0, // point 3 x\n-     1.0, // point 3 y\n- \n-     1.0, // point 4 x\n-     -1.0, // point 4 y\n- ]);\n+ const points = [];\n+ const positionData = new Float32Array(points);\n  \n  const positionBuffer = gl.createBuffer(gl.ARRAY_BUFFER);\n  \n\n```\nIterate over each vertical line of pixels of canvas `[0..width]`\n\n📄 src/webgl-hello-world.js\n```diff\n  const positionPointer = gl.getAttribLocation(program, 'position');\n  \n  const points = [];\n+ \n+ for (let i = 0; i \u003c canvas.width; i++) {\n+ \n+ }\n+ \n  const positionData = new Float32Array(points);\n  \n  const positionBuffer = gl.createBuffer(gl.ARRAY_BUFFER);\n\n```\nTransform value from `[0..width]` to `[-1..1]` (remember webgl coordinat grid? this is left most and right most coordinates)\n\n📄 src/webgl-hello-world.js\n```diff\n  const points = [];\n  \n  for (let i = 0; i \u003c canvas.width; i++) {\n- \n+     const x = i / canvas.width * 2 - 1;\n  }\n  \n  const positionData = new Float32Array(points);\n\n```\nCalculate `cos` and add both x and y to `points` array\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  for (let i = 0; i \u003c canvas.width; i++) {\n      const x = i / canvas.width * 2 - 1;\n+     const y = Math.cos(x * Math.PI);\n+ \n+     points.push(x, y);\n  }\n  \n  const positionData = new Float32Array(points);\n\n```\nGraph looks a bit weird, let's fix our vertex shader\n\n📄 src/webgl-hello-world.js\n```diff\n  attribute vec2 position;\n  \n  void main() {\n-     gl_PointSize = 20.0;\n-     gl_Position = vec4(position / 2.0, 0, 1);\n+     gl_PointSize = 2.0;\n+     gl_Position = vec4(position, 0, 1);\n  }\n  `;\n  \n\n```\nNiiiice 😎 We now have fancy cos graph!\n\n![Cos graph](https://git-tutor-assets.s3.eu-west-2.amazonaws.com/cos-graph.png)\n\n\nWe calculated `cos` with JavaScript, but if we need to calculate something for a large dataset, javascript may block rendering thread. Why won't facilitate computation power of GPU (cos will be calculated for each point in parallel).\n\nGLSL doesn't have `Math` namespace, so we'll need to define `M_PI` variable\n`cos` function is there though 😏\n\n📄 src/webgl-hello-world.js\n```diff\n  const vShaderSource = `\n  attribute vec2 position;\n  \n+ #define M_PI 3.1415926535897932384626433832795\n+ \n  void main() {\n      gl_PointSize = 2.0;\n-     gl_Position = vec4(position, 0, 1);\n+     gl_Position = vec4(position.x, cos(position.y * M_PI), 0, 1);\n  }\n  `;\n  \n  \n  for (let i = 0; i \u003c canvas.width; i++) {\n      const x = i / canvas.width * 2 - 1;\n-     const y = Math.cos(x * Math.PI);\n- \n-     points.push(x, y);\n+     points.push(x, x);\n  }\n  \n  const positionData = new Float32Array(points);\n\n```\nWe have another JavaScript computation inside cycle where we transform pixel coordinates to `[-1..1]` range\nHow do we move this to GPU?\nWe've learned that we can pass some data to a shader with `attribute`, but `width` is constant, it doesn't change between points.\n\nThere is a special kind of variables – `uniforms`. Treat uniform as a global variable which can be assigned only once before draw call and stays the same for all \"points\"\n\n\nLet's define a `uniform`\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  const vShaderSource = `\n  attribute vec2 position;\n+ uniform float width;\n  \n  #define M_PI 3.1415926535897932384626433832795\n  \n\n```\nTo assign a value to a uniform, we'll need to do smth similar to what we did with attribute. We need to get location of the uniform.\n\n📄 src/webgl-hello-world.js\n```diff\n  gl.useProgram(program);\n  \n  const positionPointer = gl.getAttribLocation(program, 'position');\n+ const widthUniformLocation = gl.getUniformLocation(program, 'width');\n  \n  const points = [];\n  \n\n```\nThere's a bunch of methods which can assign different types of values to uniforms\n\n* `gl.uniform1f` – assigns a number to a float uniform (`gl.uniform1f(0.0)`)\n* `gl.uniform1fv` – assigns an array of length 1 to a float uniform (`gl.uniform1fv([0.0])`)\n* `gl.uniform2f` - assigns two numbers to a vec2 uniform (`gl.uniform2f(0.0, 1.0)`)\n* `gl.uniform2f` - assigns an array of length 2 to a vec2 uniform (`gl.uniform2fv([0.0, 1.0])`)\n\netc\n\n📄 src/webgl-hello-world.js\n```diff\n  const positionPointer = gl.getAttribLocation(program, 'position');\n  const widthUniformLocation = gl.getUniformLocation(program, 'width');\n  \n+ gl.uniform1f(widthUniformLocation, canvas.width);\n+ \n  const points = [];\n  \n  for (let i = 0; i \u003c canvas.width; i++) {\n\n```\nAnd finally let's move our js computation to a shader\n\n📄 src/webgl-hello-world.js\n```diff\n  #define M_PI 3.1415926535897932384626433832795\n  \n  void main() {\n+     float x = position.x / width * 2.0 - 1.0;\n      gl_PointSize = 2.0;\n-     gl_Position = vec4(position.x, cos(position.y * M_PI), 0, 1);\n+     gl_Position = vec4(x, cos(x * M_PI), 0, 1);\n  }\n  `;\n  \n  const points = [];\n  \n  for (let i = 0; i \u003c canvas.width; i++) {\n-     const x = i / canvas.width * 2 - 1;\n-     points.push(x, x);\n+     points.push(i, i);\n  }\n  \n  const positionData = new Float32Array(points);\n\n```\n### Rendering lines\n\nNow let's try to render lines\n\nWe need to fill our position data with line starting and ending point coordinates\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  gl.uniform1f(widthUniformLocation, canvas.width);\n  \n- const points = [];\n+ const lines = [];\n+ let prevLineY = 0;\n  \n- for (let i = 0; i \u003c canvas.width; i++) {\n-     points.push(i, i);\n+ for (let i = 0; i \u003c canvas.width - 5; i += 5) {\n+     lines.push(i, prevLineY);\n+     const y =  Math.random() * canvas.height;\n+     lines.push(i + 5, y);\n+ \n+     prevLineY = y;\n  }\n  \n- const positionData = new Float32Array(points);\n+ const positionData = new Float32Array(lines);\n  \n  const positionBuffer = gl.createBuffer(gl.ARRAY_BUFFER);\n  \n\n```\nWe'll also need to transform `y` to a WebGL clipspace, so let's pass a resolution of canvas, not just width\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  const vShaderSource = `\n  attribute vec2 position;\n- uniform float width;\n+ uniform vec2 resolution;\n  \n  #define M_PI 3.1415926535897932384626433832795\n  \n  void main() {\n-     float x = position.x / width * 2.0 - 1.0;\n+     vec2 transformedPosition = position / resolution * 2.0 - 1.0;\n      gl_PointSize = 2.0;\n-     gl_Position = vec4(x, cos(x * M_PI), 0, 1);\n+     gl_Position = vec4(transformedPosition, 0, 1);\n  }\n  `;\n  \n  gl.useProgram(program);\n  \n  const positionPointer = gl.getAttribLocation(program, 'position');\n- const widthUniformLocation = gl.getUniformLocation(program, 'width');\n+ const resolutionUniformLocation = gl.getUniformLocation(program, 'resolution');\n  \n- gl.uniform1f(widthUniformLocation, canvas.width);\n+ gl.uniform2fv(resolutionUniformLocation, [canvas.width, canvas.height]);\n  \n  const lines = [];\n  let prevLineY = 0;\n\n```\nThe final thing – we need to change primitive type to `gl.LINES`\n\n📄 src/webgl-hello-world.js\n```diff\n  gl.enableVertexAttribArray(positionPointer);\n  gl.vertexAttribPointer(positionPointer, attributeSize, type, nomralized, stride, offset);\n  \n- gl.drawArrays(gl.POINTS, 0, positionData.length / 2);\n+ gl.drawArrays(gl.LINES, 0, positionData.length / 2);\n\n```\nCool! We can render lines now 👍\n\n![Lines](https://git-tutor-assets.s3.eu-west-2.amazonaws.com/line-graph.png)\n\nLet's try to make the line a bit thicker\n\n\nUnlike point size, line width should be set from javascript. There is a method `gl.lineWidth(width)`\n\nLet's try to use it\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);\n  gl.bufferData(gl.ARRAY_BUFFER, positionData, gl.STATIC_DRAW);\n+ gl.lineWidth(10);\n  \n  const attributeSize = 2;\n  const type = gl.FLOAT;\n\n```\nNothing changed 😢 But why??\n\nThat's why 😂\n\n![Line browser support](https://git-tutor-assets.s3.eu-west-2.amazonaws.com/line-width-support.png)\n\nNobody cares.\n\nSo if you need a fancy line with custom line cap – `gl.LINES` is not for you\n\n\nBut how do we render fancy line?\n\nTurns out – everything could be rendered with help of next WebGL primitive – triangle.\nThis is the last primitive which could be rendered with WebGL\n\nBuilding a line of custom width from triangle might seem like a tough task, but don't worry, there are a lot of packages that could help you render custom 2d shapes (and even svg)\n\nSome of these tools:\n\n- [svg-path-contours](https://github.com/mattdesl/svg-path-contours)\n- [cdt2d](https://www.npmjs.com/package/cdt2d)\n- [adaptive-bezier-curve](https://www.npmjs.com/package/adaptive-bezier-curve)\n\nand others\n\nFrom now on, remember: EVERYTHING, could be built with triangles and that's how rendering works\n\n1. Input – triangle vertices\n2. vertex shader – transform vertices to webgl clipspace\n3. Rasterization – calculate which pixels are inside of certain triangle\n4. Calculate color of each pixel\n\nHere's an illustration of this process from [https://opentechschool-brussels.github.io/intro-to-webGL-and-shaders/log1_graphic-pipeline](https://opentechschool-brussels.github.io/intro-to-webGL-and-shaders/log1_graphic-pipeline)\n\n![WebGL pipeline](https://opentechschool-brussels.github.io/intro-to-webGL-and-shaders/assets/log1_graphicPipeline.jpg)\n\n\u003e Disclamer: this is a simplified version of what's going on under the hood, [read this](https://www.khronos.org/opengl/wiki/Rendering_Pipeline_Overview) for more detailed explanation\n\n\nSo lets finally render a triangle\n\nAgain – we need to update our position data\n\n\nand change primitive type\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  gl.uniform2fv(resolutionUniformLocation, [canvas.width, canvas.height]);\n  \n- const lines = [];\n- let prevLineY = 0;\n+ const triangles = [\n+     0, 0, // v1 (x, y)\n+     canvas.width / 2, canvas.height, // v2 (x, y)\n+     canvas.width, 0, // v3 (x, y)\n+ ];\n  \n- for (let i = 0; i \u003c canvas.width - 5; i += 5) {\n-     lines.push(i, prevLineY);\n-     const y =  Math.random() * canvas.height;\n-     lines.push(i + 5, y);\n- \n-     prevLineY = y;\n- }\n- \n- const positionData = new Float32Array(lines);\n+ const positionData = new Float32Array(triangles);\n  \n  const positionBuffer = gl.createBuffer(gl.ARRAY_BUFFER);\n  \n  gl.enableVertexAttribArray(positionPointer);\n  gl.vertexAttribPointer(positionPointer, attributeSize, type, nomralized, stride, offset);\n  \n- gl.drawArrays(gl.LINES, 0, positionData.length / 2);\n+ gl.drawArrays(gl.TRIANGLES, 0, positionData.length / 2);\n\n```\nAnd one more thing... Let's pass a color from javascript instead of hardcoding it inside fragment shader.\n\nWe'll need to go through the same steps as for resolution uniform, but declare this uniform in fragment shader\n\n📄 src/webgl-hello-world.js\n```diff\n  `;\n  \n  const fShaderSource = `\n+     uniform vec4 color;\n+ \n      void main() {\n-         gl_FragColor = vec4(1, 0, 0, 1);\n+         gl_FragColor = color / 255.0;\n      }\n  `;\n  \n  \n  const positionPointer = gl.getAttribLocation(program, 'position');\n  const resolutionUniformLocation = gl.getUniformLocation(program, 'resolution');\n+ const colorUniformLocation = gl.getUniformLocation(program, 'color');\n  \n  gl.uniform2fv(resolutionUniformLocation, [canvas.width, canvas.height]);\n+ gl.uniform4fv(colorUniformLocation, [255, 0, 0, 255]);\n  \n  const triangles = [\n      0, 0, // v1 (x, y)\n\n```\nWait, what? An Error 🛑 😱\n\n```\nNo precision specified for (float)\n```\n\nWhat is that?\n\nTurns out that glsl shaders support different precision of float and you need to specify it.\nUsually `mediump` is both performant and precise, but sometimes you might want to use `lowp` or `highp`. But be careful, `highp` is not supported by some mobile GPUs and there is no guarantee you won't get any weird rendering artifacts withh high precesion\n\n📄 src/webgl-hello-world.js\n```diff\n  `;\n  \n  const fShaderSource = `\n+     precision mediump float;\n      uniform vec4 color;\n  \n      void main() {\n\n```\n### Homework\n\nRender different shapes using triangles:\n\n* rectangle\n* hexagon\n* circle\n\n\nSee you tomorrow 👋\n\n[Subscribe](https://twitter.com/lesnitsky_a) for updates or [join mailing list](http://eepurl.com/gwiSeH)\n\n[Soruce code available here](https://github.com/lesnitsky/webgl-month)\n\n![GitHub stars](https://img.shields.io/github/stars/lesnitsky/webgl-month.svg?style=social)\n\n\u003e Built with [GitTutor](https://github.com/lesnitsky/git-tutor)\n\n\n## Day 4. Shader varyings\n\nThis is a series of blog posts related to WebGL. New post will be available every day\n\n[Subscribe](https://twitter.com/lesnitsky_a) for updates or [join mailing list](http://eepurl.com/gwiSeH)\n\n[Soruce code available here](https://github.com/lesnitsky/webgl-month)\n\n![GitHub stars](https://img.shields.io/github/stars/lesnitsky/webgl-month.svg?style=social)\n\n\u003e Built with [GitTutor](https://github.com/lesnitsky/git-tutor)\n\n\n[Yesterday](https://dev.to/lesnitsky/webgl-month-day-3-shader-uniforms-lines-and-triangles-5dof) we learned how to render lines and triangles, so let's get started with the homework\n\nHow do we draw a rectangle if webgl can only render triangles? We should split a rectangle into two triangles\n\n```\n-------\n|    /|\n|  /  |\n|/    |\n-------\n```\n\nPretty simple, right?\n\n\nLet's define the coordinates of triangle vertices\n\n📄 src/webgl-hello-world.js\n```diff\n  gl.uniform4fv(colorUniformLocation, [255, 0, 0, 255]);\n  \n  const triangles = [\n-     0, 0, // v1 (x, y)\n-     canvas.width / 2, canvas.height, // v2 (x, y)\n-     canvas.width, 0, // v3 (x, y)\n+     // first triangle\n+     0, 150, // top left\n+     150, 150, // top right\n+     0, 0, // bottom left\n+     \n+     // second triangle\n+     0, 0, // bottom left\n+     150, 150, // top right\n+     150, 0, // bottom right\n  ];\n  \n  const positionData = new Float32Array(triangles);\n\n```\n![Rectangle](https://git-tutor-assets.s3.eu-west-2.amazonaws.com/rectangle.png)\n\nGreat, we can render rectangles now!\n\n\nNow let's draw a hexagon. This is somewhat harder to draw manually, so let's create a helper function\n\n📄 src/webgl-hello-world.js\n```diff\n      150, 0, // bottom right\n  ];\n  \n+ function createHexagon(center, radius, segmentsCount) {\n+     \n+ }\n+ \n  const positionData = new Float32Array(triangles);\n  \n  const positionBuffer = gl.createBuffer(gl.ARRAY_BUFFER);\n\n```\nWe need to iterate over (360 - segment angle) degrees with a step of a signle segment angle\n\n📄 src/webgl-hello-world.js\n```diff\n  gl.uniform2fv(resolutionUniformLocation, [canvas.width, canvas.height]);\n  gl.uniform4fv(colorUniformLocation, [255, 0, 0, 255]);\n  \n- const triangles = [\n-     // first triangle\n-     0, 150, // top left\n-     150, 150, // top right\n-     0, 0, // bottom left\n-     \n-     // second triangle\n-     0, 0, // bottom left\n-     150, 150, // top right\n-     150, 0, // bottom right\n- ];\n- \n- function createHexagon(center, radius, segmentsCount) {\n-     \n+ const triangles = [createHexagon()];\n+ \n+ function createHexagon(centerX, centerY, radius, segmentsCount) {\n+     const vertices = [];\n+ \n+     for (let i = 0; i \u003c Math.PI * 2; i += Math.PI * 2 / (segmentsCount - 1)) {\n+         \n+     }\n+ \n+     return vertices;\n  }\n  \n  const positionData = new Float32Array(triangles);\n\n```\nAnd apply some simple school math\n\n![Hexagon](https://git-tutor-assets.s3.eu-west-2.amazonaws.com/hexagon.png)\n\n📄 src/webgl-hello-world.js\n```diff\n  gl.uniform2fv(resolutionUniformLocation, [canvas.width, canvas.height]);\n  gl.uniform4fv(colorUniformLocation, [255, 0, 0, 255]);\n  \n- const triangles = [createHexagon()];\n+ const triangles = createHexagon(canvas.width / 2, canvas.height / 2, canvas.height / 2, 6);\n  \n  function createHexagon(centerX, centerY, radius, segmentsCount) {\n      const vertices = [];\n+     const segmentAngle =  Math.PI * 2 / (segmentsCount - 1);\n  \n-     for (let i = 0; i \u003c Math.PI * 2; i += Math.PI * 2 / (segmentsCount - 1)) {\n-         \n+     for (let i = 0; i \u003c Math.PI * 2; i += segmentAngle) {\n+         const from = i;\n+         const to = i + segmentAngle;\n+ \n+         vertices.push(centerX, centerY);\n+         vertices.push(centerX + Math.cos(from) * radius, centerY + Math.sin(from) * radius);\n+         vertices.push(centerX + Math.cos(to) * radius, centerY + Math.sin(to) * radius);\n      }\n  \n      return vertices;\n\n```\nNow how do we render circle?\nActually a circle can be built with the same function, we just need to increase the number of \"segments\"\n\n📄 src/webgl-hello-world.js\n```diff\n  gl.uniform2fv(resolutionUniformLocation, [canvas.width, canvas.height]);\n  gl.uniform4fv(colorUniformLocation, [255, 0, 0, 255]);\n  \n- const triangles = createHexagon(canvas.width / 2, canvas.height / 2, canvas.height / 2, 6);\n+ const triangles = createHexagon(canvas.width / 2, canvas.height / 2, canvas.height / 2, 360);\n  \n  function createHexagon(centerX, centerY, radius, segmentsCount) {\n      const vertices = [];\n\n```\n![Circle](https://git-tutor-assets.s3.eu-west-2.amazonaws.com/circle.png)\n\n\n## Varyings\n\nOk, what next? Let's add some color 🎨\nAs we already know, we can pass a color to a fragment shader via `uniform`\nBut that's not the only way.\nVertex shader can pass a `varying` to a fragment shader for each vertex, and the value will be interpolated\n\nSounds a bit complicated, let's see how it works\n\n\nWe need to define a `varying` in both vertex and fragment shaders.\nMake sure type matches. If e.g. varying will be `vec3` in vertex shader and `vec4` in fragment shader – `gl.linkProgram(program)` will fail. You can check if program was successfully linked with `gl.getProgramParameter(program, gl.LINK_STATUS)` and if it is false – `gl.getProgramInfoLog(program)` to see what went wrang\n\n📄 src/webgl-hello-world.js\n```diff\n  attribute vec2 position;\n  uniform vec2 resolution;\n  \n+ varying vec4 vColor;\n+ \n  #define M_PI 3.1415926535897932384626433832795\n  \n  void main() {\n      vec2 transformedPosition = position / resolution * 2.0 - 1.0;\n      gl_PointSize = 2.0;\n      gl_Position = vec4(transformedPosition, 0, 1);\n+ \n+     vColor = vec4(255, 0, 0, 255);\n  }\n  `;\n  \n  const fShaderSource = `\n      precision mediump float;\n-     uniform vec4 color;\n+ \n+     varying vec4 vColor;\n  \n      void main() {\n-         gl_FragColor = color / 255.0;\n+         gl_FragColor = vColor / 255.0;\n      }\n  `;\n  \n  \n  const positionPointer = gl.getAttribLocation(program, 'position');\n  const resolutionUniformLocation = gl.getUniformLocation(program, 'resolution');\n- const colorUniformLocation = gl.getUniformLocation(program, 'color');\n  \n  gl.uniform2fv(resolutionUniformLocation, [canvas.width, canvas.height]);\n- gl.uniform4fv(colorUniformLocation, [255, 0, 0, 255]);\n  \n  const triangles = createHexagon(canvas.width / 2, canvas.height / 2, canvas.height / 2, 360);\n  \n\n```\nNow let's try to colorize our circle based on `gl_Position`\n\n📄 src/webgl-hello-world.js\n```diff\n      gl_PointSize = 2.0;\n      gl_Position = vec4(transformedPosition, 0, 1);\n  \n-     vColor = vec4(255, 0, 0, 255);\n+     vColor = vec4((gl_Position.xy + 1.0 / 2.0) * 255.0, 0, 255);\n  }\n  `;\n  \n\n```\n![Colorized circle](https://git-tutor-assets.s3.eu-west-2.amazonaws.com/colorized-circle.png)\n\nLooks cool, right?\n\nBut how do we pass some specific colors from js?\n\n\nWe need to create another attribute\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  const vShaderSource = `\n  attribute vec2 position;\n+ attribute vec4 color;\n  uniform vec2 resolution;\n  \n  varying vec4 vColor;\n      gl_PointSize = 2.0;\n      gl_Position = vec4(transformedPosition, 0, 1);\n  \n-     vColor = vec4((gl_Position.xy + 1.0 / 2.0) * 255.0, 0, 255);\n+     vColor = color;\n  }\n  `;\n  \n  \n  gl.useProgram(program);\n  \n- const positionPointer = gl.getAttribLocation(program, 'position');\n+ const positionLocation = gl.getAttribLocation(program, 'position');\n+ const colorLocation = gl.getAttribLocation(program, 'color');\n+ \n  const resolutionUniformLocation = gl.getUniformLocation(program, 'resolution');\n  \n  gl.uniform2fv(resolutionUniformLocation, [canvas.width, canvas.height]);\n  const stride = 0;\n  const offset = 0;\n  \n- gl.enableVertexAttribArray(positionPointer);\n- gl.vertexAttribPointer(positionPointer, attributeSize, type, nomralized, stride, offset);\n+ gl.enableVertexAttribArray(positionLocation);\n+ gl.vertexAttribPointer(positionLocation, attributeSize, type, nomralized, stride, offset);\n  \n  gl.drawArrays(gl.TRIANGLES, 0, positionData.length / 2);\n\n```\nSetup buffer for this attribute\n\n📄 src/webgl-hello-world.js\n```diff\n  }\n  \n  const positionData = new Float32Array(triangles);\n+ const colorData = new Float32Array(colors);\n  \n  const positionBuffer = gl.createBuffer(gl.ARRAY_BUFFER);\n+ const colorBuffer = gl.createBuffer(gl.ARRAY_BUFFER);\n+ \n+ gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);\n+ gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.STATIC_DRAW);\n  \n  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);\n  gl.bufferData(gl.ARRAY_BUFFER, positionData, gl.STATIC_DRAW);\n\n```\nFill buffer with data\n\n📄 src/webgl-hello-world.js\n```diff\n  gl.uniform2fv(resolutionUniformLocation, [canvas.width, canvas.height]);\n  \n  const triangles = createHexagon(canvas.width / 2, canvas.height / 2, canvas.height / 2, 360);\n+ const colors = fillWithColors(360);\n  \n  function createHexagon(centerX, centerY, radius, segmentsCount) {\n      const vertices = [];\n      return vertices;\n  }\n  \n+ function fillWithColors(segmentsCount) {\n+     const colors = [];\n+ \n+     for (let i = 0; i \u003c segmentsCount; i++) {\n+         for (let j = 0; j \u003c 3; j++) {\n+             if (j == 0) { // vertex in center of circle\n+                 colors.push(0, 0, 0, 255);\n+             } else {\n+                 colors.push(i / 360 * 255, 0, 0, 255);\n+             }\n+         }\n+     }\n+ \n+     return colors;\n+ }\n+ \n  const positionData = new Float32Array(triangles);\n  const colorData = new Float32Array(colors);\n  \n\n```\nAnd setup the attribute pointer (the way how attribute reads data from the buffer).\n\n📄 src/webgl-hello-world.js\n```diff\n  gl.enableVertexAttribArray(positionLocation);\n  gl.vertexAttribPointer(positionLocation, attributeSize, type, nomralized, stride, offset);\n  \n+ gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);\n+ \n+ gl.enableVertexAttribArray(colorLocation);\n+ gl.vertexAttribPointer(colorLocation, 4, type, nomralized, stride, offset);\n+ \n  gl.drawArrays(gl.TRIANGLES, 0, positionData.length / 2);\n\n```\nNotice this `gl.bindBuffer` before attribute related calls. `gl.vertexAttribPointer` points attribute to a buffer which wa most recently bound, don't forget this step, this is a common mistake\n\n\n![Colored circle](https://git-tutor-assets.s3.eu-west-2.amazonaws.com/colored-circle-2.png)\n\n### Conclusion\n\nWe've learned another way to pass data to a fragment shader.\nThis is useful for per vertex colors and textures (we'll work with textures later)\n\n### Homework\n\nRender a 7-gon and colorize each triangle with colors of rainbow 🌈\n\nSee you tomorrow 👋\n\n---\n\n[Subscribe](https://twitter.com/lesnitsky_a) for updates or [join mailing list](http://eepurl.com/gwiSeH)\n\n[Soruce code available here](https://github.com/lesnitsky/webgl-month)\n\n![GitHub stars](https://img.shields.io/github/stars/lesnitsky/webgl-month.svg?style=social)\n\n\u003e Built with [GitTutor](https://github.com/lesnitsky/git-tutor)\n\n\n## Day 5. Interleaved buffers\n\nThis is a series of blog posts related to WebGL. New post will be available every day\n\n[Subscribe](https://twitter.com/lesnitsky_a) for updates or [join mailing list](http://eepurl.com/gwiSeH)\n\n[Source code available here](https://github.com/lesnitsky/webgl-month)\n\n![GitHub stars](https://img.shields.io/github/stars/lesnitsky/webgl-month.svg?style=social)\n\n\u003e Built with [GitTutor](https://github.com/lesnitsky/git-tutor)\n\n\nHey 👋 Welcome to a WebGL month. [Yesterday](https://dev.to/lesnitsky/shader-varyings-2p0f) we've learned how to use varyings. Today we're going to explore one more concept, but let's solve a homework from yesterday first\n\n\nWe need to define raingbow colors first\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  gl.uniform2fv(resolutionUniformLocation, [canvas.width, canvas.height]);\n  \n+ const rainbowColors = [\n+     [255, 0.0, 0.0, 255], // red\n+     [255, 165, 0.0, 255], // orange\n+     [255, 255, 0.0, 255], // yellow\n+     [0.0, 255, 0.0, 255], // green\n+     [0.0, 101, 255, 255], // skyblue\n+     [0.0, 0.0, 255, 255], // blue,\n+     [128, 0.0, 128, 255], // purple\n+ ];\n+ \n  const triangles = createHexagon(canvas.width / 2, canvas.height / 2, canvas.height / 2, 360);\n  const colors = fillWithColors(360);\n  \n\n```\nRender a 7-gon\n\n📄 src/webgl-hello-world.js\n```diff\n      [128, 0.0, 128, 255], // purple\n  ];\n  \n- const triangles = createHexagon(canvas.width / 2, canvas.height / 2, canvas.height / 2, 360);\n- const colors = fillWithColors(360);\n+ const triangles = createHexagon(canvas.width / 2, canvas.height / 2, canvas.height / 2, 7);\n+ const colors = fillWithColors(7);\n  \n  function createHexagon(centerX, centerY, radius, segmentsCount) {\n      const vertices = [];\n\n```\nFill colors buffer with rainbow colors\n\n📄 src/webgl-hello-world.js\n```diff\n  \n      for (let i = 0; i \u003c segmentsCount; i++) {\n          for (let j = 0; j \u003c 3; j++) {\n-             if (j == 0) { // vertex in center of circle\n-                 colors.push(0, 0, 0, 255);\n-             } else {\n-                 colors.push(i / 360 * 255, 0, 0, 255);\n-             }\n+             colors.push(...rainbowColors[i]);\n          }\n      }\n  \n\n```\n![Rainbow](https://git-tutor-assets.s3.eu-west-2.amazonaws.com/rainbow.png)\n\nWhere's the red? Well, to render 7 polygons, we need 8-gon 🤦 My bad, sorry.\n\n\nNow we have a colored 8-gon and we store vertices coordinates and colors in two separate buffers.\nHaving two separate buffers allows to update them separately (imagine we need to change colors, but not positions)\n\nOn the other hand if both positions and colors will be the same – we can store this data in a single buffer.\n\nLet's refactor the code to acheive it\n\n\nWe need to structure our buffer data by attribute.\n\n```\nx1, y1, color.r, color.g, color.b, color.a\nx2, y2, color.r, color.g, color.b, color.a\nx3, y3, color.r, color.g, color.b, color.a\n...\n```\n\n📄 src/webgl-hello-world.js\n```diff\n  ];\n  \n  const triangles = createHexagon(canvas.width / 2, canvas.height / 2, canvas.height / 2, 7);\n- const colors = fillWithColors(7);\n  \n  function createHexagon(centerX, centerY, radius, segmentsCount) {\n-     const vertices = [];\n+     const vertexData = [];\n      const segmentAngle =  Math.PI * 2 / (segmentsCount - 1);\n  \n      for (let i = 0; i \u003c Math.PI * 2; i += segmentAngle) {\n          const from = i;\n          const to = i + segmentAngle;\n  \n-         vertices.push(centerX, centerY);\n-         vertices.push(centerX + Math.cos(from) * radius, centerY + Math.sin(from) * radius);\n-         vertices.push(centerX + Math.cos(to) * radius, centerY + Math.sin(to) * radius);\n+         const color = rainbowColors[i / segmentAngle];\n+ \n+         vertexData.push(centerX, centerY);\n+         vertexData.push(...color);\n+ \n+         vertexData.push(centerX + Math.cos(from) * radius, centerY + Math.sin(from) * radius);\n+         vertexData.push(...color);\n+ \n+         vertexData.push(centerX + Math.cos(to) * radius, centerY + Math.sin(to) * radius);\n+         vertexData.push(...color);\n      }\n  \n-     return vertices;\n+     return vertexData;\n  }\n  \n  function fillWithColors(segmentsCount) {\n\n```\nWe don't need color buffer anymore\n\n📄 src/webgl-hello-world.js\n```diff\n  }\n  \n  const positionData = new Float32Array(triangles);\n- const colorData = new Float32Array(colors);\n- \n  const positionBuffer = gl.createBuffer(gl.ARRAY_BUFFER);\n- const colorBuffer = gl.createBuffer(gl.ARRAY_BUFFER);\n- \n- gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);\n- gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.STATIC_DRAW);\n  \n  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);\n  gl.bufferData(gl.ARRAY_BUFFER, positionData, gl.STATIC_DRAW);\n\n```\nand it also makes sense to rename `positionData` and `positionBuffer` to a `vertexData` and `vertexBuffer`\n\n📄 src/webgl-hello-world.js\n```diff\n      return colors;\n  }\n  \n- const positionData = new Float32Array(triangles);\n- const positionBuffer = gl.createBuffer(gl.ARRAY_BUFFER);\n+ const vertexData = new Float32Array(triangles);\n+ const vertexBuffer = gl.createBuffer(gl.ARRAY_BUFFER);\n  \n- gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);\n- gl.bufferData(gl.ARRAY_BUFFER, positionData, gl.STATIC_DRAW);\n+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);\n+ gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW);\n  gl.lineWidth(10);\n  \n  const attributeSize = 2;\n\n```\nBut how do we specify how this data should be read from buffer and passed to a valid shader attributes\n\nWe can do this with `vertexAttribPointer`, `stride` and `offset` arguments\n\n`stride` tells how much data should be read for each vertex in bytes\n\nEach vertex contains:\n\n- position (x, y, 2 floats)\n- color (r, g, b, a, 4 floats)\n\nSo we have a total of `6` floats `4` bytes each\nThis means that stride is `6 * 4`\n\n\nOffset specifies how much data should be skipped in the beginning of the chunk\n\nColor data goes right after position, position is 2 floats, so offset for color is `2 * 4`\n\n📄 src/webgl-hello-world.js\n```diff\n  const attributeSize = 2;\n  const type = gl.FLOAT;\n  const nomralized = false;\n- const stride = 0;\n+ const stride = 24;\n  const offset = 0;\n  \n  gl.enableVertexAttribArray(positionLocation);\n  gl.vertexAttribPointer(positionLocation, attributeSize, type, nomralized, stride, offset);\n  \n- gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);\n- \n  gl.enableVertexAttribArray(colorLocation);\n- gl.vertexAttribPointer(colorLocation, 4, type, nomralized, stride, offset);\n+ gl.vertexAttribPointer(colorLocation, 4, type, nomralized, stride, 8);\n  \n- gl.drawArrays(gl.TRIANGLES, 0, positionData.length / 2);\n+ gl.drawArrays(gl.TRIANGLES, 0, vertexData.length / 6);\n\n```\nAnd voila, we have the same result, but with a single buffer 🎉\n\n\n### Conclusion\n\nLet's summarize how `vertexAttribPointer(location, size, type, normalized, stride offset)` method works for a single buffer (this buffer is called interleavd)\n\n- `location`: specifies which attribute do we want to setup\n- `size`: how much data should be read for this exact attribute\n- `type`: type of data being read\n- `normalized`: whether the data should be \"normalized\" (clamped to `[-1..1]` for gl.BYTE and gl.SHORT, and to `[0..1]` for gl.UNSIGNED_BYTE and gl.UNSIGNED_SHORT)\n- `stride`: how much data is there for each vertex in total (in bytes)\n- `offset`: how much data should be skipped in a beginning of each chunk of data\n\nSo now you can use different combinations of buffers to fill your attributes with data\n\nSee you tomorrow 👋\n\n---\n\nThis is a series of blog posts related to WebGL. New post will be available every day\n\n[Subscribe](https://twitter.com/lesnitsky_a) for updates or [join mailing list](http://eepurl.com/gwiSeH)\n\n[Source code available here](https://github.com/lesnitsky/webgl-month)\n\n![GitHub stars](https://img.shields.io/github/stars/lesnitsky/webgl-month.svg?style=social)\n\n\u003e Built with [GitTutor](https://github.com/lesnitsky/git-tutor)\n\n\n## Day 6. Index buffer\n\nThis is a series of blog posts related to WebGL. New post will be available every day\n\n[Subscribe](https://twitter.com/lesnitsky_a) for updates or [join mailing list](http://eepurl.com/gwiSeH)\n\n[Source code available here](https://github.com/lesnitsky/webgl-month)\n\n![GitHub stars](https://img.shields.io/github/stars/lesnitsky/webgl-month.svg?style=social)\n\n\u003e Built with [GitTutor](https://github.com/lesnitsky/git-tutor)\n\nHey 👋Welcome back to WebGL month. [Yesterday](https://dev.to/lesnitsky/webgl-month-day-5-interleaved-buffers-2k9a) we've learned how to use interleaved buffers. However our buffer contains a lot of duplicate data, because some polygons share the same vertices\n\n\nLet's get back to a simple example of rectangle\n\n📄 src/webgl-hello-world.js\n```diff\n      [128, 0.0, 128, 255], // purple\n  ];\n  \n- const triangles = createHexagon(canvas.width / 2, canvas.height / 2, canvas.height / 2, 7);\n+ const triangles = createRect(0, 0, canvas.height, canvas.height);\n  \n  function createHexagon(centerX, centerY, radius, segmentsCount) {\n      const vertexData = [];\n\n```\nand fill it only with unique vertex coordinates\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  const triangles = createRect(0, 0, canvas.height, canvas.height);\n  \n+ function createRect(top, left, width, height) {\n+     return [\n+         left, top, // x1 y1\n+         left + width, top, // x2 y2\n+         left, top + height, // x3 y3\n+         left + width, top + height, // x4 y4\n+     ];\n+ }\n+ \n  function createHexagon(centerX, centerY, radius, segmentsCount) {\n      const vertexData = [];\n      const segmentAngle =  Math.PI * 2 / (segmentsCount - 1);\n\n```\nLet's also disable color attribute for now\n\n📄 src/webgl-hello-world.js\n```diff\n  const attributeSize = 2;\n  const type = gl.FLOAT;\n  const nomralized = false;\n- const stride = 24;\n+ const stride = 0;\n  const offset = 0;\n  \n  gl.enableVertexAttribArray(positionLocation);\n  gl.vertexAttribPointer(positionLocation, attributeSize, type, nomralized, stride, offset);\n  \n- gl.enableVertexAttribArray(colorLocation);\n- gl.vertexAttribPointer(colorLocation, 4, type, nomralized, stride, 8);\n+ // gl.enableVertexAttribArray(colorLocation);\n+ // gl.vertexAttribPointer(colorLocation, 4, type, nomralized, stride, 8);\n  \n  gl.drawArrays(gl.TRIANGLES, 0, vertexData.length / 6);\n\n```\nOk, so our buffer contains 4 vertices, but how does webgl render 2 triangles with only 4 vertices?\nTHere's a special type of buffer which can specify how to fetch data from vertex buffer and build primitives (in our case triangles)\n\n\nThis buffer is called `index buffer` and it contains indices of vertex data chunks in vertex buffer.\nSo we need to specify indices of triangle vertices.\n\n📄 src/webgl-hello-world.js\n```diff\n  const vertexData = new Float32Array(triangles);\n  const vertexBuffer = gl.createBuffer(gl.ARRAY_BUFFER);\n  \n+ const indexBuffer = gl.createBuffer(gl.ARRAY_BUFFER);\n+ \n+ const indexData = new Uint6Array([\n+     0, 1, 2, // first triangle\n+     1, 2, 3, // second trianlge\n+ ]);\n+ \n  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);\n  gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW);\n  gl.lineWidth(10);\n\n```\nNext step – upload data to a WebGL buffer.\nTo tell GPU that we're using `index buffer` we need to pass `gl.ELEMENT_ARRAY_BUFFER` as a first argument of `gl.bindBuffer` and `gl.bufferData`\n\n📄 src/webgl-hello-world.js\n```diff\n      1, 2, 3, // second trianlge\n  ]);\n  \n+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);\n+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexData, gl.STATIC_DRAW);\n+ \n  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);\n  gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW);\n  gl.lineWidth(10);\n\n```\nAnd the final step: to render indexed vertices we need to call different method – `drawElements` instead of `drawArrays`\n\n📄 src/webgl-hello-world.js\n```diff\n  \n  const indexBuffer = gl.createBuffer(gl.ARRAY_BUFFER);\n  \n- const indexData = new Uint6Array([\n+ const indexData = new Uint8Array([\n      0, 1, 2, // first triangle\n      1, 2, 3, // second trianlge\n  ]);\n  // gl.enableVertexAttribArray(colorLocation);\n  // gl.vertexAttribPointer(colorLocation, 4, type, nomralized, stride, 8);\n  \n- gl.drawArrays(gl.TRIANGLES, 0, vertexData.length / 6);\n+ gl.drawElements(gl.TRIANGLES, indexData.length, gl.UNSIGNED_BYTE, 0);\n\n```\nWait, why is nothing rendered?\n\n\nThe reason is that we've disabled color attribute, so it got filled with zeros. (0, 0, 0, 0) – transparent black.\nLet's fix that\n\n📄 src/webgl-hello-world.js\n```diff\n  \n      void main() {\n          gl_FragColor = vColor / 255.0;\n+         gl_FragColor.a = 1.0;\n      }\n  `;\n  \n\n```\n### Conclusion\n\nWe now know how to use index buffer to eliminate number of vertices we need to upload to gpu.\nRectangle example is very simple (only 2 vertices are duplicated), on the other hand this is 33%, so on a larger amount of data being rendered this might be quite a performance improvement, especially if you update vertex data frequently and reupload buffer contents\n\n### Homework\n\nRender n-gon using index buffer\n\nSee you tomorrow 👋\n\n---\n\nThis is a series of blog posts related to WebGL. New post will be available every day\n\n[Subscribe](https://twitter.com/lesnitsky_a) for updates or [join mailing list](http://eepurl.com/gwiSeH)\n\n[Source code available here](https://github.com/lesnitsky/webgl-month)\n\n![GitHub stars](https://img.shields.io/github/stars/lesnitsky/webgl-month.svg?style=social)\n\n\u003e Built with [GitTutor](https://github.com/lesnitsky/git-tutor)\n\n\n## WebGL month. Day 7. Tooling and refactor\n\nThis is a series of blog posts related to WebGL. New post will be available every day\n\n[Subscribe](https://twitter.com/lesnitsky_a) for updates or [join mailing list](http://eepurl.com/gwiSeH)\n\n[Source code available here](https://github.com/lesnitsky/webgl-month)\n\n![GitHub stars](https://img.shields.io/github/stars/lesnitsky/webgl-month.svg?style=social)\n\n\u003e Built with [GitTutor](https://github.com/lesnitsky/git-tutor)\n\n---\n\nHey 👋\n\nWelcome to the WebGL month.\n\nSince our codebase grows and will keep getting more complicated, we need some tooling and cleanup.\n\n\nWe'll need webpack, so let's create `package.json` and install it\n\n📄 package.json\n```json\n{\n  \"name\": \"webgl-month\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Daily WebGL tutorials\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" \u0026\u0026 exit 1\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/lesnitsky/webgl-month.git\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"bugs\": {\n    \"url\": \"https://github.com/lesnitsky/webgl-month/issues\"\n  },\n  \"homepage\": \"https://github.com/lesnitsky/webgl-month#readme\",\n  \"devDependencies\": {\n    \"webpack\": \"^4.35.2\",\n    \"webpack-cli\": \"^3.3.5\"\n  }\n}\n\n```\nWe'll need a simple webpack config\n\n📄 webpack.config.js\n```js\nconst path = require('path');\n\nmodule.exports = {\n    entry: {\n        'week-1': './src/week-1.js',\n    },\n\n    output: {\n        path: path.resolve(__dirname, 'dist'),\n        filename: '[name].js',\n    },\n\n    mode: 'development',\n};\n\n```\nand update script source\n\n📄 index.html\n```diff\n    \u003c/head\u003e\n    \u003cbody\u003e\n      \u003ccanvas\u003e\u003c/canvas\u003e\n-     \u003cscript src=\"./src/webgl-hello-world.js\"\u003e\u003c/script\u003e\n+     \u003cscript src=\"./dist/week-1.js\"\u003e\u003c/script\u003e\n    \u003c/body\u003e\n  \u003c/html\u003e\n\n```\nSince shaders are raw strings, we can store shader source in separate file and use `raw-loader` for `webpack`.\n\n📄 package.json\n```diff\n    },\n    \"homepage\": \"https://github.com/lesnitsky/webgl-month#readme\",\n    \"devDependencies\": {\n+     \"raw-loader\": \"^3.0.0\",\n      \"webpack\": \"^4.35.2\",\n      \"webpack-cli\": \"^3.3.5\"\n    }\n\n```\n📄 webpack.config.js\n```diff\n          filename: '[name].js',\n      },\n  \n+     module: {\n+         rules: [\n+             {\n+                 test: /\\.glsl$/,\n+                 use: 'raw-loader',\n+             },\n+         ],\n+     },\n+ \n      mode: 'development',\n  };\n\n```\nand let's actually move shaders to separate files\n\n📄 src/shaders/fragment.glsl\n```glsl\nprecision mediump float;\n\nvarying vec4 vColor;\n\nvoid main() {\n    gl_FragColor = vColor / 255.0;\n    gl_FragColor.a = 1.0;\n}\n\n```\n📄 src/shaders/vertex.glsl\n```glsl\nattribute vec2 position;\nattribute vec4 color;\nuniform vec2 resolution;\n\nvarying vec4 vColor;\n\n#define M_PI 3.1415926535897932384626433832795\n\nvoid main() {\n    vec2 transformedPosition = position / resolution * 2.0 - 1.0;\n    gl_PointSize = 2.0;\n    gl_Position = vec4(transformedPosition, 0, 1);\n\n    vColor = color;\n}\n\n```\n📄 src/week-1.js\n```diff\n+ import vShaderSource from './shaders/vertex.glsl';\n+ import fShaderSource from './shaders/fragment.glsl';\n+ \n  const canvas = document.querySelector('canvas');\n  const gl = canvas.getContext('webgl');\n  \n  const vertexShader = gl.createShader(gl.VERTEX_SHADER);\n  const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);\n  \n- const vShaderSource = `\n- attribute vec2 position;\n- attribute vec4 color;\n- uniform vec2 resolution;\n- \n- varying vec4 vColor;\n- \n- #define M_PI 3.1415926535897932384626433832795\n- \n- void main() {\n-     vec2 transformedPosition = position / resolution * 2.0 - 1.0;\n-     gl_PointSize = 2.0;\n-     gl_Position = vec4(transformedPosition, 0, 1);\n- \n-     vColor = color;\n- }\n- `;\n- \n- const fShaderSource = `\n-     precision mediump float;\n- \n-     varying vec4 vColor;\n- \n-     void main() {\n-         gl_FragColor = vColor / 255.0;\n-         gl_FragColor.a = 1.0;\n-     }\n- `;\n- \n  function compileShader(shader, source) {\n      gl.shaderSource(shader, source);\n      gl.compileShader(shader);\n\n```\nWe can also move functions which create vertices positions to separate file\n\n📄 src/shape-helpers.js\n```js\nexport function createRect(top, left, width, height) {\n    return [\n        left, top, // x1 y1\n        left + width, top, // x2 y2\n        left, top + height, // x3 y3\n        left + width, top + height, // x4 y4\n    ];\n}\n\nexport function createHexagon(centerX, centerY, radius, segmentsCount) {\n    const vertexData = [];\n    const segmentAngle =  Math.PI * 2 / (segmentsCount - 1);\n\n    for (let i = 0; i \u003c Math.PI * 2; i += segmentAngle) {\n        const from = i;\n        const to = i + segmentAngle;\n\n        const color = rainbowColors[i / segmentAngle];\n\n        vertexData.push(centerX, centerY);\n        vertexData.push(...color);\n\n        vertexData.push(centerX + Math.cos(from) * radius, centerY + Math.sin(from) * radius);\n        vertexData.push(...color);\n\n        vertexData.push(centerX + Math.cos(to) * radius, centerY + Math.sin(to) * radius);\n        vertexData.push(...color);\n    }\n\n    return vertexData;\n}\n\n```\n📄 src/week-1.js\n```diff\n  import vShaderSource from './shaders/vertex.glsl';\n  import fShaderSource from './shaders/fragment.glsl';\n  \n+ import { createRect } from './shape-helpers';\n+ \n+ \n  const canvas = document.querySelector('canvas');\n  const gl = canvas.getContext('webgl');\n  \n  \n  const triangles = createRect(0, 0, canvas.height, canvas.height);\n  \n- function createRect(top, left, width, height) {\n-     return [\n-         left, top, // x1 y1\n-         left + width, top, // x2 y2\n-         left, top + height, // x3 y3\n-         left + width, top + height, // x4 y4\n-     ];\n- }\n- \n- function createHexagon(centerX, centerY, radius, segmentsCount) {\n-     const vertexData = [];\n-     const segmentAngle =  Math.PI * 2 / (segmentsCount - 1);\n- \n-     for (let i = 0; i \u003c Math.PI * 2; i += segmentAngle) {\n-         const from = i;\n-         const to = i + segmentAngle;\n- \n-         const color = rainbowColors[i / segmentAngle];\n- \n-         vertexData.push(centerX, centerY);\n-         vertexData.push(...color);\n- \n-         vertexData.push(centerX + Math.cos(from) * radius, centerY + Math.sin(from) * radius);\n-         vertexData.push(...color);\n- \n-         vertexData.push(centerX + Math.cos(to) * radius, centerY + Math.sin(to) * radius);\n-         vertexData.push(...color);\n-     }\n- \n-     return vertexData;\n- }\n- \n  function fillWithColors(segmentsCount) {\n      const colors = [];\n  \n\n```\nSince we're no longer using color attribute, we can drop everyhting related to it\n\n📄 src/shaders/fragment.glsl\n```diff\n  precision mediump float;\n  \n- varying vec4 vColor;\n- \n  void main() {\n-     gl_FragColor = vColor / 255.0;\n-     gl_FragColor.a = 1.0;\n+     gl_FragColor = vec4(1, 0, 0, 1);\n  }\n\n```\n📄 src/shaders/vertex.glsl\n```diff\n  attribute vec2 position;\n- attribute vec4 color;\n  uniform vec2 resolution;\n  \n- varying vec4 vColor;\n- \n  #define M_PI 3.1415926535897932384626433832795\n  \n  void main() {\n      vec2 transformedPosition = position / resolution * 2.0 - 1.0;\n      gl_PointSize = 2.0;\n      gl_Position = vec4(transformedPosition, 0, 1);\n- \n-     vColor = color;\n  }\n\n```\n📄 src/week-1.js\n```diff\n  \n  import { createRect } from './shape-helpers';\n  \n- \n  const canvas = document.querySelector('canvas');\n  const gl = canvas.getContext('webgl');\n  \n  gl.useProgram(program);\n  \n  const positionLocation = gl.getAttribLocation(program, 'position');\n- const colorLocation = gl.getAttribLocation(program, 'color');\n- \n  const resolutionUniformLocation = gl.getUniformLocation(program, 'resolution');\n  \n  gl.uniform2fv(resolutionUniformLocation, [canvas.width, canvas.height]);\n  \n- const rainbowColors = [\n-     [255, 0.0, 0.0, 255], // red\n-     [255, 165, 0.0, 255], // orange\n-     [255, 255, 0.0, 255], // yellow\n-     [0.0, 255, 0.0, 255], // green\n-     [0.0, 101, 255, 255], // skyblue\n-     [0.0, 0.0, 255, 255], // blue,\n-     [128, 0.0, 128, 255], // purple\n- ];\n- \n  const triangles = createRect(0, 0, canvas.height, canvas.height);\n  \n- function fillWithColors(segmentsCount) {\n-     const colors = [];\n- \n-     for (let i = 0; i \u003c segmentsCount; i++) {\n-         for (let j = 0; j \u003c 3; j++) {\n-             colors.push(...rainbowColors[i]);\n-         }\n-     }\n- \n-     return colors;\n- }\n- \n  const vertexData = new Float32Array(triangles);\n  const vertexBuffer = gl.createBuffer(gl.ARRAY_BUFFER);\n  \n  gl.enableVertexAttribArray(positionLocation);\n  gl.vertexAttribPointer(positionLocation, attributeSize, type, nomralized, stride, offset);\n  \n- // gl.enableVertexAttribArray(colorLocation);\n- // gl.vertexAttribPointer(colorLocation, 4, type, nomralized, stride, 8);\n- \n  gl.drawElements(gl.TRIANGLES, indexData.length, gl.UNSIGNED_BYTE, 0);\n\n```\nWebpack will help us keep our codebase cleaner in the future, but we're good for now\n\nSee you tomorrow 👋\n\n---\n\nThis is a series of blog posts related to WebGL. New post will be available every day\n\n[Subscribe](https://twitter.com/lesnitsky_a) for updates or [join mailing list](http://eepurl.com/gwiSeH)\n\n[Source code available here](https://github.com/lesnitsky/webgl-month)\n\n![GitHub stars](https://img.shields.io/github/stars/lesnitsky/webgl-month.svg?style=social)\n\n\u003e Built with [GitTutor](https://github.com/lesnitsky/git-tutor)\n\n\n## Day 8. Textures\n\nThis is a series of blog posts related to WebGL. New post will be available every day\n\n[Subscribe](https://twitter.com/lesnitsky_a) for updates or [join mailing list](http://eepurl.com/gwiSeH)\n\n[Source code available here](https://github.com/lesnitsky/webgl-month)\n\n![GitHub stars](https://img.shields.io/github/stars/lesnitsky/webgl-month.svg?style=social)\n\n\u003e Built with [GitTutor](https://github.com/lesnitsky/git-tutor)\n\n---\n\nHey 👋 Welcome back to WebGL month.\n\nWe've already learned several ways to pass color data to shader, but there is one more and it is very powerful. Today we'll learn about textures\n\n\nLet's create simple shaders\n\n📄 src/shaders/texture.f.glsl\n```glsl\nprecision mediump float;\n\nvoid main() {\n    gl_FragColor = vec4(1, 0, 0, 1);\n}\n\n```\n📄 src/shaders/texture.v.glsl\n```glsl\nattribute vec2 position;\n\nvoid main() {\n    gl_Position = vec4(position, 0, 1);\n}\n\n```\n📄 src/texture.js\n```js\nimport vShaderSource from './shaders/texture.v.glsl';\nimport fShaderSource from './shaders/texture.f.glsl';\n\n```\nGet the webgl context\n\n📄 src/texture.js\n```diff\n  import vShaderSource from './shaders/texture.v.glsl';\n  import fShaderSource from './shaders/texture.f.glsl';\n+ \n+ const canvas = document.querySelector('canvas');\n+ const gl = canvas.getContext('webgl');\n\n```\nCreate shaders\n\n📄 src/texture.js\n```diff\n  import vShaderSource from './shaders/texture.v.glsl';\n  import fShaderSource from './shaders/texture.f.glsl';\n+ import { compileShader } from './gl-helpers';\n  \n  const canvas = document.querySelector('canvas');\n  const gl = canvas.getContext('webgl');\n+ \n+ const vShader = gl.createShader(gl.VERTEX_SHADER);\n+ const fShader = gl.createShader(gl.FRAGMENT_SHADER);\n+ \n+ compileShader(gl, vShader, vShaderSource);\n+ compileShader(gl, fShader, fShaderSource);\n\n```\nand program\n\n📄 src/texture.js\n```diff\n  \n  compileShader(gl, vShader, vShaderSource);\n  compileShader(gl, fShader, fShaderSource);\n+ \n+ const program = gl.createProgram();\n+ \n+ gl.attachShader(program, vShader);\n+ gl.attachShader(program, fShader);\n+ \n+ gl.linkProgram(program);\n+ gl.useProgram(program);\n\n```\nCreate a vertex position buffer and fill it with data\n\n📄 src/texture.js\n```diff\n  import vShaderSource from './shaders/texture.v.glsl';\n  import fShaderSource from './shaders/texture.f.glsl';\n  import { compileShader } from './gl-helpers';\n+ import { createRect } from './shape-helpers';\n+ \n  \n  const canvas = document.querySelector('canvas');\n  const gl = canvas.getContext('webgl');\n  \n  gl.linkProgram(program);\n  gl.useProgram(program);\n+ \n+ const vertexPosition = new Float32Array(createRect(-1, -1, 2, 2));\n+ const vertexPositionBuffer = gl.createBuffer();\n+ \n+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);\n+ gl.bufferData(gl.ARRAY_BUFFER, vertexPosition, gl.STATIC_DRAW);\n\n```\nSetup position attribute\n\n📄 src/texture.js\n```diff\n  \n  gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);\n  gl.bufferData(gl.ARRAY_BUFFER, vertexPosition, gl.STATIC_DRAW);\n+ \n+ const attributeLocations = {\n+     position: gl.getAttribLocation(program, 'position'),\n+ };\n+ \n+ gl.enableVertexAttribArray(attributeLocations.position);\n+ gl.vertexAttribPointer(attributeLocations.position, 2, gl.FLOAT, false, 0, 0);\n\n```\nsetup index buffer\n\n📄 src/texture.js\n```diff\n  \n  gl.enableVertexAttribArray(attributeLocations.position);\n  gl.vertexAttribPointer(attributeLocations.position, 2, gl.FLOAT, false, 0, 0);\n+ \n+ const vertexIndices = new Uint8Array([0, 1, 2, 1, 2, 3]);\n+ const indexBuffer = gl.createBuffer();\n+ \n+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);\n+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, vertexIndices, gl.STATIC_DRAW);\n\n```\nand issue a draw call\n\n📄 src/texture.js\n```diff\n  \n  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);\n  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, vertexIndices, gl.STATIC_DRAW);\n+ \n+ gl.drawElements(gl.TRIANGLES, vertexIndices.length, gl.UNSIGNED_BYTE, 0);\n\n```\nSo now we can proceed to textures.\n\nYou can upload image to a GPU and use it to calculate pixel color. In a simple case, when canvas size is the same or at least proportional to image size, we can render image pixel by pixel reading each pixel color of image and using it as `gl_FragColor`\n\nLet's make a helper to load images\n\n📄 src/gl-helpers.js\n```diff\n          throw new Error(log);\n      }\n  }\n+ \n+ export async function loadImage(src) {\n+     const img = new Image();\n+ \n+     let _resolve;\n+     const p = new Promise((resolve) =\u003e _resolve = resolve);\n+ \n+     img.onload = () =\u003e {\n+         _resolve(img);\n+     }\n+ \n+     img.src = src;\n+ \n+     return p;\n+ }\n\n```\nLoad image and create webgl texture\n\n📄 src/texture.js\n```diff\n  import vShaderSource from './shaders/texture.v.glsl';\n  import fShaderSource from './shaders/texture.f.glsl';\n- import { compileShader } from './gl-helpers';\n+ import { compileShader, loadImage } from './gl-helpers';\n  import { createRect } from './shape-helpers';\n  \n+ import textureImageSrc from '../assets/images/texture.jpg';\n  \n  const canvas = document.querySelector('canvas');\n  const gl = canvas.getContext('webgl');\n  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);\n  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, vertexIndices, gl.STATIC_DRAW);\n  \n- gl.drawElements(gl.TRIANGLES, vertexIndices.length, gl.UNSIGNED_BYTE, 0);\n+ loadImage(textureImageSrc).then((textureImg) =\u003e {\n+     const texture = gl.createTexture();\n+ \n+     gl.drawElements(gl.TRIANGLES, vertexIndices.length, gl.UNSIGNED_BYTE, 0);\n+ });\n\n```\n[GTI} add image\n\n📄 assets/images/texture.jpg\n```jpg\n\n```\nwe also need an appropriate webpack loader\n\n📄 package.json\n```diff\n    \"homepage\": \"https://github.com/lesnitsky/webgl-month#readme\",\n    \"devDependencies\": {\n      \"raw-loader\": \"^3.0.0\",\n+     \"url-loader\": \"^2.0.1\",\n      \"webpack\": \"^4.35.2\",\n      \"webpack-cli\": \"^3.3.5\"\n    }\n\n```\n📄 webpack.config.js\n```diff\n                  test: /\\.glsl$/,\n                  use: 'raw-loader',\n              },\n+ \n+             {\n+                 test: /\\.jpg$/,\n+                 use: 'url-loader',\n+             },\n          ],\n      },\n  \n\n```\nto operate with textures we need to do the same as with buffers – bind it\n\n📄 src/texture.js\n```diff\n  loadImage(textureImageSrc).then((textureImg) =\u003e {\n      const texture = gl.createTexture();\n  \n+     gl.bindTexture(gl.TEXTURE_2D, texture);\n+ \n      gl.drawElements(gl.TRIANGLES, vertexIndices.length, gl.UNSIGNED_BYTE, 0);\n  });\n\n```\nand upload image to a bound texture\n\n📄 src/texture.js\n```diff\n  \n      gl.bindTexture(gl.TEXTURE_2D, texture);\n  \n+     gl.texImage2D(\n+         gl.TEXTURE_2D,\n+     );\n+ \n      gl.drawElements(gl.TRIANGLES, vertexIndices.length, gl.UNSIGNED_BYTE, 0);\n  });\n\n```\nLet's ignore the 2nd argument for now, we'll speak about it later\n\n📄 src/texture.js\n```diff\n  \n      gl.texImage2D(\n          gl.TEXTURE_2D,\n+         0,\n      );\n  \n      gl.drawElements(gl.TRIANGLES, vertexIndices.length, gl.UNSIGNED_BYTE, 0);\n\n```\nthe 3rd and the 4th argumetns specify internal texture format and source (image) format. For our image it is gl.RGBA. [Check out this page for more details about formats](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texImage2D)\n\n📄 src/texture.js\n```diff\n      gl.texImage2D(\n          gl.TEXTURE_2D,\n          0,\n+         gl.RGBA,\n+         gl.RGBA,\n      );\n  \n      gl.drawElements(gl.TRIANGLES, vertexIndices.length, gl.UNSIGNED_BYTE, 0);\n\n```\nnext argument specifies source type (0..255 is UNSIGNED_BYTE)\n\n📄 src/texture.js\n```diff\n          0,\n          gl.RGBA,\n          gl.RGBA,\n+         gl.UNSIGNED_BYTE,\n      );\n  \n      gl.drawElements(gl.TRIANGLES, vertexIndices.length, gl.UNSIGNED_BYTE, 0);\n\n```\nand image itself\n\n📄 src/texture.js\n```diff\n          gl.RGBA,\n          gl.RGBA,\n          gl.UNSIGNED_BYTE,\n+         textureImg,\n      );\n  \n      gl.drawElements(gl.TRIANGLES, vertexIndices.length, gl.UNSIGNED_BYTE, 0);\n\n```\nWe also need to specify different parameters of texture. We'll talk about this parameters in next tutorials.\n\n📄 src/texture.js\n```diff\n          textureImg,\n      );\n  \n+     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n+     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n+     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n+     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n+ \n      gl.drawElements(gl.TRIANGLES, vertexIndices.length, gl.UNSIGNED_BYTE, 0);\n  });\n\n```\nTo be able to work with texture in shader we need to specify a uniform of `sampler2D` type\n\n📄 src/shaders/texture.f.glsl\n```diff\n  precision mediump float;\n  \n+ uniform sampler2D texture;\n+ \n  void main() {\n      gl_FragColor = vec4(1, 0, 0, 1);\n  }\n\n```\nand specify the value of this uniform. There is a way to use multiple textures, we'll talk about it in next tutorials\n\n📄 src/texture.js\n```diff\n      position: gl.getAttribLocation(program, 'position'),\n  };\n  \n+ const uniformLocations = {\n+     texture: gl.getUniformLocation(program, 'texture'),\n+ };\n+ \n  gl.enableVertexAttribArray(attributeLocations.position);\n  gl.vertexAttribPointer(attributeLocations.position, 2, gl.FLOAT, false, 0, 0);\n  \n      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n  \n+     gl.activeTexture(gl.TEXTURE0);\n+     gl.uniform1i(uniformLocations.texture, 0);\n+ \n      gl.drawElements(gl.TRIANGLES, vertexIndices.length, gl.UNSIGNED_BYTE, 0);\n  });\n\n```\nLet's also pass canvas resolution to a shader\n\n📄 src/shaders/texture.f.glsl\n```diff\n  precision mediump float;\n  \n  uniform sampler2D texture;\n+ uniform vec2 resolution;\n  \n  void main() {\n      gl_FragColor = vec4(1, 0, 0, 1);\n\n```\n📄 src/texture.js\n```diff\n  \n  const uniformLocations = {\n      texture: gl.getUniformLocation(program, 'texture'),\n+     resolution: gl.getUniformLocation(program, 'resolution'),\n  };\n  \n  gl.enableVertexAttribArray(attributeLocations.position);\n      gl.activeTexture(gl.TEXTURE0);\n      gl.uniform1i(uniformLocations.texture, 0);\n  \n+     gl.uniform2fv(uniformLocations.resolution, [canvas.width, canvas.height]);\n+ \n      gl.drawElements(gl.TRIANGLES, vertexIndices.length, gl.UNSIGNED_BYTE, 0);\n  });\n\n```\nThere is a special `gl_FragCoord` variable which contains coordinate of each pixel. Together with `resolution` uniform we can get a `texture coordinate` (coordinate of the pixel in image). Texture coordinates are in range `[0..1]`.\n\n📄 src/shaders/texture.f.glsl\n```diff\n  uniform vec2 resolution;\n  \n  void main() {\n+     vec2 texCoord = gl_FragCoord.xy / resolution;\n      gl_FragColor = vec4(1, 0, 0, 1);\n  }\n\n```\nand use `texture2D` to render the whole image.\n\n📄 src/shaders/texture.f.glsl\n```diff\n  \n  void main() {\n      vec2 texCoord = gl_FragCoord.xy / resolution;\n-     gl_FragColor = vec4(1, 0, 0, 1);\n+     gl_FragColor = texture2D(texture, texCoord);\n  }\n\n```\nCool 😎 We can now render images, but there is much more to learn about textures, so see you tomorrow\n\n---\n\nThis is a series of blog posts related to WebGL. New post will be available every day\n\n[Subscribe](https://twitter.com/lesnitsky_a) for updates or [join mailing list](http://eepurl.com/gwiSeH)\n\n[Source code available here](https://github.com/lesnitsky/webgl-month)\n\n![GitHub stars](https://img.shields.io/github/stars/lesnitsky/webgl-month.svg?style=social)\n\n\u003e Built with [GitTutor](https://github.com/lesnitsky/git-tutor)\n\n\n## WebGL Month. Day 9. Image filters\n\nThis is a series of blog posts related to WebGL. New post will be available every day\n\n[Subscribe](https://twitter.com/lesnitsky_a) for updates or [join mailing list](http://eepurl.com/gwiSeH)\n\n[Source code available here](https://github.com/lesnitsky/webgl-month)\n\n![GitHub stars](https://img.shields.io/github/stars/lesnitsky/webgl-month.svg?style=social)\n\n\u003e Built with [GitTutor](https://github.com/lesnitsky/git-tutor)\n\n\nHey 👋 Welcome back to WebGL month\n\n[Yesterday](https://dev.to/lesnitsky/webgl-month-day-8-textures-1mk8) we've learned how to use textures in webgl, so let's get advantage of that knowledge to build something fun.\n\nToday we're going to explore how to implement simple image filters\n\n\n### Inverse\n\nThe very first and simple filter might be inverse all colors of the image.\n\nHow do we inverse colors?\n\nOriginal values are in range `[0..1]`\n\nIf we subtract from each component `1` we'll get negative values, there's an `abs` function in glsl\n\nYou can also define other functions apart of `void main` in glsl pretty much like in C/C++, so let's create `inverse` function\n\n📄 src/shaders/texture.f.glsl\n```diff\n  uniform sampler2D texture;\n  uniform vec2 resolution;\n  \n+ vec4 inverse(vec4 color) {\n+     return abs(vec4(color.rgb - 1.0, color.a));\n+ }\n+ \n  void main() {\n      vec2 texCoord = gl_FragCoord.xy / resolution;\n      gl_FragColor = texture2D(texture, texCoord);\n\n```\nand let's actually use it\n\n📄 src/shaders/texture.f.glsl\n```diff\n  void main() {\n      vec2 texCoord = gl_FragCoord.xy / resolution;\n      gl_FragColor = texture2D(texture, texCoord);\n+ \n+     gl_FragColor = inverse(gl_FragColor);\n  }\n\n```\nVoila, we have an inverse filter with just 4 lines of code\n\n![Inverse](https://git-tutor-assets.s3.eu-west-2.amazonaws.com/inverse-filter.png)\n\n\n### Black and White\n\nLet's think of how to implement black and white filter.\n\nWhite color is `vec4(1, 1, 1, 1)`\n\nBlack is `vec4(0, 0, 0, 1)`\n\nWhat are shades of gray? Aparently we need to add the same value to each color component.\n\nSo basically we need to calculate the \"brightness\" value of each component. In very naive implmentation we can just add all color components and divide by 3 (arithmetical mean).\n\n\u003e Note: this is not the best approach, as different colors will give the same result (eg. vec3(0.5, 0, 0) and vec3(0, 0.5, 0), but in reality these colors have different \"brightness\", I'm just trying to keep these examples simple to understand)\n\nOk, let's try to implement this\n\n📄 src/shaders/texture.f.glsl\n```diff\n      return abs(vec4(color.rgb - 1.0, color.a));\n  }\n  \n+ vec4 blackAndWhite(vec4 color) {\n+     return vec4(vec3(1.0, 1.0, 1.0) * (color.r + color.g + color.b) / 3.0, color.a);\n+ }\n+ \n  void main() {\n      vec2 texCoord = gl_FragCoord.xy / resolution;\n      gl_FragColor = texture2D(texture, texCoord);\n  \n-     gl_FragColor = inverse(gl_FragColor);\n+     gl_FragColor = blackAndWhite(gl_FragColor);\n  }\n\n```\nWhoa! Looks nice\n\n![Black and white](https://git-tutor-assets.s3.eu-west-2.amazonaws.com/black-and-white.png)\n\n\n### Sepia\n\nOk, one more fancy effect is a \"old-fashioned\" photos with sepia filter.\n\n[Sepia is reddish-brown color](https://en.wikipedia.org/wiki/Sepia_%28color%29). RGB values are `112, 66, 20`\n\n\nLet's define `sepia` function and color\n\n📄 src/shaders/texture.f.glsl\n```diff\n      return vec4(vec3(1.0, 1.0, 1.0) * (color.r + color.g + color.b) / 3.0, color.a);\n  }\n  \n+ vec4 sepia(vec4 color) {\n+     vec3 sepiaColor = vec3(112, 66, 20) / 255.0;\n+ }\n+ \n  void main() {\n      vec2 texCoord = gl_FragCoord.xy / resolution;\n      gl_FragColor = texture2D(texture, texCoord);\n\n```\nA naive and simple implementation will be to interpolate original color with sepia color by a certain factor. There is a `mix` function for this\n\n📄 src/shaders/texture.f.glsl\n```diff\n  \n  vec4 sepia(vec4 color) {\n      vec3 sepiaColor = vec3(112, 66, 20) / 255.0;\n+     return vec4(\n+         mix(color.rgb, sepiaColor, 0.4),\n+         color.a\n+     );\n  }\n  \n  void main() {\n      vec2 texCoord = gl_FragCoord.xy / resolution;\n      gl_FragColor = texture2D(texture, texCoord);\n  \n-     gl_FragColor = blackAndWhite(gl_FragColor);\n+     gl_FragColor = sepia(gl_FragColor);\n  }\n\n```\nResult:\n\n![Sepia](https://git-tutor-assets.s3.eu-west-2.amazonaws.com/sepia.png)\n\n\nThis should give you a better idea of what can be done in fragment shader.\n\nTry to implement some other filters, like saturation or vibrance\n\nSee you tomorrow 👋\n\n---\n\nThis is a series of blog posts related to WebGL. New post will be available every day\n\n[Subscribe](https://twitter.com/lesnitsky_a) for updates or [join mailing list](http://eepurl.com/gwiSeH)\n\n[Source code available here](https://github.com/lesnitsky/webgl-month)\n\n![GitHub stars](https://img.shields.io/github/stars/lesnitsky/webgl-month.svg?style=social)\n\n\u003e Built with [GitTutor](https://github.com/lesnitsky/git-tutor)\n\n\n## WebGL Month. Day 10. Multiple textures\n\nThis is a series of blog posts related to WebGL. New post will be available every day\n\n[Subscribe](https://twitter.com/lesnitsky_a) for updates or [join mailing list](http://eepurl.com/gwiSeH)\n\n[Source code available here](https://github.com/lesnitsky/webgl-month)\n\n![GitHub stars](https://img.shields.io/github/stars/lesnitsky/webgl-month.svg?style=social)\n\n\u003e Built with [GitTutor](https://github.com/lesnitsky/git-tutor)\n\n---\n\nHey 👋 Welcome back to WebGL month.\nWe already know how to use a single image as a texture, but what if we want to render multiple images?\n\nWe'll learn how to do this today.\n\n\nFirst we need to define another `sampler2D` in fragment shader\n\n📄 src/shaders/texture.f.glsl\n```diff\n  precision mediump float;\n  \n  uniform sampler2D texture;\n+ uniform sampler2D otherTexture;\n  uniform vec2 resolution;\n  \n  vec4 inverse(vec4 color) {\n\n```\nAnd render 2 rectangles instead of a single one. Left rectangle will use already existing texture, right – new one.\n\n📄 src/texture.js\n```diff\n  gl.linkProgram(program);\n  gl.useProgram(program);\n  \n- const vertexPosition = new Float32Array(createRect(-1, -1, 2, 2));\n+ const vertexPosition = new Float32Array([\n+     ...createRect(-1, -1, 1, 2), // left rect\n+     ...createRect(-1, 0, 1, 2), // right rect\n+ ]);\n  const vertexPositionBuffer = gl.createBuffer();\n  \n  gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);\n  gl.enableVertexAttribArray(attributeLocations.position);\n  gl.vertexAttribPointer(attributeLocations.position, 2, gl.FLOAT, false, 0, 0);\n  \n- const vertexIndices = new Uint8Array([0, 1, 2, 1, 2, 3]);\n+ const vertexIndices = new Uint8Array([\n+     // left rect\n+     0, 1, 2, \n+     1, 2, 3, \n+     \n+     // right rect\n+     4, 5, 6, \n+     5, 6, 7,\n+ ]);\n  const indexBuffer = gl.createBuffer();\n  \n  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);\n\n```\nWe'll also need a way to specify texture coordinates for each rectangle, as we can't use `gl_FragCoord` any longer, so we need to define another attribute (`texCoord`)\n\n📄 src/shaders/texture.v.glsl\n```diff\n  attribute vec2 position;\n+ attribute vec2 texCoord;\n  \n  void main() {\n      gl_Position = vec4(position, 0, 1);\n\n```\nThe content of this attribute should be coordinates of 2 rectangles. Top left is `0,0`, width and height are `1.0`\n\n📄 src/texture.js\n```diff\n  gl.linkProgram(program);\n  gl.useProgram(program);\n  \n+ const texCoords = new Float32Array([\n+     ...createRect(0, 0, 1, 1), // left rect\n+     ...createRect(0, 0, 1, 1), // right rect\n+ ]);\n+ const texCoordsBuffer = gl.createBuffer();\n+ \n+ gl.bindBuffer(gl.ARRAY_BUFFER, texCoordsBuffer);\n+ gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);\n+ \n  const vertexPosition = new Float32Array([\n      ...createRect(-1, -1, 1, 2), // left rect\n      ...createRect(-1, 0, 1, 2), // right rect\n\n```\nWe also need to setup texCoord attribute in JS\n\n📄 src/texture.js\n```diff\n  \n  const attributeLocations = {\n      position: gl.getAttribLocation(program, 'position'),\n+     texCoord: gl.getAttribLocation(program, 'texCoord'),\n  };\n  \n  const uniformLocations = {\n  gl.enableVertexAttribArray(attributeLocations.position);\n  gl.vertexAttribPointer(attributeLocations.position, 2, gl.FLOAT, false, 0, 0);\n  \n+ gl.bindBuffer(gl.ARRAY_BUFFER, texCoordsBuffer);\n+ \n+ gl.enableVertexAttribArray(attributeLocations.texCoord);\n+ gl.vertexAttribPointer(attributeLocations.texCoord, 2, gl.FLOAT, false, 0, 0);\n+ \n  const vertexIndices = new Uint8Array([\n      // left rect\n      0, 1, 2, \n\n```\nand pass this data to fragment shader via varying\n\n📄 src/shaders/texture.f.glsl\n```diff\n      );\n  }\n  \n+ varying vec2 vTexCoord;\n+ \n  void main() {\n-     vec2 texCoord = gl_FragCoord.xy / resolution;\n+     vec2 texCoord = vTexCoord;\n      gl_FragColor = texture2D(texture, texCoord);\n  \n      gl_FragColor = sepia(gl_FragColor);\n\n```\n📄 src/shaders/texture.v.glsl\n```diff\n  attribute vec2 position;\n  attribute vec2 texCoord;\n  \n+ varying vec2 vTexCoord;\n+ \n  void main() {\n      gl_Position = vec4(position, 0, 1);\n+ \n+     vTexCoord = texCoord;\n  }\n\n```\nOk, we rendered two rectangles, but they use the same texture. Let's add one more attribute which will specify which texture to use and pass this data to fragment shader via another varying\n\n📄 src/shaders/texture.v.glsl\n```diff\n  attribute vec2 position;\n  attribute vec2 texCoord;\n+ attribute float texIndex;\n  \n  varying vec2 vTexCoord;\n+ varying float vTexIndex;\n  \n  void main() {\n      gl_Position = vec4(position, 0, 1);\n  \n      vTexCoord = texCoord;\n+     vTexIndex = texIndex;\n  }\n\n```\nSo now fragment shader will know which texture to use\n\n\u003e DISCLAMER: this is not the perfect way to use multiple textures in a fragment shader, but rather an example of how to acheive this\n\n📄 src/shaders/texture.f.glsl\n```diff\n  }\n  \n  varying vec2 vTexCoord;\n+ varying float vTexIndex;\n  \n  void main() {\n      vec2 texCoord = vTexCoord;\n-     gl_FragColor = texture2D(texture, texCoord);\n  \n-     gl_FragColor = sepia(gl_FragColor);\n+     if (vTexIndex == 0.0) {\n+         gl_FragColor = texture2D(texture, texCoord);\n+     } else {\n+         gl_FragColor = texture2D(otherTexture, texCoord);\n+     }\n  }\n\n```\ntex indices are 0 for the left rectangle and 1 for the right\n\n📄 src/texture.js\n```diff\n  gl.bindBuffer(gl.ARRAY_BUFFER, texCoordsBuffer);\n  gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);\n  \n+ const texIndicies = new Float32Array([\n+     ...Array.from({ length: 4 }).fill(0), // left rect\n+     ...Array.from({ length: 4 }).fill(1), // right rect\n+ ]);\n+ const texIndiciesBuffer = gl.createBuffer();\n+ \n+ gl.bindBuffer(gl.ARRAY_BUFFER, texIndiciesBuffer);\n+ gl.bufferData(gl.ARRAY_BUFFER, texIndicies, gl.STATIC_DRAW);\n+ \n  const vertexPosition = new Float32Array([\n      ...createRect(-1, -1, 1, 2), // left rect\n      ...createRect(-1, 0, 1, 2), // right rect\n\n```\nand again, we need to setup vertex attribute\n\n📄 src/texture.js\n```diff\n  const attributeLocations = {\n      position: gl.getAttribLocation(program, 'position'),\n      texCoord: gl.getAttribLocation(program, 'texCoord'),\n+     texIndex: gl.getAttribLocation(program, 'texIndex'),\n  };\n  \n  const uniformLocations = {\n  gl.enableVertexAttribArray(attributeLocations.texCoord);\n  gl.vertexAttribPointer(attributeLocations.texCoord, 2, gl.FLOAT, false, 0, 0);\n  \n+ gl.bindBuffer(gl.ARRAY_BUFFER, texIndiciesBuffer);\n+ \n+ gl.enableVertexAttribArray(attributeLocations.texIndex);\n+ gl.vertexAttribPointer(attributeLocations.texIndex, 1, gl.FLOAT, false, 0, 0);\n+ \n  const vertexIndices = new Uint8Array([\n      // left rect\n      0, 1, 2, \n\n```\nNow let's load our second texture image\n\n📄 src/texture.js\n```diff\n  import { createRect } from './shape-helpers';\n  \n  import textureImageSrc from '../assets/images/texture.jpg';\n+ import textureGreenImageSrc from '../assets/images/texture-green.jpg';\n  \n  const canvas = document.querySelector('canvas');\n  const gl = canvas.getContext('webgl');\n  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);\n  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, vertexIndices, gl.STATIC_DRAW);\n  \n- loadImage(textureImageSrc).then((textureImg) =\u003e {\n+ Promise.all([\n+     loadImage(textureImageSrc),\n+     loadImage(textureGreenImageSrc),\n+ ]).then(([textureImg, textureGreenImg]) =\u003e {\n      const texture = gl.createTexture();\n  \n      gl.bindTexture(gl.TEXTURE_2D, texture);\n\n```\nAs we'll have to create another texture – we'll need to extract some common code to separate helper functions\n\n📄 src/gl-helpers.js\n```diff\n  \n      return p;\n  }\n+ \n+ export function createTexture(gl) {\n+     const texture = gl.createTexture();\n+     \n+     gl.bindTexture(gl.TEXTURE_2D, texture);\n+     \n+     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n+     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n+     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n+     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n+ \n+     return texture;\n+ }\n+ \n+ export function setImage(gl, texture, img) {\n+     gl.bindTexture(gl.TEXTURE_2D, texture);\n+ \n+     gl.texImage2D(\n+         gl.TEXTURE_2D,\n+         0,\n+         gl.RGBA,\n+         gl.RGBA,\n+         gl.UNSIGNED_BYTE,\n+         img,\n+     );\n+ }\n\n```\n📄 src/texture.js\n```diff\n      loadImage(textureImageSrc),\n      loadImage(textureGreenImageSrc),\n  ]).then(([textureImg, textureGreenImg]) =\u003e {\n-     const texture = gl.createTexture();\n- \n-     gl.bindTexture(gl.TEXTURE_2D, texture);\n- \n-     gl.texImage2D(\n-         gl.TEXTURE_2D,\n-         0,\n-         gl.RGBA,\n-         gl.RGBA,\n-         gl.UNSIGNED_BYTE,\n-         textureImg,\n-     );\n- \n-     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n-     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n-     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n-     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n+ \n  \n      gl.activeTexture(gl.TEXTURE0);\n      gl.uniform1i(uniformLocations.texture, 0);\n\n```\nNow let's use our newely created helpers\n\n📄 src/texture.js\n```diff\n  import vShaderSource from './shaders/texture.v.glsl';\n  import fShaderSource from './shaders/texture.f.glsl';\n- import { compileShader, loadImage } from './gl-helpers';\n+ import { compileShader, loadImage, createTexture, setImage } from './gl-helpers';\n  import { createRect } from './shape-helpers';\n  \n  import textureImageSrc from '../assets/images/texture.jpg';\n      loadImage(textureImageSrc),\n      loadImage(textureGreenImageSrc),\n  ]).then(([textureImg, textureGreenImg]) =\u003e {\n+     const texture = createTexture(gl);\n+     setImage(gl, texture, textureImg);\n  \n+     const otherTexture = createTexture(gl);\n+     setImage(gl, otherTexture, textureGreenImg);\n  \n      gl.activeTexture(gl.TEXTURE0);\n      gl.uniform1i(uniformLocations.texture, 0);\n\n```\nget uniform location\n\n📄 src/texture.js\n```diff\n  \n  const uniformLocations = {\n      texture: gl.getUniformLocation(program, 'texture'),\n+     otherTexture: gl.getUniformLocation(program, 'otherTexture'),\n      resolution: gl.getUniformLocation(program, 'resolution'),\n  };\n  \n\n```\nand set necessary textures to necessary uniforms\n\nto set a texture to a uniform you should specify\n\n* active texture unit in range `[gl.TEXTURE0..gl.TEXTURE31]` (number of texture units depends on GPU and can be retreived with `gl.getParameter`)\n* bind texture to a texture unit\n* set texture unit \"index\" to a `sampler2D` uniform\n\n📄 src/texture.js\n```diff\n      setImage(gl, otherTexture, textureGreenImg);\n  \n      gl.activeTexture(gl.TEXTURE0);\n+     gl.bindTexture(gl.TEXTURE_2D, texture);\n      gl.uniform1i(uniformLocations.texture, 0);\n  \n+     gl.activeTexture(gl.TEXTURE1);\n+     gl.bindTexture(gl.TEXTURE_2D, otherTexture);\n+     gl.uniform1i(uniformLocations.otherTexture, 1);\n+ \n      gl.uniform2fv(uniformLocations.resolution, [canvas.width, canvas.height]);\n  \n      gl.drawElements(gl.TRIANGLES, vertexIndices.length, gl.UNSIGNED_BYTE, 0);\n\n```\nThat's it, we can now render multiple textures\n\nSee you tomorrow 👋\n\n---\n\nThis is a series of blog posts related to WebGL. New post will be available every day\n\n[Subscribe](https://twitter.com/lesnitsky_a) for updates or [join mailing list](http://eepurl.com/gwiSeH)\n\n[Source code available here](https://github.com/lesnitsky/webgl-month)\n\n![GitHub stars](https://img.shields.io/github/stars/lesnitsky/webgl-month.svg?style=social\u0026hash=day-9)\n\n\u003e Built with [GitTutor](https://github.com/lesnitsky/git-tutor)\n\n\n## Day 11. Reducing boilerplate\n\nThis is a series of blog posts related to WebGL. New post will be available every day\n\n![GitHub stars](https://img.shields.io/github/stars/lesnitsky/webgl-month.svg?style=social\u0026hash=day11)\n![Twitter Follow](https://img.shields.io/twitter/follow/lesnitsky_a.svg?label=Follow%20me\u0026style=social\u0026hash=day11)\n\n[Join mailing list](http://eepurl.com/gwiSeH) to get new posts right to your inbox\n\n[Source code available here](https://github.com/lesnitsky/webgl-month)\n\nBuilt with\n\n[![Git Tutor Logo](https://git-tutor-assets.s3.eu-west-2.amazonaws.com/git-tutor-logo-50.png)](https://github.com/lesnitsky/git-tutor)\n\n\n[Yesterday](https://dev.to/lesnitsky/webgl-month-day-10-multiple-textures-gf3) we've learned how to use multiple textures. This required a shader modification, as well as javascript, but this changes might be partially done automatically\n\nThere is a package [glsl-extract-","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flesnitsky%2Fwebgl-month","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flesnitsky%2Fwebgl-month","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flesnitsky%2Fwebgl-month/lists"}