Skip to content

vondas-network/webgl-ordinals

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

web graphics on the Bitcoin blockchain

Overview

I’m a software/hardware developer and a video artist that likes to create tech that doesn’t exist with the goal of empowering others. Doing my best to find tools that are available to everyone. This is one way to approach the problem of content distribution for artist. Some goals for the project:

  • Create a single HTML5 website template that renders WebGL fragment shaders without external libraries.
  • Share insights to others about the Bitcoin inscription process for artistic purposes.
  • Use Bitcoin to distribute art without downtime or censorship.
  • Add libraries to the blockchain where needed.

Why is this important?

  • OpenGL allows for high-fidelity graphics by working directly with the GPU. WebGL is the web version of OpenGL. If you’ve used a computer in the past 30 years, you’ve experienced OpenGL graphics. For example, Three.js is a user-friendly wrapper for WebGL.
  • Empower others to push the limits of graphical possibilities on the Bitcoin blockchain.
  • The size of a shader is small, which makes it great for inscribing. Technically, there is no size limitation to shaders that I know of.
  • No external dependencies. HTML5 + Javascript is all you need.
  • A HTML5 + WebGL website example inscription hasn’t happened yet.

On-chain Files

Inscription On-chain Content
glsl-canvas.js v0.2.8 link
#11635450 link
#11637749 link
#11805310 link
#11875520 link
#11875521 link
#11875522 link
#11875523 link
#11875524 link
#11875525 link
#11875526 link
#12077747 link
#12077748 link
#12077749 link
#12077750 link
#12077751 link
#12077752 link
#12077753 link
#12077753 link
#12077754 link
#12077755 link
#12102422 link
#12102423 link
#12102424 link
#12102425 link
#12102426 link

Tutorial

HTML5 Template

To start, I needed a working example which demonstrated how to run a WebGL example within a HTML5 file. The key was finding one that loads a fragment shader. Luckily, I found a working an example from Pablo Colapinto that runs locally without any external dependencies. This example is pasted below.

<html>
<script type="text/javascript">

var GL;
var shaderId;
var vertexBuffer;
var indexBuffer;
var timer = 0;

function initWebGL(){
    // Get Canvas Element
    var canvas = document.getElementById("glcanvas");
    // Get A WebGL Context
    GL = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
    // Test for Success
    if (!GL) {
      alert("Unable to initialize WebGL. Your browser may not support it.");
    }
    // Set clear color to red, fully opaque
    GL.clearColor(1.0, 0.0, 0.0, 1.0);
    // Clear Screen
    GL.clear(GL.COLOR_BUFFER_BIT| GL.DEPTH_BUFFER_BIT);

    initShaderProgram();
    initBuffers();
}

function initShaderProgram(){

    //Create Program and Shaders
    shaderId = GL.createProgram();
    var vertId = GL.createShader(GL.VERTEX_SHADER);
    var fragId = GL.createShader(GL.FRAGMENT_SHADER);

    //Load Shader Source (source text are in scripts below)
    var vert = document.getElementById("vertScript").text;
    var frag = document.getElementById("fragScript").text;

    GL.shaderSource(vertId, vert);
    GL.shaderSource(fragId, frag);

    //Compile Shaders
    GL.compileShader(vertId);
    GL.compileShader(fragId);

    //Check Vertex Shader Compile Status
    if (!GL.getShaderParameter(vertId, GL.COMPILE_STATUS)) {  
        alert("Vertex Shader Compiler Error: " + GL.getShaderInfoLog(id));  
        GL.deleteShader(vertId);
        return null;  
    }

    //Check Fragment Shader Compile Status
    if (!GL.getShaderParameter(fragId, GL.COMPILE_STATUS)) {  
        alert("Fragment Shader Compiler Error: " + GL.getShaderInfoLog(id));  
        GL.deleteShader(fragId);
        return null;  
    }

    //Attach and Link Shaders
    GL.attachShader(shaderId, vertId);
    GL.attachShader(shaderId, fragId);
    GL.linkProgram(shaderId);

    //Check Shader Program Link Status
    if (!GL.getProgramParameter(shaderId, GL.LINK_STATUS)) {
      alert("Shader Linking Error: " + GL.getProgramInfoLog(shader));
    }     

}

