Related
As shown below, all I can think of is to describe all the points on a circle and then use triangulation to draw a ring with width
I can also think of a way to use overlay. First draw a circle and then draw a circle with a smaller radius
const TAU_SEGMENTS = 360;
const TAU = Math.PI * 2;
export function arc(x0: number, y0: number, radius: number, startAng = 0, endAng = Math.PI * 2) {
const ang = Math.min(TAU, endAng - startAng);
const ret = ang === TAU ? [] : [x0, y0];
const segments = Math.round(TAU_SEGMENTS * ang / TAU);
for(let i = 0; i <= segments; i++) {
const x = x0 + radius * Math.cos(startAng + ang * i / segments);
const y = y0 + radius * Math.sin(startAng + ang * i / segments);
ret.push(x, y);
}
return ret;
}
const gl = container.current.getContext("webgl2");
const program = initProgram(gl);
const position = new Float32Array(arc(0, 0, 1).concat(...arc(0, 0, 0.9)));
let buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, position, gl.STATIC_DRAW);
let gl_position = gl.getAttribLocation(program, "position");
gl.vertexAttribPointer(gl_position, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(gl_position);
gl.drawArrays(gl.LINE_STRIP, 0, position.length / 2);
The final effect of the code I wrote is as follows. May I ask how I should modify it to become the same as the above picture
You have to add a color attributes for the vertices and you have to draw a gl.TRIANGLE_STRIP primitive instead of a gl.LINE_STRIP primitive.
The color can be calculated from the angle. Map the angle from the range [0, PI] to the range [0, 1] and use the formula for the hue value from the HSL and HSV color space:
function HUEtoRGB(hue) {
return [
Math.min(1, Math.max(0, Math.abs(hue * 6.0 - 3.0) - 1.0)),
Math.min(1, Math.max(0, 2.0 - Math.abs(hue * 6.0 - 2.0))),
Math.min(1, Math.max(0, 2.0 - Math.abs(hue * 6.0 - 4.0)))
];
}
Create vertices in pairs for the inner and outer arcs with the corresponding color attribute:
const TAU_SEGMENTS = 360;
const TAU = Math.PI * 2;
function arc(x0, y0, innerRadius, outerRadius, startAng = 0, endAng = Math.PI * 2) {
const ang = Math.min(TAU, endAng - startAng);
const position = ang === TAU ? [] : [x0, y0];
const color = []
const segments = Math.round(TAU_SEGMENTS * ang / TAU);
for(let i = 0; i <= segments; i++) {
const angle = startAng + ang * i / segments;
const x1 = x0 + innerRadius * Math.cos(angle);
const y1 = y0 + innerRadius * Math.sin(angle);
const x2 = x0 + outerRadius * Math.cos(angle);
const y2 = y0 + outerRadius * Math.sin(angle);
position.push(x1, y1, x2, y2);
let hue = (Math.PI/2 - angle) / (2 * Math.PI);
if (hue < 0) hue += 1;
const rgb = HUEtoRGB(hue);
color.push(...rgb);
color.push(...rgb);
}
return { 'position': position, 'color': color };
}
Create a shader with a color attribute and pass the color attribute from the vertex to the fragment shader:
#version 300 es
precision highp float;
in vec2 position;
in vec4 color;
out vec4 vColor;
void main()
{
vColor = color;
gl_Position = vec4(position.xy, 0.0, 1.0);
}
#version 300 es
precision highp float;
in vec4 vColor;
out vec4 fragColor;
void main()
{
fragColor = vColor;
}
Vertex specification:
const attributes = arc(0, 0, 0.6, 0.9);
position = new Float32Array(attributes.position);
color = new Float32Array(attributes.color);
let position_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, position_buffer);
gl.bufferData(gl.ARRAY_BUFFER, position, gl.STATIC_DRAW);
let gl_position = gl.getAttribLocation(program, "position");
gl.vertexAttribPointer(gl_position, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(gl_position);
let color_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
gl.bufferData(gl.ARRAY_BUFFER, color, gl.STATIC_DRAW);
let gl_color = gl.getAttribLocation(program, "color");
gl.vertexAttribPointer(gl_color, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(gl_color);
Complet and runnable example:
const canvas = document.getElementById( "ogl-canvas");
const gl = canvas.getContext("webgl2");
const program = gl.createProgram();
for (let i = 0; i < 2; ++i) {
let source = document.getElementById(i==0 ? "draw-shader-vs" : "draw-shader-fs").text;
let shaderObj = gl.createShader(i==0 ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER);
gl.shaderSource(shaderObj, source);
gl.compileShader(shaderObj);
let status = gl.getShaderParameter(shaderObj, gl.COMPILE_STATUS);
if (!status) alert(gl.getShaderInfoLog(shaderObj));
gl.attachShader(program, shaderObj);
gl.linkProgram(program);
}
status = gl.getProgramParameter(program, gl.LINK_STATUS);
if ( !status ) alert(gl.getProgramInfoLog(program));
gl.useProgram(program);
function HUEtoRGB(hue) {
return [
Math.min(1, Math.max(0, Math.abs(hue * 6.0 - 3.0) - 1.0)),
Math.min(1, Math.max(0, 2.0 - Math.abs(hue * 6.0 - 2.0))),
Math.min(1, Math.max(0, 2.0 - Math.abs(hue * 6.0 - 4.0)))
];
}
const TAU_SEGMENTS = 360;
const TAU = Math.PI * 2;
function arc(x0, y0, innerRadius, outerRadius, startAng = 0, endAng = Math.PI * 2) {
const ang = Math.min(TAU, endAng - startAng);
const position = ang === TAU ? [] : [x0, y0];
const color = []
const segments = Math.round(TAU_SEGMENTS * ang / TAU);
for(let i = 0; i <= segments; i++) {
const angle = startAng + ang * i / segments;
const x1 = x0 + innerRadius * Math.cos(angle);
const y1 = y0 + innerRadius * Math.sin(angle);
const x2 = x0 + outerRadius * Math.cos(angle);
const y2 = y0 + outerRadius * Math.sin(angle);
position.push(x1, y1, x2, y2);
let hue = (Math.PI/2 - angle) / (2 * Math.PI);
if (hue < 0) hue += 1;
const rgb = HUEtoRGB(hue);
color.push(...rgb);
color.push(...rgb);
}
return { 'position': position, 'color': color };
}
const attributes = arc(0, 0, 0.6, 0.9);
position = new Float32Array(attributes.position);
color = new Float32Array(attributes.color);
let position_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, position_buffer);
gl.bufferData(gl.ARRAY_BUFFER, position, gl.STATIC_DRAW);
let gl_position = gl.getAttribLocation(program, "position");
gl.vertexAttribPointer(gl_position, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(gl_position);
let color_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
gl.bufferData(gl.ARRAY_BUFFER, color, gl.STATIC_DRAW);
let gl_color = gl.getAttribLocation(program, "color");
gl.vertexAttribPointer(gl_color, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(gl_color);
gl.enable( gl.DEPTH_TEST );
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
//vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight];
vp_size = [window.innerWidth, window.innerHeight];
vp_size = [256, 256]
canvas.width = vp_size[0];
canvas.height = vp_size[1];
gl.viewport( 0, 0, canvas.width, canvas.height );
gl.clearColor(1, 1, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, attributes.position.length / 2);
<script id="draw-shader-vs" type="x-shader/x-vertex">#version 300 es
precision highp float;
in vec2 position;
in vec4 color;
out vec4 vColor;
void main()
{
vColor = color;
gl_Position = vec4(position.xy, 0.0, 1.0);
}
</script>
<script id="draw-shader-fs" type="x-shader/x-fragment">#version 300 es
precision highp float;
in vec4 vColor;
out vec4 fragColor;
void main()
{
fragColor = vColor;
}
</script>
<canvas id="ogl-canvas" style="border: none"></canvas>
I have been trying to create a full circle using the triangle fan approach. However, I've tried increasing the number of fans from 80 to 360. Then I tried increasing it to 500, 5000, 50000. It disappears at 50000 only because the slice is so small... I am wondering how I can fill in that missing slice.
Here is the code I am working with:
// RotatingTriangle.js (c) 2012 matsuda
// Vertex shader program
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'uniform mat4 u_ModelMatrix;\n' +
'void main() {\n' +
' gl_Position = a_Position;\n' +
'}\n';
// Fragment shader program
var FSHADER_SOURCE =
'void main() {\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' +
'}\n';
function main() {
// Retrieve <canvas> element
var canvas = document.getElementById('webgl');
// Get the rendering context for WebGL
var gl = getWebGLContext(canvas);
if (!gl) {
console.log('Failed to get the rendering context for WebGL');
return;
}
// Initialize shaders
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('Failed to intialize shaders.');
return;
}
// Write the positions of vertices to a vertex shader
var n = initVertexBuffers(gl);
if (n < 0) {
console.log('Failed to set the positions of the vertices');
return;
}
// Specify the color for clearing <canvas>
gl.clearColor(0, 0, 0, 1);
// Clear <canvas>
gl.clear(gl.COLOR_BUFFER_BIT);
// Draw the rectangle
gl.drawArrays(gl.TRIANGLE_FAN, 0, n);
}
function initVertexBuffers(gl) {
var circle = {x: 0, y:0, r: 0.75};
var ATTRIBUTES = 2;
var numFans = 64;
var degreePerFan = (2* Math.PI) / numFans;
var vertexData = [
0.0, 0.0
];
// updated here, but the problem still persists
for(var i = 0; i <= numFans; i++) {
var index = 2 + i*2;
var angle = degreePerFan * (i+1);
//console.log(angle)
vertexData[index] = Math.cos(angle) * 0.5;
vertexData[index + 1] = Math.sin(angle) * 0.5;
}
//console.log(vertexData);
var vertexDataTyped = new Float32Array(vertexData);
// Create a buffer object
var vertexBuffer = gl.createBuffer();
if (!vertexBuffer) {
console.log('Failed to create the buffer object');
return -1;
}
// Bind the buffer object to target
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
// Write date into the buffer object
gl.bufferData(gl.ARRAY_BUFFER, vertexDataTyped, gl.STATIC_DRAW);
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
if (a_Position < 0) {
console.log('Failed to get the storage location of a_Position');
return -1;
}
// Assign the buffer object to a_Position variable
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
// Enable the assignment to a_Position variable
gl.enableVertexAttribArray(a_Position);
return numFans;
}
Picture of missing slice
// SOLUTION
for(var i = 0; i <= numFans; i++) {
var index = i*2; <--- (used to be 'var index = 2 + i*2;')
var angle = degreePerFan * (i+1);
//console.log(angle)
vertexData[index] = Math.cos(angle) * 0.5;
vertexData[index + 1] = Math.sin(angle) * 0.5;
}
I cannot reproduce the issue, however the generation of the vertices is not correct. The index of the 2nd vertex coordinate is 0.0, 0.0:
var index = 2*3 + i*2;
var index = 2 + i*2;
Vertex generation:
var vertexData = [
0.0, 0.0,
];
for(var i = 0; i <= numFans; i++) {
var index = 2 + i*2;
var angle = degreePerFan * i;
vertexData[index] = Math.cos(angle) * 0.5;
vertexData[index + 1] = Math.sin(angle) * 0.5;
}
I have been trying to create shading in webgl just like in this image: where we can see a cone, a sphere, and a light (which we can change his position with the sliders).
I've tried to write some code in the html file by seeing multiple examples of shading from some webgl tutoring sites, but right now, I can't even see the shapes. It's sure that I'm doing something wrong, but I just don't know where. Here's my code and I also included a link because it contains multiple files. Thanks in advance.
Link: https://wetransfer.com/downloads/cd0f66f2e2866c0d118e95b02e01cb0520200923203442/274553
<html>
<head>
<title>Light and Shading</title>
<meta http-equiv='content-type' content='text/html; charset=ISO-8859-1'>
<!-- CSS Styles //-->
<link href='css/style.css' type='text/css' rel='stylesheet'>
<link href='css/desert.css' type='text/css' rel='stylesheet'/>
<link href='css/colorpicker.css' type='text/css' rel='stylesheet'/>
<link href='css/smoothness/jquery-ui-1.8.13.custom.css' type='text/css' rel='stylesheet' />
<!-- JavaScript Libraries //-->
<script type='text/javascript' src='js/gl-matrix-min.js'></script>
<script type='text/javascript' src='js/jquery-1.5.1.min.js'></script>
<script type='text/javascript' src='js/jquery-ui-1.8.13.custom.min.js'></script>
<script type='text/javascript' src='js/prettify.js'></script>
<script type='text/javascript' src='js/utils.js'></script>
<script type='text/javascript' src='js/colorpicker.js'></script>
<script type='text/javascript' src='js/codeview.js'></script>
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
attribute vec3 aVertexNormal;
// matrice model-view combinee.
uniform mat4 uMVMatrix;
// matrice de projection
uniform mat4 uPMatrix;
// matrice des normales.
uniform mat3 uNMatrix;
// position de la lumiere.
uniform vec3 uLightPosition;
// La normale transformee
varying vec3 vNormal;
// la direction vertex-lumiere
varying vec3 vLightRay;
// la direction camera-vertex
varying vec3 vEyeVec;
uniform vec4 uLightAmbient;
uniform vec4 uLightDiffuse;
uniform vec4 uLightSpecular;
uniform vec4 uMaterialAmbient;
uniform vec4 uMaterialDiffuse;
uniform vec4 uMaterialSpecular;
void main(void) {
vec4 ambientProduct= uLightAmbient* uMaterialAmbient;
vec4 diffuseProduct= uLightDiffuse*uMaterialDiffuse;
vec4 specularProduct= uLightSpecular*uMaterialSpecular;
vec3 pos = (uMVMatrix*vec4(aVertexPosition, 1.0)).xyz;
// position de l'oeil/camera.
const vec3 eyePosition = vec3(0,0,-40);
//Transformed normal position
vNormal = normalize((uNMatrix* aVertexNormal).xyz) ;
//Transformed light position
vec4 light = uMVMatrix * vec4(uLightPosition,1.0);
vec3 lightPos = (uMVMatrix * light).xyz;
//Light position
vLightRay = normalize(pos - lightPos);
//Vector Eye
vEyeVec = -normalize(pos);
//Final vertex position
gl_Position = uMVMatrix*uPMatrix* vec4(aVertexPosition, 1.0);
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
#ifdef GL_ES
precision highp float;
#endif
varying vec3 vNormal;
varying vec3 vLightRay;
varying vec3 vEyeVec;
uniform vec4 ambientProduct;
uniform vec4 diffuseProduct;
uniform vec4 specularProduct;
uniform float uShininess;
void main(void)
{
vec4 diffuse = max(dot( vNormal,vLightRay), 0.0) * diffuseProduct;
vec3 H = normalize(vLightRay+vEyeVec);
vec4 specular =
pow(max(dot(vNormal, H), 0.0), uShininess) * specularProduct;
if (dot(vLightRay, vNormal) < 0.0)
specular = vec4(0.0, 0.0, 0.0, 1.0);
vec4 fColor = ambientProduct + diffuse + specular;
fColor.a = 1.0;
gl_FragColor =fColor;
}
</script>
<script id='code-js' type="text/javascript">
var gl = null; // WebGL context
var prg = null; // The program (shaders)
var c_width = 0; // Variable to store the width of the canvas
var c_height = 0; // Variable to store the height of the canvas
var mvMatrix = mat4.create(); // The Model-View matrix
var pMatrix = mat4.create(); // The projection matrix
var nMatrix = mat4.create(); // The normal matrix
var distance = -40;
var animateFlag = false;
var objects = [];
/**
* The program contains a series of instructions that tell the Graphic Processing Unit (GPU)
* what to do with every vertex and fragment that we pass it.
* The vertex shader and the fragment shader together are called the program.
*/
function initProgram() {
var fragmentShader = utils.getShader(gl, "shader-fs");
var vertexShader = utils.getShader(gl, "shader-vs");
prg = gl.createProgram();
gl.attachShader(prg, vertexShader);
gl.attachShader(prg, fragmentShader);
gl.linkProgram(prg);
if (!gl.getProgramParameter(prg, gl.LINK_STATUS)) {
alert("Could not initialise shaders");
}
gl.useProgram(prg);
prg.aVertexPosition = gl.getAttribLocation(prg, "aVertexPosition");
prg.aVertexNormal = gl.getAttribLocation(prg, "aVertexNormal");
prg.uPMatrix = gl.getUniformLocation(prg, "uPMatrix");
prg.uMVMatrix = gl.getUniformLocation(prg, "uMVMatrix");
prg.uNMatrix = gl.getUniformLocation(prg, "uNMatrix");
prg.uMaterialAmbient = gl.getUniformLocation(prg, "uMaterialAmbient");
prg.uMaterialDiffuse = gl.getUniformLocation(prg, "uMaterialDiffuse");
prg.uMaterialSpecular = gl.getUniformLocation(prg, "uMaterialSpecular");
prg.uShininess = gl.getUniformLocation(prg, "uShininess");
prg.uLightPosition = gl.getUniformLocation(prg, "uLightPosition");
prg.uLightAmbient = gl.getUniformLocation(prg, "uLightAmbient");
prg.uLightDiffuse = gl.getUniformLocation(prg, "uLightDiffuse");
prg.uLightSpecular = gl.getUniformLocation(prg, "uLightSpecular");
}
function initLights(){
//Light uniforms
gl.uniform3fv(prg.uLightPosition,[4.5,3.0,15.0]);
gl.uniform4f(prg.uLightAmbient ,1.0,1.0,1.0,1.0);
gl.uniform4f(prg.uLightDiffuse,1.0,1.0,1.0,1.0);
gl.uniform4f(prg.uLightSpecular,1.0,1.0,1.0,1.0);
//Object Uniforms
gl.uniform4f(prg.uMaterialAmbient, 0.1,0.1,0.1,1.0);
gl.uniform4f(prg.uMaterialDiffuse, 0.5,0.8,0.1,1.0);
gl.uniform4f(prg.uMaterialSpecular, 0.6,0.6,0.6,1.0);
gl.uniform1f(prg.uShininess, 200.0);
}
/**
* Creates an AJAX request to load the scene asynchronously
*/
function loadScene(){
loadObject('models/plane.json');
loadObject('models/cone.json','cone');
loadObject('models/sphere.json','sphere');
loadObject('models/smallsph.json','lightsource');
}
function getObject(alias){
for(var i=0; i<objects.length; i++){
if (alias == objects[i].alias) return objects[i];
}
return null;
}
/**
* Ajax and JSON in action
*/
function loadObject(filename,alias){
var request = new XMLHttpRequest();
console.info('Requesting ' + filename);
request.open("GET",filename);
request.onreadystatechange = function() {
if (request.readyState == 4) {
if(request.status == 404) {
console.info(filename + ' does not exist');
}
else {
var o = JSON.parse(request.responseText);
o.alias = (alias==null)?'none':alias;
handleLoadedObject(filename,o);
}
}
}
request.send();
}
/**
* Creates the buffers that contain the geometry of the object
*/
function handleLoadedObject(filename,object) {
console.info(filename + ' has been retrieved from the server');
var vertexBufferObject = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBufferObject);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(object.vertices), gl.STATIC_DRAW);
var normalBufferObject = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normalBufferObject);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(calcNormals(object.vertices, object.indices)), gl.STATIC_DRAW);
var indexBufferObject = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBufferObject);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(object.indices), gl.STATIC_DRAW);
object.vbo = vertexBufferObject;
object.ibo = indexBufferObject;
object.nbo = normalBufferObject;
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
gl.bindBuffer(gl.ARRAY_BUFFER,null);
objects.push(object);
}
/**
* Main rendering function. Called every 500ms according to WebGLStart function (see below)
*/
function drawScene() {
gl.clearColor(0.3,0.3,0.3, 1.0);
gl.clearDepth(100.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.viewport(0, 0, c_width, c_height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
mat4.perspective(60, c_width / c_height, 0.1, 1000.0, pMatrix);
try{
gl.enableVertexAttribArray(prg.aVertexPosition);
gl.enableVertexAttribArray(prg.aVertexNormal);
for (var i = 0; i < objects.length; i++){
var object = objects[i];
mat4.identity(mvMatrix);
mat4.translate(mvMatrix, [0.0, 0.0, distance]); //Sets the camera to a reasonable distance to view the part
mat4.rotate(mvMatrix, 30*Math.PI/180, [1,0,0]);
mat4.rotate(mvMatrix, angle*Math.PI/180, [0,1,0]);
if (object.alias == 'lightsource'){
var lightPos = gl.getUniform(prg, prg.uLightPosition);
mat4.translate(mvMatrix,lightPos);
}
gl.uniformMatrix4fv(prg.uMVMatrix, false, mvMatrix);
gl.uniformMatrix4fv(prg.uPMatrix, false, pMatrix);
mat4.set(mvMatrix, nMatrix);
mat4.inverse(nMatrix);
mat4.transpose(nMatrix);
gl.uniformMatrix4fv(prg.uNMatrix, false, nMatrix);
gl.uniform4fv(prg.uMaterialAmbient, object.ambient);
gl.uniform4fv(prg.uMaterialDiffuse, object.diffuse);
gl.uniform4fv(prg.uMaterialSpecular, object.specular);
gl.bindBuffer(gl.ARRAY_BUFFER, object.vbo);
gl.vertexAttribPointer(prg.aVertexPosition, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(prg.aVertexPosition);
gl.bindBuffer(gl.ARRAY_BUFFER, object.nbo);
gl.vertexAttribPointer(prg.aVertexNormal, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(prg.aVertexNormal);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, object.ibo);
gl.drawElements(gl.TRIANGLES, object.indices.length, gl.UNSIGNED_SHORT,0);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
}
catch(err){
alert(err);
message(err.description);
}
}
var lastTime = 0;
var angle = 0;
/**
* Updates the angle of rotation by a little bit each time
*/
function animate() {
var timeNow = new Date().getTime();
if (lastTime != 0) {
var elapsed = timeNow - lastTime;
if (animateFlag) angle += (90 * elapsed) / 10000.0;
}
lastTime = timeNow;
}
/**
* Render Loop
*/
function renderLoop() {
requestAnimFrame(renderLoop);
drawScene();
animate();
}
/**
* Entry point. This function is invoked when the page is loaded
*/
function runWebGLApp() {
//Obtains a WebGL context
gl = utils.getGLContext("canvas-element-id");
//Initializes the program (shaders)
initProgram();
//Initializes lights
initLights();
//Load Scene
loadScene();
//Renders the scene!
renderLoop();
}
</script>
</head>
<body onLoad='runWebGLApp()'>
<div id='top'>
<div id='contents'>
<div id='canvasContainer'>
<canvas id='canvas-element-id' width='480' height='400'>
Your browser does not support the HTML5 canvas element.
</canvas>
</div>
</div>
<div id='bottom'>
<table style='padding=0px'>
<tr>
<td>X:</td><td id='slider-x-value' width='30px'>4.5</td><td width='150px'><div id='slider-x'/></td>
</tr>
<tr>
<td>Y:</td><td id='slider-y-value' width='30px'>3.0</td><td width='150px'><div id='slider-y'/></td>
</tr>
<tr>
<td>Z:</td> <td id='slider-z-value' width='30px'>15.0</td><td width='150px'><div id='slider-z'/></td>
</tr>
</table>
</div>
<script>cview.run(cview.MODE_VIEW);</script>
<script>
$('#slider-shininess').slider({value:200, min:1, max:300, step:1, slide:updateShininess});
$('#slider-x').slider({value:4.5, min:-50, max:50, step:0.1, slide:updateLightPosition, change:updateLightPosition});
$('#slider-y').slider({value:3.0, min:0, max:50, step:0.1, slide:updateLightPosition, change:updateLightPosition});
$('#slider-z').slider({value:15.0, min:-50, max:50, step:0.1, slide:updateLightPosition, change:updateLightPosition});
$('#animate-btn').button();
$('#animate-btn').click(
function(){
if ($('#animate-btn:checked').val()==null){
animateFlag = false;
}
else{
animateFlag = true;
}
});
function updateShininess(){
var v = $('#slider-shininess').slider("value");
gl.uniform1f(prg.uShininess, v);
$('#slider-shininess-value').html(v);
}
function updateLightPosition(){
var x = $('#slider-x').slider("value");
var y = $('#slider-y').slider("value");
var z = $('#slider-z').slider("value");
gl.uniform3fv(prg.uLightPosition, [x,y,z]);
$('#slider-x-value').html(x);
$('#slider-y-value').html(y);
$('#slider-z-value').html(z);
}
function updateDistance(){
var d = $('#slider-distance').slider("value");
$('#slider-distance-value').html(distance);
distance = -d;
}
function updateObjectColor(alias, r,g,b){
var object = getObject(alias);
if (object != null){
object.diffuse = [r,g,b,1.0];
}
}
$('#colorSelectorSphere').ColorPicker({
onSubmit: function(hsb, hex, rgb, el) {
$(el).val(hex);
$(el).ColorPickerHide();
},
color: '#00ff00',
onShow: function (colpkr) {
$(colpkr).fadeIn(500);
return false;
},
onHide: function (colpkr) {
$(colpkr).fadeOut(500);
return false;
},
onChange: function (hsb, hex, rgb) {
$('#colorSelectorSphere div').css('backgroundColor', '#' + hex);
updateObjectColor('sphere',rgb.r/256,rgb.g/256,rgb.b/256);
},
onBeforeShow: function (colpkr) {
$(this).ColorPickerSetColor('rgb(0.5,0.8,0.1)');
}
})
$('#colorSelectorCone').ColorPicker({
onSubmit: function(hsb, hex, rgb, el) {
$(el).val(hex);
$(el).ColorPickerHide();
},
color: '#00ff00',
onShow: function (colpkr) {
$(colpkr).fadeIn(500);
return false;
},
onHide: function (colpkr) {
$(colpkr).fadeOut(500);
return false;
},
onChange: function (hsb, hex, rgb) {
$('#colorSelectorCone div').css('backgroundColor', '#' + hex);
updateObjectColor('cone',rgb.r/256,rgb.g/256,rgb.b/256);
},
onBeforeShow: function (colpkr) {
$(this).ColorPickerSetColor('rgb(0.8,0.1,0.5)');
}
})
// Calcule les normales des vertex. La normale de chaque vertex est
// la moyenne des triangles voisins.
//
// vertices: la liste des vertex.
// ind: la liste des indices.
// retour: la liste des normales par vertex.
function calcNormals(vertices, ind){
var x=0;
var y=1;
var z=2;
var v1 = [], v2 = [], thisNormal = [];
// initialiser la liste des normales.
var ns = [];
for(var i=0;i<vertices.length;i++)
{
ns[i]=0.0;
}
for(var i=0;i<ind.length;i=i+3){
//v1 = p1 - p0
v1[x] = vertices[3*ind[i+1]+x] - vertices[3*ind[i]+x];
v1[y] = vertices[3*ind[i+1]+y] - vertices[3*ind[i]+y];
v1[z] = vertices[3*ind[i+1]+z] - vertices[3*ind[i]+z];
// v2 = p2 - p1
v2[x] = vertices[3*ind[i+2]+x] - vertices[3*ind[i]+x];
v2[y] = vertices[3*ind[i+2]+y] - vertices[3*ind[i]+y];
v2[z] = vertices[3*ind[i+2]+z] - vertices[3*ind[i]+z];
// N = v2 x v1 (cross product).
thisNormal[x] = v1[y]*v2[z] - v1[z]*v2[y];
thisNormal[y] = v1[z]*v2[x] - v1[x]*v2[z];
thisNormal[z] = v1[x]*v2[y] - v1[y]*v2[x];
for(j=0;j<3;j++)
{
// N += thisNormal. on additionne les normales.
ns[3*ind[i+j]+x] = ns[3*ind[i+j]+x] + thisNormal[x];
ns[3*ind[i+j]+y] = ns[3*ind[i+j]+y] + thisNormal[y];
ns[3*ind[i+j]+z] = ns[3*ind[i+j]+z] + thisNormal[z];
}
}
// Normalisation.
for(var i=0;i<vertices.length;i=i+3){
var nn=[];
var len = 0;
for(var j = 0; j < 3; j++)
{
nn[j] = ns[i+j];
len += nn[j] * nn[j];
}
// La norme de la normale.
len = Math.sqrt(len);
if (len == 0)
len = 0.00001;
for(var j = 0; j < 3; j++)
ns[i+j] = nn[j] / len;
console.log(len);
}
return ns;
}
</script>
</body>
</html>
Honestly, there are too many issues to cover. Trying to get your code work. First you really need to learn how to make a minimal repo. The code you posted doesn't run, references several scripts that don't exist, and data that doesn't exist.
The tutorial the code is based off appears to be old. No one uses XMLHttpRequest in 2020. There's no such function as requestAnimFrame it's requestAnimationFrame. I think that's left over from a polyfill from like 2011. It's still using <body onload=""> which few use anymore. It's also using new Date().getTime() which there's no reason to use as the time is passed into requestAnimationFrame. It's calling some function to get a webgl context but there's no reason for that either in 2020. It's also apparently using an old version of glMatrix because the current version uses a different API. In the current version every function takes a matrix to store the result as the first argument. Also in the current version perspective takes the field of view in radians. I have no idea if it used to take it in degrees but the code was passing degrees.
I changed the XHR code to just return a cube. (an example of the minimal and complete parts of an "minimal complete verifiable example" (mcve) - I think now S.O calls them a "minimal reproducible example". I also removed all the references to ColorPicker (another example of making a minimal repo)
The code should have gotten errors in the JavaScript console. Did you check the JavaScript console? In particular uNMatrix is a mat3 but the code was calling gl.uniformMatrix4fv to set it which is an error.
Using webgl-lint pointed out several uniforms were not being set including "ambientProduct", "diffuseProduct", "specularProduct" and several were being set that don't exist (that part is not a bug necessarily) but there are several uniforms in the vertex shader that are not actually used and for example the colors seem to be set with
gl.uniform4fv(prg.uMaterialAmbient, object.ambient);
gl.uniform4fv(prg.uMaterialDiffuse, object.diffuse);
gl.uniform4fv(prg.uMaterialSpecular, object.specular);
but those uniforms are not used in the shaders (they appear in the shader but if you look at the code you'll see the effect nothing)
Then, when I finally got rid of all the errors the remaining issue to get something on the screen was the math for gl_Position had the 2 matrices backward. It should be projection * modelView * position but it had modelView * projection * position
But, further, all 3 models are drawn at the same position. Maybe the geometry in those models are at different positions. I have no idea but normally you'd want to give each thing you draw it's own position in some form or another, based on the loop index or some array of positions or per object data.
Anyway, that gets something on the screen but having spent 45 minutes already I don't feel like trying to fix the lighting. Rather, I suggest you read some slightly more up to date tutorials like this one and the ones it links to.
var mat4 = glMatrix.mat4;
var gl = null; // WebGL context
var prg = null; // The program (shaders)
var c_width = 480; // Variable to store the width of the canvas
var c_height = 400; // Variable to store the height of the canvas
var mvMatrix = mat4.create(); // The Model-View matrix
var pMatrix = mat4.create(); // The projection matrix
var nMatrix = mat4.create(); // The normal matrix
var distance = -40;
var animateFlag = false;
var objects = [];
const utils = {
getShader(gl, id) {
const elem = document.getElementById(id);
const type = /vertex/.test(elem.type) ?
gl.VERTEX_SHADER :
gl.FRAGMENT_SHADER;
const sh = gl.createShader(type);
gl.shaderSource(sh, elem.text);
gl.compileShader(sh);
if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
throw new Error(gl.getShaderInfoLog(sh));
}
return sh;
},
};
/**
* The program contains a series of instructions that tell the Graphic Processing Unit (GPU)
* what to do with every vertex and fragment that we pass it.
* The vertex shader and the fragment shader together are called the program.
*/
function initProgram() {
var fragmentShader = utils.getShader(gl, "shader-fs");
var vertexShader = utils.getShader(gl, "shader-vs");
prg = gl.createProgram();
gl.attachShader(prg, vertexShader);
gl.attachShader(prg, fragmentShader);
gl.linkProgram(prg);
if (!gl.getProgramParameter(prg, gl.LINK_STATUS)) {
alert("Could not initialise shaders");
}
gl.useProgram(prg);
prg.aVertexPosition = gl.getAttribLocation(prg, "aVertexPosition");
prg.aVertexNormal = gl.getAttribLocation(prg, "aVertexNormal");
prg.uPMatrix = gl.getUniformLocation(prg, "uPMatrix");
prg.uMVMatrix = gl.getUniformLocation(prg, "uMVMatrix");
prg.uNMatrix = gl.getUniformLocation(prg, "uNMatrix");
prg.uMaterialAmbient = gl.getUniformLocation(prg, "uMaterialAmbient");
prg.uMaterialDiffuse = gl.getUniformLocation(prg, "uMaterialDiffuse");
prg.uMaterialSpecular = gl.getUniformLocation(prg, "uMaterialSpecular");
prg.uShininess = gl.getUniformLocation(prg, "uShininess");
prg.uLightPosition = gl.getUniformLocation(prg, "uLightPosition");
prg.uLightAmbient = gl.getUniformLocation(prg, "uLightAmbient");
prg.uLightDiffuse = gl.getUniformLocation(prg, "uLightDiffuse");
prg.uLightSpecular = gl.getUniformLocation(prg, "uLightSpecular");
prg.ambientProduct = gl.getUniformLocation(prg, "ambientProduct");
prg.diffuseProduct = gl.getUniformLocation(prg, "diffuseProduct");
prg.specularProduct = gl.getUniformLocation(prg, "specularProduct");
}
function initLights() {
//Light uniforms
gl.uniform3fv(prg.uLightPosition, [4.5, 3.0, 15.0]);
gl.uniform4f(prg.uLightAmbient, 1.0, 1.0, 1.0, 1.0);
gl.uniform4f(prg.uLightDiffuse, 1.0, 1.0, 1.0, 1.0);
gl.uniform4f(prg.uLightSpecular, 1.0, 1.0, 1.0, 1.0);
//Object Uniforms
gl.uniform4f(prg.uMaterialAmbient, 0.1, 0.1, 0.1, 1.0);
gl.uniform4f(prg.uMaterialDiffuse, 0.5, 0.8, 0.1, 1.0);
gl.uniform4f(prg.uMaterialSpecular, 0.6, 0.6, 0.6, 1.0);
gl.uniform1f(prg.uShininess, 200.0);
}
/**
* Creates an AJAX request to load the scene asynchronously
*/
function loadScene() {
loadObject('models/plane.json');
loadObject('models/cone.json', 'cone');
loadObject('models/sphere.json', 'sphere');
loadObject('models/smallsph.json', 'lightsource');
}
function getObject(alias) {
for (var i = 0; i < objects.length; i++) {
if (alias == objects[i].alias) return objects[i];
}
return null;
}
/**
* Ajax and JSON in action
*/
const vertices = [
-1, -1, 1,
1, -1, 1,
-1, 1, 1,
1, 1, 1,
1, -1, 1,
1, -1, -1,
1, 1, 1,
1, 1, -1,
1, -1, -1,
-1, -1, -1,
1, 1, -1,
-1, 1, -1,
-1, -1, -1,
-1, -1, 1,
-1, 1, -1,
-1, 1, 1,
1, 1, -1,
-1, 1, -1,
1, 1, 1,
-1, 1, 1,
1, -1, 1,
-1, -1, 1,
1, -1, -1,
-1, -1, -1,
];
let modelNum = 0
function loadObject(filename, alias) {
setTimeout(() => {
const o = {
alias: (alias == null) ? 'none' : alias,
ambient: [0.1, 0.1, 0.1, 1],
diffuse: [Math.random(), Math.random(), Math.random(), 1],
specular: [1, 1, 1, 1],
// to make up for the fact the code does not have different positions
// for each model we'll move the vertices (bad)
vertices: vertices.map((v, i) => i % 3 === 0 ? v + modelNum * 3 : v),
indices: [
0, 1, 2, 2, 1, 3,
4, 5, 6, 6, 5, 7,
8, 9, 10, 10, 9, 11,
12, 13, 14, 14, 13, 15,
16, 17, 18, 18, 17, 19,
20, 21, 22, 22, 21, 23,
],
};
handleLoadedObject(filename, o);
++modelNum;
});
}
/**
* Creates the buffers that contain the geometry of the object
*/
function handleLoadedObject(filename, object) {
//console.info(filename + ' has been retrieved from the server');
var vertexBufferObject = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBufferObject);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(object.vertices), gl.STATIC_DRAW);
var normalBufferObject = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normalBufferObject);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(calcNormals(object.vertices, object.indices)), gl.STATIC_DRAW);
var indexBufferObject = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBufferObject);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(object.indices), gl.STATIC_DRAW);
object.vbo = vertexBufferObject;
object.ibo = indexBufferObject;
object.nbo = normalBufferObject;
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
objects.push(object);
}
/**
* Main rendering function. Called every 500ms according to WebGLStart function (see below)
*/
function drawScene() {
gl.clearColor(0.3, 0.3, 0.3, 1.0);
//gl.clearDepth(100.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.viewport(0, 0, c_width, c_height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
mat4.perspective(pMatrix, 60 * Math.PI / 180, c_width / c_height, 0.1, 1000.0);
gl.enableVertexAttribArray(prg.aVertexPosition);
gl.enableVertexAttribArray(prg.aVertexNormal);
for (var i = 0; i < objects.length; i++) {
var object = objects[i];
mat4.identity(mvMatrix);
mat4.translate(mvMatrix, mvMatrix, [0.0, 0.0, distance]); //Sets the camera to a reasonable distance to view the part
mat4.rotate(mvMatrix, mvMatrix, 30 * Math.PI / 180, [1, 0, 0]);
mat4.rotate(mvMatrix, mvMatrix, angle * Math.PI / 180, [0, 1, 0]);
if (object.alias == 'lightsource') {
var lightPos = gl.getUniform(prg, prg.uLightPosition);
mat4.translate(mvMatrix, mvMatrix, lightPos);
}
gl.uniformMatrix4fv(prg.uMVMatrix, false, mvMatrix);
gl.uniformMatrix4fv(prg.uPMatrix, false, pMatrix);
mat4.set(mvMatrix, nMatrix);
mat4.invert(nMatrix, nMatrix);
mat4.transpose(nMatrix, nMatrix);
const t3 = glMatrix.mat3.create();
glMatrix.mat3.fromMat4(t3, nMatrix);
gl.uniformMatrix3fv(prg.uNMatrix, false, t3);
gl.uniform4fv(prg.ambientProduct, object.ambient);
gl.uniform4fv(prg.diffuseProduct, object.diffuse);
gl.uniform4fv(prg.specularProduct, object.specular);
gl.bindBuffer(gl.ARRAY_BUFFER, object.vbo);
gl.vertexAttribPointer(prg.aVertexPosition, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(prg.aVertexPosition);
gl.bindBuffer(gl.ARRAY_BUFFER, object.nbo);
gl.vertexAttribPointer(prg.aVertexNormal, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(prg.aVertexNormal);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, object.ibo);
gl.drawElements(gl.TRIANGLES, object.indices.length, gl.UNSIGNED_SHORT, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
}
var lastTime = 0;
var angle = 0;
/**
* Updates the angle of rotation by a little bit each time
*/
function animate() {
var timeNow = new Date().getTime();
if (lastTime != 0) {
var elapsed = timeNow - lastTime;
if (animateFlag) angle += (90 * elapsed) / 10000.0;
}
lastTime = timeNow;
}
/**
* Render Loop
*/
function renderLoop() {
drawScene();
animate();
requestAnimationFrame(renderLoop);
}
/**
* Entry point. This function is invoked when the page is loaded
*/
function runWebGLApp() {
//Obtains a WebGL context
gl = document.getElementById("canvas-element-id").getContext('webgl');
//Initializes the program (shaders)
initProgram();
//Initializes lights
initLights();
//Load Scene
loadScene();
//Renders the scene!
renderLoop();
}
$('#slider-shininess').slider({
value: 200,
min: 1,
max: 300,
step: 1,
slide: updateShininess
});
$('#slider-x').slider({
value: 4.5,
min: -50,
max: 50,
step: 0.1,
slide: updateLightPosition,
change: updateLightPosition
});
$('#slider-y').slider({
value: 3.0,
min: 0,
max: 50,
step: 0.1,
slide: updateLightPosition,
change: updateLightPosition
});
$('#slider-z').slider({
value: 15.0,
min: -50,
max: 50,
step: 0.1,
slide: updateLightPosition,
change: updateLightPosition
});
$('#animate-btn').button();
$('#animate-btn').click(
function() {
if ($('#animate-btn:checked').val() == null) {
animateFlag = false;
} else {
animateFlag = true;
}
});
function updateShininess() {
var v = $('#slider-shininess').slider("value");
gl.uniform1f(prg.uShininess, v);
$('#slider-shininess-value').html(v);
}
function updateLightPosition() {
var x = $('#slider-x').slider("value");
var y = $('#slider-y').slider("value");
var z = $('#slider-z').slider("value");
gl.uniform3fv(prg.uLightPosition, [x, y, z]);
$('#slider-x-value').html(x);
$('#slider-y-value').html(y);
$('#slider-z-value').html(z);
}
function updateDistance() {
var d = $('#slider-distance').slider("value");
$('#slider-distance-value').html(distance);
distance = -d;
}
function updateObjectColor(alias, r, g, b) {
var object = getObject(alias);
if (object != null) {
object.diffuse = [r, g, b, 1.0];
}
}
// Calcule les normales des vertex. La normale de chaque vertex est
// la moyenne des triangles voisins.
//
// vertices: la liste des vertex.
// ind: la liste des indices.
// retour: la liste des normales par vertex.
function calcNormals(vertices, ind) {
var x = 0;
var y = 1;
var z = 2;
var v1 = [],
v2 = [],
thisNormal = [];
// initialiser la liste des normales.
var ns = [];
for (var i = 0; i < vertices.length; i++) {
ns[i] = 0.0;
}
for (var i = 0; i < ind.length; i = i + 3) {
//v1 = p1 - p0
v1[x] = vertices[3 * ind[i + 1] + x] - vertices[3 * ind[i] + x];
v1[y] = vertices[3 * ind[i + 1] + y] - vertices[3 * ind[i] + y];
v1[z] = vertices[3 * ind[i + 1] + z] - vertices[3 * ind[i] + z];
// v2 = p2 - p1
v2[x] = vertices[3 * ind[i + 2] + x] - vertices[3 * ind[i] + x];
v2[y] = vertices[3 * ind[i + 2] + y] - vertices[3 * ind[i] + y];
v2[z] = vertices[3 * ind[i + 2] + z] - vertices[3 * ind[i] + z];
// N = v2 x v1 (cross product).
thisNormal[x] = v1[y] * v2[z] - v1[z] * v2[y];
thisNormal[y] = v1[z] * v2[x] - v1[x] * v2[z];
thisNormal[z] = v1[x] * v2[y] - v1[y] * v2[x];
for (j = 0; j < 3; j++) {
// N += thisNormal. on additionne les normales.
ns[3 * ind[i + j] + x] = ns[3 * ind[i + j] + x] + thisNormal[x];
ns[3 * ind[i + j] + y] = ns[3 * ind[i + j] + y] + thisNormal[y];
ns[3 * ind[i + j] + z] = ns[3 * ind[i + j] + z] + thisNormal[z];
}
}
// Normalisation.
for (var i = 0; i < vertices.length; i = i + 3) {
var nn = [];
var len = 0;
for (var j = 0; j < 3; j++) {
nn[j] = ns[i + j];
len += nn[j] * nn[j];
}
// La norme de la normale.
len = Math.sqrt(len);
if (len == 0)
len = 0.00001;
for (var j = 0; j < 3; j++)
ns[i + j] = nn[j] / len;
}
return ns;
}
runWebGLApp();
<link href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet">
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
attribute vec3 aVertexNormal;
// matrice model-view combinee.
uniform mat4 uMVMatrix;
// matrice de projection
uniform mat4 uPMatrix;
// matrice des normales.
uniform mat3 uNMatrix;
// position de la lumiere.
uniform vec3 uLightPosition;
// La normale transformee
varying vec3 vNormal;
// la direction vertex-lumiere
varying vec3 vLightRay;
// la direction camera-vertex
varying vec3 vEyeVec;
uniform vec4 uLightAmbient;
uniform vec4 uLightDiffuse;
uniform vec4 uLightSpecular;
uniform vec4 uMaterialAmbient;
uniform vec4 uMaterialDiffuse;
uniform vec4 uMaterialSpecular;
void main(void) {
vec4 ambientProduct= uLightAmbient* uMaterialAmbient;
vec4 diffuseProduct= uLightDiffuse*uMaterialDiffuse;
vec4 specularProduct= uLightSpecular*uMaterialSpecular;
vec3 pos = (uMVMatrix*vec4(aVertexPosition, 1.0)).xyz;
// position de l'oeil/camera.
const vec3 eyePosition = vec3(0,0,-40);
//Transformed normal position
vNormal = normalize((uNMatrix* aVertexNormal).xyz) ;
//Transformed light position
vec4 light = uMVMatrix * vec4(uLightPosition,1.0);
vec3 lightPos = (uMVMatrix * light).xyz;
//Light position
vLightRay = normalize(pos - lightPos);
//Vector Eye
vEyeVec = -normalize(pos);
//Final vertex position
gl_Position = uPMatrix*uMVMatrix* vec4(aVertexPosition, 1.0);
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
#ifdef GL_ES
precision highp float;
#endif
varying vec3 vNormal;
varying vec3 vLightRay;
varying vec3 vEyeVec;
uniform vec4 ambientProduct;
uniform vec4 diffuseProduct;
uniform vec4 specularProduct;
uniform float uShininess;
void main(void)
{
vec4 diffuse = max(dot( vNormal,vLightRay), 0.0) * diffuseProduct;
vec3 H = normalize(vLightRay+vEyeVec);
vec4 specular =
pow(max(dot(vNormal, H), 0.0), uShininess) * specularProduct;
if (dot(vLightRay, vNormal) < 0.0)
specular = vec4(0.0, 0.0, 0.0, 1.0);
vec4 fColor = ambientProduct + diffuse + specular;
fColor.a = 1.0;
gl_FragColor =fColor;
}
</script>
<div id='top'>
<div id='contents'>
<div id='canvasContainer'>
<canvas id='canvas-element-id' width='480' height='400'>
Your browser does not support the HTML5 canvas element.
</canvas>
</div>
</div>
<div id='bottom'>
<table style='padding=0px'>
<tr>
<td>X:</td><td id='slider-x-value' width='30px'>4.5</td><td width='150px'><div id='slider-x'/></td>
</tr>
<tr>
<td>Y:</td><td id='slider-y-value' width='30px'>3.0</td><td width='150px'><div id='slider-y'/></td>
</tr>
<tr>
<td>Z:</td> <td id='slider-z-value' width='30px'>15.0</td><td width='150px'><div id='slider-z'/></td>
</tr>
</table>
</div>
<script src="https://cdn.jsdelivr.net/npm/gl-matrix#3.3.0/gl-matrix-min.js"></script>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<script src="https://greggman.github.io/webgl-lint/webgl-lint.js"
data-gman-debug-helper='
{
"warnUndefinedUniforms": false
}
'
></script>
Here is the demo.
// A set of utility functions for /common operations across our application
const utils = {
// Find and return a DOM element given an ID
getCanvas(id) {
const canvas = document.getElementById(id);
if (!canvas) {
console.error(`There is no canvas with id ${id} on this page.`);
return null;
}
return canvas;
},
// Given a canvas element, return the WebGL2 context
getGLContext(canvas) {
return canvas.getContext('webgl2') || console.error('WebGL2 is not available in your browser.');
},
// Given a canvas element, expand it to the size of the window
// and ensure that it automatically resizes as the window changes
autoResizeCanvas(canvas) {
const expandFullScreen = () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
expandFullScreen();
// Resize screen when the browser has triggered the resize event
window.addEventListener('resize', expandFullScreen);
},
// Given a WebGL context and an id for a shader script,
// return a compiled shader
getShader(gl, id) {
const script = document.getElementById(id);
if (!script) {
return null;
}
const shaderString = script.text.trim();
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;
}
gl.shaderSource(shader, shaderString);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader));
return null;
}
return shader;
},
// Normalize colors from 0-255 to 0-1
normalizeColor(color) {
return color.map(c => c / 255);
},
// De-normalize colors from 0-1 to 0-255
denormalizeColor(color) {
return color.map(c => c * 255);
},
// Returns computed normals for provided vertices.
// Note: Indices have to be completely defined--NO TRIANGLE_STRIP only TRIANGLES.
calculateNormals(vs, ind) {
const
x = 0,
y = 1,
z = 2,
ns = [];
// For each vertex, initialize normal x, normal y, normal z
for (let i = 0; i < vs.length; i += 3) {
ns[i + x] = 0.0;
ns[i + y] = 0.0;
ns[i + z] = 0.0;
}
// We work on triads of vertices to calculate
for (let i = 0; i < ind.length; i += 3) {
// Normals so i = i+3 (i = indices index)
const v1 = [],
v2 = [],
normal = [];
// p2 - p1
v1[x] = vs[3 * ind[i + 2] + x] - vs[3 * ind[i + 1] + x];
v1[y] = vs[3 * ind[i + 2] + y] - vs[3 * ind[i + 1] + y];
v1[z] = vs[3 * ind[i + 2] + z] - vs[3 * ind[i + 1] + z];
// p0 - p1
v2[x] = vs[3 * ind[i] + x] - vs[3 * ind[i + 1] + x];
v2[y] = vs[3 * ind[i] + y] - vs[3 * ind[i + 1] + y];
v2[z] = vs[3 * ind[i] + z] - vs[3 * ind[i + 1] + z];
// Cross product by Sarrus Rule
normal[x] = v1[y] * v2[z] - v1[z] * v2[y];
normal[y] = v1[z] * v2[x] - v1[x] * v2[z];
normal[z] = v1[x] * v2[y] - v1[y] * v2[x];
// Update the normals of that triangle: sum of vectors
for (let j = 0; j < 3; j++) {
ns[3 * ind[i + j] + x] = ns[3 * ind[i + j] + x] + normal[x];
ns[3 * ind[i + j] + y] = ns[3 * ind[i + j] + y] + normal[y];
ns[3 * ind[i + j] + z] = ns[3 * ind[i + j] + z] + normal[z];
}
}
// Normalize the result.
// The increment here is because each vertex occurs.
for (let i = 0; i < vs.length; i += 3) {
// With an offset of 3 in the array (due to x, y, z contiguous values)
const nn = [];
nn[x] = ns[i + x];
nn[y] = ns[i + y];
nn[z] = ns[i + z];
let len = Math.sqrt((nn[x] * nn[x]) + (nn[y] * nn[y]) + (nn[z] * nn[z]));
if (len === 0) len = 1.0;
nn[x] = nn[x] / len;
nn[y] = nn[y] / len;
nn[z] = nn[z] / len;
ns[i + x] = nn[x];
ns[i + y] = nn[y];
ns[i + z] = nn[z];
}
return ns;
},
// A simpler API on top of the dat.GUI API, specifically
// designed for this book for a simpler codebase
configureControls(settings, options = {
width: 300
}) {
// Check if a gui instance is passed in or create one by default
const gui = options.gui || new dat.GUI(options);
const state = {};
const isAction = v => typeof v === 'function';
const isFolder = v =>
!isAction(v) &&
typeof v === 'object' &&
(v.value === null || v.value === undefined);
const isColor = v =>
(typeof v === 'string' && ~v.indexOf('#')) ||
(Array.isArray(v) && v.length >= 3);
Object.keys(settings).forEach(key => {
const settingValue = settings[key];
if (isAction(settingValue)) {
state[key] = settingValue;
return gui.add(state, key);
}
if (isFolder(settingValue)) {
// If it's a folder, recursively call with folder as root settings element
return utils.configureControls(settingValue, {
gui: gui.addFolder(key)
});
}
const {
value,
min,
max,
step,
options,
onChange = () => null,
} = settingValue;
// set state
state[key] = value;
let controller;
// There are many other values we can set on top of the dat.GUI
// API, but we'll only need a few for our purposes
if (options) {
controller = gui.add(state, key, options);
} else if (isColor(value)) {
controller = gui.addColor(state, key)
} else {
controller = gui.add(state, key, min, max, step)
}
controller.onChange(v => onChange(v, state))
});
},
// Calculate tangets for a given set of vertices
calculateTangents(vs, tc, ind) {
const tangents = [];
for (let i = 0; i < vs.length / 3; i++) {
tangents[i] = [0, 0, 0];
}
let
a = [0, 0, 0],
b = [0, 0, 0],
triTangent = [0, 0, 0];
for (let i = 0; i < ind.length; i += 3) {
const i0 = ind[i];
const i1 = ind[i + 1];
const i2 = ind[i + 2];
const pos0 = [vs[i0 * 3], vs[i0 * 3 + 1], vs[i0 * 3 + 2]];
const pos1 = [vs[i1 * 3], vs[i1 * 3 + 1], vs[i1 * 3 + 2]];
const pos2 = [vs[i2 * 3], vs[i2 * 3 + 1], vs[i2 * 3 + 2]];
const tex0 = [tc[i0 * 2], tc[i0 * 2 + 1]];
const tex1 = [tc[i1 * 2], tc[i1 * 2 + 1]];
const tex2 = [tc[i2 * 2], tc[i2 * 2 + 1]];
vec3.subtract(a, pos1, pos0);
vec3.subtract(b, pos2, pos0);
const c2c1b = tex1[1] - tex0[1];
const c3c1b = tex2[0] - tex0[1];
triTangent = [c3c1b * a[0] - c2c1b * b[0], c3c1b * a[1] - c2c1b * b[1], c3c1b * a[2] - c2c1b * b[2]];
vec3.add(triTangent, tangents[i0], triTangent);
vec3.add(triTangent, tangents[i1], triTangent);
vec3.add(triTangent, tangents[i2], triTangent);
}
// Normalize tangents
const ts = [];
tangents.forEach(tan => {
vec3.normalize(tan, tan);
ts.push(tan[0]);
ts.push(tan[1]);
ts.push(tan[2]);
});
return ts;
}
};
let
gl,
program,
modelViewMatrix = mat4.create(),
projectionMatrix = mat4.create(),
normalMatrix = mat4.create(),
vao,
indices,
sphereIndicesBuffer,
currentX,
currentY,
lastX,
lastY,
dragging,
rotateAxis = [0, 1, 0],
angle = 0,
shininess = 10,
clearColor = [0.9, 0.9, 0.9],
lightColor = [1, 1, 1, 1],
lightAmbient = [0.03, 0.03, 0.03, 1],
lightSpecular = [1, 1, 1, 1],
lightDirection = [-0.25, -0.25, -0.25],
materialDiffuse = [46 / 256, 99 / 256, 191 / 256, 1],
materialAmbient = [1, 1, 1, 1],
materialSpecular = [1, 1, 1, 1];
function initProgram() {
// Configure `canvas`
const canvas = utils.getCanvas('webgl-canvas');
utils.autoResizeCanvas(canvas);
// Configure `gl`
gl = utils.getGLContext(canvas);
gl.clearColor(...clearColor, 1);
gl.clearDepth(100);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
// Shader source
const vertexShader = utils.getShader(gl, 'vertex-shader');
const fragmentShader = utils.getShader(gl, 'fragment-shader');
// Configure `program`
program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error('Could not initialize shaders');
}
gl.useProgram(program);
// Set locations onto `program` instance
program.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition');
program.aVertexNormal = gl.getAttribLocation(program, 'aVertexNormal');
program.uProjectionMatrix = gl.getUniformLocation(program, 'uProjectionMatrix');
program.uModelViewMatrix = gl.getUniformLocation(program, 'uModelViewMatrix');
program.uNormalMatrix = gl.getUniformLocation(program, 'uNormalMatrix');
program.uMaterialAmbient = gl.getUniformLocation(program, 'uMaterialAmbient');
program.uMaterialDiffuse = gl.getUniformLocation(program, 'uMaterialDiffuse');
program.uMaterialSpecular = gl.getUniformLocation(program, 'uMaterialSpecular');
program.uShininess = gl.getUniformLocation(program, 'uShininess');
program.uLightAmbient = gl.getUniformLocation(program, 'uLightAmbient');
program.uLightDiffuse = gl.getUniformLocation(program, 'uLightDiffuse');
program.uLightSpecular = gl.getUniformLocation(program, 'uLightSpecular');
program.uLightDirection = gl.getUniformLocation(program, 'uLightDirection');
canvas.onmousedown = event => onMouseDown(event);
canvas.onmouseup = event => onMouseUp(event);
canvas.onmousemove = event => onMouseMove(event);
}
// Configure lights
function initLights() {
gl.uniform4fv(program.uLightDiffuse, lightColor);
gl.uniform4fv(program.uLightAmbient, lightAmbient);
gl.uniform4fv(program.uLightSpecular, lightSpecular);
gl.uniform3fv(program.uLightDirection, lightDirection);
gl.uniform4fv(program.uMaterialDiffuse, materialDiffuse);
gl.uniform4fv(program.uMaterialAmbient, materialAmbient);
gl.uniform4fv(program.uMaterialSpecular, materialSpecular);
gl.uniform1f(program.uShininess, shininess);
}
function initBuffers() {
const vertices = [
1.5, 0, 0, -1.5, 1, 0, -1.5, 0.809017, 0.587785, -1.5, 0.309017, 0.951057, -1.5, -0.309017, 0.951057, -1.5, -0.809017, 0.587785, -1.5, -1, 0, -1.5, -0.809017, -0.587785, -1.5, -0.309017, -0.951057, -1.5, 0.309017, -0.951057, -1.5, 0.809017, -0.587785
];
indices = [
0, 1, 2,
0, 2, 3,
0, 3, 4,
0, 4, 5,
0, 5, 6,
0, 6, 7,
0, 7, 8,
0, 8, 9,
0, 9, 10,
0, 10, 1
];
const normals = utils.calculateNormals(vertices, indices);
// Create VAO
vao = gl.createVertexArray();
// Bind VAO
gl.bindVertexArray(vao);
// Vertices
const sphereVerticesBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, sphereVerticesBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
// Configure VAO instructions
gl.enableVertexAttribArray(program.aVertexPosition);
gl.vertexAttribPointer(program.aVertexPosition, 3, gl.FLOAT, false, 0, 0);
// Normals
const sphereNormalsBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, sphereNormalsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);
// Configure VAO instructions
gl.enableVertexAttribArray(program.aVertexNormal);
gl.vertexAttribPointer(program.aVertexNormal, 3, gl.FLOAT, false, 0, 0);
// Indices
sphereIndicesBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, sphereIndicesBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
// Clean
gl.bindVertexArray(null);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
function onMouseDown(event) {
dragging = true;
currentX = event.clientX;
currentY = event.clientY;
}
function onMouseMove(event) {
if (dragging) {
lastX = currentX;
lastY = currentY;
currentX = event.clientX;
currentY = event.clientY;
const dx = currentX - lastX;
rotateAxis = [0, 1, 0];
angle = dx;
}
}
function onMouseUp() {
dragging = false;
}
function draw() {
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
mat4.perspective(projectionMatrix, 45, gl.canvas.width / gl.canvas.height, 0.1, 10000);
mat4.identity(modelViewMatrix);
mat4.translate(modelViewMatrix, modelViewMatrix, [0, 0, -5]);
mat4.rotate(modelViewMatrix, modelViewMatrix, angle * Math.PI / 180, [0, 1, 0]);
mat4.copy(normalMatrix, modelViewMatrix);
mat4.invert(normalMatrix, normalMatrix);
mat4.transpose(normalMatrix, normalMatrix);
gl.uniformMatrix4fv(program.uNormalMatrix, false, normalMatrix);
gl.uniformMatrix4fv(program.uModelViewMatrix, false, modelViewMatrix);
gl.uniformMatrix4fv(program.uProjectionMatrix, false, projectionMatrix);
// We will start using the `try/catch` to capture any errors from our `draw` calls
try {
gl.bindVertexArray(vao);
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
} catch (error) {
console.error(error);
}
}
function render() {
requestAnimationFrame(render);
draw();
}
function init() {
initProgram();
initBuffers();
initLights();
render();
}
init();
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js"></script>
<!-- vertex Shader -->
<script id="vertex-shader" type="x-shader/x-vertex">
#version 300 es
precision mediump float;
uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;
uniform mat4 uNormalMatrix;
in vec3 aVertexPosition;
in vec3 aVertexNormal;
out vec3 vNormal;
out vec3 vEyeVector;
void main(void) {
vec4 vertex = uModelViewMatrix * vec4(aVertexPosition, 1.0);
// Set varyings to be used inside of fragment shader
vNormal = vec3(uNormalMatrix * vec4(aVertexNormal, 1.0));
vEyeVector = -vec3(vertex.xyz);
gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0);
}
</script>
<!-- fragment Shader -->
<script id="fragment-shader" type="x-shader/x-fragment">
#version 300 es
precision mediump float;
uniform float uShininess;
uniform vec3 uLightDirection;
uniform vec4 uLightAmbient;
uniform vec4 uLightDiffuse;
uniform vec4 uLightSpecular;
uniform vec4 uMaterialAmbient;
uniform vec4 uMaterialDiffuse;
uniform vec4 uMaterialSpecular;
in vec3 vNormal;
in vec3 vEyeVector;
out vec4 fragColor;
void main(void) {
// Normalized light direction
vec3 L = normalize(uLightDirection);
// Normalized normal
vec3 N = normalize(vNormal);
float lambertTerm = dot(N, -L);
// Ambient
vec4 Ia = uLightAmbient * uMaterialAmbient;
// Diffuse
vec4 Id = vec4(0.0, 0.0, 0.0, 1.0);
// Specular
vec4 Is = vec4(0.0, 0.0, 0.0, 1.0);
if (lambertTerm > 0.0) {
Id = uLightDiffuse * uMaterialDiffuse * lambertTerm;
vec3 E = normalize(vEyeVector);
vec3 R = reflect(L, N);
float specular = pow( max(dot(R, E), 0.0), uShininess);
Is = uLightSpecular * uMaterialSpecular * specular;
}
// Final fargment color takes into account all light values that
// were computed within the fragment shader
fragColor = vec4(vec3(Ia + Id + Is), 1.0);
}
</script>
<canvas id="webgl-canvas"> </canvas>
The thing I wanted to achieve was, I wanted to use my mouse to drag this cone and rotate it around y axis.
so first I have my canvas events set up.
canvas.onmousedown = event => onMouseDown(event);
canvas.onmouseup = event => onMouseUp(event);
canvas.onmousemove = event => onMouseMove(event);
...
function onMouseDown(event) {
dragging = true;
currentX = event.clientX;
currentY = event.clientY;
}
function onMouseMove(event) {
if(dragging) {
lastX = currentX;
lastY = currentY;
currentX = event.clientX;
currentY = event.clientY;
const dx = currentX - lastX;
rotateAxis = [0,1,0];
angle = dx;
}
}
function onMouseUp() {
dragging = false;
}
The logic is, when the mouse is pressed, I set the flag dragging to be true and store the screen coordinates to currentX and currentY, then moving the mouse, I will calculate the difference between the current coordinates and the previous coordinates and increase or decrease the rotation angle. And then the mouse is not pressed, i.e. it is up, I set the flag to be false.
The problem I have is, even though it works, but it kinda have this not-smooth feel to it. Can someone please help me improve it?
The fix is:
angle += dx;, as you want to accumulate the rotation deltas. Currently you just overwrite the angle with latest dx.
Updated example below:
// A set of utility functions for /common operations across our application
const utils = {
// Find and return a DOM element given an ID
getCanvas(id) {
const canvas = document.getElementById(id);
if (!canvas) {
console.error(`There is no canvas with id ${id} on this page.`);
return null;
}
return canvas;
},
// Given a canvas element, return the WebGL2 context
getGLContext(canvas) {
return canvas.getContext('webgl2') || console.error('WebGL2 is not available in your browser.');
},
// Given a canvas element, expand it to the size of the window
// and ensure that it automatically resizes as the window changes
autoResizeCanvas(canvas) {
const expandFullScreen = () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
expandFullScreen();
// Resize screen when the browser has triggered the resize event
window.addEventListener('resize', expandFullScreen);
},
// Given a WebGL context and an id for a shader script,
// return a compiled shader
getShader(gl, id) {
const script = document.getElementById(id);
if (!script) {
return null;
}
const shaderString = script.text.trim();
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;
}
gl.shaderSource(shader, shaderString);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader));
return null;
}
return shader;
},
// Normalize colors from 0-255 to 0-1
normalizeColor(color) {
return color.map(c => c / 255);
},
// De-normalize colors from 0-1 to 0-255
denormalizeColor(color) {
return color.map(c => c * 255);
},
// Returns computed normals for provided vertices.
// Note: Indices have to be completely defined--NO TRIANGLE_STRIP only TRIANGLES.
calculateNormals(vs, ind) {
const
x = 0,
y = 1,
z = 2,
ns = [];
// For each vertex, initialize normal x, normal y, normal z
for (let i = 0; i < vs.length; i += 3) {
ns[i + x] = 0.0;
ns[i + y] = 0.0;
ns[i + z] = 0.0;
}
// We work on triads of vertices to calculate
for (let i = 0; i < ind.length; i += 3) {
// Normals so i = i+3 (i = indices index)
const v1 = [],
v2 = [],
normal = [];
// p2 - p1
v1[x] = vs[3 * ind[i + 2] + x] - vs[3 * ind[i + 1] + x];
v1[y] = vs[3 * ind[i + 2] + y] - vs[3 * ind[i + 1] + y];
v1[z] = vs[3 * ind[i + 2] + z] - vs[3 * ind[i + 1] + z];
// p0 - p1
v2[x] = vs[3 * ind[i] + x] - vs[3 * ind[i + 1] + x];
v2[y] = vs[3 * ind[i] + y] - vs[3 * ind[i + 1] + y];
v2[z] = vs[3 * ind[i] + z] - vs[3 * ind[i + 1] + z];
// Cross product by Sarrus Rule
normal[x] = v1[y] * v2[z] - v1[z] * v2[y];
normal[y] = v1[z] * v2[x] - v1[x] * v2[z];
normal[z] = v1[x] * v2[y] - v1[y] * v2[x];
// Update the normals of that triangle: sum of vectors
for (let j = 0; j < 3; j++) {
ns[3 * ind[i + j] + x] = ns[3 * ind[i + j] + x] + normal[x];
ns[3 * ind[i + j] + y] = ns[3 * ind[i + j] + y] + normal[y];
ns[3 * ind[i + j] + z] = ns[3 * ind[i + j] + z] + normal[z];
}
}
// Normalize the result.
// The increment here is because each vertex occurs.
for (let i = 0; i < vs.length; i += 3) {
// With an offset of 3 in the array (due to x, y, z contiguous values)
const nn = [];
nn[x] = ns[i + x];
nn[y] = ns[i + y];
nn[z] = ns[i + z];
let len = Math.sqrt((nn[x] * nn[x]) + (nn[y] * nn[y]) + (nn[z] * nn[z]));
if (len === 0) len = 1.0;
nn[x] = nn[x] / len;
nn[y] = nn[y] / len;
nn[z] = nn[z] / len;
ns[i + x] = nn[x];
ns[i + y] = nn[y];
ns[i + z] = nn[z];
}
return ns;
},
// A simpler API on top of the dat.GUI API, specifically
// designed for this book for a simpler codebase
configureControls(settings, options = {
width: 300
}) {
// Check if a gui instance is passed in or create one by default
const gui = options.gui || new dat.GUI(options);
const state = {};
const isAction = v => typeof v === 'function';
const isFolder = v =>
!isAction(v) &&
typeof v === 'object' &&
(v.value === null || v.value === undefined);
const isColor = v =>
(typeof v === 'string' && ~v.indexOf('#')) ||
(Array.isArray(v) && v.length >= 3);
Object.keys(settings).forEach(key => {
const settingValue = settings[key];
if (isAction(settingValue)) {
state[key] = settingValue;
return gui.add(state, key);
}
if (isFolder(settingValue)) {
// If it's a folder, recursively call with folder as root settings element
return utils.configureControls(settingValue, {
gui: gui.addFolder(key)
});
}
const {
value,
min,
max,
step,
options,
onChange = () => null,
} = settingValue;
// set state
state[key] = value;
let controller;
// There are many other values we can set on top of the dat.GUI
// API, but we'll only need a few for our purposes
if (options) {
controller = gui.add(state, key, options);
} else if (isColor(value)) {
controller = gui.addColor(state, key)
} else {
controller = gui.add(state, key, min, max, step)
}
controller.onChange(v => onChange(v, state))
});
},
// Calculate tangets for a given set of vertices
calculateTangents(vs, tc, ind) {
const tangents = [];
for (let i = 0; i < vs.length / 3; i++) {
tangents[i] = [0, 0, 0];
}
let
a = [0, 0, 0],
b = [0, 0, 0],
triTangent = [0, 0, 0];
for (let i = 0; i < ind.length; i += 3) {
const i0 = ind[i];
const i1 = ind[i + 1];
const i2 = ind[i + 2];
const pos0 = [vs[i0 * 3], vs[i0 * 3 + 1], vs[i0 * 3 + 2]];
const pos1 = [vs[i1 * 3], vs[i1 * 3 + 1], vs[i1 * 3 + 2]];
const pos2 = [vs[i2 * 3], vs[i2 * 3 + 1], vs[i2 * 3 + 2]];
const tex0 = [tc[i0 * 2], tc[i0 * 2 + 1]];
const tex1 = [tc[i1 * 2], tc[i1 * 2 + 1]];
const tex2 = [tc[i2 * 2], tc[i2 * 2 + 1]];
vec3.subtract(a, pos1, pos0);
vec3.subtract(b, pos2, pos0);
const c2c1b = tex1[1] - tex0[1];
const c3c1b = tex2[0] - tex0[1];
triTangent = [c3c1b * a[0] - c2c1b * b[0], c3c1b * a[1] - c2c1b * b[1], c3c1b * a[2] - c2c1b * b[2]];
vec3.add(triTangent, tangents[i0], triTangent);
vec3.add(triTangent, tangents[i1], triTangent);
vec3.add(triTangent, tangents[i2], triTangent);
}
// Normalize tangents
const ts = [];
tangents.forEach(tan => {
vec3.normalize(tan, tan);
ts.push(tan[0]);
ts.push(tan[1]);
ts.push(tan[2]);
});
return ts;
}
};
let
gl,
program,
modelViewMatrix = mat4.create(),
projectionMatrix = mat4.create(),
normalMatrix = mat4.create(),
vao,
indices,
sphereIndicesBuffer,
currentX,
currentY,
lastX,
lastY,
dragging,
rotateAxis = [0, 1, 0],
angle = 0,
shininess = 10,
clearColor = [0.9, 0.9, 0.9],
lightColor = [1, 1, 1, 1],
lightAmbient = [0.03, 0.03, 0.03, 1],
lightSpecular = [1, 1, 1, 1],
lightDirection = [-0.25, -0.25, -0.25],
materialDiffuse = [46 / 256, 99 / 256, 191 / 256, 1],
materialAmbient = [1, 1, 1, 1],
materialSpecular = [1, 1, 1, 1];
function initProgram() {
// Configure `canvas`
const canvas = utils.getCanvas('webgl-canvas');
utils.autoResizeCanvas(canvas);
// Configure `gl`
gl = utils.getGLContext(canvas);
gl.clearColor(...clearColor, 1);
gl.clearDepth(100);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
// Shader source
const vertexShader = utils.getShader(gl, 'vertex-shader');
const fragmentShader = utils.getShader(gl, 'fragment-shader');
// Configure `program`
program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error('Could not initialize shaders');
}
gl.useProgram(program);
// Set locations onto `program` instance
program.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition');
program.aVertexNormal = gl.getAttribLocation(program, 'aVertexNormal');
program.uProjectionMatrix = gl.getUniformLocation(program, 'uProjectionMatrix');
program.uModelViewMatrix = gl.getUniformLocation(program, 'uModelViewMatrix');
program.uNormalMatrix = gl.getUniformLocation(program, 'uNormalMatrix');
program.uMaterialAmbient = gl.getUniformLocation(program, 'uMaterialAmbient');
program.uMaterialDiffuse = gl.getUniformLocation(program, 'uMaterialDiffuse');
program.uMaterialSpecular = gl.getUniformLocation(program, 'uMaterialSpecular');
program.uShininess = gl.getUniformLocation(program, 'uShininess');
program.uLightAmbient = gl.getUniformLocation(program, 'uLightAmbient');
program.uLightDiffuse = gl.getUniformLocation(program, 'uLightDiffuse');
program.uLightSpecular = gl.getUniformLocation(program, 'uLightSpecular');
program.uLightDirection = gl.getUniformLocation(program, 'uLightDirection');
canvas.onmousedown = event => onMouseDown(event);
canvas.onmouseup = event => onMouseUp(event);
canvas.onmousemove = event => onMouseMove(event);
}
// Configure lights
function initLights() {
gl.uniform4fv(program.uLightDiffuse, lightColor);
gl.uniform4fv(program.uLightAmbient, lightAmbient);
gl.uniform4fv(program.uLightSpecular, lightSpecular);
gl.uniform3fv(program.uLightDirection, lightDirection);
gl.uniform4fv(program.uMaterialDiffuse, materialDiffuse);
gl.uniform4fv(program.uMaterialAmbient, materialAmbient);
gl.uniform4fv(program.uMaterialSpecular, materialSpecular);
gl.uniform1f(program.uShininess, shininess);
}
function initBuffers() {
const vertices = [
1.5, 0, 0, -1.5, 1, 0, -1.5, 0.809017, 0.587785, -1.5, 0.309017, 0.951057, -1.5, -0.309017, 0.951057, -1.5, -0.809017, 0.587785, -1.5, -1, 0, -1.5, -0.809017, -0.587785, -1.5, -0.309017, -0.951057, -1.5, 0.309017, -0.951057, -1.5, 0.809017, -0.587785
];
indices = [
0, 1, 2,
0, 2, 3,
0, 3, 4,
0, 4, 5,
0, 5, 6,
0, 6, 7,
0, 7, 8,
0, 8, 9,
0, 9, 10,
0, 10, 1
];
const normals = utils.calculateNormals(vertices, indices);
// Create VAO
vao = gl.createVertexArray();
// Bind VAO
gl.bindVertexArray(vao);
// Vertices
const sphereVerticesBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, sphereVerticesBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
// Configure VAO instructions
gl.enableVertexAttribArray(program.aVertexPosition);
gl.vertexAttribPointer(program.aVertexPosition, 3, gl.FLOAT, false, 0, 0);
// Normals
const sphereNormalsBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, sphereNormalsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);
// Configure VAO instructions
gl.enableVertexAttribArray(program.aVertexNormal);
gl.vertexAttribPointer(program.aVertexNormal, 3, gl.FLOAT, false, 0, 0);
// Indices
sphereIndicesBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, sphereIndicesBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
// Clean
gl.bindVertexArray(null);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
function onMouseDown(event) {
dragging = true;
currentX = event.clientX;
currentY = event.clientY;
}
function onMouseMove(event) {
if (dragging) {
lastX = currentX;
lastY = currentY;
currentX = event.clientX;
currentY = event.clientY;
const dx = currentX - lastX;
rotateAxis = [0, 1, 0];
angle += dx;
}
}
function onMouseUp() {
dragging = false;
}
function draw() {
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
mat4.perspective(projectionMatrix, 45, gl.canvas.width / gl.canvas.height, 0.1, 10000);
mat4.identity(modelViewMatrix);
mat4.translate(modelViewMatrix, modelViewMatrix, [0, 0, -5]);
mat4.rotate(modelViewMatrix, modelViewMatrix, angle * Math.PI / 180, [0, 1, 0]);
mat4.copy(normalMatrix, modelViewMatrix);
mat4.invert(normalMatrix, normalMatrix);
mat4.transpose(normalMatrix, normalMatrix);
gl.uniformMatrix4fv(program.uNormalMatrix, false, normalMatrix);
gl.uniformMatrix4fv(program.uModelViewMatrix, false, modelViewMatrix);
gl.uniformMatrix4fv(program.uProjectionMatrix, false, projectionMatrix);
// We will start using the `try/catch` to capture any errors from our `draw` calls
try {
gl.bindVertexArray(vao);
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
} catch (error) {
console.error(error);
}
}
function render() {
requestAnimationFrame(render);
draw();
}
function init() {
initProgram();
initBuffers();
initLights();
render();
}
init();
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js"></script>
<!-- vertex Shader -->
<script id="vertex-shader" type="x-shader/x-vertex">
#version 300 es
precision mediump float;
uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;
uniform mat4 uNormalMatrix;
in vec3 aVertexPosition;
in vec3 aVertexNormal;
out vec3 vNormal;
out vec3 vEyeVector;
void main(void) {
vec4 vertex = uModelViewMatrix * vec4(aVertexPosition, 1.0);
// Set varyings to be used inside of fragment shader
vNormal = vec3(uNormalMatrix * vec4(aVertexNormal, 1.0));
vEyeVector = -vec3(vertex.xyz);
gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0);
}
</script>
<!-- fragment Shader -->
<script id="fragment-shader" type="x-shader/x-fragment">
#version 300 es
precision mediump float;
uniform float uShininess;
uniform vec3 uLightDirection;
uniform vec4 uLightAmbient;
uniform vec4 uLightDiffuse;
uniform vec4 uLightSpecular;
uniform vec4 uMaterialAmbient;
uniform vec4 uMaterialDiffuse;
uniform vec4 uMaterialSpecular;
in vec3 vNormal;
in vec3 vEyeVector;
out vec4 fragColor;
void main(void) {
// Normalized light direction
vec3 L = normalize(uLightDirection);
// Normalized normal
vec3 N = normalize(vNormal);
float lambertTerm = dot(N, -L);
// Ambient
vec4 Ia = uLightAmbient * uMaterialAmbient;
// Diffuse
vec4 Id = vec4(0.0, 0.0, 0.0, 1.0);
// Specular
vec4 Is = vec4(0.0, 0.0, 0.0, 1.0);
if (lambertTerm > 0.0) {
Id = uLightDiffuse * uMaterialDiffuse * lambertTerm;
vec3 E = normalize(vEyeVector);
vec3 R = reflect(L, N);
float specular = pow( max(dot(R, E), 0.0), uShininess);
Is = uLightSpecular * uMaterialSpecular * specular;
}
// Final fargment color takes into account all light values that
// were computed within the fragment shader
fragColor = vec4(vec3(Ia + Id + Is), 1.0);
}
</script>
<canvas id="webgl-canvas"> </canvas>
/**
* A class creating buffers for a textured box to render it with WebGL
*/
class RasterTextureBox {
/**
* Creates all WebGL buffers for the textured box
* 6 ------- 7
* / | / |
* 3 ------- 2 |
* | | | |
* | 5 -----|- 4
* | / | /
* 0 ------- 1
* looking in negative z axis direction
* #param {WebGLContext} gl - The canvas' context
* #param {Vector} minPoint - The minimal x,y,z of the box
* #param {Vector} maxPoint - The maximal x,y,z of the box
*/
constructor(gl, minPoint, maxPoint, texture) {
this.gl = gl;
const mi = minPoint;
const ma = maxPoint;
let vertices = [
// front
mi.x, mi.y, ma.z, ma.x, mi.y, ma.z, ma.x, ma.y, ma.z,
ma.x, ma.y, ma.z, mi.x, ma.y, ma.z, mi.x, mi.y, ma.z,
// back
ma.x, mi.y, mi.z, mi.x, mi.y, mi.z, mi.x, ma.y, mi.z,
mi.x, ma.y, mi.z, ma.x, ma.y, mi.z, ma.x, mi.y, mi.z,
// right
ma.x, mi.y, ma.z, ma.x, mi.y, mi.z, ma.x, ma.y, mi.z,
ma.x, ma.y, mi.z, ma.x, ma.y, ma.z, ma.x, mi.y, ma.z,
// top
mi.x, ma.y, ma.z, ma.x, ma.y, ma.z, ma.x, ma.y, mi.z,
ma.x, ma.y, mi.z, mi.x, ma.y, mi.z, mi.x, ma.y, ma.z,
// left
mi.x, mi.y, mi.z, mi.x, mi.y, ma.z, mi.x, ma.y, ma.z,
mi.x, ma.y, ma.z, mi.x, ma.y, mi.z, mi.x, mi.y, mi.z,
// bottom
mi.x, mi.y, mi.z, ma.x, mi.y, mi.z, ma.x, mi.y, ma.z,
ma.x, mi.y, ma.z, mi.x, mi.y, ma.z, mi.x, mi.y, mi.z
];
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
this.vertexBuffer = vertexBuffer;
this.elements = vertices.length / 3;
let cubeTexture = gl.createTexture();
let cubeImage = new Image();
cubeImage.onload = function () {
gl.bindTexture(gl.TEXTURE_2D, cubeTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, cubeImage);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.bindTexture(gl.TEXTURE_2D, null);
}
cubeImage.src = texture;
this.texBuffer = cubeTexture;
let uv = [
// front
0, 0, 1, 0, 1, 1,
1, 1, 0, 1, 0, 0,
// back
0, 0, 1, 0, 1, 1,
1, 1, 0, 1, 0, 0,
// right
0, 0, 1, 0, 1, 1,
1, 1, 0, 1, 0, 0,
// top
0, 0, 1, 0, 1, 1,
1, 1, 0, 1, 0, 0,
// left
0, 0, 1, 0, 1, 1,
1, 1, 0, 1, 0, 0,
// bottom
0, 0, 1, 0, 1, 1,
1, 1, 0, 1, 0, 0,
];
let uvBuffer = this.gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer);
gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(uv),
gl.STATIC_DRAW);
this.texCoords = uvBuffer;
}
render(shader) {
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
const positionLocation = shader.getAttributeLocation("a_position");
this.gl.enableVertexAttribArray(positionLocation);
this.gl.vertexAttribPointer(positionLocation, 3, this.gl.FLOAT, false, 0, 0);
// Bind the texture coordinates in this.texCoords
// to their attribute in the shader
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoords);
const texCoordLocation = shader.getAttributeLocation("a_texCoord");
this.gl.enableVertexAttribArray(texCoordLocation);
this.gl.vertexAttribPointer(texCoordLocation, 2, this.gl.FLOAT, false, 0, 0);
this.gl.activeTexture(gl.TEXTURE0);
this.gl.bindTexture(gl.TEXTURE_2D, this.texBuffer);
shader.getUniformInt("sampler").set(0);
this.gl.drawArrays(this.gl.TRIANGLES, 0, this.elements);
this.gl.disableVertexAttribArray(positionLocation);
//disable texture vertex attrib array
this.gl.disableVertexAttribArray(texCoordLocation);
}
}
/**
* Class representing a 4x4 Matrix
*/
class Matrix {
constructor(mat) {
this.data = new Float32Array(16);
for (let row = 0; row < 4; row++) {
for (let col = 0; col < 4; col++) {
this.data[row * 4 + col] = mat[col * 4 + row];
}
}
}
getVal(row, col) {
return this.data[col * 4 + row];
}
setVal(row, col, val) {
this.data[col * 4 + row] = val;
}
static translation(translation) {
let m = Matrix.identity();
m.setVal(0, 3, translation.x);
m.setVal(1, 3, translation.y);
m.setVal(2, 3, translation.z);
return m;
}
static rotation(axis, angle) {
let m = Matrix.identity()
let sin = Math.sin(angle);
let cos = Math.cos(angle);
if (axis.x != 0) {
m.setVal(1, 1, cos);
m.setVal(1, 2, -sin);
m.setVal(2, 1, sin);
m.setVal(2, 2, cos);
} else if (axis.y != 0) {
m.setVal(0, 0, cos);
m.setVal(0, 2, sin);
m.setVal(2, 0, -sin);
m.setVal(2, 2, cos);
} else {
m.setVal(0, 0, cos);
m.setVal(0, 1, -sin);
m.setVal(1, 0, sin);
m.setVal(1, 1, cos);
}
return m;
}
static scaling(scale) {
let m = Matrix.identity();
m.setVal(0, 0, scale.x);
m.setVal(1, 1, scale.y);
m.setVal(2, 2, scale.z);
return m;
}
/**
* Constructs a lookat matrix
* #param {Vector} eye - The position of the viewer
* #param {Vector} center - The position to look at
* #param {Vector} up - The up direction
* #return {Matrix} The resulting lookat matrix
*/
static lookat(eye, center, up) {
let fBig = center.sub(eye);
// Vom Eye zum Center Punkt
let f = fBig.normalised();
// UP-Vektor
let upNorm = up.normalised();
// Kreuzprodukt
let s = f.cross(upNorm);
let u = s.normalised().cross(f);
// s, u und f sind die Vektoren des Kamerakoordinatensystems
// Lookat Matrix, 3x3 betrifft Rotation und Skalierung
let mat = new Matrix([
s.x, s.y, s.z, 0,
u.x, u.y, u.z, 0, -f.x, -f.y, -f.z, 0,
0, 0, 0, 1
]);
// Noch weitere Berechnungen? Translation
let trans = Matrix.translation(eye.mul(-1));
mat = mat.mul(trans);
return mat;
}
static frustum(left, right, bottom, top, near, far) {
// TODO [exercise 9]
const n2 = 2 * near;
const rpl = right + left;
const rml = right - left;
const tpb = top + bottom;
const tmb = top - bottom;
const fpn = far + near;
const fmn = far - near;
const n2f = n2 * far;
return new Matrix([
n2 / rml, 0, rpl / rml, 0,
0, n2 / tmb, tpb / tmb, 0,
0, 0, -fpn / fmn, -n2f / fmn,
0, 0, -1, 0
]);
}
static perspective(fovy, aspect, near, far) {
// frustum Methode verwenden. Foliensatz 10
const top = near * Math.tan((Math.PI / 180) * (fovy / 2));
const bottom = -top;
const right = top * aspect;
const left = -right;
return Matrix.frustum(left, right, bottom, top, near, far);
}
/**
* Returns the identity matrix
*/
static identity() {
return new Matrix([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]);
}
mul(other) {
if (other instanceof Matrix) {
// [exercise 7]
let m = Matrix.identity();
for (let row = 0; row < 4; row++) {
for (let col = 0; col < 4; col++) {
let sum = 0;
for (let i = 0; i < 4; i++) {
sum += this.getVal(row, i) * other.getVal(i, col);
}
m.setVal(row, col, sum);
}
}
return m;
} else {
let v = [0, 0, 0, 0];
for (let row = 0; row < 4; row++) {
for (let i = 0; i < 4; i++) {
v[row] += this.getVal(row, i) * other.valueOf()[i];
}
}
return new Vector(v[0], v[1], v[2], v[3]);
}
}
transpose() {
let m = Matrix.identity();
for (let row = 0; row < 4; row++) {
for (let col = 0; col < 4; col++) {
m.setVal(row, col, this.getVal(col, row));
}
}
return m;
}
invert() {
let mat = this.data;
let dst = new Float32Array(16); //ret.getValues();
let tmp = new Float32Array(12);
/* temparray for pairs */
let src = new Float32Array(16); //new float[16];
/* array of transpose source matrix */
let det;
for (let i = 0; i < 4; i++) {
src[i] = mat[i * 4];
src[i + 4] = mat[i * 4 + 1];
src[i + 8] = mat[i * 4 + 2];
src[i + 12] = mat[i * 4 + 3];
}
tmp[0] = src[10] * src[15];
tmp[1] = src[11] * src[14];
tmp[2] = src[9] * src[15];
tmp[3] = src[11] * src[13];
tmp[4] = src[9] * src[14];
tmp[5] = src[10] * src[13];
tmp[6] = src[8] * src[15];
tmp[7] = src[11] * src[12];
tmp[8] = src[8] * src[14];
tmp[9] = src[10] * src[12];
tmp[10] = src[8] * src[13];
tmp[11] = src[9] * src[12];
dst[0] = tmp[0] * src[5] + tmp[3] * src[6] + tmp[4] * src[7];
dst[0] -= tmp[1] * src[5] + tmp[2] * src[6] + tmp[5] * src[7];
dst[1] = tmp[1] * src[4] + tmp[6] * src[6] + tmp[9] * src[7];
dst[1] -= tmp[0] * src[4] + tmp[7] * src[6] + tmp[8] * src[7];
dst[2] = tmp[2] * src[4] + tmp[7] * src[5] + tmp[10] * src[7];
dst[2] -= tmp[3] * src[4] + tmp[6] * src[5] + tmp[11] * src[7];
dst[3] = tmp[5] * src[4] + tmp[8] * src[5] + tmp[11] * src[6];
dst[3] -= tmp[4] * src[4] + tmp[9] * src[5] + tmp[10] * src[6];
dst[4] = tmp[1] * src[1] + tmp[2] * src[2] + tmp[5] * src[3];
dst[4] -= tmp[0] * src[1] + tmp[3] * src[2] + tmp[4] * src[3];
dst[5] = tmp[0] * src[0] + tmp[7] * src[2] + tmp[8] * src[3];
dst[5] -= tmp[1] * src[0] + tmp[6] * src[2] + tmp[9] * src[3];
dst[6] = tmp[3] * src[0] + tmp[6] * src[1] + tmp[11] * src[3];
dst[6] -= tmp[2] * src[0] + tmp[7] * src[1] + tmp[10] * src[3];
dst[7] = tmp[4] * src[0] + tmp[9] * src[1] + tmp[10] * src[2];
dst[7] -= tmp[5] * src[0] + tmp[8] * src[1] + tmp[11] * src[2];
tmp[0] = src[2] * src[7];
tmp[1] = src[3] * src[6];
tmp[2] = src[1] * src[7];
tmp[3] = src[3] * src[5];
tmp[4] = src[1] * src[6];
tmp[5] = src[2] * src[5];
tmp[6] = src[0] * src[7];
tmp[7] = src[3] * src[4];
tmp[8] = src[0] * src[6];
tmp[9] = src[2] * src[4];
tmp[10] = src[0] * src[5];
tmp[11] = src[1] * src[4];
dst[8] = tmp[0] * src[13] + tmp[3] * src[14] + tmp[4] * src[15];
dst[8] -= tmp[1] * src[13] + tmp[2] * src[14] + tmp[5] * src[15];
dst[9] = tmp[1] * src[12] + tmp[6] * src[14] + tmp[9] * src[15];
dst[9] -= tmp[0] * src[12] + tmp[7] * src[14] + tmp[8] * src[15];
dst[10] = tmp[2] * src[12] + tmp[7] * src[13] + tmp[10] * src[15];
dst[10] -= tmp[3] * src[12] + tmp[6] * src[13] + tmp[11] * src[15];
dst[11] = tmp[5] * src[12] + tmp[8] * src[13] + tmp[11] * src[14];
dst[11] -= tmp[4] * src[12] + tmp[9] * src[13] + tmp[10] * src[14];
dst[12] = tmp[2] * src[10] + tmp[5] * src[11] + tmp[1] * src[9];
dst[12] -= tmp[4] * src[11] + tmp[0] * src[9] + tmp[3] * src[10];
dst[13] = tmp[8] * src[11] + tmp[0] * src[8] + tmp[7] * src[10];
dst[13] -= tmp[6] * src[10] + tmp[9] * src[11] + tmp[1] * src[8];
dst[14] = tmp[6] * src[9] + tmp[11] * src[11] + tmp[3] * src[8];
dst[14] -= tmp[10] * src[11] + tmp[2] * src[8] + tmp[7] * src[9];
dst[15] = tmp[10] * src[10] + tmp[4] * src[8] + tmp[9] * src[9];
dst[15] -= tmp[8] * src[9] + tmp[11] * src[10] + tmp[5] * src[8];
det = src[0] * dst[0] + src[1] * dst[1] + src[2] * dst[2] + src[3] * dst[3];
if (det == 0.0) {
throw new Error("singular matrix is not invertible");
}
/* calculate matrix inverse */
det = 1 / det;
for (let j = 0; j < 16; j++) {
dst[j] *= det;
}
let ret = Matrix.identity();
ret.data = dst;
return ret;
}
}
/**
* Class representing a vector in 4D space
*/
class Vector {
/**
* Create a vector
* #param {number} x - The x component
* #param {number} y - The y component
* #param {number} z - The z component
* #param {number} w - The w component
* #return {number} The resulting vector
*/
constructor(x, y, z, w) {
this.data = [x, y, z, w];
}
//has getter and setter
add(other) {
return new Vector(
this.x + other.x,
this.y + other.y,
this.z + other.z,
this.w + other.w
);
}
sub(other) {
return new Vector(
this.x - other.x,
this.y - other.y,
this.z - other.z,
this.w - other.w
);
}
mul(other) {
return new Vector(
this.x * other,
this.y * other,
this.z * other,
this.w
);
}
div(other) {
return new Vector(
this.x / other,
this.y / other,
this.z / other,
this.w
);
}
dot(other) {
if (other instanceof Vector) {
return this.x * other.x + this.y * other.y + this.z * other.z;
} else {
throw new Error("Dot product only works with vectors!");
}
}
cross(other) {
if (other instanceof Vector) {
return new Vector(
this.y * other.z - this.z * other.y,
this.z * other.x - this.x * other.z,
this.x * other.y - this.y * other.x,
0
);
} else {
throw new Error("Dot product only works with vectors!");
}
}
valueOf() {
return this.data;
}
normalised() {
const l = this.length;
return this.div(l);
}
equals(other) {
return (
Math.abs(this.x - other.x) <= Number.EPSILON &&
Math.abs(this.y - other.y) <= Number.EPSILON &&
Math.abs(this.z - other.z) <= Number.EPSILON &&
((!this.w && !other.w) || Math.abs(this.w - other.w) <= Number.EPSILON)
);
}
get length() {
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
}
}
/**
* Class representing a Node in a Scenegraph
*/
class Node {
/**
* Accepts a visitor according to the visitor pattern
* #param {Visitor} visitor - The visitor
*/
accept(visitor) { }
}
/**
* Class representing a GroupNode in the Scenegraph.
* A GroupNode holds a transformation and is able
* to have child nodes attached to it.
* #extends Node
*/
class GroupNode extends Node {
/**
* Constructor
* #param {Matrix} mat - A matrix describing the node's transformation
*/
constructor(mat) {
super();
this.matrix = mat;
// TODO [exercise 8]
this.children = [];
}
/**
* Accepts a visitor according to the visitor pattern
* #param {Visitor} visitor - The visitor
*/
accept(visitor) {
// TODO [exercise 8]
visitor.visitGroupNode(this);
}
/**
* Adds a child node
* #param {Node} childNode - The child node to add
*/
add(childNode) {
// TODO [exercise 8]
this.children.push(childNode);
}
}
/**
* Class representing a Textured Axis Aligned Box in the Scenegraph
* #extends Node
*/
class TextureBoxNode extends Node {
constructor(minPoint, maxPoint, texture) {
super();
this.minPoint = minPoint;
this.maxPoint = maxPoint;
this.texture = texture;
}
accept(visitor) {
// TODO [exercise 8]
visitor.visitTextureBoxNode(this);
}
}
//Texture Fragment Shader
precision mediump float;
uniform sampler2D sampler;
varying vec2 v_texCoord;
void main( void ) {
//gl_FragColor = vec4( 0.0, 0.0, 0.5, 1.0 );
// Read fragment color from texture
// TODO [exercise 9]
gl_FragColor = texture2D(sampler, vec2(v_texCoord.s, v_texCoord.t));
}
//Texture Vertex Shader
attribute vec3 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
uniform mat4 M;
uniform mat4 V;
uniform mat4 P;
void main() {
gl_Position = P * V * M * vec4( a_position, 1.0 );
v_texCoord = a_texCoord;
}
// Phong Vertex Shader
attribute vec3 a_position;
attribute vec3 a_normal;
// Pass color as attribute and forward it
// to the fragment shader
attribute vec4 a_color;
uniform mat4 M;
uniform mat4 V;
uniform mat4 P;
uniform mat4 N; // normal matrix
varying vec3 v_normal;
// Pass the vertex position in view space
// to the fragment shader
// TODO [exercise 9]
varying vec4 v_position;
varying vec4 v_color;
void main() {
gl_Position = P * V * M * vec4( a_position, 1.0 );
// Pass the color and transformed vertex position through
v_position = gl_Position;
v_color = a_color;
v_normal = (N * vec4(a_normal, 0)).xyz;
}
//Phong Fragment Shader
//precision mediump float;
// TODO [exercise 5]
//void main( void ) {
//gl_FragColor = vec4( 0.0, 0.0, 0.5, 1.0 );
// TODO [exercise 5]
//}
// Wird mindestens einmal pro Pixel ausgefuehrt
precision mediump float;
// TODO [exercise 5]
varying vec4 v_color;
varying vec4 v_position;
varying vec3 v_normal;
const vec3 lightPos = vec3(0.2,-1.0,-1.0);
const float shininess = 16.0;
const float k_a = 1.0;
const float k_d = 0.6;
const float k_s = 0.3;
// Farbe von Vertex shader durchreichen und Interpolieren
void main( void ) {
// Rot, Gruen, Blau, Alpha
//gl_FragColor = vec4( 0.0, 0.0, 0.5, 1.0 );
// TODO [exercise 5]
vec3 vertPos = vec3(v_position) / v_position.w;
vec3 N = normalize(v_normal);
vec3 L = normalize(lightPos - vertPos);
vec4 L_j = vec4(1,1,1,1);
vec4 diffuse = L_j * max(dot(N, L), 0.0);
vec3 R = reflect(-L, N);
vec3 V = normalize(-vertPos);
float specAngle = max(dot(R, V), 0.0);
vec4 specular = L_j * pow(specAngle, shininess);
vec4 color = vec4(k_a * v_color + k_d * diffuse + k_s * specular);
gl_FragColor = color;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>ICG-11 Animation</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
crossorigin="anonymous">
</head>
<body>
<div class="container text-center">
<h1>ICG Animation</h1>
<hr>
<p>Implement a Rasteriser with WebGL using a Scenegraph.</p>
<canvas id="rasteriser" width="500" height="500"></canvas>
<script src="vector.js"></script>
<script src="raster-texture-box.js"></script>
<script src="matrix.js"></script>
<script src="nodes.js"></script>
<script src="rastervisitor.js"></script>
<script src="shader.js"></script>
<script src="animation-nodes.js"></script>
<script>
const canvas = document.getElementById("rasteriser");
const gl = canvas.getContext("webgl");
// construct scene graph
const sg = new GroupNode(Matrix.scaling(new Vector(0.2, 0.2, 0.2)));
const gn1 = new GroupNode(Matrix.translation(new Vector(1, 1, 0)));
sg.add(gn1);
let gn2 = new GroupNode(Matrix.translation(new Vector(-.7, -0.4, .1)));
sg.add(gn2);
const cube = new TextureBoxNode(
new Vector(-1, -1, -1, 1),
new Vector(1, 1, 1, 1),
'diffuse.png'
);
gn2.add(cube);
// setup for rendering
const setupVisitor = new RasterSetupVisitor(gl);
setupVisitor.setup(sg);
const visitor = new RasterVisitor(gl);
let camera = {
eye: new Vector(-.5, .5, -1, 1),
center: new Vector(0, 0, 0, 1),
up: new Vector(0, 1, 0, 0),
fovy: 60,
aspect: canvas.width / canvas.height,
near: 0.1,
far: 100
};
const phongShader = new Shader(gl,
"phong-vertex-perspective-shader.glsl",
"phong-fragment-shader.glsl"
);
visitor.shader = phongShader;
const textureShader = new Shader(gl,
"texture-vertex-perspective-shader.glsl",
"texture-fragment-shader.glsl"
);
visitor.textureshader = textureShader;
let animationNodes = [
new RotationNode(gn2, new Vector(0, 0, 1))
];
function simulate(deltaT) {
for (animationNode of animationNodes) {
animationNode.simulate(deltaT);
}
}
let lastTimestamp = performance.now();
function animate(timestamp) {
simulate(timestamp - lastTimestamp);
visitor.render(sg, camera);
lastTimestamp = timestamp;
window.requestAnimationFrame(animate);
}
Promise.all(
[textureShader.load(), phongShader.load()]
).then(x =>
window.requestAnimationFrame(animate)
);
</script>
</div>
</body>
</html>
Hey there I`m trying since a while now to add a second texture
to my cube and do some bump mapping. But I am a progam beginner, so its kinda hard for me. All my maths for the matrix and vector are in the same named js.files. I also have to kinds of shaders, the texture and the phong shader. Now everyone says I have to calculate my normals, but how do I do that? And where?
Looking forward for your help!
With a normal map as in the question, Bump mapping can be performed. At bump mapping the normal vector of a fragment is read from a normal map and used for the light calculations.
In general the incident light vector is transformed into texture space. This is the "orientation" of the normal map on the object (fragment). In order to set up a 3x3 orientation matrix that describes the orientation of the map, the tangent vector and the bi-tangent vector as well as the normal vector must be known. If there is no tangent vector and no bi-tangent vector, the vectors can be approximated by the partial derivative of the vertex position and the texture coordinate in the fragment shader.
So at least the texture coordinate and the normal vector attribute are required. In the fragment shader the calculations are done in world space respectively texture space. The vertex shader is straight forward:
precision highp float;
attribute vec3 a_position;
attribute vec3 a_normal;
attribute vec2 a_texCoord;
varying vec3 w_pos;
varying vec3 w_nv;
varying vec2 o_uv;
uniform mat4 P;
uniform mat4 V;
uniform mat4 M;
void main()
{
o_uv = a_texCoord;
w_nv = normalize(mat3(M) * a_normal);
vec4 worldPos = M * vec4(a_position, 1.0);
w_pos = worldPos.xyz;
gl_Position = P * V * worldPos;
}
In the fragment shader, the normal vector is read from the normal map:
vec3 mapN = normalize(texture2D(u_normal_map, o_uv.st).xyz * 2.0 - 1.0);
The light vector is transformed to texture space:
vec3 L = tbn_inv * normalize(u_light_pos - w_pos);
With this vector the light calculations can be performed:
float kd = max(0.0, dot(mapN, L));
To calculate the matrix which transforms form world space to texture space, the partial derivative functions (dFdx, dFdy) are required. This causes that the "OES_standard_derivatives" has to be enabled (or "webgl2" context):
gl = canvas.getContext( "experimental-webgl" );
var standard_derivatives = gl.getExtension("OES_standard_derivatives");
The algorithm to calculate the tangent vector and binormal vector is explained in another answer - How to calculate Tangent and Binormal?.
Final fragment shader:
#extension GL_OES_standard_derivatives : enable
precision mediump float;
varying vec3 w_pos;
varying vec3 w_nv;
varying vec2 o_uv;
uniform vec3 u_light_pos;
uniform sampler2D u_diffuse;
uniform sampler2D u_normal_map;
void main()
{
vec3 N = normalize(w_nv);
vec3 dp1 = dFdx( w_pos );
vec3 dp2 = dFdy( w_pos );
vec2 duv1 = dFdx( o_uv );
vec2 duv2 = dFdy( o_uv );
vec3 dp2perp = cross(dp2, N);
vec3 dp1perp = cross(N, dp1);
vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;
vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;
float invmax = inversesqrt(max(dot(T, T), dot(B, B)));
mat3 tm = mat3(T * invmax, B * invmax, N);
mat3 tbn_inv = mat3(vec3(tm[0].x, tm[1].x, tm[2].x), vec3(tm[0].y, tm[1].y, tm[2].y), vec3(tm[0].z, tm[1].z, tm[2].z));
vec3 L = tbn_inv * normalize(u_light_pos - w_pos);
vec3 mapN = normalize(texture2D(u_normal_map, o_uv.st).xyz * 2.0 - 1.0);
float kd = max(0.0, dot(mapN, L));
vec3 color = texture2D(u_diffuse, o_uv.st).rgb;
vec3 light_col = (0.0 + kd) * color.rgb;
gl_FragColor = vec4(clamp(light_col, 0.0, 1.0), 1.0);
}
And will produce bump mapping like this:
If the tangent vector is know the calculation of tbn_inv matrix can be simplified very much:
mat3 tm = mat3(normalize(w_tv), normalize(cross(w_nv, w_tv)), normalize(w_nv));
mat3 tbn_inv = mat3(vec3(tm[0].x, tm[1].x, tm[2].x), vec3(tm[0].y, tm[1].y, tm[2].y), vec3(tm[0].z, tm[1].z, tm[2].z));
If you want Parallax mapping like in this Example then a displacement map is required, too.
The white areas on this map are pushed "into" the object. The algorithm is described in detail at LearnOpengl - Parallax Mapping.
The idea is that each fragment is associated to a height of the displacement map. This can be imagined as a rectangular pillar standing on the fragment. The view ray is tracked until a displaced fragment is hit.
For a high performance algorithm samples are taken of the displacement texture. When a fragment is identified, then the corresponding fragment of the e normal map and the diffuse texture is read. This gives a 3 dimensional look of the geometry. Note this algorithm is bot able to handle silhouettes.
Final fragment shader with steep parallax mapping:
#extension GL_OES_standard_derivatives : enable
precision mediump float;
varying vec3 w_pos;
varying vec3 w_nv;
varying vec2 o_uv;
uniform float u_height_scale;
uniform vec3 u_light_pos;
uniform vec3 u_view_pos;
uniform sampler2D u_diffuse;
uniform sampler2D u_normal_map;
uniform sampler2D u_displacement_map;
vec2 ParallaxMapping (vec2 texCoord, vec3 viewDir)
{
float numLayers = 32.0 - 31.0 * abs(dot(vec3(0.0, 0.0, 1.0), viewDir));
float layerDepth = 1.0 / numLayers;
vec2 P = viewDir.xy / viewDir.z * u_height_scale;
vec2 deltaTexCoords = P / numLayers;
vec2 currentTexCoords = texCoord;
float currentLayerDepth = 0.0;
float currentDepthMapValue = texture2D(u_displacement_map, currentTexCoords).r;
for (int i=0; i<32; ++ i)
{
if (currentLayerDepth >= currentDepthMapValue)
break;
currentTexCoords -= deltaTexCoords;
currentDepthMapValue = texture2D(u_displacement_map, currentTexCoords).r;
currentLayerDepth += layerDepth;
}
vec2 prevTexCoords = currentTexCoords + deltaTexCoords;
float afterDepth = currentDepthMapValue - currentLayerDepth;
float beforeDepth = texture2D(u_displacement_map, prevTexCoords).r - currentLayerDepth + layerDepth;
float weight = afterDepth / (afterDepth - beforeDepth);
return prevTexCoords * weight + currentTexCoords * (1.0 - weight);
}
void main()
{
vec3 N = normalize(w_nv);
vec3 dp1 = dFdx( w_pos );
vec3 dp2 = dFdy( w_pos );
vec2 duv1 = dFdx( o_uv );
vec2 duv2 = dFdy( o_uv );
vec3 dp2perp = cross(dp2, N);
vec3 dp1perp = cross(N, dp1);
vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;
vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;
float invmax = inversesqrt(max(dot(T, T), dot(B, B)));
mat3 tm = mat3(T * invmax, B * invmax, N);
mat3 tbn_inv = mat3(vec3(tm[0].x, tm[1].x, tm[2].x), vec3(tm[0].y, tm[1].y, tm[2].y), vec3(tm[0].z, tm[1].z, tm[2].z));
vec3 view_dir = tbn_inv * normalize(w_pos - u_view_pos);
vec2 uv = ParallaxMapping(o_uv, view_dir);
if (uv.x > 1.0 || uv.y > 1.0 || uv.x < 0.0 || uv.y < 0.0)
discard;
vec3 L = tbn_inv * normalize(u_light_pos - w_pos);
vec3 mapN = normalize(texture2D(u_normal_map, uv.st).xyz * 2.0 - 1.0);
float kd = max(0.0, dot(mapN, L));
vec3 color = texture2D(u_diffuse, uv.st).rgb;
vec3 light_col = (0.1 + kd) * color.rgb;
gl_FragColor = vec4(clamp(light_col, 0.0, 1.0), 1.0);
}
The result is much more impressive:
(function loadscene() {
var gl, progDraw, vp_size;
var bufCube = {};
var diffuse_tex = 1;
var height_tex = 2;
var normal_tex = 3;
function render(deltaMS){
var height_scale = 0.3 * document.getElementById("height").value / 100.0;
// setup view projection and model
vp_size = [canvas.width, canvas.height];
camera.Update( vp_size );
var prjMat = camera.Perspective();
var viewMat = camera.LookAt();
var modelMat = camera.AutoModelMatrix();
gl.viewport( 0, 0, vp_size[0], vp_size[1] );
gl.enable( gl.DEPTH_TEST );
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
gl.frontFace(gl.CCW)
gl.cullFace(gl.BACK)
gl.enable(gl.CULL_FACE)
// set up draw shader
ShProg.Use( progDraw );
ShProg.SetF3( progDraw, "u_view_pos", camera.pos )
ShProg.SetF3( progDraw, "u_light_pos", [0.0, 5.0, 5.0] )
ShProg.SetF1( progDraw, "u_height_scale", height_scale );
ShProg.SetI1( progDraw, "u_diffuse", diffuse_tex );
ShProg.SetI1( progDraw, "u_displacement_map", height_tex );
ShProg.SetI1( progDraw, "u_normal_map", normal_tex );
ShProg.SetM44( progDraw, "P", prjMat );
ShProg.SetM44( progDraw, "V", viewMat );
ShProg.SetM44( progDraw, "M", modelMat );
// draw scene
VertexBuffer.Draw( bufCube );
requestAnimationFrame(render);
}
function initScene() {
canvas = document.getElementById( "canvas");
gl = canvas.getContext( "experimental-webgl" );
var standard_derivatives = gl.getExtension("OES_standard_derivatives"); // dFdx, dFdy
if (!standard_derivatives)
alert('no standard derivatives support (no dFdx, dFdy)');
//gl = canvas.getContext( "webgl2" );
if ( !gl )
return null;
progDraw = ShProg.Create(
[ { source : "draw-shader-vs", stage : gl.VERTEX_SHADER },
{ source : "draw-shader-fs", stage : gl.FRAGMENT_SHADER }
] );
if ( !progDraw.progObj )
return null;
progDraw.inPos = ShProg.AttrI( progDraw, "a_position" );
progDraw.inNV = ShProg.AttrI( progDraw, "a_normal" );
progDraw.inUV = ShProg.AttrI( progDraw, "a_texCoord" );
// create cube
let Pos = [ -1,-1,1, 1,-1,1, 1,1,1, -1,1,1, -1,-1,-1, 1,-1,-1, 1,1,-1, -1,1,-1 ];
let Col = [ 1,0,0, 1,0.5,0, 1,0,1, 1,1,0, 0,1,0, 0, 0, 1 ];
let NV = [ 0,0,1, 1,0,0, 0,0,-1, -1,0,0, 0,1,0, 0,-1,0 ];
let TV = [ 1,0,0, 0,0,-1, -1,0,0, 0,0,1, 1,0,0, -1,0,0 ];
var cubeHlpInx = [ 0,1,2,3, 1,5,6,2, 5,4,7,6, 4,0,3,7, 3,2,6,7, 1,0,4,5 ];
var cubePosData = [];
for ( var i = 0; i < cubeHlpInx.length; ++ i ) cubePosData.push(Pos[cubeHlpInx[i]*3], Pos[cubeHlpInx[i]*3+1], Pos[cubeHlpInx[i]*3+2] );
var cubeNVData = [];
for ( var i1 = 0; i1 < 6; ++ i1 ) {
for ( i2 = 0; i2 < 4; ++ i2 ) cubeNVData.push(NV[i1*3], NV[i1*3+1], NV[i1*3+2]);
}
var cubeTVData = [];
for ( var i1 = 0; i1 < 6; ++ i1 ) {
for ( i2 = 0; i2 < 4; ++ i2 ) cubeTVData.push(TV[i1*3], TV[i1*3+1], TV[i1*3+2]);
}
var cubeColData = [];
for ( var is = 0; is < 6; ++ is ) {
for ( var ip = 0; ip < 4; ++ ip ) cubeColData.push(Col[is*3], Col[is*3+1], Col[is*3+2]);
}
var cubeTexData = []
for ( var i = 0; i < 6; ++ i ) cubeTexData.push( 0, 0, 1, 0, 1, 1, 0, 1 );
var cubeInxData = [];
for ( var i = 0; i < cubeHlpInx.length; i += 4 ) cubeInxData.push( i, i+1, i+2, i, i+2, i+3 );
bufCube = VertexBuffer.Create(
[ { data : cubePosData, attrSize : 3, attrLoc : progDraw.inPos },
{ data : cubeNVData, attrSize : 3, attrLoc : progDraw.inNV },
//{ data : cubeTVData, attrSize : 3, attrLoc : progDraw.inTV },
{ data : cubeTexData, attrSize : 2, attrLoc : progDraw.inUV },
//{ data : cubeColData, attrSize : 3, attrLoc : progDraw.inCol },
],
cubeInxData, gl.TRIANGLES );
Texture.LoadTexture2D( diffuse_tex, "https://raw.githubusercontent.com/Rabbid76/graphics-snippets/master/resource/texture/woodtiles.jpg" );
Texture.LoadTexture2D( height_tex, "https://raw.githubusercontent.com/Rabbid76/graphics-snippets/master/resource/texture/toy_box_disp.png" );
Texture.LoadTexture2D( normal_tex, "https://raw.githubusercontent.com/Rabbid76/graphics-snippets/master/resource/texture/toy_box_normal.png" );
camera = new Camera( [0, 3, 0], [0, 0, 0], [0, 0, 1], 90, vp_size, 0.5, 100 );
window.onresize = resize;
resize();
requestAnimationFrame(render);
}
function resize() {
//vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight];
vp_size = [window.innerWidth, window.innerHeight];
//vp_size = [256, 256];
canvas.width = vp_size[0];
canvas.height = vp_size[1];
}
function Fract( val ) {
return val - Math.trunc( val );
}
function CalcAng( deltaTime, interval ) {
return Fract( deltaTime / (1000*interval) ) * 2.0 * Math.PI;
}
function CalcMove( deltaTime, interval, range ) {
var pos = self.Fract( deltaTime / (1000*interval) ) * 2.0
var pos = pos < 1.0 ? pos : (2.0-pos)
return range[0] + (range[1] - range[0]) * pos;
}
function IdentM44() {
return [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ];
};
function RotateAxis(matA, angRad, axis) {
var aMap = [ [1, 2], [2, 0], [0, 1] ];
var a0 = aMap[axis][0], a1 = aMap[axis][1];
var sinAng = Math.sin(angRad), cosAng = Math.cos(angRad);
var matB = matA.slice(0);
for ( var i = 0; i < 3; ++ i ) {
matB[a0*4+i] = matA[a0*4+i] * cosAng + matA[a1*4+i] * sinAng;
matB[a1*4+i] = matA[a0*4+i] * -sinAng + matA[a1*4+i] * cosAng;
}
return matB;
}
function Rotate(matA, angRad, axis) {
var s = Math.sin(angRad), c = Math.cos(angRad);
var x = axis[0], y = axis[1], z = axis[2];
matB = [
x*x*(1-c)+c, x*y*(1-c)-z*s, x*z*(1-c)+y*s, 0,
y*x*(1-c)+z*s, y*y*(1-c)+c, y*z*(1-c)-x*s, 0,
z*x*(1-c)-y*s, z*y*(1-c)+x*s, z*z*(1-c)+c, 0,
0, 0, 0, 1 ];
return Multiply(matA, matB);
}
function Multiply(matA, matB) {
matC = IdentM44();
for (var i0=0; i0<4; ++i0 )
for (var i1=0; i1<4; ++i1 )
matC[i0*4+i1] = matB[i0*4+0] * matA[0*4+i1] + matB[i0*4+1] * matA[1*4+i1] + matB[i0*4+2] * matA[2*4+i1] + matB[i0*4+3] * matA[3*4+i1]
return matC;
}
function Cross( a, b ) { return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0], 0.0 ]; }
function Dot( a, b ) { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; }
function Normalize( v ) {
var len = Math.sqrt( v[0] * v[0] + v[1] * v[1] + v[2] * v[2] );
return [ v[0] / len, v[1] / len, v[2] / len ];
}
Camera = function( pos, target, up, fov_y, vp, near, far ) {
this.Time = function() { return Date.now(); }
this.pos = pos;
this.target = target;
this.up = up;
this.fov_y = fov_y;
this.vp = vp;
this.near = near;
this.far = far;
this.orbit_mat = this.current_orbit_mat = this.model_mat = this.current_model_mat = IdentM44();
this.mouse_drag = this.auto_spin = false;
this.auto_rotate = true;
this.mouse_start = [0, 0];
this.mouse_drag_axis = [0, 0, 0];
this.mouse_drag_angle = 0;
this.mouse_drag_time = 0;
this.drag_start_T = this.rotate_start_T = this.Time();
this.Ortho = function() {
var fn = this.far + this.near;
var f_n = this.far - this.near;
var w = this.vp[0];
var h = this.vp[1];
return [
2/w, 0, 0, 0,
0, 2/h, 0, 0,
0, 0, -2/f_n, 0,
0, 0, -fn/f_n, 1 ];
};
this.Perspective = function() {
var n = this.near;
var f = this.far;
var fn = f + n;
var f_n = f - n;
var r = this.vp[0] / this.vp[1];
var t = 1 / Math.tan( Math.PI * this.fov_y / 360 );
return [
t/r, 0, 0, 0,
0, t, 0, 0,
0, 0, -fn/f_n, -1,
0, 0, -2*f*n/f_n, 0 ];
};
this.LookAt = function() {
var mz = Normalize( [ this.pos[0]-this.target[0], this.pos[1]-this.target[1], this.pos[2]-this.target[2] ] );
var mx = Normalize( Cross( this.up, mz ) );
var my = Normalize( Cross( mz, mx ) );
var tx = Dot( mx, this.pos );
var ty = Dot( my, this.pos );
var tz = Dot( [-mz[0], -mz[1], -mz[2]], this.pos );
return [mx[0], my[0], mz[0], 0, mx[1], my[1], mz[1], 0, mx[2], my[2], mz[2], 0, tx, ty, tz, 1];
};
this.AutoModelMatrix = function() {
return this.auto_rotate ? Multiply(this.current_model_mat, this.model_mat) : this.model_mat;
};
this.Update = function(vp_size) {
if (vp_size)
this.vp = vp_size;
var current_T = this.Time();
this.current_model_mat = IdentM44()
var auto_angle_x = Fract( (current_T - this.rotate_start_T) / 13000.0 ) * 2.0 * Math.PI;
var auto_angle_y = Fract( (current_T - this.rotate_start_T) / 17000.0 ) * 2.0 * Math.PI;
this.current_model_mat = RotateAxis( this.current_model_mat, auto_angle_x, 0 );
this.current_model_mat = RotateAxis( this.current_model_mat, auto_angle_y, 1 );
};
}
var Texture = {};
Texture.HandleLoadedTexture2D = function( texture, flipY ) {
gl.activeTexture( gl.TEXTURE0 + texture.unit );
gl.bindTexture( gl.TEXTURE_2D, texture.obj );
gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, flipY != undefined && flipY == true );
gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT );
return texture;
}
Texture.LoadTexture2D = function( unit, name ) {
var texture = {};
texture.obj = gl.createTexture();
texture.unit = unit;
texture.image = new Image();
texture.image.setAttribute('crossorigin', 'anonymous');
texture.image.onload = function () {
Texture.HandleLoadedTexture2D( texture, false )
}
texture.image.src = name;
return texture;
}
var ShProg = {
Create: function (shaderList) {
var shaderObjs = [];
for (var i_sh = 0; i_sh < shaderList.length; ++i_sh) {
var shderObj = this.Compile(shaderList[i_sh].source, shaderList[i_sh].stage);
if (shderObj) shaderObjs.push(shderObj);
}
var prog = {}
prog.progObj = this.Link(shaderObjs)
if (prog.progObj) {
prog.attrInx = {};
var noOfAttributes = gl.getProgramParameter(prog.progObj, gl.ACTIVE_ATTRIBUTES);
for (var i_n = 0; i_n < noOfAttributes; ++i_n) {
var name = gl.getActiveAttrib(prog.progObj, i_n).name;
prog.attrInx[name] = gl.getAttribLocation(prog.progObj, name);
}
prog.uniLoc = {};
var noOfUniforms = gl.getProgramParameter(prog.progObj, gl.ACTIVE_UNIFORMS);
for (var i_n = 0; i_n < noOfUniforms; ++i_n) {
var name = gl.getActiveUniform(prog.progObj, i_n).name;
prog.uniLoc[name] = gl.getUniformLocation(prog.progObj, name);
}
}
return prog;
},
AttrI: function (prog, name) { return prog.attrInx[name]; },
UniformL: function (prog, name) { return prog.uniLoc[name]; },
Use: function (prog) { gl.useProgram(prog.progObj); },
SetI1: function (prog, name, val) { if (prog.uniLoc[name]) gl.uniform1i(prog.uniLoc[name], val); },
SetF1: function (prog, name, val) { if (prog.uniLoc[name]) gl.uniform1f(prog.uniLoc[name], val); },
SetF2: function (prog, name, arr) { if (prog.uniLoc[name]) gl.uniform2fv(prog.uniLoc[name], arr); },
SetF3: function (prog, name, arr) { if (prog.uniLoc[name]) gl.uniform3fv(prog.uniLoc[name], arr); },
SetF4: function (prog, name, arr) { if (prog.uniLoc[name]) gl.uniform4fv(prog.uniLoc[name], arr); },
SetM33: function (prog, name, mat) { if (prog.uniLoc[name]) gl.uniformMatrix3fv(prog.uniLoc[name], false, mat); },
SetM44: function (prog, name, mat) { if (prog.uniLoc[name]) gl.uniformMatrix4fv(prog.uniLoc[name], false, mat); },
Compile: function (source, shaderStage) {
var shaderScript = document.getElementById(source);
if (shaderScript)
source = shaderScript.text;
var shaderObj = gl.createShader(shaderStage);
gl.shaderSource(shaderObj, source);
gl.compileShader(shaderObj);
var status = gl.getShaderParameter(shaderObj, gl.COMPILE_STATUS);
if (!status) alert(gl.getShaderInfoLog(shaderObj));
return status ? shaderObj : null;
},
Link: function (shaderObjs) {
var prog = gl.createProgram();
for (var i_sh = 0; i_sh < shaderObjs.length; ++i_sh)
gl.attachShader(prog, shaderObjs[i_sh]);
gl.linkProgram(prog);
status = gl.getProgramParameter(prog, gl.LINK_STATUS);
if ( !status ) alert(gl.getProgramInfoLog(prog));
return status ? prog : null;
} };
var VertexBuffer = {
Create: function(attribs, indices, type) {
var buffer = { buf: [], attr: [], inx: gl.createBuffer(), inxLen: indices.length, primitive_type: type ? type : gl.TRIANGLES };
for (var i=0; i<attribs.length; ++i) {
buffer.buf.push(gl.createBuffer());
buffer.attr.push({ size : attribs[i].attrSize, loc : attribs[i].attrLoc });
gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buf[i]);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array( attribs[i].data ), gl.STATIC_DRAW);
}
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer.inx);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( indices ), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
return buffer;
},
Draw: function(bufObj) {
for (var i=0; i<bufObj.buf.length; ++i) {
gl.bindBuffer(gl.ARRAY_BUFFER, bufObj.buf[i]);
gl.vertexAttribPointer(bufObj.attr[i].loc, bufObj.attr[i].size, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray( bufObj.attr[i].loc);
}
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufObj.inx);
gl.drawElements(bufObj.primitive_type, bufObj.inxLen, gl.UNSIGNED_SHORT, 0);
for (var i=0; i<bufObj.buf.length; ++i)
gl.disableVertexAttribArray(bufObj.attr[i].loc);
gl.bindBuffer( gl.ARRAY_BUFFER, null );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
} };
initScene();
})();
html,body { margin: 0; overflow: hidden; }
#gui { position : absolute; top : 0; left : 0; }
<script id="draw-shader-vs" type="x-shader/x-vertex">
precision highp float;
attribute vec3 a_position;
attribute vec3 a_normal;
attribute vec2 a_texCoord;
varying vec3 w_pos;
varying vec3 w_nv;
varying vec2 o_uv;
uniform mat4 P;
uniform mat4 V;
uniform mat4 M;
void main()
{
o_uv = a_texCoord;
w_nv = normalize(mat3(M) * a_normal);
vec4 worldPos = M * vec4(a_position, 1.0);
w_pos = worldPos.xyz;
gl_Position = P * V * worldPos;
}
</script>
<script id="draw-shader-fs" type="x-shader/x-fragment">
#extension GL_OES_standard_derivatives : enable
precision mediump float;
varying vec3 w_pos;
varying vec3 w_nv;
varying vec2 o_uv;
uniform float u_height_scale;
uniform vec3 u_light_pos;
uniform vec3 u_view_pos;
uniform sampler2D u_diffuse;
uniform sampler2D u_normal_map;
uniform sampler2D u_displacement_map;
vec2 ParallaxMapping (vec2 texCoord, vec3 viewDir)
{
float numLayers = 32.0 - 31.0 * abs(dot(vec3(0.0, 0.0, 1.0), viewDir));
float layerDepth = 1.0 / numLayers;
vec2 P = viewDir.xy / viewDir.z * u_height_scale;
vec2 deltaTexCoords = P / numLayers;
vec2 currentTexCoords = texCoord;
float currentLayerDepth = 0.0;
float currentDepthMapValue = texture2D(u_displacement_map, currentTexCoords).r;
for (int i=0; i<32; ++ i)
{
if (currentLayerDepth >= currentDepthMapValue)
break;
currentTexCoords -= deltaTexCoords;
currentDepthMapValue = texture2D(u_displacement_map, currentTexCoords).r;
currentLayerDepth += layerDepth;
}
vec2 prevTexCoords = currentTexCoords + deltaTexCoords;
float afterDepth = currentDepthMapValue - currentLayerDepth;
float beforeDepth = texture2D(u_displacement_map, prevTexCoords).r - currentLayerDepth + layerDepth;
float weight = afterDepth / (afterDepth - beforeDepth);
return prevTexCoords * weight + currentTexCoords * (1.0 - weight);
}
void main()
{
vec3 N = normalize(w_nv);
vec3 dp1 = dFdx( w_pos );
vec3 dp2 = dFdy( w_pos );
vec2 duv1 = dFdx( o_uv );
vec2 duv2 = dFdy( o_uv );
vec3 dp2perp = cross(dp2, N);
vec3 dp1perp = cross(N, dp1);
vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;
vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;
float invmax = inversesqrt(max(dot(T, T), dot(B, B)));
mat3 tm = mat3(T * invmax, B * invmax, N);
mat3 tbn_inv = mat3(vec3(tm[0].x, tm[1].x, tm[2].x), vec3(tm[0].y, tm[1].y, tm[2].y), vec3(tm[0].z, tm[1].z, tm[2].z));
vec3 view_dir = tbn_inv * normalize(w_pos - u_view_pos);
vec2 uv = ParallaxMapping(o_uv, view_dir);
if (uv.x > 1.0 || uv.y > 1.0 || uv.x < 0.0 || uv.y < 0.0)
discard;
vec3 L = tbn_inv * normalize(u_light_pos - w_pos);
vec3 mapN = normalize(texture2D(u_normal_map, uv.st).xyz * 2.0 - 1.0);
float kd = max(0.0, dot(mapN, L));
vec3 color = texture2D(u_diffuse, uv.st).rgb;
vec3 light_col = (0.1 + kd) * color.rgb;
gl_FragColor = vec4(clamp(light_col, 0.0, 1.0), 1.0);
}
</script>
<body>
<div>
<form id="gui" name="inputs">
<table>
<tr>
<td> <font color=#CCF>height scale</font> </td>
<td> <input type="range" id="height" min="0" max="100" value="50"/></td>
</tr>
</table>
</form>
</div>
<canvas id="canvas" style="border: none;" width="100%" height="100%"></canvas>