Jay Taylor's notes

back to listing index

WebGL Shaders and GLSL

[web search]
Original source (webglfundamentals.org)
Tags: webgl shaders glsl webglfundamentals.org
Clipped on: 2026-01-26

WebGL Shaders and GLSL

This is a continuation from WebGL Fundamentals. If you haven't read about how WebGL works you might want to read this first.

We've talked about shaders and GLSL but haven't really given them any specific details. I think I was hoping it would be clear by example but let's try to make it clearer just in case.

As mentioned in how it works WebGL requires 2 shaders every time you draw something. A vertex shader and a fragment shader. Each shader is a function. A vertex shader and fragment shader are linked together into a shader program (or just program). A typical WebGL app will have many shader programs.

Vertex Shader

A Vertex Shader's job is to generate clip space coordinates. It always takes the form

  • void main() {
  • gl_Position = doMathToMakeClipspaceCoordinates
  • }

Your shader is called once per vertex. Each time it's called you are required to set the special global variable, gl_Position to some clip space coordinates.

Vertex shaders need data. They can get that data in 3 ways.

  1. Attributes (data pulled from buffers)
  2. Uniforms (values that stay the same for all vertices of a single draw call)
  3. Textures (data from pixels/texels)

Attributes

The most common way is through buffers and attributes. How it works covered buffers and attributes. You create buffers,

  • var buf = gl.createBuffer();

put data in those buffers

  • gl.bindBuffer(gl.ARRAY_BUFFER, buf);
  • gl.bufferData(gl.ARRAY_BUFFER, someData, gl.STATIC_DRAW);

Then, given a shader program you made you look up the location of its attributes at initialization time

  • var positionLoc = gl.getAttribLocation(someShaderProgram, "a_position");

and at render time tell WebGL how to pull data out of those buffers and into the attribute

  • // turn on getting data out of a buffer for this attribute
  • gl.enableVertexAttribArray(positionLoc);
  •  
  • var numComponents = 3; // (x, y, z)
  • var type = gl.FLOAT; // 32bit floating point values
  • var normalize = false; // leave the values as they are
  • var offset = 0; // start at the beginning of the buffer
  • var stride = 0; // how many bytes to move to the next vertex
  • // 0 = use the correct stride for type and numComponents
  •  
  • gl.vertexAttribPointer(positionLoc, numComponents, type, normalize, stride, offset);

In WebGL fundamentals we showed that we can do no math in the shader and just pass the data directly through.

  • attribute vec4 a_position;
  •  
  • void main() {
  • gl_Position = a_position;
  • }

If we put clip space vertices into our buffers it will work.

Attributes can use float, vec2, vec3, vec4, mat2, mat3, and mat4 as types.

Uniforms

For a shader uniforms are values passed to the shader that stay the same for all vertices in a draw call. As a very simple example we could add an offset to the vertex shader above

  • attribute vec4 a_position;
  • uniform vec4 u_offset;
  •  
  • void main() {
  • gl_Position = a_position + u_offset;
  • }

And now we could offset every vertex by a certain amount. First we'd look up the location of the uniform at initialization time

  • var offsetLoc = gl.getUniformLocation(someProgram, "u_offset");

And then before drawing we'd set the uniform

  • gl.uniform4fv(offsetLoc, [1, 0, 0, 0]); // offset it to the right half the screen

Note that uniforms belong to individual shader programs. If you have multiple shader programs with uniforms of the same name both uniforms will have their own locations and hold their own values. When calling gl.uniform??? you're only setting the uniform for the current program. The current program is the last program you passed to gl.useProgram.

