Using WebGL I am trying to draw a simple triangle from scratch.
I have experience writing openGL applications in C++, and have looked at the webGL reference card to translate my code.
However, I am having difficulty debugging the application.
The particular error message I am getting is:
GL ERROR :GL_INVALID_OPERATION : glDrawArrays: attempt to access out of range vertices in attribute 0
The entire code is here: https://github.com/gheshu/webGL_experiments
The vertex data is laid out as 3 vectors of 3 floats.
Three attributes exist: position, normal, and color, and should be bound on indices 0, 1, 2.
some important snippets:
mesh class:
class Mesh{
constructor(){
this.num_vertices = 0;
this.vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 4*3*3, 0);
gl.enableVertexAttribArray(1);
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 4*3*3, 4*3);
gl.enableVertexAttribArray(2);
gl.vertexAttribPointer(2, 3, gl.FLOAT, false, 4*3*3, 4*3*2);
}
upload(buffer){
console.log(buffer);
gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo);
gl.bufferData(gl.ARRAY_BUFFER, buffer, gl.STATIC_DRAW);
this.num_vertices = buffer.length / 9;
}
draw(){
gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo);
gl.drawArrays(gl.TRIANGLES, 0, this.num_vertices);
}
destroy(){
gl.deleteBuffer(this.vbo);
}
}
program class:
class GLProgram{
constructor(vertShader, fragShader){
this.prog = gl.createProgram();
gl.attachShader(this.prog, vertShader.id);
gl.attachShader(this.prog, fragShader.id);
gl.bindAttribLocation(this.prog, 0, "position");
gl.bindAttribLocation(this.prog, 1, "normal");
gl.bindAttribLocation(this.prog, 2, "color");
gl.linkProgram(this.prog);
var log = gl.getProgramInfoLog(this.prog);
if(log.length){
console.log();
}
gl.detachShader(this.prog, vertShader.id);
vertShader.destroy();
gl.detachShader(this.prog, fragShader.id);
fragShader.destroy();
}
bind(){
gl.useProgram(this.prog);
}
destroy(){
gl.deleteProgram(this.prog);
}
}
vertex shader:
attribute vec3 position;
attribute vec3 normal;
attribute vec3 color;
varying vec3 vColor;
void main(){
gl_Position = vec4(position, 1.0);
vColor = color;
}
fragment shader:
precision mediump float;
varying vec3 vColor;
void main(){
gl_FragColor = vec4(vColor, 1.0);
}
I would greatly appreciate any help or tips you may have in fixing this issue.
At the bottom of your draw.js file, you destroy mesh and prog:
mesh.destroy();
prog.destroy();
In JavaScript window.requestAnimationFrame(onFrame); will actually invoke onFrame after those destroy methods are called. So by the time onFrame is executed both mesh and prog don't exist. I would recommend reading more about requestAnimationFrame so you can destroy these later (i.e. after your animation loop has stopped running).
You can verify the behavior just by removing those destroy lines and it will render fine.
Related
I'm trying to create a vertex color change feature using varying which is intended to give a result of gradient appearance of each vertex like this
Meanwhile after inserting such a color combination like this
...
this.vertices[0].setColor([255,0,0,1]); // red
this.vertices[1].setColor([255,0,0,1]); // red
this.vertices[2].setColor([255,0,0,1]); // red
this.vertices[3].setColor([255,255,0,1]); // yellow
...
The result is like this
It is supposed to be intended as a yellow gradient only on bottom left side of the shape, but instead, a half triangle was created.
Here is my shader C source code
const vSource = `
attribute vec4 vPosition;
attribute vec4 vColor;
varying vec4 fColor;
void main() {
gl_Position = vPosition;
fColor = vColor;
}
`;
const fSource = `
precision mediump float;
varying vec4 fColor;
void main() {
gl_FragColor = fColor;
}
`;
And here is the implementation of render function
render = (gl, program, vBuffer, cBuffer) => {
const vertices = [];
const colors = [];
for (let j = 0; j < this.vertices.length; j++) {
vertices.push(this.vertices[j].coordinate);
colors.push(this.vertices[j].color);
}
gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
gl.bufferData(gl.ARRAY_BUFFER, flatten(vertices), gl.STATIC_DRAW);
const vPosition = gl.getAttribLocation(program, "vPosition");
gl.vertexAttribPointer(vPosition,2,gl.FLOAT,false,0,0);
gl.enableVertexAttribArray(vPosition);
gl.bindBuffer(gl.ARRAY_BUFFER, cBuffer);
gl.bufferData(gl.ARRAY_BUFFER, flatten(colors), gl.STATIC_DRAW);
const vColor = gl.getAttribLocation(program, "vColor");
gl.vertexAttribPointer(vColor,4,gl.FLOAT,false,0,0);
gl.enableVertexAttribArray(vColor);
if (this.shape == "line") {
gl.drawArrays(gl.LINES, 0, vertices.length);
} else {
gl.drawArrays(gl.TRIANGLE_FAN, 0, vertices.length); // in this case, it is going to be TRIANGLE_FAN primitive
}
};
Would be thankful if you guys can catch something odd that might result in the bug.
P.S.: I have implemented shape creation such as polygon, transformation, and such, the coordinate-related action seems perfect, I just couldn't comprehend the problem that caused the vertices color to be like this.
The values of the color channels are floating point numbers in range [0.0, 1.0], but not integral numbers in range [0, 255]. e.g.:
this.vertices[0].setColor([1,0,0,1]); // red
this.vertices[1].setColor([0,1,0,1]); // green
this.vertices[2].setColor([1,0,1,1]); // blue
this.vertices[3].setColor([1,1,0,1]); // yellow
I can render a single 2d particle using sdf like this:
void renderShape(out vec3 col, vec2 p) {
vec2 translation = vec2(0.0, 0.5);
// some math
col = // some color.
}
translation variable controls where to render this particle.
Now I calculate particle positions on the cpu, and need to pass these positions to the shaders and render each particle on different positions. How do I do that?
Currently only data I pass is the vertices of a quad like this:
let positions = [
-1, 1,
-1, -1,
1, -1,
-1, 1,
1,-1,
1, 1
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
// on render
gl.bindVertexArray(vao);
gl.drawArrays(gl.TRIANGLES, 0, 6);
vertex shader:
#version 300 es
precision mediump float;
in vec2 a_position;
uniform vec2 u_resolution;
void main() {
gl_Position = vec4(a_position, 0, 1);
}
I want particles to combine with each other (using union operation) so I can't render them individually like with gl.POINTS
I have a small program I'm working on to render sprites with 2D transformations, link here. My problem is that I'm trying to render a 100px by 100px square, but it's being stretched into a rectangle. I have absolutely zero idea what the offending code is, but here's some relevant pieces.
const position = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, position)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
-w/2, h/2,
w/2, h/2,
-w/2, -h/2,
w/2, -h/2
]), gl.STATIC_DRAW)
gl.bindBuffer(gl.ARRAY_BUFFER, position)
gl.vertexAttribPointer(attrib.vertexPosition,
2, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(attrib.vertex)
gl.uniformMatrix2fv(uniform.transformMatrix, false, transform)
gl.uniform2f(uniform.translation, x+w/2, y+h/2)
gl.uniform2f(uniform.screenRes, gl.canvas.width, gl.canvas.height)
Vertex shader:
attribute vec2 aVertexPosition;
attribute vec2 aTextureCoord;
uniform mat2 uTransformMatrix;
uniform vec2 uTranslation;
uniform vec2 uScreenRes;
varying vec2 vTextureCoord;
void main() {
gl_Position = vec4(2.0 * (uTransformMatrix * aVertexPosition + uTranslation) / uScreenRes - 1.0, 1.0, 1.0);
vTextureCoord = aTextureCoord;
}
Feel free to toy around with the variables in the pen, especially the canvas dimensions; when you scale a dimension down, that dimension of the sprite scales up, and vice versa.
P.S. I'm not concerned about how the texture is inverted. I'm shelving that for later.
Your code is correct, however you forgot to specify the viewport.
Add this right before you make any draw calls (in your case, ideally after gl.clear())
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height)
The WebGLRenderingContext.viewport() method of the WebGL API sets the
viewport, which specifies the affine transformation of x and y from
normalized device coordinates to window coordinates.
I see a weird flickering of some rendered geometry Chrome on Windows 10 x64 with nVidia chips. I've also tested in in Chrome for Linux, Firefox for both platforms, Android, and with Intel GPU. It works fine everywhere, except the one platform mentioned.
Minimal example looks like this:
Vertex shader:
precision mediump float;
smooth out vec2 pointCoord;
const vec2 vertexCoord[] = vec2[](
vec2(0.0, 0.0),
vec2(1.0, 0.0),
vec2(1.0, 1.0),
vec2(0.0, 0.0),
vec2(1.0, 1.0),
vec2(0.0, 1.0)
);
void main()
{
gl_Position = vec4(vertexCoord[gl_VertexID], 0.0, 1.0);
pointCoord = vertexCoord[gl_VertexID];
}
Fragment shader:
precision mediump float;
out vec4 outFragColor;
smooth in vec2 pointCoord;
void main()
{
outFragColor = vec4(pointCoord, 0.0, 1.0);
}
GL state:
gl.clearColor(0, 0, 0, 1);
gl.disable(gl.BLEND);
gl.disable(gl.DEPTH_TEST);
gl.depthMask(gl.FALSE);
gl.stencilMask(gl.FALSE);
Rendering code:
var mainLoop = function()
{
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 6);
window.requestAnimationFrame(mainLoop);
}
window.requestAnimationFrame(mainLoop);
Working web page with full code is available here.
What I see on affected platforms:
I tried webgl1 context with a vertex attribute, as there is no gl_VertexID variable in glsl 100es, and it produces the same result. I also tried to add glFinish or glFlush at the end of main loop. It reduces flicker frequency, but does not solve the problem.
What is the problem? Is there any undefined behavior in this code, so it works different ways on different platforms? Or is it a bug in Chrome or Angle?
UPD:
Following Nabr's answer, I added a VBO bound to enabled vertex attribute, and constructed another example which flickers on my machines.
UPD2:
Reported a bug to Chromium project:
https://bugs.chromium.org/p/chromium/issues/detail?id=836788
no, flicker here. The quad isn't showing at all.
Chrome 65
Windows 10
NVidia GTX 1050
i follow this since months, the implementation from browser (release) to browser and it's platform is different. you can't have a bufferless "shape" crossplatform, my experience.
you on the save side if you bindBuffer, see here:
https://stackoverflow.com/a/44448514
// added a buffer array
// note: commented some consolelogs out, it looks nicer on stackoverflow
var canvas = document.getElementById("canvas");
var gl = canvas.getContext("webgl2");
if (!gl) {
console.log("Could not get context.");
throw new Error();
}
var checkError = function() {
if (gl.getError() != gl.NO_ERROR) {
console.log("Webgl error.");
throw new Error();
}
}
// GL setup
gl.clearColor(0, 0, 0, 1);
gl.disable(gl.BLEND);
gl.disable(gl.DEPTH_TEST);
gl.depthMask(gl.FALSE);
gl.stencilMask(gl.FALSE);
// Shader
var vertexSource = document.getElementById("vertexShader").text;
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexSource);
gl.compileShader(vertexShader);
// console.log("Vertex log: " + gl.getShaderInfoLog(vertexShader));
var fragmentSource = document.getElementById("fragmentShader").text;
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentSource);
gl.compileShader(fragmentShader);
// console.log("Fragment log: " + gl.getShaderInfoLog(fragmentShader));
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
// console.log("Link log: " + gl.getProgramInfoLog(program));
checkError();
gl.useProgram(program);
var time_loc = gl.getUniformLocation(program, "time");
// CHANGED
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 0.0,
1.0, 1.0,
0.0, 1.0
]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
gl.bindVertexArray(null);
var tick = 0;
var mainLoop = function(tick) {
tick *= 0.001;
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 6);
gl.uniform1f(time_loc, tick);
window.requestAnimationFrame(mainLoop);
}
window.requestAnimationFrame(mainLoop);
<style>canvas {
width: 100%;
height: 500px;
}
</style>
<script id="vertexShader" type="x-shader/x-vertex">#version 300 es
precision mediump float;
in vec2 vertexCoord;
uniform float time;
smooth out vec2 pointCoord;
void main() {
gl_Position = vec4(vec2[](vertexCoord)[ gl_VertexID ], 0.0, 1.0);
// for testig purposes suffix likely to fail in the past on firefox 56 windows 7
pointCoord = vec3[3u](vec3(1.f, 1.f*sin(time), 0.f), vec3(0.0), vec3(0.0))[gl_VertexID%int(mod(time,3.))].xy;
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">#version 300 es
precision mediump float;
out vec4 outFragColor;
smooth in vec2 pointCoord; void main() {
outFragColor = vec4(pointCoord,0.0, 1.0);
}
</script>
<canvas id="canvas" style="width:100vw;height:100vh;display:block"></canvas>
My project has similar problem, later I found if I comment the gl.depthMask, there would be no flickering.
My final solution is close the development tool, the flicker will gone then.
I intend to create a simple photo editor in JS. My main question is, is it possible to create filters that render in real-time? For example, adjusting brightness and saturation. All I need is a 2D image where I can apply filters using the GPU.
All the tutorials I've read are very complex and don't really explain what the API mean. Please point me in the right direction. Thanks.
I was going to write a tutorial and post it on my blog but I don't know when I'll have time to finish so here's what I have Here's a more detailed set of posts on my blog.
WebGL is actually a rasterization library. I takes in attributes (streams of data), uniforms (variables) and expects you to provide "clip space" coordinates in 2d and color data for pixels.
Here's a simple example of 2d in WebGL (some details left out)
// Get A WebGL context
var gl = canvas.getContext("experimental-webgl");
// setup GLSL program
vertexShader = createShaderFromScriptElement(gl, "2d-vertex-shader");
fragmentShader = createShaderFromScriptElement(gl, "2d-fragment-shader");
program = createProgram(gl, vertexShader, fragmentShader);
gl.useProgram(program);
// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");
// Create a buffer and put a single clipspace rectangle in
// it (2 triangles)
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
-1.0, -1.0,
1.0, -1.0,
-1.0, 1.0,
-1.0, 1.0,
1.0, -1.0,
1.0, 1.0]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
// draw
gl.drawArrays(gl.TRIANGLES, 0, 6);
Here's the 2 shaders
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
void main() {
gl_Position = vec4(a_position, 0, 1);
}
</script>
<script id="2d-fragment-shader" type="x-shader/x-fragment">
void main() {
gl_FragColor = vec4(0,1,0,1); // green
}
</script>
This will draw a green rectangle the entire size of the canvas.
In WebGL it's your responsibility to provide a vertex shader that provides clipspace coordinates. Clipspace coordinates always go from -1 to +1 regardless of the size of the canvas. If you want 3d it's up to you to supply shaders that convert from 3d to 2d because WebGL is only a rasterization API
In one simple example, if you want to work in pixels you could pass in a rectangle that uses pixels instead of clip space coordinates and convert to clip space in the shader
For example:
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
uniform vec2 u_resolution;
void main() {
// convert the rectangle from pixels to 0.0 to 1.0
vec2 zeroToOne = a_position / u_resolution;
// convert from 0->1 to 0->2
vec2 zeroToTwo = zeroToOne * 2.0;
// convert from 0->2 to -1->+1 (clipspace)
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace, 0, 1);
}
</script>
Now we can draw rectangles by changing the data we supply
// set the resolution
var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
// setup a rectangle from 10,20 to 80,30 in pixels
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
10, 20,
80, 20,
10, 30,
10, 30,
80, 20,
80, 30]), gl.STATIC_DRAW);
You'll notice WebGL considers the bottom right corner to be 0,0. To get it to be the more traditional top right corner used for 2d graphics we just flip the y coordinate.
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
You want to manipulate images you need to pass in textures. In the same way the size of the canvas is represented by clipspace coordinates textures are are referenced by texture coordinates that go from 0 to 1.
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
attribute vec2 a_texCoord;
uniform vec2 u_resolution;
varying vec2 v_texCoord;
void main() {
// convert the rectangle from pixels to 0.0 to 1.0
vec2 zeroToOne = a_position / u_resolution;
// convert from 0->1 to 0->2
vec2 zeroToTwo = zeroToOne * 2.0;
// convert from 0->2 to -1->+1 (clipspace)
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace, 0, 1);
// pass the texCoord to the fragment shader
// The GPU will interpolate this value between points.
v_texCoord = a_texCoord;
}
</script>
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision float mediump;
// our texture
uniform sampler2D u_image;
// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;
void main() {
gl_FragColor = texture2D(u_image, v_texCoord);
}
</script>
To draw an image requires loading the image and since that happen asynchronously we need to change our code a little. Take all the code we had and put it in a function called "render"
var image = new Image();
image.src = "http://someimage/on/our/server"; // MUST BE SAME DOMAIN!!!
image.onload = function() {
render();
}
function render() {
...
// all the code we had before except gl.draw
// look up where the vertex data needs to go.
var texCoordLocation = gl.getAttribLocation(program, "a_texCoord");
// provide texture coordinates for the rectangle.
var texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
1.0, 1.0,
0.0, 1.0,
0.0, 0.0,
1.0, 1.0,
0.0, 0.0,
1.0, 0.0]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(texCoordLocation);
gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
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_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.draw(...)
If you want to do image processing you just change your shader. Example, Swap red and blue
void main() {
gl_FragColor = texture2D(u_image, v_texCoord).bgra;
}
Or blend with the pixels next to it.
uniform vec2 u_textureSize;
void main() {
vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;
gl_FragColor = (texture2D(u_image, v_texCoord) +
texture2D(u_image, v_texCoord + vec2(onePixel.x, 0.0)) +
texture2D(u_image, v_texCoord + vec2(-onePixel.x, 0.0))) / 3.0;
}
And we have to pass in the size of the texture
var textureSizeLocation = gl.getUniformLocation(program, "u_textureSize");
...
gl.uniform2f(textureSizeLocation, image.width, image.height);
Etc... Click the last link below for a convolution sample.
Here are working versions with a slightly different progression
Draw Rect in Clip Space
Draw Rect in Pixels
Draw Rect with origin at top left
Draw a bunch of rects in different colors
Draw an image
Draw an image red and blue swapped
Draw an image with left and right pixels averaged
Draw an image with a 3x3 convolution
Draw an image with multiple effects
You can make a custom pixel shader for each operation you're intending to use. Just learn some GLSL and follow the "Learning WebGL" tutorials to get a grasp of basic WebGL.
You can render your image with the shader modifying the parameters you can include to control the different visual styles and then when the user clicks "ok" you can read back the pixels to store it as your current image.
Just remember to avoid cross domain images, because that will disable the reading back of pixels.
Also, check the quick reference card (PDF) for quick info on shader operations.
Just try glfx ( http://evanw.github.com/glfx.js/ )
I think it is exactly what you need.
You can use set of predefined shaders or easily add yours ;)
enjoy! It is very easy with glfx!
<script src="glfx.js"></script>
<script>
window.onload = function() {
// try to create a WebGL canvas (will fail if WebGL isn't supported)
try {
var canvas = fx.canvas();
} catch (e) {
alert(e);
return;
}
// convert the image to a texture
var image = document.getElementById('image');
var texture = canvas.texture(image);
// apply the ink filter
canvas.draw(texture).ink(0.25).update();
// replace the image with the canvas
image.parentNode.insertBefore(canvas, image);
image.parentNode.removeChild(image);
};
</script>
<img id="image" src="image.jpg">