Jump to content
  • Advertisement
Sign in to follow this  
cozzie

Subdividing triangles using a GS

This topic is 811 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi,

I've been trying to subdivide triangles using the geometry shader.

After lots of attempts I'm afraid I'm blindstaring and overseeing something.

 

The object I'm using is a sphere.

 

Here is the code I'm using. I've also created a 'dummy' geometry shader to see if the rest of the application is OK. The dummy GS gives correct output. Also tried different meshes/ models as input (cube etc)., but in alle cases the output is a 'star-like' mesh, see the screenshot below.

 

Any input would be really appreciated.

// the geometry shader
[maxvertexcount(8)]
void GS(triangle VertexOut gin[3], inout TriangleStream<GeoOut> triStream)
{	
	// calculate distance to camera
/*	float4 worldVtxIn = mul(testOut[0], gMatWorld);
	float3 toEye = gEyePosW - worldVtxIn.xyz;
	float dist = length(toEye);

	if(dist > 50.0f) return;*/

	VertexOut tempVertexOut[6];
	SubDivide(gin, tempVertexOut);

	GeoOut gout[6];
	[unroll]
	for(int i=0;i<6;++i)
	{
		gout[i].PosW     = mul(float4(tempVertexOut[i].PosL, 1.0f), gMatWorld).xyz;
		gout[i].NormalW  = mul(tempVertexOut[i].NormalL, (float3x3)gWorldInvTranspose);

		gout[i].PosH     = mul(float4(tempVertexOut[i].PosL, 1.0f), gWorldViewProj);

		gout[i].Tex      = tempVertexOut[i].Tex;
	}

	[unroll]
	for(int j=0;j<5;++j)
	{
		triStream.Append(gout[j]);
	}
	triStream.RestartStrip();

	triStream.Append(gout[1]);
	triStream.Append(gout[5]);
	triStream.Append(gout[3]);
}

// SPECIFIC FUNCTIONS FOR SUBDIVISION OF TRIANGLES - CHAPTER 11, EXERCISE 2: ICOSAHEDRON
void SubDivide(VertexOut inVerts[3], out VertexOut outVerts[6])
{
	VertexOut m[3];

	// calculate edge midpoints
	m[0].PosL = 0.5f * (inVerts[0].PosL + inVerts[1].PosL);
	m[1].PosL = 0.5f * (inVerts[1].PosL + inVerts[2].PosL);
	m[2].PosL = 0.5f * (inVerts[2].PosL + inVerts[0].PosL);
	
	// project onto unit sphere
	m[0].PosL = normalize(m[0].PosL);
	m[1].PosL = normalize(m[1].PosL);
	m[2].PosL = normalize(m[2].PosL);

	// derive normals
	m[0].NormalL = m[0].PosL;
	m[1].NormalL = m[1].PosL;
	m[2].NormalL = m[2].PosL;

	// interpolate tex coords
	m[0].Tex = 0.5f * (inVerts[0].Tex + inVerts[1].Tex);
	m[1].Tex = 0.5f * (inVerts[1].Tex + inVerts[2].Tex);
	m[2].Tex = 0.5f * (inVerts[2].Tex + inVerts[0].Tex);

	//  output
	outVerts[0] = inVerts[0];
	outVerts[1] = m[0];
	outVerts[2] = m[2];
	outVerts[3] = m[1];
	outVerts[4] = inVerts[2];
	outVerts[5] = inVerts[1];
};

And the backup / working GS:

// backup geometry shader
[maxvertexcount(3)]
void GSBackup(triangle VertexOut gin[3], inout TriangleStream<GeoOut> triStream)
{
	float4 testOut[3];
	testOut[0] = float4(gin[0].PosL, 1.0f);
	testOut[1] = float4(gin[1].PosL, 1.0f);
	testOut[2] = float4(gin[2].PosL, 1.0f);

	// calculate distance to camera
	float4 worldVtxIn = mul(testOut[0], gMatWorld);
	float3 toEye = gEyePosW - worldVtxIn.xyz;
	float dist = length(toEye);

//	if(dist > 50.0f) return;

	GeoOut gout;
	[unroll]
	for(int i=0;i<3;++i)
	{
		gout.PosH     = mul(testOut[i], gViewProj);
		gout.PosW     = mul(testOut[i], gMatWorld).xyz;
		gout.NormalW  = mul(float4(gin[i].NormalL, 1.0f), gMatWorld).xyz;
		gout.Tex      = gin[i].Tex;
		
		triStream.Append(gout);
	}
}

Result:

 