Uniforms can be many types. For each type you have to call the corresponding function to set it.

  • gl.uniform1f (floatUniformLoc, v); // for float
  • gl.uniform1fv(floatUniformLoc, [v]); // for float or float array
  • gl.uniform2f (vec2UniformLoc, v0, v1); // for vec2
  • gl.uniform2fv(vec2UniformLoc, [v0, v1]); // for vec2 or vec2 array
  • gl.uniform3f (vec3UniformLoc, v0, v1, v2); // for vec3
  • gl.uniform3fv(vec3UniformLoc, [v0, v1, v2]); // for vec3 or vec3 array
  • gl.uniform4f (vec4UniformLoc, v0, v1, v2, v4); // for vec4
  • gl.uniform4fv(vec4UniformLoc, [v0, v1, v2, v4]); // for vec4 or vec4 array
  •  
  • gl.uniformMatrix2fv(mat2UniformLoc, false, [ 4x element array ]) // for mat2 or mat2 array
  • gl.uniformMatrix3fv(mat3UniformLoc, false, [ 9x element array ]) // for mat3 or mat3 array
  • gl.uniformMatrix4fv(mat4UniformLoc, false, [ 16x element array ]) // for mat4 or mat4 array
  •  
  • gl.uniform1i (intUniformLoc, v); // for int
  • gl.uniform1iv(intUniformLoc, [v]); // for int or int array
  • gl.uniform2i (ivec2UniformLoc, v0, v1); // for ivec2
  • gl.uniform2iv(ivec2UniformLoc, [v0, v1]); // for ivec2 or ivec2 array
  • gl.uniform3i (ivec3UniformLoc, v0, v1, v2); // for ivec3
  • gl.uniform3iv(ivec3UniformLoc, [v0, v1, v2]); // for ivec3 or ivec3 array
  • gl.uniform4i (ivec4UniformLoc, v0, v1, v2, v4); // for ivec4
  • gl.uniform4iv(ivec4UniformLoc, [v0, v1, v2, v4]); // for ivec4 or ivec4 array
  •  
  • gl.uniform1i (sampler2DUniformLoc, v); // for sampler2D (textures)
  • gl.uniform1iv(sampler2DUniformLoc, [v]); // for sampler2D or sampler2D array
  •  
  • gl.uniform1i (samplerCubeUniformLoc, v); // for samplerCube (textures)
  • gl.uniform1iv(samplerCubeUniformLoc, [v]); // for samplerCube or samplerCube array

There's also types bool, bvec2, bvec3, and bvec4. They use either the gl.uniform?f? or gl.uniform?i? functions.

Note that for an array you can set all the uniforms of the array at once. For example

  • // in shader
  • uniform vec2 u_someVec2[3];
  •  
  • // in JavaScript at init time
  • var someVec2Loc = gl.getUniformLocation(someProgram, "u_someVec2");
  •  
  • // at render time
  • gl.uniform2fv(someVec2Loc, [1, 2, 3, 4, 5, 6]); // set the entire array of u_someVec2

But if you want to set individual elements of the array you must look up the location of each element individually.

  • // in JavaScript at init time
  • var someVec2Element0Loc = gl.getUniformLocation(someProgram, "u_someVec2[0]");
  • var someVec2Element1Loc = gl.getUniformLocation(someProgram, "u_someVec2[1]");
  • var someVec2Element2Loc = gl.getUniformLocation(someProgram, "u_someVec2[2]");
  •  
  • // at render time
  • gl.uniform2fv(someVec2Element0Loc, [1, 2]); // set element 0
  • gl.uniform2fv(someVec2Element1Loc, [3, 4]); // set element 1
  • gl.uniform2fv(someVec2Element2Loc, [5, 6]); // set element 2

Similarly if you create a struct

  • struct SomeStruct {
  • bool active;
  • vec2 someVec2;
  • };
  • uniform SomeStruct u_someThing;

you have to look up each field individually

  • var someThingActiveLoc = gl.getUniformLocation(someProgram, "u_someThing.active");
  • var someThingSomeVec2Loc = gl.getUniformLocation(someProgram, "u_someThing.someVec2");

Textures in Vertex Shaders

See Textures in Fragment Shaders.

Fragment Shader

A Fragment Shader's job is to provide a color for the current pixel being rasterized. It always takes the form

  • precision mediump float;
  •  
  • void main() {
  • gl_FragColor = doMathToMakeAColor;
  • }

Your fragment shader is called once per pixel. Each time it's called you are required to set the special global variable, gl_FragColor to some color.

Fragment shaders need data. They can get data in 3 ways

  1. Uniforms (values that stay the same for every pixel of a single draw call)
  2. Textures (data from pixels/texels)
  3. Varyings (data passed from the vertex shader and interpolated)

Uniforms in Fragment Shaders

See Uniforms in Shaders.

Textures in Fragment Shaders

