WebGL: Texture rendering not working correctly - javascript

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

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>

How to run a shader from Shadertoy in your J/s file with WebGL and GLSL?

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>

Is there a way to have two images on a cube?

I'm trying to have two external images as textures for my cube. When I use one image, it works perfectly and my cube is rendered but I'm having problems rendering two images on one cube. One is a spotlight image that has the middle cut out so the second image placed can be seen through that hole. The only warning I get is
WebGL warning: generateMipmap: Tex image TEXTURE_2D level 0 is incurring lazy initialization.
When I try to run your code in Chrome 80 I get error messages in the JavaScript console
The first one is this
Uncaught TypeError: Failed to execute 'texImage2D' on 'WebGLRenderingContext': parameter 9 is not of type 'ArrayBufferView'.
at configureTexture1 (js:148)
You're trying to pass an HTMLImageElement to texImage2D but the only form of texImage2D that takes an HTMLImageElement has 6 parameters, not 9. No width, no height, and no border parameter. It should be
gl.texImage2D(
gl.TEXTURE_2D,
0, // level
gl.RGBA, // internal format
gl.RGBA, // format
gl.UNSIGNED_BYTE, // type
someImageElement);
Fixing those 2 errors leads to a new error
WARNING: there is no texture bound to the unit 1
When you use multiple textures you need to assign each texture to a texture unit. Example
// bind "someTexture" to texture unit 0
gl.activeTexture(gl.TEXTURE0 + 0);
gl.bindTexture(gl.TEXTURE_2D, someTexture);
// bind "otherTexture" to texture unit 1
gl.activeTexture(gl.TEXTURE0 + 1);
gl.bindTexture(gl.TEXTURE_2D, otherTexture);
var canvas;
var gl;
var numVertices = 36;
var texSize = 64;
var image1 = document.getElementById("texImage1");
var image2 = document.getElementById("texImage2");
var program;
var texture1, texture2;
var pointsArray = [];
var colorsArray = [];
var texCoordsArray = [];
//texture coordinates
var texCoord = [
vec2(0, 0),
vec2(0, 1),
vec2(1, 1),
vec2(1, 0)
];
//cube dimensions
var 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 )
];
var vertexColors = [
vec4( 0.0, 0.0, 0.0, 1.0 ),
vec4( 1.0, 0.0, 0.0, 1.0 ),
vec4( 1.0, 1.0, 0.0, 1.0 ),
vec4( 0.0, 1.0, 0.0, 1.0 ),
vec4( 0.0, 0.0, 1.0, 1.0 ),
vec4( 1.0, 0.0, 1.0, 1.0 ),
vec4( 0.0, 1.0, 1.0, 1.0 ),
vec4( 0.0, 1.0, 1.0, 1.0 )
];
//rotation parameters
var xAxis = 0;
var yAxis = 1;
var zAxis = 2;
var axis = xAxis;
var theta = [45.0, 45.0, 45.0];
var thetaLoc;
function configureTexture1( image ) {
texture1 = gl.createTexture();
gl.activeTexture( gl.TEXTURE0 + 0 );
gl.bindTexture( gl.TEXTURE_2D, texture1 );
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image1);
gl.generateMipmap( gl.TEXTURE_2D );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER,gl.NEAREST_MIPMAP_LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.uniform1i(gl.getUniformLocation( program, "texture1"), 0);
}
function configureTexture2( image ) {
texture2 = gl.createTexture();
gl.activeTexture( gl.TEXTURE0 + 1 );
gl.bindTexture( gl.TEXTURE_2D, texture2 );
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image2);
gl.generateMipmap( gl.TEXTURE_2D );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER,gl.NEAREST_MIPMAP_LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.uniform1i(gl.getUniformLocation( program, "texture2"), 1);
}
function quad(a, b, c, d) {
pointsArray.push(vertices[a]);
colorsArray.push(vertexColors[a]);
texCoordsArray.push(texCoord[0]);
pointsArray.push(vertices[b]);
colorsArray.push(vertexColors[a]);
texCoordsArray.push(texCoord[1]);
pointsArray.push(vertices[c]);
colorsArray.push(vertexColors[a]);
texCoordsArray.push(texCoord[2]);
pointsArray.push(vertices[a]);
colorsArray.push(vertexColors[a]);
texCoordsArray.push(texCoord[0]);
pointsArray.push(vertices[c]);
colorsArray.push(vertexColors[a]);
texCoordsArray.push(texCoord[2]);
pointsArray.push(vertices[d]);
colorsArray.push(vertexColors[a]);
texCoordsArray.push(texCoord[3]);
}
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 );
}
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( 1.0, 1.0, 1.0, 1.0 );
gl.enable(gl.DEPTH_TEST);
program = initShaders( gl, "vertex-shader", "fragment-shader" );
gl.useProgram( program );
colorCube();
//loading/initialising/binding buffers
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 );
var tBuffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, tBuffer);
gl.bufferData( gl.ARRAY_BUFFER, flatten(texCoordsArray), gl.STATIC_DRAW );
var vTexCoord = gl.getAttribLocation( program, "vTexCoord" );
gl.vertexAttribPointer( vTexCoord, 2, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( vTexCoord );
configureTexture1(image1);
configureTexture2(image2);
thetaLoc = gl.getUniformLocation(program, "theta");
document.getElementById("ButtonX").onclick = function(){axis = xAxis;};
document.getElementById("ButtonY").onclick = function(){axis = yAxis;};
document.getElementById("ButtonZ").onclick = function(){axis = zAxis;};
render();
}
var render = function() {
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
theta[axis] += 2.0;
gl.uniform3fv(thetaLoc, flatten(theta));
gl.drawArrays( gl.TRIANGLES, 0, numVertices );
requestAnimFrame(render);
}
<button id = "ButtonX">Rotate X</button>
<button id = "ButtonY">Rotate Y</button>
<button id = "ButtonZ">Rotate Z</button>
<script id="vertex-shader" type="x-shader/x-vertex">
attribute vec4 vPosition;
attribute vec4 vColor;
attribute vec2 vTexCoord;
varying vec4 fColor;
varying vec2 fTexCoord;
uniform vec3 theta;
void main()
{
vec3 angles = radians( theta );
vec3 c = cos( angles );
vec3 s = sin( angles );
mat4 rx = mat4( 1.0, 0.0, 0.0, 0.0,
0.0, c.x, s.x, 0.0,
0.0, -s.x, c.x, 0.0,
0.0, 0.0, 0.0, 1.0 );
mat4 ry = mat4( c.y, 0.0, -s.y, 0.0,
0.0, 1.0, 0.0, 0.0,
s.y, 0.0, c.y, 0.0,
0.0, 0.0, 0.0, 1.0 );
mat4 rz = mat4( c.z, s.z, 0.0, 0.0,
-s.z, c.z, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0 );
fColor = vColor;
fTexCoord = vTexCoord;
gl_Position = rz * ry * rx * vPosition;
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
precision mediump float;
varying vec4 fColor;
varying vec2 fTexCoord;
uniform sampler2D texture1;
uniform sampler2D texture2;
void
main()
{
gl_FragColor = fColor*(texture2D(texture1, fTexCoord)*texture2D(texture2, fTexCoord));
}
</script>
<script type="text/javascript"
src="https://esangel.github.io/WebGL/Common/webgl-utils.js"></script>
<script type="text/javascript" src="https://esangel.github.io/WebGL/Common/initShaders.js"></script>
<script type="text/javascript"
src="https://esangel.github.io/WebGL/Common/MV.js"></script>
<canvas id="gl-canvas" width="512" height="512">
Oops ... your browser doesn't support the HTML5 canvas element
</canvas>
<img id="texImage1" crossorigin=" " src = "https://i.imgur.com/ZKMnXce.png" crossOrigin="anonymous" hidden></img>
<img id="texImage2" crossorigin=" " src = "https://i.imgur.com/aqZRuFj.png" crossOrigin="anonymous" hidden></img>
I used these 2 images
An F: https://i.imgur.com/ZKMnXce.png
A white dot: https://imgur.com/aqZRuFj
note that the code is not following normal WebGL practices. Many things are being done once at init time which will only work as long as you're onyl drawing a single thing.
See this answer and/or this page and this one too

Slider to rotate a square

I have to use a slider to change the angle of my rotating square :
JS
"use strict";
var canvas;
var gl;
var direction = true;
var degrees = 0;
var NumVertices = 36;
var points = [];
var colors = [];
var xAxis = 0;
var yAxis = 1;
var zAxis = 2;
var axis = 0;
var theta = [ 0, 0, 0 ];
var thetaLoc;
window.onload = function init()
{
canvas = document.getElementById( "gl-canvas" );
gl = WebGLUtils.setupWebGL( canvas );
if ( !gl ) { alert( "WebGL isn't available" ); }
colorCube();
gl.viewport( 0, 0, 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 );
var cBuffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, cBuffer );
gl.bufferData( gl.ARRAY_BUFFER, flatten(colors), 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(points), gl.STATIC_DRAW );
var vPosition = gl.getAttribLocation( program, "vPosition" );
gl.vertexAttribPointer( vPosition, 4, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( vPosition );
thetaLoc = gl.getUniformLocation(program, "theta");
//event listeners for buttons
document.getElementById( "xButton" ).onclick = function () {
axis = xAxis;
};
document.getElementById( "yButton" ).onclick = function () {
axis = yAxis;
};
document.getElementById( "zButton" ).onclick = function () {
axis = zAxis;
};
// Start or Stop
var btn = document.getElementsByTagName('input')[0];
btn.addEventListener('click', function() {
if (direction == true)
direction = false;
else
direction = true;
});
// SLIDER
document.getElementById("slide").onchange = function(){
degrees = parseInt(event.target.value, 10);
};
render();
}
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 quad(a, b, c, d)
{
var 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 )
];
var vertexColors = [
[ 0.0, 0.0, 0.0, 1.0 ], // black
[ 1.0, 0.0, 0.0, 1.0 ], // red
[ 1.0, 1.0, 0.0, 1.0 ], // yellow
[ 0.0, 1.0, 0.0, 1.0 ], // green
[ 0.0, 0.0, 1.0, 1.0 ], // blue
[ 1.0, 0.0, 1.0, 1.0 ], // magenta
[ 0.0, 1.0, 1.0, 1.0 ], // cyan
[ 1.0, 1.0, 1.0, 1.0 ] // white
];
// We need to parition the quad into two triangles in order for
// WebGL to be able to render it. In this case, we create two
// triangles from the quad indices
//vertex color assigned by the index of the vertex
var indices = [ a, b, c, a, c, d ];
for ( var i = 0; i < indices.length; ++i ) {
points.push( vertices[indices[i]] );
//colors.push( vertexColors[indices[i]] );
// for solid colored faces use
colors.push(vertexColors[a]);
}
}
function render()
{
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
if (direction == false){
theta[axis] == degrees;
//theta[axis] = degrees;
}
else {
theta[axis] += 2.0;
}
/// How to control the rotation degree ?
gl.uniform3fv(thetaLoc, theta);
gl.drawArrays( gl.TRIANGLES, 0, NumVertices );
requestAnimFrame( render );
}
HTML
<!DOCTYPE html>
<html>
<script id="vertex-shader" type="x-shader/x-vertex">
attribute vec4 vPosition;
attribute vec4 vColor;
varying vec4 fColor;
uniform vec3 theta;
void main()
{
// Compute the sines and cosines of theta for each of
// the three axes in one computation.
vec3 angles = radians( theta );
vec3 c = cos( angles );
vec3 s = sin( angles );
// Remeber: thse matrices are column-major
mat4 rx = mat4( 1.0, 0.0, 0.0, 0.0,
0.0, c.x, s.x, 0.0,
0.0, -s.x, c.x, 0.0,
0.0, 0.0, 0.0, 1.0 );
mat4 ry = mat4( c.y, 0.0, -s.y, 0.0,
0.0, 1.0, 0.0, 0.0,
s.y, 0.0, c.y, 0.0,
0.0, 0.0, 0.0, 1.0 );
mat4 rz = mat4( c.z, s.z, 0.0, 0.0,
-s.z, c.z, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0 );
fColor = vColor;
gl_Position = rz * ry * rx * vPosition;
gl_Position.z = -gl_Position.z;
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
precision mediump float;
varying vec4 fColor;
void
main()
{
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="Exercise1_1.js"></script>
<body>
<canvas id="gl-canvas" width="512"" height="512">
Oops ... your browser doesn't support the HTML5 canvas element
</canvas>
<br/>
<button id= "xButton">Rotate X</button>
<button id= "yButton">Rotate Y</button>
<button id= "zButton">Rotate Z</button>
<input type="button" value="Start or Stop" />
<div>
Angle 0 <input id="slide" type="range"
min="0" max="10" step="1" value="1" />
10 </div>
</body>
</html>
This square must stop when I click the button and restart the rotation if I click it again and this task works. The problem is that I have to change the angle using the slider and it works too if I remove the comment in the last if statement.
The problem is that if I click the start button and then I stop it , it stops in the beginning position and not in the last/current position (this happens if I remove the comment from the if statement code , if I leave the comment the start/stop works well but I can not change the angle).
Thanks for the help.
Fist of all, this line is wrong theta[axis] == degrees;
As you can see you have 2x = so you compare the theta[axis] with the degrees. This means that this line does nothing.
You want to stop the movement of the cube if the button is pressed and start it if it is pressed again, so thats why you have the variable direction.
If the direction is true, then you cube must move, so your theta[axis] should increase. If your direction is false your cube should stay still, so theta[axis] must keep its value.
So something like this:
// If cube is moving
if (direction == true) {
theta[axis] += 2.0;
}
// If no movement
else {
// Do nothing
// the theta[axis] stays the same
}
But now your cube is rotating with the same speed...
Maybe you want your cube to rotate with the degrees speed.
So you should change the static value of 2.0 to the variable degrees
that gets its value from the slider.
"use strict";
var canvas;
var gl;
var direction = true;
var degrees = 1.0; // Slider starts from value 1.0 as it has value="1" on it
var NumVertices = 36;
var points = [];
var colors = [];
var xAxis = 0;
var yAxis = 1;
var zAxis = 2;
var axis = 0;
var theta = [ 0, 0, 0 ];
var thetaLoc;
window.onload = function init()
{
canvas = document.getElementById( "gl-canvas" );
gl = WebGLUtils.setupWebGL( canvas );
if ( !gl ) { alert( "WebGL isn't available" ); }
colorCube();
gl.viewport( 0, 0, 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 );
var cBuffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, cBuffer );
gl.bufferData( gl.ARRAY_BUFFER, flatten(colors), 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(points), gl.STATIC_DRAW );
var vPosition = gl.getAttribLocation( program, "vPosition" );
gl.vertexAttribPointer( vPosition, 4, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( vPosition );
thetaLoc = gl.getUniformLocation(program, "theta");
//event listeners for buttons
document.getElementById( "xButton" ).onclick = function () {
axis = xAxis;
};
document.getElementById( "yButton" ).onclick = function () {
axis = yAxis;
};
document.getElementById( "zButton" ).onclick = function () {
axis = zAxis;
};
// Start or Stop
var btn = document.getElementsByTagName('input')[0];
btn.addEventListener('click', function() {
if (direction == true)
direction = false;
else
direction = true;
});
// SLIDER
document.getElementById("slide").onchange = function(){
// As this is an event of the slider,
// here inside the handler, the variable `this`
// points to the slider, so we can do `this.value`
// to get the slider's value
degrees = parseInt(this.value, 10);
};
render();
}
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 quad(a, b, c, d)
{
var 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 )
];
var vertexColors = [
[ 0.0, 0.0, 0.0, 1.0 ], // black
[ 1.0, 0.0, 0.0, 1.0 ], // red
[ 1.0, 1.0, 0.0, 1.0 ], // yellow
[ 0.0, 1.0, 0.0, 1.0 ], // green
[ 0.0, 0.0, 1.0, 1.0 ], // blue
[ 1.0, 0.0, 1.0, 1.0 ], // magenta
[ 0.0, 1.0, 1.0, 1.0 ], // cyan
[ 1.0, 1.0, 1.0, 1.0 ] // white
];
// We need to parition the quad into two triangles in order for
// WebGL to be able to render it. In this case, we create two
// triangles from the quad indices
//vertex color assigned by the index of the vertex
var indices = [ a, b, c, a, c, d ];
for ( var i = 0; i < indices.length; ++i ) {
points.push( vertices[indices[i]] );
//colors.push( vertexColors[indices[i]] );
// for solid colored faces use
colors.push(vertexColors[a]);
}
}
function render()
{
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
if (direction != false){
theta[axis] += degrees;
// theta[axis] is increased by the degrees variable
// that is being updated with the value of the slider
// every time we slide the slider
}
// How to control the rotation degree ?
// With a nice slider! ...
gl.uniform3fv(thetaLoc, theta);
gl.drawArrays( gl.TRIANGLES, 0, NumVertices );
requestAnimFrame( render );
}

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>

Categories

Resources