Rendering

Let's take a step back and assess the big picture. We previously mentioned that, in our architecture, we have defined three main functions that define the life cycle of our WebGL application. These functions are configureload, and draw.

Thus far, we've set up the scene by writing the code for the configure function. After that, we created our JSON cars and loaded them by writing the code for the load function. Now, we will implement the code for the third function: the draw function.

The code is pretty standard and almost identical to the draw functions that we've written in previous chapters. As the following code demonstrates, we set and clear the area that we are going to draw. Then, we check the camera's perspective and process every object in scene.

One important consideration is that we need to ensure that we are correctly mapping the material properties defined in our JSON objects to the appropriate shader uniforms.

Let's start implementing the draw function:

function draw() {
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
transforms.updatePerspective();

// ...
}

First, we set our viewport and clear the scene, followed by applying the perspective update by using the transforms instance we initialized inside of configure.

Then, we move to the objects in our scene:

try {
scene.traverse(object => {
if (!object.visible) return;

transforms.calculateModelView();
transforms.push();
transforms.setMatrixUniforms();
transforms.pop();

gl.uniform3fv(program.uKa, object.Ka);
gl.uniform3fv(program.uKd, object.Kd);
gl.uniform3fv(program.uKs, object.Ks);
gl.uniform1f(program.uNs, object.Ns);
gl.uniform1f(program.uD, object.d);
gl.uniform1i(program.uIllum, object.illum);

// Bind
gl.bindVertexArray(object.vao);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, object.ibo);

if (object.wireframe) {
gl.uniform1i(program.uWireframe, 1);
gl.drawElements(gl.LINES, object.indices.length, gl.UNSIGNED_SHORT,
0);
}
else {
gl.uniform1i(program.uWireframe, 0);
gl.drawElements(gl.TRIANGLES, object.indices.length,
gl.UNSIGNED_SHORT, 0);
}

// Clean
gl.bindVertexArray(null);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
});
}
catch (error) {
console.error(error);
}

It may be helpful to take a look at the list of uniforms that was defined in the earlier section on shaders. We need to make sure that all of the shader uniforms are paired with object attributes.

The following diagram shows the process that occurs inside the draw function:

Each car part is a different JSON file. The draw function iterates through all of these parts inside the scene. For each part, the material properties are passed as uniforms to the shaders and the geometry is passed as attributes (reading data from the respective VBOs). Finally, the draw call (drawElements) is executed. The result looks something like this:

Here's the final JavaScript source code that can be found in ch09_02_showroom.html:

<html>
<head>
<title>Real-Time 3D Graphics with WebGL2</title>
<link rel="shortcut icon" type="image/png"
href="/common/images/favicon.png" />

<!-- libraries -->
<link rel="stylesheet" href="/common/lib/normalize.css">
<script type="text/javascript" src="/common/lib/dat.gui.js"></script>
<script type="text/javascript" src="/common/lib/gl-matrix.js"></script>

<!-- modules -->
<script type="text/javascript" src="/common/js/utils.js"></script>
<script type="text/javascript" src="/common/js/EventEmitter.js"></script>
<script type="text/javascript" src="/common/js/Camera.js"></script>
<script type="text/javascript" src="/common/js/Clock.js"></script>
<script type="text/javascript" src="/common/js/Controls.js"></script>
<script type="text/javascript" src="/common/js/Floor.js"></script>
<script type="text/javascript" src="/common/js/Light.js"></script>
<script type="text/javascript" src="/common/js/Program.js"></script>
<script type="text/javascript" src="/common/js/Scene.js"></script>
<script type="text/javascript" src="/common/js/Texture.js"></script>
<script type="text/javascript" src="/common/js/Transforms.js"></script>

The following code is for the vertex shader:

  <script id="vertex-shader" type="x-shader/x-vertex">
#version 300 es
precision mediump float;

const int numLights = 4;

uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;
uniform mat4 uNormalMatrix;
uniform vec3 uLightPosition[numLights];

in vec3 aVertexPosition;
in vec3 aVertexNormal;

out vec3 vNormal;
out vec3 vLightRay[numLights];
out vec3 vEye[numLights];

void main(void) {
vec4 vertex = uModelViewMatrix * vec4(aVertexPosition, 1.0);
vec4 lightPosition = vec4(0.0);

for(int i= 0; i < numLights; i++) {
lightPosition = vec4(uLightPosition[i], 1.0);
vLightRay[i] = vertex.xyz - lightPosition.xyz;
vEye[i] = -vec3(vertex.xyz);
}

vNormal = vec3(uNormalMatrix * vec4(aVertexNormal, 1.0));
gl_Position = uProjectionMatrix * uModelViewMatrix *
vec4(aVertexPosition, 1.0);
}
</script>

The following code is for the fragment shader:

  <script id="fragment-shader" type="x-shader/x-fragment">
#version 300 es
precision mediump float;

const int numLights = 4;

uniform vec3 uLd[numLights];
uniform vec3 uLs[numLights];
uniform vec3 uLightPosition[numLights];
uniform vec3 uKa;
uniform vec3 uKd;
uniform vec3 uKs;
uniform float uNs;
uniform float uD;
uniform int uIllum;
uniform bool uWireframe;

in vec3 vNormal;
in vec3 vLightRay[numLights];
in vec3 vEye[numLights];

out vec4 fragColor;

