Render to texture mipmap level - webgl2

I am trying to understand the correct approach to render to a specific texture mipmap level.
In the example below, I attempt to render the color cyan to mipmap level 1 of texture. If I change the level from 1 to 0 in the framebufferTexture2D call, the canvas displays cyan as expected. However I don't understand why only level 0 works here, because non-zero levels are supported in the WebGL 2/OpenGL ES 3 specification.
I've also tried explicitly detaching level 0 (binding to null) and various other combinations (i.e. using texImage2D instead of texStorage2D), but none of the combinations seem to render to the mipmap level.
const
canvas = document.createElement('canvas'),
gl = canvas.getContext('webgl2'),
triangle = new Float32Array([ 0, 0, 2, 0, 0, 2 ]);
texture = gl.createTexture(),
framebuffer = gl.createFramebuffer(),
size = 100,
vertex = createShader(gl.VERTEX_SHADER, `#version 300 es
precision mediump float;
uniform sampler2D sampler;
layout(location = 0) in vec2 position;
out vec4 color;
void main() {
color = textureLod(sampler, position, 0.5);
gl_Position = vec4(position * 2. - 1., 0, 1);
}`
),
fragment = createShader(gl.FRAGMENT_SHADER, `#version 300 es
precision mediump float;
in vec4 color;
out vec4 fragColor;
void main() {
fragColor = color;
}`
),
program = gl.createProgram();
canvas.width = canvas.height = size;
document.body.appendChild(canvas);
gl.viewport(0, 0, size, size);
gl.attachShader(program, vertex);
gl.attachShader(program, fragment);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error('program');
}
gl.useProgram(program);
// Create a big triangle
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, triangle, gl.STATIC_DRAW);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(0);
// Create a texture with mipmap levels 0 (base) and 1
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texStorage2D(gl.TEXTURE_2D, 2, gl.RGB8, 2, 2);
// Setup framebuffer to render to texture level 1, clear to cyan
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
texture,
1 // Switching this to `0` will work fine
);
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (status !== gl.FRAMEBUFFER_COMPLETE) {
console.error(status);
}
gl.clearColor(0, 1, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// Detach framebuffer, clear to red
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.clearColor(1, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// Draw the triangle
gl.drawArrays(gl.TRIANGLES, 0, 3);
// Some utility functions to cleanup the above code
function createShader(type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.log(gl.getShaderInfoLog(shader));
}
return shader;
}
I expect that I'm doing something wrong in the setup, but I haven't been able to find many examples of this.

Don't you want either
color = textureLod(sampler, position, 0.0); // lod 0
or
color = textureLod(sampler, position, 1.0); // lod 1
?
The code didn't set filtering in a way that you can actually access the other lods.
It had them set to gl.NEAREST which means only ever use lod 0.
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl2');
const triangle = new Float32Array([0, -1, 1, -1, 1, 1]);
const texture = gl.createTexture();
const framebuffers = [];
canvas.width = canvas.height = 100;
document.body.appendChild(canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
const vertex = createShader(gl.VERTEX_SHADER, `#version 300 es
precision mediump float;
uniform sampler2D sampler;
uniform float lod;
uniform vec4 offset;
layout(location = 0) in vec4 position;
out vec4 color;
void main() {
color = textureLod(sampler, vec2(.5), lod);
gl_Position = position + offset;
}`
);
const fragment = createShader(gl.FRAGMENT_SHADER, `#version 300 es
precision mediump float;
in vec4 color;
out vec4 fragColor;
void main() {
fragColor = color;
}`
);
const program = createProgram(vertex, fragment);
const lodLocation = gl.getUniformLocation(program, "lod");
const offsetLocation = gl.getUniformLocation(program, "offset");
// Create a big triangle
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, triangle, gl.STATIC_DRAW);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(0);
// Create a texture with mipmap levels 0 (base) and 1
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_NEAREST);
gl.texStorage2D(gl.TEXTURE_2D, 2, gl.RGB8, 2, 2);
// Setup framebuffers for each level
for (let i = 0; i < 2; ++i) {
const framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
texture,
i);
let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (status !== gl.FRAMEBUFFER_COMPLETE) {
console.error(glErrToString(gl, status));
}
const r = (i === 0) ? 1 : 0;
const g = (i === 1) ? 1 : 0;
gl.clearColor(r, g, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
framebuffers.push(framebuffer);
};
// Detach framebuffer, clear to red
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// Draw the triangle
gl.uniform1f(lodLocation, 0);
gl.uniform4fv(offsetLocation, [0, 0, 0, 0]);
gl.drawArrays(gl.TRIANGLES, 0, 3);
gl.uniform1f(lodLocation, 1.);
gl.uniform4fv(offsetLocation, [-1, 0, 0, 0]);
gl.drawArrays(gl.TRIANGLES, 0, 3);
// Some utility functions to cleanup the above code
function createShader(shaderType, source) {
const shader = gl.createShader(shaderType);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.log(gl.getShaderInfoLog(shader));
}
return shader;
}
function createProgram(vertex, fragment) {
const program = gl.createProgram();
gl.attachShader(program, vertex);
gl.attachShader(program, fragment);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error('program');
}
gl.useProgram(program);
return program;
}
function glErrToString(gl, error) {
for (var key in gl) {
if (gl[key] === error) {
return key;
}
}
return "0x" + error.toString(16);
}

Related

p5.js how to correctly compute the 3D rotation of a point in respect of the origin