function initBuffers(){

    //Some Vertex Data
    var vertices = new Float32Array( [
      -1.0, -1.0, 0.0,
      -1.0,  1.0, 0.0,
       1.0,  1.0, 0.0,
       1.0, -1.0, 0.0
    ]);
    //Create A Buffer
    vertexBuffer = GL.createBuffer();
    //Bind it to Array Buffer
    GL.bindBuffer(GL.ARRAY_BUFFER, vertexBuffer);
    //Allocate Space on GPU
    GL.bufferData(GL.ARRAY_BUFFER, vertices.byteLength, GL.STATIC_DRAW);
    //Copy Data Over, passing in offset
    GL.bufferSubData(GL.ARRAY_BUFFER, 0, vertices );

    //Some Index Data
    var indices = new Uint16Array([ 0,1,3,2 ]);
    //Create A Buffer
    indexBuffer = GL.createBuffer();
    //Bind it to Element Array Buffer
    GL.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, indexBuffer);
    //Allocate Space on GPU
    GL.bufferData(GL.ELEMENT_ARRAY_BUFFER, indices.byteLength, GL.STATIC_DRAW);
    //Copy Data Over, passing in offset
    GL.bufferSubData(GL.ELEMENT_ARRAY_BUFFER, 0, indices );

}

//ANIMATION FUNCTION (to be passed a callback)  see also http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
window.requestAnimFrame = ( function() {
   
    //Find best option given current browser
    return  window.requestAnimationFrame || 
            window.webkitRequestAnimationFrame ||  
            window.mozRequestAnimationFrame || 
            window.oRequestAnimationFrame || 
            window.msRequestAnimationFrame ||
    
    // if none of the above, use non-native timeout method
    function(callback) {
      window.setTimeout(callback, 1000 / 60);
    };
  
  } ) (); 

function animationLoop(){
    // feedback loop requests new frame
    requestAnimFrame( animationLoop );
    // render function is defined below
    render(); 
}

function render(){
    timer+=.1;

    //Bind Shader
    GL.useProgram(shaderId);   
    //Update uniform variable on shader
    var uID = GL.getUniformLocation(shaderId, "uTime");
    GL.uniform1f(uID, timer);
    //Enable Position Attribute
    var attId = GL.getAttribLocation(shaderId, "position");
    GL.enableVertexAttribArray(attId);
    //Bind Vertex Buffer
    GL.bindBuffer(GL.ARRAY_BUFFER, vertexBuffer);
    ///Point to Attribute (loc, size, datatype, normalize, stride, offset)
    GL.vertexAttribPointer( attId, 3, GL.FLOAT, false, 0, 0);
    //Bind Index Buffer
    GL.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, indexBuffer);
    //Draw!      --( mode, number_of_elements, data type, offset )
    GL.drawElements(GL.TRIANGLE_STRIP, 4, GL.UNSIGNED_SHORT, 0);
}

function start(){
    initWebGL();
    animationLoop();
}

</script>

<!-- VERTEX SHADER SOURCE -->
<script id="vertScript" type="text/glsl">

  #ifdef GL_ES
  precision lowp float;
  #endif

  attribute vec3 position; 

  void main(void) {
    gl_Position = vec4(position,1.0);
  }

</script>

<!-- FRAGMENT SHADER SOURCE -->

<script id="fragScript" type="text/glsl">
<!-- ADD GLSL SHADER CODE HERE -->
  #ifdef GL_ES
  precision lowp float;
  #endif

  #define PI 3.14159265359

  uniform float uTime;

  void main(void) {

    //divide pixel location by canvas width and height
    //to get values are now between 0.0 and 1.0
    vec2 st = gl_FragCoord.xy/vec2(640,480);
    //some fun functions for picking colors
    float r = sin(uTime+8.*PI*st.x);
    float g = fract( sin(3.*PI*st.y) );   
    float b = sin(uTime+PI*st.x*st.y);
    vec3 color = vec3(r,b,g);
    gl_FragColor = vec4(color,1.0);
  }
</script>

<body onload = start() >
<canvas id="glcanvas" width=640 height=480 style = "margin:auto; display:block"> 
 Oops, browser has no <code> canvas </code> tag support 
</canvas>
</body>

</html>

I look for the Fragment Shader section and removed the code from inside the <script> tag. Below is what it should look like once you remove the code from the template.

...
<!-- FRAGMENT SHADER SOURCE -->
<script id="fragScript" type="text/glsl">
<!-- ADD GLSL SHADER CODE HERE -->
</script>
...

Then, I pasted my GLSL shader, or Fragment Shader, into the area where it says Add GLSL Shader Code here.

