Skip to main content
GameDev.net gamedev.net
🔒 Locked

DX11, Patches, Control Points, Tesselation

Started by Pyrogame Feb 2, 2010 at 4:36 PM 5 replies 13.9k views
Original Post
Pyrogame
Pyrogame
Has someone a small example, where a simple patch 0---1 |.....| 3---2 is drawn, and then tesselated in DX11? What is a PatchWith4ControlPoints? Is this a simple quad with 4 position-informations (like vertices) that will be converted in real vertices later in the domain shader? (look upstairs for my patch ^^)? Or what is a patch? Have I to take thouse 4 vertices and create manually the triangles in the domain shader? If so, then the sorting of the vertices doesn't matter, does it? The tesselator adds new control points, so I get something like this? 0-4-1 7..8..5 3-6-2 I looked on the DX SDK, and I understand 98% of the code, but there is something I can't simply unterstand. Something that prevents me to say "oh good, its simple!". I can't understand WHY they making something the way, they made it. Can someone explain me in his own words, how a PatchWith4ControlPoints is processed by the vertex shader, hull shader, tesselator and domain shader and how I can make triangles then? The best way would be by showing some pictures, the same way I tried ^^. I do not need a runnable code, but an idea would be great. Or does someone known a good, very easy (with pictures ^^) book or paper or website with an explaination of all this?
MJP
MJP
Jack Hoxley has a few informative blog posts, complete with diagrams. I would start around here and work your way through.
DieterVW
DieterVW
The tessellator doesn't force your control points to mean anything. So it is completely up to the developer to decide how to use them. I'll try to explain what that means.

First, the tessellator will only tessellate 3 types of geometry: isoline/triangle/quad. In your shaders, you will pick the domain type and that defines what tessellation factors for the edges and interiors you have to calculate. The tessellator itself only needs the domain type, tessfactors, winding direction, and a few other things, in order to produce geometry. It doesn't actually use your control points at all. It does not look at your control points because it doesn't know how to interpret them.

Control points are used by the developer in the hull shader to decide how to tessellate the domain. So, your shader will analyze the topology of the control points and then use that info to set the tessellation factors. You are free to use any number of control points to describe the topology of your domain, but you also have to interpret that information yourself in the hull shader.

The domain shader is then fed the tessellated domain geometry with uv coordinates for each vertex. You could reinterpret these vertices as new control points, but they aren't worth much yet since they all lie on a plane/line. In the domain shader you will interpret your original control points again in order to correctly position a vertex on your patch surface. The uv coordinates of each vertex provides the foundation for knowing where on the patch the vertex belongs but it's still up to you to position it correctly.

So, in your example, the 4 control points would be used for the quad domain. Presuming that they don't actually lie on a plane, you could linearly interpret the position of the vertices 0-8 that were produced by the tessellator in order to move the new geometry to where you want it.

[Edited by - DieterVW on February 3, 2010 5:57:58 PM]
Pyrogame
Pyrogame
"oh good, its simple!" :)

Thanks very much, you both explained me this (by a link and by a simple understandable text). So, I will try to explain this in my own words, what I understand now:

There is a Vertex-Buffer containing some vertices. But thouse vertices are not more vertices, now they are called control points. And there is an Index-Buffer containing some indices to these control points. Let's say, the Index-Buffer points to 4 of them:

vertexbuffer:
0---1  4---5|   |  |   ||   |  |   ||   |  |   |3---2  7---6

indexbuffer: 0, 1, 2, 3 = patch with 4 control points :)

1) Vertex-Shader
VS puts the control points to the world-space, and it calculates some per-control-point lightning, or some other things. It should not calculate the projection-space (because this prevents the creation of 3d triangles later in the domain shader).
0---1|   ||   ||   |3---2

2) The Hull-Shader
It takes the transformed control points, and calculates per patch (in my example that would be 1x) the tesselation factors. The HS also takes the control points and calculates per control points (in my example that would be 4x) some other things, like maybe new control points (the same way the geometry shader processes the geometry). All this control points (the old + the new one) are passed directly to the Domain-Shader.

tess factors: 2,2,2,2,2 (or something like this)

control points (no new are created, they are just passed directly to the domain shader):
0---1|   ||   ||   |3---2

3) Tesselator
It calculates based on the given tesselation factors and topology so called "vertices". But this vertices are only an interpolation between the control points, and they are between 0..1 (I think). So the tesselator can be implemented in a static way using look-up-tables. It converts only the tesselation factors to an array of UVW's. There is no info about the real positions or something else.

