This JavaScript program demonstrates how create a setup for rendering with a few simple classes in WebGL.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>XoaX.net's WebGL</title>
</head>
<style>
#idCanvas {
border: 1px solid black;
width: 800px;
height: 600px
}
</style>
<script id="idVertexShader" type="c">
attribute vec4 av4Position;
uniform mat4 um4MvpMatrix;
uniform vec3 uv3ObjectColor;
uniform vec3 uv3Normal;
uniform vec3 uv3LightDirection;
varying vec4 vv4Color;
void main() {
vec3 v3NormalLightDir = normalize(uv3LightDirection);
gl_Position = um4MvpMatrix*av4Position;
float fIntensity = max(dot(v3NormalLightDir, uv3Normal), 0.0);
vec3 v3FinalColor = (.5*fIntensity + .5)*uv3ObjectColor;
vv4Color = vec4(v3FinalColor, 0.5);
}
</script>
<script id="idFragmantShader" type="c">
precision mediump float;
//uniform vec4 uv4Color;
varying vec4 vv4Color;
void main() {
//gl_FragColor = vec4(.5, .75, 1, 1);//uv4Color;
gl_FragColor = vv4Color;
}
</script>
<script type="text/javascript" src="BasicSetup.js"></script>
<body onload="render()">
<canvas id="idCanvas"></canvas>
</body>
</html>class CLinAlg {
// The rows are actually columns. So, the top row is the first column.
static CreatePerspectiveMatrix(fFieldOfViewDeg, fAspectRatio, fNearPlane, fFarPlane) {
let fFieldOfViewRad = Math.PI*fFieldOfViewDeg/180;
let fSin = Math.sin(fFieldOfViewRad);
let fCos = Math.cos(fFieldOfViewRad);
let fCot = fCos/fSin;
let fDepth = fFarPlane - fNearPlane;
let faMatrix = new Float32Array([
fCot/fAspectRatio, 0.0, 0.0, 0.0,
0.0, fCot, 0.0, 0.0,
0.0, 0.0, -(fFarPlane + fNearPlane)/fDepth, -1.0,
0.0, 0.0, -(2.0*fFarPlane*fNearPlane)/fDepth, 0.0]);
return faMatrix;
}
static CreateLookAtMatrix(faEye, faObject, faUp) {
// Vector from the eye to the object
let faViewDirection = CLinAlg.DifferenceVector(faObject, faEye);
CLinAlg.NormalizeVector(faViewDirection);
let faLeft = CLinAlg.Cross(faUp, faViewDirection);
CLinAlg.NormalizeVector(faLeft);
let faStraightUp = CLinAlg.Cross(faViewDirection, faLeft);
let faReverseView = CLinAlg.Negate(faViewDirection);
let faMatrix = new Float32Array([
faLeft[0], faStraightUp[0], faReverseView[0], 0.0,
faLeft[1], faStraightUp[1], faReverseView[1], 0.0,
faLeft[2], faStraightUp[2], faReverseView[2], 0.0,
-CLinAlg.Dot(faEye, faLeft), -CLinAlg.Dot(faEye, faStraightUp), -CLinAlg.Dot(faEye, faReverseView), 1.0]);
return faMatrix;
}
static CreateARotationAroundZMatrix(fRotateRadians) {
let fSin = Math.sin(fRotateRadians);
let fCos = Math.cos(fRotateRadians);
let faMatrix = new Float32Array([
fCos, -fSin, 0.0, 0.0,
fSin, fCos, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0]);
return faMatrix;
}
static CreateARotationAroundYMatrix(fRotateRadians) {
let fSin = Math.sin(fRotateRadians);
let fCos = Math.cos(fRotateRadians);
let faMatrix = new Float32Array([
fCos, 0.0, -fSin, 0.0,
0.0, 1.0, 0.0, 0.0,
fSin, 0.0, fCos, 0.0,
0.0, 0.0, 0.0, 1.0]);
return faMatrix;
}
static CreateIdentityMatrix() {
let faMatrix = new Float32Array([
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0]);
return faMatrix;
}
static Negate(faV) {
let faReversed = new Float32Array(3);
faReversed[0] = -faV[0];
faReversed[1] = -faV[1];
faReversed[2] = -faV[2];
return faReversed;
}
static NormalizeVector(faV) {
let dLength = 1.0/Math.sqrt(faV[0]*faV[0] + faV[1]*faV[1] + faV[2]*faV[2]);
faV[0] *= dLength;
faV[1] *= dLength;
faV[2] *= dLength;
}
static Cross(faV1, faV2) {
let faCross = new Float32Array(3);
faCross[0] = faV1[1]*faV2[2] - faV1[2]*faV2[1];
faCross[1] = faV1[2]*faV2[0] - faV1[0]*faV2[2];
faCross[2] = faV1[0]*faV2[1] - faV1[1]*faV2[0];
return faCross;
}
static DifferenceVector(faV1, faV2) {
let faDiff = new Float32Array(3);
faDiff[0] = faV1[0] - faV2[0];
faDiff[1] = faV1[1] - faV2[1];
faDiff[2] = faV1[2] - faV2[2];
return faDiff;
}
static Dot(faV1, faV2) {
return faV1[0]*faV2[0] + faV1[1]*faV2[1] + faV1[2]*faV2[2];
}
static TransposeMatrix(faM) {
let fSwap = faM[1]; faM[1] = faM[4]; faM[4] = fSwap;
fSwap = faM[2]; faM[2] = faM[8]; faM[8] = fSwap;
fSwap = faM[3]; faM[3] = faM[12]; faM[12] = fSwap;
fSwap = faM[6]; faM[6] = faM[9]; faM[9] = fSwap;
fSwap = faM[7]; faM[7] = faM[13]; faM[13] = fSwap;
fSwap = faM[11]; faM[11] = faM[14]; faM[14] = fSwap;
}
static MultiplyMatrices(faaM, faaA) { // M = M*A, Note M != A
var faRow = [0,0,0,0];
for (let iRow = 0; iRow < 4; ++iRow) {
// Copy the current row
for(let iCol = 0; iCol < 4; ++iCol) {
faRow[iCol] = faaM[iRow + 4*iCol];
}
for(let iCol = 0; iCol < 4; ++iCol) {
faaM[iRow + 4*iCol] = 0.0;
for (let k = 0; k < 4; ++k) {
faaM[iRow + 4*iCol] += faRow[k]*faaA[4*iCol + k];
}
}
}
}
}
function CreateProgram(kqGL) {
// Compile the vertex shader
const ksVertexShaderCode = document.getElementById("idVertexShader").innerHTML;
let qVertexShader = kqGL.createShader(kqGL.VERTEX_SHADER);
kqGL.shaderSource(qVertexShader, ksVertexShaderCode);
kqGL.compileShader(qVertexShader);
if (!kqGL.getShaderParameter(qVertexShader, kqGL.COMPILE_STATUS)) {
alert("The vertex shader failed to compile!");
kqGL.deleteShader(qVertexShader);
return null;
}
// Compile the fragment shader
const ksFragmentShaderCode = document.getElementById("idFragmantShader").innerHTML;
let qFragmentShader = kqGL.createShader(kqGL.FRAGMENT_SHADER);
kqGL.shaderSource(qFragmentShader, ksFragmentShaderCode);
kqGL.compileShader(qFragmentShader);
if (!kqGL.getShaderParameter(qFragmentShader, kqGL.COMPILE_STATUS)) {
alert("The fragment shader failed to compile!");
kqGL.deleteShader(qFragmentShader);
return null;
}
// Compile and link the program
let qProgram = kqGL.createProgram();
kqGL.attachShader(qProgram, qVertexShader);
kqGL.attachShader(qProgram, qFragmentShader);
kqGL.linkProgram(qProgram);
if (!kqGL.getProgramParameter(qProgram, kqGL.LINK_STATUS)) {
alert("The program failed to initialize", kqGL.getProgramInfoLog(qProgram));
return null;
}
return qProgram;
}
class CWebGL {
#mqGL;
#mqProgram;
#mdaMatrix;
#mdaRotation;
constructor() {
const qCanvas = document.getElementById("idCanvas");
qCanvas.width = qCanvas.clientWidth;
qCanvas.height = qCanvas.clientHeight;
this.#mqGL = qCanvas.getContext("webgl");
this.#mqProgram = CreateProgram(this.#mqGL);
if (this.#mqProgram == null) {
alert("Closing the program!");
}
this.#mqGL.useProgram(this.#mqProgram);
}
Context() {
return this.#mqGL;
}
Program() {
return this.#mqGL;
}
// All values in [0, 1]
Clear(dR, dG, dB, dA) {
this.#mqGL.clearColor(dR, dG, dB, dA);
this.#mqGL.clear(this.#mqGL.COLOR_BUFFER_BIT);
// Do not use the depth test with alpha blending
// Enable alpha blending
this.#mqGL.enable(this.#mqGL.BLEND);
// Set blending function
this.#mqGL.blendFunc(this.#mqGL.SRC_ALPHA, this.#mqGL.ONE_MINUS_SRC_ALPHA);
}
ClearDepthBuffer() {
this.#mqGL.clear(this.#mqGL.DEPTH_BUFFER_BIT);
}
EnableScissorTest() {
this.#mqGL.enable(this.#mqGL.SCISSOR_TEST);
}
ScissorRectangle(x, y, w, h) {
this.#mqGL.scissor(x, y, w, h);
}
EnableDepthTest() {
this.#mqGL.enable(this.#mqGL.DEPTH_TEST);
}
UseProgram() {
this.#mqGL.useProgram(this.#mqProgram);
}
SetViewMatrix(dRadians) {
const kdFOV = 30;
const kdAspect = this.#mqGL.canvas.clientWidth/this.#mqGL.canvas.clientHeight;
const kdNearPlane = 0.1;
const kdFarPlane = 1000.0;
// Create the matrices and multiply them.
let faPerspective = CLinAlg.CreatePerspectiveMatrix(kdFOV, kdAspect, kdNearPlane, kdFarPlane);
let faLookAt = CLinAlg.CreateLookAtMatrix([1, 1, 1],[0, 0, 0],[0, 1, 0]);
let faRotationMatrix = CLinAlg.CreateARotationAroundYMatrix(dRadians);
let faRotationZMatrix = CLinAlg.CreateARotationAroundZMatrix(dRadians/5)
this.#mdaMatrix = faPerspective;
CLinAlg.MultiplyMatrices(this.#mdaMatrix, faLookAt);
CLinAlg.MultiplyMatrices(this.#mdaMatrix, faRotationMatrix);
CLinAlg.MultiplyMatrices(this.#mdaMatrix, faRotationZMatrix);
let qMvpMatrix = this.#mqGL.getUniformLocation(this.#mqProgram, 'um4MvpMatrix');
this.#mqGL.uniformMatrix4fv(qMvpMatrix, false, this.#mdaMatrix);
this.#mdaRotation = faRotationMatrix;
}
DrawBox(dX, dY, dZ, dL, dW, dH) {
// Create 8 vertices
let fa3V = new Float32Array(3*8);
// [dX, dY, dZ, dX + dL, dY, dZ, dX, dY + dW, dZ, dX + dL, dY + dW, dZ,
// dX, dY, dZ + dH, dX + dL, dY, dZ + dH, dX, dY + dW, dZ + dH, dX + dL, dY + dW, dZ + dH]
for (let i = 0; i < 8; ++i) {
fa3V[3*i] = ((i & 1) ? dX + dL : dX);
fa3V[3*i+1] = ((i & 2) ? dY + dW : dY);
fa3V[3*i+2] = ((i & 4) ? dZ + dH : dZ);
}
const kqGL = this.#mqGL;
// Create the vertex buffer
let qVertexBuffer = kqGL.createBuffer();
kqGL.bindBuffer(kqGL.ARRAY_BUFFER, qVertexBuffer);
kqGL.bufferData(kqGL.ARRAY_BUFFER, fa3V, kqGL.STATIC_DRAW);
let qAttributeLocation = kqGL.getAttribLocation(this.#mqProgram, "av4Position");
// Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
var iCoordinates = 3; // Use three coordinates per point
var iDataType = kqGL.FLOAT; // Coordinates are 32 bit floats
var bNormalize = false; // Turn off normalization
var iStride = 0; // The number of entries to jump over for each iteration
var iOffset = 0; // The first entry to start reading coordinates from
kqGL.vertexAttribPointer(qAttributeLocation, iCoordinates, iDataType, bNormalize, iStride, iOffset);
kqGL.enableVertexAttribArray(qAttributeLocation);
kqGL.bindBuffer(kqGL.ARRAY_BUFFER, null);
// 6 sides with 4 vertex strips
// Sides [0,2,1,3] [4,5,6,7] - [0,1,4,5] [2,6,3,7] - [0,4,2,6] [1,3,5,7]
let ui8aIndices = new Uint8Array([0,2,1,3, 4,5,6,7, 0,1,4,5, 2,6,3,7, 0,4,2,6, 1,3,5,7]);
const qIndiciesBuffer = kqGL.createBuffer();
kqGL.bindBuffer(kqGL.ELEMENT_ARRAY_BUFFER, qIndiciesBuffer);
kqGL.bufferData(kqGL.ELEMENT_ARRAY_BUFFER, ui8aIndices, kqGL.STATIC_DRAW,);
let faaColors = [[1.0, 0.0, 0.0],[0.0, 1.0, 0.0],[0.0, 0.0, 1.0],
[1.0, 1.0, 0.0],[1.0, 0.0, 1.0],[0.0, 1.0, 1.0]];
let faaNormals = [[0.0, 0.0, -1.0],[0.0, 0.0, 1.0],[0.0, -1.0, 0.0],
[0.0, 1.0, 0.0],[-1.0, 0.0, 0.0],[1.0, 0.0, 0.0]];
// Perform the actual draw
const kiVertexCount = 24;
let qObjectColor = kqGL.getUniformLocation(this.#mqProgram, 'uv3ObjectColor');
let qLightDirection = kqGL.getUniformLocation(this.#mqProgram, 'uv3LightDirection');
// Light direction (-1,-1,-1) and rotate it
kqGL.uniform3f(qLightDirection,
(this.#mdaRotation[0] + this.#mdaRotation[1] + this.#mdaRotation[2]),
(this.#mdaRotation[4] + this.#mdaRotation[5] + this.#mdaRotation[6]),
(this.#mdaRotation[8] + this.#mdaRotation[9] + this.#mdaRotation[10]));
let qNormal = kqGL.getUniformLocation(this.#mqProgram, 'uv3Normal');
// Draw the sides
for (let i = 0; i < 6; ++i) {
kqGL.uniform3f(qObjectColor, faaColors[i][0], faaColors[i][1], faaColors[i][2]);
kqGL.uniform3f(qNormal, faaNormals[i][0], faaNormals[i][1], faaNormals[i][2]);
kqGL.drawElements(kqGL.TRIANGLE_STRIP, 4, kqGL.UNSIGNED_BYTE, 4*i);
}
// Draw the sides
for (let i = 0; i < 6; ++i) {
kqGL.uniform3f(qObjectColor, 1, 1, 1);
kqGL.drawElements(kqGL.LINE_LOOP, 4, kqGL.UNSIGNED_BYTE, 4*i);
}
}
}
function render() {
let qWebGL = new CWebGL();
let dPreviousMS = document.timeline.currentTime;
let dTotal = 0;
// dTimeStamp is equal to document.timeline.currentTime and it is in milliseconds
function RenderNextFrame(dTimeStamp) {
let dElapsedMS = dTimeStamp - dPreviousMS;
dTotal += dElapsedMS;
dPreviousMS = dTimeStamp;
qWebGL.SetViewMatrix(dTotal/1000);
qWebGL.Clear(.25, .25, 0.25, 1.0);
// THe depth buffer can cause transparency to fail
//qWebGL.ClearDepthBuffer();
//qWebGL.EnableDepthTest();
qWebGL.DrawBox(-.5, -.75, -.25, 1, 1.5, .5);
requestAnimationFrame(RenderNextFrame);
}
requestAnimationFrame(RenderNextFrame);
}
© 20072026 XoaX.net LLC. All rights reserved.