I'm really struggling here and I can't get it right, not even knowing why.
I'm using p5.js in WEBGL mode, I want to compute the position of on point rotated on the 3 axes around the origin in order to follow the translation and the rotation given to object through p5.js, translation and rotatation on X axis, Y axis and Z axis.
The fact is that drawing a sphere in 3d space, withing p5.js, is obtained by translating and rotating, since the sphere is created at the center in the origin, and there is no internal model giving the 3d-coordinates.
After hours of wandering through some math too high for my knowledge, I understood that the rotation over 3-axis is not as simple as I thought, and I ended up using Quaternion.js. But I'm still not able to match the visual position of the sphere in the 3d world with the coordinates I have computed out of the original point on the 2d-plane (150, 0, [0]).
For example, here the sphere is rotated on 3 axis. At the beginning the coordinates are good (if I ignore the fact that Z is negated) but at certain point it gets completely out of sync. The computed position of the sphere seems to be completely unrelated:
It's really hours that I'm trying to solve this issue, with no result, what did I miss?
Here it follows my code:
//font for WEBGL
var robotoFont;
var dotId = 0;
var rotating = true;
var orbits = [];
var dotsData = [];
function preload() {
robotoFont = loadFont('./assets/Roboto-Regular.ttf');
}
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
textFont(robotoFont);
background(0);
let orbit1 = new Orbit(0, 0, 0, 0.5, 0.5, 0.5);
orbit1.obj.push(new Dot(0, 0));
orbits.push(orbit1);
// let orbit2 = new Orbit(90, 45, 0);
// orbit2.obj.push(new Dot(0, 0));
// orbits.push(orbit2);
}
function draw() {
angleMode(DEGREES);
background(0);
orbitControl();
let len = 200;
fill('white');
stroke('white');
sphere(2);
stroke('red');
line(0, 0, 0, len, 0, 0);
text('x', len, 0)
stroke('green');
line(0, 0, 0, 0, len, 0);
text('y', 0, len)
push();
rotateX(90);
stroke('yellow');
line(0, 0, 0, 0, len, 0);
text('z', 0, len)
pop();
dotsData = [];
orbits.forEach(o => o.draw());
textSize(14);
push();
for (let i = 0; i < 2; i++) {
let yPos = -(windowHeight / 2) + 15;
for (let i = 0; i < dotsData.length; i++) {
let [id, pos, pos3d] = dotsData[i];
let [x1, y1, z1] = [pos[0].toFixed(0), pos[1].toFixed(0), pos[2].toFixed(0)];
let [x2, y2, z2] = [pos3d.x.toFixed(0), pos3d.y.toFixed(0), pos3d.z.toFixed(0)];
text(`${id}: (${x1}, ${y1}, ${z1}) -> (${x2}, ${y2}, ${z2})`, -windowWidth / 2 + 5, yPos);
yPos += 18;
}
rotateX(-90);
}
pop();
}
function mouseClicked() {
// controls.mousePressed();
}
function keyPressed() {
// controls.keyPressed(keyCode);
if (keyCode === 32) {
rotating = !rotating;
}
}
class Orbit {
constructor(x, y, z, xr, yr, zr) {
this.obj = [];
this.currentRot = [
x ? x : 0,
y ? y : 0,
z ? z : 0
]
this.rot = [
xr ? xr : 0,
yr ? yr : 0,
zr ? zr : 0
]
}
draw() {
push();
if (rotating) {
this.currentRot[0] += this.rot[0];
this.currentRot[1] += this.rot[1];
this.currentRot[2] += this.rot[2];
}
rotateY(this.currentRot[1]);
rotateX(this.currentRot[0]);
rotateZ(this.currentRot[2]);
noFill();
stroke('white');
ellipse(0, 0, 300, 300);
for (let i = 0; i < this.obj.length; i++) {
let o = this.obj[i];
o.draw();
dotsData.push([o.id, o.getPosition(), this.#get3DPos(o)]);
}
pop();
}
#get3DPos(o) {
let [x, y, z] = o.getPosition();
let w = 0;
let rotX = this.currentRot[0] * PI / 180;
let rotY = this.currentRot[1] * PI / 180;
let rotZ = this.currentRot[2] * PI / 180;
let rotation = Quaternion.fromEuler(rotZ, rotX, rotY, 'ZXY').conjugate();
[x, y, z] = rotation.rotateVector([x, y, z]);
return createVector(x, y, z);
}
}
class Dot {
constructor(angle) {
this.id = ++dotId;
this.x = cos(angle) * 150;
this.y = sin(angle) * 150;
}
draw() {
push();
fill('gray');
translate(this.x, this.y);
noStroke();
sphere(15);
pop();
}
getPosition() {
return [this.x, this.y, 0];
}
}
It doesn't work in stackoverflow because I need local asset like the font.
Here the working code: https://editor.p5js.org/cigno5/sketches/_ZVq0kjJL
I've finally sorted out. I can't really understand why works this way but I didn't need quaternion at all, and my first intuition of using matrix multiplications to apply rotation on 3-axis was correct.
What I did miss in first instance (and made my life miserable) is that matrix multiplication is not commutative. This means that applying rotation on x, y and z-axis is not equivalent to apply same rotation angle on z, y and x.
The working solution has been achieved with 3 simple steps:
Replace quaternion with matrix multiplications using vectors (method #resize2)
Rotating the drawing plane with Z-Y-X order
Doing the math of rotation in X-Y-Z order
//font for WEBGL
var robotoFont;
var dotId = 0;
var rotating = true;
var orbits = [];
var dotsData = [];
function preload() {
robotoFont = loadFont('./assets/Roboto-Regular.ttf');
}
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
textFont(robotoFont);
background(0);
let orbit1 = new Orbit(0, 0, 0, 0.5, 0.5, 0.5);
orbit1.obj.push(new Dot(0, 0.5));
orbits.push(orbit1);
// let orbit2 = new Orbit(90, 45, 0);
// orbit2.obj.push(new Dot(0, 0));
// orbits.push(orbit2);
}
function draw() {
angleMode(DEGREES);
background(0);
orbitControl();
let len = 200;
fill('white');
stroke('white');
sphere(2);
stroke('red');
line(0, 0, 0, len, 0, 0);
text('x', len, 0)
stroke('green');
line(0, 0, 0, 0, len, 0);
text('y', 0, len)
push();
rotateX(90);
stroke('yellow');
line(0, 0, 0, 0, len, 0);
text('z', 0, len)
pop();
dotsData = [];
orbits.forEach(o => o.draw());
textSize(14);
push();
for (let i = 0; i < 2; i++) {
let yPos = -(windowHeight / 2) + 15;
for (let i = 0; i < dotsData.length; i++) {
let [id, pos, pos3d] = dotsData[i];
let [x1, y1, z1] = [pos[0].toFixed(0), pos[1].toFixed(0), pos[2].toFixed(0)];
let [x2, y2, z2] = [pos3d.x.toFixed(0), pos3d.y.toFixed(0), pos3d.z.toFixed(0)];
text(`${id}: (${x1}, ${y1}, ${z1}) -> (${x2}, ${y2}, ${z2})`, -windowWidth / 2 + 5, yPos);
yPos += 18;
}
rotateX(-90);
}
pop();
}
function mouseClicked() {
// controls.mousePressed();
}
function keyPressed() {
// controls.keyPressed(keyCode);
if (keyCode === 32) {
rotating = !rotating;
}
}
class Orbit {
constructor(x, y, z, xr, yr, zr) {
this.obj = [];
this.currentRot = [
x ? x : 0,
y ? y : 0,
z ? z : 0
]
this.rot = [
xr ? xr : 0,
yr ? yr : 0,
zr ? zr : 0
]
}
draw() {
push();
if (rotating) {
this.currentRot[0] += this.rot[0];
this.currentRot[1] += this.rot[1];
this.currentRot[2] += this.rot[2];
}
rotateZ(this.currentRot[2]);
rotateY(this.currentRot[1]);
rotateX(this.currentRot[0]);
noFill();
stroke('white');
ellipse(0, 0, 300, 300);
for (let i = 0; i < this.obj.length; i++) {
let o = this.obj[i];
o.draw();
dotsData.push([o.id, o.getPosition(), this.#get3DPos(o)]);
}
pop();
}
#get3DPos(o) {
let [x, y, z] = o.getPosition();
let pos = createVector(x, y, z);
pos = this.#rotate2(pos, createVector(1, 0, 0), this.currentRot[0]);
pos = this.#rotate2(pos, createVector(0, 1, 0), this.currentRot[1]);
pos = this.#rotate2(pos, createVector(0, 0, 1), this.currentRot[2]);
return pos;
}
//https://stackoverflow.com/questions/67458592/how-would-i-rotate-a-vector-in-3d-space-p5-js
#rotate2(vect, axis, angle) {
// Make sure our axis is a unit vector
axis = p5.Vector.normalize(axis);
return p5.Vector.add(
p5.Vector.mult(vect, cos(angle)),
p5.Vector.add(
p5.Vector.mult(
p5.Vector.cross(axis, vect),
sin(angle)
),
p5.Vector.mult(
p5.Vector.mult(
axis,
p5.Vector.dot(axis, vect)
),
(1 - cos(angle))
)
)
);
}
}
class Dot {
constructor(angle, speed) {
this.id = ++dotId;
this.angle = angle;
this.speed = speed
}
draw() {
this.angle += this.speed;
this.x = cos(this.angle) * 150;
this.y = sin(this.angle) * 150;
push();
fill('gray');
translate(this.x, this.y);
noStroke();
sphere(15);
pop();
}
getPosition() {
return [this.x, this.y, 0];
}
}
And now it works like a charm:
https://editor.p5js.org/cigno5/sketches/PqB9CEnBp

I just know how to use for to draw the tree, but now I want to use recursion to draw the tree

I just know how to use for to draw a tree (the tree data is the picture one, the result is picture two), but now I want to use recursion to draw the tree.
Please tell me how change writing style from for to recursive
first input point
//input point
const line_point =[0, 0, 0,
2, 151, 2,
2, 151, 2,
-62, 283, 63,
2, 151, 2,
62, 297, -58,
-62, 283, 63,
-104, 334, 74,
-62, 283, 63,
-58, 338, 45,
62, 297, -58,
67, 403, -55,
62, 297, -58,
105, 365, -86];
take out star point and end point
const star_line_x= new Array();
const star_line_y= new Array();
const star_line_z= new Array();
const end_line_x= new Array();
const end_line_y= new Array();
const end_line_z= new Array();
for (var q=0; q < line_point.length; q+=6){
star_line_x.push(line_point[q]);
}
for (var r=1; r < line_point.length; r+=6){
star_line_y.push(line_point[r]);
}
for (var s=2; s < line_point.length; s+=6){
star_line_z.push(line_point[s]);
}
for (var t=3; t < line_point.length; t+=6){
end_line_x.push(line_point[t]);
}
for (var u=4; u < line_point.length; u+=6){
end_line_y.push(line_point[u]);
}
for (var v=5; v < line_point.length; v+=6){
end_line_z.push(line_point[v]);
}
var cylinder_star_point = new Array();
var cylinder_end_point = new Array();
//star_point end_point
for (var w=0; w < line_point.length/6; w++){
var star_point = new THREE.Vector3 (star_line_x[w],star_line_y[w],star_line_z[w]);
var end_point = new THREE.Vector3 (end_line_x[w],end_line_y[w],end_line_z[w]);
cylinder_star_point.push( star_point);
cylinder_end_point.push( end_point);
}
calculation cylinder high
//calculation cylinder high
var line_len = new Array();
for (var dd=0; dd < line_point.length/6; dd++){
var len_x = Math.pow(end_line_x[dd]-star_line_x[dd],2);
var len_y = Math.pow(end_line_y[dd]-star_line_y[dd],2);
var len_z = Math.pow(end_line_z[dd]-star_line_z[dd],2);
var len_direction = Math.sqrt(len_x+len_y+len_z);
line_len.push(len_direction);//Cylinder high
}
calculation center point
//center_point
const cylinder_center_point= new Array();
for (var bb=0; bb< cylinder_end_point.length; bb++){
var star_set_point = cylinder_star_point[bb];
var end_set_point = cylinder_end_point[bb];
var center_point = end_set_point.clone().add(star_set_point).divideScalar(2);
cylinder_center_point.push(center_point);
}
calculation cylinder direction vector
//cylinder direction
const cylinder_direction= new Array();
for (var cc=0; cc < cylinder_end_point.length; cc++){
var star_direction = cylinder_star_point[cc];
var end_direction = cylinder_end_point[cc];
var center_direction = end_direction.clone().sub(star_direction);
cylinder_direction.push(center_direction);
}
draw cylinder
for (var dd=0; dd <cylinder_direction.length;dd++){
var material = new THREE.MeshPhongMaterial({color:'#ff0000'});
let upVector = new THREE.Vector3(0, 1, 0);
var geometry = new THREE.CylinderGeometry(5, 5, line_len[dd], 20, 1, false);
var mesh = new THREE.Mesh(geometry, material);
mesh.position.set(0, line_len[dd]/2, 0);
var group = new THREE.Group();
group.position.set(star_line_x[dd],star_line_y[dd],star_line_z[dd]);
group.add(mesh);
let targetVector =cylinder_direction[dd];
let quaternion = new THREE.Quaternion().setFromUnitVectors(upVector, targetVector.normalize());
group.setRotationFromQuaternion(quaternion)
scene.add(group)
}
picture two: use for to draw the tree
For a tree the simplest method is to start with just a tree depth and assume 2 children. The function makes one branch and if depth > 0 then it recursively calls itself to make 2 more branches.
const numBranches = 2;
const spread = 1.5;
const branchShrinkFactor = 0.8;
const branchSpreadFactor = 0.8;
function addBranch(parent, depth, offset, angle, branchLength, spread) {
const material = new THREE.MeshPhongMaterial({color:'#ff0000'});
const geometry = new THREE.CylinderBufferGeometry(5, 5, branchLength, 20, 1, false);
geometry.translate(0, branchLength / 2, 0);
const mesh = new THREE.Mesh(geometry, material);
mesh.position.y = offset;
mesh.rotation.z = angle;
parent.add(mesh);
if (depth > 1) {
for (let i = 0; i < numBranches; ++i) {
const a = i / (numBranches - 1) - 0.5;
addBranch(mesh, depth - 1, branchLength, a * spread, branchLength * branchShrinkFactor, spread * branchSpreadFactor)
}
}
}
addBranch(scene, 5, 0, 0, 100, 1.5);
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="c"></canvas>
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/build/three.module.js';
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 75;
const aspect = 2; // the canvas default
const near = 1;
const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(0, 150, 300);
const scene = new THREE.Scene();
scene.background = new THREE.Color('lightskyblue');
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
const numBranches = 2;
const spread = 1.5;
const branchShrinkFactor = 0.8;
const branchSpreadFactor = 0.8;
function addBranch(parent, depth, offset, angle, branchLength, spread) {
const material = new THREE.MeshPhongMaterial({color:'#ff0000'});
const geometry = new THREE.CylinderBufferGeometry(5, 5, branchLength, 20, 1, false);
geometry.translate(0, branchLength / 2, 0);
const mesh = new THREE.Mesh(geometry, material);
mesh.position.y = offset;
mesh.rotation.z = angle;
parent.add(mesh);
if (depth > 1) {
for (let i = 0; i < numBranches; ++i) {
const a = i / (numBranches - 1) - 0.5;
addBranch(mesh, depth - 1, branchLength, a * spread, branchLength * branchShrinkFactor, spread * branchSpreadFactor)
}
}
}
addBranch(scene, 5, 0, 0, 100, 1.5);
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
// requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>
If you want specific data for each branch then you need to pass that in. For example
const tree = [
{ length: 100, angle: 0, branches: 2 }, // root
{ length: 40, angle: -1, branches: 3 }, // first branch
{ length: 50, angle: 0.8, branches: 0 }, // 1st child branch
{ length: 40, angle: 0.3, branches: 0 }, // 2nd child branch
{ length: 30, angle: -0.3, branches: 0 }, // 3rd child branch
{ length: 50, angle: 0.8, branches: 2 }, // second branch
{ length: 50, angle: 0.5, branches: 0 }, // 1st child branch
{ length: 40, angle: -0.6, branches: 2 }, // 2nd child branch
{ length: 40, angle: -0.3, branches: 0 }, // 1st grandchild branch
{ length: 95, angle: 0.3, branches: 0 }, // 2st grandchild branch
];
and then walk the tree description, if a branches for a particular branch is > 0 then it recursively calls itself to add those branches. Each branches consumes a row in the array of branches so we pass back ndx so we can tell how many rows were consumed.
function addBranch(parent, offset, tree, ndx = 0) {
const {length, angle, branches} = tree[ndx];
const material = new THREE.MeshPhongMaterial({color:'#ff0000'});
const geometry = new THREE.CylinderGeometry(5, 5, length, 20, 1, false);
geometry.translate(0, length / 2, 0);
const mesh = new THREE.Mesh(geometry, material);
mesh.position.y = offset;
mesh.rotation.z = angle;
parent.add(mesh);
for (let i = 0; i < branches; ++i) {
ndx = addBranch(mesh, length, tree, ++ndx);
}
return ndx;
}
addBranch(scene, 0, tree);
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="c"></canvas>
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/build/three.module.js';
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 75;
const aspect = 2; // the canvas default
const near = 1;
const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(0, 150, 300);
const scene = new THREE.Scene();
scene.background = new THREE.Color('lightskyblue');
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
const tree = [
{ length: 100, angle: 0, branches: 2 }, // root
{ length: 40, angle: -1, branches: 3 }, // first branch
{ length: 50, angle: 0.8, branches: 0 }, // 1st child branch
{ length: 40, angle: 0.3, branches: 0 }, // 2nd child branch
{ length: 30, angle: -0.3, branches: 0 }, // 3rd child branch
{ length: 50, angle: 0.8, branches: 2 }, // second branch
{ length: 50, angle: 0.5, branches: 0 }, // 1st child branch
{ length: 40, angle: -0.6, branches: 2 }, // 2nd child branch
{ length: 40, angle: -0.3, branches: 0 }, // 1st grandchild branch
{ length: 95, angle: 0.3, branches: 0 }, // 2st grandchild branch
];
function addBranch(parent, offset, tree, ndx = 0) {
const {length, angle, branches} = tree[ndx];
const material = new THREE.MeshPhongMaterial({color:'#ff0000'});
const geometry = new THREE.CylinderGeometry(5, 5, length, 20, 1, false);
geometry.translate(0, length / 2, 0);
const mesh = new THREE.Mesh(geometry, material);
mesh.position.y = offset;
mesh.rotation.z = angle;
parent.add(mesh);
for (let i = 0; i < branches; ++i) {
ndx = addBranch(mesh, length, tree, ++ndx);
}
return ndx;
}
addBranch(scene, 0, tree);
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
// requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>
It's not clear to me what your input data is. Your tree has a depth of 3 and 2 branches per level so this data would work
const endPoints = [
[ 0, 0, 0], // A
[ 2, 151, 2], // B
[ -62, 283, 63], // C
[-104, 334, 74], // E
[ -58, 338, 45], // F
[ 62, 296, -58], // D
[ 67, 403, -55], // G
[ 105, 365, -86], // H
];
using this code
// assumes there are 2 branches per
function addBranch(parent, depth, offset, tree, parentNdx = 0, childNdx = 1) {
const start = tree[parentNdx];
const end = tree[childNdx];
const length = start.distanceTo(end);
const material = new THREE.MeshPhongMaterial({color:'#ff0000'});
const geometry = new THREE.CylinderGeometry(5, 5, length, 20, 1, false);
geometry.translate(0, length / 2, 0);
geometry.rotateX(Math.PI / 2);
const mesh = new THREE.Mesh(geometry, material);
mesh.position.z = offset;
parent.add(mesh);
mesh.lookAt(end);
let ndx = childNdx + 1;
if (depth > 1) {
const numBranches = 2;
for (let i = 0; i < numBranches; ++i) {
ndx = addBranch(mesh, depth - 1, length, tree, childNdx, ndx);
}
}
return ndx;
}
addBranch(scene, 3, 0, tree);
I pointed the cylinders in the positive Z direction which means I can use lookAt to point the cylinder from its start to its end point.
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="c"></canvas>
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/build/three.module.js';
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 75;
const aspect = 2; // the canvas default
const near = 1;
const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(250, 170, 250);
camera.lookAt(0, 170, 0);
const scene = new THREE.Scene();
scene.background = new THREE.Color('lightskyblue');
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
const tree = [
[ 0, 0, 0], // A
[ 2, 151, 2], // B
[ -62, 283, 63], // C
[-104, 334, 74], // E
[ -58, 338, 45], // F
[ 62, 296, -58], // D
[ 67, 403, -55], // G
[ 105, 365, -86], // H
].map(v => new THREE.Vector3().fromArray(v));
// assumes there are 2 branches per
function addBranch(parent, depth, offset, tree, parentNdx = 0, childNdx = 1) {
const start = tree[parentNdx];
const end = tree[childNdx];
const length = start.distanceTo(end);
const material = new THREE.MeshPhongMaterial({color:'#ff0000'});
const geometry = new THREE.CylinderGeometry(5, 5, length, 20, 1, false);
geometry.translate(0, length / 2, 0);
geometry.rotateX(Math.PI / 2);
const mesh = new THREE.Mesh(geometry, material);
mesh.position.z = offset;
parent.add(mesh);
mesh.lookAt(end);
let ndx = childNdx + 1;
if (depth > 1) {
const numBranches = 2;
for (let i = 0; i < numBranches; ++i) {
ndx = addBranch(mesh, depth - 1, length, tree, childNdx, ndx);
}
}
return ndx;
}
addBranch(scene, 3, 0, tree);
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
// requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>
note: this only one of infinite ways to create the tree recursively. Rather than an array in depth first order you could also create a tree structure to pass into the algorithm
const E = {
pos: [-104, 334, 74],
};
const F = {
pos: [ -58, 338, 45],
};
const C = {
pos: [ -62, 283, 63],
children: [E, F],
};
const G = {
pos: [ 67, 403, -55],
};
const H = {
pos: [ 105, 365, -86],
};
const D = {
pos: [ 62, 296, -58],
children: [G, H],
};
const B = {
pos: [ 2, 151, 2],
children: [C, D],
};
const A = {
pos: [0, 0, 0],
children: [B],
};
function addBranch(parent, branch, offset = 0) {
const {pos, children} = branch;
const start = new THREE.Vector3().fromArray(pos);
for (const child of children) {
const end = new THREE.Vector3().fromArray(child.pos);
const length = start.distanceTo(end);
const geometry = new THREE.CylinderGeometry(5, 5, length, 20, 1, false);
geometry.translate(0, length / 2, 0);
geometry.rotateX(Math.PI / 2);
const material = new THREE.MeshPhongMaterial({color: 'red'});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.z = offset;
parent.add(mesh);
mesh.lookAt(end);
if (child.children) {
addBranch(mesh, child, length);
}
}
}
addBranch(scene, A);
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="c"></canvas>
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/build/three.module.js';
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 75;
const aspect = 2; // the canvas default
const near = 1;
const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(250, 170, 250);
camera.lookAt(0, 170, 0);
const scene = new THREE.Scene();
scene.background = new THREE.Color('lightskyblue');
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
const E = {
pos: [-104, 334, 74],
};
const F = {
pos: [ -58, 338, 45],
};
const C = {
pos: [ -62, 283, 63],
children: [E, F],
};
const G = {
pos: [ 67, 403, -55],
};
const H = {
pos: [ 105, 365, -86],
};
const D = {
pos: [ 62, 296, -58],
children: [G, H],
};
const B = {
pos: [ 2, 151, 2],
children: [C, D],
};
const A = {
pos: [0, 0, 0],
children: [B],
};
function addBranch(parent, branch, offset = 0) {
const {pos, children} = branch;
const start = new THREE.Vector3().fromArray(pos);
for (const child of children) {
const end = new THREE.Vector3().fromArray(child.pos);
const length = start.distanceTo(end);
const geometry = new THREE.CylinderGeometry(5, 5, length, 20, 1, false);
geometry.translate(0, length / 2, 0);
geometry.rotateX(Math.PI / 2);
const material = new THREE.MeshPhongMaterial({color: 'red'});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.z = offset;
parent.add(mesh);
mesh.lookAt(end);
if (child.children) {
addBranch(mesh, child, length);
}
}
}
addBranch(scene, A);
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
// requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>

Rendering unsigned 8-bit textures specificities

I have been trying to use integer-based textures (see this question for context), but I can't manage to make the transition from float-based textures gl.RGBA/gl.RGBA to gl.RGBA8UI/gl.RGBA_INTEGER.
I've replaced mentions of sampler2D to usampler2D, vec4 to uvec4 (for fragColor), rewritten the texture formats, but nothing is drawn. I couldn't also use glClear either, showing with the error: glClear: can't be called on integer buffers. Is there any specificities to have in mind when using integer-based textures?
Edit: It seems that it is working on Google Chrome, not on Firefox?
const baseImage = new Image();
baseImage.src = 'https://i.imgur.com/O6aW2Tg.png';
baseImage.crossOrigin = 'anonymous';
baseImage.onload = function() {
render(baseImage);
};
const vertexShaderSource = `#version 300 es
precision mediump float;
in vec2 position;
out vec2 textureCoordinate;
void main() {
textureCoordinate = vec2(1.0 - position.x, 1.0 - position.y);
gl_Position = vec4((1.0 - 2.0 * position), 0, 1);
}`;
const fragmentShaderSource = `#version 300 es
precision mediump float;
precision highp usampler2D;
uniform usampler2D inputTexture;
in vec2 textureCoordinate;
out uvec4 fragColor;
void main() {
fragColor = texture(inputTexture, textureCoordinate);
}`;
function render(image) {
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl2');
if (!gl) {
return;
}
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1]),
gl.STATIC_DRAW
);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
const program = webglUtils.createProgramFromSources(gl, [
vertexShaderSource,
fragmentShaderSource,
]);
const positionAttributeLocation = gl.getAttribLocation(
program,
'position'
);
const inputTextureUniformLocation = gl.getUniformLocation(
program,
'inputTexture'
);
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
gl.enableVertexAttribArray(positionAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(
positionAttributeLocation,
2,
gl.FLOAT,
false,
0,
0
);
gl.bindVertexArray(null);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
const rawTexture = gl.createTexture();
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, rawTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8UI, gl.RGBA_INTEGER, gl.UNSIGNED_BYTE, image);
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);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_BASE_LEVEL, 0);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAX_LEVEL, 0);
const outputTexture = gl.createTexture();
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, outputTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8UI, image.width,
image.height,
0, gl.RGBA_INTEGER, gl.UNSIGNED_BYTE, null);
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);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_BASE_LEVEL, 0);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAX_LEVEL, 0);
const framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
outputTexture,
0
);
gl.viewport(0, 0, image.width, image.height);
gl.clearColor(0, 0, 0, 1.0);
// gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.useProgram(program);
gl.uniform1i(inputTextureUniformLocation, 0);
gl.bindVertexArray(vao);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, rawTexture);
gl.drawArrays(gl.TRIANGLES, 0, 6);
gl.bindVertexArray(null);
const pixels = new Uint8Array(4 * image.width * image.height);
gl.readPixels(
0,
0,
image.width,
image.height,
gl.RGBA_INTEGER,
gl.UNSIGNED_BYTE,
pixels
);
console.log(pixels);
}
<canvas id="canvas"></canvas>
<script src="https://webgl2fundamentals.org/webgl/resources/webgl-utils.js"></script>
Your code rendered just fine. It failed on readPixels which we can see in the JavaScript console, firefox printed the error
Error: WebGL warning: readPixels: Incompatible format or type.
This is an unfortunate part of the spec.
The spec lists all the internal formats of textures you can create and what format/type combinations of data you can pass to texImage2D to upload data to each of them. But, going the opposite way it is not as explicit which format/type combinations you can use to read pixels.
This is what it says, section 4.3.2
Only two combinations of format and type are accepted in most cases. The first varies depending on the format of the currently bound rendering surface. For normalized fixed-point rendering surfaces, the combination format RGBA and type
UNSIGNED_BYTE is accepted. For signed integer rendering surfaces, the combination format RGBA_INTEGER and type INT is accepted. For unsigned integer rendering surfaces, the combination format RGBA_INTEGER and type UNSIGNED_INT is accepted.
The second is an implementation-chosen format from among those defined
in table 3.2, excluding formats DEPTH_COMPONENT and DEPTH_STENCIL. The
values of format and type for this format may be determined by calling GetIntegerv with the symbolic constants IMPLEMENTATION_COLOR_READ_FORMAT
and IMPLEMENTATION_COLOR_READ_TYPE, respectively. ... The implementation-chosen format may vary depending on the format of the selected read buffer of the currently bound read framebuffer.
Additionally, when the internal format of the rendering surface is RGB10_A2,
a third combination of format RGBA and type UNSIGNED_INT_2_10_10_10_REV
is accepted.
Table 3.2, which you can see a version of on this page 4th table on the page, lists tons a format/type combos and it's important to note the spec does not dictate which format/type combos are valid. In other words it does not say pick a format/type combo from table 3.2 that corresponds to the current internal format. Instead it just says any format/type combo in that table is valid. Yes, you read that right. According to the spec you could upload RGBA/INT textures and the implementation might decide your second format is R/FLOAT ¯\_(ツ)_/¯
Here's some code to print out the 2nd allowed readPixels format/type combo for a RGBA8UI texture
function main() {
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl2');
if (!gl) {
return alert('need webgl2');
}
const outputTexture = gl.createTexture();
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, outputTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8UI, 4, 4,
0, gl.RGBA_INTEGER, gl.UNSIGNED_BYTE, null);
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);
const framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
outputTexture,
0
);
console.log(
`format/type: ${
glEnumToString(gl, gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT))
}/${
glEnumToString(gl, gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_TYPE))
}`);
}
main();
function glEnumToString(gl, value) {
for (const key in gl) {
if (gl[key] === value) {
return key;
}
}
return `0x${value.toFixed(16)}`;
}
<canvas id="canvas"></canvas>
If I run the code above Chrome says
format/type: RGBA_INTEGER/UNSIGNED_BYTE
But firefox says
format/type: RGBA_INTEGER/UNSIGNED_INT
Both of which are valid according to the spec.
If you want it to work everywhere you need to read the data as RGBA_INTEGER/UNSIGNED_INT as the first part of the spec above says that format is always supported for unsigned integer formats.
Changing your code to do that makes it work on both browsers
const baseImage = new Image();
baseImage.src = 'https://i.imgur.com/O6aW2Tg.png';
baseImage.crossOrigin = 'anonymous';
baseImage.onload = function() {
render(baseImage);
};
const vertexShaderSource = `#version 300 es
precision mediump float;
in vec2 position;
out vec2 textureCoordinate;
void main() {
textureCoordinate = vec2(1.0 - position.x, 1.0 - position.y);
gl_Position = vec4((1.0 - 2.0 * position), 0, 1);
}`;
const fragmentShaderSource = `#version 300 es
precision mediump float;
precision highp usampler2D;
uniform usampler2D inputTexture;
in vec2 textureCoordinate;
out uvec4 fragColor;
void main() {
fragColor = texture(inputTexture, textureCoordinate);
}`;
function render(image) {
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl2');
if (!gl) {
return;
}
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1]),
gl.STATIC_DRAW
);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
const program = webglUtils.createProgramFromSources(gl, [
vertexShaderSource,
fragmentShaderSource,
]);
const positionAttributeLocation = gl.getAttribLocation(
program,
'position'
);
const inputTextureUniformLocation = gl.getUniformLocation(
program,
'inputTexture'
);
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
gl.enableVertexAttribArray(positionAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(
positionAttributeLocation,
2,
gl.FLOAT,
false,
0,
0
);
gl.bindVertexArray(null);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
const rawTexture = gl.createTexture();
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, rawTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8UI, gl.RGBA_INTEGER, gl.UNSIGNED_BYTE, image);
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);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_BASE_LEVEL, 0);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAX_LEVEL, 0);
const outputTexture = gl.createTexture();
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, outputTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8UI, image.width,
image.height,
0, gl.RGBA_INTEGER, gl.UNSIGNED_BYTE, null);
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);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_BASE_LEVEL, 0);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAX_LEVEL, 0);
const framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
outputTexture,
0
);
gl.viewport(0, 0, image.width, image.height);
gl.clearColor(0, 0, 0, 1.0);
// gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.useProgram(program);
gl.uniform1i(inputTextureUniformLocation, 0);
gl.bindVertexArray(vao);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, rawTexture);
gl.drawArrays(gl.TRIANGLES, 0, 6);
gl.bindVertexArray(null);
const pixels = new Uint32Array(4 * image.width * image.height);
gl.readPixels(
0,
0,
image.width,
image.height,
gl.RGBA_INTEGER,
gl.UNSIGNED_INT,
pixels
);
console.log(pixels.slice(0, 40));
}
<canvas id="canvas"></canvas>
<script src="https://webgl2fundamentals.org/webgl/resources/webgl-utils.js"></script>