UVW's: 0.5 (top-center), 0.5 (right-center), 0.5 (bottom-center), 0.5 (left-center), 0.5 (center-center)

4) Domain-Shader
The DS takes all the control points from the Hull-Shader. For every UVW from the tesselator it calculates a position of a real vertex. For example, it uses two control points, which are connected by each other with an edge. For this you need the control-point-id, which is provided by a DS-parameter. Then it takes the UVW value and interpolates the position of the real vertex on the given edge between the two control points. UVW of 0.5 points to the midle of the edge (only an example), and so on. The Domain-Shader creates the vertices and connects them to triangles. The Domain-Shader also transforms the vertices (because we've them now for the first time in the pipeline) into the projection-space. It also can displace them, because with the UV-coordinates provided by the control points and the interpolation UVW's from the tesselator, we can calculate the right UV position on a high-map. Then we can read the high and move the new vertice along its normal. The normal can either be interpolated too, or we can read it from a texture. Because a texture can contain 4 values argb on one cell, we can store the normal AND the high-value together to speed up things: Nx Ny Nz H.

If we do this all, we get these vertices:
0-1-2|   |7 8 3|   |6-5-4

cw-triangles: 0,1,8 - 1,2,8 - 2,3,8 - 3,4,8 - 4,5,8 - 5,6,8 - 6,7,8 - 7,0,8 = 8 triangles :)

Triangles:
0-1-2|\|/|7-8-3|/|\|6-5-4

5) Geometry-Shader
Called on every triangle. It takes the triangles and does something with them. Discards them, or creates new geometry based of a triangle, and so on. (If you provide a geometry shader, it would be better to convert the vertices to the projection-space on the geometry shader, not on the domain-shader.)

6) Pixel-Shader
Called on every resterized pixel in the projection-space. Outputs a color for this pixel, depth, etc.

Am I right? :)


EDIT: I think, I know now, how this barycentric stuff works. The tesselator outputs lineary tesselated UVW's, but i can modify this values to make the tesselation very "smooth", if I need to change my level of tesselation dynamically.

[Edited by - Pyrogame on February 3, 2010 5:12:33 PM]
DieterVW
DieterVW
I think you pretty much have that right now. I may just clarify a few things just in case.

Hull Shader:
The hull shader consists of two parts: control point shader, patch constant function.

Control point shader :
- uses in the SV_OutputControlPointID
- uses incoming patch control points
- The shader is statically compiled knowing how many control points will be emitted per incoming patch. In the simplest case you can emit the same control points as the patch contains in the first place. However, it is often useful to change geometric basis of the patch data. So, this stage allows for the number of control points to be amplified or compressed. The function will be marked with an attribute [outputcontrolpoints(X)] Where X indicates how many control points will be generated and thus how many times the control point shader will be executed. Each time the control point shader is executed it will have access to the entire set of incoming patch control points so that it can do the necessary analysis and determine where each new control point belongs. The SV_OutputControlPointID indicates which invocation of the control point shader is being executed at the time.

Patch constant function:
- accepts in the new set of control points generated by the control point shader.
- writes out tessfactos, which indicated how to tessellate the domain.
This is run once for each patch after the control point shader.

Tessellator:
- uses the [domian(X)] type (statically compiled into your shaders)
- uses tessfactors, dynamically calculated in the patch constant function
- uses [partitioning(X)], (statically compiled into your shaders)
- uses [outputtopology(X)], (statically compiled into your shaders)
It will then generate the geometry requested. At this point the number of vertices and the triangles is determined and emitted based on the above parameters. Control points are not part of this input.
*Quad: a tessellated quad will be emitted respecting the above parameters. That means this quad will have triangle winding order appropriate for the direction you want the geometry to face, and it will have all of the vertices positioned on a plane with UV coordinates from [0,1].
*Triange: same as quad accept that the shape of the tessellated plane is a triangle.
*Isoline: A bunch of veritices on a line are emitted with total length = 1.

Domain Shader:
- uses tessellation factors
- uses control points from the control point shader
- uses SV_DomainLocation - which contains the UV coordinates for the vertex.
The domain shader executes once for each vertex emitted by the tessellator for the patch geometry. This is just like running a vertex shader, only you now have control point data available so that you can decide how to transform each vertex within the patch.
Pyrogame
Pyrogame
Thank you very much DieterVW :) In your words, how you explain it, this all is easy understandable.

Using the examples provided with DX are to complex to learn from them. They want to show all you can do with a feature only in one complex example. And sometimes they are using an whole example framework, which is a no-go for a "tutorial" or "example" for a newbie like me.

