There's a lot of ways to handle this.
Buffers define geometry, and shader programs define how that geometry makes it to the screen. It's up to you how to put those together to get what you want.
The short version is that when you call a gl.draw*() function, the bound shader program is executed with what ever buffers and uniforms you told it to use. And you can do that as many times as with different buffers, shaders, or inputs to the shader as you need to to finish that frame.
Let's say you had a cube, a sphere. They are the same color and style (meaning they use the same shader). Let's say that shader takes vertex positions as attribute vec3 aVert and a color as uniform vec4 uColor.
You would probably create a buffer for each object with the vertex data for that shape. Then to render:
- Bind the shader
- Set the
uColor uniform on the shader to [1,0,0,1] (red)
- Cube:
- Bind the cube buffer
- Set the buffer data to the
aVert attribute
- call
gl.drawArrays(gl.TRIANGLES, 0, vertexCount)
- Sphere:
- Bind the sphere buffer
- Set the buffer data to the
aVert attribute
- call
gl.drawArrays(gl.TRIANGLES, 0, vertexCount)
In this example, you draw two different buffers with the same shader using two draw calls.
Now let's say you had multiple cubes, that you want to render with different colors (same shader, but different uniforms). Let's say your shader also takes a uniform vec3 aPos value that positions the center of the cube.
- Bind the shader
- Set the buffer data to the
aVert attribute
- Red Cube:
- Set the
uColor uniform on the shader to [1,0,0,1] (red)
- Set the
uPos uniform to [0,0,0]
- call
gl.drawArrays(gl.TRIANGLES, 0, vertexCount)
- Blue Cube:
- Set the
uColor uniform on the shader to [0,0,1,1] (blue)
- Set the
uPos uniform to [10,0,0]
- call
gl.drawArrays(gl.TRIANGLES, 0, vertexCount)
In this example you draw one buffer with one shader as two object with two different draw calls, with different uniform values in that shader for each draw call.
Am I supposed to create more buffers or create more vertex arrays or create more programs?
Yes, or both, as needed. Do you have different geometry to draw? Then you need more buffers. Do you have different ways to render that geometry? Then you need more shaders. Buffers define a blank canvas, and the shader is your paint and paintbrush. Maybe you paint multiple objects with the same brush and the same paint, or maybe you don't. It's up to you.
I'm currently trying to make multiple spheres for a project. The spheres will act as electrons orbiting a nucleus of an atom. Basically I'm trying to make a 3d model of an atom.
A simple version of this one buffer (a sphere) and one shader. The shader would define:
attribute vec3 aVert;
uniform vec4 uColor;
uniform float uRadius; // radius of sphere
uniform float uOrbitRadius; // radius of orbit distance
uniform fload uOrbitAngle; // radians around the orbit
This shader would have a vertex shader that scaled the sphere according to uRadius, and then moved the sphere into orbit with some trigonometry on uOrbitAngle and uOrbitRadius.
And it would have a fragment shader that did shading/lighting/whatever and tinted the result by uColor.
Then you render a hydrogen atom by:
- Bind the shader
- Bind the buffer
- Set the
aVert attribute in the shader
- Nucleus:
- Set shader
uColor to [0.5,0.5,0.5,1] (gray)
- Set shader
uRadius to 10
- Set shader
uOrbitRadius to 0
- Set shader
uOrbitAngle to 0
- Call
gl.drawArrays()
- Electron:
- Set shader
uColor to [1,0,1,1] (megenta)
- Set shader
uRadius to 1
- Set shader
uOrbitRadius to 20
- Set shader
uOrbitAngle to an increasing amount every frame
- Call
gl.drawArrays()
Then you could add more electrons by repeating the electron render, changing only the uOrbitAngle.
But this really depends on how you want the two objects to look, which will dictate how you structure this.