I've been trying out some WebGL but there's a bug I cannot seem to find out how to fix.
Currently I have the following setup:
I have around 100 triangles which all have a position and are being drawn by a single gl.drawArrays function. To have them drawn in the correct order I used gl.enable(gl.DEPTH_TEST); which gave the correct result.
The problem I have now is that if I update the gl_Position of the triangles in the vertex shader the updated Z value is not being used in the depth test. The result is that a triangle with a gl_Position.z of 1 can be drawn on top of a triangle with a gl_Position.z of 10, which is not exactly what I want..
What have I tried?
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.GEQUAL);
with
gl.clear(gl.DEPTH_BUFFER_BIT);
gl.clearDepth(0);
gl.drawArrays(gl.TRIANGLES, 0, verticesCount);
in the render function.
The following code is used to create the buffer:
gl.bindBuffer(gl.ARRAY_BUFFER, dataBuffer);
gl.bufferData(gl.ARRAY_BUFFER, positionBufferData, gl.STATIC_DRAW);
const positionLocation = gl.getAttribLocation(program, 'position');
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, false, 0, 0);
The triangles with a higher z value are much bigger in size (due to the perspective) but small triangles still appear over it (due to the render order).
In the fragment shader I've used gl_fragCoord.z to see if that was correct and smaller triangles (further away) received a higher alpha than bigger ones (up close).
What could be the cause of the weird drawing behaviour?
Depth in clipspace goes from -1 to 1. Depth written to the depth buffer goes from 0 to 1. You're clearing to 1. There is no depth value > 1 so the only things you should see drawn are at gl_Position.z = 1. Anything less than 1 will fail the test gl.depthFunc(gl.GEQUAL);. Anything > 1 will be clipped. Only 1 is both in the depth range and Greater than or Equal to 1
The example below draws smaller to larger rectangles with different z values. The red is standard gl.depthFunc(gl.LESS) with depth cleared to 1. The green is gl.depthFunc(gl.GEQUAL) with depth cleared to 0. The blue is gl.depthFunc(gl.GEQUAL) with depth cleared to 1. Notice blue only draws the single rectangle at gl_Position.z = 1 because all other rectangles fail the test since they are at Z < 1.
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const vs = `
attribute vec4 position;
varying vec4 v_position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
v_position = abs(position);
}
`;
const fs = `
precision mediump float;
varying vec4 v_position;
uniform vec4 color;
void main() {
gl_FragColor = vec4(1. - v_position.xxx, 1) * color;
}
`;
// compiles shaders, links program, looks up attributes
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// calls gl.createBuffer, gl.bindBindbuffer, gl.bufferData for each array
const z0To1BufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: [
...makeQuad( .2, 0.00),
...makeQuad( .4, .25),
...makeQuad( .6, .50),
...makeQuad( .8, .75),
...makeQuad(1.0, 1.00),
],
});
const z1To0BufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: [
...makeQuad(.2, 1.00),
...makeQuad(.4, .75),
...makeQuad(.6, .50),
...makeQuad(.8, .25),
...makeQuad(1., 0.00),
],
});
function makeQuad(xy, z) {
return [
-xy, -xy, z,
xy, -xy, z,
-xy, xy, z,
-xy, xy, z,
xy, -xy, z,
xy, xy, z,
];
}
gl.useProgram(programInfo.program);
gl.enable(gl.DEPTH_TEST);
gl.clearDepth(1);
gl.clear(gl.DEPTH_BUFFER_BIT);
gl.depthFunc(gl.LESS);
drawRects(-0.66, z0To1BufferInfo, [1, 0, 0, 1]);
gl.clearDepth(0);
gl.clear(gl.DEPTH_BUFFER_BIT);
gl.depthFunc(gl.GEQUAL);
drawRects(0, z1To0BufferInfo, [0, 1, 0, 1]);
gl.clearDepth(1);
gl.clear(gl.DEPTH_BUFFER_BIT);
gl.depthFunc(gl.GEQUAL);
drawRects(0.66, z1To0BufferInfo, [0, 0, 1, 1]);
function drawRects(xoffset, bufferInfo, color) {
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
let mat = m4.translation([xoffset, 0, 0]);
mat = m4.scale(mat, [.3, .5, 1]);
// calls gl.uniformXXX
twgl.setUniforms(programInfo, {
color: color,
matrix: mat,
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
}
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas></canvas>
<pre>
red : depthFunc: LESS, clearDepth: 1
green: depthFunc: GEQUAL, clearDepth: 0
blue : depthFunc: GEQUAL, clearDepth: 1
</pre>
Related
I have a fragment shader that can draw an arc based on a set of parameters. The idea was to make the shader resolution independent, so I pass the center of the arc and the bounding radii as pixel values on the screen. You can then just render the shader by setting your vertex positions in the shape of a square. This is the shader:
precision mediump float;
#define PI 3.14159265359
#define _2_PI 6.28318530718
#define PI_2 1.57079632679
// inputs
vec2 center = u_resolution / 2.;
vec2 R = vec2( 100., 80. );
float ang1 = 1.0 * PI;
float ang2 = 0.8 * PI;
vec3 color = vec3( 0., 1.0, 0. );
// prog vars
uniform vec2 u_resolution;
float smOOth = 1.3;
vec3 bkgd = vec3( 0.0 ); // will be a sampler
void main () {
// get the dist from the current pixel to the coord.
float r = distance( gl_FragCoord.xy, center );
if ( r < R.x && r > R.y ) {
// If we are in the radius, do some trig to find the angle and normalize
// to
float theta = -( atan( gl_FragCoord.y - center.y,
center.x - gl_FragCoord.x ) ) + PI;
// This is to make sure the angles are clipped at 2 pi, but if you pass
// the values already clipped, then you can safely delete this and make
// the code more efficinent.
ang1 = mod( ang1, _2_PI );
ang2 = mod( ang2, _2_PI );
float angSum = ang1 + ang2;
bool thetaCond;
vec2 thBound; // short for theta bounds: used to calculate smoothing
// at the edges of the circle.
if ( angSum > _2_PI ) {
thBound = vec2( ang2, angSum - _2_PI );
thetaCond = ( theta > ang2 && theta < _2_PI ) ||
( theta < thetaBounds.y );
} else {
thBound = vec2( ang2, angSum );
thetaCond = theta > ang2 && theta < angSum;
}
if ( thetaCond ) {
float angOpMult = 10000. / ( R.x - R.y ) / smOOth;
float opacity = smoothstep( 0.0, 1.0, ( R.x - r ) / smOOth ) -
smoothstep( 1.0, 0.0, ( r - R.y ) / smOOth ) -
smoothstep( 1.0, 0.0, ( theta - thBound.x )
* angOpMult ) -
smoothstep( 1.0, 0.0, ( thBound.y - theta )
* angOpMult );
gl_FragColor = vec4( mix( bkgd, color, opacity ), 1.0 );
} else
discard;
} else
discard;
}
I figured this way of drawing a circle would yield better quality circles and be less hassle than loading a bunch of vertices and drawing triangle fans, even though it probably isn't as efficient. This works fine, but I don't just want to draw one fixed circle. I want to draw any circle I would want on the screen. So I had an idea to set the 'inputs' to varyings and pass a buffer with parameters to each of the vertices of a given bounding square. So my vertex shader looks like this:
attribute vec2 a_square;
attribute vec2 a_center;
attribute vec2 a_R;
attribute float a_ang1;
attribute float a_ang2;
attribute vec3 a_color;
varying vec2 center;
varying vec2 R;
varying float ang1;
varying float ang2;
varying vec3 color;
void main () {
gl_Position = vec4( a_square, 0.0, 1.0 );
center = a_center;
R = a_R;
ang1 = a_ang1;
ang2 = a_ang2;
color = a_color;
}
'a_square' is just the vertex for the bounding square that the circle would sit in.
Next, I define a buffer for the inputs for one test circle (in JS). One of the problems with doing it this way is that the circle parameters have to be repeated for each vertex, and for a box, this means four times. 'pw' and 'ph' are the width and height of the canvas, respectively.
var circleData = new Float32Array( [
pw / 2, ph / 2,
440, 280,
Math.PI * 1.2, Math.PI * 0.2,
1000, 0, 0,
pw/2,ph/2,440,280,Math.PI*1.2,Math.PI*0.2,1000,0,0,
pw/2,ph/2,440,280,Math.PI*1.2,Math.PI*0.2,1000,0,0,
pw/2,ph/2,440,280,Math.PI*1.2,Math.PI*0.2,1000,0,0,
] );
Then I simply load my data into a gl buffer (circleBuffer) and bind the appropriate attributes to it.
gl.bindBuffer( gl.ARRAY_BUFFER, bkgd.circleBuffer );
gl.vertexAttribPointer( bkgd.aCenter, 2, gl.FLOAT, false, 0 * floatSiz, 9 * floatSiz );
gl.enableVertexAttribArray( bkgd.aCenter );
gl.vertexAttribPointer( bkgd.aR, 2, gl.FLOAT, false, 2 * floatSiz, 9 * floatSiz );
gl.enableVertexAttribArray( bkgd.aR );
gl.vertexAttribPointer( bkgd.aAng1, 1, gl.FLOAT, false, 4 * floatSiz, 9 * floatSiz );
gl.enableVertexAttribArray( bkgd.aAng1 );
gl.vertexAttribPointer( bkgd.aAng2, 1, gl.FLOAT, false, 5 * floatSiz, 9 * floatSiz );
gl.enableVertexAttribArray( bkgd.aAng2 );
gl.vertexAttribPointer( bkgd.aColor, 3, gl.FLOAT, false, 6 * floatSiz, 9 * floatSiz );
gl.enableVertexAttribArray( bkgd.aColor );
When I load my page, I do see a circle, but it seems to me that the radii are the only attributes that are actually reflecting any type of responsiveness. The angles, center, and color are not reflecting the values they are supposed to be, and I have absolutely no idea why the radii are the only things that are actually working.
Nonetheless, this seems to be an inefficient way to load arguments into a fragment shader to draw a circle, as I have to reload the values for every vertex of the box, and then the GPU interpolates those values for no reason. Is there a better way to pass something like an attribute buffer to a fragment shader, or in general to use a fragment shader in this way? Or should I just use vertices to draw my circle instead?
If you're only drawing circles you can use instanced drawing to not repeat the info.
See this Q&A: what does instancing do in webgl
Or this article
Instancing lets you use some data per instance, as in per circle.
You can also use a texture to store the per circle data or all data. See this Q&A: How to do batching without UBOs?
Whether either are more or less efficient depends on the GPU/driver/OS/Browser. If you need to draw 1000s of circles this might be efficient. Most apps draw a variety of things so would chose a more generic solution unless they had special needs to draw 1000s of circles.
Also it may not be efficient because you're still calling the fragment shader for every pixel that is in the square but not in the circle. That's 30% more calls to the fragment shader than using triangles and that assumes your code is drawing quads that fit the circles. It looks at a glance that your actual code is drawing full canvas quads which is terribly inefficient.
I'm trying to draw a lot of cubes in webgl using instanced rendering (ANGLE_instanced_arrays).
However I can't seem to wrap my head around how to setup the divisors. I have the following buffers;
36 vertices (6 faces made from 2 triangles using 3 vertices each).
6 colors per cube (1 for each face).
1 translate per cube.
To reuse the vertices for each cube; I've set it's divisor to 0.
For color I've set the divisor to 2 (i.e. use same color for two triangles - a face)).
For translate I've set the divisor to 12 (i.e. same translate for 6 faces * 2 triangles per face).
For rendering I'm calling
ext_angle.drawArraysInstancedANGLE(gl.TRIANGLES, 0, 36, num_cubes);
This however does not seem to render my cubes.
Using translate divisor 1 does but the colors are way off then, with cubes being a single solid color.
I'm thinking it's because my instances are now the full cube, but if I limit the count (i.e. vertices per instance), I do not seem to get all the way through the vertices buffer, effectively I'm just rendering one triangle per cube then.
How would I go about rendering a lot of cubes like this; with varying colored faces?
Instancing works like this:
Eventually you are going to call
ext.drawArraysInstancedANGLE(mode, first, numVertices, numInstances);
So let's say you're drawing instances of a cube. One cube has 36 vertices (6 per face * 6 faces). So
numVertices = 36
And lets say you want to draw 100 cubes so
numInstances = 100
Let's say you have a vertex shader like this
Let's say you have the following shader
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
If you did nothing else and just called
var mode = gl.TRIANGLES;
var first = 0;
var numVertices = 36
var numInstances = 100
ext.drawArraysInstancedANGLE(mode, first, numVertices, numInstances);
It would just draw the same cube in the same exact place 100 times
Next up you want to give each cube a different translation so you update your shader to this
attribute vec4 position;
attribute vec3 translation;
uniform mat4 matrix;
void main() {
gl_Position = matrix * (position + vec4(translation, 0));
}
You now make a buffer and put one translation per cube then you setup the attribute like normal
gl.vertexAttribPointer(translationLocation, 3, gl.FLOAT, false, 0, 0)
But you also set a divisor
ext.vertexAttribDivisorANGLE(translationLocation, 1);
That 1 says 'only advance to the next value in the translation buffer once per instance'
Now you want have a different color per face per cube and you only want one color per face in the data (you don't want to repeat colors). There is no setting that would to that Since your numVertices = 36 you can only choose to advance every vertex (divisor = 0) or once every multiple of 36 vertices (ie, numVertices).
So you say, what if instance faces instead of cubes? Well now you've got the opposite problem. Put one color per face. numVertices = 6, numInstances = 600 (100 cubes * 6 faces per cube). You set color's divisor to 1 to advance the color once per face. You can set translation divisor to 6 to advance the translation only once every 6 faces (every 6 instances). But now you no longer have a cube you only have a single face. In other words you're going to draw 600 faces all facing the same way, every 6 of them translated to the same spot.
To get a cube back you'd have to add something to orient the face instances in 6 direction.
Ok, you fill a buffer with 6 orientations. That won't work. You can't set divisor to anything that will use those 6 orientations advance only once every face but then resetting after 6 faces for the next cube. There's only 1 divisor setting. Setting it to 6 to repeat per face or 36 to repeat per cube but you want advance per face and reset back per cube. No such option exists.
What you can do is draw it with 6 draw calls, one per face direction. In other words you're going to draw all the left faces, then all the right faces, the all the top faces, etc...
To do that we make just 1 face, 1 translation per cube, 1 color per face per cube. We set the divisor on the translation and the color to 1.
Then we draw 6 times, one for each face direction. The difference between each draw is we pass in an orientation for the face and we change the attribute offset for the color attribute and set it's stride to 6 * 4 floats (6 * 4 * 4).
var vs = `
attribute vec4 position;
attribute vec3 translation;
attribute vec4 color;
uniform mat4 viewProjectionMatrix;
uniform mat4 localMatrix;
varying vec4 v_color;
void main() {
vec4 localPosition = localMatrix * position + vec4(translation, 0);
gl_Position = viewProjectionMatrix * localPosition;
v_color = color;
}
`;
var fs = `
precision mediump float;
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
`;
var m4 = twgl.m4;
var gl = document.querySelector("canvas").getContext("webgl");
var ext = gl.getExtension("ANGLE_instanced_arrays");
if (!ext) {
alert("need ANGLE_instanced_arrays");
}
var program = twgl.createProgramFromSources(gl, [vs, fs]);
var positionLocation = gl.getAttribLocation(program, "position");
var translationLocation = gl.getAttribLocation(program, "translation");
var colorLocation = gl.getAttribLocation(program, "color");
var localMatrixLocation = gl.getUniformLocation(program, "localMatrix");
var viewProjectionMatrixLocation = gl.getUniformLocation(
program,
"viewProjectionMatrix");
function r(min, max) {
if (max === undefined) {
max = min;
min = 0;
}
return Math.random() * (max - min) + min;
}
function rp() {
return r(-20, 20);
}
// make translations and colors, colors are separated by face
var numCubes = 1000;
var colors = [];
var translations = [];
for (var cube = 0; cube < numCubes; ++cube) {
translations.push(rp(), rp(), rp());
// pick a random color;
var color = [r(1), r(1), r(1), 1];
// now pick 4 similar colors for the faces of the cube
// that way we can tell if the colors are correctly assigned
// to each cube's faces.
var channel = r(3) | 0; // pick a channel 0 - 2 to randomly modify
for (var face = 0; face < 6; ++face) {
color[channel] = r(.7, 1);
colors.push.apply(colors, color);
}
}
var buffers = twgl.createBuffersFromArrays(gl, {
position: [ // one face
-1, -1, -1,
-1, 1, -1,
1, -1, -1,
1, -1, -1,
-1, 1, -1,
1, 1, -1,
],
color: colors,
translation: translations,
});
var faceMatrices = [
m4.identity(),
m4.rotationX(Math.PI / 2),
m4.rotationX(Math.PI / -2),
m4.rotationY(Math.PI / 2),
m4.rotationY(Math.PI / -2),
m4.rotationY(Math.PI),
];
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.translation);
gl.enableVertexAttribArray(translationLocation);
gl.vertexAttribPointer(translationLocation, 3, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.color);
gl.enableVertexAttribArray(colorLocation);
ext.vertexAttribDivisorANGLE(positionLocation, 0);
ext.vertexAttribDivisorANGLE(translationLocation, 1);
ext.vertexAttribDivisorANGLE(colorLocation, 1);
gl.useProgram(program);
var fov = 60;
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var projection = m4.perspective(fov * Math.PI / 180, aspect, 0.5, 100);
var radius = 30;
var eye = [
Math.cos(time) * radius,
Math.sin(time * 0.3) * radius,
Math.sin(time) * radius,
];
var target = [0, 0, 0];
var up = [0, 1, 0];
var camera = m4.lookAt(eye, target, up);
var view = m4.inverse(camera);
var viewProjection = m4.multiply(projection, view);
gl.uniformMatrix4fv(viewProjectionMatrixLocation, false, viewProjection);
// 6 faces * 4 floats per color * 4 bytes per float
var stride = 6 * 4 * 4;
var numVertices = 6;
faceMatrices.forEach(function(faceMatrix, ndx) {
var offset = ndx * 4 * 4; // 4 floats per color * 4 floats
gl.vertexAttribPointer(
colorLocation, 4, gl.FLOAT, false, stride, offset);
gl.uniformMatrix4fv(localMatrixLocation, false, faceMatrix);
ext.drawArraysInstancedANGLE(gl.TRIANGLES, 0, numVertices, numCubes);
});
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/2.x/twgl-full.min.js"></script>
<canvas></canvas>
I know how to use a uniform variable to move the rectangle around, but I don't know how to make it smaller or bigger to fit one into the other. Any help is appreciated. Thank you!
var vertices =
[
vec2(0.0, 0.0 ),
vec2(0.4, 0),
vec2(0, 0.4),
vec2(0.4, 0.4)
];
gl.viewport( 0, 0, canvas.width, canvas.height );
gl.clearColor( 0.9, 0.9, 0.9, 1.0 );
var program = initShaders( gl, "vertex-shader", "fragment-shader" );
gl.useProgram( program );
// Create a buffer for the vertex shader in the GPU.
var bufferId = gl.createBuffer();
// Tell the GPU to expect data for this buffer
gl.bindBuffer( gl.ARRAY_BUFFER, bufferId );
// Send data into the buffer.
gl.bufferData( gl.ARRAY_BUFFER, flatten(vertices), gl.STATIC_DRAW );
// Set up the buffer for use
var vPosition = gl.getAttribLocation( program, "myvPosition" );
// myvPosition (identified using vPosition) will correspond to 2 floats per vertex,
gl.vertexAttribPointer( vPosition, 2, gl.FLOAT, false, 0, 0 );
// Enable use of the vertex buffer with myvPosition
gl.enableVertexAttribArray( vPosition );
// Get an index to each uniform variable in the GPU's shader
var xIndex = gl.getUniformLocation( program, "xAdjust" );
var yIndex = gl.getUniformLocation( program, "yAdjust" );
var rIndex = gl.getUniformLocation( program, "red" );
var gIndex = gl.getUniformLocation( program, "green" );
var bIndex = gl.getUniformLocation( program, "blue" );
gl.uniform1f( xIndex, -0.25 ); // move to the left
gl.uniform1f( gIndex, 1.0 );
gl.clear( gl.COLOR_BUFFER_BIT ); // note new place to put clear
render();
gl.uniform1f( xIndex, +0.25 ); // move to the right
gl.uniform1f( rIndex, 1.0 );
render();
};
function render()
{
gl.drawArrays( gl.TRIANGLE_STRIP, 0, 4 );
}
for example: I can change the value in the gl.uniform1f(xIndex, ) to move the rectangle along x axis
Time to learn about transformation matrix. There is a lot of math, but I will try to explain it as simple as possible.
Lets pick new square 1x1:
var vertices =
[
vec2(0, 0),
vec2(1, 0),
vec2(0, 1),
vec2(1, 1)
];
Now if you would like to move it to the left by 1 (as you did), you want to add 1 to [x] of all your vertices. This look simple.
If you want to rotate it, it is much more complicated. Imagine your object would be from 50000 vertices and not just 4 => super complicated!
So people invented some procedure that is widely used. We create transformation matrix for each object we have. In 2D, matrix is 3x3. In 3D, matrix is 4x4.
How the matrix works? First you create vertices, then initalize matrix with
// js example
var model1M = mat3.create([
1, 0, 0,
0, 1, 0,
0, 0, 1]);
Which means "no transformation done" yet. Then you translate, rotate, scale your object by operations with matrix. Remember, transformation order is important!!
move & rotate != rotate & move
Once you want to render, you send matrix to the shader.
// this is how you send 1 float value
gl.uniform1f( xIndex, -0.25 ); // move to the left
// this is how we send 3x3 matrix
var mvmi = gl.getUniformLocation( program, "modelViewMatrix" );
gl.uniformMatrix3fv(mvmi, false, model1M);
And in shader:
// you have to modify what is in vec4
gl_Position = modelViewMatrix * vec4( position, 1.0 );
and its done.
Problem is mat3 doesnt exist in js. Math for transformations:
http://upload.wikimedia.org/wikipedia/commons/2/2c/2D_affine_transformation_matrix.svg
You need to implement all the math first. But easier is just download library for example http://glmatrix.net/ and include gl-matrix-min.js. Then follow documentation http://glmatrix.net/docs/2.2.0/symbols/mat3.html .
Simple cookbook:
var DEG_TO_RAD = 0.0174532925;
// create matrix, you dont have to type numbers in
var modelMatrix = mat3.create();
// move
mat3.translate(modelMatrix, modelMatrix, [-0.5, -0.5]);
// rotate by 45 degrees
mat3.rotate(modelMatrix, modelMatrix, 45*DEG_TO_RAD);
// make square smaller
mat3.scale(modelMatrix, modelMatrix, [0.4, 0.4]);
I am using triangles(using vertices and face position) to draw the graphics.I am storing color information for each vertex and applying colors accordingly. But the problem is all the geometries in my scene are of single color(say cone=red, cylinder=blue). SO, storing color for each vertex is apparently of no use to me.
Is their any other approach by which coloring can be done in webgl apart from storing color information of each vertices in the scene. Maybe something like coloring the entire geometry(say a cone).
It's clear from your question you might not really understand WebGL yet? You might want to check out these tutorials.
WebGL uses shaders, those shaders use whatever inputs you define and output whatever you tell them to output. That means WebGL doesn't require vertex colors. Vertex colors are something you decide on when you write your shaders. If you don't want to use vertex colors, don't write a shader that references vertex colors.
That said there if you have a shader that happens to use vertex colors you can easily provide the shader with a constant color. Let's assume you have shaders like this that just use vertex colors.
vertex shader:
attribute vec4 a_position;
attribute vec4 a_color; // vertex colors
varying vec4 v_color; // so we can pass the colors to the fragment shader
uniform mat4 u_matrix;
void main() {
gl_Position = u_matrix * a_position;
v_color = a_color;
}
fragment shader:
precision mediump float;
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
Now, all you have to do to use a constant color is turn off the attribute for a_color and set a constant value with gl.vertexAttrib4f like this
// at init time
var a_colorLocation = gl.getAttribLocation(program, "a_color";
// at draw time
gl.disableVertexAttribArray(a_colorLocation); // turn off the attribute
gl.vertexAttrib4f(a_colorLocation, r, g, b, a); // supply a constant color
Note that turning off attribute 0 will slow down WebGL on desktops because if differences between OpenGL and OpenGL ES. It's possible a_colorLocation is attribute 0. To avoid this problem bind your attribute locations BEFORE you link your program. Specifically since you'll always use a position (which is called "a_position" in the example above) just bind that to location 0 like this
..compile shaders..
..attach shaders to program..
// Must happen before you call linkProgram
gl.bindAttribLocation(program, 0, "a_position");
gl.linkProgram(program);
...check for errors, etc...
This will force the attribute for "a_position" to be attribute 0 so you'll always enable it.
Here's a sample
function main() {
var canvas = document.getElementById("c");
var gl = canvas.getContext("webgl");
if (!gl) {
alert("no WebGL");
return;
}
// NOTE:! This function binds attribute locations
// based on the indices of the second array
var program = twgl.createProgramFromScripts(
gl,
["vshader", "fshader"],
["a_position", "a_color"]); // a_position will get location 0
// a_color will get location 1
var a_positionLoc = 0;
var a_colorLoc = 1;
var u_matrixLoc = gl.getUniformLocation(program, "u_matrix");
gl.useProgram(program);
var verts = [
1, 1,
-1, 1,
-1, -1,
1, 1,
-1, -1,
1, -1,
];
var colors = [
255, 0, 0, 255,
0, 255, 0, 255,
0, 0, 255, 255,
255, 255, 0, 255,
0, 255, 255, 255,
255, 0, 255, 255,
];
var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW);
gl.enableVertexAttribArray(a_positionLoc);
gl.vertexAttribPointer(a_positionLoc, 2, gl.FLOAT, false, 0, 0);
var colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array(colors), gl.STATIC_DRAW);
gl.enableVertexAttribArray(a_colorLoc);
gl.vertexAttribPointer(a_colorLoc, 4, gl.UNSIGNED_BYTE, true, 0, 0);
// Draw in the bottom right corner
gl.uniformMatrix4fv(
u_matrixLoc,
false,
[0.5, 0, 0, 0,
0, 0.5, 0, 0,
0, 0, 1, 0,
-0.5, -0.5, 0, 1]);
gl.drawArrays(gl.TRIANGLES, 0, 6);
// Now turn off the a_color attribute and supply a solid color
gl.disableVertexAttribArray(a_colorLoc);
var r = 0.5;
var g = 1;
var b = 0.5;
var a = 1;
gl.vertexAttrib4f(a_colorLoc, r, g, b, a); // greenish
// Draw in the top left corner
gl.uniformMatrix4fv(
u_matrixLoc,
false,
[0.5, 0, 0, 0,
0, 0.5, 0, 0,
0, 0, 1, 0,
0.5, 0.5, 0, 1]);
gl.drawArrays(gl.TRIANGLES, 0, 6);
};
main();
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/3.x/twgl.min.js"></script>
<script id="vshader" type="whatever">
attribute vec4 a_position;
attribute vec4 a_color;
varying vec4 v_color;
uniform mat4 u_matrix;
void main() {
gl_Position = u_matrix * a_position;
v_color = a_color;
}
</script>
<script id="fshader" type="whatever">
precision mediump float;
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
</script>
<canvas id="c" width="300" height="300"></canvas>
If your geometry has color per object, that doesn't change across the geometry, then you should pass that color as the uniform variable.
So you en up with only one attribute - position of vertices, few matrix uniforms - say model, view, projection matrices, that for the vertex shader, and one vector uniform variable for the fragment shader for "shading" the object.
I am having a look at web gl, and trying to render a cube, but I am having a problem when I try to add projection into the vertex shader. I have added an attribute, but when I use it to multiple the modelview and position, it stops displaying the cube. Im not sure why and was wondering if anyone could help? Ive tried looking at a few examples but just cant get this to work
vertex shader
attribute vec3 aVertexPosition;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
void main(void) {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
//gl_Position = uMVMatrix * vec4(aVertexPosition, 1.0);
}
fragment shader
#ifdef GL_ES
precision highp float; // Not sure why this is required, need to google it
#endif
uniform vec4 uColor;
void main() {
gl_FragColor = uColor;
}
function init() {
// Get a reference to our drawing surface
canvas = document.getElementById("webglSurface");
gl = canvas.getContext("experimental-webgl");
/** Create our simple program **/
// Get our shaders
var v = document.getElementById("vertexShader").firstChild.nodeValue;
var f = document.getElementById("fragmentShader").firstChild.nodeValue;
// Compile vertex shader
var vs = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vs, v);
gl.compileShader(vs);
// Compile fragment shader
var fs = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fs, f);
gl.compileShader(fs);
// Create program and attach shaders
program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
// Some debug code to check for shader compile errors and log them to console
if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS))
console.log(gl.getShaderInfoLog(vs));
if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS))
console.log(gl.getShaderInfoLog(fs));
if (!gl.getProgramParameter(program, gl.LINK_STATUS))
console.log(gl.getProgramInfoLog(program));
/* Create some simple VBOs*/
// Vertices for a cube
var vertices = new Float32Array([
-0.5, 0.5, 0.5, // 0
-0.5, -0.5, 0.5, // 1
0.5, 0.5, 0.5, // 2
0.5, -0.5, 0.5, // 3
-0.5, 0.5, -0.5, // 4
-0.5, -0.5, -0.5, // 5
-0.5, 0.5, -0.5, // 6
-0.5,-0.5, -0.5 // 7
]);
// Indices of the cube
var indicies = new Int16Array([
0, 1, 2, 1, 2, 3, // front
5, 4, 6, 5, 6, 7, // back
0, 1, 5, 0, 5, 4, // left
2, 3, 6, 6, 3, 7, // right
0, 4, 2, 4, 2, 6, // top
5, 3, 1, 5, 3, 7 // bottom
]);
// create vertices object on the GPU
vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// Create indicies object on th GPU
ibo = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indicies, gl.STATIC_DRAW);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST);
// Render scene every 33 milliseconds
setInterval(render, 33);
}
var mvMatrix = mat4.create();
var pMatrix = mat4.create();
function render() {
// Set our viewport and clear it before we render
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.useProgram(program);
// Bind appropriate VBOs
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
// Set the color for the fragment shader
program.uColor = gl.getUniformLocation(program, "uColor");
gl.uniform4fv(program.uColor, [0.3, 0.3, 0.3, 1.0]);
//
// code.google.com/p/glmatrix/wiki/Usage
program.uPMatrix = gl.getUniformLocation(program, "uPMatrix");
program.uMVMatrix = gl.getUniformLocation(program, "uMVMatrix");
mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 1.0, 10.0, pMatrix);
mat4.identity(mvMatrix);
mat4.translate(mvMatrix, [0.0, -0.25, -1.0]);
gl.uniformMatrix4fv(program.uPMatrix, false, pMatrix);
gl.uniformMatrix4fv(program.uMVMatrix, false, mvMatrix);
// Set the position for the vertex shader
program.aVertexPosition = gl.getAttribLocation(program, "aVertexPosition");
gl.enableVertexAttribArray(program.aVertexPosition);
gl.vertexAttribPointer(program.aVertexPosition, 3, gl.FLOAT, false, 3*4, 0); // position
// Render the Object
gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0);
}
Thanks in advance for any help
Problem is here:
..., gl.viewportWidth / gl.viewportHeight, ...
Both gl.viewportWidth and gl.viewportHeight are undefined values.
I think you missed this two lines:
gl.viewportWidth = canvas.width;
gl.viewportHeight = canvas.height;
You will see a lot of people doing this:
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
gl.viewportWidth = canvas.width;
gl.viewportHeight = canvas.height;
But please note that WebGL context also have this two attributes:
gl.drawingBufferWidth
gl.drawingBufferHeight
So your cube shows up without the perspective matrix, correct?
At first glance I would think that you may be clipping away your geometry with the near plane. You provide a near an far plane to the perpective function as 1.0 and 10.0 respectively. This means that for any fragments to be visible they must fall in the z range of [1, 10]. You cube is 1 unit per side, centered on (0, 0, 0), and you are moving it "back" from the camera 1 unit. This means that the nearest face to the camera will actually be at 0.5 Z, which is outside the clipping range and therefore discarded. About half of your cube WILL be at z > 1, but you'll be looking at the inside of the cube at that point. If you have backface culling turned on you won't see anything.
Long story short - Your cube is probably too close to the camera. Try this instead:
mat4.translate(mvMatrix, [0.0, -0.25, -3.0]);