WebGl: Making Objects Move Together and Separately - javascript

I am trying to make a robot that uses sliders that rotates the body all together and body parts move together and separately, i.e. moving the upper arm moves the entire arm and rotating the body rotates every body part. But I also want the body parts to move separately such as the head moves and the lower arm moves by itself.
My problem is that not all of my objects are showing up, just the first 3 objects and I feel like that has something to do with the use of theta for the sliders. Also when I move the head, the arm moves as well. I understand it has something to do with the model view matrix and that every transformation I make will keep applying to the rest, but when I try to use pop() and push() it makes the object disappear or freeze and can't be moved. Can someone point me in the right direction? I included most of my code but not all the variables.
var theta = [0,0,0];
function scale4(a, b, c) {
var result = mat4();
result[0][0] = a;
result[1][1] = b;
result[2][2] = c;
return result;
}
window.onload = function init()
{
var canvas = document.getElementById( "webgl-robot" );
gl = WebGLUtils.setupWebGL( canvas );
if ( !gl ) { alert( "WebGL isn't available" ); }
gl.viewport( 0, 0, canvas.width, canvas.height );
gl.clearColor( 1.0, 1.0, 1.0, 1.0 );
gl.enable( gl.DEPTH_TEST );
program = initShaders( gl, "vertex-shader", "fragment-shader" );
gl.useProgram( program);
colorCube();
program = initShaders( gl, "vertex-shader", "fragment-shader" );
gl.useProgram( program );
var vBuffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, vBuffer );
gl.bufferData( gl.ARRAY_BUFFER, flatten(points), gl.DYNAMIC_DRAW );
var vPosition = gl.getAttribLocation( program, "vPosition" );
gl.vertexAttribPointer( vPosition, 4, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( vPosition );
var cBuffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, cBuffer );
gl.bufferData( gl.ARRAY_BUFFER, flatten(colors), gl.DYNAMIC_DRAW );
var vColor = gl.getAttribLocation( program, "vColor" );
gl.vertexAttribPointer( vColor, 4, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( vColor );
modelView = gl.getUniformLocation( program, "modelView" );
projection = gl.getUniformLocation( program, "projection" );
document.getElementById("slider1").onchange = function() {
theta[0] = event.srcElement.value;
};
document.getElementById("slider2").onchange = function() {
theta[1] = event.srcElement.value;
};
document.getElementById("slider3").onchange = function() {
theta[2] = event.srcElement.value;
};
document.getElementById("slider4").onchange = function() {
theta[3] = event.srcElement.value;
};
document.getElementById("slider5").onchange = function() {
theta[4] = event.srcElement.value;
};
modelView2 = gl.getUniformLocation( program, "modelView" );
projection2 = gl.getUniformLocation( program, "projection" );
modelViewMatrixLoc = gl.getUniformLocation( program, "modelViewMatrix");
projection = gl.getUniformLocation( program, "projection" );
projectionMatrix = ortho(-10, 10, -10, 10, -10, 10);
gl.uniformMatrix4fv( gl.getUniformLocation(program, "projectionMatrix"),
false, flatten(projectionMatrix) );
render();
}
function base() {
var s = scale4(BASE_WIDTH, BASE_HEIGHT, BASE_WIDTH);
var instanceMatrix = mult( translate( 0.0, 0.5 * BASE_HEIGHT, 0.0 ), s);
var t = mult(modelViewMatrix, instanceMatrix);
gl.uniformMatrix4fv(modelViewMatrixLoc, false, flatten(t) );
gl.drawArrays( gl.TRIANGLES, 0, 36 );
}
function head() {
var s = scale4(HEAD_WIDTH, HEAD_HEIGHT, HEAD_WIDTH);
var instanceMatrix = mult(translate( 0.0, 0.5 * HEAD_HEIGHT, 0.0 ),s);
var t = mult(modelViewMatrix, instanceMatrix);
gl.uniformMatrix4fv( modelViewMatrixLoc, false, flatten(t) );
gl.drawArrays( gl.TRIANGLES, 0, 36 );
}
function leftUpperArm()
{
var s = scale4(LEFT_UPPER_WIDTH, LEFT_UPPER_HEIGHT, LEFT_UPPER_WIDTH);
var instanceMatrix = mult( translate( 0.0, 0.5 * LEFT_UPPER_HEIGHT, 0.0 ),
s);
var t = mult(modelViewMatrix, instanceMatrix);
gl.uniformMatrix4fv( modelViewMatrixLoc, false, flatten(t) );
gl.drawArrays( gl.TRIANGLES, 0, 36 );
}
function leftLowerArm()
{
var s = scale4(LEFT_LOWER_WIDTH, LEFT_LOWER_HEIGHT, LEFT_LOWER_WIDTH);
var instanceMatrix = mult( translate( 0.0, 0.5 * LEFT_LOWER_HEIGHT, 0.0 ),
s);
var t = mult(modelViewMatrix, instanceMatrix);
gl.uniformMatrix4fv( modelViewMatrixLoc, false, flatten(t) );
gl.drawArrays( gl.TRIANGLES, 0, 36 );
}
function rightUpperArm()
{
var s = scale4(RIGHT_UPPER_WIDTH, RIGHT_UPPER_HEIGHT, RIGHT_UPPER_WIDTH);
var instanceMatrix = mult( translate( -9.3, 0.5 * RIGHT_UPPER_HEIGHT, 0.0
), s);
var t = mult(modelViewMatrix, instanceMatrix);
gl.uniformMatrix4fv( modelViewMatrixLoc, false, flatten(t) );
gl.drawArrays( gl.TRIANGLES, 0, 36 );
}
function render() {
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
modelViewMatrix = rotate(theta[Base], 0, 1, 0 );
base();
modelViewMatrix = mult(modelViewMatrix, translate(0.0, BASE_HEIGHT, 0.0));
modelViewMatrix = mult(modelViewMatrix, rotate(theta[Head], 0, 0, 1 ));
head();
modelViewMatrix = mult(modelViewMatrix, translate(1.3, -0.7, 0.0));
modelViewMatrix = mult(modelViewMatrix, rotate(theta[LeftUpper], 1, 0, 0)
);
leftUpperArm();
modelViewMatrix = mult(modelViewMatrix, translate(0.0, LEFT_UPPER_HEIGHT,
0.0));
modelViewMatrix = mult(modelViewMatrix, rotate(theta[LeftLower], 0, 0, 1 ));
leftLowerArm();
modelViewMatrix = mult(modelViewMatrix, translate(5.3, -0.7, 0.0));
modelViewMatrix = mult(modelViewMatrix, rotate(theta[RightUpper], 1, 0, 0)
);
rightUpperArm();
requestAnimFrame(render);
}

