2
votes

Is it possible to access low-level WebGL from openlayers in the same way simple canvas html5 element can be accessed?

var context = event.glContext;
var gl = context.getGL();

I've tried to adapt the low-level clipping example in openlayers to draw a simple triangle such as the one in WebGL Fundamentals tutorial hooking it to the precompose event but without any luck.

Is this the right event to hook to?

The following code has an openstreetmap tile layer as base and static image with reprojection on top following this example.

The idea was to manipulate the top static image as canvas to test low-level WebGL. But the only thing that happens is that the top layer clips the bottom OSM layer no matter what points I pass to the buffer.

I have a very limited understanding of WebGL, can somebody point me in the right direction?

proj4.defs('EPSG:27700', '+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 ' +
  '+x_0=400000 +y_0=-100000 +ellps=airy ' +
  '+towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 ' +
  '+units=m +no_defs');
var imageExtent = [0, 0, 700000, 1300000];

var img = new ol.layer.Image({
  source: new ol.source.ImageStatic({
    url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/1/18/' +
      'British_National_Grid.svg/2000px-British_National_Grid.svg.png',
    crossOrigin: '',
    projection: 'EPSG:27700',
    imageExtent: imageExtent
  })
});

var myosm = new ol.layer.Tile({
  source: new ol.source.OSM()
});

var map = new ol.Map({
  //layers: [myosm, img],
  layers: [myosm, img],
  target: 'map',
  renderer: /** @type {Array<ol.renderer.Type>} */ (['webgl', 'canvas']),
  view: new ol.View({
    center: ol.proj.transform(
      ol.extent.getCenter(imageExtent), 'EPSG:27700', 'EPSG:3857'),
    zoom: 4
  })
});

var vertexShaderSource = document.getElementById("2d-vertex-shader").text;
var fragmentShaderSource = document.getElementById("2d-fragment-shader").text;

img.on('precompose', function(e) {
  var context = e.glContext;
  var gl = context.getGL();
  var program = gl.createProgram();
  var vertexShader = gl.createShader(gl.VERTEX_SHADER);
  gl.shaderSource(vertexShader, vertexShaderSource);
  gl.compileShader(vertexShader);
  gl.attachShader(program, vertexShader);
  var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
  gl.shaderSource(fragmentShader, fragmentShaderSource);
  gl.compileShader(fragmentShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);

  var positionLocation = gl.getAttribLocation(program, 'a_position');
  var positions = [
    0, 0,
    0, 0.5,
    0.7, 0,
  ];
  var buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
  gl.clearColor(0, 0, 0, 0);
  gl.clear(gl.COLOR_BUFFER_BIT);
  context.useProgram(program);
  gl.enableVertexAttribArray(positionLocation);

  gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
  gl.drawArrays(gl.TRIANGLES, 0, 24);
});
<link href="https://openlayers.org/en/v4.6.4/css/ol.css" rel="stylesheet"/>
<script src="https://openlayers.org/en/v4.6.4/build/ol.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.4.4/proj4.js"></script>
<div id="map" class="map"></div>
<div id="no-webgl" class="alert alert-danger" style="display: none">
  This example requires a browser that supports <a href="http://get.webgl.org/">WebGL</a>.
</div>
<script id="2d-vertex-shader" type="notjs">
  // an attribute will receive data from a buffer
  attribute vec4 a_position;

  // all shaders have a main function
  void main() {

    // gl_Position is a special variable a vertex shader
    // is responsible for setting
    gl_Position = a_position;
  }
</script>
<script id="2d-fragment-shader" type="notjs">
  // fragment shaders don't have a default precision so we need
  // to pick one. mediump is a good default
  precision mediump float;

  void main() {
    // gl_FragColor is a special variable a fragment shader
    // is responsible for setting
    gl_FragColor = vec4(1, 0, 0.5, 1); // return redish-purple
  }
</script>
1

1 Answers

2
votes

I Found a way to manipulate low-level WebGL on top of openlayers by using an image layer with an ImageCanvas as source:

var canvasLayer = new ol.layer.Image({
  source: new ol.source.ImageCanvas({
    canvasFunction: canvasFunction,
    projection: 'EPSG:3857'
  })
});

