The HTML5 Canvas API allows you to draw in only two dimensions. Using Web Graphics Library (WebGL), you can show interactive 2D graphics and 3D graphics within any modern web browser (Internet Explorer 11 has only partial support for WebGL) without the use of plugins. WebGL elements are drawn on a canvas element, and can be combined with other HTML elements. Programs that use WebGL are a mixture of Dart (or JavaScript) code for control, and are of specific WebGL shader code. This shader code is executed on the computer's Graphic Processing Unit (GPU), allowing GPU-accelerated usage of physics effects and image processing as part of the web page canvas, so we have real parallel processing here!
WebGL provides a low-level 3D API; mastering it needs more than a recipe, probably a course or book on its own. However, this recipe will provide you with the basics in the webgl
project and we point to some links to get further information.
Look at the code of the project webgl
. When executed, we see a rectangular red point on a black surface. When we click on the surface, new points are shown:
Start using WebGL by performing the following steps:
webgl.html
page simply defines a <canvas>
tag on which we will draw, using the following code:<canvas id="webgl" style="border: none;" width="500" height="500"></canvas>
dart_webgl
package:import 'dart:web_gl';
shader
code for the drawing:// Vertex shader program var VSHADER_SOURCE = '''attribute vec4 a_Position; void main() { gl_Position = a_Position; gl_PointSize = 10.0; } '''; // Fragment shader program var FSHADER_SOURCE = '''void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // Set the point color } ''';
To set the vertex coordinates of the point to a fixed value, use the following code:
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
main()
function starts by getting a reference to the canvas and the 3D rendering context:void main() { // Retrieve <canvas> element var canvas = querySelector("#webgl"); if (canvas == null) { print('Failed to retrieve the <canvas> element'), } // Get the rendering context for WebGL RenderingContext gl = canvas.getContext3d(); if (gl == null) { print('Failed to get the rendering context for WebGL'), return; }
// compiling the GPU code Shader fragShader = gl.createShader(FRAGMENT_SHADER); gl.shaderSource(fragShader, FSHADER_SOURCE); gl.compileShader(fragShader); Shader vertShader = gl.createShader(VERTEX_SHADER); gl.shaderSource(vertShader, VSHADER_SOURCE); gl.compileShader(vertShader); Program program = gl.createProgram(); gl.attachShader(program, vertShader); gl.attachShader(program, fragShader); gl.linkProgram(program); if (!gl.getProgramParameter(program, LINK_STATUS)) { print("Could not initialise shaders"); return; } gl.useProgram(program);
variable a _Position
:var a_Position = gl.getAttribLocation(program, 'a_Position'), if (a_Position < 0) { print('Failed to get the storage location of a_Position'), return; }
canvas.onMouseDown.listen((ev) => click(ev, gl, canvas, a_Position));
<canvas>
. Clear the canvas and draw the point:gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clear(COLOR_BUFFER_BIT); gl.drawArrays(POINTS, 0, 1); }
g_points
to remember the mouse click positions:List<num> g_points = new List<num>(); void click(ev, RenderingContext gl, canvas, a_Position) { var x = ev.clientX; // x coordinate of a mouse pointer var y = ev.clientY; // y coordinate of a mouse pointer var rect = ev.target.getBoundingClientRect(); x = ((x - rect.left) - canvas.width / 2) / (canvas.width / 2); y = (canvas.height / 2 - (y - rect.top))/ (canvas.height / 2); // Store the coordinates to g_points array g_points.add(x); g_points.add(y); gl.clear(COLOR_BUFFER_BIT); var len = g_points.length; for (var i = 0; i < len; i += 2) { // Pass the position of a point to a_Position variable gl.vertexAttrib3f(a_Position, g_points[i], g_points[i + 1], 0.0); gl.drawArrays(POINTS, 0, 1); } }
The shader code in the fourth step consists of a vertex shader program (to draw the shape boundaries) and fragment shader (for colors, texturing, and lighting) program; they are hard coded in strings assigned to the constants VSHADER_SOURCE
and FSHADER_SOURCE
.
The piece of code in the sixth step looks daunting, but don't worry, this code can simply be reused in other drawings.
Step 7 is necessary to make a connection between the a_position
variable in the shader code, and the variable with the same name in Dart. Notice that the click event handler in steps 8 and 10 needs the GL context and canvas as second and third parameters.
WebGL in itself has no built-in support to load a 3D scene defined in a regular 3D file format. The viewer code or a library such as three.dart
, which is a port of Three.js
(get it from the pub as three is necessary to display a 3D scene). To create content, use a regular content-creation tool and export the content to a viewer-readable format.