The normal way to make/move/animate something like that (a hierarchical model) is to use a scenegraph and/or a matrix stack.
Using a matrix stack you start at the root (could be the waist, could be a point between the feet) then you walk through the tree of the character, multiplying matrices
root
waist
left thigh
left lower leg
left foot
right thigh
right lower leg
right foot
stomach
chest
left upper arm
left forearm
left hand
right upper arm
right forearm
right hand
neck
head
left eye
right eye
At each point in the tree you save the current model matrix (push it on the stack), then pass that down to the children to add their orientation to.
This way if you move/rotate the chest, everything deeper in the tree of parts (arms, neck, head) automatically move/rotate with it.
You can see in the sample below using a matrix stack. Only the chest is animated but because of the matrix stack the arms and the head move with the chest.
const m4 = twgl.m4;
const v3 = twgl.v3;
const gl = document.querySelector("canvas").getContext("webgl");
const vs = `
attribute vec4 position;
attribute vec3 normal;
uniform mat4 u_projection;
uniform mat4 u_view;
uniform mat4 u_model;
varying vec3 v_normal;
void main() {
gl_Position = u_projection * u_view * u_model * position;
v_normal = mat3(u_model) * normal; // better to use inverse-transpose-model
}
`
const fs = `
precision mediump float;
varying vec3 v_normal;
uniform vec3 u_lightDir;
uniform vec3 u_color;
void main() {
float light = dot(normalize(v_normal), u_lightDir) * .5 + .5;
gl_FragColor = vec4(u_color * light, 1);
}
`;
// compiles shaders, links program, looks up attributes
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
const cubeBufferInfo = twgl.primitives.createCubeBufferInfo(gl, 1);
const stack = [];
const color = [1, .5, .3];
const lightDir = v3.normalize([1, 5, 10]);
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.useProgram(programInfo.program);
const fov = Math.PI * .25;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.01;
const zFar = 100;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const cameraPosition = [1, 4, 10]
const target = [0, 3, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(cameraPosition, target, up);
const view = m4.inverse(camera);
// make base position of robot
let m = m4.translation([0, 0, 0]);
pushMatrix(m);
{
// move up to waist
m = m4.translate(m, [0, 3, 0]);
drawCube(projection, view, m);
pushMatrix(m);
{
// move to left thigh
m = m4.translate(m, [1.1, -.5, 0]);
drawCube(projection, view, m);
pushMatrix(m);
{
// move to left foot
m = m4.translate(m, [0, -1.1, 0]);
drawCube(projection, view, m);
}
m = popMatrix(m);
}
m = popMatrix(m);
pushMatrix(m);
{
// move to right thigh
m = m4.translate(m, [-1.1, -.5, 0]);
drawCube(projection, view, m);
pushMatrix(m);
{
// move to right foot
m = m4.translate(m, [0, -1.1, 0]);
drawCube(projection, view, m);
}
m = popMatrix(m);
}
m = popMatrix(m);
pushMatrix(m);
{
// move to chest
m = m4.translate(m, [0, 1.1, 0]);
m = m4.rotateY(m, Math.sin(time) * .6);
drawCube(projection, view, m);
pushMatrix(m);
{
// move to left arm
m = m4.translate(m, [-1.1, 0, 0]);
drawCube(projection, view, m);
}
m = popMatrix(m);
pushMatrix(m);
{
// move to right arm
m = m4.translate(m, [1.1, 0, 0]);
drawCube(projection, view, m);
}
m = popMatrix(m);
pushMatrix(m);
{
// move to head
m = m4.translate(m, [0, 1.1, 0]);
drawCube(projection, view, m);
}
m = popMatrix(m);
}
m = popMatrix(m);
}
m = popMatrix();
requestAnimationFrame(render);
}
requestAnimationFrame(render);
function pushMatrix(m) {
stack.push(m);
}
function popMatrix() {
return stack.pop();
}
function drawCube(projection, view, model) {
// there is no reason to call these each time since we're always
// using the same cube but a real robot would probably
// have different parts for each section:
// leg, arm, left hand, right foot...
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, cubeBufferInfo);
// calls gl.uniformXXX
twgl.setUniforms(programInfo, {
u_color: color,
u_lightDir: lightDir,
u_projection: projection,
u_view: view,
u_model: model,
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, cubeBufferInfo);
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas></canvas>
A scenegraph is an organizational tool so instead of writing all the code you see in the sample above you can more generalize things by writing some code that walks the scenegraph and computes the matricies.

Related

How can i run ShaderToy code in standalone windows web browser [duplicate]

I'm new to programming shaders and I want to create a shader with WebGL and GLSL. To see how it really works, I want to test a shader from Shadertoy. But how do you take the code from Shadertoy and actually get to run it in a J/S file? Do you just need to copy the code from Shadertoy into the fragment shader, or what?
See the following very basic example. You can put the Shadertoy code in the mainImage function in the fragment shader:
(function loadscene() {
var canvas, gl, vp_size, prog, bufObj = {}, mousepos = [0, 0];
function initScene() {
canvas = document.getElementById( "ogl-canvas");
gl = canvas.getContext( "experimental-webgl" );
if ( !gl )
return;
canvas.addEventListener('mousemove', (e) => {
mousepos = [e.clientX, e.clientY];
});
progDraw = gl.createProgram();
for (let i = 0; i < 2; ++i) {
let source = document.getElementById(i==0 ? "draw-shader-vs" : "draw-shader-fs").text;
let shaderObj = gl.createShader(i==0 ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER);
gl.shaderSource(shaderObj, source);
gl.compileShader(shaderObj);
let status = gl.getShaderParameter(shaderObj, gl.COMPILE_STATUS);
if (!status) alert(gl.getShaderInfoLog(shaderObj));
gl.attachShader(progDraw, shaderObj);
gl.linkProgram(progDraw);
}
status = gl.getProgramParameter(progDraw, gl.LINK_STATUS);
if ( !status ) alert(gl.getProgramInfoLog(progDraw));
progDraw.inPos = gl.getAttribLocation(progDraw, "inPos");
progDraw.iTime = gl.getUniformLocation(progDraw, "iTime");
progDraw.iMouse = gl.getUniformLocation(progDraw, "iMouse");
progDraw.iResolution = gl.getUniformLocation(progDraw, "iResolution");
gl.useProgram(progDraw);
var pos = [ -1, -1, 1, -1, 1, 1, -1, 1 ];
var inx = [ 0, 1, 2, 0, 2, 3 ];
bufObj.pos = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.pos );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( pos ), gl.STATIC_DRAW );
bufObj.inx = gl.createBuffer();
bufObj.inx.len = inx.length;
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( inx ), gl.STATIC_DRAW );
gl.enableVertexAttribArray( progDraw.inPos );
gl.vertexAttribPointer( progDraw.inPos, 2, gl.FLOAT, false, 0, 0 );
gl.enable( gl.DEPTH_TEST );
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
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 render(deltaMS) {
gl.viewport( 0, 0, canvas.width, canvas.height );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
gl.uniform1f(progDraw.iTime, deltaMS/1000.0);
gl.uniform2f(progDraw.iResolution, canvas.width, canvas.height);
gl.uniform2f(progDraw.iMouse, mousepos[0], mousepos[1]);
gl.drawElements( gl.TRIANGLES, bufObj.inx.len, gl.UNSIGNED_SHORT, 0 );
requestAnimationFrame(render);
}
initScene();
})();
<script id="draw-shader-fs" type="x-shader/x-fragment">
precision mediump float;
uniform vec2 iResolution;
uniform vec2 iMouse;
uniform float iTime;
void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
vec2 st = fragCoord.xy / iResolution.xy;
fragColor = vec4(st, 0.0, 1.0);
}
void main()
{
mainImage( gl_FragColor, gl_FragCoord.xy );
}
</script>
<script id="draw-shader-vs" type="x-shader/x-vertex">
attribute vec2 inPos;
void main()
{
gl_Position = vec4(inPos, 0.0, 1.0);
}
</script>
<canvas id="ogl-canvas" style="border: none"></canvas>

two equal object on same canvas (split mode) webgl

