There are some nice sample projects included in the DirectX SDK that cover tessellation, but I felt that they were a little too ... complex. Don't get me wrong, I feel that they are great samples of doing things like model subdivision, detail tessellation, and bezier curves. I just felt that there should be a very simple demonstration of tessellation in the most basic sense. I decided to write one myself and share it here.
I should note that this was written using SlimDX. I love having the power of DirectX in C#!
As I stated, this is pretty much the most basic example of tessellation I could think of. It has one single quad with vertices defined in screen-space, which allows us to skip any transformation. The shader then tessellates the quad by using hard-coded tessellation factors. That's it!
The main application code isn't that important. It just creates the vertex buffer consisting of 4 vertices. It then draws using the new "4 control point patch" primitive. All the rest of the magic happens in the HLSL code.
The vertex shader isn't that impressive. It is simply a pass-through shader and passes the vertex position through to the hull shader.
VS_OUTPUT VS( VS_INPUT input )
{
VS_OUTPUT output;
output.position = input.position;
return output;
}
The hull shader constant fuction simply sets the hard-coded tessellation factors for the edges and inside. Currently I have it hard-coded to a factor of 32. You may manually change this value to be anywhere from 1-64.
HS_CONSTANT_OUTPUT HSConstant( InputPatch<VS_OUTPUT, 4> ip, uint pid : SV_PrimitiveID )
{
HS_CONSTANT_OUTPUT output;
float edge = 32.0f;
float inside = 32.0f;
output.edges[0] = edge;
output.edges[1] = edge;
output.edges[2] = edge;
output.edges[3] = edge;
output.inside[0] = inside;
output.inside[1] = inside;
return output;
}
The hull shader itself does not perform a basis change, and therefore it passes through all 4 of the input points to the output. As you can see from the attributes, it is operating on the quad domain and it uses the standard clockwise winding.
[domain("quad")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(4)]
[patchconstantfunc("HSConstant")]
HS_OUTPUT HS( InputPatch<VS_OUTPUT, 4> ip, uint cpid : SV_OutputControlPointID, uint pid : SV_PrimitiveID )
{
HS_OUTPUT Output;
Output.position = ip[cpid].position;
return Output;
}
Before explaining the domain shader, let me first explain the orientation of the UV coordinates coming from the tessellator.
Let's assume your vertices are defined in this manner:
u
0-----1
v| |
| |
3-----2
The U dimension ranges from [0-1] in the direction of vertex 0 to vertex 1.
The V dimension ranges from [0-1] in the direction of vertex 0 to vertex 3.
I specifically state this now because I had wrongly assumed that it was oriented such that the U and V coordinates were reversed, like so:
WRONG!
1-----2
| |
v| |
0-----3
u
Now, about the domain shader itself. This is normally where the samples got rather complex calculating bezier curves and such. This is the simplest algorithm I could come up with. It uses three linear interpolations to calculate the vertex position. I visualize it as sliding two lines along the the quad and marking where they intersect as the vertex.
The first lerp finds the "midpoint" between vertex 0 and 1 by a factor of U.
The second lerp finds the "midpoint" between vertex 3 and 2 by a factor of U.
The third lerp finds the "midpoint" between the first and second calulated midpoints by a factor of V.
This is rather hard to "draw" a diagram for, but hopefully this makes some sense:
lerp1
0--|--1
| _ | lerp3
| |
3--|--2
lerp2
The color of the vertex is set based upon the tessellation UV coordinates.
[domain("quad")]
DS_OUTPUT DS( HS_CONSTANT_OUTPUT input, float2 UV : SV_DomainLocation, const OutputPatch<HS_OUTPUT, 4> patch )
{
DS_OUTPUT Output;
float3 topMidpoint = lerp(patch[0].position, patch[1].position, UV.x);
float3 bottomMidpoint = lerp(patch[3].position, patch[2].position, UV.x);
Output.position = float4(lerp(topMidpoint, bottomMidpoint, UV.y), 1);
Output.color = float4(UV.yx, 1-UV.x, 1);
return Output;
}
The pixel shader just writes out the color.
float4 PS( DS_OUTPUT input ) : SV_Target
{
return input.color;
}
There you have it! Hopefully this simple example of tessellating a single quad will be useful to other people and help to illustrate how the tessellator works.
You can download the full source to this example here: Tessellation.zip
2 comments:
Oh wow, I was expecting to have difficulty figuring out the basics of tessellation, but then I found this in my first google search.
Thank you, Mr. McCarthy!
Nice, great example!
However, I could not find your file!
Could you upload again?
Thank you very much!
Post a Comment