I want to implement thick lines in Directx11. I thought, I can use the instanced rendering technique to render a "high quality" geometry for each line like this picture shows:
P1 and P2 are representing the not equidistant line vertices which are given in as a "D3D11_PRIMITIVE_TOPOLOGY_LINELIST". The line thickness is stored into the constantbuffer. each line has the same thickness. The instance geometry also has an indexbuffer with the information about how to connect the vertices to triangles (in the picture the vertices are I0 - I11).
should I get the P1 and P2 position and the SV_VertexID into one vertexshader thread, I can calculate each position of the I0 - I11 vertices. So that is not a problem.
The question is: Is it possible to use the instanced rendering technique to achieve this?
And if so: Is it a good idea to use it like this? or are there more performance ways to implement thick rounded lines? for example with a geometryshader or just make 1000 drawcalls with that geometry...
I tried a lot to use the instanced rendering idea, but now I changed the idea to geometryshader and it is extremely easy to implement. As input it becomes a line (2 vertices) and the output are 30 triangles.
Here the pixelshader input struct
struct PS_INPUT
{
float4 Pos : SV_POSITION;
};
Here the geometry shader:
#include <psiPosition.hlsli>
//#pragma warning (disable:3206)
//#pragma warning (disable:3554)
static const float PI = 3.1415926f;
static const float fRatio = 2.0f;
static float fThickness = 0.01f;
void addHalfCircle(inout TriangleStream<PS_INPUT> triangleStream, int nCountTriangles, float4 linePointToConnect, float fPointWComponent, float fAngle)
{
PS_INPUT output = (PS_INPUT)0;
for (int nI = 0; nI < nCountTriangles; ++nI)
{
output.Pos.x = cos(fAngle + (PI / nCountTriangles * nI)) * fThickness / fRatio;
output.Pos.y = sin(fAngle + (PI / nCountTriangles * nI)) * fThickness;
output.Pos.z = 0.0f;
output.Pos.w = 0.0f;
output.Pos += linePointToConnect;
output.Pos *= fPointWComponent;
triangleStream.Append(output);
output.Pos = linePointToConnect * fPointWComponent;
triangleStream.Append(output);
output.Pos.x = cos(fAngle + (PI / nCountTriangles * (nI + 1))) * fThickness / fRatio;
output.Pos.y = sin(fAngle + (PI / nCountTriangles * (nI + 1))) * fThickness;
output.Pos.z = 0.0f;
output.Pos.w = 0.0f;
output.Pos += linePointToConnect;
output.Pos *= fPointWComponent;
triangleStream.Append(output);
triangleStream.RestartStrip();
}
}
[maxvertexcount(42)]
void main(line PS_INPUT input[2], inout TriangleStream<PS_INPUT> triangleStream)
{
PS_INPUT output= (PS_INPUT)0;
int nCountTriangles =6;
float4 positionPoint0Transformed = input[0].Pos;
float4 positionPoint1Transformed = input[1].Pos;
float fPoint0w = positionPoint0Transformed.w;
float fPoint1w = positionPoint1Transformed.w;
//calculate out the W parameter, because of usage of perspective rendering
positionPoint0Transformed.xyz = positionPoint0Transformed.xyz / positionPoint0Transformed.w;
positionPoint0Transformed.w = 1.0f;
positionPoint1Transformed.xyz = positionPoint1Transformed.xyz / positionPoint1Transformed.w;
positionPoint1Transformed.w = 1.0f;
//calculate the angle between the 2 points on the screen
float3 positionDifference = positionPoint0Transformed.xyz - positionPoint1Transformed.xyz;
float3 coordinateSysten = float3(1.0f, 0.0f, 0.0f);
positionDifference.z = 0.0f;
coordinateSysten.z = 0.0f;
float fAngle = acos(dot(positionDifference.xy, coordinateSysten.xy) / (length(positionDifference.xy) * length(coordinateSysten.xy)));
if (cross(positionDifference, coordinateSysten).z < 0.0f)
{
fAngle = 2.0f * PI - fAngle;
}
fAngle *= -1.0f;
fAngle -= PI *0.5f;
//first half circle of the line
addHalfCircle(triangleStream, nCountTriangles, positionPoint0Transformed, fPoint0w, fAngle);
addHalfCircle(triangleStream, nCountTriangles, positionPoint1Transformed, fPoint1w, fAngle + PI);
//connection between the two circles
//triangle1
output.Pos.x = cos(fAngle) * fThickness / fRatio;
output.Pos.y = sin(fAngle) * fThickness;
output.Pos.z = 0.0f;
output.Pos.w = 0.0f;
output.Pos += positionPoint0Transformed;
output.Pos *= fPoint0w; //undo calculate out the W parameter, because of usage of perspective rendering
triangleStream.Append(output);
output.Pos.x = cos(fAngle + (PI / nCountTriangles * (nCountTriangles))) * fThickness / fRatio;
output.Pos.y = sin(fAngle + (PI / nCountTriangles * (nCountTriangles))) * fThickness;
output.Pos.z = 0.0f;
output.Pos.w = 0.0f;
output.Pos += positionPoint0Transformed;
output.Pos *= fPoint0w;
triangleStream.Append(output);
output.Pos.x = cos(fAngle + (PI / nCountTriangles * (nCountTriangles))) * fThickness / fRatio;
output.Pos.y = sin(fAngle + (PI / nCountTriangles * (nCountTriangles))) * fThickness;
output.Pos.z = 0.0f;
output.Pos.w = 0.0f;
output.Pos += positionPoint1Transformed;
output.Pos *= fPoint1w;
triangleStream.Append(output);
//triangle2
output.Pos.x = cos(fAngle) * fThickness / fRatio;
output.Pos.y = sin(fAngle) * fThickness;
output.Pos.z = 0.0f;
output.Pos.w = 0.0f;
output.Pos += positionPoint0Transformed;
output.Pos *= fPoint0w;
triangleStream.Append(output);
output.Pos.x = cos(fAngle) * fThickness / fRatio;
output.Pos.y = sin(fAngle) * fThickness;
output.Pos.z = 0.0f;
output.Pos.w = 0.0f;
output.Pos += positionPoint1Transformed;
output.Pos *= fPoint1w;
triangleStream.Append(output);
output.Pos.x = cos(fAngle + (PI / nCountTriangles * (nCountTriangles))) * fThickness / fRatio;
output.Pos.y = sin(fAngle + (PI / nCountTriangles * (nCountTriangles))) * fThickness;
output.Pos.z = 0.0f;
output.Pos.w = 0.0f;
output.Pos += positionPoint1Transformed;
output.Pos *= fPoint1w;
triangleStream.Append(output);
}
I know it is extremely hardcoded, but at least it works ;) Now a picture of a cube with thicker lines (first in perspective projection, second in orthographic projection)
I hope I can help someone with this. If someone has a better idea of implementing thick lines please leave a comment.