I'm try to use the same object (a simple cube) in split screen.
Initially I create two canvas, with two different programs and I used a same point for render twice a cube.
Obviously didn't work and i read on this Topic that I can't do this way.
The answer suggest to use
single canvas using gl.enable(gl.SCISSOR_TEST), gl.scissor and
gl.viewport
I'm new with this stuff and i don't understand how to do.
He suggest an example too, but is very complex and I don't understand.
My example is very simple, i just want draw this two cube(from the same points) with different projection just for see in real time the difference between different projection.
Anyone can help me?
Edit: As suggest from Gman i edit my code in this way
window.onload = function init() {
canvas = document.getElementById( "gl-canvas" );
gl = WebGLUtils.setupWebGL( canvas );
if ( !gl ) { alert( "WebGL isn't available" ); }
//4
const width = gl.canvas.width;
const height = gl.canvas.height;
const displayWidth = gl.canvas.clientWidth;
const displayHeight = gl.canvas.clientHeight;
gl.clearColor( 1.0, 1.0, 1.0, 1.0 );
gl.viewport( 0, 0, canvas.width, canvas.height );
gl.enable(gl.DEPTH_TEST);
//
// Load shaders and initialize attribute buffers
//
program = initShaders( gl, "vertex-shader", "fragment-shader" );
gl.useProgram( program );
colorCube();
render(0, 0, width / 2, height, displayWidth / 2, displayHeight);
// draw on right
render(width / 2, 0, width / 2, height, displayWidth / 2, displayHeight);
and the render function is
var render = function(drawX, drawY, drawWidth, drawHeight, dispWidth, dispHeight) {
eye = vec3(radius*Math.sin(phi), radius*Math.sin(theta),radius*Math.cos(phi));
gl.enable(gl.SCISSOR_TEST);
gl.viewport(drawX, drawY, drawWidth, drawHeight);
gl.scissor(drawX, drawY, drawWidth, drawHeight);
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
mvMatrix = lookAt(eye, at , up);
const aspect = dispWidth / dispHeight;
pMatrix = ortho(left, right, bottom, ytop, near, far);
gl.uniformMatrix4fv( modelView, false, flatten(mvMatrix) );
gl.uniformMatrix4fv( projection, false, flatten(pMatrix) );
gl.drawArrays( gl.TRIANGLES, 0, numVertices );
//requestAnimFrame(render);
}
if i don't remove requestAnimFrame i see the 2 cube just for a sec and after all will be delete.
There is nothing hard about using gl.viewport and gl.scissor
A typical WebGL program does this to render
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const projection = someProjectionFunction(fieldOfView, aspect, zNear, zFar)
... draw stuff ...
So, let's change that into a function that takes a projection
function render(drawX, drawY, drawWidth, projection) {
gl.viewport(drawX, drawY, drawWidth, drawHeight);
... draw stuff ...
}
We can now call it like this
const width = gl.canvas.width;
const height = gl.canvas.height;
const displayWidth = gl.canvas.clientWidth;
const displayHeight = gl.canvas.clientHeight;
const aspect = displayWidth / displayHeight;
const projection = someProjectionFunction(fieldOfView, aspect, zNear, zFar)
// draw on left
render(0, 0, width / 2, height, projection);
// draw on right
render(width / 2, 0, width / 2, height, projection);
That already handles the viewport part and will work. All that's left is the scissor
function render(drawX, drawY, drawWidth, drawHeight, projection) {
gl.viewport(drawX, drawY, drawWidth, drawHeight);
gl.scissor(drawX, drawY, drawWidth, drawHeight);
gl.enable(gl.SCISSOR_TEST);
... draw stuff using projection ...
}
Now go update it pass in more info like a different projection or a different camera
const gl = document.querySelector('canvas').getContext('webgl');
const m4 = twgl.m4;
const vs = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
const fs = `
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0, 0, 1);
}
`;
const program = twgl.createProgram(gl, [vs, fs]);
const posLoc = gl.getAttribLocation(program, 'position');
const matLoc = gl.getUniformLocation(program, 'matrix');
const buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
-1, -1, -1,
1, -1, -1,
-1, 1, -1,
1, 1, -1,
-1, -1, 1,
1, -1, 1,
-1, 1, 1,
1, 1, 1,
]), gl.STATIC_DRAW);
const indices = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indices);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([
0, 1, 1, 3, 3, 2, 2, 0,
4, 5, 5, 7, 7, 6, 6, 4,
0, 4, 1, 5, 3, 7, 2, 6,
]), gl.STATIC_DRAW);
function renderLoop(time) {
time *= 0.001;
function render(drawX, drawY, drawWidth, drawHeight, projection) {
gl.viewport(drawX, drawY, drawWidth, drawHeight);
gl.scissor(drawX, drawY, drawWidth, drawHeight);
gl.enable(gl.SCISSOR_TEST);
gl.clear(gl.COLOR_BUFFER_BIT);
let mat = m4.copy(projection);
mat = m4.translate(mat, [0, 0, -5]);
mat = m4.rotateZ(mat, time);
mat = m4.rotateX(mat, time * 0.5);
gl.useProgram(program);
gl.uniformMatrix4fv(matLoc, false, mat);
gl.enableVertexAttribArray(posLoc);
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.vertexAttribPointer(posLoc, 3, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indices);
gl.drawElements(gl.LINES, 24, gl.UNSIGNED_SHORT, 0);
}
const width = gl.canvas.width;
const height = gl.canvas.height;
const displayWidth = gl.canvas.clientWidth;
const displayHeight = gl.canvas.clientHeight;
// draw on left
{
const drawX = 0;
const drawY = 0;
const drawWidth = width / 2;
const drawHeight = height;
const dispWidth = displayWidth / 2;
const dispHeight = displayHeight;
const fieldOfView = 45 * Math.PI / 180;
const aspect = dispWidth / dispHeight;
const zNear = 0.1;
const zFar = 20;
const projection = m4.perspective(fieldOfView, aspect, zNear, zFar)
gl.clearColor(1, 1, 0, 1);
render(drawX, drawY, drawWidth, drawHeight, projection);
}
// draw on right
{
const drawX = width / 2;
const drawY = 0;
const drawWidth = width / 2;
const drawHeight = height;
const dispWidth = displayWidth / 2;
const dispHeight = displayHeight;
const aspect = dispWidth / dispHeight;
const top = 2;
const bottom = -top;
const right = top * aspect;
const left = -right;
const zNear = 0.1;
const zFar = 20;
const projection = m4.ortho(left, right, bottom, top, zNear, zFar);
gl.clearColor(0, 1, 1, 1);
render(drawX, drawY, drawWidth, drawHeight, projection);
}
requestAnimationFrame(renderLoop);
}
requestAnimationFrame(renderLoop);
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
Example using your book's example
"use strict";
var canvas;
var gl;
var NumVertices = 36;
var pointsArray = [];
var colorsArray = [];
var vertices = [
vec4(-0.5, -0.5, 1.5, 1.0),
vec4(-0.5, 0.5, 1.5, 1.0),
vec4(0.5, 0.5, 1.5, 1.0),
vec4(0.5, -0.5, 1.5, 1.0),
vec4(-0.5, -0.5, 0.5, 1.0),
vec4(-0.5, 0.5, 0.5, 1.0),
vec4(0.5, 0.5, 0.5, 1.0),
vec4( 0.5, -0.5, 0.5, 1.0)
];
var vertexColors = [
vec4( 0.0, 0.0, 0.0, 1.0 ), // black
vec4( 1.0, 0.0, 0.0, 1.0 ), // red
vec4( 1.0, 1.0, 0.0, 1.0 ), // yellow
vec4( 0.0, 1.0, 0.0, 1.0 ), // green
vec4( 0.0, 0.0, 1.0, 1.0 ), // blue
vec4( 1.0, 0.0, 1.0, 1.0 ), // magenta
vec4( 0.0, 1.0, 1.0, 1.0 ), // cyan
vec4( 1.0, 1.0, 1.0, 1.0 ), // white
];
var near = 0.3;
var far = 3.0;
var radius = 4.0;
var theta = 0.0;
var phi = 0.0;
var dr = 5.0 * Math.PI/180.0;
var fovy = 45.0; // Field-of-view in Y direction angle (in degrees)
var aspect; // Viewport aspect ratio
var mvMatrix, pMatrix;
var modelView, projection;
var eye;
const at = vec3(0.0, 0.0, 0.0);
const up = vec3(0.0, 1.0, 0.0);
function quad(a, b, c, d) {
pointsArray.push(vertices[a]);
colorsArray.push(vertexColors[a]);
pointsArray.push(vertices[b]);
colorsArray.push(vertexColors[a]);
pointsArray.push(vertices[c]);
colorsArray.push(vertexColors[a]);
pointsArray.push(vertices[a]);
colorsArray.push(vertexColors[a]);
pointsArray.push(vertices[c]);
colorsArray.push(vertexColors[a]);
pointsArray.push(vertices[d]);
colorsArray.push(vertexColors[a]);
}
function colorCube()
{
quad( 1, 0, 3, 2 );
quad( 2, 3, 7, 6 );
quad( 3, 0, 4, 7 );
quad( 6, 5, 1, 2 );
quad( 4, 5, 6, 7 );
quad( 5, 4, 0, 1 );
}
function init() {
canvas = document.getElementById( "gl-canvas" );
gl = WebGLUtils.setupWebGL( canvas );
if ( !gl ) { alert( "WebGL isn't available" ); }
gl.viewport( 0, 0, canvas.width, canvas.height );
aspect = canvas.width/canvas.height;
gl.clearColor( 1.0, 1.0, 1.0, 1.0 );
gl.enable(gl.DEPTH_TEST);
//
// Load shaders and initialize attribute buffers
//
var program = initShaders( gl, "vertex-shader", "fragment-shader" );
gl.useProgram( program );
colorCube();
var cBuffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, cBuffer );
gl.bufferData( gl.ARRAY_BUFFER, flatten(colorsArray), gl.STATIC_DRAW );
var vColor = gl.getAttribLocation( program, "vColor" );
gl.vertexAttribPointer( vColor, 4, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( vColor);
var vBuffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, vBuffer );
gl.bufferData( gl.ARRAY_BUFFER, flatten(pointsArray), gl.STATIC_DRAW );
var vPosition = gl.getAttribLocation( program, "vPosition" );
gl.vertexAttribPointer( vPosition, 4, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( vPosition );
modelView = gl.getUniformLocation( program, "modelView" );
projection = gl.getUniformLocation( program, "projection" );
// buttons for viewing parameters
document.getElementById("Button1").onclick = function(){near *= 1.1; far *= 1.1;};
document.getElementById("Button2").onclick = function(){near *= 0.9; far *= 0.9;};
document.getElementById("Button3").onclick = function(){radius *= 2.0;};
document.getElementById("Button4").onclick = function(){radius *= 0.5;};
document.getElementById("Button5").onclick = function(){theta += dr;};
document.getElementById("Button6").onclick = function(){theta -= dr;};
document.getElementById("Button7").onclick = function(){phi += dr;};
document.getElementById("Button8").onclick = function(){phi -= dr;};
render();
}
var render = function(){
function renderScene(drawX, drawY, drawWidth, drawHeight, pMatrix) {
gl.enable(gl.SCISSOR_TEST);
gl.viewport(drawX, drawY, drawWidth, drawHeight);
gl.scissor(drawX, drawY, drawWidth, drawHeight);
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
eye = vec3(radius*Math.sin(theta)*Math.cos(phi),
radius*Math.sin(theta)*Math.sin(phi), radius*Math.cos(theta));
mvMatrix = lookAt(eye, at , up);
gl.uniformMatrix4fv( modelView, false, flatten(mvMatrix) );
gl.uniformMatrix4fv( projection, false, flatten(pMatrix) );
gl.drawArrays( gl.TRIANGLES, 0, NumVertices );
}
const width = gl.canvas.width;
const height = gl.canvas.height;
const displayWidth = gl.canvas.clientWidth;
const displayHeight = gl.canvas.clientHeight;
// draw left
{
const dispWidth = displayWidth / 2;
const dispHeight = displayHeight;
const aspect = dispWidth / dispHeight;
const pMatrix = perspective(fovy, aspect, near, far);
gl.clearColor(0.1, 0.1, 0.1, 1);
renderScene(0, 0, width / 2, height, pMatrix);
}
// draw right
{
const dispWidth = displayWidth / 2;
const dispHeight = displayHeight;
const aspect = dispWidth / dispHeight;
const top = 1;
const bottom = -top;
const right = top * aspect;
const left = -right;
const pMatrix = ortho(left, right, bottom, top, near, far);
gl.clearColor(0.2, 0.2, 0.2, 1);
renderScene(width / 2, 0, width / 2, height, pMatrix);
}
requestAnimFrame(render);
}
init();
<p> </p>
<button id = "Button1">Increase Z</button>
<button id = "Button2">Decrease Z</button>
<button id = "Button3">Increase R</button>
<button id = "Button4">Decrease R</button>
<p> </p>
<button id = "Button5">Increase theta</button>
<button id = "Button6">Decrease theta</button>
<button id = "Button7">Increase phi</button>
<button id = "Button8">Decrease phi</button>
<p> </p>
<script id="vertex-shader" type="x-shader/x-vertex">
attribute vec4 vPosition;
attribute vec4 vColor;
varying vec4 fColor;
uniform mat4 modelView;
uniform mat4 projection;
void main()
{
gl_Position = projection*modelView*vPosition;
fColor = vColor;
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
precision mediump float;
varying vec4 fColor;
void
main()
{
gl_FragColor = fColor;
}
</script>
<script src="https://esangel.github.io/WebGL/Common/webgl-utils.js"></script>
<script src="https://esangel.github.io/WebGL/Common/initShaders.js"></script>
<script src="https://esangel.github.io/WebGL/Common/MV.js"></script>
<canvas id="gl-canvas" width="400" height="100"></canvas>

WebGL: Texture rendering not working correctly

I'm trying to use textures for the first time in WebGL and I'm having a problem getting it to work. I'm trying to apply a stone-like texture to cubes that are moving around in a 3D space, as you can see in the rocks.js below. I always get this error which I don't know what to make of (I'm using Chrome btw):
RENDER WARNING: there is no texture bound to the unit 0
Here is my index.html file:
<html>
<script id="vertex-shader" type="x-shader/x-vertex">
attribute vec4 vPosition;
attribute vec4 vColor;
attribute vec2 vTexCoord;
varying vec2 fTexCoord;
varying vec4 fColor;
uniform mat4 projection;
uniform mat4 modelview;
void main()
{
fTexCoord = vTexCoord;
fColor = vColor;
gl_Position = projection * modelview * vPosition;
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
precision mediump float;
varying vec4 fColor;
varying vec2 fTexCoord;
uniform sampler2D texture;
void
main()
{
gl_FragColor = texture2D( texture, fTexCoord );
//gl_FragColor = fColor;
}
</script>
<script type="text/javascript" src="../Common/webgl-utils.js"></script>
<script type="text/javascript" src="../Common/initShaders.js"></script>
<script type="text/javascript" src="../Common/MV.js"></script>
<script type="text/javascript" src="main.js"></script>
<script type="text/javascript" src="grid.js"></script>
<script type="text/javascript" src="spaceship.js"></script>
<script type="text/javascript" src="rocks.js"></script>
<body>
<canvas id="gl-canvas" width="1100" height="1200" style="float:left;">
Oops ... your browser doesn't support the HTML5 canvas element
</canvas>
</div>
</body>
</html>
Here is my rocks.js where the object which I'm attempting to apply texture to is defined:
// global webgl variables
var gl;
var vBuffer;
var cBuffer;
var iBuffer;
var tBuffer;
var mvLoc;
var rocks = (function() {
var direction = vec3();
var lastDirection = vec3();
var location = vec3();
var lastLocation = vec3();
var rock = {
NumVertices : 36,
indices :[
1, 0, 3,
3, 2, 1,
2, 3, 7,
7, 6, 2,
3, 0, 4,
4, 7, 3,
6, 5, 1,
1, 2, 6,
4, 5, 6,
6, 7, 4,
5, 4, 0,
0, 1, 1
],
vertices : [
vec4( -0.5, -0.5, 0.5, 1.0 ),
vec4( -0.5, 0.5, 0.5, 1.0 ),
vec4( 0.5, 0.5, 0.5, 1.0 ),
vec4( 0.5, -0.5, 0.5, 1.0 ),
vec4( -0.5, -0.5, -0.5, 1.0 ),
vec4( -0.5, 0.5, -0.5, 1.0 ),
vec4( 0.5, 0.5, -0.5, 1.0 ),
vec4( 0.5, -0.5, -0.5, 1.0 )
],
texVertices : [
vec2(-0.5, -0.5),
vec2(-0.5, 0.5 ),
vec2( 0.5, 0.5 ),
vec2( 0.5, -0.5 ),
vec2( -0.5, -0.5 ),
vec2( -0.5, 0.5 ),
vec2( 0.5, 0.5 ),
vec2( 0.5, -0.5 )
],
render : function(mv) {
gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, flatten(this.vertices));
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, iBuffer);
gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, new Uint8Array(this.indices))
gl.bindBuffer( gl.ARRAY_BUFFER, tBuffer);
gl.bufferSubData( gl.ARRAY_BUFFER, 0, flatten(this.texVertices) );
mv = mult( mv, scalem(0.2, 0.2, 0.2) );
mv = mult( mv, translate(0.0, 0.0, 0.0) );
gl.uniformMatrix4fv(mvLoc, false, flatten(mv));
gl.drawElements( gl.TRIANGLES, this.NumVertices, gl.UNSIGNED_BYTE, 0 );
}
}
var init = function(glIn, vBufferIn, iBufferIn, tBufferIn, mvLocIn) {
// set global webgl variagles
gl = gl;
vBuffer = vBufferIn;
iBuffer = iBufferIn;
mvLoc = mvLocIn;
tBuffer = tBufferIn;
direction = [(Math.random()*0.06-0.03)/2, (Math.random()*0.06-0.03)/2, (Math.random()*0.06-0.03)/2];
location = [Math.random()*6-3, Math.random()*6-3, Math.random()*6-3];
lastDirection = direction;
lastLocation = location;
}
var render = function(spinX, spinY, mv) {
mv = mult( mv, rotateX(spinX) );
mv = mult( mv, rotateY(spinY) );
mv = mult( mv, translate(location) );
rock.render(mv);
}
var updateRock = function() {
// direction = updatedDir;
location = add( location, direction );
if(location[0] > 3 || location[0] < -3) location[0] = -location[0];
if(location[1] > 3 || location[1] < -3) location[1] = -location[1];
if(location[2] > 3 || location[2] < -3) location[2] = -location[2];
lastDirection = direction;
lastLocation = location;
}
return {
init : init,
render : render,
update : updateRock
};
});
And finally, my main.js for running everyting:
// webgl global variables
var gl;
var canvas;
var texture;
var texCoords = [];
var movement = false;
var spinX = 0;
var spinY = 0;
var origX;
var origY;
var xRot = 0;
var yRot = 0;
var grid;
var spaceship;
var rocksArray = [];
function configureTexture( image ) {
texture = gl.createTexture();
gl.bindTexture( gl.TEXTURE_2D, texture );
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image );
gl.generateMipmap( gl.TEXTURE_2D );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
}
window.onload = function init()
{
canvas = document.getElementById( "gl-canvas" );
gl = WebGLUtils.setupWebGL( canvas );
if ( !gl ) { alert( "WebGL isn't available" ); }
gl.viewport( 0, 0, canvas.width, canvas.height );
gl.clearColor( 0.0, 0.0, 0.0, 0.0 );
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
var program = initShaders( gl, "vertex-shader", "fragment-shader" );
gl.useProgram( program );
var iBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, iBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, 36, gl.DYNAMIC_DRAW);
var cBuffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, cBuffer );
gl.bufferData( gl.ARRAY_BUFFER, 128, gl.DYNAMIC_DRAW );
var vColor = gl.getAttribLocation( program, "vColor" );
gl.vertexAttribPointer( vColor, 4, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( vColor );
var vBuffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, vBuffer );
gl.bufferData( gl.ARRAY_BUFFER, 1024, gl.DYNAMIC_DRAW );
var vPosition = gl.getAttribLocation( program, "vPosition" );
gl.vertexAttribPointer( vPosition, 4, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( vPosition );
var tBuffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, tBuffer);
gl.bufferData( gl.ARRAY_BUFFER, 128, gl.STATIC_DRAW );
var vTexCoord = gl.getAttribLocation( program, "vTexCoord" );
gl.vertexAttribPointer( vTexCoord, 2, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( vTexCoord );
var image = new Image();
image.src = "rock.jpg"
image.onload = function() {
configureTexture( image );
}
var proLoc = gl.getUniformLocation( program, "projection" );
mvLoc = gl.getUniformLocation( program, "modelview" );
gl.uniform1i(gl.getUniformLocation(program, "texture"), 0);
var pers = perspective( 100.0, 1.0, 0.2, 100.0 );
gl.uniformMatrix4fv(proLoc, false, flatten(pers));
grid = new grid();
grid.init(gl, vBuffer, cBuffer, iBuffer, mvLoc);
spaceship = new spaceship();
spaceship.init(gl, vBuffer, cBuffer, iBuffer, mvLoc);
for (var i = 0; i < 100; i++) {
rocksArray[i] = new rocks();
rocksArray[i].init(gl, vBuffer, iBuffer, tBuffer, mvLoc);
}
attachEventHandlers();
render();
}
function reloadPage() {
location.reload();
}
function attachEventHandlers() {
canvas.addEventListener("mousedown", function(e){
movement = true;
origX = e.offsetX;
origY = e.offsetY;
// Disable drag and drop
e.preventDefault();
});
canvas.addEventListener("mouseup", function(e){
movement = false;
});
canvas.addEventListener("mousemove", function(e){
if(movement) {
spinY += (e.offsetX - origX) % 360;
spinX += (e.offsetY - origY) % 360;
origX = e.offsetX;
origY = e.offsetY;
}
});
window.addEventListener("keydown", function(e) {
spaceship.keyArray[e.keyCode] = true;
});
window.addEventListener("keyup", function(e) {
spaceship.keyArray[e.keyCode] = false;
});
}
function render() {
spaceship.moveSpaceship();
rocksArray.forEach(function(rock) {
rock.update();
});
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
mv = lookAt( vec3(0.0, 0.0, 1.0), vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 0.0) );
mv = mult(mv, rotateX(90));
// spaceship.render(spinX, spinY, mv);
mv = mult( mv, translate(-xVal, -yVal, -zVal));
mv = mult(mv, rotateZ(-zSpin));
mv = mult(mv, rotateX(-xSpin));
grid.render(spinX, spinY, mv);
rocksArray.forEach(function(rock) {
rock.render(spinX, spinY, mv);
});
requestAnimFrame( render );
}
The issue is most likely you start rendering immediately but your texture doesn't get created until the image has downloaded.
My suggestion is to create a 1x1 pixel texture to start, then replace the contents of that texture with the image once it has downloaded.
function configureTexture( image ) {
// texture = gl.createTexture(); -- delete this line
...
}
And change your initialization to something like
texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// put a 1x1 red pixel in the texture so it's renderable immediately
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA,
gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255]));
// now load the image, the image will replace the contents of the
// texture once it has finished downloading
var image = new Image();
image.src = "rock.jpg"
image.onload = function() {
configureTexture( image );
}
I'd suggest taking a look at these tutorials