geom_triangle_division_NOK.jpg

Share this post


Link to post
Share on other sites
Advertisement

Hi,

 

This order:

    outVerts[0] = inVerts[0];
    outVerts[1] = m[0];
    outVerts[2] = m[2];
    outVerts[3] = m[1];
    outVerts[4] = inVerts[2];
    outVerts[5] = inVerts[1];

Doesn't look right. m[1] is between inVert[1] and inVert[2] so a triangle made up from them doesn't seem correct. You should state, are you using triangle list or strip, because you have a restart strip command in there. Also a pic of what you expect would help.

 

PS: Why is max vertex count 8?

Edited by TeaTreeTim

Share this post


Link to post
Share on other sites

Hi.
I'm inputting 1 triangle (lists) with 3 verts. The output of each GS execution should be a tri strip with 3 triangles, 2 as one strip and the last triangle as a new strip (subdivision). I believe that's why I need max 8 verts out. But now I think about it, 6 should be enough.

 

This is the Original mesh/ object, without subdivision (drawn with the dummy GS):

GS_dummy_nosubdivision.jpg

 

This is the aimed result, all triangles subdivided once through the GS:

(generated by making the Original mesh have 1 subdivision)

 

GS_aimedresult_subdivision.jpg

 

The unlogical order of the vertices of the 1st two triangles, might be because they're drawn as a strip.

Edited by cozzie

Share this post


Link to post
Share on other sites
I think I figured the theory out.
An example:

v0
.. /\
. / . \
./ . \
/------\
v1. v2

We start with 3 vertices, and we add 3 more for the subdivision:

........ /.. \
v3.../.......\. v4
..../---------- \
../...\…..../.....\
/------\-*-/-------\
.........v5

This leads to 6 unique vertices.
To be able to output the subdivided triangle, I need two strips:

Strip 1: v1, v3, v5, v4, v2
Strip 2: v3, v0, v4

Voila, 5 + 3 = max vertexcount of the GS: 8.

To calculate the new vertices (v3, 4 and 5), I take the midpoints of the 3 edges of the original triangle. Just figured this out by drawing it on paper and making the calculations by hand. Now I just have to verify this (correct) theory with the code.

Share this post


Link to post
Share on other sites

Hi, A long hand version:

PSIn MidPoint(PSIn a, PSIn b)
{
	PSIn result;
	result.pos = (a.pos + b.pos) / 2.0f;
	result.tex = (a.tex + b.tex) / 2.0f;
// project pos to the same radius as the other points here
	return result;
}


[maxvertexcount(8)]
void GS(triangle PSIn input[3], inout TriangleStream<PSIn> triStream)
{
	PSIn pointA = input[0];
	PSIn pointB = input[1];
	PSIn pointC = input[2];

	PSIn pointAB = MidPoint(pointA, pointB);
	PSIn pointAC = MidPoint(pointA, pointC);
	PSIn pointBC = MidPoint(pointB, pointC);

	pointA.pos = mul(pointA.pos, viewMatrix);
	pointA.pos = mul(pointA.pos, projectionMatrix);
	pointB.pos = mul(pointB.pos, viewMatrix);
	pointB.pos = mul(pointB.pos, projectionMatrix);
	pointC.pos = mul(pointC.pos, viewMatrix);
	pointC.pos = mul(pointC.pos, projectionMatrix);
	pointAB.pos = mul(pointAB.pos, viewMatrix);
	pointAB.pos = mul(pointAB.pos, projectionMatrix);
	pointAC.pos = mul(pointAC.pos, viewMatrix);
	pointAC.pos = mul(pointAC.pos, projectionMatrix);
	pointBC.pos = mul(pointBC.pos, viewMatrix);
	pointBC.pos = mul(pointBC.pos, projectionMatrix);


	triStream.Append(pointA);
	triStream.Append(pointAB);
	triStream.Append(pointAC);
	triStream.Append(pointBC);
	triStream.Append(pointC);

	triStream.RestartStrip();

	triStream.Append(pointAB);
	triStream.Append(pointB);
	triStream.Append(pointBC);

	triStream.RestartStrip();
}

Using triangle strip just seems silly to me, I'd use list and it would make more logical sense but the above will work, you'd need to project the mid points to the sphere as well though. I stand by my original point that I think your order is incorrect, and add the second restart strip like I did.

Share this post


Link to post
Share on other sites
Thanks, I agree on using the lists. Using a strip is for practice and understanding (an actual excercise in my d3d11 book).

I'm gonna play around and see if I can get it working.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!