What is the logic of binding buffers in webgl? - javascript

I sometimes find myself struggling between declaring the buffers (with createBuffer/bindBuffer/bufferdata) in different order and rebinding them in other parts of the code, usually in the draw loop.
If I don't rebind the vertex buffer before drawing arrays, the console complains about an attempt to access out of range vertices. My suspect is the the last bound object is passed at the pointer and then to the drawarrays but when I change the order at the beginning of the code, nothing changes. What effectively works is rebinding the buffer in the draw loop. So, I can't really understand the logic behind that. When do you need to rebind? Why do you need to rebind? What is attribute0 referring to?

I don't know if this will help. As some people have said, GL/WebGL has a bunch of internal state. All the functions you call set up the state. When it's all setup you call drawArrays or drawElements and all of that state is used to draw things
This has been explained elsewhere on SO but binding a buffer is just setting 1 of 2 global variables inside WebGL. After that you refer to the buffer by its bind point.
You can think of it like this
gl = function() {
// internal WebGL state
let lastError;
let arrayBuffer = null;
let vertexArray = {
elementArrayBuffer: null,
attributes: [
{ enabled: false, type: gl.FLOAT, size: 3, normalized: false,
stride: 0, offset: 0, buffer: null },
{ enabled: false, type: gl.FLOAT, size: 3, normalized: false,
stride: 0, offset: 0, buffer: null },
{ enabled: false, type: gl.FLOAT, size: 3, normalized: false,
stride: 0, offset: 0, buffer: null },
{ enabled: false, type: gl.FLOAT, size: 3, normalized: false,
stride: 0, offset: 0, buffer: null },
{ enabled: false, type: gl.FLOAT, size: 3, normalized: false,
stride: 0, offset: 0, buffer: null },
...
],
}
// these values are used when a vertex attrib is disabled
let attribValues = [
[0, 0, 0, 1],
[0, 0, 0, 1],
[0, 0, 0, 1],
[0, 0, 0, 1],
[0, 0, 0, 1],
...
];
...
// Implementation of gl.bindBuffer.
// note this function is doing nothing but setting 2 internal variables.
this.bindBuffer = function(bindPoint, buffer) {
switch(bindPoint) {
case gl.ARRAY_BUFFER;
arrayBuffer = buffer;
break;
case gl.ELEMENT_ARRAY_BUFFER;
vertexArray.elementArrayBuffer = buffer;
break;
default:
lastError = gl.INVALID_ENUM;
break;
}
};
...
}();
After that other WebGL functions reference those. For example gl.bufferData might do something like
// implementation of gl.bufferData
// Notice you don't pass in a buffer. You pass in a bindPoint.
// The function gets the buffer one of its internal variable you set by
// previously calling gl.bindBuffer
this.bufferData = function(bindPoint, data, usage) {
// lookup the buffer from the bindPoint
var buffer;
switch (bindPoint) {
case gl.ARRAY_BUFFER;
buffer = arrayBuffer;
break;
case gl.ELEMENT_ARRAY_BUFFER;
buffer = vertexArray.elemenArrayBuffer;
break;
default:
lastError = gl.INVALID_ENUM;
break;
}
// copy data into buffer
buffer.copyData(data); // just making this up
buffer.setUsage(usage); // just making this up
};
Separate from those bindpoints there's number of attributes. The attributes are also global state by default. They define how to pull data out of the buffers to supply to your vertex shader. Calling gl.getAttribLocation(someProgram, "nameOfAttribute") tells you which attribute the vertex shader will look at to get data out of a buffer.
So, there's 4 functions that you use to configure how an attribute will get data from a buffer. gl.enableVertexAttribArray, gl.disableVertexAttribArray, gl.vertexAttribPointer, and gl.vertexAttrib??.
They're effectively implemented something like this
this.enableVertexAttribArray = function(location) {
const attribute = vertexArray.attributes[location];
attribute.enabled = true; // true means get data from attribute.buffer
};
this.disableVertexAttribArray = function(location) {
const attribute = vertexArray.attributes[location];
attribute.enabled = false; // false means get data from attribValues[location]
};
this.vertexAttribPointer = function(location, size, type, normalized, stride, offset) {
const attribute = vertexArray.attributes[location];
attribute.size = size; // num values to pull from buffer per vertex shader iteration
attribute.type = type; // type of values to pull from buffer
attribute.normalized = normalized; // whether or not to normalize
attribute.stride = stride; // number of bytes to advance for each iteration of the vertex shader. 0 = compute from type, size
attribute.offset = offset; // where to start in buffer.
// IMPORTANT!!! Associates whatever buffer is currently *bound* to
// "arrayBuffer" to this attribute
attribute.buffer = arrayBuffer;
};
this.vertexAttrib4f = function(location, x, y, z, w) {
const attrivValue = attribValues[location];
attribValue[0] = x;
attribValue[1] = y;
attribValue[2] = z;
attribValue[3] = w;
};
Now, when you call gl.drawArrays or gl.drawElements the system knows how you want to pull data out of the buffers you made to supply your vertex shader. See here for how that works.
Since the attributes are global state that means every time you call drawElements or drawArrays how ever you have the attributes setup is how they'll be used. If you set up attributes #1 and #2 to buffers that each have 3 vertices but you ask to draw 6 vertices with gl.drawArrays you'll get an error. Similarly if you make an index buffer which you bind to the gl.ELEMENT_ARRAY_BUFFER bindpoint and that buffer has an indice that is > 2 you'll get that index out of range error. If your buffers only have 3 vertices then the only valid indices are 0, 1, and 2.
Normally, every time you draw something different you rebind all the attributes needed to draw that thing. Drawing a cube that has positions and normals? Bind the buffer with position data, setup the attribute being used for positions, bind the buffer with normal data, setup the attribute being used for normals, now draw. Next you draw a sphere with positions, vertex colors and texture coordinates. Bind the buffer that contains position data, setup the attribute being used for positions. Bind the buffer that contains vertex color data, setup the attribute being used for vertex colors. Bind the buffer that contains texture coordinates, setup the attribute being used for texture coordinates.
The only time you don't rebind buffers is if you're drawing the same thing more than once. For example drawing 10 cubes. You'd rebind the buffers, then set the uniforms for one cube, draw it, set the uniforms for the next cube, draw it, repeat.
I should also add that there's an extension [OES_vertex_array_object] which is also a feature of WebGL 2.0. A Vertex Array Object is the global state above called vertexArray which includes the elementArrayBuffer and all the attributes.
Calling gl.createVertexArray makes new one of those. Calling gl.bindVertexArray sets the global attributes to point to the one in the bound vertexArray.
Calling gl.bindVertexArray would then be
this.bindVertexArray = function(vao) {
vertexArray = vao ? vao : defaultVertexArray;
}
This has the advantage of letting you set up all attributes and buffers at init time and then at draw time just 1 WebGL call will set all buffers and attributes.
Here is a webgl state diagram that might help visualize this better.