Need help doing simple rendering with Qt5 Qml + OpenGL

I need to make it so part of my Qml view is “taken over” by some non-Qt OpenGL rendering, and I was having issues getting a texture to display properly so I thought I would just draw a line and get that to work before moving on to more complicated code.
For those not familiar with Qt5, the entire window is drawn using OpenGL, and I'm hooking into Qt's OpenGL drawing mechanism using their QQuickWindow::beforeRendering() signal which means my painting code is executed every redraw (every vertical sync).
I took the Squircle sample code ( http://qt-project.org/doc/qt-5/qtquick-scenegraph-openglunderqml-example.html ) and modified it slightly so it would draw in a specified portion of the screen (instead of the entire screen) and that is working perfectly. Then, I modified just the renderer::paint() function to initially draw three green lines, and after 2 seconds to instead draw one blue line:
void CtRenderer::paint()
{
static int n = 0;
if (n == 0)
{
glViewport(m_rect.x() + 10, m_rect.y() + 10, m_rect.width() - 20, m_rect.height() - 20);
glEnable(GL_SCISSOR_TEST);
glScissor(m_rect.x() + 10, m_rect.y() + 10, m_rect.width() - 20, m_rect.height() - 20);
glDisable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);
// glClearColor(1, 0, 0, 1);
// glClear(GL_COLOR_BUFFER_BIT);
glLineWidth(10);
glColor4f(0.0, 1.0, 0.0, 1);
glBegin(GL_LINES);
glVertex3f(0.0, 0.0, 1); glVertex3f(1, 0.5, 1);
glVertex3f(0.0, 0.0, 1); glVertex3f(0.5, 0.5, 1);
glVertex3f(0.0, 0.0, 1); glVertex3f(0.5, 1, 1);
glEnd();
}
else if (n == 120)
{
glViewport(m_rect.x() + 10, m_rect.y() + 10, m_rect.width() - 20, m_rect.height() - 20);
glEnable(GL_SCISSOR_TEST);
glScissor(m_rect.x() + 10, m_rect.y() + 10, m_rect.width() - 20, m_rect.height() - 20);
glDisable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);
// glClearColor(1, 0, 0, 1);
// glClear(GL_COLOR_BUFFER_BIT);
glLineWidth(10);
glColor4f(0.0, 0.0, 1.0, 1);
glBegin(GL_LINES);
glVertex3f(0.0, 0.0, 1); glVertex3f(0.4, 0.8, 1);
glEnd();
}
n++;
return;
}`
What I get instead are three gray lines that flicker continuously and never change to being a single line. After some online research, I thought that maybe I should not use glBegin()/glEnd() so I changed the code :
void CtRenderer::paint()
{
static bool bOnce = true;
if (bOnce)
{
glViewport(m_rect.x() + 10, m_rect.y() + 10, m_rect.width() - 20, m_rect.height() - 20);
glEnable(GL_SCISSOR_TEST);
glScissor(m_rect.x() + 10, m_rect.y() + 10, m_rect.width() - 20, m_rect.height() - 20);
float vertices[] = {-0.5f, -0.5f, 0.5f, 0.5f};
glLineWidth(10);
glColor4f(0.0, 1.0, 0.0, 1);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, vertices);
glDrawArrays(GL_LINES, 0, 2);
glDisableClientState(GL_VERTEX_ARRAY);
bOnce = false;
}
return;
}
This still gives me a flickering gray line.
When I try this code in a simple GLUT application, outside of Qt, it works just fine, so it seems to be some interaction between OpenGL and Qt5 Qml. What can I try next?
p.s. I'm using Qt version 5.3 on a Linux Ubuntu box
p.p.s. In response to some comments, I updated my code to look like this:
void dumpGlErrors(int iLine)
{
for (;;)
{
GLenum err = glGetError();
if (err == GL_NO_ERROR)
return;
std::cout << "GL error " << err << " detected in line " << iLine << std::endl;
}
}
void CtRenderer::paint()
{
glPushMatrix();
glLoadIdentity();
glViewport(m_rect.x() + 10, m_rect.y() + 10, m_rect.width() - 20, m_rect.height() - 20);
glEnable(GL_SCISSOR_TEST);
glScissor(m_rect.x() + 10, m_rect.y() + 10, m_rect.width() - 20, m_rect.height() - 20);
dumpGlErrors(__LINE__);
glClearColor(1, 0, 1, 1); // Magenta
glClear(GL_COLOR_BUFFER_BIT);
bool bDepth = glIsEnabled(GL_DEPTH_TEST);
glDisable(GL_DEPTH_TEST);
bool bLighting = glIsEnabled(GL_LIGHTING);
glDisable(GL_LIGHTING);
bool bTexture = glIsEnabled(GL_TEXTURE_2D);
glDisable(GL_TEXTURE_2D);
dumpGlErrors(__LINE__);
float vertices[] = { -0.5f, -0.5f, 0.1f, 0.5f, 0.5f, 0.1f, 0.5f, 0.5f, 1.0f, 0.5f, -0.5f, 1.0f };
float colors[] = { 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f };
glLineWidth(10);
glColor4f(0.0, 0.0, 1.0, 1);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, vertices);
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(4, GL_FLOAT, 0, colors);
dumpGlErrors(__LINE__);
glDrawArrays(GL_LINES, 0, 4); // 4, not 2.
dumpGlErrors(__LINE__);
// glFlush();
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
if (bTexture)
glEnable(GL_TEXTURE_2D);
if (bLighting)
glEnable(GL_LIGHTING);
if (bDepth)
glEnable(GL_DEPTH_TEST);
glPopMatrix();
dumpGlErrors(__LINE__);
// In case somebody else calls glClear()
glClearColor(0, 1, 1, 1); // Cyan
}
Now I get a nice magenta background and I see my line flash once and then it goes away and I'm left with only magenta.
p.p.p.s. I tried using VBO type functions:
class Point
{
public:
float m_vertex[3];
float m_color[4];
};
void SquircleRenderer::drawBuffer()
{
QOpenGLFunctions glFuncs(QOpenGLContext::currentContext());
if (!m_bBufInit)
{
std::cout << "SquircleRenderer::drawBuffer()" << std::endl;
// Adding these two lines doesn't change anything
/*
glFuncs.initializeOpenGLFunctions();
glFuncs.glUseProgram(0);
*/
// Create a new VBO
glFuncs.glGenBuffers(1, &m_buf);
// Make the new VBO active
glFuncs.glBindBuffer(GL_ARRAY_BUFFER, m_buf);
static const Point points[4] = {
{ { -0.5f, -0.5f, 0.1f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
{ { 0.5f, 0.5f, 0.1f }, { 1.0f, 0.0f, 0.0f, 1.0f } },
{ { 0.5f, 0.5f, 1.0f }, { 0.0f, 0.0f, 1.0f, 1.0f } },
{ { 0.5f, -0.5f, 1.0f }, { 1.0f, 1.0f, 0.0f, 1.0f } }
};
// Upload vertex data to the video device
glFuncs.glBufferData(GL_ARRAY_BUFFER, 4 * sizeof(Point), points, GL_STATIC_DRAW);
dumpGlErrors(__LINE__);
m_bBufInit = true;
}
glPushMatrix();
glLoadIdentity();
glViewport(m_rect.x() + 10, m_rect.y() + 10, m_rect.width() - 20, m_rect.height() - 20);
glEnable(GL_SCISSOR_TEST);
glScissor(m_rect.x() + 10, m_rect.y() + 10, m_rect.width() - 20, m_rect.height() - 20);
glClearColor(1, 1, 0, 1); // Yellow
glClear(GL_COLOR_BUFFER_BIT);
glFuncs.glBindBuffer(GL_ARRAY_BUFFER, m_buf);
dumpGlErrors(__LINE__);
/*
* "If a non-zero named buffer object is bound to the GL_ARRAY_BUFFER target
* (see glBindBuffer) while a vertex array is
* specified, pointer is treated as a byte offset into the buffer object's data store"
*/
glLineWidth(10);
glColor4f(0.0, 0.0, 1.0, 1);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, sizeof(Point), 0);
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(4, GL_FLOAT, sizeof(Point), (void*)offsetof(Point, m_color));
dumpGlErrors(__LINE__);
glDrawArrays(GL_LINES, 0, 4); // 4, not 2.
dumpGlErrors(__LINE__);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
// In case somebody else calls glClear()
glClearColor(0, 1, 1, 1); // Cyan
glPopMatrix();
dumpGlErrors(__LINE__);
}
As with all my other efforts, I get a yellow background and my lines appear for what I assume is 1/60 second before the lines disappear and all I have is my yellow background.
I discovered the answer: I have to tell Qt that I'm using the old fixed function pipeline. Making one function call before doing any drawing does the trick:
QOpenGLFunctions glFuncs(QOpenGLContext::currentContext());
glFuncs.glUseProgram(0);
With Qt Quick there are three ways you can mix OpenGL:
You can draw under the (entire) QML (see http://qt-project.org/doc/qt-5/qtquick-scenegraph-openglunderqml-example.html)
You can draw over the (entire) QML
You can supply an OpenGL texture that is drawn on a QML element (see http://qt-project.org/doc/qt-5/qsgsimpletexturenode.html)
Alternatively you can build a QWidget application and use OpenGL in a QWidget window and QML in another (QML widget window).
void SquircleRenderer::paint()
{
if (!m_program) {
m_program = new QOpenGLShaderProgram();
m_program->addShaderFromSourceCode(QOpenGLShader::Vertex,
"attribute highp vec4 aVertices;"
"attribute highp vec4 aColors;"
"varying highp vec4 vColors;"
"void main() {"
" gl_Position = aVertices;"
" vColors= aColors;"
"}");
m_program->addShaderFromSourceCode(QOpenGLShader::Fragment,
"varying highp vec4 vColors;"
"void main() {"
" gl_FragColor = vColors;"
"}");
m_program->bindAttributeLocation("aVertices", 0);
m_program->bindAttributeLocation("aColors", 1);
m_program->link();
}
m_program->bind();
m_program->enableAttributeArray(0);
m_program->enableAttributeArray(1);
float vertices[] = {
-1, -1, //Diag bottom left to top right
1, 1,
-1, 1, //Diag top left to bottom right
1, -1,
-1, 0, //Horizontal line
1, 0
};
float colors[] = {
1, 1, 0, 1,
1, 0, 1, 1,
0, 1, 1, 1,
1, 0, 0, 1,
0, 0, 1, 1,
0, 1, 0, 1
};
m_program->setAttributeArray(0, GL_FLOAT, vertices, 2); //3rd to 0, 4th to 1 by default
m_program->setAttributeArray(1, GL_FLOAT, colors, 4);
glViewport(0, 0, m_viewportSize.width(), m_viewportSize.height());
glDisable(GL_DEPTH_TEST);
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
//Here I draw 3 lines, reduce to 2 instead of 6 to draw only one.
//Change second param to choose which line to draw
glDrawArrays(GL_LINES, 0, 6);
m_program->disableAttributeArray(0);
m_program->disableAttributeArray(1);
m_program->release();
}
My conclusion would be to leave the Legacy version of OpenGL in the history book. Even if it's for dummy shaders like those one, it saves you the trouble of managing any matrices and you know what is happening.
PS : The code hasn't been tested, it's just a tweak of the Squircle code. Let me know if there is trouble, but I'd rather continue the troubleshoot session from this piece of code.

WebGL -Trying to texture a square, but it won't work for some reason

So I am in dire need of a code read through, if someone would be so kind- I have no idea where a fault can come from- I've been comparing this code to the code from the tutorial here:
http://learningwebgl.com/lessons/lesson05/index.html
and have looked through both about 10 times- I just don't know...Need some help from the pros... Just trying to texture a square, without any of the 3d stuff that I don't care for at the moment-
<script id="shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec2 vTextureCoord;
uniform sampler2D uSampler;
void main(void) {
gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
// gl_FragColor= vec4(0.0, 1.0, 0.0, 1.0);
}
</script>
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
attribute vec2 aTextureCoord;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
varying vec2 vTextureCoord;
void main(void) {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
vTextureCoord = aTextureCoord;
}
</script>
<script type="text/javascript">
var gl;
function initGL(canvas) {
try {
gl = canvas.getContext("experimental-webgl");
gl.viewportWidth = canvas.width;
gl.viewportHeight = canvas.height;
} catch (e) {
}
if (!gl) {
alert("Could not initialise WebGL, sorry :-(");
}
}
function getShader(gl, id) {
var shaderScript = document.getElementById(id);
if (!shaderScript) {
return null;
}
var str = "";
var k = shaderScript.firstChild;
while (k) {
if (k.nodeType == 3) {
str += k.textContent;
}
k = k.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, str);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert(gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
var shaderProgram;
function initShaders() {
var fragmentShader = getShader(gl, "shader-fs");
var vertexShader = getShader(gl, "shader-vs");
shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("Could not initialise shaders");
}
gl.useProgram(shaderProgram);
shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
shaderProgram.textureCoordAttribute=gl.getAttribLocation(shaderProgram, "aTextureCoord");
gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute); //**
shaderProgram.samplerUniform= gl.getUniformLocation(shaderProgram, "uSampler");
shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
}
var mvMatrix = mat4.create();
var pMatrix = mat4.create();
function setMatrixUniforms() {
gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix);
gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);
}
var triangleVertexPositionBuffer;
var squareVertexPositionBuffer;
function initBuffers() {
squareVertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
vertices = [
1.0, 1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, -1.0, 0.0,
-1.0, -1.0, 0.0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
squareVertexPositionBuffer.itemSize = 3;
squareVertexPositionBuffer.numItems = 4;
squareTexPositionBuffer=gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareTexPositionBuffer);
texvert= [1.0, 0.0,
0.0, 0.0,
1.0, 1.0,
0.0, 1.0];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texvert), gl.STATIC_DRAW);
squareTexPositionBuffer.itemSize=2;
squareTexPositionBuffer.numItems=4;
}
function drawScene() {
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);
mat4.identity(mvMatrix);
mat4.translate(mvMatrix, [-2.0, 0.0, -7.0]);
mat4.translate(mvMatrix, [3.0, 0.0, 0.0]);
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, squareTexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, squareTexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, neheTexture);
gl.uniform1i(shaderProgram.samplerUniform, 0);
setMatrixUniforms();
gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems);
}
function handleLoadedTexture(texture) {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.bindTexture(gl.TEXTURE_2D, null);
}
var neheTexture;
function initTexture(){
neheTexture = gl.createTexture();
neheTexture.image = new Image();
neheTexture.image.onload = function() {
handleLoadedTexture(neheTexture)
}
neheTexture.image.src = "nehe.gif";
}
function webGLStart() {
var canvas = document.getElementById("lesson01-canvas");
initGL(canvas);
initShaders();
initBuffers();
initTexture();
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST);
drawScene();
}
</script>
Texture won't be loaded immediately.
This is not a problem when you have an animation, because the scene will be rendered with blank texture while it's not fully loaded, and once it is the objects will become textured. You don't have animation, only one drawing call, that executes before fully loading image.
So after you load image you should make another drawing call, so the scene is drawn with texture.
So something like:
neheTexture.image.onload = function() {
handleLoadedTexture(neheTexture);
drawScene(); // <- now draw scene again, once I got my texture
}
Hope this helps. :)
Here's kind of a follow up question- Let's say I wanted to have two different shapes- one with a color buffer and another with a texture buffer, how woud I write that code out in the shaders since the tutorials made it almost like you could only have one or the other, but not both?
So like in the following code, I have something for the texture and something to make the color blue in another line of code- how would I make that differentiation in this language- I tried using ints to symbolize the choice between the two but it didn't work out very well...
<script id="shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec2 vTextureCoord;
uniform sampler2D uSampler;
void main(void) {
gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
// gl_FragColor= vec4(0.0, 1.0, 0.0, 1.0);
}
</script>

Resources