I am working through the Tavares WebGL tutorial, and getting stuck in the mud.
I want to draw single pixels using GL.points
. Something is clearly wrong with my array. See in FF or Chrome Canary:
http://codepen.io/anon/pen/EPJVjK
/**
* Creates a program, attaches shaders, links the program.
* @param {WebGLShader[]} shaders. The shaders to attach.
*/
var createGLProgram = function( gl, shaders ) {
var program = gl.createProgram();
for ( var i = 0; i < shaders.length; i += 1 ) {
gl.attachShader( program, shaders[ i ] );
}
gl.linkProgram( program );
// Check the link status
var linked = gl.getProgramParameter( program, gl.LINK_STATUS );
if ( !linked ) {
// Something went wrong with the link
var lastError = gl.getProgramInfoLog( program );
window.console.error( "Error in program linking: " + lastError );
gl.deleteProgram( program );
return null;
}
return program;
};
var myCreateShader = function( gl, shaderScriptText, shaderType ) {
// Create the shader object
var shader = gl.createShader( shaderType );
// Load the shader source
gl.shaderSource( shader, shaderScriptText );
// Compile the shader
gl.compileShader( shader );
return shader;
};
// Get A WebGL context.
var canvas = document.getElementById( "canvas" );
var gl = canvas.getContext( "webgl", { antialias: false } )
var vertexShader = myCreateShader( gl,
`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;
// Flip 0,0 from bottom left to conventional 2D top left.
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
}`, gl.VERTEX_SHADER );
var fragmentShader = myCreateShader( gl,
`precision mediump float;
uniform vec4 u_color;
void main() {
gl_FragColor = u_color;
}`, gl.FRAGMENT_SHADER );
var program = createGLProgram( gl, [ vertexShader, fragmentShader ] );
gl.useProgram( program );
// Store color location.
var colorLocation = gl.getUniformLocation( program, "u_color" );
// Look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation( program, "a_position" );
// Set the resolution.
var resolutionLocation = gl.getUniformLocation( program, "u_resolution");
gl.uniform2f( resolutionLocation, canvas.width, canvas.height);
// Create a buffer.
var buffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
gl.enableVertexAttribArray( positionLocation );
// Send the vertex data to the shader program.
gl.vertexAttribPointer( positionLocation, 2, gl.FLOAT, false, 0, 0 );
// Set color to black.
gl.uniform4f( colorLocation, 0, 0, 0, 1);
function drawOneBlackPixel( gl, x, y ) {
// Fills the buffer with a single point?
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array([
x, y,
0, y,
x, 0,
x, 0,
0, y,
0, 0]), gl.STATIC_DRAW );
// Draw one point.
gl.drawArrays( gl.POINTS, 0, 1 );
}
// These tests are supposed to be x,y coordinates from top left.
drawOneBlackPixel( gl, 0, 0 );
drawOneBlackPixel( gl, 1, 1 );
drawOneBlackPixel( gl, 2, 2 );
drawOneBlackPixel( gl, 3, 3 );
drawOneBlackPixel( gl, 4, 4 );
drawOneBlackPixel( gl, 5, 5 );
drawOneBlackPixel( gl, 6, 6 );
drawOneBlackPixel( gl, 7, 7 );
drawOneBlackPixel( gl, 10, 5 );
drawOneBlackPixel( gl, 15, 5 );
drawOneBlackPixel( gl, 20, 5 );
drawOneBlackPixel( gl, 12, 34 );
drawOneBlackPixel( gl, 42, 42 );
Issues are that the pixels aren't rendering quite in place. (0,0) doesn't paint in the top left corner. And what should be a diagonal line of pixels is instead "stairstepped" in two-pixel chunks. A working Codepen would be ideal.
As a second "bonus" question, I will next want to be able to batch multiple pixels on a single drawArrays
call, called on requestAnimationFrame
. Advice on how to best do that would be much appreciated.
P.S. I've read:
How can I set the color of a pixel on a canvas using WebGL?
but my error seems somewhat lower-level than that. And please don't advise me to use canvas2D. I want the performance of webGL, and I'm trying to learn from the ground up :)
Several things:
First you need to add gl_PointSize = 1.0;
to your vertex shader to tell webgl the size of your gl.POINT
.
Second, the coordinate you pass in are the center of each pixel rather than at left-top-corner of each pixel, thus,
function drawOneBlackPixel( gl, x, y ) {
// Fills the buffer with a single point?
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array([
x+0.5, y+0.5]), gl.STATIC_DRAW );
// Draw one point.
gl.drawArrays( gl.POINTS, 0, 1 );
}
is what you want.
Working Codepen here: http://codepen.io/anon/pen/pgBjBy.
Bonus question: What you need to do here is manual memory management of the gl.buffer
(gasp) so you can do gl.drawArrays( gl.POINTS, 0, x );
to draw x points. For example, if you want to draw 3 points at (0,0) (1,1), (2,2) then you need to do gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0.5, 0.5, 1.5, 1.5, 2.5, 2.5], gl.STATIC_DRAW)
and then gl.drawArrays(gl.POINTS, 0, 3);
For example, you can first allocate a Float32Array with room for 4 points then whenever you call drawOneBlackPixel
it would first set the Float32Array to [p1x,p1y,0,0,0,0,0,0] and on the next drawOneBlackPixel
it would set the array to [p1x,p1y,p2x,p2y,0,0,0,0] and so on. Of course, you have to handle other things like growing and copying the Float32Array as needed and upload Float32Array to the GPU when you make a change. How to handle the "erasing" of points is also something to keep in mind.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With