<!-- FRAGMENT SHADER SOURCE -->
<script id="fragScript" type="text/glsl">
<!-- ADD GLSL SHADER CODE HERE -->
    #ifdef GL_ES
    precision mediump float;
    #endif
    
    #define PI 3.14159265359
    
    uniform vec2 uResolution;
    uniform vec2 uMouse;
    uniform float uTime;
    
    float plot(vec2 st, float pct){
      return  smoothstep( pct-0.02, pct, st.y) -
              smoothstep( pct, pct+0.02, st.y);
    }
    
    void main() {
        vec2 st = gl_FragCoord.xy/vec2(640,480);
        st -= 0.5; 
        st *= 12.0;
        float pct2 = 0.0;
        pct2 = distance(st,vec2(0.5));
    
        float y3 = sin(cos((st.y)*(0.10)))-sin(st.x+st.y*02.2);
        float y = smoothstep(1.2-(sin(((y3)))*(cos(st.y/-9.2))),0.5,st.x) - smoothstep(0.5,0.1,(st.x*0.2))+(sin(st.x-(uTime*.0412)));
        float y2 = smoothstep(04.91,02.5,st.y+st.x) - smoothstep(0.481,0.918,st.y);
        vec3 colorA = vec3(y*y2)-(y3-(0.14))*(sin(st.x-(uTime*.02412)));
        colorA = (1.0)*colorA*vec3(.020+(sin(((y3+y)))+(cos(pct2*-0.2))),0.120,(0.10+(sin(y3))));
    
        float y4 = cos(cos((st.x)*(0.13)*sin(0.23)));
        float y5 = smoothstep(0.2,0.25,0.3) - smoothstep(0.25,0.31,(st.y*0.12));
        float y6 = smoothstep(0.9+(st.x*0.012),0.1915,st.y+cos(st.y)) - smoothstep(0.641,0.8191,(st.x+st.y)*0.2);
        vec3 colorB = vec3(y4+y5)-(y3*(0.913))*(sin(st.x+(uTime*.031592)));
        colorB = (1.0)*colorB*vec3(.920,0.95030,(0.20-y6))*(sin(st.y-(uTime*.032412)));
    
        vec2 bl = step(vec2(0.5),st);
        float pct = bl.x * bl.y;
        vec3 colorMix = vec3(0.0);
        colorMix = mix(colorA*0.432, colorB+0.3892, 0.983*(sin(((y6+y2)))*(cos(pct2/-2.2))));
    
        gl_FragColor = vec4(colorMix,1.0);
    }
</script>

Save the file as a new index.html. Check to see if the shader rasters correctly.

Inscribing to the Bitcoin blockchain

We’ve tested it locally, and it runs as expected. Code is here if you are curious. It’s time to inscribe.

Inscribing the HTML file

Go to looksordinal. It is a cost-effective site for self-custodial bulk inscriptions. The website looks like the image below.

Add your Ordinal address to the Receiving Address section. Don’t forget that your Ordinal address is different than your BTC recipient address.

1_c5bwlsi6n0Pz912brWPGGA copy

Choose the file you would like to inscribe. Here, it’s index.html.

1_c5bwlsi6n0Pz912brWPGGA copy

Select the freerate. I’d suggest Mid because if you choose Min, you risk waiting a long time for the inscription to complete. Don’t forget to tip your dev!

1_c5bwlsi6n0Pz912brWPGGA copy

Click Estimate Fees to see the cost of the inscription. Then the press the inscribe button.

1_c5bwlsi6n0Pz912brWPGGA copy

The page will transition to the payment process. Scan the QR code with your preferred Bitcoin app and send the payment to the address provided.

1_c5bwlsi6n0Pz912brWPGGA copy

Once the payment is received, the inscription process begins. Click the transaction link.

1_c5bwlsi6n0Pz912brWPGGA copy

Track the progress of your inscription by using mempool.space.

1_c5bwlsi6n0Pz912brWPGGA copy

When it’s finished inscribing, check it out on Ordinals.

Final thoughts

This is the first time a WebGL compatible HTML5 webpage has been inscribed to the Bitcoin blockchain. It is a massive moment for decentralized graphics and pushes the perception of how a blockchain can be used. It’s your turn, change the world.

Glossary

Here is primer if you are unfamiliar with concepts mentioned above.

Bitcoin Ordinals: The new Ordinals protocol allows people who operate Bitcoin nodes to inscribe each sat with data, creating something called an Ordinal. That data inscribed on Bitcoin can include smart contracts, which in turn enables NFTs. In rough terms, Ordinals are NFTs you can mint directly onto the Bitcoin blockchain.

WebGL: WebGL is a JavaScript API for rendering interactive 2D and 3D graphics within any compatible web browser without the use of plug-ins. WebGL is fully integrated with other web standards, allowing GPU-accelerated usage of physics and image processing and effects as part of the web page canvas.

GLSL Shaders: Shaders use GLSL (OpenGL Shading Language), a special OpenGL Shading Language with syntax similar to C. GLSL is executed directly by the graphics pipeline. There are several kinds of shaders, but two are commonly used to create graphics on the web: Vertex Shaders and Fragment (Pixel) Shaders. Applications using OpenGL include computer games, virtual reality, augmented reality, 3D animation, CAD and other visual simulations.

OpenGL: OpenGL (Open Graphics Library) is a cross-language, multi-platform application programming interface (API) for rendering 2D and 3D vector graphics. The API is typically used to interact with a graphics processing unit (GPU), to achieve hardware-accelerated rendering.

References