2. Generating Texture Coordinates
To control how the texture gets applied to
the models, we need to add a 2D texture coordinate attribute to the
vertices. The natural place to generate texture coordinates is in the
ParametricSurface class. Each subclass should specify
how many repetitions of the texture get tiled across each axis of
domain. Consider the torus: its outer circumference is much longer than
the circumference of its cross section. Since the x-axis in the domain
follows the outer circumference and the y-axis circumscribes the cross
section, it follows that more copies of the texture need to be tiled
across the x-axis than the y-axis. This prevents the grid pattern from
being stretched along one axis.
Recall that each parametric surface describes
its domain using the ParametricInterval structure, so
that’s a natural place to store the number of repetitions; see Example 4. Note that the repetition counts
for each axis are stored in a vec2.
Example 4. Texture support in ParametricSurface.hpp
#include "Interfaces.hpp"
struct ParametricInterval { ivec2 Divisions; vec2 UpperBound; vec2 TextureCount; };
class ParametricSurface : public ISurface { ... private: vec2 ComputeDomain(float i, float j) const; ivec2 m_slices; ivec2 m_divisions; vec2 m_upperBound; vec2 m_textureCount; };
|
The texture counts that I chose for the cone
and sphere are shown in bold in Example 5.
Example 5. Texture support in ParametricEquations.hpp
#include "ParametricSurface.hpp"
class Cone : public ParametricSurface { public: Cone(float height, float radius) : m_height(height), m_radius(radius) { ParametricInterval interval = { ivec2(20, 20), vec2(TwoPi, 1), vec2(30,20) }; SetInterval(interval); } ... };
class Sphere : public ParametricSurface { public: Sphere(float radius) : m_radius(radius) { ParametricInterval interval = { ivec2(20, 20), vec2(Pi, TwoPi), vec2(20, 35) }; SetInterval(interval); } ... };
|
Next we need to flesh out a couple methods in
ParametericSurface (Example 6). Recall that we’re passing in a set
of flags to GenerateVertices to request a set of
vertex attributes; until now, we’ve been ignoring the
VertexFlagsTexCoords flag.
Example 6. Texture support in ParametricSurface.cpp
void ParametricSurface::SetInterval(const ParametricInterval& interval) { m_divisions = interval.Divisions; m_slices = m_divisions - ivec2(1, 1); m_upperBound = interval.UpperBound; m_textureCount = interval.TextureCount; }
...
void ParametricSurface::GenerateVertices(vector<float>& vertices, unsigned char flags) const { int floatsPerVertex = 3; if (flags & VertexFlagsNormals) floatsPerVertex += 3; if (flags & VertexFlagsTexCoords) floatsPerVertex += 2;
vertices.resize(GetVertexCount() * floatsPerVertex); float* attribute = &vertices[0];
for (int j = 0; j < m_divisions.y; j++) { for (int i = 0; i < m_divisions.x; i++) {
// Compute Position vec2 domain = ComputeDomain(i, j); vec3 range = Evaluate(domain); attribute = range.Write(attribute);
// Compute Normal if (flags & VertexFlagsNormals) { ... } // Compute Texture Coordinates if (flags & VertexFlagsTexCoords) { float s = m_textureCount.x * i / m_slices.x; float t = m_textureCount.y * j / m_slices.y; attribute = vec2(s, t).Write(attribute); } } } }
|
In OpenGL, texture coordinates are normalized
such that (0,0) maps to one corner of the image and (1, 1) maps to the
other corner, regardless of its size. The inner loop in Example 6 computes the texture coordinates like
this:
float s = m_textureCount.x * i / m_slices.x;
float t = m_textureCount.y * j / m_slices.y;
Since the s coordinate
ranges from zero up to m_textureCount.x (inclusive),
OpenGL horizontally tiles m_textureCount.x
repetitions of the texture across the surface.
Note that if you were loading the model data
from an OBJ file or other 3D format, you’d probably obtain the texture
coordinates directly from the model file rather than computing them like
we’re doing here.