我建议先绘制(绿色)区域,然后绘制虚线(白色)。
绘制线条时,Geometry Shader 可用于将线段转换为四边形(具有 4 个点的三角形条纹),以给定的线条粗细包围薄区域,该区域将由实线填充。片段着色器最终必须划线(切割线的一部分)。最后Fragment Shader 必须生成虚线(从线中切出部分)。
要画线,必须使用Primitive 类型GL_LINE_STRIP_ADJACENCY(参见GLSL Tutorial - Primitive Assembly),因为每个线段都必须知道它的前任和后继,才能计算角度角平分线。如果多边形由线列表定义,则可以轻松完成。列表的最后一个点必须添加到点列表的开头,列表的第一个点必须添加到点列表的末尾。
例如如果一个多边形由点A、B、C和D组成,那么GL_LINE_STRIP_ADJACENCY 将是 D、A、B、C、D 和一个。这将给出以下 4 个线段:
-
A 到 B,前任 D 和后继 C
-
B 到 C,前任 A 和后继 D
-
C 到 D,前任 B 和后继 A
-
D 到 A,前任 C 和后继 B
Vertex Shader 只需通过多边形的角点:
#version 400
layout (location = 0) in vec3 inPos;
out TVertexData
{
out vec3 pos;
} outData;
void main()
{
outData.pos = inPos;
gl_Position = vec4( inPos, 1.0 );
}
Geometry Shader计算每条线段的起点和终点的角平分线,并根据线的粗细生成一个四边形。
由于片段着色器还需要生成虚线,所以计算出线的长度和线的中心,并传递给片段着色器。
#version 400
layout( lines_adjacency ) in;
layout( triangle_strip, max_vertices = 4 ) out;
in TVertexData
{
vec3 pos;
} inData[];
out TGeometryData
{
vec3 linePos;
vec3 lineMidPoint;
float lineLen;
} outData;
uniform float u_thickness;
void main()
{
if ( gl_InvocationID != 0 )
return;
vec3 pos0 = inData[1].pos;
vec3 pos1 = inData[2].pos;
vec3 dirPred = normalize( inData[0].pos - pos0 );
vec3 dirSucc = normalize( inData[3].pos - pos1 );
vec3 dirLine = normalize( pos1 - pos0 );
vec3 dirNorm = normalize( dirPred - dirLine * dot(dirLine, dirPred) );
dirSucc = faceforward( dirSucc, -dirNorm, dirSucc );
vec3 dir0 = abs( dot(dirPred, dirLine) ) > 0.99 ? dirNorm : normalize( dirPred + dirLine );
vec3 dir1 = abs( dot(dirSucc, dirLine) ) > 0.99 ? dirNorm : normalize( dirSucc - dirLine );
vec3 pos01 = pos0 + dir0 * u_thickness / dot(dir0, dirNorm);
vec3 pos11 = pos1 + dir1 * u_thickness / dot(dir1, dirNorm);
vec3 lineMidPoint0 = (pos0 + pos1) * 0.5;
vec3 lineMidPoint1 = lineMidPoint0 + dirNorm * u_thickness;
outData.lineLen = length( pos1 - pos0 );
outData.lineMidPoint = lineMidPoint0;
outData.linePos = pos0;
gl_Position = vec4(pos0, 1.0); EmitVertex();
outData.linePos = pos1;
gl_Position = vec4(pos1, 1.0); EmitVertex();
outData. lineMidPoint = lineMidPoint1;
outData.linePos = pos01;
gl_Position = vec4(pos01, 1.0); EmitVertex();
outData.linePos = pos11;
gl_Position = vec4(pos11, 1.0); EmitVertex();
EndPrimitive();
}
最后Fragment Shader 通过丢弃间隙中的片段生成虚线。
#version 400
in TGeometryData
{
vec3 linePos;
vec3 lineMidPoint;
float lineLen;
} inData;
out vec4 fragColor;
uniform float u_dashLen;
uniform float u_gapLen;
uniform vec4 u_color;
void main()
{
float midDist = length( inData.linePos - inData.lineMidPoint );
float modDist = mod( midDist + u_dashLen * 0.5, u_dashLen + u_gapLen );
float onDash = max( step( modDist, u_dashLen ),
step( inData.lineLen * 0.5 - u_dashLen, midDist ) );
if ( onDash < 0.5 )
discard;
fragColor = u_color * onDash;
}
注意,这个Fragment Shader 使用discard 关键字来丢弃虚线间隙中的片段。如果使用Blending,则可以省略(if ( onDash < 0.5 ) discard;),因为间隙中的片段将使用alpha通道0绘制。
如果先绘制填充的(绿色)多边形,然后绘制虚线(白色),则结果可能如下所示: