Related
I am trying to render a textured cube (using vertices, indices and tex coords) with a textured skybox (using cubemap) around it but somehow I always get the following error message:
WebGL: INVALID_OPERATION: bindTexture: textures can not be used with multiple targets
I have two textures and am probably using gl.activeTexture wrongly but I cannot figure it out.
As you can see, the textured cube breifly flashes before the skybox seems to be drawn over it.
temporary (24h) website with this code: http://priceless-dijkstra-4bf2a5.netlify.com/
Any ideas?
<!-- Licensed under a BSD license. See license.html for license -->
<!-- src: https://webglfundamentals.org/ -->
<!DOCTYPE html>
<html>
<head>
<meta charset = "utf-8">
<meta name = "viewport" content = "width=device-width, initial-scale=1.0, user-scalable=yes">
<title> WebGL - Textures - Data Texture 3 x2</title>
<link type = "text/css" href = "./webgl-tutorials.css" rel = "stylesheet" />
</head>
<body>
<div class = "description">
A 3 x2 texture <br />
</div>
<canvas id = "canvas"></canvas>
</body>
<!-- vertex shader -->
<script id = "3d-vertex-shader" type = "x-shader/x-vertex">
attribute vec4 a_position;
attribute vec2 a_texcoord;
uniform mat4 u_matrix;
varying vec2 v_texcoord;
void main()
{
// Multiply the position by the matrix.
gl_Position = u_matrix * a_position;
// Pass the texcoord to the fragment shader.
v_texcoord = a_texcoord;
}
</script>
<!-- fragment shader -->
<script id = "3d-fragment-shader" type = "x-shader/x-fragment">
precision mediump float;
// Passed in from the vertex shader.
varying vec2 v_texcoord;
// The texture.
uniform sampler2D u_texture;
void main()
{
gl_FragColor = texture2D(u_texture, v_texcoord);
}
</script>
<!--skybox vertex shader-->
<script id="skybox-vertex-shader" type="x-shader/x-vertex">
attribute vec4 a_position;
varying vec4 v_position;
void main()
{
v_position = a_position;
gl_Position = a_position;
}
</script>
<!--skybox fragment shader-->
<script id="skybox-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
uniform samplerCube u_skybox;
uniform mat4 u_viewDirectionProjectionInverse;
varying vec4 v_position;
void main()
{
vec4 t = u_viewDirectionProjectionInverse * v_position;
gl_FragColor = textureCube(u_skybox, normalize(t.xyz / t.w));
}
</script>
<script src = "./webgl-utils.js"></script>
<script src = "./m4.js"></script>
<script src = "./primitives.js"></script>
<script type = "module">
"use strict";
function main()
{
// Get A WebGL context
/** #type {HTMLCanvasElement} */
var canvas = document.getElementById("canvas");
var gl = canvas.getContext("webgl");
if (!gl)
{
return;
}
// setup GLSL program
var program = webglUtils.createProgramFromScripts(gl, ["3d-vertex-shader", "3d-fragment-shader"]);
// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");
var texcoordLocation = gl.getAttribLocation(program, "a_texcoord");
// lookup uniforms
var matrixLocation = gl.getUniformLocation(program, "u_matrix");
var textureLocation = gl.getUniformLocation(program, "u_texture");
//create program for skybox
const skyboxProgramInfo = webglUtils.createProgramInfo(gl, ["skybox-vertex-shader", "skybox-fragment-shader"]);
var sb_textureLocation = gl.getUniformLocation(skyboxProgramInfo.program, "u_skybox");
// create buffers and fill with vertex data
const cubeBufferInfo = primitives.createCubeBufferInfo(gl, 1);
const quadBufferInfo = primitives.createXYQuadBufferInfo(gl);
// Create a texture.
const sb_texture = gl.createTexture();
gl.activeTexture(gl.TEXTURE0 + 1);
gl.bindTexture(gl.TEXTURE_CUBE_MAP, sb_texture);
const faceInfos =
[
{ target: gl.TEXTURE_CUBE_MAP_POSITIVE_X, url: './pos-x.jpg', },
{ target: gl.TEXTURE_CUBE_MAP_NEGATIVE_X, url: './neg-x.jpg', },
{ target: gl.TEXTURE_CUBE_MAP_POSITIVE_Y, url: './pos-y.jpg', },
{ target: gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, url: './neg-y.jpg', },
{ target: gl.TEXTURE_CUBE_MAP_POSITIVE_Z, url: './pos-z.jpg', },
{ target: gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, url: './neg-z.jpg', },
];
faceInfos.forEach((faceInfo) =>
{
const {target, url} = faceInfo;
// Upload the canvas to the cubemap face.
const level = 0;
const internalFormat = gl.RGBA;
const width = 512;
const height = 512;
const format = gl.RGBA;
const type = gl.UNSIGNED_BYTE;
// setup each face so it's immediately renderable
gl.texImage2D(target, level, internalFormat, width, height, 0, format, type, null);
// Asynchronously load an image
const image = new Image();
image.src = url;
image.addEventListener('load', function()
{
// Now that the image has loaded make copy it to the skybox texture.
gl.activeTexture(gl.TEXTURE0 + 1);
gl.bindTexture(gl.TEXTURE_CUBE_MAP, sb_texture);
gl.texImage2D(target, level, internalFormat, format, type, image);
gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
});
});
gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
// Create a buffer for positions
var positionBuffer = gl.createBuffer();
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Put the positions in the buffer
setGeometry(gl);
// Create a buffer for positions
var indexBuffer = gl.createBuffer();
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
// Put the positions in the buffer
setIndices(gl);
// provide texture coordinates for the rectangle.
var texcoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
// Set Texcoords.
setTexcoords(gl);
// Create a texture.
var texture = gl.createTexture();
//void gl.bindTexture(target, texture);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
// fill texture with 3x2 pixels
const level = 0;
const internalFormat = gl.RGB;
const width = 2;
const height = 2;
const border = 0;
const format = gl.RGB;
const type = gl.UNSIGNED_BYTE;
const data = new Uint8Array
([
255, 0, 0, 0, 255, 0,
0, 0, 255, 128, 128, 128,
]);
const alignment = 1;
gl.pixelStorei(gl.UNPACK_ALIGNMENT, alignment);
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, width, height, border, format, type, data);
// set the filtering so we don't need mips and it's not filtered
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
function degToRad(d)
{
return d * Math.PI / 180;
}
var fieldOfViewRadians = degToRad(60);
var modelXRotationRadians = degToRad(0);
var modelYRotationRadians = degToRad(0);
// Get the starting time.
var then = 0;
requestAnimationFrame(drawScene);
// Draw the scene.
function drawScene(time)
{
// convert to seconds
time *= 0.001;
// Subtract the previous time from the current time
var deltaTime = time - then;
// Remember the current time for the next frame.
then = time;
webglUtils.resizeCanvasToDisplaySize(gl.canvas);
// Tell WebGL how to convert from clip space to pixels
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.CULL_FACE);
gl.enable(gl.DEPTH_TEST);
// Animate the rotation
modelYRotationRadians += -0.7 * deltaTime;
modelXRotationRadians += -0.4 * deltaTime;
// Clear the canvas AND the depth buffer.
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Tell it to use our program (pair of shaders)
gl.useProgram(program);
// Turn on the position attribute
gl.enableVertexAttribArray(positionLocation);
// Bind the position buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
var size = 3; // 3 components per iteration
var type = gl.FLOAT; // the data is 32bit floats
var normalize = false; // don't normalize the data
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer( positionLocation, size, type, normalize, stride, offset);
// Turn on the teccord attribute
gl.enableVertexAttribArray(texcoordLocation);
// Bind the position buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
// Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
var size = 2; // 2 components per iteration
var type = gl.FLOAT; // the data is 32bit floats
var normalize = false; // don't normalize the data
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer( texcoordLocation, size, type, normalize, stride, offset);
// Compute the projection matrix
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var projectionMatrix = m4.perspective(fieldOfViewRadians, aspect, 1, 2000);
var cameraPosition = [0, 0, 2];
var up = [0, 1, 0];
var target = [0, 0, 0];
// Compute the camera's matrix using look at.
var cameraMatrix = m4.lookAt(cameraPosition, target, up);
// Make a view matrix from the camera matrix.
var viewMatrix = m4.inverse(cameraMatrix);
var viewProjectionMatrix = m4.multiply(projectionMatrix, viewMatrix);
var matrix = m4.xRotate(viewProjectionMatrix, modelXRotationRadians);
matrix = m4.yRotate(matrix, modelYRotationRadians);
// Set the matrix.
gl.uniformMatrix4fv(matrixLocation, false, matrix);
// Tell the shader to use texture unit 0 for u_texture
gl.uniform1i(textureLocation, 0);
// Draw the geometry.
gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0);
// Skybox: we only care about direction so remove the translation
var viewDirectionMatrix = m4.copy(viewMatrix);
viewDirectionMatrix[12] = 0;
viewDirectionMatrix[13] = 0;
viewDirectionMatrix[14] = 0;
var viewDirectionProjectionMatrix = m4.multiply(projectionMatrix, viewDirectionMatrix);
var viewDirectionProjectionInverseMatrix = m4.inverse(viewDirectionProjectionMatrix);
// draw the skybox
gl.useProgram(skyboxProgramInfo.program);
webglUtils.setBuffersAndAttributes(gl, skyboxProgramInfo, quadBufferInfo);
webglUtils.setUniforms(skyboxProgramInfo, {
u_viewDirectionProjectionInverse: viewDirectionProjectionInverseMatrix,
u_skybox: texture,
});
// Tell the shader to use texture unit 0 for u_texture
gl.uniform1i(sb_textureLocation, 1);
webglUtils.drawBufferInfo(gl, quadBufferInfo);
requestAnimationFrame(drawScene);
}
}
// Fill the buffer with the values that define a cube.
function setGeometry(gl)
{
var positions = new Float32Array
([
// Front face
-0.5, -0.5, 0.5,
0.5, -0.5, 0.5,
0.5, 0.5, 0.5,
-0.5, 0.5, 0.5,
// Back face
-0.5, -0.5, -0.5,
-0.5, 0.5, -0.5,
0.5, 0.5, -0.5,
0.5, -0.5, -0.5,
// Top face
-0.5, 0.5, -0.5,
-0.5, 0.5, 0.5,
0.5, 0.5, 0.5,
0.5, 0.5, -0.5,
// Bottom face
-0.5, -0.5, -0.5,
0.5, -0.5, -0.5,
0.5, -0.5, 0.5,
-0.5, -0.5, 0.5,
// Right face
0.5, -0.5, -0.5,
0.5, 0.5, -0.5,
0.5, 0.5, 0.5,
0.5, -0.5, 0.5,
// Left face
-0.5, -0.5, -0.5,
-0.5, -0.5, 0.5,
-0.5, 0.5, 0.5,
-0.5, 0.5, -0.5,
]);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
}
// Fill the buffer with texture coordinates the cube.
function setTexcoords(gl)
{
gl.bufferData
(
gl.ARRAY_BUFFER,
new Float32Array
([
// Front
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
// Back
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
// Top
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
// Bottom
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
// Right
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
// Left
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
]),
gl.STATIC_DRAW);
}
// Fill the buffer with vertex indices
function setIndices(gl)
{
var indices = new Uint16Array
([
0, 1, 2, 0, 2, 3, // front
4, 5, 6, 4, 6, 7, // back
8, 9, 10, 8, 10, 11, // top
12, 13, 14, 12, 14, 15, // bottom
16, 17, 18, 16, 18, 19, // right
20, 21, 22, 20, 22, 23, // left
]);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
}
main();
</script>
</html>
To get the code to work I had to do 3 things
Bind the indexBuffer before drawing the cube
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
Don't set the texture at the bottom
gl.uniform1i(sb_textureLocation, 1);
Use the correct texture with the skybox
webglUtils.setUniforms(skyboxProgramInfo, {
u_viewDirectionProjectionInverse: viewDirectionProjectionInverseMatrix,
u_skybox: texture, // wrong---------------
u_skybox: sb_texture, // right---------------
});
A few things.
webglUtils.setBuffersAndAttributes sets all the buffers and attributes needed for draw the given object. In this case it means when you call
webglUtils.setBuffersAndAttributes(gl, skyboxProgramInfo, quadBufferInfo);
the indices needed for the skybox are bound to ELEMENT_ARRAY_BUFFER.
That means the second time through drawScene the indexBuffer is not bound
for your cube.
webglUtils.setUniforms manages active texture units for you. That means this call
webglUtils.setUniforms(skyboxProgramInfo, {
u_viewDirectionProjectionInverse: viewDirectionProjectionInverseMatrix,
u_skybox: texture,
});
was setting texture to active unit 0. setUniforms just starts at 0 and counts up for each texture used. texture the wrong texture for u_skybox which is why you got the error. the code above translates to
gl.uniformMatrix4fv(u_viewDirectionProjectionInverseLocation, false, viewDirectionProjectionInverseMatrix);
gl.activeTexture(gl.TEXTURE0 + 0);
gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
gl.uniform1i(u_skyboxLocation, 0);
Texture units are generally something you only care about at draw time, not init time. They are an array of global places to attach textures for the next draw call. Between every draw call you're expected to set them up however is needed for the draw all you're about to make.
For each texture the shaders used by the next draw call need
gl.activeTexture(gl.TEXTURE0 + n);
gl.bindTexture(targetTypeForTexture, texture);
gl.uniform1i(n);
Also see https://webglfundamentals.org/webgl/lessons/webgl-texture-units.html
"use strict";
function main() {
// Get A WebGL context
/** #type {HTMLCanvasElement} */
var canvas = document.getElementById("canvas");
var gl = canvas.getContext("webgl");
if (!gl) {
return;
}
// setup GLSL program
var program = webglUtils.createProgramFromScripts(gl, ["3d-vertex-shader", "3d-fragment-shader"]);
// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");
var texcoordLocation = gl.getAttribLocation(program, "a_texcoord");
// lookup uniforms
var matrixLocation = gl.getUniformLocation(program, "u_matrix");
var textureLocation = gl.getUniformLocation(program, "u_texture");
//create program for skybox
const skyboxProgramInfo = webglUtils.createProgramInfo(gl, ["skybox-vertex-shader", "skybox-fragment-shader"]);
var sb_textureLocation = gl.getUniformLocation(skyboxProgramInfo.program, "u_skybox");
// create buffers and fill with vertex data
const cubeBufferInfo = primitives.createCubeBufferInfo(gl, 1);
const quadBufferInfo = primitives.createXYQuadBufferInfo(gl);
// Create a texture.
const sb_texture = gl.createTexture();
gl.activeTexture(gl.TEXTURE0 + 1);
gl.bindTexture(gl.TEXTURE_CUBE_MAP, sb_texture);
const faceInfos = [
{
target: gl.TEXTURE_CUBE_MAP_POSITIVE_X,
url: 'https://webglfundamentals.org/webgl/resources/images/computer-history-museum/pos-x.jpg',
},
{
target: gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
url: 'https://webglfundamentals.org/webgl/resources/images/computer-history-museum/neg-x.jpg',
},
{
target: gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
url: 'https://webglfundamentals.org/webgl/resources/images/computer-history-museum/pos-y.jpg',
},
{
target: gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
url: 'https://webglfundamentals.org/webgl/resources/images/computer-history-museum/neg-y.jpg',
},
{
target: gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
url: 'https://webglfundamentals.org/webgl/resources/images/computer-history-museum/pos-z.jpg',
},
{
target: gl.TEXTURE_CUBE_MAP_NEGATIVE_Z,
url: 'https://webglfundamentals.org/webgl/resources/images/computer-history-museum/neg-z.jpg',
},
];
faceInfos.forEach((faceInfo) => {
const {
target,
url
} = faceInfo;
// Upload the canvas to the cubemap face.
const level = 0;
const internalFormat = gl.RGBA;
const width = 512;
const height = 512;
const format = gl.RGBA;
const type = gl.UNSIGNED_BYTE;
// setup each face so it's immediately renderable
gl.texImage2D(target, level, internalFormat, width, height, 0, format, type, null);
// Asynchronously load an image
const image = new Image();
image.src = url;
image.crossOrigin = 'anonymous';
image.addEventListener('load', function() {
// Now that the image has loaded make copy it to the skybox texture.
gl.activeTexture(gl.TEXTURE0 + 1);
gl.bindTexture(gl.TEXTURE_CUBE_MAP, sb_texture);
gl.texImage2D(target, level, internalFormat, format, type, image);
gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
});
});
gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
// Create a buffer for positions
var positionBuffer = gl.createBuffer();
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Put the positions in the buffer
setGeometry(gl);
// Create a buffer for positions
var indexBuffer = gl.createBuffer();
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
// Put the positions in the buffer
setIndices(gl);
// provide texture coordinates for the rectangle.
var texcoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
// Set Texcoords.
setTexcoords(gl);
// Create a texture.
var texture = gl.createTexture();
//void gl.bindTexture(target, texture);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
// fill texture with 3x2 pixels
const level = 0;
const internalFormat = gl.RGB;
const width = 2;
const height = 2;
const border = 0;
const format = gl.RGB;
const type = gl.UNSIGNED_BYTE;
const data = new Uint8Array([
255, 0, 0, 0, 255, 0,
0, 0, 255, 128, 128, 128,
]);
const alignment = 1;
gl.pixelStorei(gl.UNPACK_ALIGNMENT, alignment);
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, width, height, border, format, type, data);
// set the filtering so we don't need mips and it's not filtered
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
function degToRad(d) {
return d * Math.PI / 180;
}
var fieldOfViewRadians = degToRad(60);
var modelXRotationRadians = degToRad(0);
var modelYRotationRadians = degToRad(0);
// Get the starting time.
var then = 0;
requestAnimationFrame(drawScene);
// Draw the scene.
function drawScene(time) {
// convert to seconds
time *= 0.001;
// Subtract the previous time from the current time
var deltaTime = time - then;
// Remember the current time for the next frame.
then = time;
webglUtils.resizeCanvasToDisplaySize(gl.canvas);
// Tell WebGL how to convert from clip space to pixels
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.CULL_FACE);
gl.enable(gl.DEPTH_TEST);
// Animate the rotation
modelYRotationRadians += -0.7 * deltaTime;
modelXRotationRadians += -0.4 * deltaTime;
// Clear the canvas AND the depth buffer.
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Tell it to use our program (pair of shaders)
gl.useProgram(program);
// Turn on the position attribute
gl.enableVertexAttribArray(positionLocation);
// Bind the position buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
var size = 3; // 3 components per iteration
var type = gl.FLOAT; // the data is 32bit floats
var normalize = false; // don't normalize the data
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(positionLocation, size, type, normalize, stride, offset);
// Turn on the teccord attribute
gl.enableVertexAttribArray(texcoordLocation);
// Bind the position buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
// Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
var size = 2; // 2 components per iteration
var type = gl.FLOAT; // the data is 32bit floats
var normalize = false; // don't normalize the data
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(texcoordLocation, size, type, normalize, stride, offset);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
// Compute the projection matrix
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var projectionMatrix = m4.perspective(fieldOfViewRadians, aspect, 1, 2000);
var cameraPosition = [0, 0, 2];
var up = [0, 1, 0];
var target = [0, 0, 0];
// Compute the camera's matrix using look at.
var cameraMatrix = m4.lookAt(cameraPosition, target, up);
// Make a view matrix from the camera matrix.
var viewMatrix = m4.inverse(cameraMatrix);
var viewProjectionMatrix = m4.multiply(projectionMatrix, viewMatrix);
var matrix = m4.xRotate(viewProjectionMatrix, modelXRotationRadians);
matrix = m4.yRotate(matrix, modelYRotationRadians);
// Set the matrix.
gl.uniformMatrix4fv(matrixLocation, false, matrix);
// Tell the shader to use texture unit 0 for u_texture
gl.uniform1i(textureLocation, 0);
// Draw the geometry.
gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0);
// Skybox: we only care about direction so remove the translation
var viewDirectionMatrix = m4.copy(viewMatrix);
viewDirectionMatrix[12] = 0;
viewDirectionMatrix[13] = 0;
viewDirectionMatrix[14] = 0;
var viewDirectionProjectionMatrix = m4.multiply(projectionMatrix, viewDirectionMatrix);
var viewDirectionProjectionInverseMatrix = m4.inverse(viewDirectionProjectionMatrix);
// draw the skybox
gl.useProgram(skyboxProgramInfo.program);
webglUtils.setBuffersAndAttributes(gl, skyboxProgramInfo, quadBufferInfo);
webglUtils.setUniforms(skyboxProgramInfo, {
u_viewDirectionProjectionInverse: viewDirectionProjectionInverseMatrix,
u_skybox: sb_texture,
});
// Tell the shader to use texture unit 0 for u_texture
webglUtils.drawBufferInfo(gl, quadBufferInfo);
requestAnimationFrame(drawScene);
}
}
// Fill the buffer with the values that define a cube.
function setGeometry(gl) {
var positions = new Float32Array([
// Front face
-0.5, -0.5, 0.5,
0.5, -0.5, 0.5,
0.5, 0.5, 0.5, -0.5, 0.5, 0.5,
// Back face
-0.5, -0.5, -0.5, -0.5, 0.5, -0.5,
0.5, 0.5, -0.5,
0.5, -0.5, -0.5,
// Top face
-0.5, 0.5, -0.5, -0.5, 0.5, 0.5,
0.5, 0.5, 0.5,
0.5, 0.5, -0.5,
// Bottom face
-0.5, -0.5, -0.5,
0.5, -0.5, -0.5,
0.5, -0.5, 0.5, -0.5, -0.5, 0.5,
// Right face
0.5, -0.5, -0.5,
0.5, 0.5, -0.5,
0.5, 0.5, 0.5,
0.5, -0.5, 0.5,
// Left face
-0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5,
]);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
}
// Fill the buffer with texture coordinates the cube.
function setTexcoords(gl) {
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
// Front
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
// Back
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
// Top
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
// Bottom
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
// Right
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
// Left
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
]),
gl.STATIC_DRAW);
}
// Fill the buffer with vertex indices
function setIndices(gl) {
var indices = new Uint16Array([
0, 1, 2, 0, 2, 3, // front
4, 5, 6, 4, 6, 7, // back
8, 9, 10, 8, 10, 11, // top
12, 13, 14, 12, 14, 15, // bottom
16, 17, 18, 16, 18, 19, // right
20, 21, 22, 20, 22, 23, // left
]);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
}
main();
<div class = "description">
A 3 x2 texture <br />
</div>
<canvas id = "canvas"></canvas>
<!-- vertex shader -->
<script id = "3d-vertex-shader" type = "x-shader/x-vertex">
attribute vec4 a_position;
attribute vec2 a_texcoord;
uniform mat4 u_matrix;
varying vec2 v_texcoord;
void main()
{
// Multiply the position by the matrix.
gl_Position = u_matrix * a_position;
// Pass the texcoord to the fragment shader.
v_texcoord = a_texcoord;
}
</script>
<!-- fragment shader -->
<script id = "3d-fragment-shader" type = "x-shader/x-fragment">
precision mediump float;
// Passed in from the vertex shader.
varying vec2 v_texcoord;
// The texture.
uniform sampler2D u_texture;
void main()
{
gl_FragColor = texture2D(u_texture, v_texcoord);
}
</script>
<!--skybox vertex shader-->
<script id="skybox-vertex-shader" type="x-shader/x-vertex">
attribute vec4 a_position;
varying vec4 v_position;
void main()
{
v_position = a_position;
gl_Position = a_position;
}
</script>
<!--skybox fragment shader-->
<script id="skybox-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
uniform samplerCube u_skybox;
uniform mat4 u_viewDirectionProjectionInverse;
varying vec4 v_position;
void main()
{
vec4 t = u_viewDirectionProjectionInverse * v_position;
gl_FragColor = textureCube(u_skybox, normalize(t.xyz / t.w));
}
</script>
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/m4.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/primitives.js"></script>
I have to write a program in which i should be able to click a button "add cube" and there will be added as many cubes as I want into the scene. I must be able to select any of them and move around. I managed to create one (hardcoded) cube and apply scaling and translation to it.
I tried to create a class "cube" and invoke intitialization and render as part of an object but didnt work as expected. Here is the working example for one cube and its transformations (note only the scaling and translation work)
HTML:
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>Lab 0</title>
<script id="vertex-shader" type="x-shader/x-vertex">
attribute vec4 vPosition;
attribute vec4 vColors;
varying vec4 fragColor;
uniform vec3 theta;
uniform float coeff;
uniform vec3 trCoeff;
uniform mat4 modelView;
uniform mat4 projection;
vec3 angles = radians( theta );
void main() {
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 );
mat4 sc = mat4(
coeff, 0, 0, 0,
0, coeff, 0, 0,
0, 0, coeff, 0,
0, 0, 0, 1
);
mat4 tr = mat4(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
trCoeff.x, trCoeff.y, trCoeff.z, 1
);
fragColor = vColors;
//projection * modelView *
gl_Position = tr * rz * ry * rx * sc * vPosition;
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
precision mediump float;
varying vec4 fragColor;
void main() {
gl_FragColor = vec4(fragColor);
}
</script>
<script type="text/javascript" src="webgl-utils.js"></script>
<script type="text/javascript" src="initShaders.js"></script>
<script type="text/javascript" src="MV.js"></script>
<script type="text/javascript" src="shape0.js"></script>
</head>
<body>
<canvas id="gl-canvas" width="512" height="512">
Oops ... your browser doesn't support the HTML5 canvas element
</canvas>
</body>
<p> </p>
<button id="xButton">Rotate X</button>
<button id="yButton">Rotate Y</button>
<button id="zButton">Rotate Z</button>
<button id="stopButton">Stop Fucking Rotation</button>
<p> </p>
<button id="sUpButton">Scale Up</button>
<button id="sDownButton">Scale Down</button>
<p> </p>
<button id="leftButton">Left</button>
<button id="rightButton">Right</button>
<button id="upButton">Up</button>
<button id="downButton">Down</button>
<button id="closeButton">Closer</button>
<button id="furtherButton">Further</button>
<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>
</html>
JS:
"use strict"
var canvas;
var gl;
var thetaRot = [10, 10, 10];
var thetaLoc;
var coeff = 1;
var coeffLoc;
var trCoeff = [0, 0, 0];
var trCoeffLoc;
var flag = true;
var axis = 0;
var xAxis = 0;
var yAxis = 1;
var zAxis = 2;
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);
var coord = {
// For cube
'1': vec3(0.1, 0.1, 0.1),
'2': vec3(0.5, 0.1, 0.1),
'3': vec3(0.5, 0.1, 0.5),
'4': vec3(0.1, 0.1, 0.5),
'5': vec3(0.1, 0.5, 0.1),
'6': vec3(0.1, 0.5, 0.5),
'7': vec3(0.5, 0.5, 0.5),
'8': vec3(0.5, 0.5, 0.1),
}
var vertices = [
// For cube
coord[4], coord[3], coord[2],
coord[2], coord[4], coord[1],
coord[1], coord[2], coord[5],
coord[5], coord[8], coord[2],
coord[2], coord[3], coord[8],
coord[8], coord[3], coord[7],
coord[7], coord[3], coord[4],
coord[4], coord[7], coord[6],
coord[6], coord[4], coord[1],
coord[1], coord[5], coord[6],
coord[6], coord[7], coord[8],
coord[8], coord[6], coord[5],
]
var colors = [
// each face of the cube
0, 0, 1, 1.0,
0, 0, 1, 1.0,
0, 0, 1, 1.0,
0, 0, 1, 1.0,
0, 0, 1, 1.0,
0, 0, 1, 1.0,
0, 1, 0, 1.0,
0, 1, 0, 1.0,
0, 1, 0, 1.0,
0, 1, 0, 1.0,
0, 1, 0, 1.0,
0, 1, 0, 1.0,
0, 0.7, 1, 1.0,
0, 0.7, 1, 1.0,
0, 0.7, 1, 1.0,
0, 0.7, 1, 1.0,
0, 0.7, 1, 1.0,
0, 0.7, 1, 1.0,
0.5, 0, 1, 1.0,
0.5, 0, 1, 1.0,
0.5, 0, 1, 1.0,
0.5, 0, 1, 1.0,
0.5, 0, 1, 1.0,
0.5, 0, 1, 1.0,
1, 0, 0, 1.0,
1, 0, 0, 1.0,
1, 0, 0, 1.0,
1, 0, 0, 1.0,
1, 0, 0, 1.0,
1, 0, 0, 1.0,
0.2, 0, 1, 1.0,
0.2, 0, 1, 1.0,
0.2, 0, 1, 1.0,
0.2, 0, 1, 1.0,
0.2, 0, 1, 1.0,
0.2, 0, 1, 1.0,
]
// add vertices for cone
window.onload = init;
function init() {
canvas = document.getElementById("gl-canvas");
gl = WebGLUtils.setupWebGL(canvas);
if (!gl) { alert("WebGL isn't available"); }
aspect = canvas.width / canvas.height;
var program = initShaders(gl, "vertex-shader", "fragment-shader");
gl.useProgram(program);
gl.enable(gl.DEPTH_TEST);
var bufferId = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, bufferId);
gl.bufferData(gl.ARRAY_BUFFER, 50000, gl.STATIC_DRAW);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, flatten(vertices));
var vPosition = gl.getAttribLocation(program, "vPosition");
gl.vertexAttribPointer(vPosition, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(vPosition);
var bufferColor = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, bufferColor);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
var vColors = gl.getAttribLocation(program, "vColors");
gl.vertexAttribPointer(vColors, 4, gl.FLOAT, false, 4 * Float32Array.BYTES_PER_ELEMENT, 0);
gl.enableVertexAttribArray(vColors);
thetaLoc = gl.getUniformLocation(program, "theta");
document.getElementById("xButton").onclick = function () {
flag = false
axis = xAxis;
};
document.getElementById("yButton").onclick = function () {
flag = false
axis = yAxis;
};
document.getElementById("zButton").onclick = function () {
flag = false
axis = zAxis;
};
document.getElementById("stopButton").onclick = function () {
flag = true;
};
document.getElementById("sUpButton").onclick = function () {
coeff += 0.1;
};
document.getElementById("sDownButton").onclick = function () {
coeff -= 0.1;
};
document.getElementById("leftButton").onclick = function () {
trCoeff[0] -= 0.1;
};
document.getElementById("rightButton").onclick = function () {
trCoeff[0] += 0.1;
};
document.getElementById("upButton").onclick = function () {
trCoeff[1] += 0.1;
};
document.getElementById("downButton").onclick = function () {
trCoeff[1] -= 0.1;
};
document.getElementById("closeButton").onclick = function () {
trCoeff[2] += 0.1;
};
document.getElementById("furtherButton").onclick = function () {
trCoeff[2] -= 0.1;
};
modelView = gl.getUniformLocation(program, "modelView");
projection = gl.getUniformLocation(program, "projection");
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; };
coeffLoc = gl.getUniformLocation(program, "coeff");
trCoeffLoc = gl.getUniformLocation(program, "trCoeff");
render();
}
function render() {
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
if (!flag) {
thetaRot[axis] += 2.0;
}
gl.uniform3fv(thetaLoc, thetaRot);
gl.uniform1f(coeffLoc, coeff);
gl.uniform3fv(trCoeffLoc, trCoeff);
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);
pMatrix = perspective(fovy, aspect, near, far);
gl.uniformMatrix4fv(modelView, false, flatten(mvMatrix));
gl.uniformMatrix4fv(projection, false, flatten(pMatrix));
gl.drawArrays(gl.TRIANGLES, 0, vertices.length);
requestAnimFrame(render);
}
I expect managing to add an infinite amount of object (or until I run out of ram) to the scene and to apply transformations to one at a time.
WebGL just draws pixels. It doesn't have a concept of a "Scene" and "objects". All of that is 100% up to you. How you represent objects and how you represent the scene is completely up to you. One example
const scene = [];
function addNewObjectToScene() {
const obj = {
x: Math.random() * 400,
y: Math.random() * 400,
};
scene.push(obj);
}
Now add a button or a timer or whatever you want to call addNewObjectToScene
In your own code you'd have a render or drawScene function that goes over your "scene" and draws each object
function render() {
for (const object of scene) {
gl.useProgram(whateverShaderProgramCurrentObjectNeeds);
// setBuffersAndAttributes for object
// for each attribute in object
// gl.bindBuffer(gl.ARRAY_BUFFER, bufferForThisAttribute)
// gl.enableVertexAttribArray(locationForThisAttrubute);
// gl.vertexAttribPointer(locationForThisAttibute, other settings for this attribute)
// set any uniforms. This is probably where you'd use x and y from the code above
// example:
// gl.uniform2f(offsetUniformLocation, object.x, object.y);
// now draw
// gl.drawArrays or gl.drawElements with how ever many vertices are in the current object.
}
}
How you organize your "scene" and what's in your objects is entire up to you. Three.js makes a scene from an object called THREE.Object3D each of which contains array of "children". This forms a scene graph.
Other apps do different things.
Being able to select one is also way too big a topic. What does "select" mean. Can you have an HTML <select> form where you pick from a dropdown list which object you want to select? Maybe there is an <input type="text"> element where you type the name of the object you want to select?
If you want to click on objects, well now you've got a way too broad of a topic to cover. You could store collision data for each object. You could compute the position of each triangle your data makes and see if the mouse hit. You could use GPU picking where you draw the scene with each object in a different color using different shaders and then look at the pixel under the mouse to see what color it is effectively telling you want object was chosen. But all of this is dependent on how you created your scene.
Suggest you read some tutorials on WebGL and on drawing multiple things and maybe a scene graph
When I resize my four canvases (actual width and height, not just CSS, to adapt to screen ratio when going fullscreen), the canvas which uses webgl does not update. Though I call
gl.viewport( 0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight );
it stays with original ratio, and so it is offset from the other canvas.
What am I missing ?
The only info I could find on the internet is to update my matrix too, but I do not use any matrix, I just use a texture on two triangles.
creation code :
gl.viewport(0, 0, game.w, game.h);
gl.vertexShader = createShaderFromScriptElement(gl, "2d-vertex-shader");
gl.fragmentShader = createShaderFromScriptElement(gl, "2d-fragment-shader");
gl.program = createProgram(gl, [gl.vertexShader, gl.fragmentShader]);
gl.useProgram( gl.program);
gl.positionLocation = gl.getAttribLocation( gl.program, "a_position");
gl.texCoordLocation = gl.getAttribLocation( gl.program, "a_texCoord");
gl.texCoordBuffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, gl.texCoordBuffer );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0]), gl.STATIC_DRAW);
gl.enableVertexAttribArray( gl.texCoordLocation );
gl.vertexAttribPointer( gl.texCoordLocation , 2, gl.FLOAT, false, 0, 0);
gl.posBuffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER , gl.posBuffer );
gl.bufferData( gl.ARRAY_BUFFER , new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0]), gl.STATIC_DRAW);
gl.enableVertexAttribArray( gl.positionLocation );
gl.vertexAttribPointer( gl.positionLocation, 2, gl.FLOAT, false, 0, 0 );
gl.resolutionLocation = gl.getUniformLocation( gl.program, "u_resolution" );
webglalpha = gl.getUniformLocation( gl.program, "alpha" );
I found out by myself, the resolution uniform has to be updated too :
gl.uniform2f( gl.resolutionLocation , game.width , game.height );
var gl;
var canvas;
var shaderProgram;
var triangleVertexBuffer;
var triangleVertexColorBuffer;
var stripElementBuffer;
var stripVertexBuffer;
//Declare new variables here
function createGLContext(canvas) {
var names = ["webgl", "experimental-webgl"];
var context = null;
for (var i=0; i < names.length; i++) {
try {
context = canvas.getContext(names[i]);
} catch(e) {}
if (context) {
break;
}
}
if (context) {
context.viewportWidth = canvas.width;
context.viewportHeight = canvas.height;
} else {
alert("Failed to create WebGL context!");
}
return context;
}
function loadShaderFromDOM(id) {
var shaderScript = document.getElementById(id);
if (!shaderScript) {
return null;
}
var shaderSource = "";
var currentChild = shaderScript.firstChild;
while (currentChild) {
if (currentChild.nodeType == 3) { // 3 corresponds to TEXT_NODE
shaderSource += currentChild.textContent;
}
currentChild = currentChild.nextSibling;
}
var shader;
if (shaderScript.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (shaderScript.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
return null;
}
gl.shaderSource(shader, shaderSource);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert(gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
function setupShaders() {
vertexShader = loadShaderFromDOM("shader-vs");
fragmentShader = loadShaderFromDOM("shader-fs");
shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("Failed to setup shaders");
}
gl.useProgram(shaderProgram);
shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
shaderProgram.vertexColorAttribute = gl.getAttribLocation(shaderProgram, "aVertexColor");
gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
// For the triangle we want to use per-vertex color so
// the vertexColorAttribute, aVertexColor, in the vertex shader
// is enabled.
// You must enable this attribute here or in draw method before the
//triangle is drawn
gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute);
}
function setupBuffers() {
triangleVertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexBuffer);
var triangleVertices = [
0.0, 0.5, 0.0,
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(triangleVertices), gl.STATIC_DRAW);
triangleVertexBuffer.itemSize = 3;
triangleVertexBuffer.numberOfItems = 3;
// Triangle vertex colours
triangleVertexColorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexColorBuffer);
var colors = [
1.0, 0.0, 0.0, 1.0, //v0
0.0, 1.0, 0.0, 1.0, //v1
0.0, 0.0, 1.0, 1.0 //v2
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
triangleVertexColorBuffer.itemSize = 4;
triangleVertexColorBuffer.numberOfItems = 3;
// Add new items: the followings are newly added items
//hexagon vertices
hexagonVertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, hexagonVertexBuffer);
var hexagonVertices = [
-0.3, 0.6, 0.0, //v0
-0.4, 0.8, 0.0, //v1
-0.6, 0.8, 0.0, //v2
-0.7, 0.6, 0.0, //v3
-0.6, 0.4, 0.0, //v4
-0.4, 0.4, 0.0, //v5
-0.3, 0.6, 0.0, //v6
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(hexagonVertices), gl.STATIC_DRAW);
hexagonVertexBuffer.itemSize = 3;
hexagonVertexBuffer.numberOfItems = 7;
//Triangle strip vertices.
stripVertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, stripVertexBuffer);
var stripVertices = [
-0.5, 0.2, 0.0, //v0
-0.4, 0.0, 0.0, //v1
-0.3, 0.2, 0.0, //v2
-0.2, 0.0, 0.0, //v3
-0.1, 0.2, 0.0, //v4
0.0, 0.0, 0.0, //v5
0.1, 0.2, 0.0, //v6
0.2, 0.0, 0.0, //v7
0.3, 0.2, 0.0, //v8
0.4, 0.0, 0.0, //v9
0.5, 0.2, 0.0, //v10
// Second strip
-0.5, -0.3, 0.0, //v11
-0.4, -0.5, 0.0, //v12
-0.3, -0.3, 0.0, //v13
-0.2, -0.5, 0.0, //v14
-0.1, -0.3, 0.0, //v15
0.0, -0.5, 0.0, //v16
0.1, -0.3, 0.0, //v17
0.2, -0.5, 0.0, //v18
0.3, -0.3, 0.0, //v19
0.4, -0.5, 0.0, //v20
0.5, -0.3, 0.0 //v21
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(stripVertices), gl.STATIC_DRAW);
stripVertexBuffer.itemSize = 3;
stripVertexBuffer.numberOfItems = 22;
// Strip vertex indices
stripElementBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, stripElementBuffer);
var indices = [
0.0, 0.5, 0.0,
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0
// put correct indices here. Use degenerated triangles to link the
// strips together
];
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
stripElementBuffer.numberOfItems = 25;
}
function draw() {
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
gl.clear(gl.COLOR_BUFFER_BIT);
// Draw triangle. No change is made to the last week's code here
// For the triangle we want to use per-vertex color so
// the vertexColorAttribute, aVertexColor, in the vertex shader
// is enabled
// gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute);
// Make vertex buffer "triangleVertexBuffer" the current buffer
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexBuffer);
// Link the current buffer to the attribute "aVertexPosition" in
// the vertex shader
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexBuffer.itemSize, gl.FLOAT, false, 0, 0);
// Make color buffer "triangleVertexColorBuffer" the current buffer
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexColorBuffer);
// Link the current buffer to the attribute "aVertexColor" in
// the vertex shader
gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, triangleVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, triangleVertexBuffer.numberOfItems);
gl.drawElements(gl.TRIANGLE_STRIP, 0, stripVertexBuffer.numberOfItems, 25);
// Draw the newly added items
}
function startup() {
canvas = document.getElementById("myGLCanvas");
gl = createGLContext(canvas);
setupShaders();
setupBuffers();
gl.clearColor(1.0, 1.0, 1.0, 1.0);
draw();
}
startup();
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
attribute vec4 aVertexColor;
varying vec4 vColor;
void main() {
vColor = aVertexColor;
gl_Position = vec4(aVertexPosition, 1.0);
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec4 vColor;
void main() {
gl_FragColor = vColor;
}
</script>
<canvas id="myGLCanvas" width="250" height="250"></canvas>
Hey guys. New to WEBGL, trying to draw the triangle strip but no clue on how to approach this.
what I know:
When drawing with gl.TRIANGLE_STRIP mode, the order of the vertex coordinates or indices in the buffer is important.
Instead of specifying triangles by the programmer, WebGL constructs triangles automatically.
It reads vertex coordinate buffer or index buffer and use them in the following order to construct triangles:
these 2 lines in the code don't make any sense
gl.drawArrays(gl.TRIANGLES, 0, triangleVertexBuffer.numberOfItems);
gl.drawElements(gl.TRIANGLE_STRIP, 0, stripVertexBuffer.numberOfItems, 25);
It generally makes no sense to call draw twice without changing something in between since to draw 2 things requires setting up different data.
Further, the 2nd line is just plain wrong
gl.drawElements(gl.TRIANGLE_STRIP, 0, stripVertexBuffer.numberOfItems, 25);
If you had opened your JavaScript Console you would have seen an error something like
There are several issues
The code is passing a bad value to the type parameter of gl.drawElements
Type parameter to gl.drawElements is the type of data in the current ELEMENT_ARRAY_BUFFER.
The 2nd parameter is the count.
It's passing the number of vertices (stripVertexBuffer.numberOfItems) not the number of indices (stripElementBuffer.numberOfItems)
It should be something like this
{
const primitive = gl.TRIANGLE_STRIP;
const count = stripElementBuffer.numberOfItems;
const offset = 0;
const indexType = gl.UNSIGNED_SHORT;
gl.drawElements(primitive, count, indexType, offset);
}
Fixing that though isn't enough because the code does not actually put indices in the index buffer. That code
// Strip vertex indices
stripElementBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, stripElementBuffer);
var indices = [
0.0, 0.5, 0.0,
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0
// put correct indices here. Use degenerated triangles to link the
// strips together
];
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
stripElementBuffer.numberOfItems = 25;
also makes no sense. Indices are unsigned integer values from 0 to N where N is the one less than the number of vertices bound to the attributes. Further only 9 values are put in but the code sets stripElementBuffer.numberOfItems to 25 .. ?
Then, on top of all of that the code is not setting up the attributes for using the strip vertices.
To draw multiple things in WebGL works like this
for each thing you want to draw
gl.useProgram(theProgramYouWantToDrawWith);
// setup attributes
for each attribute
gl.bindBuffer(gl.ARRAY_BUFFER, bufferWithDataForAttribute);
gl.enableVertexAttribArray(attribLocation);
gl.vertexAttribPointer(attribLocation, ... how to get data out of buffer ...)
// if using indices setup the ELEMENT_ARRAY_BUFFER
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufferWithIndices);
// setup textures
for each texture you're going to draw with
gl.activeTexture(gl.TEXTURE0 + unit);
gl.bindTexture(gl.TEXTURE_??, someTexture);
// setup uniforms
for each uniform
gl.uniformXXX(...)
// draw
gl.drawXXX (either gl.drawArrays or gl.drawElements)
Before you can even attempt degenerate triangle strips you need to fix your code to follow that pattern. Also here's some other tutorials you might find helpful
I have a framebuffer to which I rendered my scene and now I want to render this to a "fullscreen" quad. How can I set my camera and what should I put in my vertex shader in order to render the framebuffer's texture to the whole screen.
I've tried creating a fullscreen quad like this
var gl = this.gl;
var quad_vertex_buffer = gl.createBuffer();
var quad_vertex_buffer_data = new Float32Array([
-1.0, -1.0, 0.0,
1.0, -1.0, 0.0,
-1.0, 1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, -1.0, 0.0,
1.0, 1.0, 0.0]);
gl.bufferData(quad_vertex_buffer, quad_vertex_buffer_data, gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, quad_vertex_buffer);
gl.vertexAttribPointer(this.shaderProgram.vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
//gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
gl.drawArrays(gl.TRIANGLES,0, 6);
but it still renders everything black. Any ideeas or examples/tutorials I can follow?
It is really not a big deal, once you get how to use Vertex Buffers and shaders. Then you can easily write an utility function to do it. Here is one I normally use, if you are looking for a reference:
drawFullScreenQuad : function(shaderProgram) {
if (!shaderProgram)
{
utils.warning("Missing the shader program!");
return;
}
// Only created once
if (this.screenQuadVBO == null)
{
var verts = [
// First triangle:
1.0, 1.0,
-1.0, 1.0,
-1.0, -1.0,
// Second triangle:
-1.0, -1.0,
1.0, -1.0,
1.0, 1.0
];
this.screenQuadVBO = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.screenQuadVBO);
this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(verts), this.gl.STATIC_DRAW);
}
// Bind:
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.screenQuadVBO);
this.gl.enableVertexAttribArray(shaderProgram.vertexAttributes.vertexPositionNDC);
this.gl.vertexAttribPointer(shaderProgram.vertexAttributes.vertexPositionNDC, 2, this.gl.FLOAT, false, 0, 0);
// Draw 6 vertexes => 2 triangles:
this.gl.drawArrays(this.gl.TRIANGLES, 0, 6);
// Cleanup:
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
},
Then you can go fancy like I did and compute the texture coordinates on-the-fly in the vertex shader:
Vertex Shader:
precision lowp float;
// xy = vertex position in normalized device coordinates ([-1,+1] range).
attribute vec2 vertexPositionNDC;
varying vec2 vTexCoords;
const vec2 scale = vec2(0.5, 0.5);
void main()
{
vTexCoords = vertexPositionNDC * scale + scale; // scale vertex attribute to [0,1] range
gl_Position = vec4(vertexPositionNDC, 0.0, 1.0);
}
Fragment Shader:
precision mediump float;
uniform sampler2D colorMap;
varying vec2 vTexCoords;
void main()
{
gl_FragColor = texture2D(colorMap, vTexCoords);
}
The important point to note are the vertexes in Normalized Device Coordinates (NDC), so you just pass the vertexes in the [-1,1] range and forward them directly to gl_Position without the need to multiply by a projection matrix.
Why do you need a camera to render a fullscreen quad? Rendering a fullscreen quad is pretty much the simplest thing you can do in WebGL. Given the buffer you already setup just use a shader like this
vertex shader:
attribute vec4 v_position;
void main() {
gl_Position = v_position;
}
fragment shader:
precision mediump float;
void main() {
gl_FragColor = vec4(0,1,0,1); // green
}
You should get a green screen.
There's a few bugs in the code though. You need to bind the buffer before you try up put data in it. You need to reference the buffer through the bind point, not the buffer object.
old (incorrect)
gl.bufferData(quad_vertex_buffer, quad_vertex_buffer_data, gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, quad_vertex_buffer);
new (correct)
gl.bindBuffer(gl.ARRAY_BUFFER, quad_vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, quad_vertex_buffer_data, gl.STATIC_DRAW);
Example:
const vs = `
attribute vec4 v_position;
void main() {
gl_Position = v_position;
}
`;
const fs = `
precision mediump float;
void main() {
gl_FragColor = vec4(0,1,0,1); // green
}
`;
var gl = document.querySelector("canvas").getContext("webgl");
var shader_program = twgl.createProgram(gl, [vs, fs]);
gl.useProgram(shader_program);
var vertexPositionAttribute = gl.getAttribLocation(shader_program, "v_position");
var quad_vertex_buffer = gl.createBuffer();
var quad_vertex_buffer_data = new Float32Array([
-1.0, -1.0, 0.0,
1.0, -1.0, 0.0,
-1.0, 1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, -1.0, 0.0,
1.0, 1.0, 0.0]);
gl.bindBuffer(gl.ARRAY_BUFFER, quad_vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, quad_vertex_buffer_data, gl.STATIC_DRAW);
gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(vertexPositionAttribute)
gl.drawArrays(gl.TRIANGLES, 0, 6);
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas></canvas>
Another example with a simple pattern to show it's working
const vs = `
attribute vec4 v_position;
void main() {
gl_Position = v_position;
}
`;
const fs = `
precision mediump float;
void main() {
gl_FragColor = vec4(fract(gl_FragCoord.xy / vec2(16., 32.)),0,1);
}
`;
var gl = document.querySelector("canvas").getContext("webgl");
var shader_program = twgl.createProgram(gl, [vs, fs]);
gl.useProgram(shader_program);
var vertexPositionAttribute = gl.getAttribLocation(shader_program, "v_position");
var quad_vertex_buffer = gl.createBuffer();
var quad_vertex_buffer_data = new Float32Array([
-1.0, -1.0, 0.0,
1.0, -1.0, 0.0,
-1.0, 1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, -1.0, 0.0,
1.0, 1.0, 0.0]);
gl.bindBuffer(gl.ARRAY_BUFFER, quad_vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, quad_vertex_buffer_data, gl.STATIC_DRAW);
gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(vertexPositionAttribute)
gl.drawArrays(gl.TRIANGLES, 0, 6);
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas></canvas>
I'd suggest you read some WebGL tutorials.