{"id":3409,"date":"2021-04-03T14:07:45","date_gmt":"2021-04-03T14:07:45","guid":{"rendered":"https:\/\/rengga.dev\/blog\/?p=3409"},"modified":"2023-02-28T05:04:51","modified_gmt":"2023-02-28T05:04:51","slug":"js-tutorial-metaballs-webgl","status":"publish","type":"post","link":"https:\/\/rengga.dev\/blog\/js-tutorial-metaballs-webgl\/","title":{"rendered":"JS Tutorial &#8211; Metaballs &#8211; WebGL"},"content":{"rendered":"<p><span style=\"color: #ef3207;\"><a style=\"color: #ef3207;\" href=\"https:\/\/rengga.dev\/\" target=\"_blank\" rel=\"noopener\"><strong>Rengga Dev<\/strong><\/a> <\/span>&#8211; Try playing with the parameters on the gui to the right. The Metaballs is just one effect of WebGL.<\/p>\n<p><iframe style=\"width: 100%;\" title=\"JS Tutorial - Metaballs - WebGL\" src=\"https:\/\/codepen.io\/renggagumilar\/embed\/YzLzMWa?default-tab=result&amp;theme-id=dark\" height=\"500\" frameborder=\"no\" scrolling=\"no\" allowfullscreen=\"allowfullscreen\"><br \/>\nSee the Pen <a href=\"https:\/\/codepen.io\/renggagumilar\/pen\/YzLzMWa\"><br \/>\nJS Tutorial &#8211; Metaballs &#8211; WebGL<\/a> by Rengga Gumilar (<a href=\"https:\/\/codepen.io\/renggagumilar\">@renggagumilar<\/a>)<br \/>\non <a href=\"https:\/\/codepen.io\">CodePen<\/a>.<br \/>\n<\/iframe><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">var canvas = document.createElement(\"canvas\");\r\nvar width = canvas.width = window.innerWidth * 0.75;\r\nvar height = canvas.height = window.innerHeight * 0.75;\r\ndocument.body.appendChild(canvas);\r\nvar gl = canvas.getContext('webgl');\r\n\r\nvar mouse = {x: 0, y: 0};\r\n\r\nvar numMetaballs = 30;\r\nvar metaballs = [];\r\n\r\nfor (var i = 0; i &lt; numMetaballs; i++) {\r\n  var radius = Math.random() * 60 + 10;\r\n  metaballs.push({\r\n    x: Math.random() * (width - 2 * radius) + radius,\r\n    y: Math.random() * (height - 2 * radius) + radius,\r\n    vx: (Math.random() - 0.5) * 3,\r\n    vy: (Math.random() - 0.5) * 3,\r\n    r: radius * 0.75\r\n  });\r\n}\r\n\r\nvar vertexShaderSrc = `\r\nattribute vec2 position;\r\n\r\nvoid main() {\r\n\/\/ position specifies only x and y.\r\n\/\/ We set z to be 0.0, and w to be 1.0\r\ngl_Position = vec4(position, 0.0, 1.0);\r\n}\r\n`;\r\n\r\nvar fragmentShaderSrc = `\r\nprecision highp float;\r\n\r\nconst float WIDTH = ` + (width &gt;&gt; 0) + `.0;\r\nconst float HEIGHT = ` + (height &gt;&gt; 0) + `.0;\r\n\r\nuniform vec3 metaballs[` + numMetaballs + `];\r\n\r\nvoid main(){\r\nfloat x = gl_FragCoord.x;\r\nfloat y = gl_FragCoord.y;\r\n\r\nfloat sum = 0.0;\r\nfor (int i = 0; i &lt; ` + numMetaballs + `; i++) {\r\nvec3 metaball = metaballs[i];\r\nfloat dx = metaball.x - x;\r\nfloat dy = metaball.y - y;\r\nfloat radius = metaball.z;\r\n\r\nsum += (radius * radius) \/ (dx * dx + dy * dy);\r\n}\r\n\r\nif (sum &gt;= 0.99) {\r\ngl_FragColor = vec4(mix(vec3(x \/ WIDTH, y \/ HEIGHT, 1.0), vec3(0, 0, 0), max(0.0, 1.0 - (sum - 0.99) * 100.0)), 1.0);\r\nreturn;\r\n}\r\n\r\ngl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);\r\n}\r\n\r\n`;\r\n\r\nvar vertexShader = compileShader(vertexShaderSrc, gl.VERTEX_SHADER);\r\nvar fragmentShader = compileShader(fragmentShaderSrc, gl.FRAGMENT_SHADER);\r\n\r\nvar program = gl.createProgram();\r\ngl.attachShader(program, vertexShader);\r\ngl.attachShader(program, fragmentShader);\r\ngl.linkProgram(program);\r\ngl.useProgram(program);\r\n\r\nvar vertexData = new Float32Array([\r\n  -1.0,  1.0, \/\/ top left\r\n  -1.0, -1.0, \/\/ bottom left\r\n  1.0,  1.0, \/\/ top right\r\n  1.0, -1.0, \/\/ bottom right\r\n]);\r\nvar vertexDataBuffer = gl.createBuffer();\r\ngl.bindBuffer(gl.ARRAY_BUFFER, vertexDataBuffer);\r\ngl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW);\r\n\r\nvar positionHandle = getAttribLocation(program, 'position');\r\ngl.enableVertexAttribArray(positionHandle);\r\ngl.vertexAttribPointer(positionHandle,\r\n                       2, \/\/ position is a vec2\r\n                       gl.FLOAT, \/\/ each component is a float\r\n                       gl.FALSE, \/\/ don't normalize values\r\n                       2 * 4, \/\/ two 4 byte float components per vertex\r\n                       0 \/\/ offset into each span of vertex data\r\n                      );\r\n\r\nvar metaballsHandle = getUniformLocation(program, 'metaballs');\r\n\r\nloop();\r\nfunction loop() {\r\n  for (var i = 0; i &lt; numMetaballs; i++) {\r\n    var metaball = metaballs[i];\r\n    metaball.x += metaball.vx;\r\n    metaball.y += metaball.vy;\r\n\r\n    if (metaball.x &lt; metaball.r || metaball.x &gt; width - metaball.r) metaball.vx *= -1;\r\n    if (metaball.y &lt; metaball.r || metaball.y &gt; height - metaball.r) metaball.vy *= -1;\r\n  }\r\n\r\n  var dataToSendToGPU = new Float32Array(3 * numMetaballs);\r\n  for (var i = 0; i &lt; numMetaballs; i++) {\r\n    var baseIndex = 3 * i;\r\n    var mb = metaballs[i];\r\n    dataToSendToGPU[baseIndex + 0] = mb.x;\r\n    dataToSendToGPU[baseIndex + 1] = mb.y;\r\n    dataToSendToGPU[baseIndex + 2] = mb.r;\r\n  }\r\n  gl.uniform3fv(metaballsHandle, dataToSendToGPU);\r\n  \r\n  \/\/Draw\r\n  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\r\n\r\n  requestAnimationFrame(loop);\r\n}\r\n\r\nfunction compileShader(shaderSource, shaderType) {\r\n  var shader = gl.createShader(shaderType);\r\n  gl.shaderSource(shader, shaderSource);\r\n  gl.compileShader(shader);\r\n\r\n  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\r\n    throw \"Shader compile failed with: \" + gl.getShaderInfoLog(shader);\r\n  }\r\n\r\n  return shader;\r\n}\r\n\r\nfunction getUniformLocation(program, name) {\r\n  var uniformLocation = gl.getUniformLocation(program, name);\r\n  if (uniformLocation === -1) {\r\n    throw 'Can not find uniform ' + name + '.';\r\n  }\r\n  return uniformLocation;\r\n}\r\n\r\nfunction getAttribLocation(program, name) {\r\n  var attributeLocation = gl.getAttribLocation(program, name);\r\n  if (attributeLocation === -1) {\r\n    throw 'Can not find attribute ' + name + '.';\r\n  }\r\n  return attributeLocation;\r\n}\r\n\r\ncanvas.onmousemove = function(e) {\r\n  mouse.x = e.clientX;\r\n  mouse.y = e.clientY;\r\n}<\/pre>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Rengga Dev &#8211; Try playing with the parameters on the gui to <a class=\"read-more\" href=\"https:\/\/rengga.dev\/blog\/js-tutorial-metaballs-webgl\/\" title=\"JS Tutorial &#8211; Metaballs &#8211; WebGL\" itemprop=\"url\"><\/a><\/p>\n","protected":false},"author":1,"featured_media":3825,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[20],"tags":[325,264,263,332,103,227],"newstopic":[],"class_list":{"0":"post-3409","1":"post","2":"type-post","3":"status-publish","4":"format-standard","5":"has-post-thumbnail","7":"category-javascript","8":"tag-background-effects","9":"tag-javascript-tutorial","10":"tag-js-tutorial","11":"tag-metaballs-webgl","12":"tag-web-design","13":"tag-web-designer"},"_links":{"self":[{"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/posts\/3409","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/comments?post=3409"}],"version-history":[{"count":1,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/posts\/3409\/revisions"}],"predecessor-version":[{"id":3410,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/posts\/3409\/revisions\/3410"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/media\/3825"}],"wp:attachment":[{"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/media?parent=3409"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/categories?post=3409"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/tags?post=3409"},{"taxonomy":"newstopic","embeddable":true,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/newstopic?post=3409"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}