How do you link together triangle strips with degenerated triangles? - webgl

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

Related

WebGL multiple independent objects [duplicate]

This question already has answers here:
Drawing many shapes in WebGL
(3 answers)
WebGL Drawing Multiple Shapes of Different Colour
(1 answer)
WebGl adding multiple objects to one canvas
(1 answer)
Closed 3 years ago.
I'm not sure how to alter my solution to allow multiple shapes in webGL. Is it that I need multiple buffer arrays and vertex buffers for each shape? I currently have an object built out of a few transformed cubes, but now I want to add a square based pyramid, for example. I know how to set up the array buffer for this if it were the only object in the scene, but I am not sure how to do it such that it works in harmony with the rest of the objects. Would I also need another draw function, since one used at the moment takes in specific cube matrices as input and uses them to build the cubes? Here is the complete code:
var VSHADER_SOURCE = `
attribute vec4 a_Position;
attribute vec4 a_Color;
attribute vec4 a_Normal;
uniform mat4 u_ModelMatrix;
uniform mat4 u_NormalMatrix;
uniform mat4 u_ViewMatrix;
uniform mat4 u_ProjMatrix;
uniform vec3 u_LightColor;
uniform vec3 u_LightDirection; // Light direction (in the world coordinate, normalized)
varying vec4 v_Color;
uniform bool u_isLighting;
void main() {
gl_Position = u_ProjMatrix * u_ViewMatrix * u_ModelMatrix * a_Position;
if(u_isLighting)
{
vec3 normal = normalize((u_NormalMatrix * a_Normal).xyz);
float nDotL = max(dot(normal, u_LightDirection), 0.0);
// Calculate the color due to diffuse reflection
vec3 diffuse = u_LightColor * a_Color.rgb * nDotL;
v_Color = vec4(diffuse, a_Color.a); }
else
{
v_Color = a_Color;
}
}
`;
// Fragment shader program
var FSHADER_SOURCE = `
precision mediump float;
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
`;
var modelMatrix = new Matrix4(); // The model matrix
var viewMatrix = new Matrix4(); // The view matrix
var projMatrix = new Matrix4(); // The projection matrix
var g_normalMatrix = new Matrix4(); // Coordinate transformation matrix for normals
var ANGLE_STEP = 3.0; // The increments of rotation angle (degrees)
var g_xAngle = 0.0; // The rotation x angle (degrees)
var g_yAngle = 0.0; // The rotation y angle (degrees)
function main() {
// Retrieve <canvas> element
var canvas = document.getElementById('webgl');
// Get the rendering context for WebGL
var gl = getWebGLContext(canvas);
if (!gl) {
console.log('Failed to get the rendering context for WebGL');
return;
}
// Initialize shaders
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('Failed to intialize shaders.');
return;
}
// Set clear color and enable hidden surface removal
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST);
// Clear color and depth buffer
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Get the storage locations of uniform attributes
var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');
var u_ViewMatrix = gl.getUniformLocation(gl.program, 'u_ViewMatrix');
var u_NormalMatrix = gl.getUniformLocation(gl.program, 'u_NormalMatrix');
var u_ProjMatrix = gl.getUniformLocation(gl.program, 'u_ProjMatrix');
var u_LightColor = gl.getUniformLocation(gl.program, 'u_LightColor');
var u_LightDirection = gl.getUniformLocation(gl.program, 'u_LightDirection');
// Trigger using lighting or not
var u_isLighting = gl.getUniformLocation(gl.program, 'u_isLighting');
if (!u_ModelMatrix || !u_ViewMatrix || !u_NormalMatrix ||
!u_ProjMatrix || !u_LightColor || !u_LightDirection ||
!u_isLighting ) {
console.log('Failed to Get the storage locations of u_ModelMatrix, u_ViewMatrix, and/or u_ProjMatrix');
return;
}
// Set the light color (white)
gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0);
// Set the light direction (in the world coordinate)
var lightDirection = new Vector3([0.5, 3.0, 4.0]);
lightDirection.normalize(); // Normalize
gl.uniform3fv(u_LightDirection, lightDirection.elements);
// Calculate the view matrix and the projection matrix
viewMatrix.setLookAt(0, 0, 15, 0, 0, -100, 0, 1, 0);
projMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100);
// Pass the model, view, and projection matrix to the uniform variable respectively
gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements);
gl.uniformMatrix4fv(u_ProjMatrix, false, projMatrix.elements);
document.onkeydown = function(ev){
keydown(ev, gl, u_ModelMatrix, u_NormalMatrix, u_isLighting);
};
var then = 0;
// Draw the scene repeatedly
function render(now) {
now *= 0.001; // convert to seconds
const deltaTime = now - then;
then = now;
g_yAngle = (g_yAngle + 1) % 360;
drawchair(gl, u_ModelMatrix, u_NormalMatrix, u_isLighting, deltaTime)
requestAnimationFrame(render);
}
requestAnimationFrame(render);
//draw(gl, u_ModelMatrix, u_NormalMatrix, u_isLighting);
}
//keyboard functionality
function keydown(ev, gl, u_ModelMatrix, u_NormalMatrix, u_isLighting) {
switch (ev.keyCode) {
case 40: // Up arrow key -> the positive rotation of arm1 around the y-axis
g_xAngle = (g_xAngle + ANGLE_STEP) % 360;
break;
case 38: // Down arrow key -> the negative rotation of arm1 around the y-axis
g_xAngle = (g_xAngle - ANGLE_STEP) % 360;
break;
case 39: // Right arrow key -> the positive rotation of arm1 around the y-axis
g_yAngle = (g_yAngle + ANGLE_STEP) % 360;
break;
case 37: // Left arrow key -> the negative rotation of arm1 around the y-axis
g_yAngle = (g_yAngle - ANGLE_STEP) % 360;
break;
default: return; // Skip drawing at no effective action
}
}
// square vertices
function initVertexBuffersCube(gl) {
// Create a cube
// v6----- v5
// /| /|
// v1------v0|
// | | | |
// | |v7---|-|v4
// |/ |/
// v2------v3
var vertices = new Float32Array([ // Coordinates
0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5,-0.5, 0.5, 0.5,-0.5, 0.5, // v0-v1-v2-v3 front
0.5, 0.5, 0.5, 0.5,-0.5, 0.5, 0.5,-0.5,-0.5, 0.5, 0.5,-0.5, // v0-v3-v4-v5 right
0.5, 0.5, 0.5, 0.5, 0.5,-0.5, -0.5, 0.5,-0.5, -0.5, 0.5, 0.5, // v0-v5-v6-v1 up
-0.5, 0.5, 0.5, -0.5, 0.5,-0.5, -0.5,-0.5,-0.5, -0.5,-0.5, 0.5, // v1-v6-v7-v2 left
-0.5,-0.5,-0.5, 0.5,-0.5,-0.5, 0.5,-0.5, 0.5, -0.5,-0.5, 0.5, // v7-v4-v3-v2 down
0.5,-0.5,-0.5, -0.5,-0.5,-0.5, -0.5, 0.5,-0.5, 0.5, 0.5,-0.5 // v4-v7-v6-v5 back
]);
//shading based on vertices
var colors = new Float32Array([ // Colors
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v1-v2-v3 front
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v3-v4-v5 right
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v5-v6-v1 up
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v1-v6-v7-v2 left
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v7-v4-v3-v2 down
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0  // v4-v7-v6-v5 back
]);
var normals = new Float32Array([ // Normal
0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, // v0-v1-v2-v3 front
1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, // v0-v3-v4-v5 right
0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v0-v5-v6-v1 up
-1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, // v1-v6-v7-v2 left
0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, // v7-v4-v3-v2 down
0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0 // v4-v7-v6-v5 back
]);
// Indices of the vertices, from which things are built from triangle
// 1_________2
// | /|
// | / |
// | / |
// | / |
// |/________|
// 0 3
// A clockwise arrangement, as it were starting bottom left
var indices = new Uint8Array([
0, 1, 2, 0, 2, 3, // front
4, 5, 6, 4, 6, 7, // right
8, 9,10, 8,10,11, // up
12,13,14, 12,14,15, // left
16,17,18, 16,18,19, // down
20,21,22, 20,22,23 // back
]);
// Write the vertex property to buffers (coordinates, colors and normals)
if (!initArrayBuffer(gl, 'a_Position', vertices, 3, gl.FLOAT)) return -1;
if (!initArrayBuffer(gl, 'a_Color', colors, 3, gl.FLOAT)) return -1;
if (!initArrayBuffer(gl, 'a_Normal', normals, 3, gl.FLOAT)) return -1;
// Write the indices to the buffer object
var indexBuffer = gl.createBuffer();
if (!indexBuffer) {
console.log('Failed to create the buffer object');
return false;
}
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
return indices.length;
}
function initArrayBuffer (gl, attribute, data, num, type) {
// Create a buffer object
var buffer = gl.createBuffer();
if (!buffer) {
console.log('Failed to create the buffer object');
return false;
}
// Write date into the buffer object
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
// Assign the buffer object to the attribute variable
var a_attribute = gl.getAttribLocation(gl.program, attribute);
if (a_attribute < 0) {
console.log('Failed to get the storage location of ' + attribute);
return false;
}
gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);
// Enable the assignment of the buffer object to the attribute variable
gl.enableVertexAttribArray(a_attribute);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
return true;
}
var g_matrixStack = []; // Array for storing a matrix
function pushMatrix(m) { // Store the specified matrix to the array
var m2 = new Matrix4(m);
g_matrixStack.push(m2);
}
function popMatrix() { // Retrieve the matrix from the array
return g_matrixStack.pop();
}
function drawchair(gl, u_ModelMatrix, u_NormalMatrix, u_isLighting) {
// Clear color and depth buffer
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.uniform1i(u_isLighting, false); // Will not apply lighting
// Calculate the view matrix and the projection matrix
modelMatrix.setTranslate(0, 0, 0); // No Translation
// Pass the model matrix to the uniform variable
gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);
// Draw x and y axes
gl.drawArrays(gl.LINES, 0, n);
gl.uniform1i(u_isLighting, true); // Will apply lighting
// Set the vertex coordinates and color (for the cube)
var n = initVertexBuffersCube(gl);
if (n < 0) {
console.log('Failed to set the vertex information');
return;
}
//x,y,z
// Rotate, and then translate
modelMatrix.setTranslate(0, 0, -5); // Translation (No translation is supported here)
modelMatrix.rotate(g_yAngle, 0, 1, 0); // Rotate along y axis
modelMatrix.rotate(g_xAngle, 1, 0, 0); // Rotate along x axis
// Model the chair seat
pushMatrix(modelMatrix);
modelMatrix.scale(2.0, 0.4, 2.0); // Scale
drawbox(gl, u_ModelMatrix, u_NormalMatrix, n);
modelMatrix = popMatrix();
// Model the chair back
pushMatrix(modelMatrix);
modelMatrix.translate(0, 1.20, -0.8); // Translation
modelMatrix.scale(2.0, 2.2, 0.4); // Scale
drawbox(gl, u_ModelMatrix, u_NormalMatrix, n);
modelMatrix = popMatrix();
//As if you were sitting on the chair;
//Back right leg
pushMatrix(modelMatrix);
modelMatrix.scale(0.3, 1.9, 0.3);
modelMatrix.translate(-2.6,-0.4,-2.6)
drawbox(gl, u_ModelMatrix, u_NormalMatrix, n);
modelMatrix = popMatrix();
//Back left leg
pushMatrix(modelMatrix);
modelMatrix.scale(0.3, 1.9, 0.3);
modelMatrix.translate(2.6,-0.4,-2.6)
drawbox(gl, u_ModelMatrix, u_NormalMatrix, n);
modelMatrix = popMatrix();
//Front right leg
pushMatrix(modelMatrix);
modelMatrix.scale(0.3, 1.9, 0.3);
modelMatrix.translate(-2.6,-0.4,2.6)
drawbox(gl, u_ModelMatrix, u_NormalMatrix, n);
modelMatrix = popMatrix();
//Front left leg
pushMatrix(modelMatrix);
modelMatrix.scale(0.3, 1.9, 0.3);
modelMatrix.translate(2.6,-0.4,2.6)
drawbox(gl, u_ModelMatrix, u_NormalMatrix, n);
modelMatrix = popMatrix();
}
function drawbox(gl, u_ModelMatrix, u_NormalMatrix, n) {
pushMatrix(modelMatrix);
// Pass the model matrix to the uniform variable
gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);
// Calculate the normal transformation matrix and pass it to u_NormalMatrix
g_normalMatrix.setInverseOf(modelMatrix); //set the normal matrix as the inverse of the current model
g_normalMatrix.transpose();
gl.uniformMatrix4fv(u_NormalMatrix, false, g_normalMatrix.elements);
// Draw the cube
gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
modelMatrix = popMatrix();
}
function drawsphere(gl, u_ModelMatrix, u_NormalMatrix, n) {
pushMatrix(modelMatrix);
// Pass the model matrix to the uniform variable
gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);
// Calculate the normal transformation matrix and pass it to u_NormalMatrix
g_normalMatrix.setInverseOf(modelMatrix); //set the normal matrix as the inverse of the current model
g_normalMatrix.transpose();
gl.uniformMatrix4fv(u_NormalMatrix, false, g_normalMatrix.elements);
// Draw the cube
gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
modelMatrix = popMatrix();
}
You need to create copies of shaders etc for each graphical primitive. It could turn your code into spaghetti extremely quickly.
You can improve your code by abstracting graphical primitives into classes and use composition to create complicated shapes and figures.
Once you switch to OOP you will benefit from having multiple objects.