Related

How to extend an existing straight line in Three.js

I have an existing line initiated
// material
const material = new THREE.LineBasicMaterial({ color: 0xffffff });
// array of vertices
vertices.push(new THREE.Vector3(0, 0, 0));
vertices.push(new THREE.Vector3(0, 0, 5));
//
const geometry = new THREE.BufferGeometry().setFromPoints(vertices);
const line = new THREE.Line(geometry, material);
And what I want to do is extend this line following its initiation. I've read this page on how to update things and I don't think it fits this situation because instead of adding vertices to my shape, I want to move them. Then again, it's very likely I misunderstood. I've tried deleting the line and then redrawing it longer, but I can't get it to work without my browser crashing.
The BufferGeometry exposes its vertices through its positions BufferAttribute. To change the positions, you should do something like the following:
//
// Assuming we want to move your line segment (0, 0, 0)-(0, 0, 5) by
// one unit in the direction of positive x, to (1, 0, 0)-(1, 0, 5).
//
// Get a reference to the "position" buffer attribute
const pos = geometry.getAttribute("position");
// Set the new positions
pos.setXYZ(0, vertices[0].x + 1, vertices[0].y, vertices[0].z);
pos.setXYZ(1, vertices[1].x + 1, vertices[1].y, vertices[1].z);
// Update the vertex buffer in graphics memory
pos.needsUpdate = true;
// Update the bounds to support, e.g., frustum culling
geometry.computeBoundingBox();
geometry.computeBoundingSphere();
Other methods exist, such as modifying the attribute's backing array directly and copying in a new array, but the general process will be the same.

Prevent relative rotation in Matter.js constraint