void main(void) {
if (uWireframe || uIllum == 0) {
fragColor = vec4(uKd, uD);
return;
}

vec3 color = vec3(0.0);
vec3 light = vec3(0.0);
vec3 eye = vec3(0.0);
vec3 reflection = vec3(0.0);
vec3 normal = normalize(vNormal);

if (uIllum == 1) {
for (int i = 0; i < numLights; i++) {
light = normalize(vLightRay[i]);
normal = normalize(vNormal);
color += (uLd[i] * uKd * clamp(dot(normal, -light), 0.0, 1.0));
}
}

if (uIllum == 2) {
for (int i = 0; i < numLights; i++) {
eye = normalize(vEye[i]);
light = normalize(vLightRay[i]);
reflection = reflect(light, normal);
color += (uLd[i] * uKd * clamp(dot(normal, -light), 0.0, 1.0));
color += (uLs[i] * uKs * pow(max(dot(reflection, eye), 0.0), uNs)
* 4.0);
}
}

fragColor = vec4(color, uD);
}
</script>

The following is the application code with the appropriate global variable definitions:

  <script type="text/javascript">
'use strict';

let gl, program, scene, clock, camera, transforms, lights,
floor, selectedCar, lightPositions, carModelData,
clearColor = [0.9, 0.9, 0.9, 1];

The following is the configuration step:


function configure() {
const canvas = utils.getCanvas('webgl-canvas');
utils.autoResizeCanvas(canvas);

gl = utils.getGLContext(canvas);
gl.clearColor(...clearColor);
gl.clearDepth(1);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LESS);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

program = new Program(gl, 'vertex-shader', 'fragment-shader');

const attributes = [
'aVertexPosition',
'aVertexNormal',
'aVertexColor'
];

const uniforms = [
'uProjectionMatrix',
'uModelViewMatrix',
'uNormalMatrix',
'uLightPosition',
'uWireframe',
'uLd',
'uLs',
'uKa',
'uKd',
'uKs',
'uNs',
'uD',
'uIllum'
];

program.load(attributes, uniforms);

scene = new Scene(gl, program);
clock = new Clock();

camera = new Camera(Camera.ORBITING_TYPE);
new Controls(camera, canvas);

transforms = new Transforms(gl, program, camera, canvas);

lights = new LightsManager();

lightPositions = {
farLeft: [-1000, 1000, -1000],
farRight: [1000, 1000, -1000],
nearLeft: [-1000, 1000, 1000],
nearRight: [1000, 1000, 1000]
};

Object.keys(lightPositions).forEach(key => {
const light = new Light(key);
light.setPosition(lightPositions[key]);
light.setDiffuse([0.4, 0.4, 0.4]);
light.setSpecular([0.8, 0.8, 0.8]);
lights.add(light)
});

gl.uniform3fv(program.uLightPosition, lights.getArray('position'));
gl.uniform3fv(program.uLd, lights.getArray('diffuse'));
gl.uniform3fv(program.uLs, lights.getArray('specular'));

gl.uniform3fv(program.uKa, [1, 1, 1]);
gl.uniform3fv(program.uKd, [1, 1, 1]);
gl.uniform3fv(program.uKs, [1, 1, 1]);
gl.uniform1f(program.uNs, 1);

floor = new Floor(200, 2);

carModelData = {
'BMW i8': {
paintAlias: 'BMW',
partsCount: 25,
path: '/common/models/bmw-i8/part'
}
};
}

function goHome() {
camera.goHome([0, 0.5, 5]);
camera.setFocus([0, 0, 0]);
camera.setAzimuth(25);
camera.setElevation(-10);
}

The following code is for loading the required assets:

    function loadCar(model) {
scene.objects = [];
scene.add(floor);
const { path, partsCount } = carModelData[model];
scene.loadByParts(path, partsCount);
selectedCar = model;
}

function load() {
goHome();
loadCar('BMW i8');
}

The following code states where we draw our scene:

    function draw() {
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
transforms.updatePerspective();

try {
scene.traverse(object => {
if (!object.visible) return;

transforms.calculateModelView();
transforms.push();
transforms.setMatrixUniforms();
transforms.pop();

gl.uniform3fv(program.uKa, object.Ka);
gl.uniform3fv(program.uKd, object.Kd);
gl.uniform3fv(program.uKs, object.Ks);
gl.uniform1f(program.uNs, object.Ns);
gl.uniform1f(program.uD, object.d);
gl.uniform1i(program.uIllum, object.illum);

// Bind
gl.bindVertexArray(object.vao);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, object.ibo);

if (object.wireframe) {
gl.uniform1i(program.uWireframe, 1);
gl.drawElements(gl.LINES, object.indices.length,
gl.UNSIGNED_SHORT, 0);
}
else {
gl.uniform1i(program.uWireframe, 0);
gl.drawElements(gl.TRIANGLES, object.indices.length,
gl.UNSIGNED_SHORT, 0);
}

// Clean
gl.bindVertexArray(null);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
});
}
catch (error) {
console.error(error);
}
}

The initialization of our application after the document has loaded is performed with the following code:

    function init() {
configure();
load();
clock.on('tick', draw);
}

window.onload = init;

</script>
</head>

<body>

<canvas id="webgl-canvas">
Your browser does not support the HTML5 canvas element.
</canvas>

</body>
</html>

What just happened?

We have covered a demo that uses many of the elements we've discussed throughout this book. We used the infrastructure code that we developed throughout the previous chapters and implemented the three main functions: configureload, and draw. As we've seen, these functions define the life cycle of our application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.115.120