How to draw 2D image with TWGL (WebGL helper Library)

2.7k Views Asked by At

There are dozens of examples on how draw 3d stuff with TWGL https://github.com/greggman/twgl.js/tree/master/examples. But how can I draw 2D image with it? Especially I'm interested in how to do it without using shaders?

1

There are 1 best solutions below

3
On BEST ANSWER

TWGL's only point is to make WebGL less verbose but WebGL basically only does 1 thing and that is draw stuff with shaders you provide. So you really can't do anything in TWGL without shaders just like you can't do anything in WebGL without shaders.

If you don't want the write any shaders then you need to use a higher level 3D library like three.js or pixi.js or p5.js or even something like Unity3D as they supply the shaders for you.

Otherwise here's some code that basically implements canvas 2d's drawImage function in WebGL using TWGL

var m4 = twgl.m4;
var gl = twgl.getWebGLContext(document.getElementById("c"));
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);

// a unit quad
var bufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
      
// we're only using 1 texture so just make and bind it now
var tex = twgl.createTexture(gl, {
  src: 'https://farm6.staticflickr.com/5695/21506311038_9557089086_m_d.jpg',
  crossOrigin: '', // not needed if image on same origin
}, function(err, tex, img) {
  // wait for the image to load because we need to know it's size
  startRendering(img);
});

function startRendering(img) {

  requestAnimationFrame(render);
  
  function render(time) {
    time *= 0.001;
    
    twgl.resizeCanvasToDisplaySize(gl.canvas);
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); 
    
    drawImage(
      gl.canvas.width, gl.canvas.height,
      tex, img.width, img.height,
      Math.sin(time) * 50, Math.sin(time * 1.1) * 50);
    requestAnimationFrame(render);
  }
}
                             
                             
// we pass in texWidth and texHeight because unlike images
// we can't look up the width and height of a texture

// we pass in targetWidth and targetHeight to tell it
// the size of the thing we're drawing too. We could look 
// up the size of the canvas with gl.canvas.width and
// gl.canvas.height but maybe we want to draw to a framebuffer
// etc.. so might as well pass those in.

// srcX, srcY, srcWidth, srcHeight are in pixels 
// computed from texWidth and texHeight

// dstX, dstY, dstWidth, dstHeight are in pixels
// computed from targetWidth and targetHeight
function drawImage(
    targetWidth, targetHeight,
    tex, texWidth, texHeight,
    srcX, srcY, srcWidth, srcHeight,
    dstX, dstY, dstWidth, dstHeight) {
  if (srcWidth === undefined) {
    srcWidth = texWidth;
    srcHeight = texHeight;
  }
  if (dstX === undefined) {
    dstX = srcX;
    dstY = srcY;
    srcX = 0;
    srcY = 0;
  }
  if (dstWidth === undefined) {
    dstWidth = srcWidth;
    dstHeight = srcHeight;
  }      
      
  var mat  = m4.identity();
  var tmat = m4.identity();
  
  var uniforms = {
    matrix: mat,
    textureMatrix: tmat,
    texture: tex,
  };

  // these adjust the unit quad to generate texture coordinates
  // to select part of the src texture

  // NOTE: no check is done that srcX + srcWidth go outside of the
  // texture or are in range in any way. Same for srcY + srcHeight

  m4.translate(tmat, [srcX / texWidth, srcY / texHeight, 0], tmat);
  m4.scale(tmat, [srcWidth / texWidth, srcHeight / texHeight, 1], tmat);

  // these convert from pixels to clip space
  m4.ortho(0, targetWidth, targetHeight, 0, -1, 1, mat)

  // these move and scale the unit quad into the size we want
  // in the target as pixels
  m4.translate(mat, [dstX, dstY, 0], mat);
  m4.scale(mat, [dstWidth, dstHeight, 1], mat);

  gl.useProgram(programInfo.program);
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  twgl.setUniforms(programInfo, uniforms);
  twgl.drawBufferInfo(gl, bufferInfo);
  
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<script id="vs" type="not-js">
// we will always pass a 0 to 1 unit quad
// and then use matrices to manipulate it
attribute vec4 position;   

uniform mat4 matrix;
uniform mat4 textureMatrix;

varying vec2 texcoord;

void main () {
  gl_Position = matrix * position;
  
  texcoord = (textureMatrix * position).xy;
}
</script>
<script id="fs" type="not-js">
precision mediump float;

varying vec2 texcoord;
uniform sampler2D texture;

void main() {
  if (texcoord.x < 0.0 || texcoord.x > 1.0 ||
      texcoord.y < 0.0 || texcoord.y > 1.0) {
    discard;
  }
  gl_FragColor = texture2D(texture, texcoord);
}
</script>
<canvas id="c"></canvas>

For a more detailed explanation see this article on making a WebGL clone of drawImage from 2D canvas and this article on implementing a matrix stack like 2D canvas.