Related
I'd like to change the background color of a simple three.js scene, like the one you change with
renderer.setClearColor(/*insert color here*/)
or
scene.background = new THREE.Color(/*insert color here*/)
with fragment shaders,
but including glsl code or ShaderMaterial inside the brackets doesnt work.
How can I do it?
Here is an approach with WebGLRenderTarget.texture as scene.background:
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 100);
camera.position.set(0, 0, 10);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener("resize", event => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
gu.resolution.set(innerWidth, innerHeight);
});
let gu = {
time: {value: 0},
resolution: {value: new THREE.Vector2(innerWidth, innerHeight)}
}
let light = new THREE.DirectionalLight(0xffffff, 0.8);
light.position.setScalar(1);
scene.add(light, new THREE.AmbientLight(0xffffff, 0.2));
let ico = new THREE.Mesh(new THREE.IcosahedronGeometry(3, 0), new THREE.MeshLambertMaterial({color: 0xff4488}));
scene.add(ico);
// <background stuff>
let backScene = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), new THREE.MeshBasicMaterial({
color: 0x004488,
onBeforeCompile: shader => {
shader.uniforms.time = gu.time;
shader.uniforms.resolution = gu.resolution;
shader.vertexShader = `
varying vec2 vUv;
` + shader.vertexShader;
shader.vertexShader = shader.vertexShader.replace(
`#include <uv_vertex>`,
`vUv = uv;`
);
shader.fragmentShader = `
${noise}
uniform float time;
uniform vec2 resolution;
varying vec2 vUv;
` + shader.fragmentShader;
shader.fragmentShader = shader.fragmentShader.replace(
`vec4 diffuseColor = vec4( diffuse, opacity );`,
`
vec2 tUv = vUv * 5.0;
tUv.x *= resolution.x / resolution.y;
float t = time * 0.5;
tUv -= vec2(t * 0.25, t);
float n1 = snoise(vec3(tUv, time * 0.125));
n1 = (n1 + 1.0) * 0.5;
float n2 = snoise(vec3(n1 * 10., 1, 1));
n2 = sin(((n2 + 1.0) * 0.5) * 3.1415926 * 2.);
float effect = smoothstep(0.1, 0.125, n1) * (1. - smoothstep(0.375, 0.4, n2));
float coef = sin(n2 * 3.141526 * 0.5) * 0.125;
float e = effect - abs(coef);
e = n1 > 0.25 && n1 < 0.75? e * e : pow(e, 16.);
vec4 diffuseColor = vec4(diffuse * e, opacity);
`
);
}
}));
let backCamera = new THREE.Camera();
let backRT = new THREE.WebGLRenderTarget(innerWidth, innerHeight);
scene.background = backRT.texture; // assign to background
// </background stuff>
let clock = new THREE.Clock();
renderer.setAnimationLoop( _ => {
let t = clock.getElapsedTime();
gu.time.value = t;
ico.rotation.x = t * 0.31;
ico.rotation.y = t * 0.27;
renderer.setRenderTarget(backRT); // set render target
renderer.render(backScene, backCamera); //render background
renderer.setRenderTarget(null); // remove render target, render to default buffer
renderer.render(scene, camera); // render scene
renderer.render(scene, camera);
})
body{
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script>
var noise = `
//
// Description : Array and textureless GLSL 2D/3D/4D simplex
// noise functions.
// Author : Ian McEwan, Ashima Arts.
// Maintainer : stegu
// Lastmod : 20110822 (ijm)
// License : Copyright (C) 2011 Ashima Arts. All rights reserved.
// Distributed under the MIT License. See LICENSE file.
// https://github.com/ashima/webgl-noise
// https://github.com/stegu/webgl-noise
//
vec3 mod289(vec3 x) {
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
vec4 mod289(vec4 x) {
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
vec4 permute(vec4 x) {
return mod289(((x*34.0)+1.0)*x);
}
vec4 taylorInvSqrt(vec4 r)
{
return 1.79284291400159 - 0.85373472095314 * r;
}
float snoise(vec3 v)
{
const vec2 C = vec2(1.0/6.0, 1.0/3.0) ;
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
// First corner
vec3 i = floor(v + dot(v, C.yyy) );
vec3 x0 = v - i + dot(i, C.xxx) ;
// Other corners
vec3 g = step(x0.yzx, x0.xyz);
vec3 l = 1.0 - g;
vec3 i1 = min( g.xyz, l.zxy );
vec3 i2 = max( g.xyz, l.zxy );
// x0 = x0 - 0.0 + 0.0 * C.xxx;
// x1 = x0 - i1 + 1.0 * C.xxx;
// x2 = x0 - i2 + 2.0 * C.xxx;
// x3 = x0 - 1.0 + 3.0 * C.xxx;
vec3 x1 = x0 - i1 + C.xxx;
vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y
// Permutations
i = mod289(i);
vec4 p = permute( permute( permute(
i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
+ i.y + vec4(0.0, i1.y, i2.y, 1.0 ))
+ i.x + vec4(0.0, i1.x, i2.x, 1.0 ));
// Gradients: 7x7 points over a square, mapped onto an octahedron.
// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
float n_ = 0.142857142857; // 1.0/7.0
vec3 ns = n_ * D.wyz - D.xzx;
vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7)
vec4 x_ = floor(j * ns.z);
vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N)
vec4 x = x_ *ns.x + ns.yyyy;
vec4 y = y_ *ns.x + ns.yyyy;
vec4 h = 1.0 - abs(x) - abs(y);
vec4 b0 = vec4( x.xy, y.xy );
vec4 b1 = vec4( x.zw, y.zw );
//vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;
//vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;
vec4 s0 = floor(b0)*2.0 + 1.0;
vec4 s1 = floor(b1)*2.0 + 1.0;
vec4 sh = -step(h, vec4(0.0));
vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;
vec3 p0 = vec3(a0.xy,h.x);
vec3 p1 = vec3(a0.zw,h.y);
vec3 p2 = vec3(a1.xy,h.z);
vec3 p3 = vec3(a1.zw,h.w);
//Normalise gradients
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
p0 *= norm.x;
p1 *= norm.y;
p2 *= norm.z;
p3 *= norm.w;
// Mix final noise value
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
m = m * m;
return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1),
dot(p2,x2), dot(p3,x3) ) );
}
`;
</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>
I am using the following code to generate an animation of particles. I would like to change the colors of individual particles to random colors in each frame. Is it possible to do this and, if so, how can I achieve it? Additionally, is it possible to change the rotation of the particles in each render? Any help with either of these questions would be greatly appreciated.
<!doctype html>
<head>
<title>Triangles</title>
<style>
html,
body {
background: #000;
height: 100%;
margin: 0;
}
canvas {
width: 1280px;
height: 720px;
position: absolute;
margin: auto;
top: 0;
right: 0;
left: 0;
bottom: 0;
}
</style>
</head>
<body>
<script>
'use strict';
const triangleCount = 2e5;
const antialias = true;
const generateTriangles = (count, width, height) => {
const coords = new Float32Array(9 * count);
for (var i = 0; i < coords.length;) {
const x = Math.random() * 2 - 1;
const y = Math.random() * 2 - 1;
const z = Math.random() * 2 - 1;
const theta = Math.random() * Math.PI;
const ax = 10 * Math.cos(theta) / width;
const ay = 10 * Math.sin(theta) / height;
const bx = 10 * Math.cos(theta + 0.1) / width;
const by = 10 * Math.sin(theta + 0.1) / height;
coords[i++] = x + ax;
coords[i++] = y + ay;
coords[i++] = z;
coords[i++] = x + bx;
coords[i++] = y + by;
coords[i++] = z;
coords[i++] = x - ax;
coords[i++] = y - ay;
coords[i++] = z;
coords[i++] = x - ax;
coords[i++] = y - ay;
coords[i++] = z;
coords[i++] = x - bx;
coords[i++] = y - by;
coords[i++] = z;
coords[i++] = x + ax;
coords[i++] = y + ay;
coords[i++] = z;
}
return coords;
};
const vertexShaderSource = `
precision lowp float;
attribute vec3 aPosition;
uniform float uWobble;
void main() {
float p = 0.1 / (0.3 * aPosition.z - 0.14 + 0.1 * uWobble);
gl_Position = vec4(p * aPosition.x, p * aPosition.y, aPosition.z, 1);
}
`;
const fragmentShaderSource = `
precision lowp float;
void main() {
float z = gl_FragCoord.z;
gl_FragColor = vec4(1.5 * z, z * z, z, 1.7);
}
`;
const canvas = document.createElement('canvas');
document.body.appendChild(canvas);
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
const gl = canvas.getContext('webgl', {
alpha: false,
antialias
});
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
const aVertexPosition = gl.getAttribLocation(program, 'aPosition');
gl.enableVertexAttribArray(aVertexPosition);
const uWobble = gl.getUniformLocation(program, 'uWobble');
gl.uniform1f(uWobble, 1);
const vertices = generateTriangles(triangleCount, canvas.width, canvas.height);
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
gl.vertexAttribPointer(aVertexPosition, 3, gl.FLOAT, false, 0, 0);
const render = (timestamp) => {
requestAnimationFrame(render);
gl.uniform1f(uWobble, Math.sin(0.00002 * timestamp));
gl.drawArrays(gl.TRIANGLES, 0, vertices.length / 3);
};
window.requestAnimationFrame(render);
</script>
</body>
Here is solution to get semi-random color for each particle for each frame - change your fragmentShaderSource to below:
const fragmentShaderSource = `
precision lowp float;
uniform float uWobble;
void main() {
float r = fract(sin(uWobble*10000.0*gl_FragCoord.z));
float g = fract(cos(uWobble*10000.0*gl_FragCoord.z)*43758.5453);
float b = fract(cos(uWobble*10000.0*gl_FragCoord.z)*12.9898);
gl_FragColor = vec4(r, g, b, 1.7);
}
`;
Working example here on fiddle (for easy edit) or below:
'use strict';
const triangleCount = 2e5;
const antialias = true;
const generateTriangles = (count, width, height) => {
const coords = new Float32Array(9 * count);
for (var i = 0; i < coords.length;) {
const x = Math.random() * 2 - 1;
const y = Math.random() * 2 - 1;
const z = Math.random() * 2 - 1;
const theta = Math.random() * Math.PI;
const ax = 10 * Math.cos(theta) / width;
const ay = 10 * Math.sin(theta) / height;
const bx = 10 * Math.cos(theta + 0.1) / width;
const by = 10 * Math.sin(theta + 0.1) / height;
coords[i++] = x + ax;
coords[i++] = y + ay;
coords[i++] = z;
coords[i++] = x + bx;
coords[i++] = y + by;
coords[i++] = z;
coords[i++] = x - ax;
coords[i++] = y - ay;
coords[i++] = z;
coords[i++] = x - ax;
coords[i++] = y - ay;
coords[i++] = z;
coords[i++] = x - bx;
coords[i++] = y - by;
coords[i++] = z;
coords[i++] = x + ax;
coords[i++] = y + ay;
coords[i++] = z;
}
return coords;
};
const vertexShaderSource = `
precision lowp float;
attribute vec3 aPosition;
uniform float uWobble;
void main() {
float p = 0.1 / (0.3 * aPosition.z - 0.14 + 0.1 * uWobble);
gl_Position = vec4(p * aPosition.x, p * aPosition.y, aPosition.z, 1);
}
`;
const fragmentShaderSource = `
precision lowp float;
uniform float uWobble;
void main() {
float r = fract(sin(uWobble*10000.0*gl_FragCoord.z));
float g = fract(cos(uWobble*10000.0*gl_FragCoord.z)*43758.5453);
float b = fract(cos(uWobble*10000.0*gl_FragCoord.z)*12.9898);
gl_FragColor = vec4(r, g, b, 1.7);
}
`;
const canvas = document.createElement('canvas');
document.body.appendChild(canvas);
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
const gl = canvas.getContext('webgl', {
alpha: false,
antialias
});
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
const aVertexPosition = gl.getAttribLocation(program, 'aPosition');
gl.enableVertexAttribArray(aVertexPosition);
const uWobble = gl.getUniformLocation(program, 'uWobble');
gl.uniform1f(uWobble, 1);
const vertices = generateTriangles(triangleCount, canvas.width, canvas.height);
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
gl.vertexAttribPointer(aVertexPosition, 3, gl.FLOAT, false, 0, 0);
const render = (timestamp) => {
requestAnimationFrame(render);
gl.uniform1f(uWobble, Math.sin(0.00002 * timestamp));
gl.drawArrays(gl.TRIANGLES, 0, vertices.length / 3);
};
window.requestAnimationFrame(render);
html,
body {
background: #000;
height: 100%;
margin: 0;
}
canvas {
width: 1280px;
height: 720px;
position: absolute;
margin: auto;
top: 0;
right: 0;
left: 0;
bottom: 0;
}
/**
* 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>
I want a mouse drager event when i drag and move the mouse accordingly with the velocity of mouse movement it shuld rotate and slowly stop.
this html and javascript will give a cube which rotates at time interval 33
window.onload = startDemo;
function Point3D(x,y,z) {
this.x = x;
this.y = y;
this.z = z;
this.rotateX = function(angle) {
var rad, cosa, sina, y, z
rad = angle * Math.PI / 180
cosa = Math.cos(rad)
sina = Math.sin(rad)
y = this.y * cosa - this.z * sina
z = this.y * sina + this.z * cosa
return new Point3D(this.x, y, z)
}
this.rotateY = function(angle) {
var rad, cosa, sina, x, z
rad = angle * Math.PI / 180
cosa = Math.cos(rad)
sina = Math.sin(rad)
z = this.z * cosa - this.x * sina
x = this.z * sina + this.x * cosa
return new Point3D(x,this.y, z)
}
this.rotateZ = function(angle) {
var rad, cosa, sina, x, y
rad = angle * Math.PI / 180
cosa = Math.cos(rad)
sina = Math.sin(rad)
x = this.x * cosa - this.y * sina
y = this.x * sina + this.y * cosa
return new Point3D(x, y, this.z)
}
this.project = function(viewWidth, viewHeight, fov, viewDistance) {
var factor, x, y
factor = fov / (viewDistance + this.z)
x = this.x * factor + viewWidth / 2
y = this.y * factor + viewHeight / 2
return new Point3D(x, y, this.z)
}
}
var vertices = [
new Point3D(-1,1,-1),
new Point3D(1,1,-1),
new Point3D(1,-1,-1),
new Point3D(-1,-1,-1),
new Point3D(-1,1,1),
new Point3D(1,1,1),
new Point3D(1,-1,1),
new Point3D(-1,-1,1)
];
// Define the vertices that compose each of the 6 faces. These numbers are
// indices to the vertex list defined above.
var faces = [[0,1,2,3],[1,5,6,2],[5,4,7,6],[4,0,3,7],[0,4,5,1],[3,2,6,7]];
// Define the colors for each face.
var colors = [[255,0,0],[0,255,0],[0,0,255],[255,255,0],[0,255,255],[255,0,255]];
var angle = 0;
/* Constructs a CSS RGB value from an array of 3 elements. */
function arrayToRGB(arr) {
if( arr.length == 3 ) {
return "rgb(" + arr[0] + "," + arr[1] + "," + arr[2] + ")";
}
return "rgb(0,0,0)";
}
function startDemo() {
canvas = document.getElementById("thecanvas");
if( canvas && canvas.getContext ) {
ctx = canvas.getContext("2d");
}setInterval(loop,33);
}
function loop() {
var t = new Array();
ctx.fillStyle = "rgb(0,0,0)";
ctx.fillRect(0,0,400,250);
for( var i = 0; i < vertices.length; i++ ) {
var v = vertices[i];
var r = v.rotateZ(angle).rotateX(angle);
var p = r.project(400,250,200,4);
t.push(p)
}
var avg_z = new Array();
for( var i = 0; i < faces.length; i++ ) {
var f = faces[i];
avg_z[i] = {"index":i, "z":(t[f[0]].z + t[f[1]].z + t[f[2]].z + t[f[3]].z) / 4.0};
}
avg_z.sort(function(a,b) {
return b.z - a.z;
});
for( var i = 0; i < faces.length; i++ ) {
var f = faces[avg_z[i].index]
ctx.fillStyle = arrayToRGB(colors[avg_z[i].index]);
ctx.beginPath()
ctx.moveTo(t[f[0]].x,t[f[0]].y)
ctx.lineTo(t[f[1]].x,t[f[1]].y)
ctx.lineTo(t[f[2]].x,t[f[2]].y)
ctx.lineTo(t[f[3]].x,t[f[3]].y)
ctx.closePath()
ctx.fill()
}
angle += 2
}
<!DOCTYPE html>
<html>
<head>
<title>HTML5 Experiment: A Rotating Solid Cube</title>
</head>
<body>
<canvas id="thecanvas" width="500" height="250">
Your brows<a href=#>Click here</a> to watch the video.
</canvas>
</body>
</html>
please help me with this code
You can use
onmouseover
Follow the link and you will be understand.
http://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_onmouseover
Try this Code
<!doctype html>
<html>
<body>
<canvas width = "570" height = "570" id = "my_Canvas"></canvas>
<script>
/*============= Creating a canvas ======================*/
var canvas = document.getElementById('my_Canvas');
gl = canvas.getContext('experimental-webgl');
/*========== Defining and storing the geometry ==========*/
var 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,
];
var colors = [
5,3,7, 5,3,7, 5,3,7, 5,3,7,
1,1,3, 1,1,3, 1,1,3, 1,1,3,
0,0,1, 0,0,1, 0,0,1, 0,0,1,
1,0,0, 1,0,0, 1,0,0, 1,0,0,
1,1,0, 1,1,0, 1,1,0, 1,1,0,
0,1,0, 0,1,0, 0,1,0, 0,1,0
];
var indices = [
0,1,2, 0,2,3, 4,5,6, 4,6,7,
8,9,10, 8,10,11, 12,13,14, 12,14,15,
16,17,18, 16,18,19, 20,21,22, 20,22,23
];
// Create and store data into vertex buffer
var vertex_buffer = gl.createBuffer ();
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
// Create and store data into color buffer
var color_buffer = gl.createBuffer ();
gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
// Create and store data into index buffer
var index_buffer = gl.createBuffer ();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
/*=================== SHADERS =================== */
var vertCode = 'attribute vec3 position;'+
'uniform mat4 Pmatrix;'+
'uniform mat4 Vmatrix;'+
'uniform mat4 Mmatrix;'+
'attribute vec3 color;'+//the color of the point
'varying vec3 vColor;'+
'void main(void) { '+//pre-built function
'gl_Position = Pmatrix*Vmatrix*Mmatrix*vec4(position, 1.);'+
'vColor = color;'+
'}';
var fragCode = 'precision mediump float;'+
'varying vec3 vColor;'+
'void main(void) {'+
'gl_FragColor = vec4(vColor, 1.);'+
'}';
var vertShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertShader, vertCode);
gl.compileShader(vertShader);
var fragShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragShader, fragCode);
gl.compileShader(fragShader);
var shaderprogram = gl.createProgram();
gl.attachShader(shaderprogram, vertShader);
gl.attachShader(shaderprogram, fragShader);
gl.linkProgram(shaderprogram);
/*======== Associating attributes to vertex shader =====*/
var _Pmatrix = gl.getUniformLocation(shaderprogram, "Pmatrix");
var _Vmatrix = gl.getUniformLocation(shaderprogram, "Vmatrix");
var _Mmatrix = gl.getUniformLocation(shaderprogram, "Mmatrix");
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
var _position = gl.getAttribLocation(shaderprogram, "position");
gl.vertexAttribPointer(_position, 3, gl.FLOAT, false,0,0);
gl.enableVertexAttribArray(_position);
gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
var _color = gl.getAttribLocation(shaderprogram, "color");
gl.vertexAttribPointer(_color, 3, gl.FLOAT, false,0,0) ;
gl.enableVertexAttribArray(_color);
gl.useProgram(shaderprogram);
/*==================== MATRIX ====================== */
function get_projection(angle, a, zMin, zMax) {
var ang = Math.tan((angle*.5)*Math.PI/180);//angle*.5
return [
0.5/ang, 0 , 0, 0,
0, 0.5*a/ang, 0, 0,
0, 0, -(zMax+zMin)/(zMax-zMin), -1,
0, 0, (-2*zMax*zMin)/(zMax-zMin), 0
];
}
var proj_matrix = get_projection(40, canvas.width/canvas.height, 1, 100);
var mo_matrix = [ 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 ];
var view_matrix = [ 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 ];
view_matrix[14] = view_matrix[14]-6;
/*================= Mouse events ======================*/
var AMORTIZATION = 0.95;
var drag = false;
var old_x, old_y;
var dX = 0, dY = 0;
var mouseDown = function(e) {
drag = true;
old_x = e.pageX, old_y = e.pageY;
e.preventDefault();
return false;
};
var mouseUp = function(e){
drag = false;
};
var mouseMove = function(e) {
if (!drag) return false;
dX = (e.pageX-old_x)*2*Math.PI/canvas.width,
dY = (e.pageY-old_y)*2*Math.PI/canvas.height;
THETA+= dX;
PHI+=dY;
old_x = e.pageX, old_y = e.pageY;
e.preventDefault();
};
canvas.addEventListener("mousedown", mouseDown, false);
canvas.addEventListener("mouseup", mouseUp, false);
canvas.addEventListener("mouseout", mouseUp, false);
canvas.addEventListener("mousemove", mouseMove, false);
/*=========================rotation================*/
function rotateX(m, angle) {
var c = Math.cos(angle);
var s = Math.sin(angle);
var mv1 = m[1], mv5 = m[5], mv9 = m[9];
m[1] = m[1]*c-m[2]*s;
m[5] = m[5]*c-m[6]*s;
m[9] = m[9]*c-m[10]*s;
m[2] = m[2]*c+mv1*s;
m[6] = m[6]*c+mv5*s;
m[10] = m[10]*c+mv9*s;
}
function rotateY(m, angle) {
var c = Math.cos(angle);
var s = Math.sin(angle);
var mv0 = m[0], mv4 = m[4], mv8 = m[8];
m[0] = c*m[0]+s*m[2];
m[4] = c*m[4]+s*m[6];
m[8] = c*m[8]+s*m[10];
m[2] = c*m[2]-s*mv0;
m[6] = c*m[6]-s*mv4;
m[10] = c*m[10]-s*mv8;
}
/*=================== Drawing =================== */
var THETA = 0,
PHI = 0;
var time_old = 0;
var animate = function(time) {
var dt = time-time_old;
if (!drag) {
dX *= AMORTIZATION, dY*=AMORTIZATION;
THETA+=dX, PHI+=dY;
}
//set model matrix to I4
mo_matrix[0] = 1, mo_matrix[1] = 0, mo_matrix[2] = 0,
mo_matrix[3] = 0,
mo_matrix[4] = 0, mo_matrix[5] = 1, mo_matrix[6] = 0,
mo_matrix[7] = 0,
mo_matrix[8] = 0, mo_matrix[9] = 0, mo_matrix[10] = 1,
mo_matrix[11] = 0,
mo_matrix[12] = 0, mo_matrix[13] = 0, mo_matrix[14] = 0,
mo_matrix[15] = 1;
rotateY(mo_matrix, THETA);
rotateX(mo_matrix, PHI);
time_old = time;
gl.enable(gl.DEPTH_TEST);
// gl.depthFunc(gl.LEQUAL);
gl.clearColor(0.5, 0.5, 0.5, 0.9);
gl.clearDepth(1.0);
gl.viewport(0.0, 0.0, canvas.width, canvas.height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.uniformMatrix4fv(_Pmatrix, false, proj_matrix);
gl.uniformMatrix4fv(_Vmatrix, false, view_matrix);
gl.uniformMatrix4fv(_Mmatrix, false, mo_matrix);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer);
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
window.requestAnimationFrame(animate);
}
animate(0);
</script>
</body>
</html>
window.onload = startDemo;
function Point3D(x,y,z) {
this.x = x;
this.y = y;
this.z = z;
this.rotateX = function(angle) {
var rad, cosa, sina, y, z
rad = angle * Math.PI / 180
cosa = Math.cos(rad)
sina = Math.sin(rad)
y = this.y * cosa - this.z * sina
z = this.y * sina + this.z * cosa
return new Point3D(this.x, y, z)
}
this.rotateY = function(angle) {
var rad, cosa, sina, x, z
rad = angle * Math.PI / 180
cosa = Math.cos(rad)
sina = Math.sin(rad)
z = this.z * cosa - this.x * sina
x = this.z * sina + this.x * cosa
return new Point3D(x,this.y, z)
}
this.rotateZ = function(angle) {
var rad, cosa, sina, x, y
rad = angle * Math.PI / 180
cosa = Math.cos(rad)
sina = Math.sin(rad)
x = this.x * cosa - this.y * sina
y = this.x * sina + this.y * cosa
return new Point3D(x, y, this.z)
}
this.project = function(viewWidth, viewHeight, fov, viewDistance) {
var factor, x, y
factor = fov / (viewDistance + this.z)
x = this.x * factor + viewWidth / 2
y = this.y * factor + viewHeight / 2
return new Point3D(x, y, this.z)
}
}
var vertices = [
new Point3D(-1,1,-1),
new Point3D(1,1,-1),
new Point3D(1,-1,-1),
new Point3D(-1,-1,-1),
new Point3D(-1,1,1),
new Point3D(1,1,1),
new Point3D(1,-1,1),
new Point3D(-1,-1,1)
];
// Define the vertices that compose each of the 6 faces. These numbers are
// indices to the vertex list defined above.
var faces = [[0,1,2,3],[1,5,6,2],[5,4,7,6],[4,0,3,7],[0,4,5,1],[3,2,6,7]];
// Define the colors for each face.
var colors = [[255,0,0],[0,255,0],[0,0,255],[255,255,0],[0,255,255],[255,0,255]];
var angle = 0;
/* Constructs a CSS RGB value from an array of 3 elements. */
function arrayToRGB(arr) {
if( arr.length == 3 ) {
return "rgb(" + arr[0] + "," + arr[1] + "," + arr[2] + ")";
}
return "rgb(0,0,0)";
}
function startDemo() {
canvas = document.getElementById("thecanvas");
if( canvas && canvas.getContext ) {
ctx = canvas.getContext("2d");
}setInterval(loop,33);
}
function loop() {
var t = new Array();
ctx.fillStyle = "rgb(0,0,0)";
ctx.fillRect(0,0,400,250);
for( var i = 0; i < vertices.length; i++ ) {
var v = vertices[i];
var r = v.rotateZ(angle).rotateX(angle);
var p = r.project(400,250,200,4);
t.push(p)
}
var avg_z = new Array();
for( var i = 0; i < faces.length; i++ ) {
var f = faces[i];
avg_z[i] = {"index":i, "z":(t[f[0]].z + t[f[1]].z + t[f[2]].z + t[f[3]].z) / 4.0};
}
avg_z.sort(function(a,b) {
return b.z - a.z;
});
for( var i = 0; i < faces.length; i++ ) {
var f = faces[avg_z[i].index]
ctx.fillStyle = arrayToRGB(colors[avg_z[i].index]);
ctx.beginPath()
ctx.moveTo(t[f[0]].x,t[f[0]].y)
ctx.lineTo(t[f[1]].x,t[f[1]].y)
ctx.lineTo(t[f[2]].x,t[f[2]].y)
ctx.lineTo(t[f[3]].x,t[f[3]].y)
ctx.closePath()
ctx.fill()
}
angle += 2
}
<!DOCTYPE html>
<html>
<head>
<title>HTML5 Experiment: A Rotating Solid Cube</title>
</head>
<body>
<canvas id="thecanvas" width="500" height="250">
Your brows<a href=#>Click here</a> to watch the video.
</canvas>
</body>
</html>