Interpolate values on a triangle
Now that we can pass attribtues to shaders for lines, we will extend this to triangles.
We start again with the clipping (clip_polygon) and processing (process_triangle).
These are basically exact equivalents to the line versions.
In clip_polygon, we add a block for the clipped attributes in addition to the positions.
As the clipping only involves the intersection with the polygon edges, this is exactly the same as with the lines.
In the code, the only difference is that we are in a loop over the edges and don't only have two points.
In process_triangle we incorporate the attributes and pass them to the rasterization along with the points when triangulating the clipped polygon.
As there isn't much new happening, you can look at the code below, but for brevity, it is folded in.
For the line rasterization, we had to calculate the interpolation parameter. Now for triangles, we have three points, so the singular parameter doesn't cut it.
But we already have a very similar mechanism: Barycentric coordinates. They do basically the same thing, being the weights of each vertex. On an edge of the triangle, they are actually equivalent to the linear interpolation (one weight is )!
So it makes sense, that we basically do the same thing with the barycentric coordinates as we did with the parameter. So instead of interpolating attributes as , we do the barycentric interpolation:
And we already computed the barycentric coordinates to check if a point is part of the triangle.
So we will only implement a method interpolate_triangle that handles the interpolation, again for numbers and matrices seperately.
This will be called in the rasterize_triangle function, which looks basically the same as in the triangle rasterizer.
Before calling the fragment shader, an object is filled with interpolations of vertex shader output attributes using the already computed barycentric coordinates.
You can have a look at the changes here. Again, folded in, as there isn't too much new and to keep the page a bit leaner.
Before we get to implementing the function, a few words about what you will see.
One of the most important use cases for the attribute interpolation on triangles are texture coordinates. Basically, you have an image, the texture, that you want to draw on top of a triangle. To do that, you define for each vertex where that it would be on the image. These coordinates are then interpolated and you can use them to look up the color in the image.
Generally, these image/texture coordinates are normalized in , so they don't depend on resolution and we call them uv coordinates. The naming just comes from the fact, that surfaces are often parametrized by two variables, and . Sometimes, there are other names, for example GLSL provides the .st accessor.
We have included a function to sample an image with these coordinates.
/**
* Samples an image at a given coordinate
* @param {PixelImage} img The
* image to be sampled
* @param {Abstractmat} uv The
* uv coordinate in [0,1]^2
* @param {Object} param Additional
* parameters
* @returns {AbstractMat} The color
* at the requested position
*/
function sample(img, uv,
{
interpolation_mode = Interpolation.NEAREST,
wrap_s = Wrapping.CLAMP_TO_EDGE,
wrap_t = Wrapping.CLAMP_TO_EDGE
} = {}) {...}
There are some additional parameters after the uv to change how colors are interpolated and how edges are handled, but they don't matter too much for now.
We want to use that to put an image on our triangles!
For that we provide a field tex in the material of the objects and an attribute uv for the vertices.
For the attribute, we extend our define to:
const Attribute = {
VERTEX: 0,
UV: 1,
};
We want our shaders to do the following:
-
Vertex shader
- Write the
uvattribute into the output block - Return the
Attribute.VERTEXtransformed by theuniforms.Mmatrix
- Write the
-
Fragment shader
- Get the interpolated
uvvalue from thedataparameter - If the material has a
texfield, sample that texture with theuvcoordinate and put that color into the output. Otherweise use the materialscolorfield
- Get the interpolated
We can now implement our first texture-mapped triangles!
As usual, the solution to cross-check is below.
Exercise:
-
Implement the barycentric interpolation in
interpolate_trianglein rasterizer.js -
Go to the
vertex_shaderin shaders.js- Store the
attributes[Attribute.UV]with the name ´"uv"` in the outputs
- Store the
-
Go to the
fragment_shaderin shaders.js- Get the interpolated uv coordinate from
data - If the material has a texture (
uniforms.material.tex), multiply the color with the sampled texture
- Get the interpolated uv coordinate from
Solution: