How to avoid transparency overlap using the latest Mapbox-gl-js version? - javascript

I have been playing around with the shadows from building geometries example at (https://bl.ocks.org/andrewharvey/9490afae78301c047adddfb06523f6f1) and have been able to blend the transparent layers to become one uniform alpha value with an older version of mapbox-gl-js.
However when I change the mapbox-gl-js version to v0.54.0 or higher it no longer blends the shadows to be a uniform value. I have experimented with gl.blendFunc() and gl.blendFuncSeparate() but still seem to get a mixture of weird anti-aliasing issues or overlapping opacities.
How could I avoid this transparency issue and get a similar result to the first example provided.
0.53.1:
1.6.1:
Code using version 0.53.1:
<!DOCTYPE html>
<html>
<head>
<title>Mapbox GL JS debug page</title>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.1/mapbox-gl.css' rel='stylesheet' />
<style>
body { margin: 0; padding: 0; }
html, body, #map { height: 100%; }
#time { position: absolute; width: 90%; top: 10px; left: 10px; }
</style>
</head>
<body>
<div id='map'></div>
<input id='time' type='range' min="0" max="86400" />
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.1/mapbox-gl.js'></script>
<script src='https://unpkg.com/suncalc#1.8.0/suncalc.js'></script>
<!-- <script src='/debug/access_token_generated.js'></script> -->
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoiYWxhbnRnZW8tcHJlc2FsZXMiLCJhIjoiY2pzcTA4NjRiMTMxczQzcDFqa29maXk3bSJ9.pVYNTFKfcOXA_U_5TUwDWw';
var map = window.map = new mapboxgl.Map({
container: 'map',
zoom: 15,
center: [-74.0059, 40.7064],
style: 'mapbox://styles/mapbox/streets-v11',
hash: true
});
var date = new Date();
var time = date.getHours() * 60 * 60 + date.getMinutes() * 60 + date.getSeconds();
var timeInput = document.getElementById('time');
timeInput.value = time;
timeInput.oninput = () => {
time = +timeInput.value;
date.setHours(Math.floor(time / 60 / 60));
date.setMinutes(Math.floor(time / 60) % 60);
date.setSeconds(time % 60);
map.triggerRepaint();
};
map.addControl(new mapboxgl.NavigationControl());
class BuildingShadows {
constructor() {
this.id = 'building-shadows';
this.type = 'custom';
this.renderingMode = '3d';
this.opacity = 0.5;
}
onAdd(map, gl) {
this.map = map;
const vertexSource = `
uniform mat4 u_matrix;
uniform float u_height_factor;
uniform float u_altitude;
uniform float u_azimuth;
attribute vec2 a_pos;
attribute vec4 a_normal_ed;
attribute lowp vec2 a_base;
attribute lowp vec2 a_height;
void main() {
float base = max(0.0, a_base.x);
float height = max(0.0, a_height.x);
float t = mod(a_normal_ed.x, 2.0);
vec4 pos = vec4(a_pos, t > 0.0 ? height : base, 1);
float len = pos.z * u_height_factor / tan(u_altitude);
pos.x += cos(u_azimuth) * len;
pos.y += sin(u_azimuth) * len;
pos.z = 0.0;
gl_Position = u_matrix * pos;
}
`;
const fragmentSource = `
void main() {
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.5);
}
`;
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexSource);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
console.log(gl.FRAGMENT_SHADER)
console.log(fragmentShader)
console.log(fragmentSource)
gl.shaderSource(fragmentShader, fragmentSource);
gl.compileShader(fragmentShader);
this.program = gl.createProgram();
gl.attachShader(this.program, vertexShader);
gl.attachShader(this.program, fragmentShader);
gl.linkProgram(this.program);
gl.validateProgram(this.program);
this.uMatrix = gl.getUniformLocation(this.program, "u_matrix");
this.uHeightFactor = gl.getUniformLocation(this.program, "u_height_factor");
this.uAltitude = gl.getUniformLocation(this.program, "u_altitude");
this.uAzimuth = gl.getUniformLocation(this.program, "u_azimuth");
this.aPos = gl.getAttribLocation(this.program, "a_pos");
this.aNormal = gl.getAttribLocation(this.program, "a_normal_ed");
this.aBase = gl.getAttribLocation(this.program, "a_base");
this.aHeight = gl.getAttribLocation(this.program, "a_height");
}
render(gl, matrix) {
gl.useProgram(this.program);
const source = this.map.style.sourceCaches['composite'];
const coords = source.getVisibleCoordinates().reverse();
const buildingsLayer = map.getLayer('3d-buildings');
const context = this.map.painter.context;
const {lng, lat} = this.map.getCenter();
const pos = SunCalc.getPosition(date, lat, lng);
gl.uniform1f(this.uAltitude, pos.altitude);
gl.uniform1f(this.uAzimuth, pos.azimuth + 3 * Math.PI / 2);
map.setLight({
anchor: 'map',
position: [1.5, 180 + pos.azimuth * 180 / Math.PI, 90 - pos.altitude * 180 / Math.PI],
'position-transition': {duration: 0},
color: '#fdb'
// color: `hsl(20, ${50 * Math.cos(pos.altitude)}%, ${ 200 * Math.sin(pos.altitude) }%)`
}, {duration: 0});
this.opacity = Math.sin(Math.max(pos.altitude, 0)) * 0.9;
// ADDED: normalises the colour of the shadows
gl.blendFunc(gl.SRC_COLOR, gl.CONSTANT_COLOR)
gl.enable(gl.BLEND)
for (const coord of coords) {
const tile = source.getTile(coord);
const bucket = tile.getBucket(buildingsLayer);
if (!bucket) continue;
const [heightBuffer, baseBuffer] = bucket.programConfigurations.programConfigurations['3d-buildings']._buffers;
gl.uniformMatrix4fv(this.uMatrix, false, coord.posMatrix);
gl.uniform1f(this.uHeightFactor, Math.pow(2, coord.overscaledZ) / tile.tileSize / 8);
for (const segment of bucket.segments.get()) {
const numPrevAttrib = context.currentNumAttributes || 0;
const numNextAttrib = 2;
for (let i = numNextAttrib; i < numPrevAttrib; i++) gl.disableVertexAttribArray(i);
const vertexOffset = segment.vertexOffset || 0;
gl.enableVertexAttribArray(this.aPos);
gl.enableVertexAttribArray(this.aNormal);
gl.enableVertexAttribArray(this.aHeight);
gl.enableVertexAttribArray(this.aBase);
bucket.layoutVertexBuffer.bind();
gl.vertexAttribPointer(this.aPos, 2, gl.SHORT, false, 12, 12 * vertexOffset);
gl.vertexAttribPointer(this.aNormal, 4, gl.SHORT, false, 12, 4 + 12 * vertexOffset);
heightBuffer.bind();
gl.vertexAttribPointer(this.aHeight, 1, gl.FLOAT, false, 4, 4 * vertexOffset);
baseBuffer.bind();
gl.vertexAttribPointer(this.aBase, 1, gl.FLOAT, false, 4, 4 * vertexOffset);
bucket.indexBuffer.bind();
context.currentNumAttributes = numNextAttrib;
gl.drawElements(gl.TRIANGLES, segment.primitiveLength * 3, gl.UNSIGNED_SHORT, segment.primitiveOffset * 3 * 2);
}
}
}
}
map.on('load', () => {
map.removeLayer('building');
map.addLayer({
'id': '3d-buildings',
'source': 'composite',
'source-layer': 'building',
'type': 'fill-extrusion',
'minzoom': 14,
'paint': {
'fill-extrusion-color': '#ddd',
'fill-extrusion-height': ["number", ["get", "height"], 5],
'fill-extrusion-base': ["number", ["get", "min_height"], 0],
'fill-extrusion-opacity': 1
}
}, 'road-label');
map.addLayer(new BuildingShadows(), '3d-buildings');
});
</script>
</body>
</html>
Code using version 1.6.1:
<!DOCTYPE html>
<html>
<head>
<title>Mapbox GL JS debug page</title>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.6.1/mapbox-gl.css' rel='stylesheet' />
<style>
body { margin: 0; padding: 0; }
html, body, #map { height: 100%; }
#time { position: absolute; width: 90%; top: 10px; left: 10px; }
</style>
</head>
<body>
<div id='map'></div>
<input id='time' type='range' min="0" max="86400" />
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v1.6.1/mapbox-gl.js'></script>
<script src='https://unpkg.com/suncalc#1.8.0/suncalc.js'></script>
<!-- <script src='/debug/access_token_generated.js'></script> -->
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoiYWxhbnRnZW8tcHJlc2FsZXMiLCJhIjoiY2pzcTA4NjRiMTMxczQzcDFqa29maXk3bSJ9.pVYNTFKfcOXA_U_5TUwDWw';
var map = window.map = new mapboxgl.Map({
container: 'map',
zoom: 15,
center: [-74.0059, 40.7064],
style: 'mapbox://styles/mapbox/streets-v11',
hash: true
});
var date = new Date();
var time = date.getHours() * 60 * 60 + date.getMinutes() * 60 + date.getSeconds();
var timeInput = document.getElementById('time');
timeInput.value = time;
timeInput.oninput = () => {
time = +timeInput.value;
date.setHours(Math.floor(time / 60 / 60));
date.setMinutes(Math.floor(time / 60) % 60);
date.setSeconds(time % 60);
map.triggerRepaint();
};
map.addControl(new mapboxgl.NavigationControl());
class BuildingShadows {
constructor() {
this.id = 'building-shadows';
this.type = 'custom';
this.renderingMode = '3d';
this.opacity = 0.5;
}
onAdd(map, gl) {
this.map = map;
const vertexSource = `
uniform mat4 u_matrix;
uniform float u_height_factor;
uniform float u_altitude;
uniform float u_azimuth;
attribute vec2 a_pos;
attribute vec4 a_normal_ed;
attribute lowp vec2 a_base;
attribute lowp vec2 a_height;
void main() {
float base = max(0.0, a_base.x);
float height = max(0.0, a_height.x);
float t = mod(a_normal_ed.x, 2.0);
vec4 pos = vec4(a_pos, t > 0.0 ? height : base, 1);
float len = pos.z * u_height_factor / tan(u_altitude);
pos.x += cos(u_azimuth) * len;
pos.y += sin(u_azimuth) * len;
pos.z = 0.0;
gl_Position = u_matrix * pos;
}
`;
const fragmentSource = `
void main() {
gl_FragColor = vec4(5.0, 0.0, 0.0, 0.1);
}
`;
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexSource);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentSource);
gl.compileShader(fragmentShader);
this.program = gl.createProgram();
gl.attachShader(this.program, vertexShader);
gl.attachShader(this.program, fragmentShader);
gl.linkProgram(this.program);
gl.validateProgram(this.program);
this.uMatrix = gl.getUniformLocation(this.program, "u_matrix");
this.uHeightFactor = gl.getUniformLocation(this.program, "u_height_factor");
this.uAltitude = gl.getUniformLocation(this.program, "u_altitude");
this.uAzimuth = gl.getUniformLocation(this.program, "u_azimuth");
this.aPos = gl.getAttribLocation(this.program, "a_pos");
this.aNormal = gl.getAttribLocation(this.program, "a_normal_ed");
this.aBase = gl.getAttribLocation(this.program, "a_base");
this.aHeight = gl.getAttribLocation(this.program, "a_height");
}
render(gl, matrix) {
gl.useProgram(this.program);
const source = this.map.style.sourceCaches['composite'];
const coords = source.getVisibleCoordinates().reverse();
const buildingsLayer = map.getLayer('3d-buildings');
const context = this.map.painter.context;
const {lng, lat} = this.map.getCenter();
const pos = SunCalc.getPosition(date, lat, lng);
gl.uniform1f(this.uAltitude, pos.altitude);
gl.uniform1f(this.uAzimuth, pos.azimuth + 3 * Math.PI / 2);
map.setLight({
anchor: 'map',
position: [1.5, 180 + pos.azimuth * 180 / Math.PI, 90 - pos.altitude * 180 / Math.PI],
'position-transition': {duration: 0},
color: '#fdb'
// color: `hsl(20, ${50 * Math.cos(pos.altitude)}%, ${ 200 * Math.sin(pos.altitude) }%)`
}, {duration: 0});
this.opacity = Math.sin(Math.max(pos.altitude, 0)) * 0.9;
// ADDED: New Attempt to normalise the colour of the shadows
gl.depthMask(false);
gl.enable(gl.BLEND)
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
for (const coord of coords) {
const tile = source.getTile(coord);
const bucket = tile.getBucket(buildingsLayer);
if (!bucket) continue;
const [heightBuffer, baseBuffer] = bucket.programConfigurations.programConfigurations['3d-buildings']._buffers;
gl.uniformMatrix4fv(this.uMatrix, false, coord.posMatrix);
gl.uniform1f(this.uHeightFactor, Math.pow(2, coord.overscaledZ) / tile.tileSize / 8);
for (const segment of bucket.segments.get()) {
const numPrevAttrib = context.currentNumAttributes || 0;
const numNextAttrib = 2;
for (let i = numNextAttrib; i < numPrevAttrib; i++) gl.disableVertexAttribArray(i);
const vertexOffset = segment.vertexOffset || 0;
gl.enableVertexAttribArray(this.aPos);
gl.enableVertexAttribArray(this.aNormal);
gl.enableVertexAttribArray(this.aHeight);
gl.enableVertexAttribArray(this.aBase);
bucket.layoutVertexBuffer.bind();
gl.vertexAttribPointer(this.aPos, 2, gl.SHORT, false, 12, 12 * vertexOffset);
gl.vertexAttribPointer(this.aNormal, 4, gl.SHORT, false, 12, 4 + 12 * vertexOffset);
heightBuffer.bind();
gl.vertexAttribPointer(this.aHeight, 1, gl.FLOAT, false, 4, 4 * vertexOffset);
baseBuffer.bind();
gl.vertexAttribPointer(this.aBase, 1, gl.FLOAT, false, 4, 4 * vertexOffset);
bucket.indexBuffer.bind();
context.currentNumAttributes = numNextAttrib;
gl.drawElements(gl.TRIANGLES, segment.primitiveLength * 3, gl.UNSIGNED_SHORT, segment.primitiveOffset * 3 * 2);
}
}
}
}
map.on('load', () => {
map.removeLayer('building');
map.addLayer({
'id': '3d-buildings',
'source': 'composite',
'source-layer': 'building',
'type': 'fill-extrusion',
'minzoom': 14,
'paint': {
'fill-extrusion-color': '#ddd',
'fill-extrusion-height': ["number", ["get", "height"], 5],
'fill-extrusion-base': ["number", ["get", "min_height"], 0],
'fill-extrusion-opacity': 1
}
}, 'road-label');
map.addLayer(new BuildingShadows(), '3d-buildings');
});
</script>
</body>
</html>

There are various variations but
draw all the shadows into the stencil buffer then draw one quad with the stencil test on so that it only draws where the stencil is set?
draw all the shadows opaquely into a texture. draw the texture as a quad over the map.
draw all the shadows into the depth buffer. draw one quad with the depth test set so it only draws where there were shadows.
also
draw the shadows with the depth test or stencil test on such that no pixel can be drawn more than once.
Various related examples:
Stencil buffer in WebGL
WebGL – Use mesh as mask for background image
Is there a way in WebGL to quickly invert the stencil buffer?

Related

How to improve WebGL batch render speed

I am writing a small sprite sheet renderer in WebGL, where I render quad with set with and height then I take one big sprite sheet and use the texture coordinates to pick what sprite should be shown on what quad. I do this all in one big object with position and texture coordinates array where i push new items in. I'm also using TWGL library to make WebGL a bit less verbose. Currently this system of batching items into one draw per frame isn't working that well, how can it be improved?
class Render {
constructor(selector) {
this.vertexShaderSource = `#version 300 es
in vec2 position;
in vec2 texcoord;
uniform vec2 u_resolution;
out vec2 v_texCoord;
void main() {
vec2 zeroToOne = position / u_resolution;
vec2 zeroToTwo = zeroToOne * 2.0;
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
v_texCoord = texcoord;
}`;
this.fragmentShaderSource = `#version 300 es
precision highp float;
uniform sampler2D u_image;
in vec2 v_texCoord;
out vec4 outColor;
void main() {
outColor = texture(u_image, v_texCoord);
}`;
this.bufferInfo;
this.gl = document.querySelector(selector).getContext("webgl2");
this.programInfo = twgl.createProgramInfo(this.gl, [this.vertexShaderSource, this.fragmentShaderSource]);
this.textures = [];
this.uniforms = {
u_resolution: [innerWidth, innerHeight],
u_image: null,
}
this.arrays = {
position: [],
texcoord: [],
};
this.gl.canvas.width = innerWidth;
this.gl.canvas.height = innerHeight;
}
loadImage(url, callback) {
const texture = twgl.createTexture(this.gl, { src: url, mag: this.gl.NEAREST }, () => { callback ? callback() : "" });
this.textures.push(texture);
return {
texture: texture,
index: this.textures.length - 1
};
}
clearBuffer() {
this.arrays.position = [];
this.arrays.texcoord = [];
}
setTexture(texture) {
this.uniforms.u_image = texture;
}
addQuad(x, y, width, height, srcWidth, srcHeight, srcX, srcY, realWidth, realHeight) {
const x1 = x;
const x2 = x + width;
const y1 = y;
const y2 = y + height;
this.arrays.position.push(
x1, y1, 0,
x2, y1, 0,
x1, y2, 0,
x1, y2, 0,
x2, y1, 0,
x2, y2, 0,
);
this.arrays.texcoord.push(
srcX / srcWidth / 1, srcY / srcHeight / 1,
(srcX + realWidth) / srcWidth / 1, srcY / srcHeight / 1,
srcX / srcWidth / 1, (srcY + realHeight) / srcHeight / 1,
srcX / srcWidth / 1, (srcY + realHeight) / srcHeight / 1,
(srcX + realWidth) / srcWidth / 1, srcY / srcHeight / 1,
(srcX + realWidth) / srcWidth / 1, (srcY + realHeight) / srcHeight / 1,
);
}
setBuffer() {
this.bufferInfo = twgl.createBufferInfoFromArrays(this.gl, this.arrays);
}
resizeCanvas() {
twgl.resizeCanvasToDisplaySize(this.gl.canvas);
this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
}
render() {
this.gl.useProgram(this.programInfo.program);
twgl.setUniforms(this.programInfo, this.uniforms);
twgl.setBuffersAndAttributes(this.gl, this.programInfo, this.bufferInfo);
twgl.drawBufferInfo(this.gl, this.bufferInfo);
}
}
const render = new Render("#game");
const texture = render.loadImage("https://i.imgur.com/TQnyNHU.png", () => {
requestAnimationFrame(renderStuff);
render.resizeCanvas();
function renderStuff() {
render.clearBuffer();
render.setTexture(texture);
for(let y = 0; y < 100; y++) {
for(let x = 0; x < 100; x++) {
render.addQuad(x * 48, y * 48, 48, 48, 512, 512, 32, 0, 16, 16);
}
}
render.setBuffer();
render.render();
requestAnimationFrame(renderStuff);
}
});
* {
margin: 0px;
padding: 0px;
}
body, html {
width: 100%;
height: 100%;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdnjs.cloudflare.com/ajax/libs/twgl.js/4.19.5/twgl.min.js"></script>
<script src="main.js" type="module" defer></script>
<link rel="stylesheet" href="style.css">
<title>Prometeus</title>
</head>
<body>
<canvas id="game"></canvas>
</body>
</html>

How is it possible to change the position of an object consisted of points floating around a center in Three Js?

So I have made this galaxy object consisted of points floating around its center
const parameters = {}
parameters.count = 4500
parameters.size = 0.005
parameters.radius = 0.7
parameters.branches = 4
parameters.spin = 1
parameters.randomness = 0.1
parameters.randomnessPower = 5
parameters.insideColor = '#d42b2b'
parameters.outsideColor = '#14be4c'
let galaxyGeometry = null
let galaxyMaterial = null
let galaxyPoints = null
const generateGalaxy = () =>
{
//DESTROY STUFF
if(galaxyPoints !== null)
{
galaxyGeometry.dispose()
galaxyMaterial.dispose()
scene.remove(galaxyPoints)
}
//GALAXY GEOM AND PARTICLES
galaxyGeometry = new THREE.BufferGeometry()
const positions = new Float32Array(parameters.count * 3)
const randomness = new Float32Array(parameters.count * 3)
const galaxyColors = new Float32Array(parameters.count * 3)
const galaxyScales = new Float32Array(parameters.count * 1)
const colorInside = new THREE.Color(parameters.insideColor)
const colorOutside = new THREE.Color(parameters.outsideColor)
for(let i = 0; i < parameters.count; i++)
{
const i3 = i * 3
//POSITION
const radius = Math.random() * parameters.radius
const branchAngle = (i % parameters.branches) / parameters.branches * Math.PI * 2
const randomX = Math.pow(Math.random(), parameters.randomnessPower) * (Math.random() < 0.5 ? 1: -1) * parameters.randomness * radius
const randomY = Math.pow(Math.random(), parameters.randomnessPower) * (Math.random() < 0.5 ? 1: -1) * parameters.randomness * radius
const randomZ = Math.pow(Math.random(), parameters.randomnessPower) * (Math.random() < 0.5 ? 1: -1) * parameters.randomness * radius
positions[i3 ] = Math.cos(branchAngle) * radius
positions[i3 + 1] = 0
positions[i3 + 2] = Math.sin(branchAngle) * radius
randomness[i3 ] = randomX
randomness[i3 + 1] = randomY
randomness[i3 + 2] = randomZ
//COLORS
// Color
const mixedColor = colorInside.clone()
mixedColor.lerp(colorOutside, radius / parameters.radius)
galaxyColors[i3 ] = mixedColor.r
galaxyColors[i3 + 1] = mixedColor.g
galaxyColors[i3 + 2] = mixedColor.b
// Scale
galaxyScales[i] = Math.random()
}
galaxyGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
galaxyGeometry.setAttribute('aRandomness', new THREE.BufferAttribute(randomness, 3))
galaxyGeometry.setAttribute('color', new THREE.BufferAttribute(galaxyColors, 3))
galaxyGeometry.setAttribute('aScale', new THREE.BufferAttribute(galaxyColors, 3))
//GALAXY PARTICLE MATERIALS
galaxyMaterial = new THREE.ShaderMaterial({
depthWrite: false,
blending: THREE.AdditiveBlending,
vertexColors: true,
uniforms:
{
vAlpha: { value: 1.0 },
uTime: { value: 0 },
uSize: { value: 40 * renderer.getPixelRatio() }
},
vertexShader: galaxyVertex,
fragmentShader: galaxyFragment,
})
if(galaxyMaterial.uniforms.vAlpha.value !== 1.0)
{
galaxyGeometry.dispose()
galaxyMaterial.dispose()
scene.remove(galaxyPoints)
}
//GALAXY MESH
galaxyPoints = new THREE.Points(galaxyGeometry, galaxyMaterial)
console.log(galaxyPoints.position)
scene.add(galaxyPoints)
}
along with its vertex
uniform float uTime;
uniform float uSize;
attribute vec3 aRandomness;
attribute float aScale;
varying vec3 vColor;
void main()
{
//POSITION
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
// Rotate
float angle = atan(modelPosition.x, modelPosition.z);
float distanceToCenter = length(modelPosition.xz);
float angleOffset = (1.0 / distanceToCenter) * uTime * 3.0;
angle += angleOffset;
modelPosition.x = cos(angle) * distanceToCenter;
modelPosition.y = sin(angle) * distanceToCenter;
// Randomness
modelPosition.xyz += aRandomness;
vec4 viewPosition = viewMatrix * modelPosition;
vec4 projectedPosition = projectionMatrix * viewPosition;
gl_Position = projectedPosition;
//SIZE
gl_PointSize = uSize * aScale;
gl_PointSize *= (1.0 / - viewPosition.z);
//COLOR
vColor = color;
}
and fragment shader
varying vec3 vColor;
uniform float vAlpha;
void main()
{
//LIGHT PATTERN
float strength = distance(gl_PointCoord, vec2(0.5));
strength = 1.0 - strength;
strength = pow(strength, 8.0);
//FINAL COLOR
vec3 color = mix(vec3(0.0), vColor, strength);
gl_FragColor = vec4(color, vAlpha);
}
. So my question here is this.How is it possible to move the object around the scene without changing its original look (galaxy/portal like). As I am new to Three js the only way I've known to move an object around is picking up the mesh and setting its position to whatever needed. The problem I'm having right now though is that the points are moving around a central point. When I try to move the mesh around the whole galaxy is breaking up. What is the correct approach here in order to move the whole galaxy thing, let's say by 2.0 in the x , y or z axis? I would really appreciate an explanation to why this is happening as well. Thank you in advance.

Shading in webgl

I have been trying to create shading in webgl just like in this image: where we can see a cone, a sphere, and a light (which we can change his position with the sliders).
I've tried to write some code in the html file by seeing multiple examples of shading from some webgl tutoring sites, but right now, I can't even see the shapes. It's sure that I'm doing something wrong, but I just don't know where. Here's my code and I also included a link because it contains multiple files. Thanks in advance.
Link: https://wetransfer.com/downloads/cd0f66f2e2866c0d118e95b02e01cb0520200923203442/274553
<html>
<head>
<title>Light and Shading</title>
<meta http-equiv='content-type' content='text/html; charset=ISO-8859-1'>
<!-- CSS Styles //-->
<link href='css/style.css' type='text/css' rel='stylesheet'>
<link href='css/desert.css' type='text/css' rel='stylesheet'/>
<link href='css/colorpicker.css' type='text/css' rel='stylesheet'/>
<link href='css/smoothness/jquery-ui-1.8.13.custom.css' type='text/css' rel='stylesheet' />
<!-- JavaScript Libraries //-->
<script type='text/javascript' src='js/gl-matrix-min.js'></script>
<script type='text/javascript' src='js/jquery-1.5.1.min.js'></script>
<script type='text/javascript' src='js/jquery-ui-1.8.13.custom.min.js'></script>
<script type='text/javascript' src='js/prettify.js'></script>
<script type='text/javascript' src='js/utils.js'></script>
<script type='text/javascript' src='js/colorpicker.js'></script>
<script type='text/javascript' src='js/codeview.js'></script>
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
attribute vec3 aVertexNormal;
// matrice model-view combinee.
uniform mat4 uMVMatrix;
// matrice de projection
uniform mat4 uPMatrix;
// matrice des normales.
uniform mat3 uNMatrix;
// position de la lumiere.
uniform vec3 uLightPosition;
// La normale transformee
varying vec3 vNormal;
// la direction vertex-lumiere
varying vec3 vLightRay;
// la direction camera-vertex
varying vec3 vEyeVec;
uniform vec4 uLightAmbient;
uniform vec4 uLightDiffuse;
uniform vec4 uLightSpecular;
uniform vec4 uMaterialAmbient;
uniform vec4 uMaterialDiffuse;
uniform vec4 uMaterialSpecular;
void main(void) {
vec4 ambientProduct= uLightAmbient* uMaterialAmbient;
vec4 diffuseProduct= uLightDiffuse*uMaterialDiffuse;
vec4 specularProduct= uLightSpecular*uMaterialSpecular;
vec3 pos = (uMVMatrix*vec4(aVertexPosition, 1.0)).xyz;
// position de l'oeil/camera.
const vec3 eyePosition = vec3(0,0,-40);
//Transformed normal position
vNormal = normalize((uNMatrix* aVertexNormal).xyz) ;
//Transformed light position
vec4 light = uMVMatrix * vec4(uLightPosition,1.0);
vec3 lightPos = (uMVMatrix * light).xyz;
//Light position
vLightRay = normalize(pos - lightPos);
//Vector Eye
vEyeVec = -normalize(pos);
//Final vertex position
gl_Position = uMVMatrix*uPMatrix* vec4(aVertexPosition, 1.0);
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
#ifdef GL_ES
precision highp float;
#endif
varying vec3 vNormal;
varying vec3 vLightRay;
varying vec3 vEyeVec;
uniform vec4 ambientProduct;
uniform vec4 diffuseProduct;
uniform vec4 specularProduct;
uniform float uShininess;
void main(void)
{
vec4 diffuse = max(dot( vNormal,vLightRay), 0.0) * diffuseProduct;
vec3 H = normalize(vLightRay+vEyeVec);
vec4 specular =
pow(max(dot(vNormal, H), 0.0), uShininess) * specularProduct;
if (dot(vLightRay, vNormal) < 0.0)
specular = vec4(0.0, 0.0, 0.0, 1.0);
vec4 fColor = ambientProduct + diffuse + specular;
fColor.a = 1.0;
gl_FragColor =fColor;
}
</script>
<script id='code-js' type="text/javascript">
var gl = null; // WebGL context
var prg = null; // The program (shaders)
var c_width = 0; // Variable to store the width of the canvas
var c_height = 0; // Variable to store the height of the canvas
var mvMatrix = mat4.create(); // The Model-View matrix
var pMatrix = mat4.create(); // The projection matrix
var nMatrix = mat4.create(); // The normal matrix
var distance = -40;
var animateFlag = false;
var objects = [];
/**
* The program contains a series of instructions that tell the Graphic Processing Unit (GPU)
* what to do with every vertex and fragment that we pass it.
* The vertex shader and the fragment shader together are called the program.
*/
function initProgram() {
var fragmentShader = utils.getShader(gl, "shader-fs");
var vertexShader = utils.getShader(gl, "shader-vs");
prg = gl.createProgram();
gl.attachShader(prg, vertexShader);
gl.attachShader(prg, fragmentShader);
gl.linkProgram(prg);
if (!gl.getProgramParameter(prg, gl.LINK_STATUS)) {
alert("Could not initialise shaders");
}
gl.useProgram(prg);
prg.aVertexPosition = gl.getAttribLocation(prg, "aVertexPosition");
prg.aVertexNormal = gl.getAttribLocation(prg, "aVertexNormal");
prg.uPMatrix = gl.getUniformLocation(prg, "uPMatrix");
prg.uMVMatrix = gl.getUniformLocation(prg, "uMVMatrix");
prg.uNMatrix = gl.getUniformLocation(prg, "uNMatrix");
prg.uMaterialAmbient = gl.getUniformLocation(prg, "uMaterialAmbient");
prg.uMaterialDiffuse = gl.getUniformLocation(prg, "uMaterialDiffuse");
prg.uMaterialSpecular = gl.getUniformLocation(prg, "uMaterialSpecular");
prg.uShininess = gl.getUniformLocation(prg, "uShininess");
prg.uLightPosition = gl.getUniformLocation(prg, "uLightPosition");
prg.uLightAmbient = gl.getUniformLocation(prg, "uLightAmbient");
prg.uLightDiffuse = gl.getUniformLocation(prg, "uLightDiffuse");
prg.uLightSpecular = gl.getUniformLocation(prg, "uLightSpecular");
}
function initLights(){
//Light uniforms
gl.uniform3fv(prg.uLightPosition,[4.5,3.0,15.0]);
gl.uniform4f(prg.uLightAmbient ,1.0,1.0,1.0,1.0);
gl.uniform4f(prg.uLightDiffuse,1.0,1.0,1.0,1.0);
gl.uniform4f(prg.uLightSpecular,1.0,1.0,1.0,1.0);
//Object Uniforms
gl.uniform4f(prg.uMaterialAmbient, 0.1,0.1,0.1,1.0);
gl.uniform4f(prg.uMaterialDiffuse, 0.5,0.8,0.1,1.0);
gl.uniform4f(prg.uMaterialSpecular, 0.6,0.6,0.6,1.0);
gl.uniform1f(prg.uShininess, 200.0);
}
/**
* Creates an AJAX request to load the scene asynchronously
*/
function loadScene(){
loadObject('models/plane.json');
loadObject('models/cone.json','cone');
loadObject('models/sphere.json','sphere');
loadObject('models/smallsph.json','lightsource');
}
function getObject(alias){
for(var i=0; i<objects.length; i++){
if (alias == objects[i].alias) return objects[i];
}
return null;
}
/**
* Ajax and JSON in action
*/
function loadObject(filename,alias){
var request = new XMLHttpRequest();
console.info('Requesting ' + filename);
request.open("GET",filename);
request.onreadystatechange = function() {
if (request.readyState == 4) {
if(request.status == 404) {
console.info(filename + ' does not exist');
}
else {
var o = JSON.parse(request.responseText);
o.alias = (alias==null)?'none':alias;
handleLoadedObject(filename,o);
}
}
}
request.send();
}
/**
* Creates the buffers that contain the geometry of the object
*/
function handleLoadedObject(filename,object) {
console.info(filename + ' has been retrieved from the server');
var vertexBufferObject = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBufferObject);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(object.vertices), gl.STATIC_DRAW);
var normalBufferObject = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normalBufferObject);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(calcNormals(object.vertices, object.indices)), gl.STATIC_DRAW);
var indexBufferObject = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBufferObject);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(object.indices), gl.STATIC_DRAW);
object.vbo = vertexBufferObject;
object.ibo = indexBufferObject;
object.nbo = normalBufferObject;
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
gl.bindBuffer(gl.ARRAY_BUFFER,null);
objects.push(object);
}
/**
* Main rendering function. Called every 500ms according to WebGLStart function (see below)
*/
function drawScene() {
gl.clearColor(0.3,0.3,0.3, 1.0);
gl.clearDepth(100.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.viewport(0, 0, c_width, c_height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
mat4.perspective(60, c_width / c_height, 0.1, 1000.0, pMatrix);
try{
gl.enableVertexAttribArray(prg.aVertexPosition);
gl.enableVertexAttribArray(prg.aVertexNormal);
for (var i = 0; i < objects.length; i++){
var object = objects[i];
mat4.identity(mvMatrix);
mat4.translate(mvMatrix, [0.0, 0.0, distance]); //Sets the camera to a reasonable distance to view the part
mat4.rotate(mvMatrix, 30*Math.PI/180, [1,0,0]);
mat4.rotate(mvMatrix, angle*Math.PI/180, [0,1,0]);
if (object.alias == 'lightsource'){
var lightPos = gl.getUniform(prg, prg.uLightPosition);
mat4.translate(mvMatrix,lightPos);
}
gl.uniformMatrix4fv(prg.uMVMatrix, false, mvMatrix);
gl.uniformMatrix4fv(prg.uPMatrix, false, pMatrix);
mat4.set(mvMatrix, nMatrix);
mat4.inverse(nMatrix);
mat4.transpose(nMatrix);
gl.uniformMatrix4fv(prg.uNMatrix, false, nMatrix);
gl.uniform4fv(prg.uMaterialAmbient, object.ambient);
gl.uniform4fv(prg.uMaterialDiffuse, object.diffuse);
gl.uniform4fv(prg.uMaterialSpecular, object.specular);
gl.bindBuffer(gl.ARRAY_BUFFER, object.vbo);
gl.vertexAttribPointer(prg.aVertexPosition, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(prg.aVertexPosition);
gl.bindBuffer(gl.ARRAY_BUFFER, object.nbo);
gl.vertexAttribPointer(prg.aVertexNormal, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(prg.aVertexNormal);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, object.ibo);
gl.drawElements(gl.TRIANGLES, object.indices.length, gl.UNSIGNED_SHORT,0);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
}
catch(err){
alert(err);
message(err.description);
}
}
var lastTime = 0;
var angle = 0;
/**
* Updates the angle of rotation by a little bit each time
*/
function animate() {
var timeNow = new Date().getTime();
if (lastTime != 0) {
var elapsed = timeNow - lastTime;
if (animateFlag) angle += (90 * elapsed) / 10000.0;
}
lastTime = timeNow;
}
/**
* Render Loop
*/
function renderLoop() {
requestAnimFrame(renderLoop);
drawScene();
animate();
}
/**
* Entry point. This function is invoked when the page is loaded
*/
function runWebGLApp() {
//Obtains a WebGL context
gl = utils.getGLContext("canvas-element-id");
//Initializes the program (shaders)
initProgram();
//Initializes lights
initLights();
//Load Scene
loadScene();
//Renders the scene!
renderLoop();
}
</script>
</head>
<body onLoad='runWebGLApp()'>
<div id='top'>
<div id='contents'>
<div id='canvasContainer'>
<canvas id='canvas-element-id' width='480' height='400'>
Your browser does not support the HTML5 canvas element.
</canvas>
</div>
</div>
<div id='bottom'>
<table style='padding=0px'>
<tr>
<td>X:</td><td id='slider-x-value' width='30px'>4.5</td><td width='150px'><div id='slider-x'/></td>
</tr>
<tr>
<td>Y:</td><td id='slider-y-value' width='30px'>3.0</td><td width='150px'><div id='slider-y'/></td>
</tr>
<tr>
<td>Z:</td> <td id='slider-z-value' width='30px'>15.0</td><td width='150px'><div id='slider-z'/></td>
</tr>
</table>
</div>
<script>cview.run(cview.MODE_VIEW);</script>
<script>
$('#slider-shininess').slider({value:200, min:1, max:300, step:1, slide:updateShininess});
$('#slider-x').slider({value:4.5, min:-50, max:50, step:0.1, slide:updateLightPosition, change:updateLightPosition});
$('#slider-y').slider({value:3.0, min:0, max:50, step:0.1, slide:updateLightPosition, change:updateLightPosition});
$('#slider-z').slider({value:15.0, min:-50, max:50, step:0.1, slide:updateLightPosition, change:updateLightPosition});
$('#animate-btn').button();
$('#animate-btn').click(
function(){
if ($('#animate-btn:checked').val()==null){
animateFlag = false;
}
else{
animateFlag = true;
}
});
function updateShininess(){
var v = $('#slider-shininess').slider("value");
gl.uniform1f(prg.uShininess, v);
$('#slider-shininess-value').html(v);
}
function updateLightPosition(){
var x = $('#slider-x').slider("value");
var y = $('#slider-y').slider("value");
var z = $('#slider-z').slider("value");
gl.uniform3fv(prg.uLightPosition, [x,y,z]);
$('#slider-x-value').html(x);
$('#slider-y-value').html(y);
$('#slider-z-value').html(z);
}
function updateDistance(){
var d = $('#slider-distance').slider("value");
$('#slider-distance-value').html(distance);
distance = -d;
}
function updateObjectColor(alias, r,g,b){
var object = getObject(alias);
if (object != null){
object.diffuse = [r,g,b,1.0];
}
}
$('#colorSelectorSphere').ColorPicker({
onSubmit: function(hsb, hex, rgb, el) {
$(el).val(hex);
$(el).ColorPickerHide();
},
color: '#00ff00',
onShow: function (colpkr) {
$(colpkr).fadeIn(500);
return false;
},
onHide: function (colpkr) {
$(colpkr).fadeOut(500);
return false;
},
onChange: function (hsb, hex, rgb) {
$('#colorSelectorSphere div').css('backgroundColor', '#' + hex);
updateObjectColor('sphere',rgb.r/256,rgb.g/256,rgb.b/256);
},
onBeforeShow: function (colpkr) {
$(this).ColorPickerSetColor('rgb(0.5,0.8,0.1)');
}
})
$('#colorSelectorCone').ColorPicker({
onSubmit: function(hsb, hex, rgb, el) {
$(el).val(hex);
$(el).ColorPickerHide();
},
color: '#00ff00',
onShow: function (colpkr) {
$(colpkr).fadeIn(500);
return false;
},
onHide: function (colpkr) {
$(colpkr).fadeOut(500);
return false;
},
onChange: function (hsb, hex, rgb) {
$('#colorSelectorCone div').css('backgroundColor', '#' + hex);
updateObjectColor('cone',rgb.r/256,rgb.g/256,rgb.b/256);
},
onBeforeShow: function (colpkr) {
$(this).ColorPickerSetColor('rgb(0.8,0.1,0.5)');
}
})
// Calcule les normales des vertex. La normale de chaque vertex est
// la moyenne des triangles voisins.
//
// vertices: la liste des vertex.
// ind: la liste des indices.
// retour: la liste des normales par vertex.
function calcNormals(vertices, ind){
var x=0;
var y=1;
var z=2;
var v1 = [], v2 = [], thisNormal = [];
// initialiser la liste des normales.
var ns = [];
for(var i=0;i<vertices.length;i++)
{
ns[i]=0.0;
}
for(var i=0;i<ind.length;i=i+3){
//v1 = p1 - p0
v1[x] = vertices[3*ind[i+1]+x] - vertices[3*ind[i]+x];
v1[y] = vertices[3*ind[i+1]+y] - vertices[3*ind[i]+y];
v1[z] = vertices[3*ind[i+1]+z] - vertices[3*ind[i]+z];
// v2 = p2 - p1
v2[x] = vertices[3*ind[i+2]+x] - vertices[3*ind[i]+x];
v2[y] = vertices[3*ind[i+2]+y] - vertices[3*ind[i]+y];
v2[z] = vertices[3*ind[i+2]+z] - vertices[3*ind[i]+z];
// N = v2 x v1 (cross product).
thisNormal[x] = v1[y]*v2[z] - v1[z]*v2[y];
thisNormal[y] = v1[z]*v2[x] - v1[x]*v2[z];
thisNormal[z] = v1[x]*v2[y] - v1[y]*v2[x];
for(j=0;j<3;j++)
{
// N += thisNormal. on additionne les normales.
ns[3*ind[i+j]+x] = ns[3*ind[i+j]+x] + thisNormal[x];
ns[3*ind[i+j]+y] = ns[3*ind[i+j]+y] + thisNormal[y];
ns[3*ind[i+j]+z] = ns[3*ind[i+j]+z] + thisNormal[z];
}
}
// Normalisation.
for(var i=0;i<vertices.length;i=i+3){
var nn=[];
var len = 0;
for(var j = 0; j < 3; j++)
{
nn[j] = ns[i+j];
len += nn[j] * nn[j];
}
// La norme de la normale.
len = Math.sqrt(len);
if (len == 0)
len = 0.00001;
for(var j = 0; j < 3; j++)
ns[i+j] = nn[j] / len;
console.log(len);
}
return ns;
}
</script>
</body>
</html>
Honestly, there are too many issues to cover. Trying to get your code work. First you really need to learn how to make a minimal repo. The code you posted doesn't run, references several scripts that don't exist, and data that doesn't exist.
The tutorial the code is based off appears to be old. No one uses XMLHttpRequest in 2020. There's no such function as requestAnimFrame it's requestAnimationFrame. I think that's left over from a polyfill from like 2011. It's still using <body onload=""> which few use anymore. It's also using new Date().getTime() which there's no reason to use as the time is passed into requestAnimationFrame. It's calling some function to get a webgl context but there's no reason for that either in 2020. It's also apparently using an old version of glMatrix because the current version uses a different API. In the current version every function takes a matrix to store the result as the first argument. Also in the current version perspective takes the field of view in radians. I have no idea if it used to take it in degrees but the code was passing degrees.
I changed the XHR code to just return a cube. (an example of the minimal and complete parts of an "minimal complete verifiable example" (mcve) - I think now S.O calls them a "minimal reproducible example". I also removed all the references to ColorPicker (another example of making a minimal repo)
The code should have gotten errors in the JavaScript console. Did you check the JavaScript console? In particular uNMatrix is a mat3 but the code was calling gl.uniformMatrix4fv to set it which is an error.
Using webgl-lint pointed out several uniforms were not being set including "ambientProduct", "diffuseProduct", "specularProduct" and several were being set that don't exist (that part is not a bug necessarily) but there are several uniforms in the vertex shader that are not actually used and for example the colors seem to be set with
gl.uniform4fv(prg.uMaterialAmbient, object.ambient);
gl.uniform4fv(prg.uMaterialDiffuse, object.diffuse);
gl.uniform4fv(prg.uMaterialSpecular, object.specular);
but those uniforms are not used in the shaders (they appear in the shader but if you look at the code you'll see the effect nothing)
Then, when I finally got rid of all the errors the remaining issue to get something on the screen was the math for gl_Position had the 2 matrices backward. It should be projection * modelView * position but it had modelView * projection * position
But, further, all 3 models are drawn at the same position. Maybe the geometry in those models are at different positions. I have no idea but normally you'd want to give each thing you draw it's own position in some form or another, based on the loop index or some array of positions or per object data.
Anyway, that gets something on the screen but having spent 45 minutes already I don't feel like trying to fix the lighting. Rather, I suggest you read some slightly more up to date tutorials like this one and the ones it links to.
var mat4 = glMatrix.mat4;
var gl = null; // WebGL context
var prg = null; // The program (shaders)
var c_width = 480; // Variable to store the width of the canvas
var c_height = 400; // Variable to store the height of the canvas
var mvMatrix = mat4.create(); // The Model-View matrix
var pMatrix = mat4.create(); // The projection matrix
var nMatrix = mat4.create(); // The normal matrix
var distance = -40;
var animateFlag = false;
var objects = [];
const utils = {
getShader(gl, id) {
const elem = document.getElementById(id);
const type = /vertex/.test(elem.type) ?
gl.VERTEX_SHADER :
gl.FRAGMENT_SHADER;
const sh = gl.createShader(type);
gl.shaderSource(sh, elem.text);
gl.compileShader(sh);
if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
throw new Error(gl.getShaderInfoLog(sh));
}
return sh;
},
};
/**
* The program contains a series of instructions that tell the Graphic Processing Unit (GPU)
* what to do with every vertex and fragment that we pass it.
* The vertex shader and the fragment shader together are called the program.
*/
function initProgram() {
var fragmentShader = utils.getShader(gl, "shader-fs");
var vertexShader = utils.getShader(gl, "shader-vs");
prg = gl.createProgram();
gl.attachShader(prg, vertexShader);
gl.attachShader(prg, fragmentShader);
gl.linkProgram(prg);
if (!gl.getProgramParameter(prg, gl.LINK_STATUS)) {
alert("Could not initialise shaders");
}
gl.useProgram(prg);
prg.aVertexPosition = gl.getAttribLocation(prg, "aVertexPosition");
prg.aVertexNormal = gl.getAttribLocation(prg, "aVertexNormal");
prg.uPMatrix = gl.getUniformLocation(prg, "uPMatrix");
prg.uMVMatrix = gl.getUniformLocation(prg, "uMVMatrix");
prg.uNMatrix = gl.getUniformLocation(prg, "uNMatrix");
prg.uMaterialAmbient = gl.getUniformLocation(prg, "uMaterialAmbient");
prg.uMaterialDiffuse = gl.getUniformLocation(prg, "uMaterialDiffuse");
prg.uMaterialSpecular = gl.getUniformLocation(prg, "uMaterialSpecular");
prg.uShininess = gl.getUniformLocation(prg, "uShininess");
prg.uLightPosition = gl.getUniformLocation(prg, "uLightPosition");
prg.uLightAmbient = gl.getUniformLocation(prg, "uLightAmbient");
prg.uLightDiffuse = gl.getUniformLocation(prg, "uLightDiffuse");
prg.uLightSpecular = gl.getUniformLocation(prg, "uLightSpecular");
prg.ambientProduct = gl.getUniformLocation(prg, "ambientProduct");
prg.diffuseProduct = gl.getUniformLocation(prg, "diffuseProduct");
prg.specularProduct = gl.getUniformLocation(prg, "specularProduct");
}
function initLights() {
//Light uniforms
gl.uniform3fv(prg.uLightPosition, [4.5, 3.0, 15.0]);
gl.uniform4f(prg.uLightAmbient, 1.0, 1.0, 1.0, 1.0);
gl.uniform4f(prg.uLightDiffuse, 1.0, 1.0, 1.0, 1.0);
gl.uniform4f(prg.uLightSpecular, 1.0, 1.0, 1.0, 1.0);
//Object Uniforms
gl.uniform4f(prg.uMaterialAmbient, 0.1, 0.1, 0.1, 1.0);
gl.uniform4f(prg.uMaterialDiffuse, 0.5, 0.8, 0.1, 1.0);
gl.uniform4f(prg.uMaterialSpecular, 0.6, 0.6, 0.6, 1.0);
gl.uniform1f(prg.uShininess, 200.0);
}
/**
* Creates an AJAX request to load the scene asynchronously
*/
function loadScene() {
loadObject('models/plane.json');
loadObject('models/cone.json', 'cone');
loadObject('models/sphere.json', 'sphere');
loadObject('models/smallsph.json', 'lightsource');
}
function getObject(alias) {
for (var i = 0; i < objects.length; i++) {
if (alias == objects[i].alias) return objects[i];
}
return null;
}
/**
* Ajax and JSON in action
*/
const vertices = [
-1, -1, 1,
1, -1, 1,
-1, 1, 1,
1, 1, 1,
1, -1, 1,
1, -1, -1,
1, 1, 1,
1, 1, -1,
1, -1, -1,
-1, -1, -1,
1, 1, -1,
-1, 1, -1,
-1, -1, -1,
-1, -1, 1,
-1, 1, -1,
-1, 1, 1,
1, 1, -1,
-1, 1, -1,
1, 1, 1,
-1, 1, 1,
1, -1, 1,
-1, -1, 1,
1, -1, -1,
-1, -1, -1,
];
let modelNum = 0
function loadObject(filename, alias) {
setTimeout(() => {
const o = {
alias: (alias == null) ? 'none' : alias,
ambient: [0.1, 0.1, 0.1, 1],
diffuse: [Math.random(), Math.random(), Math.random(), 1],
specular: [1, 1, 1, 1],
// to make up for the fact the code does not have different positions
// for each model we'll move the vertices (bad)
vertices: vertices.map((v, i) => i % 3 === 0 ? v + modelNum * 3 : v),
indices: [
0, 1, 2, 2, 1, 3,
4, 5, 6, 6, 5, 7,
8, 9, 10, 10, 9, 11,
12, 13, 14, 14, 13, 15,
16, 17, 18, 18, 17, 19,
20, 21, 22, 22, 21, 23,
],
};
handleLoadedObject(filename, o);
++modelNum;
});
}
/**
* Creates the buffers that contain the geometry of the object
*/
function handleLoadedObject(filename, object) {
//console.info(filename + ' has been retrieved from the server');
var vertexBufferObject = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBufferObject);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(object.vertices), gl.STATIC_DRAW);
var normalBufferObject = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normalBufferObject);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(calcNormals(object.vertices, object.indices)), gl.STATIC_DRAW);
var indexBufferObject = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBufferObject);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(object.indices), gl.STATIC_DRAW);
object.vbo = vertexBufferObject;
object.ibo = indexBufferObject;
object.nbo = normalBufferObject;
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
objects.push(object);
}
/**
* Main rendering function. Called every 500ms according to WebGLStart function (see below)
*/
function drawScene() {
gl.clearColor(0.3, 0.3, 0.3, 1.0);
//gl.clearDepth(100.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.viewport(0, 0, c_width, c_height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
mat4.perspective(pMatrix, 60 * Math.PI / 180, c_width / c_height, 0.1, 1000.0);
gl.enableVertexAttribArray(prg.aVertexPosition);
gl.enableVertexAttribArray(prg.aVertexNormal);
for (var i = 0; i < objects.length; i++) {
var object = objects[i];
mat4.identity(mvMatrix);
mat4.translate(mvMatrix, mvMatrix, [0.0, 0.0, distance]); //Sets the camera to a reasonable distance to view the part
mat4.rotate(mvMatrix, mvMatrix, 30 * Math.PI / 180, [1, 0, 0]);
mat4.rotate(mvMatrix, mvMatrix, angle * Math.PI / 180, [0, 1, 0]);
if (object.alias == 'lightsource') {
var lightPos = gl.getUniform(prg, prg.uLightPosition);
mat4.translate(mvMatrix, mvMatrix, lightPos);
}
gl.uniformMatrix4fv(prg.uMVMatrix, false, mvMatrix);
gl.uniformMatrix4fv(prg.uPMatrix, false, pMatrix);
mat4.set(mvMatrix, nMatrix);
mat4.invert(nMatrix, nMatrix);
mat4.transpose(nMatrix, nMatrix);
const t3 = glMatrix.mat3.create();
glMatrix.mat3.fromMat4(t3, nMatrix);
gl.uniformMatrix3fv(prg.uNMatrix, false, t3);
gl.uniform4fv(prg.ambientProduct, object.ambient);
gl.uniform4fv(prg.diffuseProduct, object.diffuse);
gl.uniform4fv(prg.specularProduct, object.specular);
gl.bindBuffer(gl.ARRAY_BUFFER, object.vbo);
gl.vertexAttribPointer(prg.aVertexPosition, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(prg.aVertexPosition);
gl.bindBuffer(gl.ARRAY_BUFFER, object.nbo);
gl.vertexAttribPointer(prg.aVertexNormal, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(prg.aVertexNormal);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, object.ibo);
gl.drawElements(gl.TRIANGLES, object.indices.length, gl.UNSIGNED_SHORT, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
}
var lastTime = 0;
var angle = 0;
/**
* Updates the angle of rotation by a little bit each time
*/
function animate() {
var timeNow = new Date().getTime();
if (lastTime != 0) {
var elapsed = timeNow - lastTime;
if (animateFlag) angle += (90 * elapsed) / 10000.0;
}
lastTime = timeNow;
}
/**
* Render Loop
*/
function renderLoop() {
drawScene();
animate();
requestAnimationFrame(renderLoop);
}
/**
* Entry point. This function is invoked when the page is loaded
*/
function runWebGLApp() {
//Obtains a WebGL context
gl = document.getElementById("canvas-element-id").getContext('webgl');
//Initializes the program (shaders)
initProgram();
//Initializes lights
initLights();
//Load Scene
loadScene();
//Renders the scene!
renderLoop();
}
$('#slider-shininess').slider({
value: 200,
min: 1,
max: 300,
step: 1,
slide: updateShininess
});
$('#slider-x').slider({
value: 4.5,
min: -50,
max: 50,
step: 0.1,
slide: updateLightPosition,
change: updateLightPosition
});
$('#slider-y').slider({
value: 3.0,
min: 0,
max: 50,
step: 0.1,
slide: updateLightPosition,
change: updateLightPosition
});
$('#slider-z').slider({
value: 15.0,
min: -50,
max: 50,
step: 0.1,
slide: updateLightPosition,
change: updateLightPosition
});
$('#animate-btn').button();
$('#animate-btn').click(
function() {
if ($('#animate-btn:checked').val() == null) {
animateFlag = false;
} else {
animateFlag = true;
}
});
function updateShininess() {
var v = $('#slider-shininess').slider("value");
gl.uniform1f(prg.uShininess, v);
$('#slider-shininess-value').html(v);
}
function updateLightPosition() {
var x = $('#slider-x').slider("value");
var y = $('#slider-y').slider("value");
var z = $('#slider-z').slider("value");
gl.uniform3fv(prg.uLightPosition, [x, y, z]);
$('#slider-x-value').html(x);
$('#slider-y-value').html(y);
$('#slider-z-value').html(z);
}
function updateDistance() {
var d = $('#slider-distance').slider("value");
$('#slider-distance-value').html(distance);
distance = -d;
}
function updateObjectColor(alias, r, g, b) {
var object = getObject(alias);
if (object != null) {
object.diffuse = [r, g, b, 1.0];
}
}
// Calcule les normales des vertex. La normale de chaque vertex est
// la moyenne des triangles voisins.
//
// vertices: la liste des vertex.
// ind: la liste des indices.
// retour: la liste des normales par vertex.
function calcNormals(vertices, ind) {
var x = 0;
var y = 1;
var z = 2;
var v1 = [],
v2 = [],
thisNormal = [];
// initialiser la liste des normales.
var ns = [];
for (var i = 0; i < vertices.length; i++) {
ns[i] = 0.0;
}
for (var i = 0; i < ind.length; i = i + 3) {
//v1 = p1 - p0
v1[x] = vertices[3 * ind[i + 1] + x] - vertices[3 * ind[i] + x];
v1[y] = vertices[3 * ind[i + 1] + y] - vertices[3 * ind[i] + y];
v1[z] = vertices[3 * ind[i + 1] + z] - vertices[3 * ind[i] + z];
// v2 = p2 - p1
v2[x] = vertices[3 * ind[i + 2] + x] - vertices[3 * ind[i] + x];
v2[y] = vertices[3 * ind[i + 2] + y] - vertices[3 * ind[i] + y];
v2[z] = vertices[3 * ind[i + 2] + z] - vertices[3 * ind[i] + z];
// N = v2 x v1 (cross product).
thisNormal[x] = v1[y] * v2[z] - v1[z] * v2[y];
thisNormal[y] = v1[z] * v2[x] - v1[x] * v2[z];
thisNormal[z] = v1[x] * v2[y] - v1[y] * v2[x];
for (j = 0; j < 3; j++) {
// N += thisNormal. on additionne les normales.
ns[3 * ind[i + j] + x] = ns[3 * ind[i + j] + x] + thisNormal[x];
ns[3 * ind[i + j] + y] = ns[3 * ind[i + j] + y] + thisNormal[y];
ns[3 * ind[i + j] + z] = ns[3 * ind[i + j] + z] + thisNormal[z];
}
}
// Normalisation.
for (var i = 0; i < vertices.length; i = i + 3) {
var nn = [];
var len = 0;
for (var j = 0; j < 3; j++) {
nn[j] = ns[i + j];
len += nn[j] * nn[j];
}
// La norme de la normale.
len = Math.sqrt(len);
if (len == 0)
len = 0.00001;
for (var j = 0; j < 3; j++)
ns[i + j] = nn[j] / len;
}
return ns;
}
runWebGLApp();
<link href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet">
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
attribute vec3 aVertexNormal;
// matrice model-view combinee.
uniform mat4 uMVMatrix;
// matrice de projection
uniform mat4 uPMatrix;
// matrice des normales.
uniform mat3 uNMatrix;
// position de la lumiere.
uniform vec3 uLightPosition;
// La normale transformee
varying vec3 vNormal;
// la direction vertex-lumiere
varying vec3 vLightRay;
// la direction camera-vertex
varying vec3 vEyeVec;
uniform vec4 uLightAmbient;
uniform vec4 uLightDiffuse;
uniform vec4 uLightSpecular;
uniform vec4 uMaterialAmbient;
uniform vec4 uMaterialDiffuse;
uniform vec4 uMaterialSpecular;
void main(void) {
vec4 ambientProduct= uLightAmbient* uMaterialAmbient;
vec4 diffuseProduct= uLightDiffuse*uMaterialDiffuse;
vec4 specularProduct= uLightSpecular*uMaterialSpecular;
vec3 pos = (uMVMatrix*vec4(aVertexPosition, 1.0)).xyz;
// position de l'oeil/camera.
const vec3 eyePosition = vec3(0,0,-40);
//Transformed normal position
vNormal = normalize((uNMatrix* aVertexNormal).xyz) ;
//Transformed light position
vec4 light = uMVMatrix * vec4(uLightPosition,1.0);
vec3 lightPos = (uMVMatrix * light).xyz;
//Light position
vLightRay = normalize(pos - lightPos);
//Vector Eye
vEyeVec = -normalize(pos);
//Final vertex position
gl_Position = uPMatrix*uMVMatrix* vec4(aVertexPosition, 1.0);
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
#ifdef GL_ES
precision highp float;
#endif
varying vec3 vNormal;
varying vec3 vLightRay;
varying vec3 vEyeVec;
uniform vec4 ambientProduct;
uniform vec4 diffuseProduct;
uniform vec4 specularProduct;
uniform float uShininess;
void main(void)
{
vec4 diffuse = max(dot( vNormal,vLightRay), 0.0) * diffuseProduct;
vec3 H = normalize(vLightRay+vEyeVec);
vec4 specular =
pow(max(dot(vNormal, H), 0.0), uShininess) * specularProduct;
if (dot(vLightRay, vNormal) < 0.0)
specular = vec4(0.0, 0.0, 0.0, 1.0);
vec4 fColor = ambientProduct + diffuse + specular;
fColor.a = 1.0;
gl_FragColor =fColor;
}
</script>
<div id='top'>
<div id='contents'>
<div id='canvasContainer'>
<canvas id='canvas-element-id' width='480' height='400'>
Your browser does not support the HTML5 canvas element.
</canvas>
</div>
</div>
<div id='bottom'>
<table style='padding=0px'>
<tr>
<td>X:</td><td id='slider-x-value' width='30px'>4.5</td><td width='150px'><div id='slider-x'/></td>
</tr>
<tr>
<td>Y:</td><td id='slider-y-value' width='30px'>3.0</td><td width='150px'><div id='slider-y'/></td>
</tr>
<tr>
<td>Z:</td> <td id='slider-z-value' width='30px'>15.0</td><td width='150px'><div id='slider-z'/></td>
</tr>
</table>
</div>
<script src="https://cdn.jsdelivr.net/npm/gl-matrix#3.3.0/gl-matrix-min.js"></script>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<script src="https://greggman.github.io/webgl-lint/webgl-lint.js"
data-gman-debug-helper='
{
"warnUndefinedUniforms": false
}
'
></script>

Bump Mapping with javascript and glsl

/**
* 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>

THREE.js - passing attributes to BufferGeometry instead of ShaderMaterial?

I'm trying to get a THREE.js example for version 58 to run on a current version of THREE.js - here's the original example.
There were a few errors I was able to get rid of by just commenting stuff out, but one trickier error is this:
THREE.ShaderMaterial: attributes should now be defined in THREE.BufferGeometry instead.
THREE.ShaderMaterial: 'attributes' is not a property of this material.
The offending code is this:
var shaderMaterial = new THREE.ShaderMaterial( {
uniforms: uniforms,
attributes: attributes,
vertexShader: document.getElementById( 'vertexshader_lines' ).textContent,
fragmentShader: document.getElementById( 'fragmentshader_lines' ).textContent,
});
Where attributes is defined a few lines above as:
var attributes = {
draw: { type: 'f', value: [] },
seed: { type: 'f', value: [] },
seed2: { type: 'f', value: [] },
customColor: { type: 'c', value: [] },
index2: { type: 'f', value: [] },
norm: { type: 'v3', value: [] },
};
I tried to fix this by commenting out the attributes parameter passed to the THREE.ShaderMaterial constructor, and instead converting the THREE.Geometry from the original version (named lineGeo) into a THREE.BufferedGeometry and then passing in the attributes using its addAttribute method, like so:
var bufferLineGeo = new THREE.BufferGeometry();
bufferLineGeo.fromGeometry(lineGeo);
var values_bColor = new Float32Array(values_color.length * 3);
var values_bNorm = new Float32Array(values_norm.length * 3);
bufferLineGeo.addAttribute('draw', new THREE.BufferAttribute(new Float32Array(values_draw), 1));
bufferLineGeo.addAttribute('seed', new THREE.BufferAttribute(new Float32Array(values_seed), 1));
bufferLineGeo.addAttribute('seed2', new THREE.BufferAttribute(new Float32Array(values_seed2), 1));
bufferLineGeo.addAttribute('customColor', new THREE.BufferAttribute(values_bColor, 3).copyColorsArray( values_color));
bufferLineGeo.addAttribute('index2', new THREE.BufferAttribute(new Float32Array(values_index2), 1));
bufferLineGeo.addAttribute('norm', new THREE.BufferAttribute(values_bNorm, 3).copyVector3sArray(values_norm));
This didn't work and I'm at a dead end now for what to try next. It renders nothing - I'm left with a black screen and nothing in the console to tell me what went wrong.
Minimal, Complete Example of it not working with current versions of THREE.js
<!doctype html>
<html lang="en">
<head>
<title>Long hair</title>
<style type="text/css">
body {
background:#000000;
}
</style>
</head>
<body>
<!--<script src="../build_r58/three.min.js"></script>-->
<script src="../js/three.min.js"></script>
<script type="x-shader/x-vertex" id="vertexshader_lines">
uniform float globalTime;
uniform vec3 gravity;
uniform vec3 gravity2;
uniform float spacing;
attribute vec3 customColor;
attribute float seed;
attribute float seed2;
attribute float draw;
attribute float index2;
attribute vec3 norm;
varying vec3 vColor;
varying float vDraw;
varying vec3 vNormal;
void main() {
vDraw = draw;
vColor = customColor;
vec3 displacement = vec3(0.0,0.0,0.0);
vec3 forceDirection = vec3(0.0,0.0,0.0);
float displacementFactor = pow(index2, 1.2);
float displacementFactor2 = pow(index2, 2.5);
float displacementFactor3 = pow(1.0-index2, 1.0);
// "gravity"
vec3 g = gravity;
g.x *= displacementFactor2*seed2;
// "wind"
forceDirection.x = sin(globalTime*0.1+seed2*5.0+index2*1.0) * 0.1*displacementFactor;
forceDirection.y = cos(globalTime*0.7+seed2*5.0+index2*1.0) * 0.1*displacementFactor3;
forceDirection.z = sin(globalTime*0.7+seed2*5.0+index2*4.0) * 0.1*displacementFactor2;
displacement = g + forceDirection + ((1.0-index2)*gravity2)*seed;
vec3 aNormal = norm;
aNormal.xyz += displacement*displacementFactor;
vNormal = norm*(1.0-index2);
vNormal += (gravity2-gravity)*0.05;
vec3 animated = position;
// curl it slightly
animated.x += aNormal.x*index2*30.0*displacementFactor3;
animated += aNormal*index2*(spacing*seed);
if (animated.y < -150.0+seed2*20.0) {
animated.y = -150.0+seed2*20.0;
vDraw = 0.0;
}
vec4 mvPosition = modelViewMatrix * vec4( animated, 1.0 );
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader_lines">
uniform vec3 color;
varying vec3 vColor;
varying float vDraw;
varying vec3 vNormal;
void main() {
if (vDraw == 0.0) {
discard;
}
float depth = gl_FragCoord.z / gl_FragCoord.w;
float fogFactor = smoothstep( 450.0, 300.0, depth );
// light
vec3 light = vec3(0.5,1.0,0.8);
float d = pow(max(0.25,dot(vNormal.xyz, light))*2.0, 1.5);
gl_FragColor = vec4( (color * vColor) * d * fogFactor, 1.0 );
}
</script>
<script>
var camera, scene, renderer;
var delta;
var time;
var oldTime;
var uniforms;
var hair;
var gravity = new THREE.Vector3(0,-5,0);
var gravity2 = new THREE.Vector3(0,-5,0);
init();
render();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.z = 400;
camera.lookAt(scene.position);
scene.add( camera );
var attributes = {
draw: { type: 'f', value: [] },
seed: { type: 'f', value: [] },
seed2: { type: 'f', value: [] },
customColor: { type: 'c', value: [] },
index2: { type: 'f', value: [] },
norm: { type: 'v3', value: [] },
};
uniforms = {
color: { type: "c", value: new THREE.Color( 0xe4b67b ) },
globalTime: { type: "f", value: 0.0 },
gravity: { type: "v3", value: gravity },
gravity2: { type: "v3", value: gravity2 },
spacing: { type: "f", value: 25.0 },
};
var shaderMaterial = new THREE.ShaderMaterial( {
uniforms: uniforms,
//attributes: attributes,
vertexShader: document.getElementById( 'vertexshader_lines' ).textContent,
fragmentShader: document.getElementById( 'fragmentshader_lines' ).textContent,
});
shaderMaterial.linewidth = 1;
var lineGeo = new THREE.Geometry();
var radius = 15;
var num = 70;
var baseGeo = new THREE.SphereGeometry(radius, num, num, undefined, undefined, 0.2, Math.PI*0.8);
for (var i = 0; i < baseGeo.vertices.length; i++) {
baseGeo.vertices[i].x += Math.random()*4-2;
baseGeo.vertices[i].y += Math.random()*4-2;
baseGeo.vertices[i].z += Math.random()*4-2;
}
var seedArray = [];
var seedArray2 = [];
var colorArray = [];
var drawArray = [];
var index2Array = [];
var normArray = [];
for (var i = 0; i < baseGeo.vertices.length; i++) {
var num = 30;
var base = baseGeo.vertices[i];
var seed = 1+Math.random()*0.5;
var seed2 = 0.25 + Math.random()*0.75;
var norm = new THREE.Vector3().copy(base).normalize();
norm = norm.normalize();
var black = 0.65+Math.random()*0.75;
for (var j = 0; j < num; j++) {
var vertex = new THREE.Vector3().copy(base);
var color = new THREE.Color(0xffffff);
color.setRGB(1.0*black,1.0*black,1.0*black);
lineGeo.vertices.push( vertex );
colorArray.push( color );
seedArray.push( seed );
seedArray2.push( seed2 );
index2Array.push( j/num );
normArray.push( norm );
if (j == num-1 || j == 0) {
drawArray.push( 0 );
} else {
drawArray.push( 1 );
}
}
}
var vertices = lineGeo.vertices;
var values_color = attributes.customColor.value;
var values_seed = attributes.seed.value;
var values_seed2 = attributes.seed2.value;
var values_draw = attributes.draw.value;
var values_index2 = attributes.index2.value;
var values_norm = attributes.norm.value;
for( var v = 0; v < vertices.length; v++ ) {
values_seed[ v ] = seedArray[v];
values_seed2[ v ] = seedArray2[v];
values_draw[ v ] = drawArray[v];
values_color[ v ] = colorArray[v];
values_index2[ v ] = index2Array[v];
values_norm[ v ] = normArray[v];
}
var bufferLineGeo = new THREE.BufferGeometry();
bufferLineGeo.fromGeometry(lineGeo);
var values_bColor = new Float32Array(values_color.length * 3);
var values_bNorm = new Float32Array(values_norm.length * 3);
bufferLineGeo.addAttribute('draw', new THREE.BufferAttribute(new Float32Array(values_draw), 1));
bufferLineGeo.addAttribute('seed', new THREE.BufferAttribute(new Float32Array(values_seed), 1));
bufferLineGeo.addAttribute('seed2', new THREE.BufferAttribute(new Float32Array(values_seed2), 1));
bufferLineGeo.addAttribute('customColor', new THREE.BufferAttribute(values_bColor, 3).copyColorsArray( values_color));
bufferLineGeo.addAttribute('index2', new THREE.BufferAttribute(new Float32Array(values_index2), 1));
bufferLineGeo.addAttribute('norm', new THREE.BufferAttribute(values_bNorm, 3).copyVector3sArray(values_norm));
hair = new THREE.Line(bufferLineGeo,
//lineGeo,
shaderMaterial, THREE.LineStrip );
scene.add(hair);
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function render() {
requestAnimationFrame(render);
time = new Date().getTime();
delta = time - oldTime;
oldTime = time;
if (isNaN(delta) || delta > 1000 || delta == 0 ) {
delta = 1000/60;
}
uniforms.globalTime.value += delta * 0.005;
renderer.render( scene, camera );
}
</script>
</body>
</html>
Nearly Identical code as above, but successfully runs on version 58 of THREE.js
<!doctype html>
<html lang="en">
<head>
<title>Long hair</title>
<style type="text/css">
body {
background:#000000;
}
</style>
</head>
<body>
<script src="../build_r58/three.min.js"></script>
<!--<script src="../js/three.min.js"></script>-->
<script type="x-shader/x-vertex" id="vertexshader_lines">
uniform float globalTime;
uniform vec3 gravity;
uniform vec3 gravity2;
uniform float spacing;
attribute vec3 customColor;
attribute float seed;
attribute float seed2;
attribute float draw;
attribute float index2;
attribute vec3 norm;
varying vec3 vColor;
varying float vDraw;
varying vec3 vNormal;
void main() {
vDraw = draw;
vColor = customColor;
vec3 displacement = vec3(0.0,0.0,0.0);
vec3 forceDirection = vec3(0.0,0.0,0.0);
float displacementFactor = pow(index2, 1.2);
float displacementFactor2 = pow(index2, 2.5);
float displacementFactor3 = pow(1.0-index2, 1.0);
// "gravity"
vec3 g = gravity;
g.x *= displacementFactor2*seed2;
// "wind"
forceDirection.x = sin(globalTime*0.1+seed2*5.0+index2*1.0) * 0.1*displacementFactor;
forceDirection.y = cos(globalTime*0.7+seed2*5.0+index2*1.0) * 0.1*displacementFactor3;
forceDirection.z = sin(globalTime*0.7+seed2*5.0+index2*4.0) * 0.1*displacementFactor2;
displacement = g + forceDirection + ((1.0-index2)*gravity2)*seed;
vec3 aNormal = norm;
aNormal.xyz += displacement*displacementFactor;
vNormal = norm*(1.0-index2);
vNormal += (gravity2-gravity)*0.05;
vec3 animated = position;
// curl it slightly
animated.x += aNormal.x*index2*30.0*displacementFactor3;
animated += aNormal*index2*(spacing*seed);
if (animated.y < -150.0+seed2*20.0) {
animated.y = -150.0+seed2*20.0;
vDraw = 0.0;
}
vec4 mvPosition = modelViewMatrix * vec4( animated, 1.0 );
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader_lines">
uniform vec3 color;
varying vec3 vColor;
varying float vDraw;
varying vec3 vNormal;
void main() {
if (vDraw == 0.0) {
discard;
}
float depth = gl_FragCoord.z / gl_FragCoord.w;
float fogFactor = smoothstep( 450.0, 300.0, depth );
// light
vec3 light = vec3(0.5,1.0,0.8);
float d = pow(max(0.25,dot(vNormal.xyz, light))*2.0, 1.5);
gl_FragColor = vec4( (color * vColor) * d * fogFactor, 1.0 );
}
</script>
<script>
var camera, scene, renderer;
var delta;
var time;
var oldTime;
var uniforms;
var hair;
var gravity = new THREE.Vector3(0,-5,0);
var gravity2 = new THREE.Vector3(0,-5,0);
init();
render();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.z = 400;
camera.lookAt(scene.position);
scene.add( camera );
var attributes = {
draw: { type: 'f', value: [] },
seed: { type: 'f', value: [] },
seed2: { type: 'f', value: [] },
customColor: { type: 'c', value: [] },
index2: { type: 'f', value: [] },
norm: { type: 'v3', value: [] },
};
uniforms = {
color: { type: "c", value: new THREE.Color( 0xe4b67b ) },
globalTime: { type: "f", value: 0.0 },
gravity: { type: "v3", value: gravity },
gravity2: { type: "v3", value: gravity2 },
spacing: { type: "f", value: 25.0 },
};
var shaderMaterial = new THREE.ShaderMaterial( {
uniforms: uniforms,
attributes: attributes,
vertexShader: document.getElementById( 'vertexshader_lines' ).textContent,
fragmentShader: document.getElementById( 'fragmentshader_lines' ).textContent,
});
shaderMaterial.linewidth = 1;
var lineGeo = new THREE.Geometry();
var radius = 15;
var num = 70;
var baseGeo = new THREE.SphereGeometry(radius, num, num, undefined, undefined, 0.2, Math.PI*0.8);
for (var i = 0; i < baseGeo.vertices.length; i++) {
baseGeo.vertices[i].x += Math.random()*4-2;
baseGeo.vertices[i].y += Math.random()*4-2;
baseGeo.vertices[i].z += Math.random()*4-2;
}
var seedArray = [];
var seedArray2 = [];
var colorArray = [];
var drawArray = [];
var index2Array = [];
var normArray = [];
for (var i = 0; i < baseGeo.vertices.length; i++) {
var num = 30;
var base = baseGeo.vertices[i];
var seed = 1+Math.random()*0.5;
var seed2 = 0.25 + Math.random()*0.75;
var norm = new THREE.Vector3().copy(base).normalize();
norm = norm.normalize();
var black = 0.65+Math.random()*0.75;
for (var j = 0; j < num; j++) {
var vertex = new THREE.Vector3().copy(base);
var color = new THREE.Color(0xffffff);
color.setRGB(1.0*black,1.0*black,1.0*black);
lineGeo.vertices.push( vertex );
colorArray.push( color );
seedArray.push( seed );
seedArray2.push( seed2 );
index2Array.push( j/num );
normArray.push( norm );
if (j == num-1 || j == 0) {
drawArray.push( 0 );
} else {
drawArray.push( 1 );
}
}
}
var vertices = lineGeo.vertices;
var values_color = attributes.customColor.value;
var values_seed = attributes.seed.value;
var values_seed2 = attributes.seed2.value;
var values_draw = attributes.draw.value;
var values_index2 = attributes.index2.value;
var values_norm = attributes.norm.value;
for( var v = 0; v < vertices.length; v++ ) {
values_seed[ v ] = seedArray[v];
values_seed2[ v ] = seedArray2[v];
values_draw[ v ] = drawArray[v];
values_color[ v ] = colorArray[v];
values_index2[ v ] = index2Array[v];
values_norm[ v ] = normArray[v];
}
/*var bufferLineGeo = new THREE.BufferGeometry();
bufferLineGeo.fromGeometry(lineGeo);
var values_bColor = new Float32Array(values_color.length * 3);
var values_bNorm = new Float32Array(values_norm.length * 3);
bufferLineGeo.addAttribute('draw', new THREE.BufferAttribute(new Float32Array(values_draw), 1));
bufferLineGeo.addAttribute('seed', new THREE.BufferAttribute(new Float32Array(values_seed), 1));
bufferLineGeo.addAttribute('seed2', new THREE.BufferAttribute(new Float32Array(values_seed2), 1));
bufferLineGeo.addAttribute('customColor', new THREE.BufferAttribute(values_bColor, 3).copyColorsArray( values_color));
bufferLineGeo.addAttribute('index2', new THREE.BufferAttribute(new Float32Array(values_index2), 1));
bufferLineGeo.addAttribute('norm', new THREE.BufferAttribute(values_bNorm, 3).copyVector3sArray(values_norm));*/
hair = new THREE.Line(//bufferLineGeo,
lineGeo,
shaderMaterial, THREE.LineStrip );
scene.add(hair);
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function render() {
requestAnimationFrame(render);
time = new Date().getTime();
delta = time - oldTime;
oldTime = time;
if (isNaN(delta) || delta > 1000 || delta == 0 ) {
delta = 1000/60;
}
uniforms.globalTime.value += delta * 0.005;
renderer.render( scene, camera );
}
</script>
</body>
</html>
You can get a copy of three.min.js for version 58 here.
You can get the current version of three.min.js from here.
This line
bufferLineGeo.fromGeometry( lineGeo );
will not populate the position attribute if no faces are defined.
Do this instead
var positions = new Float32Array( vertices.length * 3 );
bufferLineGeo.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ).copyVector3sArray( vertices ) );
three.js r.89

Categories

Resources