I'm using Matter.js and I want two rectangles with a constraint to make them act if they where a single rigid object.
I am basically setting stiffness to 1, so the contraint acts like a rigid bar instead of a spring.
Also to prevent the object from rotating, I'm setting the intertia to Infinity.
// a 20x20 square with 0 friction and infinite inertia
let objectA = Bodies.rectangle(0, 0, 20, 20, {
frictionAir: 0,
inertia: 'Infinity'
});
let objectB = Bodies.rectangle(30, 0, 20, 20, {
frictionAir: 0,
inertia: 'Infinity'
});
let constraint = Constraint.create({
bodyA: objectB,
bodyB: objectB,
length: 30,
stiffness: 1);
This indeed creates 2 objects with a fixed distance and they do not rotate (both squares always have the same absolute orientation)
However the objects can rotate between them, the constrain acts as a linear constraint but not as an angular constraint.
This picture shows how the distance between objects is kept, how the absolute orientation of the objects has not changed but how the objects rotate around each other.
How can I get rid of this rotation and have the two objects act if they were a single object?
I use a different approach: building a Body from parts instead of using constraints. The result is a single rigid object. Matter handles the parts still separately, so you can e.g. drop a ball in the cart created with the code below.
let cart = bodyWithParts(200, 150, { isStatic: true, friction: 0.0 });
function bodyWithParts(x, y, options) {
options = options || {}
let w = 4;
options.parts = [];
options.parts.push(Matter.Bodies.rectangle(w, 20, 5, 20));
options.parts.push(Matter.Bodies.rectangle(40 - w, 20, 5, 20));
options.parts.push(Matter.Bodies.rectangle(20, 40 - w, 50, 5))
let body = Matter.Body.create(options)
Matter.Body.setPosition(body, { x: x, y: y });
return body;
}
Building a Body to of parts can be useful, however, the strength of the orientation "constraint" cannot be lowered. The orientation stays fixed in any situation.
Therefore, I've written the following TypeScript function which adds two constraints of zero length to two Body objects. Constraints allows us to set the stiffness parameter.
Note that removing one the constraints allows one of the bodies to rotate around its own position ("midpoint") but it cannot change its position relative the other body.
/**
* Adds constraints to `bodyA` and `bodyB` such that their current
* relative position and orientaion is preseved (depending on `stiffness`).
*
* #param bodyA the first body of the constraint
* #param bodyB the second body of the constraint
* #param stiffness the stiffness of the constraint connecting `bodyA` and `bodyB`
* #param offsetA the constraint offset on `bodyA` in its coordinate system
* #param offsetB the constraint offset on `bodyB` in its coordinate system
*/
function addRigidBodyConstraints(
bodyA: Body, bodyB: Body,
stiffness: number = 0.1,
offsetA: Vector = Vector.create(0, 0),
offsetB: Vector = Vector.create(0, 0)
) {
function makeConstraint(posA: Vector, posB: Vector): Constraint {
return Constraint.create({
bodyA: bodyA, bodyB: bodyB,
pointA: posA, pointB: posB,
// stiffness larger than 0.1 is sometimes unstable
stiffness: stiffness
})
}
// add constraints to world or compound body
World.add(world, [
makeConstraint(Vector.sub(bodyB.position, bodyA.position), offsetB),
makeConstraint(offsetA, Vector.sub(bodyA.position, bodyB.position))
])
}

aframe:How to get pixels from canvas

I want to read and process the pixel data rendered in a-frame.
I tried the code below
var canvas = document.querySelector('canvas'),
params = {
preserveDrawingBuffer: true,
},
gl = canvas.getContext('experimental-webgl', params);
var pixels = new Uint8Array(canvas.width * canvas.height * 4);
gl.readPixels(
0,
0,
canvas.width,
canvas.height,
WebGLRenderingContext.RGBA,
WebGLRenderingContext.UNSIGNED_BYTE,
pixels
);
But the pixels array was left of 0, 0, 0, 0
How can I read the pixel data on the canvas?
I'd appreciate it if you could answer this problem
In the demo you posted, find and put a breakpoint on the line that says:
_gl = _context || _canvas.getContext( 'webgl', attributes )
Take a look at attributes, it's an options object, and among other settings it contains this option:
preserveDrawingBuffer: false
Although your original post shows you manually setting this option to true, your option has not carried through into the demo you posted. If you can make this option take effect, you should be able to read the pixels back.

Getting rid of Chart.js canvas subpixels

I'm working on a really small widget which displays a simple bar chart:
I'm using Chart.js for that specific task.
var canvas = this.$(".chart-canvas")[0];
if (canvas) {
var ctx = canvas.getContext("2d");
ctx.translate(0.5, 0.5);
window.barChart = new Chart(ctx).Bar(barChartData, {
responsive: true,
maintainAspectRatio: false,
showScale: false,
scaleShowGridLines: false,
scaleGridLineWidth: 0,
barValueSpacing: 1,
barDatasetSpacing: 0,
showXAxisLabel: false,
barShowStroke: false,
showTooltips: false,
animation: false
});
As you can see, I've tried
ctx.translate(0.5, 0.5);
but that didn't really help.
Is there any way to get rid of the subpixel rendering?
I've read about Bresenham's line algorithm, but don't know how to implement it there.
Any ideas/suggestions appreciated.
Thank you in advance!
Assuming you have only one color, you can do this by extending the chart and overriding the draw to do a getImageData, "rounding" (if the pixel has a R, G or B value, set it to the color) the pixel colors and a putImageData.
You could do this for multiple colors too but it becomes a tad complicated when there are two colors close by.
However the difference in bar value spacing you are seeing is because of the way Chart.js calculates the x position for the bars - there's a rounding off that happens.
You can extend the chart and override the method that calculates the x position to get rid of the rounding off
Chart.types.Bar.extend({
// Passing in a name registers this chart in the Chart namespace in the same way
name: "BarAlt",
initialize: function (data) {
Chart.types.Bar.prototype.initialize.apply(this, arguments);
// copy paste from library code with only 1 line changed
this.scale.calculateX = function (index) {
var isRotated = (this.xLabelRotation > 0),
innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight),
valueWidth = innerWidth / Math.max((this.valuesCount - ((this.offsetGridLines) ? 0 : 1)), 1),
valueOffset = (valueWidth * index) + this.xScalePaddingLeft;
if (this.offsetGridLines) {
valueOffset += (valueWidth / 2);
}
// the library code rounds this off - we don't
return valueOffset;
}
// render again because the original initialize call does a render
// when animation is off this is the only render that happens
this.render();
}
});
You'd call it like so
var ctx = document.getElementById('canvas').getContext('2d');
var myBarChart = new Chart(ctx).BarAlt(data, {
...
Fiddle - http://jsfiddle.net/gf2c4ue4/
You can see the difference better if you zoom in.
The top one is the extended chart

Change length of cylinder or extrudedHeight of circle

I'm trying to change the length of a cylinder or the extrudedHeight of a circle when it has been added to the primitives and is shown in the cesium widget/viewer. For example this cylinder:
var length = 100;
var cylinderGeometry = new Cesium.CylinderGeometry({
length : length,
topRadius : cylinderradius,
bottomRadius : cylinderradius,
slices: cylinderslices,
vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
});
var cylinder = new Cesium.GeometryInstance({
geometry: cylinderGeometry,
modelMatrix: Cesium.Matrix4.multiplyByTranslation(
Cesium.Transforms.eastNorthUpToFixedFrame(ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(lon, lat))),
new Cesium.Cartesian3(0.0, 0.0, length * 0.5)),
attributes: {
color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.RED)
},
id: "Cylinder1"
});
var primitive = new Cesium.Primitive({
geometryInstances : cylinder ,
appearance : new Cesium.PerInstanceColorAppearance({
closed : false,
translucent: true,
flat: false,
faceForward: true
}),
allow3DOnly: true,
vertexCacheOptimize: true,
allowPicking: true,
releaseGeometryInstances: false
});
widget.scene.primitives.add(primitive);
Because it's added to the primitives array it will be shown in the widget, but after 2 seconds for example I get a notification that the length should be halved (that means set to 50). Is there any way to do this? Simply changing it in cylinderGeometry doesn't seem to do the job.
I kind of have it working by creating a new cylinder with the new height, adding it and removing the old one. This however tends to flicker the cylinder (it's gone for a fraction of a second) before the new one is shown. I fixed this problem by removing the old instance after a set time after the new one is added. This whole solution isn't very elegant and doesn't work very well on devices with a small amount of computing power, hence my search for a better solution.
I don't care if this is achieved using cylinders or extruded circles. If you need any more information don't hesitate to ask in the comments below the question.
EDIT
I implemented the second solution Matthew suggested but after a while of it running perfectly the cylinders stop changing height (which didn't occur when I used my solution. The callback in the interval does get called. Here is some code showing what my new solution is (not working):
primitives.add(prim);
window.nodeValuesInterval = setInterval(function () {
if (prim._state == Cesium.PrimitiveState.COMPLETE) {
clearInterval(window.nodeValuesInterval);
clearTimeout(window.nodeValuesTimeout);
primitives.remove(primitiveObjects.value);
primitiveObjects.value = prim;
}
}, cylindervalueinterval);
window.nodeValuesTimeout = setTimeout(function () {
clearInterval(window.nodeValuesInterval);
primitives.remove(primitiveObjects.value);
primitiveObjects.value = prim;
}, cylindervaluedelay);
Cesium's geometry is currently optimized for static data. Some attributes, such as visibility, color, and material can be changed on the fly, but items that actually modify the geometry (like cylinder height) require you to remove the primitive and recompute the geometry. The flickering your seeing is the result of asynchronous primitive creation being on by default. There are two ways to do want you want.
Disable asynchronous primitive create by passing [options.asynchronous: false to the Primitive constructor. This means that when you add a new primitive, Cesium will not render until it is ready. For one or two objects, you won't notice anything. For lots of objects it will lock up the browser until everything is ready. This does guarantee that you can remove old/add new primitives without any flicker.
The second option is to add your new primitive (without removing the old one) and then every frame, check the _state property of your new Primitive (I thought this was part of the public API but apparently it's not). When the _state is equal to Cesium.PrimitiveState.COMPLETE you can safely remove the old primitive and your guaranteed the new one will render (hence no flicker).
I think we have a bug/feature request to expose the state variable publicly or otherwise notify when the Primitive is ready; but using _state should be fine for the forseeable future. I'll update this issue if we add an official way sometime soon.
Hope that helps.
EDIT: Since more help was requested; here's a complete example. You can copy and paste the below code into Sandcastle using this link.
Basically it uses the scene.preRender event instead of a timeout (preRender is almost always the better answer here). Also, if you receive a new update before the old one is finished processing, it's important to remove that one before computing the new one. Let me know if you are still having problems.
require(['Cesium'], function(Cesium) {
"use strict";
var widget = new Cesium.CesiumWidget('cesiumContainer');
var ellipsoid = widget.scene.globe.ellipsoid;
var lon = 0;
var lat = 0;
var cylinderradius = 30000;
var length = 10000000;
var cylinderslices = 32;
var newPrimitive;
var currentPrimitive;
//This function creates a new cylinder that is half the length of the old one.
function decreaseLength() {
//If there's a pending primitive already, remove it.
if(Cesium.defined(newPrimitive)){
widget.scene.primitives.remove(newPrimitive);
}
length /= 2;
var cylinderGeometry = new Cesium.CylinderGeometry({
length : length,
topRadius : cylinderradius,
bottomRadius : cylinderradius,
slices: cylinderslices,
vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
});
var cylinder = new Cesium.GeometryInstance({
geometry: cylinderGeometry,
modelMatrix: Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame(ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(lon, lat))),
new Cesium.Cartesian3(0.0, 0.0, length * 0.5)),
attributes: {
color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.RED)
},
id: "Cylinder1"
});
newPrimitive = new Cesium.Primitive({
geometryInstances : cylinder ,
appearance : new Cesium.PerInstanceColorAppearance({
closed : false,
translucent: true,
flat: false,
faceForward: true
}),
allow3DOnly: true,
vertexCacheOptimize: true,
allowPicking: true,
releaseGeometryInstances: false
});
//We add the new cylinder immediately, but don't remove the old one yet.
widget.scene.primitives.add(newPrimitive);
}
//Create the initial cylinder.
decreaseLength();
//Subscribe to the preRender event so we can check the primitive every frame.
widget.scene.preRender.addEventListener(function(scene, time) {
//Remove the old cylinder once the new one is ready.
if(Cesium.defined(newPrimitive) && newPrimitive._state === Cesium.PrimitiveState.COMPLETE){
if(Cesium.defined(currentPrimitive)){
widget.scene.primitives.remove(currentPrimitive);
}
currentPrimitive = newPrimitive;
newPrimitive = undefined;
}
});
Sandcastle.addToolbarButton('Decrease Length', decreaseLength);
Sandcastle.finishedLoading();
});

Categories

Resources