Skip to main content

Drawing a Rectangle with WebGL_WebGL Notes 1

Free2015-12-19#WebGL#JS#webgl#WebGL入门#WebGL教程#WebGL指南#webgl实例

This article introduces a simple WebGL program. Compared to canvas 2D, besides the dramatic increase in code volume, there's also much more related knowledge to master.

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:

  1. 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.

  2. 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');
     }
    
  3. 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);
    
  4. 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
     }
    
  5. 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.

  6. 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');
     }
    
  7. 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));
     }
    
  8. 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"

Comments

No comments yet. Be the first to share your thoughts.

Leave a comment