WebGL: cannot display two textures

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>

How to dynamically add objects to scene?

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

WebGL vertexAttribPointer points to the wrong VBO

I am trying to create two meshes, a square and a triangle. The code is at the bottom.
I first create a shader program "program1", array buffer "vertexBuffer1" and an element array buffer "indexBuffer1" for the first mesh. The first mesh is a square.
Then I do the same thing for the second mesh. The second mesh is a triangle.
When I run the code I get the error:
[.Offscreen-For-WebGL-000002B76A973870]GL ERROR :GL_INVALID_OPERATION : glDrawElements: attempt to access out of range vertices in attribute 0
I don't get the error if I comment out this line:
//gl.vertexAttribPointer(loc, 3, gl.FLOAT, false, 0, 0); // <- problem!
I think what is happening is that somehow the triangle's vbo ("vertexBuffer2") gets connected to the square's shader program ("program1"). This causes
gl.drawElements(gl.TRIANGLES, indices1.length, gl.UNSIGNED_SHORT, 0);
to fail as it is trying to draw a square from the triangles vertex buffer.
I don't understand why
gl.vertexAttribPointer(loc, 3, gl.FLOAT, false, 0, 0); // <- problem!
causes "program1" to connect to "vertexBuffer2", if that is even the problem. How do I make theese two programs ("program1" and "program2") connect to each their buffers?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<canvas id="canvas" width="500" height="450" style="border:1px solid black"></canvas>
<script>
// setup gl
var canvas = document.getElementById("canvas");
var gl = canvas.getContext("webgl");
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.clearColor(0.5, 0.5, 0.5, 0.9);
gl.clearDepth(1.0);
gl.viewport(0.0, 0.0, canvas.width, canvas.height);
// setup mesh 1
// // vertex shader
var vertexShader1 = gl.createShader(gl.VERTEX_SHADER);
var vertexShaderCode = `
// in
attribute vec3 position;
void main(void) {
gl_Position = vec4(position, 1.);
}
`;
gl.shaderSource(vertexShader1, vertexShaderCode);
gl.compileShader(vertexShader1);
// // fragment shader
var fragmentShader1 = gl.createShader(gl.FRAGMENT_SHADER);
var fragmentShaderCode = `
precision mediump float;
// in
void main(void) {
gl_FragColor = vec4(1., 0., 0., 1.);
}
`;
gl.shaderSource(fragmentShader1, fragmentShaderCode);
gl.compileShader(fragmentShader1);
// // program1
var program1 = gl.createProgram();
gl.attachShader(program1, vertexShader1);
gl.attachShader(program1, fragmentShader1);
gl.linkProgram(program1);
gl.useProgram(program1);
// // create buffer 1
var vertices1 = [ // suqare
-0.5, -0.5, 0,
0.5, -0.5, 0,
0.5, 0.5, 0,
-0.5, 0.5, 0,
];
var vertexBuffer1 = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer1);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices1), gl.STATIC_DRAW);
var loc = gl.getAttribLocation(program1, "position");
gl.vertexAttribPointer(loc, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(loc);
var indices1 = [
0,1,2,
0,2,3,
];
var indexBuffer1 = gl.createBuffer ();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer1);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices1), gl.STATIC_DRAW);
// setup mesh 2
// // vertex shader
var vertexShader2 = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader2, vertexShaderCode); // uses same vertexShaderCode as above
gl.compileShader(vertexShader2);
// // fragment shader
var fragmentShader2 = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader2, fragmentShaderCode);
gl.compileShader(fragmentShader2);
// // program2
var program2 = gl.createProgram();
gl.attachShader(program2, vertexShader2);
gl.attachShader(program2, fragmentShader2);
gl.linkProgram(program2);
gl.useProgram(program2);
// // create buffer 2
var vertices2 = [ // triangle one less vertex than in buffer 1
-0.5, -0.5, 0,
0.5, -0.5, 0,
0.5, 0.5, 0,
];
var vertexBuffer2 = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer2);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices2), gl.STATIC_DRAW);
var loc = gl.getAttribLocation(program2, "position");
// On the line below it seems like program1 instead of program2 gets connected to the vertexBuffer2
gl.vertexAttribPointer(loc, 3, gl.FLOAT, false, 0, 0); // <- problem!
gl.enableVertexAttribArray(loc);
var indices2 = [
0,1,2,
];
var indexBuffer2 = gl.createBuffer ();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer2);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices2), gl.STATIC_DRAW);
// render
// // clear
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// // render mesh 1 (the square)
gl.useProgram(program1);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer1);
gl.drawElements(gl.TRIANGLES, indices1.length, gl.UNSIGNED_SHORT, 0); // <- this line fail
</script>
</body>
</html>
Note, gl.vertexAttribPointer specifies the state for a generic vertex attribute array.
First you create and bind vertexBuffer1, indexBuffer1 and program1. And you specify:
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer1);
....
var loc = gl.getAttribLocation(program1, "position"); // loc == 0
gl.vertexAttribPointer(loc, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(loc);
Second you create and bind vertexBuffer2, indexBuffer2 and program2. And you specify:
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer2);
....
var loc = gl.getAttribLocation(program2, "position"); // loc == 0
gl.vertexAttribPointer(loc, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(loc);
Finally you call:
gl.useProgram(program1);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer1);
gl.drawElements(gl.TRIANGLES, indices1.length, gl.UNSIGNED_SHORT, 0);
At this moment the vertex buffer state still refers to vertexBuffer2, because this is the last state which you specified for the generic vertex attribute with index 0.
Change yor code like this:
gl.linkProgram(program1);
var loc1 = gl.getAttribLocation(program1, "position");
....
gl.useProgram(program1);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer1);
gl.vertexAttribPointer(loc, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(loc);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer1);
gl.drawElements(gl.TRIANGLES, indices1.length, gl.UNSIGNED_SHORT, 0);
Note, it is sufficient to set the vertex attribute sate immediately before you draw a mesh. For managing different states of vertex attributes you may use VertexArrayObjects, which are supported in WebGL 2.0 or available via extension in WebGL 1.0.
See the code snippet:
// setup gl
var canvas = document.getElementById("canvas");
var gl = canvas.getContext("webgl");
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.clearColor(0.5, 0.5, 0.5, 0.9);
gl.clearDepth(1.0);
gl.viewport(0.0, 0.0, canvas.width, canvas.height);
// setup mesh 1
// // vertex shader
var vertexShader1 = gl.createShader(gl.VERTEX_SHADER);
var vertexShaderCode = `
// in
attribute vec3 position;
void main(void) {
gl_Position = vec4(position, 1.);
}
`;
gl.shaderSource(vertexShader1, vertexShaderCode);
gl.compileShader(vertexShader1);
// // fragment shader
var fragmentShader1 = gl.createShader(gl.FRAGMENT_SHADER);
var fragmentShaderCode = `
precision mediump float;
// in
void main(void) {
gl_FragColor = vec4(1., 0., 0., 1.);
}
`;
gl.shaderSource(fragmentShader1, fragmentShaderCode);
gl.compileShader(fragmentShader1);
// // program1
var program1 = gl.createProgram();
gl.attachShader(program1, vertexShader1);
gl.attachShader(program1, fragmentShader1);
gl.linkProgram(program1);
var loc1 = gl.getAttribLocation(program1, "position");
// // create buffer 1
var vertices1 = [ // suqare
-0.5, -0.5, 0,
0.5, -0.5, 0,
0.5, 0.5, 0,
-0.5, 0.5, 0,
];
var vertexBuffer1 = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer1);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices1), gl.STATIC_DRAW);
var indices1 = [
0,1,2,
0,2,3,
];
var indexBuffer1 = gl.createBuffer ();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer1);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices1), gl.STATIC_DRAW);
// setup mesh 2
// // vertex shader
var vertexShader2 = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader2, vertexShaderCode); // uses same vertexShaderCode as above
gl.compileShader(vertexShader2);
// // fragment shader
var fragmentShader2 = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader2, fragmentShaderCode);
gl.compileShader(fragmentShader2);
// // program2
var program2 = gl.createProgram();
gl.attachShader(program2, vertexShader2);
gl.attachShader(program2, fragmentShader2);
gl.linkProgram(program2);
var loc2 = gl.getAttribLocation(program2, "position");
// // create buffer 2
var vertices2 = [ // triangle one less vertex than in buffer 1
-0.5, -0.5, 0,
0.5, -0.5, 0,
0.5, 0.5, 0,
];
var vertexBuffer2 = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer2);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices2), gl.STATIC_DRAW);
var indices2 = [
0,1,2,
];
var indexBuffer2 = gl.createBuffer ();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer2);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices2), gl.STATIC_DRAW);
// render
// // clear
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// // render mesh 1 (the square)
gl.useProgram(program1);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer1);
gl.vertexAttribPointer(loc1, 3, gl.FLOAT, false, 0, 0); // <- problem!
gl.enableVertexAttribArray(loc1);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer1);
gl.drawElements(gl.TRIANGLES, indices1.length, gl.UNSIGNED_SHORT, 0); // <- this line fail
<canvas id="canvas" width="500" height="450" style="border:1px solid black"></canvas>