But ok, it doen't matter. Now I will try to use the new knowledge and hopefully I can render my small tesselated Quad-Patch :) Again, thank you for your help.
Jason Z
Jason Z
Your description sounds correct to me. This is basically how it works, but you have to remember that the hull shader contains two functions - one for per patch constants (and hence only runs once per patch) and the per control point function (which runs once per control point[grin]).

If you want a sample set of shaders, here's the most basic sample that I've been able to put together:

//--------------------------------------------------------------------------------// BasicTessellation.hlsl//// This set of shaders implements the most basic tessellation output, and just// interpolates the input vertices to locate the resulting tessellated points.//// Copyright (C) 2009 Jason Zink.  All rights reserved.//--------------------------------------------------------------------------------//--------------------------------------------------------------------------------// Resources//--------------------------------------------------------------------------------cbuffer Transforms{	matrix WorldMatrix;		matrix ViewProjMatrix;};cbuffer TessellationParameters{	float4 EdgeFactors;};cbuffer RenderingParameters{	float4 FinalColor = float4( 1.0f, 1.0f, 1.0f, 1.0f );};//--------------------------------------------------------------------------------// Inter-stage structures//--------------------------------------------------------------------------------struct VS_INPUT{	float3 position 		: POSITION;};//--------------------------------------------------------------------------------struct HS_CONTROL_POINT_INPUT{	float3 WorldPosition	: POSITION;};//--------------------------------------------------------------------------------struct HS_CONTROL_POINT_OUTPUT{	float3 WorldPosition	: POSITION;};//--------------------------------------------------------------------------------struct HS_CONSTANT_DATA_OUTPUT{    float Edges[3]			: SV_TessFactor;	float Inside			: SV_InsideTessFactor;};//--------------------------------------------------------------------------------struct DS_OUTPUT{    float4 Position			: SV_Position;};//--------------------------------------------------------------------------------HS_CONTROL_POINT_INPUT VSMAIN( in VS_INPUT input ){	HS_CONTROL_POINT_OUTPUT output;		output.WorldPosition = mul( float4( input.position, 1.0f ), WorldMatrix ).xyz;	return output;}//--------------------------------------------------------------------------------HS_CONSTANT_DATA_OUTPUT PassThroughConstantHS( 	InputPatch<HS_CONTROL_POINT_OUTPUT, 3> ip,     uint PatchID : SV_PrimitiveID ){	    HS_CONSTANT_DATA_OUTPUT output;    // Insert code to compute Output here	output.Edges[0] = EdgeFactors.x; //2.0f;	output.Edges[1] = EdgeFactors.y; //2.0f;	output.Edges[2] = EdgeFactors.z; //2.0f;	output.Inside = EdgeFactors.w; //2.0f;        return output;}//--------------------------------------------------------------------------------[domain("tri")][partitioning("fractional_even")][outputtopology("triangle_cw")][outputcontrolpoints(3)][patchconstantfunc("PassThroughConstantHS")]HS_CONTROL_POINT_OUTPUT HSMAIN(     InputPatch<HS_CONTROL_POINT_INPUT, 3> ip,     uint i : SV_OutputControlPointID,    uint PatchID : SV_PrimitiveID ){    HS_CONTROL_POINT_OUTPUT output;    // Insert code to compute Output here.    output.WorldPosition = ip.WorldPosition;    return output;}//--------------------------------------------------------------------------------[domain("tri")]DS_OUTPUT DSMAIN( HS_CONSTANT_DATA_OUTPUT input, float3 BarycentricCoordinates : SV_DomainLocation, 			 const OutputPatch<HS_CONTROL_POINT_OUTPUT, 3> TrianglePatch ){	DS_OUTPUT output;	// Interpolate world space position with barycentric coordinates	float3 vWorldPos = BarycentricCoordinates.x * TrianglePatch[0].WorldPosition         + BarycentricCoordinates.y * TrianglePatch[1].WorldPosition         + BarycentricCoordinates.z * TrianglePatch[2].WorldPosition;		// Transform world position with viewprojection matrix	output.Position = mul( float4(vWorldPos.xyz, 1.0), ViewProjMatrix );		return output;}float4 PSMAIN( in DS_OUTPUT input ) : SV_Target{	float4 color = FinalColor;		return( color );}


The result is a set of triangles that produces more triangles within the same plane that the input triangles produce. Here's an image of the output from a basic cube input model:

Jason Zink :: DirectX MVP   Direct3D 11 engine on CodePlex: Hieroglyph 3 Direct3D Books: 

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.