Related
I am new to WebGL and now learning varying variables. In this, my expected output is the image below.
In the code below, I tried to pass colour from the vertex shader to fragment shader.The primitive type pass in drawArray is a triangle but it is not drawing triangles.I wrote this code to understand varying variables in WebGL and is derived from WebGLfundamental website. I don't know what went wrong since from my knowledge, this is what I am supposed to do.
The output I was expecting is :
Code is :
"use strict";
var vs = `#version 300 es
precision highp float;
in vec2 a_position;
in vec4 a_color;
out vec4 v_color;
void main(){
gl_Position = vec4(a_position, 0, 1);
v_color = a_color;
}
`;
var fs = `#version 300 es
precision highp float;
in vec4 v_color;
out vec4 outColor;
void main(){
outColor = v_color;
}
`;
function main() {
var canvas = document.querySelector("#c");
var gl = canvas.getContext("webgl2");
if (!gl) {
return;
}
var program = webglUtils.createProgramFromSources(gl, [vs, fs]);
var vertexPosition = gl.getAttribLocation(program, 'a_position');
var vertexColor = gl.getAttribLocation(program, 'a_color');
var vao = gl.createVertexArray();
gl.bindVertexArray(vao);
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
var position = [
-150, -100,
150, -100,
-150, 100,
-150, 100,
150, -100,
150, 100
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(position), gl.STATIC_DRAW);
gl.enableVertexAttribArray(vertexPosition);
var size = 2;
var type = gl.FLOAT;
var normalize = false;
var stride = 0;
var offset = 0;
gl.vertexAttribPointer(vertexPosition, size, type, normalize, stride, offset);
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
var color = [
0.7, 0.2, 0.9, 1,
0.6, 0.7, 0.2, 1,
0.5, 0.7, 0.9, 1,
0.4, 0.7, 0.2, 1,
0.7, 0.2, 0.9, 1,
0.5, 0.7, 0.9, 1,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(color), gl.STATIC_DRAW);
gl.enableVertexAttribArray(vertexColor);
var size =4;
var type = gl.FLOAT;
var normalize = false;
var stride = 0;
var offset = 0;
gl.vertexAttribPointer(vertexColor, size, type, normalize, stride, offset);
webglUtils.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.useProgram(program);
gl.bindVertexArray(vao);
var offset = 0;
var count = 6;
gl.drawArrays(gl.TRIANGLES, offset, count);
}
main();
body{
overflow: hidden;
}
canvas {
width: 100vw;
height: 100vh;
display: block;
}
<script src="https://webgl2fundamentals.org/webgl/resources/webgl-utils.js"></script>
<canvas id="c">
</canvas>
The issue is you're providing positions in pixels but WebGL requires positions in clip space. So you're drawing what you think you were drawing except only the center 2x2 units of your 300x200 rectangle are being shown.
For example if we just hack and divide the position by 300,200
gl_Position = vec4(a_position / vec2(300, 200);
Then it works
"use strict";
var vs = `#version 300 es
precision highp float;
in vec2 a_position;
in vec4 a_color;
out vec4 v_color;
void main(){
gl_Position = vec4(a_position / vec2(300, 200), 0, 1);
v_color = a_color;
}
`;
var fs = `#version 300 es
precision highp float;
in vec4 v_color;
out vec4 outColor;
void main(){
outColor = v_color;
}
`;
function main() {
var canvas = document.querySelector("#c");
var gl = canvas.getContext("webgl2");
if (!gl) {
return;
}
var program = webglUtils.createProgramFromSources(gl, [vs, fs]);
var vertexPosition = gl.getAttribLocation(program, 'a_position');
var vertexColor = gl.getAttribLocation(program, 'a_color');
var vao = gl.createVertexArray();
gl.bindVertexArray(vao);
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
var position = [
-150, -100,
150, -100,
-150, 100,
-150, 100,
150, -100,
150, 100
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(position), gl.STATIC_DRAW);
gl.enableVertexAttribArray(vertexPosition);
var size = 2;
var type = gl.FLOAT;
var normalize = false;
var stride = 0;
var offset = 0;
gl.vertexAttribPointer(vertexPosition, size, type, normalize, stride, offset);
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
var color = [
0.7, 0.2, 0.9, 1,
0.6, 0.7, 0.2, 1,
0.5, 0.7, 0.9, 1,
0.4, 0.7, 0.2, 1,
0.7, 0.2, 0.9, 1,
0.5, 0.7, 0.9, 1,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(color), gl.STATIC_DRAW);
gl.enableVertexAttribArray(vertexColor);
var size =4;
var type = gl.FLOAT;
var normalize = false;
var stride = 0;
var offset = 0;
gl.vertexAttribPointer(vertexColor, size, type, normalize, stride, offset);
webglUtils.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.useProgram(program);
gl.bindVertexArray(vao);
var offset = 0;
var count = 6;
gl.drawArrays(gl.TRIANGLES, offset, count);
}
main();
body{
overflow: hidden;
}
canvas {
width: 100vw;
height: 100vh;
display: block;
}
<script src="https://webgl2fundamentals.org/webgl/resources/webgl-utils.js"></script>
<canvas id="c">
</canvas>
The normal way to convert positions from pixel space to clip space is to use a matrix
I have to write a program in which i should be able to click a button "add cube" and there will be added as many cubes as I want into the scene. I must be able to select any of them and move around. I managed to create one (hardcoded) cube and apply scaling and translation to it.
I tried to create a class "cube" and invoke intitialization and render as part of an object but didnt work as expected. Here is the working example for one cube and its transformations (note only the scaling and translation work)
HTML:
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>Lab 0</title>
<script id="vertex-shader" type="x-shader/x-vertex">
attribute vec4 vPosition;
attribute vec4 vColors;
varying vec4 fragColor;
uniform vec3 theta;
uniform float coeff;
uniform vec3 trCoeff;
uniform mat4 modelView;
uniform mat4 projection;
vec3 angles = radians( theta );
void main() {
vec3 angles = radians( theta );
vec3 c = cos( angles );
vec3 s = sin( angles );
// Remeber: thse matrices are column-major
mat4 rx = mat4( 1.0, 0.0, 0.0, 0.0,
0.0, c.x, s.x, 0.0,
0.0, -s.x, c.x, 0.0,
0.0, 0.0, 0.0, 1.0 );
mat4 ry = mat4( c.y, 0.0, -s.y, 0.0,
0.0, 1.0, 0.0, 0.0,
s.y, 0.0, c.y, 0.0,
0.0, 0.0, 0.0, 1.0 );
mat4 rz = mat4( c.z, s.z, 0.0, 0.0,
-s.z, c.z, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0 );
mat4 sc = mat4(
coeff, 0, 0, 0,
0, coeff, 0, 0,
0, 0, coeff, 0,
0, 0, 0, 1
);
mat4 tr = mat4(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
trCoeff.x, trCoeff.y, trCoeff.z, 1
);
fragColor = vColors;
//projection * modelView *
gl_Position = tr * rz * ry * rx * sc * vPosition;
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
precision mediump float;
varying vec4 fragColor;
void main() {
gl_FragColor = vec4(fragColor);
}
</script>
<script type="text/javascript" src="webgl-utils.js"></script>
<script type="text/javascript" src="initShaders.js"></script>
<script type="text/javascript" src="MV.js"></script>
<script type="text/javascript" src="shape0.js"></script>
</head>
<body>
<canvas id="gl-canvas" width="512" height="512">
Oops ... your browser doesn't support the HTML5 canvas element
</canvas>
</body>
<p> </p>
<button id="xButton">Rotate X</button>
<button id="yButton">Rotate Y</button>
<button id="zButton">Rotate Z</button>
<button id="stopButton">Stop Fucking Rotation</button>
<p> </p>
<button id="sUpButton">Scale Up</button>
<button id="sDownButton">Scale Down</button>
<p> </p>
<button id="leftButton">Left</button>
<button id="rightButton">Right</button>
<button id="upButton">Up</button>
<button id="downButton">Down</button>
<button id="closeButton">Closer</button>
<button id="furtherButton">Further</button>
<p> </p>
<button id="Button1">Increase Z</button>
<button id="Button2">Decrease Z</button>
<button id="Button3">Increase R</button>
<button id="Button4">Decrease R</button>
<p> </p>
<button id="Button5">Increase theta</button>
<button id="Button6">Decrease theta</button>
<button id="Button7">Increase phi</button>
<button id="Button8">Decrease phi</button>
<p> </p>
</html>
JS:
"use strict"
var canvas;
var gl;
var thetaRot = [10, 10, 10];
var thetaLoc;
var coeff = 1;
var coeffLoc;
var trCoeff = [0, 0, 0];
var trCoeffLoc;
var flag = true;
var axis = 0;
var xAxis = 0;
var yAxis = 1;
var zAxis = 2;
var near = 0.3;
var far = 3.0;
var radius = 4.0;
var theta = 0.0;
var phi = 0.0;
var dr = 5.0 * Math.PI / 180.0;
var fovy = 45.0; // Field-of-view in Y direction angle (in degrees)
var aspect; // Viewport aspect ratio
var mvMatrix, pMatrix;
var modelView, projection;
var eye;
const at = vec3(0.0, 0.0, 0.0);
const up = vec3(0.0, 1.0, 0.0);
var coord = {
// For cube
'1': vec3(0.1, 0.1, 0.1),
'2': vec3(0.5, 0.1, 0.1),
'3': vec3(0.5, 0.1, 0.5),
'4': vec3(0.1, 0.1, 0.5),
'5': vec3(0.1, 0.5, 0.1),
'6': vec3(0.1, 0.5, 0.5),
'7': vec3(0.5, 0.5, 0.5),
'8': vec3(0.5, 0.5, 0.1),
}
var vertices = [
// For cube
coord[4], coord[3], coord[2],
coord[2], coord[4], coord[1],
coord[1], coord[2], coord[5],
coord[5], coord[8], coord[2],
coord[2], coord[3], coord[8],
coord[8], coord[3], coord[7],
coord[7], coord[3], coord[4],
coord[4], coord[7], coord[6],
coord[6], coord[4], coord[1],
coord[1], coord[5], coord[6],
coord[6], coord[7], coord[8],
coord[8], coord[6], coord[5],
]
var colors = [
// each face of the cube
0, 0, 1, 1.0,
0, 0, 1, 1.0,
0, 0, 1, 1.0,
0, 0, 1, 1.0,
0, 0, 1, 1.0,
0, 0, 1, 1.0,
0, 1, 0, 1.0,
0, 1, 0, 1.0,
0, 1, 0, 1.0,
0, 1, 0, 1.0,
0, 1, 0, 1.0,
0, 1, 0, 1.0,
0, 0.7, 1, 1.0,
0, 0.7, 1, 1.0,
0, 0.7, 1, 1.0,
0, 0.7, 1, 1.0,
0, 0.7, 1, 1.0,
0, 0.7, 1, 1.0,
0.5, 0, 1, 1.0,
0.5, 0, 1, 1.0,
0.5, 0, 1, 1.0,
0.5, 0, 1, 1.0,
0.5, 0, 1, 1.0,
0.5, 0, 1, 1.0,
1, 0, 0, 1.0,
1, 0, 0, 1.0,
1, 0, 0, 1.0,
1, 0, 0, 1.0,
1, 0, 0, 1.0,
1, 0, 0, 1.0,
0.2, 0, 1, 1.0,
0.2, 0, 1, 1.0,
0.2, 0, 1, 1.0,
0.2, 0, 1, 1.0,
0.2, 0, 1, 1.0,
0.2, 0, 1, 1.0,
]
// add vertices for cone
window.onload = init;
function init() {
canvas = document.getElementById("gl-canvas");
gl = WebGLUtils.setupWebGL(canvas);
if (!gl) { alert("WebGL isn't available"); }
aspect = canvas.width / canvas.height;
var program = initShaders(gl, "vertex-shader", "fragment-shader");
gl.useProgram(program);
gl.enable(gl.DEPTH_TEST);
var bufferId = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, bufferId);
gl.bufferData(gl.ARRAY_BUFFER, 50000, gl.STATIC_DRAW);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, flatten(vertices));
var vPosition = gl.getAttribLocation(program, "vPosition");
gl.vertexAttribPointer(vPosition, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(vPosition);
var bufferColor = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, bufferColor);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
var vColors = gl.getAttribLocation(program, "vColors");
gl.vertexAttribPointer(vColors, 4, gl.FLOAT, false, 4 * Float32Array.BYTES_PER_ELEMENT, 0);
gl.enableVertexAttribArray(vColors);
thetaLoc = gl.getUniformLocation(program, "theta");
document.getElementById("xButton").onclick = function () {
flag = false
axis = xAxis;
};
document.getElementById("yButton").onclick = function () {
flag = false
axis = yAxis;
};
document.getElementById("zButton").onclick = function () {
flag = false
axis = zAxis;
};
document.getElementById("stopButton").onclick = function () {
flag = true;
};
document.getElementById("sUpButton").onclick = function () {
coeff += 0.1;
};
document.getElementById("sDownButton").onclick = function () {
coeff -= 0.1;
};
document.getElementById("leftButton").onclick = function () {
trCoeff[0] -= 0.1;
};
document.getElementById("rightButton").onclick = function () {
trCoeff[0] += 0.1;
};
document.getElementById("upButton").onclick = function () {
trCoeff[1] += 0.1;
};
document.getElementById("downButton").onclick = function () {
trCoeff[1] -= 0.1;
};
document.getElementById("closeButton").onclick = function () {
trCoeff[2] += 0.1;
};
document.getElementById("furtherButton").onclick = function () {
trCoeff[2] -= 0.1;
};
modelView = gl.getUniformLocation(program, "modelView");
projection = gl.getUniformLocation(program, "projection");
document.getElementById("Button1").onclick = function () { near *= 1.1; far *= 1.1; };
document.getElementById("Button2").onclick = function () { near *= 0.9; far *= 0.9; };
document.getElementById("Button3").onclick = function () { radius *= 2.0; };
document.getElementById("Button4").onclick = function () { radius *= 0.5; };
document.getElementById("Button5").onclick = function () { theta += dr; };
document.getElementById("Button6").onclick = function () { theta -= dr; };
document.getElementById("Button7").onclick = function () { phi += dr; };
document.getElementById("Button8").onclick = function () { phi -= dr; };
coeffLoc = gl.getUniformLocation(program, "coeff");
trCoeffLoc = gl.getUniformLocation(program, "trCoeff");
render();
}
function render() {
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
if (!flag) {
thetaRot[axis] += 2.0;
}
gl.uniform3fv(thetaLoc, thetaRot);
gl.uniform1f(coeffLoc, coeff);
gl.uniform3fv(trCoeffLoc, trCoeff);
eye = vec3(radius * Math.sin(theta) * Math.cos(phi),
radius * Math.sin(theta) * Math.sin(phi), radius * Math.cos(theta));
mvMatrix = lookAt(eye, at, up);
pMatrix = perspective(fovy, aspect, near, far);
gl.uniformMatrix4fv(modelView, false, flatten(mvMatrix));
gl.uniformMatrix4fv(projection, false, flatten(pMatrix));
gl.drawArrays(gl.TRIANGLES, 0, vertices.length);
requestAnimFrame(render);
}
I expect managing to add an infinite amount of object (or until I run out of ram) to the scene and to apply transformations to one at a time.
WebGL just draws pixels. It doesn't have a concept of a "Scene" and "objects". All of that is 100% up to you. How you represent objects and how you represent the scene is completely up to you. One example
const scene = [];
function addNewObjectToScene() {
const obj = {
x: Math.random() * 400,
y: Math.random() * 400,
};
scene.push(obj);
}
Now add a button or a timer or whatever you want to call addNewObjectToScene
In your own code you'd have a render or drawScene function that goes over your "scene" and draws each object
function render() {
for (const object of scene) {
gl.useProgram(whateverShaderProgramCurrentObjectNeeds);
// setBuffersAndAttributes for object
// for each attribute in object
// gl.bindBuffer(gl.ARRAY_BUFFER, bufferForThisAttribute)
// gl.enableVertexAttribArray(locationForThisAttrubute);
// gl.vertexAttribPointer(locationForThisAttibute, other settings for this attribute)
// set any uniforms. This is probably where you'd use x and y from the code above
// example:
// gl.uniform2f(offsetUniformLocation, object.x, object.y);
// now draw
// gl.drawArrays or gl.drawElements with how ever many vertices are in the current object.
}
}
How you organize your "scene" and what's in your objects is entire up to you. Three.js makes a scene from an object called THREE.Object3D each of which contains array of "children". This forms a scene graph.
Other apps do different things.
Being able to select one is also way too big a topic. What does "select" mean. Can you have an HTML <select> form where you pick from a dropdown list which object you want to select? Maybe there is an <input type="text"> element where you type the name of the object you want to select?
If you want to click on objects, well now you've got a way too broad of a topic to cover. You could store collision data for each object. You could compute the position of each triangle your data makes and see if the mouse hit. You could use GPU picking where you draw the scene with each object in a different color using different shaders and then look at the pixel under the mouse to see what color it is effectively telling you want object was chosen. But all of this is dependent on how you created your scene.
Suggest you read some tutorials on WebGL and on drawing multiple things and maybe a scene graph
var gl, utils, pseudoImg, vertices;
var img = null;
document.addEventListener('DOMContentLoaded', () => {
utils = new WebGLUtils();
vertices = utils.prepareVec2({x1 : -1.0, y1 : -1.0, x2 : 1.0, y2 : 1.0});
gl = utils.getGLContext(document.getElementById('canvas'));
var program = utils.getProgram(gl, 'render-vs', 'render16bit-fs');
var histogramProgram = utils.getProgram(gl, 'histogram-vs', 'histogram-fs');
var sortProgram = utils.getProgram(gl, 'sorting-vs', 'sorting-fs');
var showProgram = utils.getProgram(gl, 'showhistogram-vs', 'showhistogram-fs');
utils.activateTextureByIndex(gl, showProgram, 'histTex', 3);
utils.activateTextureByIndex(gl, showProgram, 'maxTex', 4);
utils.activateTextureByIndex(gl, sortProgram, 'tex3', 2);
utils.activateTextureByIndex(gl, histogramProgram, 'tex2', 1);
utils.activateTextureByIndex(gl, program, 'u_texture', 0);
var vertexBuffer = utils.createAndBindBuffer(gl, vertices);
var imageTexture;
computeHistogram = (AR, myFB) => {
gl.useProgram(histogramProgram);
var width = AR.width;
var height = AR.height;
var numOfPixels = width * height;
var pixelIds = new Float32Array(numOfPixels);
for (var i = 0; i < numOfPixels; i++) {
pixelIds[i] = i;
}
var histogramFbObj = utils.createTextureAndFramebuffer(gl, {
format : gl.RED,
internalFormat : gl.R32F,
filter : gl.NEAREST,
dataType : gl.FLOAT,
mipMapST : gl.CLAMP_TO_EDGE,
width : 256,
height : 256
});
gl.bindFramebuffer(gl.FRAMEBUFFER, histogramFbObj.fb);
gl.viewport(0, 0, 256, 256);
var pixelBuffer = utils.createAndBindBuffer(gl, pixelIds, true);
gl.blendFunc(gl.ONE, gl.ONE);
gl.enable(gl.BLEND);
utils.linkAndSendDataToGPU(gl, histogramProgram, 'pixelIds', pixelBuffer, 1);
gl.uniform2fv(gl.getUniformLocation(histogramProgram, 'imageDimensions'), [width, height]);
utils.sendTextureToGPU(gl, myFB.tex, 1);
gl.drawArrays(gl.POINTS, 0, numOfPixels);
gl.blendFunc(gl.ONE, gl.ZERO);
gl.disable(gl.BLEND);
return histogramFbObj;
};
sortHistogram = (histogramFbObj) => {
gl.useProgram(sortProgram);
utils.linkAndSendDataToGPU(gl, sortProgram, 'vertices', vertexBuffer, 2);
var sortFbObj = utils.createTextureAndFramebuffer(gl, {
format : gl.RED,
internalFormat : gl.R32F,
filter : gl.NEAREST,
dataType : gl.FLOAT,
mipMapST : gl.CLAMP_TO_EDGE,
width : 1,
height : 1
});
gl.bindFramebuffer(gl.FRAMEBUFFER, sortFbObj.fb);
gl.viewport(0, 0, 1, 1);
utils.sendTextureToGPU(gl, histogramFbObj.tex, 2);
gl.drawArrays(gl.TRIANGLES, 0, 6);
return sortFbObj;
};
showHistogram = (histFb, sortFb) => {
gl.useProgram(showProgram);
utils.linkAndSendDataToGPU(gl, showProgram, 'vertices', vertexBuffer, 2);
utils.sendTextureToGPU(gl, histFb.tex, 3);
utils.sendTextureToGPU(gl, sortFb.tex, 4);
gl.uniform2fv(gl.getUniformLocation(showProgram, 'imageDimensions'), [gl.canvas.width, gl.canvas.height]);
gl.drawArrays(gl.TRIANGLES, 0, 6);
};
showTexture = (AR) => {
imageTexture = utils.createAndBindTexture(gl, {
filter : gl.NEAREST,
mipMapST : gl.CLAMP_TO_EDGE,
dataType : gl.UNSIGNED_SHORT,
format : gl.RGBA_INTEGER,
internalFormat : gl.RGBA16UI,
img : AR.img,
width : AR.width,
height : AR.height
});
gl.useProgram(program);
var myFB = utils.createTextureAndFramebuffer(gl, {
filter : gl.NEAREST,
mipMapST : gl.CLAMP_TO_EDGE,
dataType : gl.UNSIGNED_BYTE,
format : gl.RGBA,
internalFormat : gl.RGBA,
width : AR.width,
height : AR.height,
});
gl.bindFramebuffer(gl.FRAMEBUFFER, myFB.fb);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
utils.linkAndSendDataToGPU(gl, program, 'vertices', vertexBuffer, 2);
gl.uniform1f(gl.getUniformLocation(program, 'flipY'), 1.0);
utils.sendTextureToGPU(gl, imageTexture, 0);
gl.drawArrays(gl.TRIANGLES, 0, 6);
var fb1 = computeHistogram(AR, myFB);
var fb2 = sortHistogram(fb1);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
showHistogram(fb1, fb2);
};
var w = 128;
var h = 128;
var size = w * h * 4;
var img = new Uint16Array(size); // need Uint16Array
for (var i = 0; i < img.length; i += 4) {
img[i + 0] = 65535; // r
img[i + 1] = i/64 * 256; // g
img[i + 2] = 0; // b
img[i + 3] = 65535; // a
}
showTexture({
img : img,
width : w,
height : h
});
});
<script id="render16bit-fs" type="not-js">
#version 300 es
precision highp float;
uniform highp usampler2D tex;
in vec2 texcoord; // receive pixel position from vertex shader
out vec4 fooColor;
void main() {
uvec4 unsignedIntValues = texture(tex, texcoord);
vec4 floatValues0To65535 = vec4(unsignedIntValues);
vec4 colorValues0To1 = floatValues0To65535;
fooColor = colorValues0To1;
}
</script>
<script type="not-js" id="render-vs">
#version 300 es
in vec2 vertices;
out vec2 texcoord;
uniform float flipY;
void main() {
texcoord = vertices.xy * 0.5 + 0.5;
gl_Position = vec4(vertices.x, vertices.y * flipY, 0.0, 1.0);
}
</script>
<script type="not-js" id="histogram-vs">
#version 300 es
in float pixelIds; //0,1,2,3,4,.......width*height
uniform sampler2D tex2;
uniform vec2 imageDimensions;
void main () {
vec2 pixel = vec2(mod(pixelIds, imageDimensions.x), floor(pixelIds / imageDimensions.x));
vec2 xy = pixel/imageDimensions;
float pixelValue = texture(tex2, xy).r;//Pick Pixel value from GPU texture ranges from 0-65535
float xDim = mod(pixelValue, 255.0)/256.0;
float yDim = floor(pixelValue / 255.0)/256.0;
float xVertex = (xDim*2.0) - 1.0;//convert 0.0 to 1.0 -> -1.0 -> 1.0, it will increment because we have used gl.blendFunc
float yVertex = 1.0 - (yDim*2.0);
gl_Position = vec4(xVertex, yVertex, 0.0, 1.0);
gl_PointSize = 1.0;
}
</script>
<script type="not-js" id="histogram-fs">
#version 300 es
precision mediump float;
out vec4 fcolor;
void main() {
fcolor = vec4(1.0, 1.0, 1.0, 1.0);
}
</script>
<script type="not-js" id="sorting-vs">
#version 300 es
in vec2 vertices;
void main () {
gl_Position = vec4(vertices, 0.0, 1.0);
}
</script>
<script type="not-js" id="sorting-fs">
#version 300 es
precision mediump float;
out vec4 fcolor;
uniform sampler2D tex3;
const int MAX_WIDTH = 65536;
void main() {
vec4 maxColor = vec4(0.0);
for (int i = 0; i < MAX_WIDTH; i++) {
float xDim = mod(float(i), 256.0)/256.0;
float yDim = floor(float(i) / 256.0)/256.0;
vec2 xy = vec2(xDim, yDim);
vec4 currPixel = texture(tex3, xy).rrra;
maxColor = max(maxColor, currPixel);
}
fcolor = vec4(maxColor);
}
</script>
<script type="not-js" id="showhistogram-vs">
#version 300 es
in vec2 vertices;
void main () {
gl_Position = vec4(vertices, 0.0, 1.0);
}
</script>
<script type="not-js" id="showhistogram-fs">
#version 300 es
precision mediump float;
uniform sampler2D histTex, maxTex;
uniform vec2 imageDimensions;
out vec4 fcolor;
void main () {
// get the max color constants
vec4 maxColor = texture(maxTex, vec2(0));
// compute our current UV position
vec2 uv = gl_FragCoord.xy / imageDimensions;
vec2 uv2 = gl_FragCoord.xy / vec2(256.0, 256.0);
// Get the history for this color
vec4 hist = texture(histTex, uv2);
// scale by maxColor so scaled goes from 0 to 1 with 1 = maxColor
vec4 scaled = hist / maxColor;
// 1 > maxColor, 0 otherwise
vec4 color = step(uv2.yyyy, scaled);
fcolor = vec4(color.rgb, 1);
}
</script>
<canvas id="canvas"></canvas>
<script type="text/javascript">
class WebGLUtils {
getGLContext = (canvas, version) => {
canvas.width = window.innerWidth * 0.99;
canvas.height = window.innerHeight * 0.85;
var gl = canvas.getContext(version ? 'webgl' : 'webgl2');
const ext = gl.getExtension("EXT_color_buffer_float");
if (!ext) {
console.log("sorry, can't render to floating point textures");
}
gl.clearColor(0, 0, 0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);
gl.lineWidth(0.5);
return gl;
};
clear = (gl) => {
gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);
};
getShader = (gl, type, shaderText) => {
var vfs = gl.createShader(type);
gl.shaderSource(vfs, shaderText);
gl.compileShader(vfs);
if (!gl.getShaderParameter(vfs, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(vfs));
}
return vfs;
};
getProgram = (gl, vertexShader, fragmentShader) => {
var program = gl.createProgram();
gl.attachShader(program, this.getShader(gl, gl.VERTEX_SHADER, document.getElementById(vertexShader).text.trim()));
gl.attachShader(program, this.getShader(gl, gl.FRAGMENT_SHADER, document.getElementById(fragmentShader).text.trim()));
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(program));
}
return program;
};
createAndBindBuffer = (gl, relatedVertices, isNotJSArray) => {
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, isNotJSArray ? relatedVertices : new Float32Array(relatedVertices), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
return buffer;
};
createAndBindTexture = (gl, _) => {
var texBuffer = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texBuffer);
if (_.img.width) {
gl.texImage2D(gl.TEXTURE_2D, 0, _.internalFormat, _.format, _.dataType, _.img);
} else {
gl.texImage2D(gl.TEXTURE_2D, 0, _.internalFormat, _.width, _.height, 0, _.format, _.dataType, _.img);
}
// set the filtering so we don't need mips
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, _.filter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, _.filter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, _.mipMapST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, _.mipMapST);
gl.bindTexture(gl.TEXTURE_2D, null);
return texBuffer;
};
createTextureAndFramebuffer = (gl, _) => {
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, _.internalFormat, _.width, _.height, 0, _.format, _.dataType, null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, _.filter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, _.filter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, _.mipMapST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, _.mipMapST);
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
console.log(`can ${status === gl.FRAMEBUFFER_COMPLETE ? "" : "NOT "}render to R32`);
return {tex: tex, fb: fb};
};
linkAndSendDataToGPU = (gl, program, linkedVariable, buffer, dimensions) => {
var vertices = gl.getAttribLocation(program, linkedVariable);
gl.enableVertexAttribArray(vertices);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.vertexAttribPointer(vertices, dimensions, gl.FLOAT, gl.FALSE, 0, 0);
return vertices;
};
sendDataToGPU = (gl, buffer, vertices, dimensions) => {
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.vertexAttribPointer(vertices, dimensions, gl.FLOAT, gl.FALSE, 0, 0);
};
sendTextureToGPU = (gl, tex, index) => {
gl.activeTexture(gl.TEXTURE0 + index);
gl.bindTexture(gl.TEXTURE_2D, tex);
};
calculateAspectRatio = (img, gl) => {
var cols = img.width;
var rows = img.height;
var imageAspectRatio = cols / rows;
var ele = gl.canvas;
var windowWidth = ele.width;
var windowHeight = ele.height;
var canvasAspectRatio = windowWidth / windowHeight;
var renderableHeight, renderableWidth;
var xStart, yStart;
/// If image's aspect ratio is less than canvas's we fit on height
/// and place the image centrally along width
if(imageAspectRatio < canvasAspectRatio) {
renderableHeight = windowHeight;
renderableWidth = cols * (renderableHeight / rows);
xStart = (windowWidth - renderableWidth) / 2;
yStart = 0;
}
/// If image's aspect ratio is greater than canvas's we fit on width
/// and place the image centrally along height
else if(imageAspectRatio > canvasAspectRatio) {
renderableWidth = windowWidth;
renderableHeight = rows * (renderableWidth / cols);
xStart = 0;
yStart = ( windowHeight - renderableHeight) / 2;
}
///keep aspect ratio
else {
renderableHeight = windowHeight ;
renderableWidth = windowWidth;
xStart = 0;
yStart = 0;
}
return {
y2 : yStart + renderableHeight,
x2 : xStart + renderableWidth,
x1 : xStart,
y1 : yStart
};
};
convertCanvasCoordsToGPUCoords = (canvas, AR) => {
//GPU -> -1, -1, 1, 1
//convert to 0 -> 1
var _0To1 = {
y2 : AR.y2/canvas.height,
x2 : AR.x2/canvas.width,
x1 : AR.x1/canvas.width,
y1 : AR.y1/canvas.height
};
//Convert -1 -> 1
return {
y2 : -1 + _0To1.y2 * 2.0,
x2 : -1 + _0To1.x2 * 2.0,
x1 : -1 + _0To1.x1 * 2.0,
y1 : -1 + _0To1.y1 * 2.0
};
};
//convert -1->+1 to 0.0->1.0
convertVertexToTexCoords = (x1, y1, x2, y2) => {
return {
y2 : (y2 + 1.0)/2.0,
x2 : (x2 + 1.0)/2.0,
x1 : (x1 + 1.0)/2.0,
y1 : (y1 + 1.0)/2.0
};
};
activateTextureByIndex = (gl, program, gpuRef, gpuTextureIndex) => {
gl.useProgram(program);
gl.uniform1i(gl.getUniformLocation(program, gpuRef), gpuTextureIndex);
};
prepareVec4 = (_) => {
return [_.x1, _.y1, 0.0, 1.0,
_.x2, _.y1, 0.0, 1.0,
_.x1, _.y2, 0.0, 1.0,
_.x2, _.y1, 0.0, 1.0,
_.x1, _.y2, 0.0, 1.0,
_.x2, _.y2, 0.0, 1.0];
};
prepareVec2 = (_) => {
return [_.x1, _.y1,
_.x2, _.y1,
_.x1, _.y2,
_.x2, _.y1,
_.x1, _.y2,
_.x2, _.y2];
};
};
</script>
I am able to render a 8 bit histogram in both WebGL1 and WebGL2 using this code. But I need to generate a 16 bit histogram using 16 bit texture.
Here's how am sending the texture to GPU:
var tex = gl.createTexture(); // create empty texture
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(
gl.TEXTURE_2D, // target
0, // mip level
gl.RGBA16UI, // internal format -> gl.RGBA16UI
w, h, // width and height
0, // border
gl.RGBA_INTEGER, //format -> gl.RGBA_INTEGER
gl.UNSIGNED_SHORT, // type -> gl.UNSIGNED_SHORT
img // texture data
);
So, take the working example in mind, I am stuck with few things :
1) How to create a 65536 X 1 framebuffer/texture to keep the 16 bit histogram as WebGL clearly says : WebGL: INVALID_VALUE: texImage2D: width or height out of range. Can we try a 256 x 256 framebuffer? I tried but stuck in point no. 2 below.
2) How to read through the pixels inside the vertex shader in case of 16 bit, below code is for 8-bit data, will it work for 16 bit as well? As I can't debug, so can't say whether it works or not:
<script id="hist-vs" type="not-js">
attribute float pixelId;
uniform vec2 u_resolution;
uniform sampler2D u_texture;
uniform vec4 u_colorMult;
void main() {
// based on an id (0, 1, 2, 3 ...) compute the pixel x, y for the source image
vec2 pixel = vec2(mod(pixelId, u_resolution.x), floor(pixelId / u_resolution.x));
// compute corresponding uv center of that pixel
vec2 uv = (pixel + 0.5) / u_resolution;
// get the pixels but 0 out channels we don't want
vec4 color = texture2D(u_texture, uv) * u_colorMult;
// add up all the channels. Since 3 are zeroed out we'll get just one channel
float colorSum = color.r + color.g + color.b + color.a;
// set the position to be over a single pixel in the 256x1 destination texture
gl_Position = vec4((colorSum * 255.0 + 0.5) / 256.0 * 2.0 - 1.0, 0.5, 0, 1);
gl_PointSize = 1.0;
}
</script>
If you just want answers to your 2 questions then
1) How to create a 65536 X 1 framebuffer/texture to keep the 16 bit histogram as WebGL clearly says : WebGL: INVALID_VALUE: texImage2D: width or height out of range. Can we try a 256 x 256 framebuffer?
yes, you'd make 256x256 texture if you want to know the totals for each of the 65536 possible values
2) How to read through the pixels inside the vertex shader in case of 16 bit, below code is for 8-bit data, will it work for 16 bit as well? As I can't debug, so can't say whether it works or not:
Of course you can debug. You try it and see if the results are correct. If they aren't you look at your code and or the error messages and try to figure out why. That's called debugging. Make a 1x1 texture, call your function, check if the histogram has the correct count of 1 for that 1x1 pixel input by calling gl.readPixels. Then try 2x1 or 2x2.
In any case you can't read gl.RGBA16UI textures with GLSL 1.0 es. You have to use version 300 es so if you actually want to create a separate bucket for all 65536 values then
Here's some a WebGL2 GLSL 3.00 ES shader that will fill out the totals for values from 0 to 65535 in a 256x256 texture
#version 300 es
uniform usampler2D u_texture;
uniform uvec4 u_colorMult;
void main() {
const int mipLevel = 0;
ivec2 size = textureSize(u_texture, mipLevel);
// based on an id (0, 1, 2, 3 ...) compute the pixel x, y for the source image
vec2 pixel = vec2(
gl_VertexID % size.x,
gl_VertexID / size.x);
// get the pixels but 0 out channels we don't want
uvec4 color = texelFetch(u_texture, pixel, mipLevel) * u_colorMult;
// add up all the channels. Since 3 are zeroed out we'll get just one channel
uint colorSum = color.r + color.g + color.b + color.a;
vec2 outputPixel = vec2(
colorSum % 256u,
colorSum / 256u);
// set the position to be over a single pixel in the 256x256 destination texture
gl_Position = vec4((outputPixel + 0.5) / 256.0 * 2.0 - 1.0, 0, 1);
gl_PointSize = 1.0;
}
Example
Notes:
In WebGL2 you don't need pixelID, you can use gl_VertexID so no need to setup any buffers or attributes. Just call
const numPixels = img.width * img.height;
gl.drawArrays(gl.POINTS, 0, numPixels);
You can use textureSize to get the size of a texture so no need to pass it in.
You can use texelFetch to get a single texel(pixel) from a texture. It takes integer pixel coordinates.
To read an unsigned integer texture format like RGBA16UI you have to use a usampler2D otherwise you'll get an error at draw time drawing to use an RGBA16UI texture on a sampler2D (this is how I know you weren't actually using a RGBA16UI texture because you would have gotten an error in the JavaScript console telling you and leading you to change your shader.
You still need to use a floating point texture as the target because the technique used requires blending but blending doesn' work with integer textures (just in case you got the idea to try to use an integer based texture in the framebuffer)
var gl;
var canvas;
var shaderProgram;
var triangleVertexBuffer;
var triangleVertexColorBuffer;
var stripElementBuffer;
var stripVertexBuffer;
//Declare new variables here
function createGLContext(canvas) {
var names = ["webgl", "experimental-webgl"];
var context = null;
for (var i=0; i < names.length; i++) {
try {
context = canvas.getContext(names[i]);
} catch(e) {}
if (context) {
break;
}
}
if (context) {
context.viewportWidth = canvas.width;
context.viewportHeight = canvas.height;
} else {
alert("Failed to create WebGL context!");
}
return context;
}
function loadShaderFromDOM(id) {
var shaderScript = document.getElementById(id);
if (!shaderScript) {
return null;
}
var shaderSource = "";
var currentChild = shaderScript.firstChild;
while (currentChild) {
if (currentChild.nodeType == 3) { // 3 corresponds to TEXT_NODE
shaderSource += currentChild.textContent;
}
currentChild = currentChild.nextSibling;
}
var shader;
if (shaderScript.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (shaderScript.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
return null;
}
gl.shaderSource(shader, shaderSource);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert(gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
function setupShaders() {
vertexShader = loadShaderFromDOM("shader-vs");
fragmentShader = loadShaderFromDOM("shader-fs");
shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("Failed to setup shaders");
}
gl.useProgram(shaderProgram);
shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
shaderProgram.vertexColorAttribute = gl.getAttribLocation(shaderProgram, "aVertexColor");
gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
// For the triangle we want to use per-vertex color so
// the vertexColorAttribute, aVertexColor, in the vertex shader
// is enabled.
// You must enable this attribute here or in draw method before the
//triangle is drawn
gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute);
}
function setupBuffers() {
triangleVertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexBuffer);
var triangleVertices = [
0.0, 0.5, 0.0,
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(triangleVertices), gl.STATIC_DRAW);
triangleVertexBuffer.itemSize = 3;
triangleVertexBuffer.numberOfItems = 3;
// Triangle vertex colours
triangleVertexColorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexColorBuffer);
var colors = [
1.0, 0.0, 0.0, 1.0, //v0
0.0, 1.0, 0.0, 1.0, //v1
0.0, 0.0, 1.0, 1.0 //v2
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
triangleVertexColorBuffer.itemSize = 4;
triangleVertexColorBuffer.numberOfItems = 3;
// Add new items: the followings are newly added items
//hexagon vertices
hexagonVertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, hexagonVertexBuffer);
var hexagonVertices = [
-0.3, 0.6, 0.0, //v0
-0.4, 0.8, 0.0, //v1
-0.6, 0.8, 0.0, //v2
-0.7, 0.6, 0.0, //v3
-0.6, 0.4, 0.0, //v4
-0.4, 0.4, 0.0, //v5
-0.3, 0.6, 0.0, //v6
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(hexagonVertices), gl.STATIC_DRAW);
hexagonVertexBuffer.itemSize = 3;
hexagonVertexBuffer.numberOfItems = 7;
//Triangle strip vertices.
stripVertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, stripVertexBuffer);
var stripVertices = [
-0.5, 0.2, 0.0, //v0
-0.4, 0.0, 0.0, //v1
-0.3, 0.2, 0.0, //v2
-0.2, 0.0, 0.0, //v3
-0.1, 0.2, 0.0, //v4
0.0, 0.0, 0.0, //v5
0.1, 0.2, 0.0, //v6
0.2, 0.0, 0.0, //v7
0.3, 0.2, 0.0, //v8
0.4, 0.0, 0.0, //v9
0.5, 0.2, 0.0, //v10
// Second strip
-0.5, -0.3, 0.0, //v11
-0.4, -0.5, 0.0, //v12
-0.3, -0.3, 0.0, //v13
-0.2, -0.5, 0.0, //v14
-0.1, -0.3, 0.0, //v15
0.0, -0.5, 0.0, //v16
0.1, -0.3, 0.0, //v17
0.2, -0.5, 0.0, //v18
0.3, -0.3, 0.0, //v19
0.4, -0.5, 0.0, //v20
0.5, -0.3, 0.0 //v21
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(stripVertices), gl.STATIC_DRAW);
stripVertexBuffer.itemSize = 3;
stripVertexBuffer.numberOfItems = 22;
// Strip vertex indices
stripElementBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, stripElementBuffer);
var indices = [
0.0, 0.5, 0.0,
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0
// put correct indices here. Use degenerated triangles to link the
// strips together
];
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
stripElementBuffer.numberOfItems = 25;
}
function draw() {
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
gl.clear(gl.COLOR_BUFFER_BIT);
// Draw triangle. No change is made to the last week's code here
// For the triangle we want to use per-vertex color so
// the vertexColorAttribute, aVertexColor, in the vertex shader
// is enabled
// gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute);
// Make vertex buffer "triangleVertexBuffer" the current buffer
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexBuffer);
// Link the current buffer to the attribute "aVertexPosition" in
// the vertex shader
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexBuffer.itemSize, gl.FLOAT, false, 0, 0);
// Make color buffer "triangleVertexColorBuffer" the current buffer
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexColorBuffer);
// Link the current buffer to the attribute "aVertexColor" in
// the vertex shader
gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, triangleVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, triangleVertexBuffer.numberOfItems);
gl.drawElements(gl.TRIANGLE_STRIP, 0, stripVertexBuffer.numberOfItems, 25);
// Draw the newly added items
}
function startup() {
canvas = document.getElementById("myGLCanvas");
gl = createGLContext(canvas);
setupShaders();
setupBuffers();
gl.clearColor(1.0, 1.0, 1.0, 1.0);
draw();
}
startup();
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
attribute vec4 aVertexColor;
varying vec4 vColor;
void main() {
vColor = aVertexColor;
gl_Position = vec4(aVertexPosition, 1.0);
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec4 vColor;
void main() {
gl_FragColor = vColor;
}
</script>
<canvas id="myGLCanvas" width="250" height="250"></canvas>
Hey guys. New to WEBGL, trying to draw the triangle strip but no clue on how to approach this.
what I know:
When drawing with gl.TRIANGLE_STRIP mode, the order of the vertex coordinates or indices in the buffer is important.
Instead of specifying triangles by the programmer, WebGL constructs triangles automatically.
It reads vertex coordinate buffer or index buffer and use them in the following order to construct triangles:
these 2 lines in the code don't make any sense
gl.drawArrays(gl.TRIANGLES, 0, triangleVertexBuffer.numberOfItems);
gl.drawElements(gl.TRIANGLE_STRIP, 0, stripVertexBuffer.numberOfItems, 25);
It generally makes no sense to call draw twice without changing something in between since to draw 2 things requires setting up different data.
Further, the 2nd line is just plain wrong
gl.drawElements(gl.TRIANGLE_STRIP, 0, stripVertexBuffer.numberOfItems, 25);
If you had opened your JavaScript Console you would have seen an error something like
There are several issues
The code is passing a bad value to the type parameter of gl.drawElements
Type parameter to gl.drawElements is the type of data in the current ELEMENT_ARRAY_BUFFER.
The 2nd parameter is the count.
It's passing the number of vertices (stripVertexBuffer.numberOfItems) not the number of indices (stripElementBuffer.numberOfItems)
It should be something like this
{
const primitive = gl.TRIANGLE_STRIP;
const count = stripElementBuffer.numberOfItems;
const offset = 0;
const indexType = gl.UNSIGNED_SHORT;
gl.drawElements(primitive, count, indexType, offset);
}
Fixing that though isn't enough because the code does not actually put indices in the index buffer. That code
// Strip vertex indices
stripElementBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, stripElementBuffer);
var indices = [
0.0, 0.5, 0.0,
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0
// put correct indices here. Use degenerated triangles to link the
// strips together
];
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
stripElementBuffer.numberOfItems = 25;
also makes no sense. Indices are unsigned integer values from 0 to N where N is the one less than the number of vertices bound to the attributes. Further only 9 values are put in but the code sets stripElementBuffer.numberOfItems to 25 .. ?
Then, on top of all of that the code is not setting up the attributes for using the strip vertices.
To draw multiple things in WebGL works like this
for each thing you want to draw
gl.useProgram(theProgramYouWantToDrawWith);
// setup attributes
for each attribute
gl.bindBuffer(gl.ARRAY_BUFFER, bufferWithDataForAttribute);
gl.enableVertexAttribArray(attribLocation);
gl.vertexAttribPointer(attribLocation, ... how to get data out of buffer ...)
// if using indices setup the ELEMENT_ARRAY_BUFFER
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufferWithIndices);
// setup textures
for each texture you're going to draw with
gl.activeTexture(gl.TEXTURE0 + unit);
gl.bindTexture(gl.TEXTURE_??, someTexture);
// setup uniforms
for each uniform
gl.uniformXXX(...)
// draw
gl.drawXXX (either gl.drawArrays or gl.drawElements)
Before you can even attempt degenerate triangle strips you need to fix your code to follow that pattern. Also here's some other tutorials you might find helpful
I have a framebuffer to which I rendered my scene and now I want to render this to a "fullscreen" quad. How can I set my camera and what should I put in my vertex shader in order to render the framebuffer's texture to the whole screen.
I've tried creating a fullscreen quad like this
var gl = this.gl;
var quad_vertex_buffer = gl.createBuffer();
var quad_vertex_buffer_data = new Float32Array([
-1.0, -1.0, 0.0,
1.0, -1.0, 0.0,
-1.0, 1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, -1.0, 0.0,
1.0, 1.0, 0.0]);
gl.bufferData(quad_vertex_buffer, quad_vertex_buffer_data, gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, quad_vertex_buffer);
gl.vertexAttribPointer(this.shaderProgram.vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
//gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
gl.drawArrays(gl.TRIANGLES,0, 6);
but it still renders everything black. Any ideeas or examples/tutorials I can follow?
It is really not a big deal, once you get how to use Vertex Buffers and shaders. Then you can easily write an utility function to do it. Here is one I normally use, if you are looking for a reference:
drawFullScreenQuad : function(shaderProgram) {
if (!shaderProgram)
{
utils.warning("Missing the shader program!");
return;
}
// Only created once
if (this.screenQuadVBO == null)
{
var verts = [
// First triangle:
1.0, 1.0,
-1.0, 1.0,
-1.0, -1.0,
// Second triangle:
-1.0, -1.0,
1.0, -1.0,
1.0, 1.0
];
this.screenQuadVBO = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.screenQuadVBO);
this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(verts), this.gl.STATIC_DRAW);
}
// Bind:
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.screenQuadVBO);
this.gl.enableVertexAttribArray(shaderProgram.vertexAttributes.vertexPositionNDC);
this.gl.vertexAttribPointer(shaderProgram.vertexAttributes.vertexPositionNDC, 2, this.gl.FLOAT, false, 0, 0);
// Draw 6 vertexes => 2 triangles:
this.gl.drawArrays(this.gl.TRIANGLES, 0, 6);
// Cleanup:
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
},
Then you can go fancy like I did and compute the texture coordinates on-the-fly in the vertex shader:
Vertex Shader:
precision lowp float;
// xy = vertex position in normalized device coordinates ([-1,+1] range).
attribute vec2 vertexPositionNDC;
varying vec2 vTexCoords;
const vec2 scale = vec2(0.5, 0.5);
void main()
{
vTexCoords = vertexPositionNDC * scale + scale; // scale vertex attribute to [0,1] range
gl_Position = vec4(vertexPositionNDC, 0.0, 1.0);
}
Fragment Shader:
precision mediump float;
uniform sampler2D colorMap;
varying vec2 vTexCoords;
void main()
{
gl_FragColor = texture2D(colorMap, vTexCoords);
}
The important point to note are the vertexes in Normalized Device Coordinates (NDC), so you just pass the vertexes in the [-1,1] range and forward them directly to gl_Position without the need to multiply by a projection matrix.
Why do you need a camera to render a fullscreen quad? Rendering a fullscreen quad is pretty much the simplest thing you can do in WebGL. Given the buffer you already setup just use a shader like this
vertex shader:
attribute vec4 v_position;
void main() {
gl_Position = v_position;
}
fragment shader:
precision mediump float;
void main() {
gl_FragColor = vec4(0,1,0,1); // green
}
You should get a green screen.
There's a few bugs in the code though. You need to bind the buffer before you try up put data in it. You need to reference the buffer through the bind point, not the buffer object.
old (incorrect)
gl.bufferData(quad_vertex_buffer, quad_vertex_buffer_data, gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, quad_vertex_buffer);
new (correct)
gl.bindBuffer(gl.ARRAY_BUFFER, quad_vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, quad_vertex_buffer_data, gl.STATIC_DRAW);
Example:
const vs = `
attribute vec4 v_position;
void main() {
gl_Position = v_position;
}
`;
const fs = `
precision mediump float;
void main() {
gl_FragColor = vec4(0,1,0,1); // green
}
`;
var gl = document.querySelector("canvas").getContext("webgl");
var shader_program = twgl.createProgram(gl, [vs, fs]);
gl.useProgram(shader_program);
var vertexPositionAttribute = gl.getAttribLocation(shader_program, "v_position");
var quad_vertex_buffer = gl.createBuffer();
var quad_vertex_buffer_data = new Float32Array([
-1.0, -1.0, 0.0,
1.0, -1.0, 0.0,
-1.0, 1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, -1.0, 0.0,
1.0, 1.0, 0.0]);
gl.bindBuffer(gl.ARRAY_BUFFER, quad_vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, quad_vertex_buffer_data, gl.STATIC_DRAW);
gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(vertexPositionAttribute)
gl.drawArrays(gl.TRIANGLES, 0, 6);
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas></canvas>
Another example with a simple pattern to show it's working
const vs = `
attribute vec4 v_position;
void main() {
gl_Position = v_position;
}
`;
const fs = `
precision mediump float;
void main() {
gl_FragColor = vec4(fract(gl_FragCoord.xy / vec2(16., 32.)),0,1);
}
`;
var gl = document.querySelector("canvas").getContext("webgl");
var shader_program = twgl.createProgram(gl, [vs, fs]);
gl.useProgram(shader_program);
var vertexPositionAttribute = gl.getAttribLocation(shader_program, "v_position");
var quad_vertex_buffer = gl.createBuffer();
var quad_vertex_buffer_data = new Float32Array([
-1.0, -1.0, 0.0,
1.0, -1.0, 0.0,
-1.0, 1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, -1.0, 0.0,
1.0, 1.0, 0.0]);
gl.bindBuffer(gl.ARRAY_BUFFER, quad_vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, quad_vertex_buffer_data, gl.STATIC_DRAW);
gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(vertexPositionAttribute)
gl.drawArrays(gl.TRIANGLES, 0, 6);
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas></canvas>
I'd suggest you read some WebGL tutorials.