How stencil buffer and masking work?

I want to draw object in just specific area. Please take a look this image for reference
The 2 triangles (picture A) being draw just in the area inside the quad (picture B), so the result will look clipped (picture C).
First i draw the quad just in stencil buffer.
gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);
gl.stencilFunc(gl.ALWAYS, 1, 0xff);
gl.stencilMask(0xff);
gl.depthMask(false);
gl.colorMask(false, false, false, false);
drawQuads();
in my understanding, now the stencil buffer has value 1s in the quad area. Then, draw the triangles.
gl.stencilFunc(gl.EQUAL, 1, 0xff);
gl.stencilMask(0x00);
gl.depthMask(true);
gl.colorMask(true, true, true, true);
drawTriagles();
I was expect the result will be like on the picture (C), but it's not. What I am doing wrong?
Please find the complete code here https://jsfiddle.net/z11zhf01/1
Your program works absolute correctly, but you have to tell the getContext function to create a stencil buffer, when the context is created:
gl = glcanvas.getContext("webgl", {stencil:true});
See Khronos WebGL Specification - WebGLContextAttributes:
stencil
If the value is true, the drawing buffer has a stencil buffer of at least 8 bits. If the value is false, no stencil buffer is available.
See the Example:
(function() {
var gl;
var gProgram;
var gVertexAttribLocation;
var gColorAttribLocation;
var gTriangleVertexBuffer;
var gTriangleColorBuffer;
var gQuadVertexBuffer;
var gQuadColorBuffer;
function initGL() {
var glcanvas = document.getElementById("glcanvas");
gl = glcanvas.getContext("webgl", {stencil:true});
}
function createAndCompileShader(type, source) {
var shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw new Error(gl.getShaderInfoLog(shader));
}
return shader;
}
function createAndLinkProgram(glVertexShader, glFragmentShader) {
var glProgram = gl.createProgram();
gl.attachShader(glProgram, glVertexShader);
gl.attachShader(glProgram, glFragmentShader);
gl.linkProgram(glProgram);
if (!gl.getProgramParameter(glProgram, gl.LINK_STATUS)) {
throw new Error("Could not initialise shaders");
}
return glProgram;
}
function initShaderPrograms() {
var gVertexShader = createAndCompileShader(gl.VERTEX_SHADER, [
"attribute vec3 a_vertex;",
"attribute vec4 a_color;",
"varying vec4 v_color;",
"void main(void) {",
"v_color = a_color;",
"gl_Position = vec4(a_vertex, 1.0);",
"}"
].join("\n"));
var gFragmentShader = createAndCompileShader(gl.FRAGMENT_SHADER, [
"precision mediump float;",
"varying vec4 v_color;",
"void main(void) {",
"gl_FragColor = v_color;",
"}"
].join("\n"));
gProgram = createAndLinkProgram(gVertexShader, gFragmentShader);
}
function initGLAttribLocations() {
gVertexAttribLocation = gl.getAttribLocation(gProgram, "a_vertex");
gColorAttribLocation = gl.getAttribLocation(gProgram, "a_color");
}
function initBuffers() {
gTriangleVertexBuffer = gl.createBuffer();
gTriangleColorBuffer = gl.createBuffer();
gQuadVertexBuffer = gl.createBuffer();
gQuadColorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, gTriangleVertexBuffer);
var vertices = new Float32Array([
0.0, 1.0, 0.0,
-1.0, -1.0, 0.0,
1.0, -1.0, 0.0,
0.0, -1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, 1.0, 0.0
]);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, gTriangleColorBuffer);
var colors = new Float32Array([
0.0, 1.0, 0.0, 1.0,
0.0, 1.0, 0.0, 1.0,
0.0, 1.0, 0.0, 1.0,
0.0, 0.0, 1.0, 1.0,
0.0, 0.0, 1.0, 1.0,
0.0, 0.0, 1.0, 1.0
]);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, gQuadVertexBuffer);
var vertices = 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
]);
for(let i = 0, ii = vertices.length; i < ii; ++i) {
vertices[i] *= 0.75;
}
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, gQuadColorBuffer);
var colors = new Float32Array([
1.0, 0.0, 0.0, 1.0,
1.0, 0.0, 0.0, 1.0,
1.0, 0.0, 0.0, 1.0,
1.0, 0.0, 0.0, 1.0,
]);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
}
function drawQuads() {
gl.bindBuffer(gl.ARRAY_BUFFER, gQuadVertexBuffer);
gl.vertexAttribPointer(gVertexAttribLocation, 3, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, gQuadColorBuffer);
gl.vertexAttribPointer(gColorAttribLocation, 4, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
function drawTriagles() {
gl.bindBuffer(gl.ARRAY_BUFFER, gTriangleVertexBuffer);
gl.vertexAttribPointer(gVertexAttribLocation, 3, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, gTriangleColorBuffer);
gl.vertexAttribPointer(gColorAttribLocation, 4, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
function renderScene() {
gl.enable(gl.STENCIL_TEST);
gl.enable(gl.DEPTH_TEST);
// gl.enable(gl.CULL_FACE);
gl.useProgram(gProgram);
gl.clearColor(0.5, 0.5, 0.5, 1.0);
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
gl.enableVertexAttribArray(gVertexAttribLocation);
gl.enableVertexAttribArray(gColorAttribLocation);
gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);
gl.stencilFunc(gl.ALWAYS, 1, 0xff);
gl.stencilMask(0xff);
gl.depthMask(false);
gl.colorMask(false, false, false, false);
drawQuads();
gl.stencilFunc(gl.EQUAL, 1, 0xff);
gl.stencilMask(0x00);
gl.depthMask(true);
gl.colorMask(true, true, true, true);
drawTriagles();
gl.disableVertexAttribArray(gVertexAttribLocation);
gl.disableVertexAttribArray(gColorAttribLocation);
gl.flush();
}
initGL();
initShaderPrograms();
initGLAttribLocations();
initBuffers();
renderScene();
}());
<main>
<canvas id="glcanvas" width="480" height="360">
WebGL not supported!
</canvas>
</main>

Resources