The canvasFunction will be the result from the WebGL routines (prepares and calls mainWebGL():

var canvasFunction = function(extent, resolution, pixelRatio, size, projection) {
  var canvas = document.createElement('canvas');
  canvas.setAttribute('width', size[0]);
  canvas.setAttribute('height', size[1]);
  var gl = canvas.getContext("webgl");
  var glOut = mainWebGL(canvas, gl);
  gl.drawArrays(glOut[1], glOut[2], glOut[3]);
  return canvas;
};

In the working example attached I exemplified how to restrict the WebGL layer to a geographic extent.

The WebGL plotting is done, of course, in [-1,1] WebGL coordinates and to get a reasonable georeferencing, an extra function with the transformation matrices should be implemented (not included but should be something along the lines of what can be found here for google maps).

// WebGL - Fundamentals
//from https://webglfundamentals.org/webgl/webgl-fundamentals.html

function main() {
  webglLayer = mainOL();
}


function mainOL() {
  proj4.defs('EPSG:27700', '+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 ' +
    '+x_0=400000 +y_0=-100000 +ellps=airy ' +
    '+towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 ' +
    '+units=m +no_defs');
  function transform(extent) {
        return ol.proj.transformExtent(extent, 'EPSG:4326', 'EPSG:3857');
      }

   var extents = {
     India: transform([68.17665, 7.96553, 97.40256, 35.49401]),
     Argentina: transform([-73.41544, -55.25, -53.62835, -21.83231]),
     Nigeria: transform([2.6917, 4.24059, 14.57718, 13.86592]),
     Sweden: transform([11.02737, 55.36174, 23.90338, 69.10625])
   };

  var canvasFunction = function(extent, resolution, pixelRatio, size, projection) {
    var canvas = document.createElement('canvas');
    var canvasWidth = size[0], 
      canvasHeight = size[1];
    canvas.setAttribute('width', canvasWidth);
    canvas.setAttribute('height', canvasHeight);
    var gl = canvas.getContext("webgl");
    var glOut = mainWebGL(canvas, gl);
    gl.drawArrays(glOut[1], glOut[2], glOut[3]);
    return canvas;
  };

  var imageExtent = [0, 0, 700000, 1300000];
  var overlay = new ol.layer.Tile({
        extent: extents.India,
        source: new ol.source.TileJSON({
          url: 'https://api.tiles.mapbox.com/v3/mapbox.world-black.json?secure',
          crossOrigin: 'anonymous'
        })
      });
  var canvasLayer = new ol.layer.Image({
    source: new ol.source.ImageCanvas({
      canvasFunction: canvasFunction,
      projection: 'EPSG:3857'
    })
  });
  var myosm = new ol.layer.Tile({
    source: new ol.source.OSM()
  });

  var map = new ol.Map({
    //layers: [myosm
    layers: [myosm],
    target: 'map',
    view: new ol.View({
      center: ol.proj.transform(
        ol.extent.getCenter(imageExtent), 'EPSG:27700', 'EPSG:3857'),
      zoom: 4
    })
  });

    map.addLayer(overlay);
  map.addLayer(canvasLayer);

  canvasLayer.setExtent(extents["India"]);
  overlay.setExtent(extents["India"]);
  return canvasLayer;
}


function createShader(gl, type, source) {
  var shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (success) {
    return shader;
  }
  gl.deleteShader(shader);
}

function createProgram(gl, vertexShader, fragmentShader) {
  var program = gl.createProgram();
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);
  var success = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (success) {
    return program;
  }
  gl.deleteProgram(program);
}

function mainWebGL(canvas, gl) {
  // Get the strings for our GLSL shaders
  var vertexShaderSource = document.getElementById("2d-vertex-shader").text;
  var fragmentShaderSource = document.getElementById("2d-fragment-shader").text;

  // create GLSL shaders, upload the GLSL source, compile the shaders
  var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
  var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);

  // Link the two shaders into a program
  var program = createProgram(gl, vertexShader, fragmentShader);

  // look up where the vertex data needs to go.
  var positionAttributeLocation = gl.getAttribLocation(program, "a_position");

  // Create a buffer and put three 2d clip space points in it
  var positionBuffer = gl.createBuffer();

  // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

  var positions = [
    0, 0,
    0, -0.5,
    0.2, 0,
  ];
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

  // code above this line is initialization code.
  // code below this line is rendering code.
  // Tell WebGL how to convert from clip space to pixels

  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

  // Clear the canvas
  gl.clearColor(0, 0, 0, 0);
  gl.clear(gl.COLOR_BUFFER_BIT);

  // Tell it to use our program (pair of shaders)
  gl.useProgram(program);

  // Turn on the attribute
  gl.enableVertexAttribArray(positionAttributeLocation);

  // Bind the position buffer.
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

  // Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
  var size = 2; // 2 components per iteration
  var type = gl.FLOAT; // the data is 32bit floats
  var normalize = false; // don't normalize the data
  var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
  var offset = 0; // start at the beginning of the buffer
  gl.vertexAttribPointer(
    positionAttributeLocation, size, type, normalize, stride, offset)

  // draw
  var primitiveType = gl.TRIANGLES;
  var offset = 0;
  var count = 3;
  //gl.drawArrays(primitiveType, offset, count);
  return [gl, primitiveType, offset, count];
  //return canvas;
}
main();
<link href="https://openlayers.org/en/v4.6.4/css/ol.css" rel="stylesheet">
<script src="https://openlayers.org/en/v4.6.4/build/ol.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.4.4/proj4.js"></script>
<div id="map" class="map"></div>

<script id="2d-vertex-shader" type="notjs">
  // an attribute will receive data from a buffer
  attribute vec4 a_position;

  // all shaders have a main function
  void main() {

    // gl_Position is a special variable a vertex shader
    // is responsible for setting
    gl_Position = a_position;
  }
</script>
<script id="2d-fragment-shader" type="notjs">
  // fragment shaders don't have a default precision so we need
  // to pick one. mediump is a good default
  precision mediump float;

  void main() {
    // gl_FragColor is a special variable a fragment shader
    // is responsible for setting
    gl_FragColor = vec4(1, 0, 0.5, 1); // return redish-purple
  }
</script>
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/webgl-lessons-helper.js"></script>