Real-Time 3D Graphics with WebGL 2
上QQ阅读APP看书,第一时间看更新

Time for Action: Rendering a Square

Follow the given steps:

  1. Open the ch02_01_square.html file in a code editor (ideally one that supports syntax highlighting).
  1. Examine the structure of this file with the help of the following diagram:

  1. The web page contains the following:
  • The <script id="vertex-shader" type="x-shader/x-vertex"> script contains the vertex shader code.
  • The <script id="fragment-shader" type="x-shader/x-fragment"> script contains the fragment shader code. We won't pay attention to these two scripts as they will be the main point of study in the next chapter. For now, simply notice that we have a fragment shader and a vertex shader.
  • The next script on our web page, <script type="text/javascript">, contains all the JavaScript WebGL code that we will need. This script is divided into the following functions:
// Global variables that are set and used
// across the application
let gl,
program,
squareVertexBuffer,
squareIndexBuffer,
indices;
  • We list a few global variables that we use throughout our application:
// Given an id, extract the content's of a shader script
// from the DOM and return the compiled shader
function getShader(id) {
const script = document.getElementById(id);
const shaderString = script.text.trim();

// Assign shader depending on the type of shader
let shader;
if (script.type === 'x-shader/x-vertex') {
shader = gl.createShader(gl.VERTEX_SHADER);
}
else if (script.type === 'x-shader/x-fragment') {
shader = gl.createShader(gl.FRAGMENT_SHADER);
}
else {
return null;
}

// Compile the shader using the supplied shader code
gl.shaderSource(shader, shaderString);
gl.compileShader(shader);

// Ensure the shader is valid
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader));
return null;
}

return shader;
}
  • getShader extracts the contents of a shader present in the HTML web page given its id:
// Create a program with the appropriate vertex and fragment shaders
function initProgram() {
const vertexShader = getShader('vertex-shader');
const fragmentShader = getShader('fragment-shader');

// Create a program
program = gl.createProgram();
// Attach the shaders to this program
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);

if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error('Could not initialize shaders');
}

// Use this program instance
gl.useProgram(program);
// We attach the location of these shader values to the program
// instance
for easy access later in the code
program.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition');
}
  • initProgram obtains a reference for the vertex shader and the fragment shader present in the web page (that is, the first two scripts that we discussed) and passes them along to the GPU to be compiled. Lastly, we attach the location of the aVertexPosition attribute to the program object so that it can be easily referenced later. Looking up attribute and uniform locations is expensive; therefore, such operations should happen once during initialization. We will cover these techniques in later chapters:
// Set up the buffers for the square
function initBuffers() {
/*
V0 V3
(-0.5, 0.5, 0) (0.5, 0.5, 0)
X---------------------X
| |
| |
| (0, 0) |
| |
| |
X---------------------X
V1 V2
(-0.5, -0.5, 0) (0.5, -0.5, 0)
*/
const vertices = [
-0.5, 0.5, 0,
-0.5, -0.5, 0,
0.5, -0.5, 0,
0.5, 0.5, 0
];

// Indices defined in counter-clockwise order
indices = [0, 1, 2, 0, 2, 3];

// Setting up the VBO
squareVertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices),
gl.STATIC_DRAW);

// Setting up the IBO
squareIndexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareIndexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices),
gl.STATIC_DRAW);

// Clean
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
  • initBuffers contains the API calls to create and initialize buffers, as we discussed earlier in this chapter. In this example, we create a VBO to store coordinates for the square and an IBO to store the indices of the square:
// We call draw to render to our canvas
function draw() {
// Clear the scene
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

// Use the buffers we've constructed
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexBuffer);
gl.vertexAttribPointer(program.aVertexPosition, 3, gl.FLOAT,
false, 0, 0);
gl.enableVertexAttribArray(program.aVertexPosition);

// Bind IBO
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareIndexBuffer);

// Draw to the scene using triangle primitives
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT,
0);

// Clean
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
  • draw maps the VBO to the respective vertex buffer attribute, program.aVertexPosition, and enables it by calling enableVertexAttribArray. It then binds the IBO and calls the drawElements function. We will cover this in more detail in later chapters:
// Entry point to our application
function init() {
// Retrieve the canvas
const canvas = utils.getCanvas('webgl-canvas');

// Set the canvas to the size of the screen
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

// Retrieve a WebGL context
gl = utils.getGLContext(canvas);
// Set the clear color to be black
gl.clearColor(0, 0, 0, 1);

// Call the functions in an appropriate order
initProgram();
initBuffers();
draw();
}
  • init is the entry point for the entire application. When the page has loaded, init is invoked via window.onload = init. It's important to note that the order of functions invoked inside of init are important to set up and render the geometry. We also set the canvas dimension to take the size of the entire window (fullscreen). As mentioned previously, in the draw function, we are using canvas.width and canvas.height as the source of truth for our drawing dimensions.
  1. Open the ch02_01_square.html file in the HTML5 browser of your preference (Firefox, Safari, Chrome, or Opera), and you should see the following:

  1. Open up the code for ch02_01_square.html and scroll down to the initBuffers function. Please pay attention to the diagram that appears as a comment inside of the function. This diagram describes how the vertices and indices are organized. You should see something like the following:
/*
V0 V3
(-0.5, 0.5, 0) (0.5, 0.5, 0)
X---------------------X
| |
| |
| (0, 0) |
| |
| |
X---------------------X
V1 V2
(-0.5, -0.5, 0) (0.5, -0.5, 0)
*/
  1. Try to modify the existing buffers to turn the square into a pentagon. How would you do this?
Updating the Geometry  Definition

Modify the vertex buffer array and index array so that the resulting figure is a pentagon instead of a square. To do this, you need to add one vertex to the vertex array and define one more triangle in the index array.
  1. Save the file with a different name and open it in the HTML5 browser of your preference to test it.

What just happened?

You have learned about the different code elements that conform to a WebGL app. The initBuffers function has been examined closely and modified to render a different geometry.