JayDee JayDee - 6 months ago 47
Javascript Question

what is the proper way to do fullscreen freehand drawing in webgl?

To implement mouse gestures in webgl, i would like to allow the users to "draw on screen" freehand mode. Doing it in 3D webgl would allow to have nice shader effects on the brush being used such as fire effects, glows or other cool graphical candy.

what is the current recommend way to draw on screen in webgl efficiently?

thanks!

Answer

You have 2 options.

You could draw to an fbo and then draw the fbo to the canvas

or

You could request 'preserveDrawingBuffer: true' when creating the webgl context.

var canvas = document.getElementById("canvas");
var gl = canvas.getContext("webgl", {preserveDrawingBuffer:true});

program = twgl.createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);
gl.useProgram(program);

var positionLoc = gl.getAttribLocation(program, "v_position");
var offsetLoc = gl.getUniformLocation(program, "u_offset");

var res = 1;
setupQuad(gl, res, positionLoc);

canvas.addEventListener('mousemove', draw, false);

function draw(event) {
  var m = getNoPaddingNoBorderCanvasRelativeMousePosition(event);
  // convert mouse coords to clipspace since that's what this
  // particlar shader is using. See 
  // http://games.greggman.com/game/webgl-fundamentals/
  // for how to use pixels instead of clipspace. 
  var x = m.x / gl.canvas.width * 2 - 1;
  var y = m.y / gl.canvas.height * -2 + 1;  // flip y

  drawBrush(x, y);
}

function drawBrush(x, y) {
  gl.uniform2f(offsetLoc, x, y);

  gl.drawElements(gl.TRIANGLES, res * res * 6, gl.UNSIGNED_SHORT, 0);
}

drawBrush(0, 0);



function setupQuad(gl, gridRes, positionLoc) {
  var scale = 0.05;
  var objects = [];

  var vertsAcross = gridRes + 1;
  var numVerts = vertsAcross * vertsAcross;
  var positions = new Float32Array(numVerts * 2);
  var indices = new Uint16Array(6 * gridRes * gridRes);
  var poffset = 0;

  for (var zz = 0; zz <= gridRes; ++zz) {
    for (var xx = 0; xx <= gridRes; ++xx) {
      var u = xx / gridRes;
      var v = zz / gridRes;
      positions[poffset + 0] = (-1 + 2 * u) * scale;
      positions[poffset + 1] = (-1 + 2 * v) * scale;
      poffset += 2;
    }
  }
    
  var tbase = 0;
  for (var zz = 0; zz < gridRes; ++zz) {
    var index = zz * vertsAcross;
    for (var xx = 0; xx < gridRes; ++xx) {
      indices[tbase + 0] = index + 0;
      indices[tbase + 1] = index + 1;
      indices[tbase + 2] = index + vertsAcross;
      indices[tbase + 3] = index + vertsAcross;
      indices[tbase + 4] = index + 1;
      indices[tbase + 5] = index + vertsAcross + 1;
      index += 1;
      tbase += 6;
    }
  }
    
  function makeBuffer(data, type, size, loc) {
    var buf = gl.createBuffer();
    gl.bindBuffer(type, buf);
    gl.bufferData(type, data, gl.STATIC_DRAW);
    if (type == gl.ARRAY_BUFFER) {
      gl.enableVertexAttribArray(loc);
      gl.vertexAttribPointer(loc, size, gl.FLOAT, false, 0, 0);
    }
    return buf;
  }
    
  objects.push(makeBuffer(positions, gl.ARRAY_BUFFER, 2, positionLoc));
  objects.push(makeBuffer(indices, gl.ELEMENT_ARRAY_BUFFER));
    
  return objects;
};

function getRelativeMousePosition(event, target) {
  target = target || event.target;
  var rect = target.getBoundingClientRect();

  return {
    x: event.clientX - rect.left,
    y: event.clientY - rect.top,
  }
}

// assumes target or event.target is canvas
function getNoPaddingNoBorderCanvasRelativeMousePosition(event, target) {
  target = target || event.target;
  var pos = getRelativeMousePosition(event, target);

  pos.x = pos.x * target.width  / canvas.clientWidth;
  pos.y = pos.y * target.height / canvas.clientHeight;

  return pos;  
}
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/twgl.min.js"></script>
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 v_position;
uniform vec2 u_offset;
    
void main() {
    gl_Position = vec4(v_position + u_offset, 0, 1);
}
</script>
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
void main() {
    gl_FragColor = vec4(0, 1, 0, 1);  
}
</script>
<canvas id="canvas" width="400" height="300"></canvas>