I. Drawing a Rectangle with Canvas 2D
Drawing a rectangle with canvas 2D is easy, accomplished with just a few lines of code:
<!-- html -->
<h5>canvas2d</h5>
<canvas id="canvas2d" width="400" height="400">
Please use a browser that supports "canvas"
</canvas>
// js
// Get element reference
var canvas2d = document.getElementById('canvas2d');
// Get 2D context
var ctx = canvas2d.getContext('2d');
// Set fill color
ctx.fillStyle = 'rgba(255, 0, 255, 0.75)';
// Draw rectangle block
ctx.fillRect(100, 100, 200, 200);
The actual effect is a 200x200px light purple rectangle block, surrounded by a 100px wide transparent ring. From a code perspective, it should be: a 200x200px purple rectangle block with 0.75 opacity is painted in the center of a 400x400px transparent canvas.
From ctx.fillRect(100, 100, 200, 200); we can see that the canvas 2D coordinate system is the same as the screen coordinate system, with the top-left corner at (0, 0), x-axis positive to the right, y-axis positive downward.
From ctx.fillStyle = 'rgba(255, 0, 255, 0.75)'; we can see that rgba() is the same as CSS, rgb values are 0255, a is 01 representing opacity.
II. Drawing a Rectangle with WebGL
1. Getting the Context
Similar to canvas2d.getContext('2d'), WebGL needs to get a dedicated context:
var webgl = document.getElementById('webgl');
var gl = webgl.getContext('webgl');
Here we conventionally name the WebGL context gl, to maintain consistency with OpenGL calling conventions when invoking APIs in the future. Yeah, just for aesthetics.
2. Clearing the Canvas
Similar to the clearRect method provided by canvas 2D, WebGL also has something like an eraser, and it's more powerful, allowing you to specify the color after erasing:
// Specify the color for clearing canvas
// Parameters are rgba, range 0.0~1.0
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// Clear canvas
// gl.COLOR_BUFFER_BIT color buffer, default clear color rgba(0.0, 0.0, 0.0, 0.0) transparent black, specified via gl.clearColor
// gl.DEPTH_BUFFER_BIT depth buffer, default depth 1.0, specified via gl.clearDepth
// gl.STENCIL_BUFFER_BIT stencil buffer, default value 0, specified via gl.clearStencil()
gl.clear(gl.COLOR_BUFFER_BIT);
For this example, clearing the canvas is not required, but you must know how to use this method, otherwise you'll go crazy. Because in canvas 2D, if we want to set a black background, we would do this:
// Black background
ctx.fillStyle = 'rgba(0, 0, 0, 1)';
ctx.fillRect(0, 0, canvas2d.width, canvas2d.height);
With the same approach, drawing a black block to fill the entire canvas, of course this is also possible in WebGL, but it would be quite troublesome.
3. Drawing the Rectangle
To draw a rectangle, we need to do 8 things first:
-
Write shader source programs
Use shader language (GLSL ES) to write shader source programs, then pass them as strings into the WebGL system for compilation and execution:
// 0. Shader source programs // Vertex shader source program var vsSrc = 'void main() {' + 'gl_Position = vec4(0.0, 0.0, 0.0, 1.0);' + // Set coordinates 'gl_PointSize = 200.0;' + // Set size '}'; // Fragment shader source program var fsSrc = 'void main() {' + 'gl_FragColor = vec4(1.0, 0.0, 1.0, 0.75);' + // Set color '}';GLSL ES syntax is quite similar to C language. Specific syntax rules will be discussed in future notes.
-
Create shader objects
Nothing much to say, just game rules.
// 1. Create shader objects var vs = gl.createShader(gl.VERTEX_SHADER); var fs = gl.createShader(gl.FRAGMENT_SHADER); // Check creation result if (vs === null) { log('gl.createShader(gl.VERTEX_SHADER) failed'); } if (fs === null) { log('gl.createShader(gl.FRAGMENT_SHADER) failed'); } -
Fill source programs
With shader objects, we can now feed the shader source programs into them.
// 2. Fill source programs gl.shaderSource(vs, vsSrc); gl.shaderSource(fs, fsSrc); -
Compile
With source code, compile quickly to see if there are any errors.
// 3. Compile gl.compileShader(vs); gl.compileShader(fs); // Check compilation errors if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) { log('gl.compileShader(vs) failed'); log(gl.getShaderInfoLog(vs)); // Output error information } if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) { log('gl.compileShader(fs) failed'); log(gl.getShaderInfoLog(fs)); // Output error information } -
Create program object
Besides shader objects, we also need a program object. JS mainly communicates with WebGL internals through this program object. Let's create one first.
// 4. Create program object var prog = gl.createProgram(); // Check creation result if (prog === null) { log('gl.createProgram() failed'); }Note: The above 4 steps each have one vertex XX and one fragment XX, but this step only needs 1 prog object.
-
Assign shaders to program object
Each prog needs 2 shaders (1 vertex shader, 1 fragment shader), so when checking assignment errors, compare with 2 to see if 2 shaders are bound to the prog.
// 5. Assign shaders to program object gl.attachShader(prog, vs); gl.attachShader(prog, fs); // Check assignment errors if (gl.getProgramParameter(prog, gl.ATTACHED_SHADERS) !== 2) { log('gl.getProgramParameter(prog, gl.ATTACHED_SHADERS) failed'); } -
Link program object
Similar to C language program's compile -> link -> run.
// 6. Link program object gl.linkProgram(prog); // Check link errors if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) { log('gl.linkProgram(prog) failed'); log(gl.getProgramInfoLog(prog)); } -
Use program object
No error checking, because there's no return value.
// 7. Use program object gl.useProgram(prog);
At this point, the preparation work for drawing a rectangle is complete.
Finally, of course, draw something:
// Draw rectangle (one point, but the point size is slightly large)
gl.drawArrays(gl.POINTS, 0, 1);
III. DEMO
For a complete example containing the above code, please view: http://www.ayqy.net/temp/webgl/hoho/index.html
IV. Differences Between WebGL and Canvas 2D
1. Different Coordinate Systems
In WebGL, the center of the canvas is (0, 0), x-axis positive to the right, y-axis positive upward, z-axis positive when shooting out from the screen pointing toward the face (right-hand coordinate system).
2. Different Coordinate Values
In WebGL, the entire canvas is Rect(-1.0, 1.0, 2, 2), meaning the top-left corner of the canvas is at (-1.0, 1.0), the top-right corner is at (1.0, 1.0), the canvas size is 2x2. If the canvas itself is 400x400px, then the unit of coordinate values in WebGL is 200px.
Note: For non-coordinate value properties, such as gl_PointSize in the shader source program above, the unit is still px.
3. Different RGB Values
The fragment shader uses color values, vec4(r, g, b, a), which is different from CSS's rgba(). In WebGL, the rgb value range is 0.0~1.0, and must be in decimal form, because the vec4 constructor only accepts float type parameters, int will not be implicitly converted to float.
4. Different Color Blending Rules
Running the DEMO reveals that the purple rectangle colors drawn by canvas 2D and WebGL are different. In canvas 2D, we first drew a black background, then painted a purple rectangle on top. The purple rectangle has 0.25 transparency, so the final displayed color is the blend of purple and black.
In WebGL, the purple rectangle's color is not blended with black (the body background color is the color we specified for the purple rectangle, you can clearly see that purple-black blending did not occur in WebGL). Try drawing a black rectangle behind the purple rectangle (z-coordinate is -0.1), and you'll find the colors still don't blend. This needs special attention.
V. Summary
From the API usage steps perspective, WebGL is just this troublesome. Drawing a rectangle requires writing so much code. Actually, we only drew one point (a slightly large point). Drawing a rectangle is even more troublesome, requiring 4 vertices to define 2 triangle strips, then coloring them separately.
As for what vertex shaders and fragment shaders are? What functions do they serve? A slightly more complex example is needed to explain. We'll explain in future notes.
References
- "WebGL Programming Guide"
No comments yet. Be the first to share your thoughts.