Related
How to console.log the polygon count in WebGL, where I want the check the reference of the polygon count before culling and after culling. I'm new to WebGL where I'm analyzing the culling concepts where I want to check the count of the polygons before and culled.
Kindly, help me out! Thanks in advance.
Here's the sample code
(function(global) {
/*
* Constants, State, and Main
* www.programmingtil.com
* www.codenameparkerllc.com
*/
var KEYPRESS_SPEED = 0.2;
var IMAGES = [
{name:"stainglass", src:"/images/txStainglass.bmp"},
{name:"crate", src:"/images/txCrate.bmp"},
];
var state = {
gl: null,
mode: 'render',
ui: {
dragging: false,
mouse: {
lastX: -1,
lastY: -1,
},
pressedKeys: {},
},
animation: {},
app: {
animate: true,
eye: {
x:2.,
y:2.,
z:5.,
w:1.,
},
fog: {
color: new Float32Array([0.5,0.5,0.5]),
dist: new Float32Array([60, 80]),
on: false,
},
light: {
ambient: [0.2, 0.2, 0.2],
diffuse: [1.0, 1.0, 1.0],
position: [1.0, 2.0, 1.7],
},
objects: [],
textures: {},
},
eyeInArray: function() {
return [this.app.eye.x, this.app.eye.y, this.app.eye.z, this.app.eye.w];
}
};
glUtils.SL.init({ callback:function() { main(); } });
function main() {
state.canvas = document.getElementById("glcanvas");
state.overlay = document.getElementById("overlay");
state.ctx = state.overlay.getContext("2d");
state.gl = glUtils.checkWebGL(state.canvas, {
preserveDrawingBuffer: true,
});
initCallbacks();
initShaders();
initGL();
initCanvasTexture();
initState();
glUtils.initTextures(IMAGES, state.app.textures, function() {
draw();
if (state.app.animate) {
animate();
}
});
}
/*
* Primitives and Objects
* www.programmingtil.com
* www.codenameparkerllc.com
*/
// Create a cube
function Cube(opts) {
var opts = opts || {};
this.id = saKnife.uuid();
this.opts = opts;
this.gl = opts.gl;
this.programs = opts.programs;
// Vextex positions
// v0-v1-v2-v3 front
// v0-v3-v4-v5 right
// v0-v5-v6-v1 up
// v1-v6-v7-v2 left
// v7-v4-v3-v2 down
// v4-v7-v6-v5 back
this.attributes = {
aColor: {
size:4,
offset:0,
bufferData: new Float32Array([
1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1,
1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1,
0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1
]),
},
aNormal: {
size:3,
offset:0,
bufferData: new Float32Array([
0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0,
1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,
-1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0,
0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0,
0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0
]),
},
aPosition: {
size:3,
offset:0,
bufferData: new Float32Array([
1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0,-1.0, 1.0, 1.0,-1.0, 1.0,
1.0, 1.0, 1.0, 1.0,-1.0, 1.0, 1.0,-1.0,-1.0, 1.0, 1.0,-1.0,
1.0, 1.0, 1.0, 1.0, 1.0,-1.0, -1.0, 1.0,-1.0, -1.0, 1.0, 1.0,
-1.0, 1.0, 1.0, -1.0, 1.0,-1.0, -1.0,-1.0,-1.0, -1.0,-1.0, 1.0,
-1.0,-1.0,-1.0, 1.0,-1.0,-1.0, 1.0,-1.0, 1.0, -1.0,-1.0, 1.0,
1.0,-1.0,-1.0, -1.0,-1.0,-1.0, -1.0, 1.0,-1.0, 1.0, 1.0,-1.0
]),
},
aSelColor: {
size:4,
offset:0,
bufferData: undefined,
},
aTexCoord: {
size:2,
offset:0,
bufferData: new Float32Array([
1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,
0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0,
1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0,
1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0
]),
},
};
this.indices = new Uint8Array([
0, 1, 2, 0, 2, 3,
4, 5, 6, 4, 6, 7,
8, 9,10, 8,10,11,
12,13,14, 12,14,15,
16,17,18, 16,18,19,
20,21,22, 20,22,23
]);
// Functionality
this.readState = function() {
this.attributes.aColorBackup = this.attributes.aColor;
this.attributes.aColor = this.attributes.aSelColor;
this.state.renderMode = 'read';
};
this.drawState = function() {
this.attributes.aColor = this.attributes.aColorBackup;
this.state.renderMode = 'render';
};
this.draw = function() {
this.gl.useProgram(this.programs[this.state.renderMode]);
var uMVPMatrix = this.gl.getUniformLocation(this.programs[this.state.renderMode], 'uMVPMatrix');
var uModelMatrix = this.gl.getUniformLocation(this.programs[this.state.renderMode], 'uModelMatrix');
var uNormalMatrix = this.gl.getUniformLocation(this.programs[this.state.renderMode], 'uNormalMatrix');
var uAmbientLight = this.gl.getUniformLocation(this.programs[this.state.renderMode], 'uAmbientLight');
var uDiffuseLight = this.gl.getUniformLocation(this.programs[this.state.renderMode], 'uDiffuseLight');
var uLightPosition = this.gl.getUniformLocation(this.programs[this.state.renderMode], 'uLightPosition');
var uFogColor = this.gl.getUniformLocation(this.programs[this.state.renderMode], 'uFogColor');
var uFogDist = this.gl.getUniformLocation(this.programs[this.state.renderMode], 'uFogDist');
var uSampler0 = this.gl.getUniformLocation(this.programs[this.state.renderMode], 'uSampler0');
var mvp = state.mvp;
this.programs[this.state.renderMode].renderBuffers(this);
var n = this.indices.length;
// Model matrix
var mm = mat4.create();
if (this.opts.translate) {
mat4.translate(mm, mm, this.opts.translate);
}
if (this.opts.scale) {
mat4.scale(mm, mm, this.opts.scale);
}
if (this.state.angle[0] || this.state.angle[1] || this.state.angle[2]) {
mat4.rotateX(mm, mm, this.state.angle[0]);
mat4.rotateY(mm, mm, this.state.angle[1]);
mat4.rotateZ(mm, mm, this.state.angle[2]);
}
this.gl.uniformMatrix4fv(uModelMatrix, false, mm);
// MVP matrix
mat4.copy(mvp, state.pm);
mat4.multiply(mvp, mvp, state.vm);
mat4.multiply(mvp, mvp, mm);
this.gl.uniformMatrix4fv(uMVPMatrix, false, mvp);
// Fog
if (state.app.fog.on) {
this.gl.uniform3fv(uFogColor, state.app.fog.color);
this.gl.uniform2fv(uFogDist, state.app.fog.dist);
}
// Lighting
if (this.state.renderMode === 'render') {
this.gl.uniform3f(uDiffuseLight, state.app.light.diffuse[0], state.app.light.diffuse[1], state.app.light.diffuse[2]);
this.gl.uniform3f(uAmbientLight, state.app.light.ambient[0], state.app.light.ambient[1], state.app.light.ambient[2]);
this.gl.uniform3f(uLightPosition, state.app.light.position[0], state.app.light.position[1], state.app.light.position[2]);
var nm = mat3.normalFromMat4(mat3.create(), mm);
this.gl.uniformMatrix3fv(uNormalMatrix, false, nm);
}
// Textures
if (this.state.hasTexture) {
this.gl.activeTexture(this.gl.TEXTURE0);
this.gl.bindTexture(this.gl.TEXTURE_2D, state.app.textures[this.opts.texture]);
this.gl.uniform1i(uSampler0, 0);
}
// Blending
if (this.state.hasBlend && this.state.renderMode === 'render') {
this.gl.blendFunc(this.state.blendSrc, this.state.blendDst);
this.gl.blendEquation(this.state.blendEquation);
this.gl.disable(this.gl.CULL_FACE);
}
else {
this.gl.enable(this.gl.DEPTH_TEST);
this.gl.disable(this.gl.BLEND);
this.gl.depthMask(true);
// Culling
this.gl.enable(this.gl.CULL_FACE);
this.gl.cullFace(this.gl.BACK);
}
// Culling
// this.gl.enable(this.gl.CULL_FACE);
// this.gl.cullFace(this.gl.FRONT_AND_BACK);
// this.gl.cullFace(this.gl.FRONT);
// this.gl.cullFace(this.gl.BACK);
// Draw!
this.gl.drawElements(this.gl.TRIANGLES, n, this.gl.UNSIGNED_BYTE, 0);
};
// Cube Initialization
this.init = function(_this) {
var selColor = opts.selColor ? opts.selColor : [0,0,0,0];
_this.selColor = selColor.map(function(n) { return n/255; });
var arrays = [
_this.selColor, _this.selColor, _this.selColor, _this.selColor,
_this.selColor, _this.selColor, _this.selColor, _this.selColor,
_this.selColor, _this.selColor, _this.selColor, _this.selColor,
_this.selColor, _this.selColor, _this.selColor, _this.selColor,
_this.selColor, _this.selColor, _this.selColor, _this.selColor,
_this.selColor, _this.selColor, _this.selColor, _this.selColor,
];
_this.attributes.aSelColor.bufferData = new Float32Array([].concat.apply([], arrays));
_this.state = {
angle: opts.angle ? opts.angle : [0,0,0],
blendEquation: opts.blendEquation ? opts.blendEquation : _this.gl.FUNC_ADD,
blendSrc: opts.blendSrc ? opts.blendSrc : _this.gl.SRC_ALPHA,
blendDst: opts.blendDst ? opts.blendDst : _this.gl.ONE_MINUS_SRC_ALPHA,
hasBlend: opts.blend ? true : false,
hasTexture: opts.texture ? true : false,
mm: mat4.create(),
nm: null,
renderMode: 'render',
};
}(this);
}
/*
* Initialization
* www.programmingtil.com
* www.codenameparkerllc.com
*/
function initCallbacks() {
document.onkeydown = keydown;
document.onkeyup = keyup;
state.canvas.onmousedown = mousedown;
state.canvas.onmouseup = mouseup;
state.canvas.onmousemove = mousemove;
}
function initShaders() {
var vertexRead = glUtils.getShader(state.gl.VERTEX_SHADER, glUtils.SL.Shaders.read.vertex),
fragmentRead = glUtils.getShader(state.gl.FRAGMENT_SHADER, glUtils.SL.Shaders.read.fragment),
vertexRender = glUtils.getShader(state.gl.VERTEX_SHADER, glUtils.SL.Shaders.render.vertex),
fragmentRender = glUtils.getShader(state.gl.FRAGMENT_SHADER, glUtils.SL.Shaders.render.fragment),
vertexTex = glUtils.getShader(state.gl.VERTEX_SHADER, glUtils.SL.Shaders.tex.vertex),
fragmentTex = glUtils.getShader(state.gl.FRAGMENT_SHADER, glUtils.SL.Shaders.tex.fragment);
state.programs = {
read: glUtils.createProgram(vertexRead, fragmentRead),
render: glUtils.createProgram(vertexRender, fragmentRender),
texture: glUtils.createProgram(vertexTex, fragmentTex),
};
}
function initGL() {
state.gl.clearColor(0,0,0,1);
}
function initState() {
state.vm = mat4.create();
state.pm = mat4.create();
state.mvp = mat4.create();
state.app.objects = [
new Cube({
blend: true,
blendDst: state.gl.ONE,
gl: state.gl,
programs: {
render: state.programs.texture,
read: state.programs.read,
},
selColor:[255,254,0,0],
scale:[0.5,0.5,0.5],
texture:'stainglass',
}),
new Cube({
gl: state.gl,
programs: {
render: state.programs.render,
read: state.programs.read,
},
selColor:[255,255,0,0],
translate:[3,0,0],
}),
new Cube({
gl: state.gl,
programs: {
render: state.programs.render,
read: state.programs.read,
},
selColor:[255,253,0,0],
scale:[0.5,0.5,0.5],
translate:[-2, 2, 0],
}),
new Cube({
blend: true,
blendDst: state.gl.ONE,
gl: state.gl,
programs: {
render: state.programs.texture,
read: state.programs.read,
},
selColor:[255,252,0,0],
scale:[0.6,0.6,0.6],
texture:'crate',
translate:[-2, -2, 2],
angle: [0,35,0],
}),
new Cube({
gl: state.gl,
programs: {
render: state.programs.texture,
read: state.programs.read,
},
selColor:[255,251,0,0],
scale:[0.2,0.2,0.2],
texture:'crate',
translate:[2, 2, 2],
angle: [75,0,0],
}),
];
}
function initCanvasTexture() {
var texture = state.gl.createTexture();
var textCanvas = document.createElement('canvas');
textCanvas.width = 256;
textCanvas.height = 256;
var ctx = textCanvas.getContext('2d');
// Setup background
ctx.fillStyle = 'rgba(53, 60, 145, 1.0)';
ctx.fillRect(0, 0, textCanvas.width, textCanvas.height);
// Setup font
ctx.font = '36px bold sans-serif';
ctx.fillStyle = 'rgba(0, 60, 145, 1.0)';
ctx.textBaseline = 'middle';
ctx.shadowColor = 'rgba(10, 160, 190, 1.0)';
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.shadowBlur = 5;
// Draw out some text
var text = 'ProgrammingTIL';
var textWidth = ctx.measureText(text).width;
ctx.fillText(text, (textCanvas.width-textWidth)/2, textCanvas.height/2);
// Change the font and draw out more text
ctx.font = '26px bold sans-serif';
ctx.fillStyle = 'white';
ctx.shadowColor = 'rgba(0, 0, 0, 0)';
text = 'David';
textWidth = ctx.measureText(text).width;
ctx.fillText(text, (textCanvas.width-textWidth)/2, textCanvas.height/2 - 60);
text = 'Parker';
textWidth = ctx.measureText(text).width;
ctx.fillText(text, (textCanvas.width-textWidth)/2, textCanvas.height/2 + 60);
// Put the canvas onto the texture object
state.gl.pixelStorei(state.gl.UNPACK_FLIP_Y_WEBGL, 1);
state.gl.bindTexture(state.gl.TEXTURE_2D, texture);
state.gl.texImage2D(state.gl.TEXTURE_2D, 0, state.gl.RGBA, state.gl.RGBA, state.gl.UNSIGNED_BYTE, textCanvas);
state.gl.texParameteri(state.gl.TEXTURE_2D, state.gl.TEXTURE_MAG_FILTER, state.gl.LINEAR);
state.gl.texParameteri(state.gl.TEXTURE_2D, state.gl.TEXTURE_MIN_FILTER, state.gl.LINEAR);
state.gl.texParameteri(state.gl.TEXTURE_2D, state.gl.TEXTURE_WRAP_S, state.gl.CLAMP_TO_EDGE);
state.gl.texParameteri(state.gl.TEXTURE_2D, state.gl.TEXTURE_WRAP_T, state.gl.CLAMP_TO_EDGE);
state.app.textures['canvastext'] = texture;
}
/*
* State Management
* www.programmingtil.com
* www.codenameparkerllc.com
*/
function updateOverlay() {
var msg = "Eye position: ("+state.app.eye.x.toFixed(2)+","+state.app.eye.y.toFixed(2)+","+state.app.eye.z.toFixed(2)+")";
state.ctx.clearRect(0,0,state.ctx.canvas.width,state.ctx.canvas.height);
state.ctx.save();
state.ctx.font = "20px Helvetica";
state.ctx.fillStyle = "white";
state.ctx.fillText(msg, 10, 25);
state.ctx.restore();
}
function updateState() {
if (state.ui.pressedKeys[37]) {
// left
state.app.eye.x += KEYPRESS_SPEED;
} else if (state.ui.pressedKeys[39]) {
// right
state.app.eye.x -= KEYPRESS_SPEED;
} else if (state.ui.pressedKeys[40]) {
// down
state.app.eye.y += KEYPRESS_SPEED;
} else if (state.ui.pressedKeys[38]) {
// up
state.app.eye.y -= KEYPRESS_SPEED;
} else if (state.ui.pressedKeys[90] && !state.ui.pressedKeys[16]) {
// z
state.app.eye.z += KEYPRESS_SPEED;
} else if (state.ui.pressedKeys[90] && state.ui.pressedKeys[16]) {
// Shift+z
state.app.eye.z -= KEYPRESS_SPEED;
}
}
/*
* Rendering / Drawing / Animation
* www.programmingtil.com
* www.codenameparkerllc.com
*/
function animate() {
state.animation.tick = function() {
updateOverlay();
updateState();
draw();
requestAnimationFrame(state.animation.tick);
};
state.animation.tick();
}
function draw() {
state.gl.clear(state.gl.COLOR_BUFFER_BIT | state.gl.DEPTH_BUFFER_BIT);
mat4.perspective(state.pm,
20, state.canvas.width/state.canvas.height, 1, 100
);
mat4.lookAt(state.vm,
vec3.fromValues(state.app.eye.x,state.app.eye.y,state.app.eye.z),
vec3.fromValues(0,0,0),
vec3.fromValues(0,1,0)
);
// Note: First you should sort (transparent) objects based on distance -> furthest away first
// For our purposes, we'll loop through everything twice. Once to draw opaque objects
// and another for transparent objects.
state.gl.enable(state.gl.DEPTH_TEST);
state.gl.disable(state.gl.BLEND);
state.gl.depthMask(true);
state.app.objects.forEach(function(obj) {
if (!obj.state.hasBlend) {
obj.draw();
}
});
// Leave on the depth test!
// state.gl.disable(state.gl.DEPTH_TEST);
state.gl.enable(state.gl.BLEND);
state.gl.depthMask(false);
state.app.objects.forEach(function(obj) {
if (obj.state.hasBlend) {
obj.draw();
}
});
state.gl.depthMask(true);
}
/*
* UI Events
* www.programmingtil.com
* www.codenameparkerllc.com
*/
function keydown(event) {
state.ui.pressedKeys[event.keyCode] = true;
}
function keyup(event) {
state.ui.pressedKeys[event.keyCode] = false;
}
function mousedown(event) {
if (uiUtils.inCanvas(event)) {
state.gl.disable(state.gl.BLEND);
state.gl.enable(state.gl.DEPTH_TEST);
state.gl.depthMask(true);
state.app.objects.forEach(function(obj) {
obj.readState();
draw();
});
var pixels = Array.from(uiUtils.pixelsFromMouseClick(event, state.canvas, state.gl));
var obj2 = uiUtils.pickObject(pixels, state.app.objects, 'selColor');
if (obj2) {
state.app.objSel = obj2;
state.ui.mouse.lastX = event.clientX;
state.ui.mouse.lastY = event.clientY;
state.ui.dragging = true;
}
state.gl.enable(state.gl.BLEND);
state.gl.disable(state.gl.DEPTH_TEST);
state.gl.depthMask(false);
state.app.objects.forEach(function(obj) {
obj.drawState();
draw();
});
}
}
function mouseup(event) {
state.ui.dragging = false;
}
function mousemove(event) {
var x = event.clientX;
var y = event.clientY;
if (state.ui.dragging) {
// The rotation speed factor
// dx and dy here are how for in the x or y direction the mouse moved
var factor = 10/state.canvas.height;
var dx = factor * (x - state.ui.mouse.lastX);
var dy = factor * (y - state.ui.mouse.lastY);
// update the latest angle
state.app.objSel.state.angle[0] = state.app.objSel.state.angle[0] + dy;
state.app.objSel.state.angle[1] = state.app.objSel.state.angle[1] + dx;
}
// update the last mouse position
state.ui.mouse.lastX = x;
state.ui.mouse.lastY = y;
}
})(window || this);
WebGL has no way to give you the info you seek.
WebGL just looks at clipspace triangles in 2D (the values your shader writes to gl_Position). In that 2D space it labels them clockwise or counter-clockwise and then discards triangles that you requested to be culled based on the culling settings.
In order to know how many triangles are drawn (or cullled) you'd need to take the math happening in your shaders related to gl_Position and reproduce that math in JavaScript. Then run your data through that math and generate triangle vertices. For any triangle you compute the area
--pseudo code--
area = 0;
for (let i = 0; i < 3; ++i) {
p0 = threeScreenSpaceTriangleVertices[i];
p1 = threeScreenSpaceTriangleVertices[(i + 1) % 3];
area += p0.x * p1.y - p1.x * p0.y;
}
area *= 0.5; // not really important
If area is positive it's clockwise, if it's negative it's counter clockwise
I guess it's not possible out of the box with WebGL.
Face culling (in your case backface culling) works by comparing the orientation of the face with the looking direction of the camera.
Thus you can easily write some code on CPU/JS side to compute the number of faces displayed when culling is enabled.
To do so, create at render time a loop over all your cubes faces. Get the normal vectors of the face, transform it with the normal matrix (world inverse transposed) and the view matrix and finally normalize it. At this point you have the normal vector transformed in eye space.
Then compute the dot product with the eye looking vector (0,0,-1) (already normalized). The result is the cosine of the angle between normal and camera.
If the sign is negative, the camera is looking at the front of the face and you can increase the draw count. It it's positive, the camera sees the back of the face and you can discard this one.
// In pseudo GLSL code (write it on JS side)
vec3 normalEyeSpace = normalize(viewMatrix * worldInverseTranspose * aNormal)
float dir = dot(normalEyeSpace, vec3(0.0, 0.0, -1.0)
if (dir < 0) drawCount++
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.
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 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>
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