{"id":37,"date":"2021-08-22T11:24:03","date_gmt":"2021-08-22T10:24:03","guid":{"rendered":"https:\/\/mrzebra.co.uk\/code\/?p=37"},"modified":"2021-08-22T14:20:57","modified_gmt":"2021-08-22T13:20:57","slug":"creating-a-shader-block-for-wordpress","status":"publish","type":"post","link":"https:\/\/zebra-north.com\/code\/2021\/08\/22\/creating-a-shader-block-for-wordpress\/","title":{"rendered":"Creating a Shader Block for WordPress"},"content":{"rendered":"\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:33.33%\">\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"260\" height=\"392\" src=\"https:\/\/zebra-north.com\/code\/wp-content\/uploads\/2021\/08\/webgl-shader-block.png\" alt=\"\" class=\"wp-image-60\" srcset=\"https:\/\/zebra-north.com\/code\/wp-content\/uploads\/2021\/08\/webgl-shader-block.png 260w, https:\/\/zebra-north.com\/code\/wp-content\/uploads\/2021\/08\/webgl-shader-block-199x300.png 199w\" sizes=\"auto, (max-width: 260px) 100vw, 260px\" \/><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:66.66%\">\n<p> Learn how to create a custom WordPress layout block to display WebGL shaders. <\/p>\n<\/div>\n<\/div>\n\n\n\n<!--more-->\n\n\n\n<h2 class=\"wp-block-heading\">Configure the Environment<\/h2>\n\n\n\n<p>Following the official <a href=\"https:\/\/developer.wordpress.org\/block-editor\/how-to-guides\/block-tutorial\/writing-your-first-block-type\/\">WordPress Guide<\/a>, the first step is to install <code>nvm<\/code> and use that to install <code>nodejs<\/code> and <code>npm<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">curl -o- https:\/\/raw.githubusercontent.com\/nvm-sh\/nvm\/v0.38.0\/install.sh | bash<\/code><\/pre>\n\n\n\n<p>Then restart your terminal and install the latest version of <code>nodejs<\/code> with:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">nvm install node<\/code><\/pre>\n\n\n\n<p>You should now have <code>nodejs<\/code> and <code>npm<\/code> installed, and you can verify this with:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">node --version<\/code><\/pre>\n\n\n\n<p>Make a new directory and <code>cd<\/code> into it, then run <code>npx<\/code> to create the plugin template.  It will prompt you for a few details, then install all the required scripts.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">mkdir my-plugin\ncd my-plugin\nnpx @wordpress\/create-block<\/code><\/pre>\n\n\n\n<p>Move your plugin so it sits under <code>wp-content\/plugins<\/code>.  Finally, run<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">npm start<\/code><\/pre>\n\n\n\n<p>This will start watching your files for changes.  You can now go to your WordPress website and click on &#8220;Plugins&#8221; in the left hand menu &#8211; your new plugin should show there.  Simply press the &#8220;Activate&#8221; link to enable it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Anatomy of a Block<\/h2>\n\n\n\n<p>The block editor is build on top of the <a href=\"https:\/\/reactjs.org\/\">React<\/a> Javascript framework.  Almost all the work of the block is done in Javascript, the PHP file is simply there to load the required Javascript files.<\/p>\n\n\n\n<p>The files created by the <code>wordpress\/create-block<\/code> command are:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><code>block.json<\/code>: Metadata describing your block.<\/li><li><code>index.js<\/code>: The main file that ties together the edit and save files.<\/li><li><code>edit.js<\/code>: How the block should appear in the page editor.<\/li><li><code>save.js<\/code>: How the block should appear when rendered on the site.<\/li><li><code>style.scss<\/code>: CSS styles applied both in the editor and on the site.<\/li><li><code>editor.scss<\/code>: CSS styles applied only in the editor.<\/li><\/ul>\n\n\n\n<p>Javascript files are written using <a href=\"https:\/\/reactjs.org\/docs\/introducing-jsx.html\">JSX<\/a>, which mixes HTML directly into the javascript code.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Creating the Block<\/h2>\n\n\n\n<p>User-editable data is attached to blocks using &#8220;attributes&#8221;.  The block only needs a single attribute: the source code for the shader.  To create this, we edit <code>block.json<\/code> and add a <code>sourceCode<\/code> string attribute of type <code>string<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-json\">&quot;attributes&quot;: {\n\t&quot;sourceCode&quot;: {\n\t\t&quot;type&quot;: &quot;string&quot;\n\t}\n}<\/code><\/pre>\n\n\n\n<p>Modify <code>edit.js<\/code> to import the <code>TextareaControl<\/code> and use it for editing the shader&#8217;s source code.  We set up attributes on the <code>&lt;TextareaControl&gt;<\/code> for the label, the value, and a change event handler to save the new value.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-javascript\">\/**\n * Retrieves the translation of text.\n *\n * @see https:\/\/developer.wordpress.org\/block-editor\/packages\/packages-i18n\/\n *\/\nimport { __ } from &#039;@wordpress\/i18n&#039;;\nimport { TextareaControl } from &#039;@wordpress\/components&#039;;\n\n\/**\n * React hook that is used to mark the block wrapper element.\n * It provides all the necessary props like the class name.\n *\n * @see https:\/\/developer.wordpress.org\/block-editor\/packages\/packages-block-editor\/#useBlockProps\n *\/\nimport { useBlockProps } from &#039;@wordpress\/block-editor&#039;;\n\n\/**\n * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files.\n * Those files can contain any CSS code that gets applied to the editor.\n *\n * @see https:\/\/www.npmjs.com\/package\/@wordpress\/scripts#using-css\n *\/\nimport &#039;.\/editor.scss&#039;;\nimport { parseWithAttributeSchema } from &#039;@wordpress\/blocks&#039;;\n\n\/**\n * The edit function describes the structure of your block in the context of the\n * editor. This represents what the editor will render when the block is used.\n *\n * @see https:\/\/developer.wordpress.org\/block-editor\/developers\/block-api\/block-edit-save\/#edit\n *\n * @return {WPElement} Element to render.\n *\/\nexport default function Edit({ attributes, setAttributes }) {\n\treturn (\n\t\t&lt;div {...useBlockProps()}&gt;\n\t\t\t&lt;TextareaControl\n\t\t\t\tlabel={ __(&#039;WebGL Shader Source Code&#039;, &#039;webgl-shader&#039;) }\n\t\t\t\tvalue={ attributes.sourceCode }\n\t\t\t\tonChange={(val) =&gt; setAttributes({ sourceCode: val })}\n\t\t\t\/&gt;\n\t\t&lt;\/div&gt;\n\t);\n}<\/code><\/pre>\n\n\n\n<p>Edit <code>save.js<\/code> to build the output HTML.  In this case we build the source code for the vertex and fragment shaders, then create a <code>&lt;script&gt;<\/code> tag to compile and output them by calling the function <code>webglShader()<\/code>, which will be defined later.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-javascript\">\/**\n * Retrieves the translation of text.\n *\n * @see https:\/\/developer.wordpress.org\/block-editor\/packages\/packages-i18n\/\n *\/\nimport { __ } from &#039;@wordpress\/i18n&#039;;\n\n\/**\n * React hook that is used to mark the block wrapper element.\n * It provides all the necessary props like the class name.\n *\n * @see https:\/\/developer.wordpress.org\/block-editor\/packages\/packages-block-editor\/#useBlockProps\n *\/\nimport { useBlockProps } from &#039;@wordpress\/block-editor&#039;;\nimport { parseWithAttributeSchema } from &#039;@wordpress\/blocks&#039;;\n\n\/**\n * The save function defines the way in which the different attributes should\n * be combined into the final markup, which is then serialized by the block\n * editor into `post_content`.\n *\n * @see https:\/\/developer.wordpress.org\/block-editor\/developers\/block-api\/block-edit-save\/#save\n *\n * @return {WPElement} Element to render.\n *\/\nexport default function save({ attributes }) {\n\tconst script = `const vertexShaderSource = \\`#version 300 es\n            in vec4 vertex;\n            in vec2 uv;\n\n            uniform mat4 modelView;\n            uniform mat4 projection;\n\n            out vec2 vUv;\n            out vec4 vSurface;\n\n            void main()\n            {\n                gl_Position = projection * modelView * vertex;\n                gl_PointSize = 2.0;\n                vUv = uv;\n                vSurface = vertex;\n            }\\`;\n\n\t\tconst fragmentShaderSource = \\`#version 300 es\n\t\t\t\tprecision mediump float;\n\n\t\t\t\tin vec2 vUv;\n\t\t\t\tout vec4 fragColor;\n\n\t\t\t\tuniform float iTime;\\`\n\t\t\t+ ${JSON.stringify(attributes.sourceCode)};\n\t\t\twebglShader(vertexShaderSource, fragmentShaderSource);`;\n\n\treturn (\n\t\t&lt;script {...useBlockProps.save()} type=&quot;text\/javascript&quot; dangerouslySetInnerHTML={{ __html: script }}&gt;\n\t\t&lt;\/script&gt;\n\t);\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Styling the Block<\/h2>\n\n\n\n<p>When you add your block in the page editor it will have a blue background with a red border &#8211; this doesn&#8217;t indicate an error!  This is just the default styling.  Edit the files <code>style.scss<\/code> and <code>editor.scss<\/code> to set the appearance.  I chose a border of <code>#ddd<\/code> with a four pixel radius, to match the <code>Code<\/code> block type.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-scss\">.wp-block-zebra-north-webgl-shader {\n\tcolor: #fff;\n\tpadding: 2px;\n}\n\n.wp-block-zebra-north-webgl-shader textarea {\n\tfont-family: &#039;Courier New&#039;, Courier, monospace;\n\tborder: 1px solid #ddd;\n\tborder-radius: 4px;\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">The Front End<\/h2>\n\n\n\n<p>At this point we have an editable block on the back end that outputs a <code>&lt;script&gt;<\/code> tag on the front end, which calls the <code>webglShader()<\/code> function.  <\/p>\n\n\n\n<p>We&#8217;ll add two new files:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><code>webgl-shader.js<\/code>: Output the shader.<\/li><li><code>gl-matrix.js<\/code>: Supporting matrix maths functions, by Brandon Jones and Colin MacKenzie IV.<\/li><\/ul>\n\n\n\n<p>glMatrix is a very useful high performance matrix maths library, available under GPL from <a href=\"https:\/\/glmatrix.net\/\">https:\/\/glmatrix.net\/<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Loading Javascript Files<\/h2>\n\n\n\n<p>To load the supporting Javascript files, modify the plugin&#8217;s PHP source code in <code>webgl-shader.php<\/code> so that the scripts are enqueued in response to the <code>wp_enqueue_scripts<\/code> event.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-php\">&lt;?php\nfunction zebra_north_webgl_shader_block_init() {\n\tregister_block_type( __DIR__ );\n}\n\nfunction zebra_north_webgl_shader_enqueue_scripts() {\n\twp_enqueue_script(&#039;zebra_north_gl_matrix&#039;, plugin_dir_url(__FILE__) . &#039;\/gl-matrix.js&#039;);\n\twp_enqueue_script(&#039;zebra_north_webgl_shader&#039;, plugin_dir_url(__FILE__) . &#039;\/webgl-shader.js&#039;);\n}\n\nadd_action( &#039;init&#039;, &#039;zebra_north_webgl_shader_block_init&#039; );\nadd_action(&#039;wp_enqueue_scripts&#039;, &#039;zebra_north_webgl_shader_enqueue_scripts&#039; );<\/code><\/pre>\n\n\n\n<p>Finally, the script to compile and render the shader.  I&#8217;ll break this down by part.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Creating the Shader HTML<\/h2>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-javascript\">&#039;use strict&#039;;\n\n\/**\n * WebGL Shader WordPress Block v1.0.0.\n *\n * Copyright (c) Zebra North, 2021\n *\/\n\n\/**\n * Create a &lt;canvas&gt; element and display a shader in it.\n *\n * @param {String} fragmentShaderSource The source code for the fragment shader.\n *\/\nfunction webglShader(fragmentShaderSource)\n{\n    \/\/ This is used to assign each shader on the page a unique ID.\n    let id = 1;\n\n    \/\/ [FUNCTIONS GO HERE]\n\n    \/\/ Create a &lt;canvas&gt; element on which to draw the shader.\n    const canvasId = &#039;webgl-shader-&#039; + id++;\n    document.write(&#039;&lt;canvas id=&quot;&#039; + canvasId + &#039;&quot; class=&quot;zebra-north-webgl-shader&quot; width=&quot;640&quot; height=&quot;360&quot;&gt;&lt;\/canvas&gt;&#039;)\n\n    \/\/ Initialise the shader when the page has finished loading.\n    window.addEventListener(&#039;DOMContentLoaded&#039;, () =&gt; main(canvasId, getVertexShaderSource(), getFragmentShaderSource(fragmentShaderSource)));\n}\n<\/code><\/pre>\n\n\n\n<p>Beginning the file with <code>&#039;use strict&#039;<\/code> sets the Javascript interpreter into strict mode, which gives you a little more error checking and hence safety when developing.<\/p>\n\n\n\n<p>All the internal functions are declared within the <code>webglShader()<\/code> function so that they do not pollute the global namespace.<\/p>\n\n\n\n<p>When the <code>webglShader()<\/code> function is called, it creates a unique ID and outputs a <code>&lt;canvas&gt;<\/code> tag.  This is the canvas onto which the shader will be drawn.  It then waits for the remainder of the DOM content to be loaded before initialising the shader.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Creating the WebGL Context<\/h2>\n\n\n\n<p>All WebGL functions require a &#8220;WebGL context&#8221; to work with.  The context stores the state of WebGL, for example the contents of the vertex buffers and the current projection matrix.  A WebGL context is created by calling <code>getContext()<\/code> on the <code>canvas<\/code> element.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-javascript\">\/**\n * Create a WebGL2 rendering context on the given canvas.\n *\n * @param {DOMElement} canvas The &lt;canvas&gt; element.\n *\n * @returns {WebGL2RenderingContext} Returns the rendering context.\n *\n * @throws {Error} Thrown if the canvas element does not exist or WebGL2 is not supported.\n *\/\nconst createContext = function (canvas)\n{\n    if (!canvas)\n        throw new Error(&#039;Could not find canvas element&#039;);\n\n    const context = canvas.getContext(&#039;webgl2&#039;);\n\n    if (!context)\n        throw new Error(&#039;Failed to get webgl context, webgl is not supported by the browser&#039;);\n\n    return context;\n};<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">The Vertex Shader Source<\/h2>\n\n\n\n<p>The vertex shader is very simple, it just multiplies the object space vertices by the <code>modelView<\/code> and <code>projection<\/code> matrices.  It also multiplies the texture UVs by the resolution (the pixel size of the <code>canvas<\/code> element) for compatibility with Shadertoy.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-javascript\">\/**\n * Get the source code for the vertex shader.\n * This multiplies the UVs by iResolution for compatibility\n * with Shadertoy.\n *\n * @returns {String} Returns shader source.\n *\/\nconst getVertexShaderSource = function ()\n{\n    return `#version 300 es\n        precision mediump float;\n        in vec4 vertex;\n        in vec2 uv;\n\n        uniform mat4 modelView;\n        uniform mat4 projection;\n        uniform vec2 iResolution;\n\n        out vec2 vUv;\n        out vec4 vSurface;\n\n        void main()\n        {\n            gl_Position = projection * modelView * vertex;\n            gl_PointSize = 2.0;\n            vUv = uv * iResolution;\n            vSurface = vertex;\n        }`;\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">The Fragment Shader Source<\/h2>\n\n\n\n<p>The fragment shader from the block input requires some boilerplate adding, to set up the shader inputs and outputs.  This function wraps the boilerplate around the block input.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-javascript\">\n\/**\n * Get the source code for the fragment shader.\n * This takes the source code from the input block and adds\n * the necessary boilerplate around it.\n *\n * @param {String} source The source code from the input block.\n *\n * @returns {String} Returns shader source.\n *\/\nconst getFragmentShaderSource = function (source)\n{\n    return `#version 300 es\n        precision mediump float;\n\n        in vec2 vUv;\n        out vec4 fragColor;\n\n        uniform float iTime;\n        uniform vec2 iResolution;\n\n        ` + source + `\n\n        void main()\n        {\n            vec4 colour = vec4(1.0, 0.0, 0.0, 1.0);\n            mainImage(colour, vUv);\n            fragColor = colour;\n        }`;\n};<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Compiling a Shader<\/h2>\n\n\n\n<p>With the source code now available for both the vertex and fragment shader, its time to compile them.  This is straightforward, just call <code>createShader()<\/code> to create the shader object, <code>shaderSource()<\/code> to load in the source code, then <code>compileShader()<\/code> to compile it.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-javascript\">\/**\n * Build a compiled shader from source code.\n *\n * @param {WebGL2RenderingContext} gl   The WebGL context.\n * @param {Number}                 type The type of shader, either gl.VERTEX_SHADER or gl.FRAGMENT_SHADER.\n * @param {String}                 id   The ID attribute of the &lt;script&gt; element containing the shader&#039;s source code.\n *\n * @returns {WebGLShader} Returns the shader.\n *\n * @throws {Error} Thrown if compilation fails, eg due to a syntax error in the source code.\n *\/\nconst createShader = function (gl, type, source)\n{\n    const shader = gl.createShader(type);\n\n    gl.shaderSource(shader, source);\n    gl.compileShader(shader);\n\n    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS))\n    {\n        const msg = &#039;Failed to compile shader &#039; + id + &#039;: &#039; + gl.getShaderInfoLog(shader);\n\n        gl.deleteShader(shader);\n\n        throw new Error(msg);\n    }\n\n    return shader;\n};<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Linking a Program<\/h2>\n\n\n\n<p>Once the shaders have been compiled, they are linked together into a single program:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-javascript\">\n\/**\n * Link shaders together into a program.\n *\n * @param {WebGL2RenderingContext} gl      The WebGL context.\n * @param {Array}                  shaders An array of WebGLShader.\n *\n * @returns {WebGLProgram} Returns a set of linked shaders.\n *\n * @throws {Error} Thrown if linking fails.\n *\/\nconst createProgram = function (gl, shaders)\n{\n    const program = gl.createProgram();\n\n    for (const shader of shaders)\n        gl.attachShader(program, shader);\n\n    gl.linkProgram(program);\n\n    if (!gl.getProgramParameter(program, gl.LINK_STATUS))\n    {\n        const msg = &#039;Failed to create GL program: &#039; + gl.getProgramInfoLog(program);\n\n        gl.deleteProgram(program);\n\n        throw new Error(msg);\n    }\n\n    return program;\n};<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Putting It All Together<\/h2>\n\n\n\n<p>The main function sets up WebGL, creates the geometry for the scene, copies the geometry data into WebGL&#8217;s internal buffers, and creates a timer that is called each frame.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-javascript\">\/**\n * Run the shader.\n *\n * @param {String} canvasId             The ID attribute of the &lt;canvas&gt; tag that will display the shader.\n * @param {String} vertexShaderSource   The source code for the vertex shader.\n * @param {String} fragmentShaderSource The source code for the fragment shader.\n *\/\nfunction main(canvasId, vertexShaderSource, fragmentShaderSource)\n{\n    const glCanvas = document.getElementById(canvasId);\n    const gl = createContext(glCanvas);\n\n    const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);\n    const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);\n\n    const program = createProgram(gl, [vertexShader, fragmentShader]);\n\n    const scene = {\n        shader: {\n            program: program,\n            uniforms: {\n                projection: gl.getUniformLocation(program, &#039;projection&#039;),\n                modelView: gl.getUniformLocation(program, &#039;modelView&#039;),\n                time: gl.getUniformLocation(program, &#039;iTime&#039;),\n                resolution: gl.getUniformLocation(program, &#039;iResolution&#039;),\n            },\n            attributes: {\n                vertex: gl.getAttribLocation(program, &#039;vertex&#039;),\n                uv: gl.getAttribLocation(program, &#039;uv&#039;),\n            },\n        },\n        buffers: {\n            vertex: gl.createBuffer(),\n            uv: gl.createBuffer(),\n            index: gl.createBuffer(),\n        },\n        geometry: {\n            vertices: [],\n            uvs: [],\n            indices: [],\n        }\n    }\n\n    \/\/ Create a mesh containing a single quad in the XY plane.\n    scene.geometry.vertices.push(0, 0, 1);\n    scene.geometry.vertices.push(0, 1, 1);\n    scene.geometry.vertices.push(-1, 0, 1);\n    scene.geometry.vertices.push(-1, 1, 1);\n\n    scene.geometry.uvs.push(0, 0);\n    scene.geometry.uvs.push(0, 1);\n    scene.geometry.uvs.push(1, 0);\n    scene.geometry.uvs.push(1, 1);\n\n    scene.geometry.indices.push(0);\n    scene.geometry.indices.push(1);\n    scene.geometry.indices.push(2);\n    scene.geometry.indices.push(3);\n\n    \/\/ Load the geometry into the WebGL buffers.\n    gl.bindBuffer(gl.ARRAY_BUFFER, scene.buffers.vertex);\n    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(scene.geometry.vertices), gl.STATIC_DRAW);\n\n    gl.bindBuffer(gl.ARRAY_BUFFER, scene.buffers.uv);\n    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(scene.geometry.uvs), gl.STATIC_DRAW);\n\n    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, scene.buffers.index);\n    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(scene.geometry.indices), gl.STATIC_DRAW);\n\n    \/\/ Draw the frame.\n    window.setInterval(() =&gt; draw(gl, glCanvas, scene), 16.66);\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Drawing a Frame<\/h2>\n\n\n\n<p>Finally, the <code>draw()<\/code> function renders a single frame.  The camera is configured, the shader inputs are updated, and then final the single quad is drawn.  The fragment shader is drawn as the texture of this quad.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-javascript\">\/**\n * Render the scene for a single frame.\n *\n * @param {WebGL2RenderingContext} gl     The WebGL2 render context.\n * @param {Canvas}                 canvas The &lt;canvas&gt; DOM element to which the scene is rendered.\n * @param {Scene}                  scene  The scene to draw.\n *\/\nconst draw = function (gl, canvas, scene)\n{\n    \/\/ Clear the screen.\n    gl.clearColor(0.0, 0.0, 0.0, 1.0);\n    gl.disable(gl.DEPTH_TEST);\n    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);\n\n    \/\/ Set up an orthographic camera.\n    const projectionMatrix = glMatrix.mat4.create();\n    glMatrix.mat4.ortho(projectionMatrix, 0, 1, 0, 1, 0.01, 200.0);\n\n    let modelView = glMatrix.mat4.create();\n    glMatrix.mat4.translate(modelView, modelView, [1, 0, 0]);\n    let target = glMatrix.vec3.create();\n    let up = glMatrix.vec3.create();\n    up[1] = 1;\n    glMatrix.mat4.lookAt(modelView, [0, 0, -1], target, up);\n\n    gl.useProgram(scene.shader.program);\n\n    \/\/ Set the shader program uniforms: the projection matrix, modelview matrix, time, and resolution.\n    gl.uniformMatrix4fv(scene.shader.uniforms.projection, false, projectionMatrix);\n    gl.uniformMatrix4fv(scene.shader.uniforms.modelView, false, modelView);\n    gl.uniform1f(scene.shader.uniforms.time, (Date.now() % 100000) \/ 1000);\n    gl.uniform2f(scene.shader.uniforms.resolution, canvas.clientWidth, canvas.clientHeight);\n\n    \/\/ Load the quad.\n    gl.enableVertexAttribArray(scene.shader.attributes.vertex);\n    gl.bindBuffer(gl.ARRAY_BUFFER, scene.buffers.vertex);\n    gl.vertexAttribPointer(scene.shader.attributes.vertex, 3, gl.FLOAT, false, 0, 0);\n\n    gl.enableVertexAttribArray(scene.shader.attributes.uv);\n    gl.bindBuffer(gl.ARRAY_BUFFER, scene.buffers.uv);\n    gl.vertexAttribPointer(scene.shader.attributes.uv, 2, gl.FLOAT, false, 0, 0);\n\n    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, scene.buffers.index);\n\n    \/\/ Draw the quad.\n    gl.drawElements(gl.TRIANGLE_STRIP, 4, gl.UNSIGNED_SHORT, 0);\n};<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>Hopefully this has given you some insight into both WordPress&#8217; block API, and WebGL.<\/p>\n\n\n\n<p>This plugin is available for download on my GitHub page: <a href=\"https:\/\/github.com\/ZebraNorth\/webgl-shader\">https:\/\/github.com\/ZebraNorth\/webgl-shader<\/a><\/p>\n\n\n\n<script class=\"wp-block-zebra-north-webgl-shader\" type=\"text\/javascript\">webglShader(\"\/\/ The maximum distance to cast a ray when ray marching.\\nconst float MAXIMUM_RAY_LENGTH = 100.0;\\n\\n\/**\\n * Calculate the distance from a position to the surface of a sphere.\\n *\\n * @param vec3 position The position to test.\\n * @param vec3 centre The centre of the sphere.\\n * @param vec3 radius The radius of the sphere.\\n *\\n * @return float Returns the distance from the position to the sphere's surface.\\n *\/\\nfloat sphereDistance(vec3 position, vec3 centre, float radius)\\n{\\n    return length(position - centre) - radius;\\n}\\n\\n\/**\\n * Calculate the distance to a horizontal plane at z = 0.\\n *\\n * @param vec3 position The position to test.\\n *\\n * @return float Returns the distance between the position and the ground plane.\\n *\/\\nfloat groundDistance(vec3 position)\\n{\\n    return position.y;\\n}\\n\\n\/**\\n * Calculate the distance to an infinite plane at centre\\n * with a given normal.\\n *\\n * @param vec3 position The position to test.\\n * @param vec3 centre   A point that the plane passes through.\\n * @param vec3 normal   The surface normal of the plane.\\n *\\n * @return float Returns the distance between the position and the plane.\\n *\/\\nfloat planeDistance(vec3 position, vec3 centre, vec3 normal)\\n{\\n    \/\/ Take a vector from the plane to the point.\\n    vec3 w = position - centre;\\n    \\n    \/\/ Project the vector onto the normal.\\n    return dot(normal, w);    \\n}\\n\\n\/**\\n * Find the shortest distance to the inside\\n * of an axis aligned unit cube.\\n *\\n * @param vec3 position The position of the ray to test.\\n * @param vec3 rayDirection The direction of the ray.\\n *\/\\nfloat unitSpaceDistance(vec3 position, vec3 rayDirection)\\n{\\n    float dx, dy, dz;\\n    \\n    if (rayDirection.x < 0.0)\\n        dx = position.x;\\n    else\\n        dx = 1.0 - position.x;\\n        \\n    if (rayDirection.y < 0.0)\\n        dy = position.y;\\n    else\\n        dy = 1.0 - position.y;\\n        \\n    if (rayDirection.z < 0.0)\\n        dz = position.z;\\n    else\\n        dz = 1.0 - position.z;\\n        \\n    return min(min(dx, dy), dz);        \\n}\\n\\n\/**\\n * Calculate the distance to a capsule.\\n *\\n * @param vec3  position The position to test.\\n * @param vec3  a        The position of the first end sphere.\\n * @param vec3  b        The position of the second end sphere.\\n * @param float radius   The radius of the end spheres.\\n *\\n * @return float Returns the distance between the position and the capsule.\\n *\/\\nfloat capsuleDistance(vec3 position, vec3 a, vec3 b, float radius)\\n{\\n    vec3 ap = position - a;\\n    vec3 bp = position - b;\\n    vec3 ab = b - a;\\n    \\n    \/\/ Find how far along the line a-b the closest point to p is.\\n    float t = dot(ap, ab) \/ dot(ab, ab);\\n    t = clamp(t, 0.0, 1.0);\\n    vec3 c = a + ab * t;\\n    \\n    return length(position - c) - radius;\\n}\\n\\n\/**\\n * Calculate the distance to a torus in the XZ plane.\\n *\\n * @param vec3  position    The position to test.\\n * @param vec3  centre      The centre of the torus.\\n * @param float majorRadius The main radius of the torus.\\n * @param flota minorRadius The radius of the tube.\\n *\/\\nfloat torusDistance(vec3 position, vec3 centre, float majorRadius, float minorRadius)\\n{\\n    \/\/ The torus is at the origin on the zx plane.\\n    \\n    \/\/ Move the position relative to the centre of the torus.\\n    position -= centre;\\n    \\n    \/\/ Get the distance from the position to the major radius on the xz plane.\\n    float x = length(position.xz) - majorRadius;\\n    \\n    \\n    return length(vec2(x, position.y)) - minorRadius;\\n}\\n\\n\/**\\n * Calculate the distance to a cube.\\n *\\n * @param vec3 position The position to test.\\n * @param vec3 centre   The centre of the cube.\\n * @param vec3 size     The length, width, and height.\\n *\\n * @return Returns the distance from the position to the closest point on the object.\\n *\/\\nfloat boxDistance(vec3 position, vec3 centre, vec3 size)\\n{\\n    position -= centre;\\n    \\n    \/\/ Rounded corners.\\n    float radius = 0.1;\\n    \\n    return length(max(abs(position) - size, 0.0)) - radius;\\n    \\n}\\n\\n\/**\\n * Calculate the distance to a cylinder.\\n *\\n * @param vec3  position The position to test.\\n * @param vec3  a        The first end point of the cylinder.\\n * @param vec3  b        The second end point of the cylinder.\\n * @param float radius   The radius of the cylinder.\\n *\\n * @return float Returns the distance from the position to the closest point on the object.\\n *\/\\nfloat cylinderDistance(vec3 position, vec3 a, vec3 b, float radius)\\n{\\n    vec3 ap = position - a;\\n    vec3 bp = position - b;\\n    vec3 ab = b - a;\\n    \\n    \/\/ Find how far along the line a-b the closest point to p is.\\n    float t = dot(ap, ab) \/ dot(ab, ab);\\n\\n    vec3 c = a + ab * t;\\n    float d = length(position - c) - radius;\\n    \\n    float y = (abs(t - 0.5) - 0.5) * length(ab);\\n    float e = length(max(vec2(d, y), 0.0));\\n    \\n    float i = min(max(d, y), 0.0);\\n    \\n    return e + i;\\n}\\n\\n\/**\\n * Get the minimum distance to the surface in modulo 1 space.\\n *\\n * @param vec3 position The current position of the ray.\\n *\\n * @return float Returns the minimum distance from the position to a surface.\\n *\/\\nfloat gridDistance(vec3 position)\\n{\\n\/\/    position = mod(position, 1.0);\\n    \\n    \/\/ Uncomment one of these to select which object to render.\\n\\n    \/\/return torusDistance(position, vec3(0.5, 0.5, 0.5), 0.2, 0.05);\\n    \/\/return sphereDistance(position, vec3(0.5, 0.5, 0.5), 0.4);\\n    return boxDistance(position, vec3(0.5, 0.5, 0.5), vec3(0.1, 0.1, 0.1));\\n}\\n\\n\\n\/**\\n * Get the distance from the given position to the nearest\\n * point on a surface of an object in the scene.\\n *\\n * @param vec3 position The position to check.\\n *\\n * @return float Returns the minium distance from the position to a surface.\\n *\/\\nfloat getSurfaceDistance(vec3 position)\\n{\\n    return gridDistance(position);\\n}\\n\\n\/**\\n * Get the surface normal at the given point in world space.\\n * This works by finding surface points a small distance away\\n * from the position.\\n *\\n * @param vec3 position The position at which to find the normal.\\n *\\n * @return vec3 Returns a normal.\\n *\/\\nvec3 getSurfaceNormal(vec3 position)\\n{\\n    vec2 epsilon = vec2(0.01, 0.0);\\n    float dist = getSurfaceDistance(position);\\n    vec3 normal = dist - vec3(\\n        getSurfaceDistance(position - epsilon.xyy),\\n        getSurfaceDistance(position - epsilon.yxy),\\n        getSurfaceDistance(position - epsilon.yyx));\\n        \\n    return normalize(normal);\\n}\\n\\nvec3 unitSpace(vec3 position)\\n{\\n    return mod(position, 1.0);\\n}\\n\\n\/**\\n * Perform ray marching in the scene.\\n * \\n * @param vec3 rayOrigin The starting point of the ray (camera position).\\n * @param vec3 rayDirection The direction of the ray, in world space.\\n *\\n * @return vec3 Returns the distance from the ray origin at which the ray\\n *              intersects the scene.\\n *\/\\nfloat rayMarch(vec3 rayOrigin, vec3 rayDirection)\\n{\\n    const int MAX_STEPS = 100;\\n    const float RAY_INTERSECTION_DISTANCE = 0.01;\\n    \\n    float distanceFromOrigin = 0.0;\\n    \\n    for (int i = 0; i < MAX_STEPS; ++i)\\n    {\\n        vec3 rayPosition = rayOrigin + distanceFromOrigin * rayDirection;\\n        float surfaceDistance = getSurfaceDistance(rayPosition);\\n        distanceFromOrigin += surfaceDistance;\\n        \\n        if (surfaceDistance < RAY_INTERSECTION_DISTANCE || distanceFromOrigin > MAXIMUM_RAY_LENGTH)\\n            break;\\n    }\\n    \\n    return min(MAXIMUM_RAY_LENGTH, distanceFromOrigin);\\n}\\n\\n\/**\\n * March rays through an infinitely tiled unit space.\\n * \\n * @param vec3 rayOrigin The starting point of the ray (camera position).\\n * @param vec3 rayDirection The direction of the ray, in world space.\\n *\\n * @return vec3 Returns the distance from the ray origin at which the ray\\n *              intersects the scene.\\n *\/\\nfloat rayMarchUnitSpace(vec3 rayOrigin, vec3 rayDirection)\\n{\\n    const int MAX_STEPS = 300;\\n    const float RAY_INTERSECTION_DISTANCE = 0.001;\\n    float distanceFromOrigin = 0.0;\\n    float totalDistance = 0.0;\\n    rayOrigin = unitSpace(rayOrigin);\\n    \\n    int i;\\n    \\n    for (i = 0; i < MAX_STEPS; ++i)\\n    {\\n        vec3 rayPosition = rayOrigin + distanceFromOrigin * rayDirection;\\n        float surfaceDistance = getSurfaceDistance(rayPosition);\\n        float unitSpaceDistance = unitSpaceDistance(rayPosition, rayDirection);\\n        \\n        if (unitSpaceDistance < surfaceDistance)\\n        {\\n            distanceFromOrigin += unitSpaceDistance;\\n            totalDistance += unitSpaceDistance;\\n            \\n            if (unitSpaceDistance < RAY_INTERSECTION_DISTANCE || totalDistance > MAXIMUM_RAY_LENGTH)\\n            {\\n                rayOrigin = unitSpace(rayOrigin + rayDirection * (distanceFromOrigin + RAY_INTERSECTION_DISTANCE));\\n                distanceFromOrigin = 0.0;\\n            }\\n        }\\n        else\\n        {\\n            distanceFromOrigin += surfaceDistance;\\n            totalDistance += surfaceDistance;\\n\\n            if (surfaceDistance < RAY_INTERSECTION_DISTANCE || totalDistance > MAXIMUM_RAY_LENGTH)\\n                break;\\n        }\\n    }\\n    \\n    if (i == MAX_STEPS)\\n        return MAXIMUM_RAY_LENGTH;\\n    \\n    return min(MAXIMUM_RAY_LENGTH, totalDistance);\\n}\\n\\n\/**\\n * Calculate Phong shading.\\n *\\n * @param vec3  surfacePoint    The point being shaded.\\n * @param vec3  normal          The surface normal.\\n * @param vec3  lightPosition   The location of the light.\\n * @param vec3  viewerDirection A unit vector from the surface point in the direction of the camera.\\n * @param float ambient         The ambient lighting, 0..1.\\n * @param float diffuse         The diffuse lighting, 0..1.\\n * @param float specular        The specular highlight strength, 0..1.\\n * @param float shininess       The highlight sharpness, 0.. higher gives a smaller reflection.\\n *\/\\nfloat phong(vec3 surfacePoint, vec3 normal, vec3 lightPosition, vec3 viewerDirection, float ambient, float diffuse, float specular, float shininess)\\n{\\n    vec3 lightDirection = normalize(lightPosition - surfacePoint);\\n    \\n    vec3 reflection = normalize(2.0 * dot(lightDirection, normal) * normal - lightDirection);\\n    return ambient + diffuse * clamp(dot(lightDirection, normal), 0.0, 1.0) + specular * pow(clamp(dot(reflection, viewerDirection), 0.0, 1.0), shininess);\\n}\\n\\n\/**\\n * Calculate the surface shading at the intersection point.\\n *\\n * @param vec3  position      The point on the surface.\\n * @param vec3  lightPosition The position of the light source.\\n * @param float ambient       The amount of ambient illumination.\\n * @param float diffuse       The brightness of the diffuse lighting, 0..1.\\n * @param float specular      The brightness of the specular highlight, 0..1.\\n * @param float shininess     The sharpness of the specular highlight, higher being sharper.\\n *\\n * @return float Returns the brightness at the given position.\\n *\/\\nfloat shade(vec3 position, vec3 lightPosition, vec3 cameraPosition, float ambient, float diffuse, float specular, float shininess)\\n{\\n    vec3 surfaceNormal = getSurfaceNormal(position);\\n    vec3 lightDirection = normalize(lightPosition - position);\\n    vec3 pointToCamera = normalize(cameraPosition - position);\\n\\n    float brightness = phong(position, surfaceNormal, lightPosition, pointToCamera, ambient, diffuse, specular, shininess);\\n    clamp(dot(lightDirection, surfaceNormal), 0.0, 1.0);\\n    \\n    \/*\\n    float distanceTowardsLight = rayMarch(position + surfaceNormal * 0.02, lightDirection);\\n    \\n    if (distanceTowardsLight < max(MAXIMUM_RAY_LENGTH, length(lightPosition - position) + 0.02))\\n      return ambient;\\n      *\/\\n     \\n    float distance = length(position - cameraPosition);\\n    \\n    \/\/ Apply fog.\\n    brightness \/= max(distance - 2.0, 0.001);\\n    \\n    return brightness;\\n}\\n\\n\/**\\n * Build a rotation matrix.\\n *\\n * @param float roll  Rotation around the x axis in radians.\\n * @param float pitch Rotation around the y axis in radians.\\n * @param float yaw   Rotation around the z axis in radians.\\n *\\n * @return mat3 Returns a rotation matrix with the given Euler angles.\\n *\/\\nmat3 rotationMatrix3d(float roll, float pitch, float yaw)\\n{\\n    float sinRoll = sin(roll);\\n    float cosRoll = cos(roll);\\n    float sinPitch = sin(pitch);\\n    float cosPitch = cos(pitch);\\n    float sinYaw = sin(yaw);\\n    float cosYaw = cos(yaw);\\n      \\n    return mat3(\\n        vec3(cosRoll * cosPitch, cosRoll * sinPitch * sinYaw - sinRoll * cosYaw, cosRoll * sinPitch * cosYaw + sinRoll * sinYaw),\\n        vec3(sinRoll * cosPitch, sinRoll * sinPitch * sinYaw + cosRoll * cosYaw, sinRoll * sinPitch * cosYaw - cosRoll * sinYaw),\\n        vec3(-sinPitch, cosPitch * sinYaw, cosPitch * cosYaw));\\n}\\n\\n\/**\\n * The fragment shader function.\\n *\\n * @param vec4 fragColor The output fragment colour.\\n * @param vec2 fragCoord The UV coordinates of the fragment.\\n *\/\\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord)\\n{\\n    \/\/ Normalized pixel coordinates (from 0 to 1).\\n    vec2 uv = ((fragCoord - iResolution.xy \/ 2.0) \/ iResolution.y);\\n    \\n    \/\/ Set up the camera.\\n    vec3 cameraPosition = vec3(5.0, 0.0, 5.0) * sin(iTime * 0.5);\\n    mat3 cameraRotation = rotationMatrix3d(iTime * 0.1, iTime, iTime * 0.3);\\n    \\n    \/\/ Set up the ray from the camera to the scene.\\n    vec3 rayDirection = vec3(uv.x, uv.y, 1.0);\\n    rayDirection = normalize(rayDirection);\\n    rayDirection *= cameraRotation;\\n    \\n    \/\/ Perform ray marching to find what the ray intersects.\\n    float surfaceDistance = rayMarchUnitSpace(cameraPosition, rayDirection);\\n    \\n    vec3 surfacePoint = cameraPosition + surfaceDistance * rayDirection;\\n    \\n    \/\/ Set up the light source.\\n    vec3 lightPosition = vec3(0.0, 5.0, 6.0);\\n    lightPosition.xz += vec2(sin(iTime), cos(iTime)) * 2.0;\\n  \\n    \/\/ Find the colour at the point of intersection.\\n    vec3 col = vec3(1.0 - shade(surfacePoint, lightPosition, cameraPosition, 1.0, 0.0, 0.0, 10.0));\\n\\n    \/\/ Set the output colour.\\n    fragColor = vec4(col, 1.0);\\n}\\n\");<\/script>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to create a custom WordPress layout block to display WebGL shaders.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7,6,5],"tags":[],"class_list":["post-37","post","type-post","status-publish","format-standard","hentry","category-shader","category-webgl","category-wordpress"],"_links":{"self":[{"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/posts\/37","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/comments?post=37"}],"version-history":[{"count":13,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/posts\/37\/revisions"}],"predecessor-version":[{"id":62,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/posts\/37\/revisions\/62"}],"wp:attachment":[{"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/media?parent=37"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/categories?post=37"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/tags?post=37"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}