Using d3-zoom to interact with WebGL

I'm trying to get a small example together that uses d3-zoom to provide simple interactivity to a canvas element that renders using WebGL. All I'd like to do is provide panning/zooming, which is fairly straightforward using a 4x4 transformation matrix.
The issue I'm having is with zooming (scaling). If you take a look at some of the d3-zoom examples, you'll see that the zooming focal point is always at the location of the mouse.
If you use the k, tx, and ty, values from the zoom transform directly, the panning works, but zooming is offset by half the width and height of the canvas, see
var width = 300,
height = 150;
var zoom = d3.zoom()
.on( 'zoom', zoomed );
var canvas = d3.select( 'body' )
.append( 'canvas' )
.attr( 'width', width )
.attr( 'height', height )
.call( zoom );
var gl = canvas.node().getContext( 'webgl' );
var shader = basic_shader(gl);
initialize_gl();
set_transform( 1, 0, 0 );
function zoomed () {
var t = d3.event.transform;
set_transform( t.k, t.x, t.y );
}
function initialize_gl () {
var sb = d3.color('steelblue');
gl.clearColor(sb.r / 255, sb.g / 255, sb.b / 255, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
var vertices = [
0.5, 0.5, 0.0, 1.0,
-0.5, 0.5, 0.0, 1.0,
0.5, -0.5, 0.0, 1.0,
-0.5, -0.5, 0.0, 1.0
];
var colors = [
1.0, 1.0, 1.0, 1.0, // white
1.0, 0.0, 0.0, 1.0, // red
0.0, 1.0, 0.0, 1.0, // green
0.0, 0.0, 1.0, 1.0 // blue
];
var vertex_buffer = gl.createBuffer();
var color_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
gl.vertexAttribPointer(shader.color_attrib, 4, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
gl.vertexAttribPointer(shader.vertex_attrib, 4, gl.FLOAT, false, 0, 0);
}
function set_transform ( k, tx, ty ) {
var matrix = new Float32Array([
k, 0, 0, 0,
0, k, 0, 0,
0, 0, 1, 0,
2*tx/width, -2*ty/height, 0, 1
]);
gl.uniformMatrix4fv( shader.matrix_uniform, false, matrix );
gl.clear( gl.COLOR_BUFFER_BIT );
gl.drawArrays( gl.TRIANGLE_STRIP, 0, 4 );
}
function basic_vertex () {
return [
'attribute vec4 vertex_position;',
'attribute vec4 vertex_color;',
'varying lowp vec4 vert_color;',
'uniform mat4 matrix;',
'void main( void ) {',
' gl_Position = matrix * vertex_position;',
' vert_color = vertex_color;',
'}'
].join('\n');
}
function basic_fragment () {
return [
'varying lowp vec4 vert_color;',
'void main( void ) {',
' gl_FragColor = vert_color;',
'}'
].join('\n');
}
function basic_shader ( gl ) {
var program = gl_program( gl, basic_vertex(), basic_fragment() );
gl.useProgram( program );
program.vertex_attrib = gl.getAttribLocation( program, 'vertex_position' );
program.color_attrib = gl.getAttribLocation( program, 'vertex_color' );
program.matrix_uniform = gl.getUniformLocation( program, 'matrix' );
program.translate_uniform = gl.getUniformLocation( program, 'translate_matrix' );
program.scale_uniform = gl.getUniformLocation( program, 'scale_matrix' );
gl.enableVertexAttribArray( program.vertex_attrib );
gl.enableVertexAttribArray( program.color_attrib );
return program;
}
function gl_shader ( gl, type, code ) {
var shader = gl.createShader( type );
gl.shaderSource( shader, code );
gl.compileShader( shader );
return shader;
}
function gl_program ( gl, vertex_source, fragment_source ) {
var shader_program = gl.createProgram();
var vertex_shader = gl_shader( gl, gl.VERTEX_SHADER, vertex_source );
var fragment_shader = gl_shader( gl, gl.FRAGMENT_SHADER, fragment_source );
if ( shader_program && vertex_shader && fragment_shader ) {
gl.attachShader( shader_program, vertex_shader );
gl.attachShader( shader_program, fragment_shader );
gl.linkProgram( shader_program );
gl.deleteShader( vertex_shader );
gl.deleteShader( fragment_shader );
return shader_program;
}
}
<script src="https://d3js.org/d3.v4.min.js"></script>
My hunch is that this has to do with the fact that in WebGL, the viewport x- and y-coordinates each go from -1 to 1, whereas d3-zoom uses the mouse coordinates within the canvas element, which when normalized can be in the range 0 to 1.
You can see that this is the case if you place the mouse in the very top left corner of the canvas ((0,0) in canvas coordinates) and try zooming. It will zoom as if the mouse is at the center of the canvas ((0,0) in WebGL coordinates).
In order to fix this, you can subtract 1 (i.e. half the width of a coordinate system [-1,1] ) from the x translation and add 1 (i.e. half the height of a coordinate system [-1,1]) to the y translation, as shown here
var width = 300,
height = 150;
var zoom = d3.zoom()
.on( 'zoom', zoomed );
var canvas = d3.select( 'body' )
.append( 'canvas' )
.attr( 'width', width )
.attr( 'height', height )
.call( zoom );
var gl = canvas.node().getContext( 'webgl' );
var shader = basic_shader(gl);
initialize_gl();
set_transform( 1, 0, 0 );
function zoomed () {
var t = d3.event.transform;
set_transform( t.k, t.x, t.y );
}
function initialize_gl () {
var sb = d3.color('steelblue');
gl.clearColor(sb.r / 255, sb.g / 255, sb.b / 255, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
var vertices = [
0.5, 0.5, 0.0, 1.0,
-0.5, 0.5, 0.0, 1.0,
0.5, -0.5, 0.0, 1.0,
-0.5, -0.5, 0.0, 1.0
];
var colors = [
1.0, 1.0, 1.0, 1.0, // white
1.0, 0.0, 0.0, 1.0, // red
0.0, 1.0, 0.0, 1.0, // green
0.0, 0.0, 1.0, 1.0 // blue
];
var vertex_buffer = gl.createBuffer();
var color_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
gl.vertexAttribPointer(shader.color_attrib, 4, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
gl.vertexAttribPointer(shader.vertex_attrib, 4, gl.FLOAT, false, 0, 0);
}
function set_transform ( k, tx, ty ) {
var matrix = new Float32Array([
k, 0, 0, 0,
0, k, 0, 0,
0, 0, 1, 0,
2*tx/width-1.0, -2*ty/height+1.0, 0, 1
]);
gl.uniformMatrix4fv( shader.matrix_uniform, false, matrix );
gl.clear( gl.COLOR_BUFFER_BIT );
gl.drawArrays( gl.TRIANGLE_STRIP, 0, 4 );
}
function basic_vertex () {
return [
'attribute vec4 vertex_position;',
'attribute vec4 vertex_color;',
'varying lowp vec4 vert_color;',
'uniform mat4 matrix;',
'void main( void ) {',
' gl_Position = matrix * vertex_position;',
' vert_color = vertex_color;',
'}'
].join('\n');
}
function basic_fragment () {
return [
'varying lowp vec4 vert_color;',
'void main( void ) {',
' gl_FragColor = vert_color;',
'}'
].join('\n');
}
function basic_shader ( gl ) {
var program = gl_program( gl, basic_vertex(), basic_fragment() );
gl.useProgram( program );
program.vertex_attrib = gl.getAttribLocation( program, 'vertex_position' );
program.color_attrib = gl.getAttribLocation( program, 'vertex_color' );
program.matrix_uniform = gl.getUniformLocation( program, 'matrix' );
program.translate_uniform = gl.getUniformLocation( program, 'translate_matrix' );
program.scale_uniform = gl.getUniformLocation( program, 'scale_matrix' );
gl.enableVertexAttribArray( program.vertex_attrib );
gl.enableVertexAttribArray( program.color_attrib );
return program;
}
function gl_shader ( gl, type, code ) {
var shader = gl.createShader( type );
gl.shaderSource( shader, code );
gl.compileShader( shader );
return shader;
}
function gl_program ( gl, vertex_source, fragment_source ) {
var shader_program = gl.createProgram();
var vertex_shader = gl_shader( gl, gl.VERTEX_SHADER, vertex_source );
var fragment_shader = gl_shader( gl, gl.FRAGMENT_SHADER, fragment_source );
if ( shader_program && vertex_shader && fragment_shader ) {
gl.attachShader( shader_program, vertex_shader );
gl.attachShader( shader_program, fragment_shader );
gl.linkProgram( shader_program );
gl.deleteShader( vertex_shader );
gl.deleteShader( fragment_shader );
return shader_program;
}
}
<script src="https://d3js.org/d3.v4.min.js"></script>
However, by performing the offset, your scene is initially translated, which isn't exactly ideal. So my question is, what is the best way to handle this? Is it best handled by the d3 side or the WebGL side?
I just moved your vertices over to match your matrix
var vertices = [
.5, -.5, 0.0, 1.0,
1.5, -.5, 0.0, 1.0,
.5, -1.5, 0.0, 1.0,
1.5, -1.5, 0.0, 1.0
];
var width = 300,
height = 150;
var zoom = d3.zoom()
.on( 'zoom', zoomed );
var canvas = d3.select( 'body' )
.append( 'canvas' )
.attr( 'width', width )
.attr( 'height', height )
.call( zoom );
var gl = canvas.node().getContext( 'webgl' );
var shader = basic_shader(gl);
initialize_gl();
set_transform( 1, 0, 0 );
function zoomed () {
var t = d3.event.transform;
set_transform( t.k, t.x, t.y );
}
function initialize_gl () {
var sb = d3.color('steelblue');
gl.clearColor(sb.r / 255, sb.g / 255, sb.b / 255, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
var vertices = [
.5, -.5, 0.0, 1.0,
1.5, -.5, 0.0, 1.0,
.5, -1.5, 0.0, 1.0,
1.5, -1.5, 0.0, 1.0
];
var colors = [
1.0, 1.0, 1.0, 1.0, // white
1.0, 0.0, 0.0, 1.0, // red
0.0, 1.0, 0.0, 1.0, // green
0.0, 0.0, 1.0, 1.0 // blue
];
var vertex_buffer = gl.createBuffer();
var color_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
gl.vertexAttribPointer(shader.color_attrib, 4, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
gl.vertexAttribPointer(shader.vertex_attrib, 4, gl.FLOAT, false, 0, 0);
}
function set_transform ( k, tx, ty ) {
var matrix = new Float32Array([
k, 0, 0, 0,
0, k, 0, 0,
0, 0, 1, 0,
2*tx/width-1.0, -2*ty/height+1.0, 0, 1
]);
gl.uniformMatrix4fv( shader.matrix_uniform, false, matrix );
gl.clear( gl.COLOR_BUFFER_BIT );
gl.drawArrays( gl.TRIANGLE_STRIP, 0, 4 );
}
function basic_vertex () {
return [
'attribute vec4 vertex_position;',
'attribute vec4 vertex_color;',
'varying lowp vec4 vert_color;',
'uniform mat4 matrix;',
'void main( void ) {',
' gl_Position = matrix * vertex_position;',
' vert_color = vertex_color;',
'}'
].join('\n');
}
function basic_fragment () {
return [
'varying lowp vec4 vert_color;',
'void main( void ) {',
' gl_FragColor = vert_color;',
'}'
].join('\n');
}
function basic_shader ( gl ) {
var program = gl_program( gl, basic_vertex(), basic_fragment() );
gl.useProgram( program );
program.vertex_attrib = gl.getAttribLocation( program, 'vertex_position' );
program.color_attrib = gl.getAttribLocation( program, 'vertex_color' );
program.matrix_uniform = gl.getUniformLocation( program, 'matrix' );
program.translate_uniform = gl.getUniformLocation( program, 'translate_matrix' );
program.scale_uniform = gl.getUniformLocation( program, 'scale_matrix' );
gl.enableVertexAttribArray( program.vertex_attrib );
gl.enableVertexAttribArray( program.color_attrib );
return program;
}
function gl_shader ( gl, type, code ) {
var shader = gl.createShader( type );
gl.shaderSource( shader, code );
gl.compileShader( shader );
return shader;
}
function gl_program ( gl, vertex_source, fragment_source ) {
var shader_program = gl.createProgram();
var vertex_shader = gl_shader( gl, gl.VERTEX_SHADER, vertex_source );
var fragment_shader = gl_shader( gl, gl.FRAGMENT_SHADER, fragment_source );
if ( shader_program && vertex_shader && fragment_shader ) {
gl.attachShader( shader_program, vertex_shader );
gl.attachShader( shader_program, fragment_shader );
gl.linkProgram( shader_program );
gl.deleteShader( vertex_shader );
gl.deleteShader( fragment_shader );
return shader_program;
}
}
<script src="https://d3js.org/d3.v4.min.js"></script>
But honestly I'd probably use a math library and use a few transforms. It's easier for me to understand the code that way. I'm not sure what the "space" of D3. I guess though it's just passing you an offset and a scale. In which case
// change the space to be pixels with 0,0 in top left
var matrix = m4.ortho(0, gl.canvas.width, gl.canvas.height, 0, -1, 1);
// apply the d3 translate and zoom
matrix = m4.translate(matrix, [tx, ty, 0]);
matrix = m4.scale(matrix, [k, k, 1]);
// translate the unit quad to the center
matrix = m4.translate(matrix, [width / 2, height / 2, 0]);
// make the unit quad be half the size of the canvas
matrix = m4.scale(matrix, [width / 2, height / 2 , 1]);
var m4 = twgl.m4;
var width = 300,
height = 150;
var zoom = d3.zoom()
.on( 'zoom', zoomed );
var canvas = d3.select( 'body' )
.append( 'canvas' )
.attr( 'width', width )
.attr( 'height', height )
.call( zoom );
var gl = canvas.node().getContext( 'webgl' );
var shader = basic_shader(gl);
initialize_gl();
set_transform( 1, 0, 0 );
function zoomed () {
var t = d3.event.transform;
set_transform( t.k, t.x, t.y );
}
function initialize_gl () {
var sb = d3.color('steelblue');
gl.clearColor(sb.r / 255, sb.g / 255, sb.b / 255, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
var vertices = [
-.5, .5, 0.0, 1.0,
.5, .5, 0.0, 1.0,
-.5, -.5, 0.0, 1.0,
.5, -.5, 0.0, 1.0
];
var colors = [
1.0, 1.0, 1.0, 1.0, // white
1.0, 0.0, 0.0, 1.0, // red
0.0, 1.0, 0.0, 1.0, // green
0.0, 0.0, 1.0, 1.0 // blue
];
var vertex_buffer = gl.createBuffer();
var color_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
gl.vertexAttribPointer(shader.color_attrib, 4, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
gl.vertexAttribPointer(shader.vertex_attrib, 4, gl.FLOAT, false, 0, 0);
}
function set_transform ( k, tx, ty ) {
// change the space to be pixels with 0,0 in top left
var matrix = m4.ortho(0, gl.canvas.width, gl.canvas.height, 0, -1, 1);
// apply the d3 translate and zoom
matrix = m4.translate(matrix, [tx, ty, 0]);
matrix = m4.scale(matrix, [k, k, 1]);
// translate the unit quad to the center
matrix = m4.translate(matrix, [width / 2, height / 2, 0]);
// make the unit quad be half the size of the canvas
matrix = m4.scale(matrix, [width / 2, height / 2 , 1]);
gl.uniformMatrix4fv( shader.matrix_uniform, false, matrix );
gl.clear( gl.COLOR_BUFFER_BIT );
gl.drawArrays( gl.TRIANGLE_STRIP, 0, 4 );
}
function basic_vertex () {
return [
'attribute vec4 vertex_position;',
'attribute vec4 vertex_color;',
'varying lowp vec4 vert_color;',
'uniform mat4 matrix;',
'void main( void ) {',
' gl_Position = matrix * vertex_position;',
' vert_color = vertex_color;',
'}'
].join('\n');
}
function basic_fragment () {
return [
'varying lowp vec4 vert_color;',
'void main( void ) {',
' gl_FragColor = vert_color;',
'}'
].join('\n');
}
function basic_shader ( gl ) {
var program = gl_program( gl, basic_vertex(), basic_fragment() );
gl.useProgram( program );
program.vertex_attrib = gl.getAttribLocation( program, 'vertex_position' );
program.color_attrib = gl.getAttribLocation( program, 'vertex_color' );
program.matrix_uniform = gl.getUniformLocation( program, 'matrix' );
program.translate_uniform = gl.getUniformLocation( program, 'translate_matrix' );
program.scale_uniform = gl.getUniformLocation( program, 'scale_matrix' );
gl.enableVertexAttribArray( program.vertex_attrib );
gl.enableVertexAttribArray( program.color_attrib );
return program;
}
function gl_shader ( gl, type, code ) {
var shader = gl.createShader( type );
gl.shaderSource( shader, code );
gl.compileShader( shader );
return shader;
}
function gl_program ( gl, vertex_source, fragment_source ) {
var shader_program = gl.createProgram();
var vertex_shader = gl_shader( gl, gl.VERTEX_SHADER, vertex_source );
var fragment_shader = gl_shader( gl, gl.FRAGMENT_SHADER, fragment_source );
if ( shader_program && vertex_shader && fragment_shader ) {
gl.attachShader( shader_program, vertex_shader );
gl.attachShader( shader_program, fragment_shader );
gl.linkProgram( shader_program );
gl.deleteShader( vertex_shader );
gl.deleteShader( fragment_shader );
return shader_program;
}
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>

WebGL Vertex Shader fails to compile and 'WebGLRenderingContext :' parameter 1 is not of type 'WebGLProgram'

When I try to run the code below in my browser I receive a pop-up notification that "Vertex shader failed to compile. The error log is:
<pre>ERROR: 0:11: 'assign' : cannot convert from 'highp 3-component vector of float' to 'Position highp 4-component vector of float'
</pre>
Followed by the console error message:
"Uncaught TypeError: Failed to execute 'useProgram' on 'WebGLRenderingContext': parameter 1 is not of type 'WebGLProgram'.init # tangram2.js:76"
I am trying to create several 2-D shapes (just one for now, as a proof of concept), each with their own buffer, so that I may translate and rotate them on the GPU individually.
"use strict";`
var canvas, gl, program;
var points = [];
var colors = [];
var shapeScale = (1/3);
/* RGBA colors */
var vertexColors = [
vec4( 0.0, 0.0, 0.0, 1.0 ), // black
vec4( 1.0, 0.0, 0.0, 1.0 ), // red
vec4( 1.0, 1.0, 0.0, 1.0 ), // yellow
vec4( 0.0, 1.0, 0.0, 1.0 ), // green
vec4( 0.0, 0.0, 1.0, 1.0 ), // blue
vec4( 1.0, 0.0, 1.0, 1.0 ), // magenta
vec4( 1.0, 1.0, 1.0, 1.0 ), // white
vec4( 0.0, 1.0, 1.0, 1.0 ) // cyan
];
/* Shader transformation matrices */
var modelViewMatrix, projectionMatrix;
/* Rotational indicies */
var LT1 = 0; /* Large triangle 01 */
var LT2 = 1; /* Large triangle 02 */
var MT1 = 2; /* Medium triangle 01 */
var ST1 = 3; /* Small triangle 01 */
var ST2 = 4; /* Small triangle 02 */
var SQR = 5; /* Square */
var PRL = 6; /* Parallelogram */
var theta = [0,0,0,0,0,0,0];
var modelViewMatrixLoc;
/* For each shape, create a vertex and color buffer */
var vLT1Buff, cLT1Buff;
var vLT2Buff, cLT2Buff;
var vMT1Buff, cMT1Buff;
var vST1Buff, cST1Buff;
var vST2Buff, cST2Buff;
var vSQRBuff, cSQRBuff;
var vPRLBuff, cPRLBuff;
/* ----------------Initialize webGL---------------- */
window.onload = function init(){
/* From robotArm.js */
canvas = document.getElementById( "gl-canvas" );
gl = WebGLUtils.setupWebGL( canvas );
if ( !gl ) { alert( "WebGL isn't available" ); }
gl.viewport( 0, 0, canvas.width, canvas.height );
gl.clearColor( 1.0, 1.0, 1.0, 1.0 );
gl.enable( gl.DEPTH_TEST );
/* Setup canvas background */
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clearColor(1.0, 0.5, 0.5, 1.0);
/* Load shaders and use the resulting shader program */
program = initShaders( gl, "vertex-shader", "fragment-shader" );
gl.useProgram( program );
/* Create an initialize buffer objects */
vLT1Buff = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, vLT1Buff );
gl.bufferData( gl.ARRAY_BUFFER, flatten(points), gl.STATIC_DRAW );
var vPosition = gl.getAttribLocation( program, "vPosition" );
gl.vertexAttribPointer( vPosition, 3, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( vPosition );
cLT1Buff = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, cLT1Buff );
gl.bufferData( gl.ARRAY_BUFFER, flatten(colors), gl.STATIC_DRAW );
var vColor = gl.getAttribLocation( program, "vColor" );
gl.vertexAttribPointer( vColor, 3, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( vColor );
modelViewMatrixLoc = gl.getUniformLocation(program, "modelViewMatrix");
projectionMatrix = ortho(-10, 10, -10, 10, -10, 10);
gl.uniformMatrix3fv( gl.getUniformLocation(program, "projectionMatrix"), false, flatten(projectionMatrix) );
//-------------------------------------//
// Initialize Shapes //
//-------------------------------------//
var largeTriangle01 = setupTriangle( 1, Math.sqrt(2) / 2);
var largeTriangle02 = setupTriangle( 1, Math.sqrt(2) / 2);
var medTriangle01 = setupTriangle( Math.sqrt(2) / 2, 0.35);
var smallTriangle01 = setupTriangle( 0.5, 0.25);
var smallTriangle02 = setupTriangle( 0.5, 0.25);
/* Add our shapes to an array of shapes for quick access */
var shapes = [
largeTriangle01,
/*
largeTriangle02,
medTriangle01,
smallTriangle01,
smallTriangle02,
square01,
*/
];
for(var i = 0; i < shapes.length; ++i){
scaleShape( shapes[i] ); //Scale our shapes
makeShape ( shapes[i] ); //And draw them
}
render(LT1);
}//end init
/* ----------------Helper funcitons---------------- */
//TODO dont forget about Parallelogram!
/* From robotArm.js */
function scale3(a, b) {
var result = mat3();
result[0][0] = a;
result[1][1] = b;
return result;
}
/* Return an array of three points representing a triangle */
function setupTriangle(hypotenuse, height){
return [
vec4( hypotenuse / 2, height, 0.0, 1.0 ),
vec4( hypotenuse , 0, 0.0, 1.0 ),
vec4( 0, 0, 0.0, 1.0 ),
];
}//end setupTriangle
/* Return an array of four points representing a quad */
function setupRectangle(width, height){
return[
vec4( -width, height, 0.0, 1.0),
vec4( width, height, 0.0, 1.0),
vec4( width, -height, 0.0, 1.0),
vec4( -width, -height, 0.0, 1.0),
];
}//end setupRectangle
function scaleShape(shape){
for( var i = 0; i < shape.length; ++i ){
shape[i] = scale( shapeScale, shape[i] );
}
}//end scaleShape
function makeShape(shape){
if(shape.length == 3){ makeTriangle (shape); }
if(shape.length == 4){ makeQuad (shape); }
}//end makeShape
function makeTriangle(listOfPoints){
for(var i = 0; i < listOfPoints.length; ++i){
points.push(listOfPoints[i]);
}
}//end makeShape
function makeQuad(listOfPoints){
points.push( listOfPoints[0] );
points.push( listOfPoints[1] );
points.push( listOfPoints[2] );
points.push( listOfPoints[0] );
points.push( listOfPoints[2] );
points.push( listOfPoints[3] );
}//end makeShape
/* ------------------------------------------------------------------------- */
function largeTriangle01(){
// var s = scale3(1.0, 1.0, 1.0);
var instanceMatrix = translate(0.5, 0.5);
var t = mult(modelViewMatrix, instanceMatrix);
gl.uniformMatrix3fv(modelViewMatrixLoc, false, flatten(t) );
gl.drawArrays( gl.TRIANGLES, 0, points.length );
}
/* ------------------------------------------------------------------------- */
function render(shape){
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
modelViewMatrix = rotate(theta[shape], 0, 1, 0);
switch(shape){
/* Large Triangle 01 */
case 0:
largeTriangle01();
break;
}//end switch
requestAnimFrame(render);
}//end render
My shaders are:
<script id="vertex-shader" type="x-shader/x-vertex">
attribute vec3 vPosition;
attribute vec4 vColor;
varying vec4 fColor;
uniform mat3 modelViewMatrix;
uniform mat3 projectionMatrix;
void main() {
fColor = vColor;
gl_Position = projectionMatrix * modelViewMatrix * vPosition;
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
precision mediump float;
varying vec4 fColor;
void main() {
gl_FragColor = fColor;
}
</script>
As #Reto said,
the error message tells you exactly what the problem is. You're trying to assign a vec3 to gl_Position, which is a vec4
It even tells you the line number: ERROR: 0:11 line 11
If we number the lines
<script id="vertex-shader" type="x-shader/x-vertex">1
2 attribute vec3 vPosition;
3 attribute vec4 vColor;
4 varying vec4 fColor;
5
6 uniform mat3 modelViewMatrix;
7 uniform mat3 projectionMatrix;
8
9 void main() {
10 fColor = vColor;
11 gl_Position = projectionMatrix * modelViewMatrix * vPosition;
12 }
</script>
I'm guessing you want
gl_Position = vec4(projectionMatrix * modelViewMatrix * vPosition, 1);

Categories

Resources