A Fast and Hardware-Friendly Surface Tessellation Method
Here is some tessellation code I've written in 2002. At that time, I didn't find any useful resource so I had to probably reinvent the wheel. Anyway, it might save you some time if you found this blog while searching for «example tessellation code». This is adapted from the method I used in my Catmull-Rom subdivision surfaces code.
Consider the following patch which has been tesselated to a 3x4 regular grid and turned into a triangle strip:
I define each patch to be a quad that covers the [0..1]x[0..1] region of (u,v)=R². So it's bound by 0<=u<=1 and 0<=v<1. Let's call this the Grid Space.
Here is a linearized version showing each vertex's index:
I use a vertex buffer containing all my vertices, and an index buffer referencing those vertices.
So, if we put [0, 4, 1, 5, 2, 6, 3, 7, 11, 7, 10, 6, 9, 5, 8, 4, 8, 12, 9, 13, 10, 14, 11, 15, ...] in an index buffer, we can use it to reference our grid as a triangle strip.
Here are some pre-declarations:
// vector_t holds vertex coordinates in whatever format you may like // (I use a GL_T2F_V3F interleaved format for mine) class vector_t; // A vertex Buffer class (basically an array of vector_t) class vertex_buffer_t; // An index Buffer class (basically an array of unsigned ints) class index_buffer_t;
And this is some pseudo code for filling the buffers:
fill_vertex_buffer(vertex_buffer, u_segments, v_segments) { for (v=0; v<v_segments; ++v) { for (u=0; u<u_segments; ++u) { with vertex_buffer[u + v*(u_segments+1)] { // define a quad in model space: .x = (float)u/(float)u_segments; .y = (float)v/(float)v_segments; .z = .0f; } } } } fill_index_buffer(index_buffer, u_segments, v_segments) { // For u_segments = 3 and v_segments>3, the result will be: // [0, 4, 1, 5, 2, 6, 3, 7, 11, 7, 10, 6, 9, 5, 8, 4, 8, 12, 9, 13, 10, 14, 11, 15, ...] // local variables: u_vertex_stride = u_segments+1; // =4: if x is the Nth vertex of a row, x+4 is the Nth vertex on the next. index_count = (u_vertex_stride*v_segments)*2; // = we will generate an index buffer with 32 indices. u_twice_vertex_stride = u_vertex_stride*2; // fill the index buffer: for (index=0; index<index_count; ++index) { // x-coordinate in tesselated grid-space: x = (index/2)%u_twice_vertex_stride; if (x>=u_vertex_stride) x=(u_twice_vertex_stride-1)-x; // y-coordinate in tesselated grid-space: y = index/u_twice_vertex_stride; if (index&1 ^ y&1) ++y; // Index of the vertex in the vertex buffer: index_buffer[index] = y*u_vertex_stride + x; } }
Now, this fill_vertex_buffer()
function is quite useless, since we do not want to tesselate a single quad, but more likely a subdivision surface described by some function.
Here is a surface function declaration:
// A function of surface_function_t type returns a vector_t // which is function of two parameters u and v, whose value is between 0 and 1. // In order to be called a surface, such function must be continuous in [0 .. 1]². typedef vector_t (*surface_function_t)(float u, float v);
Ok, now here is the tesselator that subdivides and samples a surface along a u_segments x v_segments grid in [0..1] x [0..1] range and produces a triangle strip representing the resulting sampled surface in the given vertex/index buffers:
void tesselate( surface_function_t surface, vertex_buffer_t& vertex_buffer, index_buffer_t& index_buffer, unsigned short u_segments, unsigned short v_segments) { // Reserve enough space vertex_buffer.reserve((u_segments+1)*(v_segments+1)); // Declare loop increments const float u_inc(1.f / (float)u_segments)); const float v_inc(1.f / (float)v_segments)); // Compute surface points float v=.0f; for (unsigned short v_step=0; v_step<=v_segments; ++v_step) { float u=.0f; for (unsigned short u_step=0; u_step<=u_segments; ++u_step) { vertex_buffer[v_step*(u_segments+1) + u_step] = surface(u,v); u += u_inc; } v += v_inc; } // Fill the index buffer with indices automagically forming a single triangle strip: unsigned int u_vertex_stride = ((unsigned int)u_segments)+1; unsigned int index_count = (unsigned int)(u_vertex_stride*v_segments<<1); unsigned int u_twice_vertex_stride = u_vertex_stride<<1; index_buffer.reserve(index_count); for (unsigned int index=0; index<index_count; ++index) { unsigned int x,y; x = (index>>1)%u_twice_vertex_stride; if (x>=u_vertex_stride) x=(u_twice_vertex_stride-1)-x; y = index/u_twice_vertex_stride; if (index&1 ^ y&1) ++y; index_buffer[index] = y*u_vertex_stride + x; } }
Leave a Comment