-
In the canvas above, there is a 2D indexed triangle mesh in the shape of the word "Hi". Each triangle in this mesh has an associated color.
-
Each fragment's color is determined by its nearest triangle in the mesh.
-
If the fragment is contained within a triangle, it is colored with the color associated to that triangle.
-
Otherwise, the fragment is colored with the color of the nearest triangle, scaled down by (a function of) the distance to that triangle.
-
This is more complicated than simply displaying a mesh. Pixels outside of the mesh need to be colored too. In fact, they need to know the locations of all triangles in the mesh.
-
A full-screen quad is necessary here to color pixels outside of the mesh.
-
All of the mesh data needs to be accessible from the fragment shader.
-
We could transfer the mesh to the shaders through a uniform array, but there are some restrictions that make this inconvenient.
-
Uniform arrays can only by indexed by loop variables and constants. In particular, this means we cannot use an indexed mesh structure.
-
Instead, we will use a texture to store vertex positions, and a uniform array will index into this structure.
-
We will use a separate texture to store triangle colors.
-
First, we need to create the texture and store the relevant data.
// In JavaScript...
var data = [];
// ... fill data array ...
// Note that the code below assumes you are storing vec3s in a flattened pattern
// STEP 1: create the texture on the GPU.
var texture = gl.createTexture();
// STEP 2: The following line will enable floating points to be used in textures.
// Note that WebGL 1 does not support floating point textures by default. (WebGL 2 does.)
var ext = gl.getExtension('OES_texture_float');
// STEP 3: Bind the texture. All following texture operations to the target gl.TEXTURE_2D will apply to the input texture.
// Note there is no gl.TEXTURE_1D in WebGL.
gl.bindTexture(gl.TEXTURE_2D, texture);
// STEP 4: Convert data to the appropriate type
var dataArray = new Float32Array(data);
// STEP 5: Send the texture data to the GPU.
// Arg 1 is the target (for now, always gl.TEXTURE_2D).
// Arg 2 is the level of detail (for now, always 0).
// Arg 3 is the internal format, which specifies number of data values per "pixel" in the texture.
// Use gl.ALPHA for 1, gl.RGB for 2 or 3, and gl.RGBA for 4.
// Arg 4 is the width of the texture in "pixels".
// Arg 5 is the height of the texture in "pixels".
// Arg 6 is the border. This is always 0.
// Arg 7 is the format. This is always the same as arg 3 for WebGL 1.
// Arg 8 is the type of each data value; floating point in our case, but generally gl.BYTE for image textures.
// Arg 9 is the data itself.
// Note that you can call this function multiple times without creating a new texture. Once a texture is created, it can be overwritten.
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, data.length / 3, 1, 0, gl.RGB, gl.FLOAT, dataArray);
// STEP 6: Cleanup; unbind the texture.
gl.bindTexture(gl.TEXTURE_2D, null);
-
Textures can be accessed in the shaders through a
sampler
object.
// In GLSL...
uniform float vertexTextureSize; // The width of the texture that stores vertex positions
uniform sampler2D vertexPositions; // Allows access to the vertex position texture
// ...
void main() {
// ...
ivec3 indices; // Contains integer indices for each vertex of the triangle
vec2 t0 = texture2D(vertexPositions, vec2((float(indices.x) + 0.5) / vertexTextureSize, 0.5)).xy; // extract vertex 0 from the texture
vec2 t1 = texture2D(vertexPositions, vec2((float(indices.y) + 0.5) / vertexTextureSize, 0.5)).xy; // extract vertex 1 from the texture
vec2 t2 = texture2D(vertexPositions, vec2((float(indices.z) + 0.5) / vertexTextureSize, 0.5)).xy; // extract vertex 2 from the texture
// ...
}
-
Before drawing, we need to tell WebGL which shaders are associated with with sampler.
// In JavaScript...
// STEP 1: Mark a texture unit as active. Only so many textures may be used at once;
// each one has its own texture unit, specified by gl.TEXTUREi, for some integer i.
gl.activeTexture(gl.TEXTURE0);
// STEP 2: Bind the texture.
gl.bindTexture(gl.TEXTURE_2D, texture);
// STEP 3: Set texture parameters. These lines are important, but don't worry about what they do for now.
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
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);
// STEP 4: Set the uniform sampler to read from the specified texture unit.
gl.uniform1i(gl.getUniformLocation(program, "vertexPositions"), 0);
-
Similar steps are taken to store triangle face colors in a texture.