Getting a value from a texture in a shader we create a sampler2D uniform and use the GLSL function texture2D to extract a value from it.

  • precision mediump float;
  •  
  • uniform sampler2D u_texture;
  •  
  • void main() {
  • vec2 texcoord = vec2(0.5, 0.5); // get a value from the middle of the texture
  • gl_FragColor = texture2D(u_texture, texcoord);
  • }

What data comes out of the texture is dependent on many settings. At a minimum we need to create and put data in the texture, for example

  • var tex = gl.createTexture();
  • gl.bindTexture(gl.TEXTURE_2D, tex);
  • var level = 0;
  • var width = 2;
  • var height = 1;
  • var data = new Uint8Array([
  • 255, 0, 0, 255, // a red pixel
  • 0, 255, 0, 255, // a green pixel
  • ]);
  • gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);

And set the texture's filtering

  • gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

At initialization time look up the uniform location in the shader program

  • var someSamplerLoc = gl.getUniformLocation(someProgram, "u_texture");

At render time bind the texture to a texture unit

  • var unit = 5; // Pick some texture unit
  • gl.activeTexture(gl.TEXTURE0 + unit);
  • gl.bindTexture(gl.TEXTURE_2D, tex);

And tell the shader which unit you bound the texture to

  • gl.uniform1i(someSamplerLoc, unit);

Varyings

A varying is a way to pass a value from a vertex shader to a fragment shader which we covered in how it works.

To use a varying we need to declare matching varyings in both a vertex and fragment shader. We set the varying in the vertex shader with some value per vertex. When WebGL draws pixels it will interpolate between those values and pass them to the corresponding varying in the fragment shader

Vertex shader

  • attribute vec4 a_position;
  •  
  • uniform vec4 u_offset;
  •  
  • varying vec4 v_positionWithOffset;
  •  
  • void main() {
  • gl_Position = a_position + u_offset;
  • v_positionWithOffset = a_position + u_offset;
  • }

Fragment shader

  • precision mediump float;
  •  
  • varying vec4 v_positionWithOffset;
  •  
  • void main() {
  • // convert from clip space (-1 <-> +1) to color space (0 -> 1).
  • vec4 color = v_positionWithOffset * 0.5 + 0.5;
  • gl_FragColor = color;
  • }

The example above is a mostly nonsense example. It doesn't generally make sense to directly copy the clip space values to the fragment shader and use them as colors. Nevertheless it will work and produce colors.

GLSL

GLSL stands for Graphics Library Shader Language. It's the language shaders are written in. It has some special semi unique features that are certainly not common in JavaScript. It's designed to do the math that is commonly needed to compute things for rasterizing graphics. So for example it has built in types like vec2, vec3, and vec4 which represent 2 values, 3 values, and 4 values respectively. Similarly it has mat2, mat3 and mat4 which represent 2x2, 3x3, and 4x4 matrices. You can do things like multiply a vec by a scalar.

  • vec4 a = vec4(1, 2, 3, 4);
  • vec4 b = a * 2.0;
  • // b is now vec4(2, 4, 6, 8);

Similarly it can do matrix multiplication and vector to matrix multiplication

  • mat4 a = ???
  • mat4 b = ???
  • mat4 c = a * b;
  •  
  • vec4 v = ???
  • vec4 y = c * v;

It also has various selectors for the parts of a vec. For a vec4

  • vec4 v;
  • v.x is the same as v.s and v.r and v[0].
  • v.y is the same as v.t and v.g and v[1].
  • v.z is the same as v.p and v.b and v[2].
  • v.w is the same as v.q and v.a and v[3].

It is able to swizzle vec components which means you can swap or repeat components.

  • v.yyyy

is the same as

  • vec4(v.y, v.y, v.y, v.y)

Similarly

  • v.bgra

is the same as

  • vec4(v.b, v.g, v.r, v.a)

when constructing a vec or a mat you can supply multiple parts at once. So for example

  • vec4(v.rgb, 1)

Is the same as

  • vec4(v.r, v.g, v.b, 1)

Also

  • vec4(1)

Is the same as

  • vec4(1, 1, 1, 1)

One thing you'll likely get caught up on is that GLSL is very type strict.

  • float f = 1; // ERROR 1 is an int. You can't assign an int to a float

The correct way is one of these

  • float f = 1.0; // use float
  • float f = float(1) // cast the integer to a float

The example above of vec4(v.rgb, 1) doesn't complain about the 1 because vec4 is casting the things inside just like float(1).

GLSL has a bunch of built in functions. Many of them operate on multiple components at once. So for example

  • T sin(T angle)

Means T can be float, vec2, vec3 or vec4. If you pass in vec4 you get vec4 back which the sine of each of the components. In other words if v is a vec4 then

  • vec4 s = sin(v);

is the same as

  • vec4 s = vec4(sin(v.x), sin(v.y), sin(v.z), sin(v.w));

Sometimes one argument is a float and the rest is T. That means that float will be applied to all the components. For example if v1 and v2 are vec4 and f is a float then

  • vec4 m = mix(v1, v2, f);

is the same as

  • vec4 m = vec4(
  • mix(v1.x, v2.x, f),
  • mix(v1.y, v2.y, f),
  • mix(v1.z, v2.z, f),
  • mix(v1.w, v2.w, f));

You can see a list of all the GLSL functions on the last page of the WebGL Reference Card. If you like really dry and verbose stuff you can try the GLSL spec.

Putting it all together

That's the point of this entire series of posts. WebGL is all about creating various shaders, supplying the data to those shaders and then calling gl.drawArrays or gl.drawElements to have WebGL process the vertices by calling the current vertex shader for each vertex and then render pixels by calling the current fragment shader for each pixel.

Actually creating the shaders requires several lines of code. Since those lines are the same in most WebGL programs and since once written you can pretty much ignore them. How to compile GLSL shaders and link them into a shader program is covered here.

If you're just starting from here you can go in 2 directions. If you are interested in image processing I'll show you how to do some 2D image processing. If you are interesting in learning about translation, rotation, scale and eventually 3D then start here.

Use <pre><code>code goes here</code></pre> for code blocks
G
Join the discussion…

Log in with
or sign up with Disqus

  • Hello!

    I'm sorry to say that, but I think that some lessons are not well organised. In this lesson I tryed to implement textures, but I had a black color. The problem is that it has to be written gl.generateMipmap(gl.TEXTURE_2D); after creating the texture.

    Can you give me an advice how to go through your lessons ? You started with some things which are explained better in the later lessons, so I had to don't follow the lessons in their order, like this and the fundamentals. Before this lesson maybe I should did the Textures and before the fundamentals I should look at the bottom of the page.

    I think in Fundamentals it's better to put the last paragraphs up. It didn't work the webglUtils.resizeCanvasToDisplaySize(gl.canvas); because I didn't know that I need webGLUtils file and that's said at the bottom of page... And I saw that there are some good advices before implement something !

    Otherwise, I like your tutorial. Thank you for your work !

      • I'm sorry if the lessons are not organized well. It's hard to organize one lesson that explains 100% of everything you need to know. Each lesson takes shortcuts because if one lesson tried to cover 100% of everything it would be 100 pages long and the point of that lesson would be impossible to read because in trying to make a small point it would by side tracked by 10 or 20 unrelated points.

        This lesson said to read this one first. Did you read that one? That one said it continued from this one. Did you read that one? It linked to articles that describe resizeCanvasToDisplaySize.

        You started trying to use textures from this lesson? But this lesson doesn't cover textures. This lesson is about textures and it says at the top it continues from some other lesson. I hope if you read the prerequisites to each lesson then they'll be more understandable.

        Sorry if the organization is poor. I hope that most article mention and or link to their prerequisites. Read those and maybe it will become clearer.

          • Thank you for the reply!

            Now I understand... You have right, you can't explain everything on a single page. I will look in parallel on several pages. Maybe I am used to learn just simple things that are not depending so much on others and which I see them on a single page. I think that's why I said that some pages are not well organised.

            Yes, I read them all before this. Maybe I didn't express myself clearly, I wanted to say that on the first page, at Fundamentals, when I tryed to do what you wrote, for instance the first example, it didn't worked because of that resize function and I found details about webGLUtils at the bottom of that page, when it says about Boilerplate. I wanted to tell you that for me it was better to know that information first, at the beginning of the article, like before to do this examples you will need to put this file. I didn't mean that I start with this page, with textures, and it didn't work. I resolved this problem, but after I go through all Fundamentals. It was just an observation.

            I will listen your advice to continue to read and I'm sure that it will become clearer!

            Have a nice day!

              • Hi Nicolae, I'm sorry it was hard to follow. On the fundamentals page right before it uses resizeCanvasToDispalySize for the first time there are 2 paragraphs explaining it.

                Before we draw we should resize the canvas to match its display size. Canvases just like Images have 2 sizes. The number of pixels actually in them and separately the size they are displayed. CSS determines the size the canvas is displayed. You should always set the size you want a canvas to be with CSS since it is far far more flexible than any other method.

                To make the number of pixels in the canvas match the size it's displayed I'm using a helper function you can read about here.

                Was that not clear? Maybe you have some ideas how to make it clearer?

                  • I am very sorry! You have right! I didn't look too much there, but now I see that there is explaining about resizing!

                    Sorry for my statements!

                      • No worries. Clearly I need to change something. Open to ideas.

                          • you mention textures, and #activeTexture method before introducing it in the prerequisite knowledge. I keep a 'google site search' tab open for these problems haha (site: www.webglfundamentals.org/ insert_search_parameters)

                            Generally:
                            Perhaps speak to code in terms of [repeating] functional patterns & concepts—
                            Ex: the #ARRAY_BUFFER style of associating data to an attribute — appears to repeat over and over for various properties or actions taken.
                            OR the 'create-bind-set' pattern one may grasp early, as a singular step.

                            with these functional patterns, you may easily reference prior, in-depth, explications without re-explaining the pattern again (which you seem forced to do throughout the early lessons); instead, in new cases you have the option to highlight only the differences (at a high level) from one pattern to a new, more sophisticated application of it --this makes for easy overviews of content, and helps greatly with the learner's sense of orientation in the body of code [esp as examples become complex].

                • In WebGL fundamentals we showed that we can do no math in the shader and just pass the data directly through.

                  But we are doing math like addition and subtraction of offset?

                    • In the first example on the WebGL Fundamentals page there is no math.

                      The vertex shader looks like this


                      attribute vec4 a_position;

                      void main() {
                      gl_Position = a_position;
                      }

                      and fragment shader looks like this


                      precision mediump float;
                      void main() {
                      gl_FragColor = vec4(1, 0, 0.5, 1);
                      }

                      No math.

                      That shows we can do no math in the shader and just pass the data directly through.

                    • Hi,
                      In the section "Textures in Fragment Shaders", I ran into an error message when trying to render: "RENDER WARNING: texture bound to texture unit 5 is not renderable. It maybe non-power-of-2 and have incompatible texture filtering."
                      I solved it by adding this line:
                      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
                      but not sure why that was required. If you have any insight as to why that's the case, I'd like to know, thanks!
                      (I haven't gone through the textures section yet)

                      • Hi,

                        I do support for a dept at a University.
                        This site will not load on some machines (usually older ones):
                        https://eforensics.info/lea...

                        The browser will display message: 'shader init error'.
                        I viewed the logs in 'developer options' in Chrome and discovered these lines (not complete log):

                        jquery-migrate.min.js?ver=1.4.1:2 JQMIGRATE: Migrate is installed, version 1.4.1
                        bonegl-min.js?ver=0.1:1 [Deprecation] Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check https://xhr.spec.whatwg.org/.
                        requestFile @ bonegl-min.js?ver=0.1:1
                        bonegl-min.js?ver=0.1:1 WebGL: INVALID_OPERATION: useProgram: program not valid
                        getMatrixUniforms @ bonegl-min.js?ver=0.1:1
                        bonegl-min.js?ver=0.1:1 WebGL: INVALID_OPERATION: getUniformLocation: program not linked
                        getMatrixUniforms @ bonegl-min.js?ver=0.1:1
                        bonegl-min.js?ver=0.1:1 WebGL: INVALID_OPERATION: getAttribLocation: program not linked
                        bindBuffer @ bonegl-min.js?ver=0.1:1
                        bonegl-min.js?ver=0.1:1 WebGL: too many errors, no more errors will be reported to the console for this context.
                        bindBuffer @ bonegl-min.js?ver=0.1:1

                        Any idea where what might be occurring here?

                          • The issue is most likely the page is using shaders that are too complex for that machine (the shaders exceed the minimum specs). It's up to the creator of that page to check what the user's machine can do and then either tell them they're out of luck or fall back to some simpler shader.

                            Firefox has a mode that tries to enforce the minimums to help find these kinds of issues. Running firefox I went to about:config and set webgl.min_capability_mode to true then I restarted firefox and it warned the page tried to use too many uniforms in a shader. WebGL1 is only required to support 128 uniforms vec4s in a vertex shader and 16 vec4s in a fragment shader. Most machines support more but the older a machine is the more likely it only supports the minimum.

                            It's not clear why that particular example needs a such a complex shader but you'd have to talk to the creators about that.

                          • i still did not get uniform. What the fuck "offset-something" means? Can't you just use proper words like "sliding the vertex-shaders" etc?

                            • Hi !

                              i do the following at init time and not at render time, and it works :


                              gl.useProgram(afficheurShader);
                              gl.uniform2f( afficheurShader.resolution , CSW , CSH);

                              gl.useProgram(main1Shader);
                              gl.uniform1i( main1Shader.destination, 4);

                              gl.useProgram(main2Shader);
                              gl.uniform1i( main2Shader.gridTex , 2);
                              gl.uniform1i( main2Shader.pvTex , 5);
                              gl.uniform1i( main2Shader.backup, 6);
                              gl.uniform2f( main2Shader.resolution , CSW , CSH);

                              the same with attribute : (i comment the line and it does work !)


                              //vaoPositionGl = gl.createVertexArray(); -->> useless !!??
                              //gl.bindVertexArray(vaoPositionGl); -->> useless !!??
                              gl.enableVertexAttribArray( main1Shader.positionGl);
                              gl.enableVertexAttribArray( main2Shader.positionGl);
                              gl.enableVertexAttribArray( copieShader.positionGl);
                              gl.bindBuffer(gl.ARRAY_BUFFER,positionGl);
                              gl.vertexAttribPointer( main1Shader.positionGl , 2 , gl.FLOAT , false , 0 , 0 );
                              gl.vertexAttribPointer( main2Shader.positionGl , 2 , gl.FLOAT , false , 0 , 0 );
                              gl.vertexAttribPointer( copieShader.positionGl , 2 , gl.FLOAT , false , 0 , 0 );
                              //gl.bindVertexArray(null);


                              //vaoPosition = gl.createVertexArray(); --->> useless !!???
                              // gl.bindVertexArray(vaoPosition); ------>> useless !!???
                              gl.enableVertexAttribArray( afficheurShader.position );
                              gl.enableVertexAttribArray( gridShader.position );
                              gl.enableVertexAttribArray( main1Shader.position) ;
                              gl.enableVertexAttribArray( main2Shader.position) ;
                              gl.enableVertexAttribArray( copieShader.position) ;
                              gl.bindBuffer(gl.ARRAY_BUFFER,position);
                              gl.vertexAttribPointer( afficheurShader.position , 2 , gl.FLOAT , false , 0 , 0 );
                              gl.vertexAttribPointer( gridShader.position , 2 , gl.FLOAT , false , 0 , 0 );
                              gl.vertexAttribPointer( main1Shader.position , 2 , gl.FLOAT , false , 0 , 0 );
                              gl.vertexAttribPointer( main2Shader.position , 2 , gl.FLOAT , false , 0 , 0 );
                              gl.vertexAttribPointer( copieShader.position , 2 , gl.FLOAT , false , 0 , 0 );
                              //gl.bindVertexArray(null);

                              all that is in the init time. So, what is the benefit with webgl2 and the vertex array object ?

                              edit : ohhh i think i my case "position" and "positiongl", are two buffers with the same number of elements

                                • uniforms are per program state. In a typical WebGL program the same shader would be used multiple times for multiple things so you'd be setting the uniforms at render time. If you're only using the shader once then of course you can set the uniforms at init time but then why even make them uniforms in that case, just make then constants.

                                  In your example above I see CSW and CSH. Assuming those stand for canvas size width and canvas size height then it seems like you'd want to set them at render time. The majority of WebGL programs change the size of the canvas to match the window size. Of course if you're not letting the canvas change size then of course it will work at init time but why make it harder on yourself. Doing it at render time means your code is flexible. Doing it at init time means it just works in this one case and will have to be modified if you ever do allow the canvas to change size.

                                  As for attributes, in WebGL2 you can consider attributes to be per *vertex array* state and you can assume there's one default vertex array. It's bound by default and it's rebound when you call gl.bindVertexArray(null)

                                  So, that means all your repeated calls to gl.enableVertexAttribArray and gl.vertexAttribPointer are likely doing nothing or rather the last one is what's actually working. Assuming the location of the position on your 5 shaders is the same then you're just setting the same attribute to the same settings 5 times each. Each call is effectively doing this


                                  // pseudo implementation
                                  gl.vertexAttribPointer = function(index, size, type, normalize, stride, offset) {
                                  const attrib = gl._internalState_.currentVertexArray.attribs[index];
                                  attrib.size = size;
                                  attrib.type = type;
                                  attrib.normalize = normalize;
                                  attrib.stride = stride !== 0 ? stride : sizeInBytesOf(type) * size;
                                  attrib.offset = offset;
                                  attrib.buffer = gl._internalState_.bufferBindPoints[ARRAY_BUFFER];
                                  };
                                  gl.enableVertexAttribArray = function(index) {
                                  const attrib = gl._internalState_.currentVertexArray.attribs[index];
                                  attrib.enable = true;
                                  };
                                  gl.bindVertexArray = function(va) {
                                  gl._internalState_.currentVertexArray = va !== null
                                  ? va
                                  : gl._internalState_.defaultVertexArray;
                                  };

                                  It also means if it's running for you it's probably mostly luck. Like you're using just one buffer, the same position buffer for all 5 shaders. Try drawing 2 different things with different buffers (like a cube and a sphere) and you should see the issues.

                                  • What does u_someVec2 look like in the shader?

                                      • I'm not sure what you mean by "look like". It's an array of 3 vec2s so you can access them with array accessors. For example

                                        uniform vec2 u_someVec2[3];

                                        void main() {
                                        ...
                                        vec2 foo = u_someVec2[0];
                                        vec2 bar = u_someVec2[1];
                                        vec2 moo = u_someVec2[2];
                                        ...
                                        }

                                        does that answer your question?

                                          • If I would prefer to make it varying size could I do something like

                                            uniform vec2 u_someVec2[];
                                            or

                                            uniform vec2 u_someVec2;

                                            ?

                                              • sadly that's not possible, you have to pick a size.

                                                You have a couple of options. The most common is to generate shaders

                                                var shaderSource = `
                                                #define SOMEVEC_COUNT {somevecCount}

                                                uniform vec u_someVec2[SOMEVEC_COUNT];

                                                ...
                                                `;

                                                var desiredCount = 5;
                                                var src = shaderSource.replace("{somevecCount}", desiredCount);
                                                gl.shaderSource(someShader, src);

                                                Of course you might do something fancier like use a templating library or JavaScript template literals instead of `replace` but you get the idea. It's normal to generate shaders. three.js does it as do most 3d game engines like Unity and Unreal, although they often do it at compile time instead of runtime but either way, it's common.

                                                Another is to just pick your largest size and use some flags or something to skip the ones you don't want to use. The problem with this method is even if you manage to break out of the loop internally the shader might always run the entire loop and just re-write itself so that the parts you wanted skipped have no effect. The result being even when your count is 1 the shader will run as slow as it would if count was whatever your maximum is. So, this is usually not an option people choose except when they are prototyping and haven't written something to help them generate shaders yet.

                                          • an awesome site!

                                              • I love your site!

                                                  • // in JavaScript at init time
                                                    var someVec2Element0Loc = gl.getUniformLocation(someProgram, "u_someVec2[0]");
                                                    var someVec2Element1Loc = gl.getUniformLocation(someProgram, "u_someVec2[1]");
                                                    var someVec2Element2Loc = gl.getUniformLocation(someProgram, "u_someVec2[2]");

                                                    // at render time
                                                    gl.uniform2fv(someVec2ElementLoc, [1, 2]); // set element 0
                                                    gl.uniform2fv(someVec2ElementLoc, [3, 4]); // set element 1
                                                    gl.uniform2fv(someVec2ElementLoc, [5, 6]); // set element 2

                                                    ...shouldn't the render time calls have Element<#>Loc instead of just ElementLoc?