opengl绘制刻度坐标系_使用几何着色器在着色后的模型上绘制线框

本文是OpenGL 4.0 Shading Language Cookbook的学习笔记。

前面我们展示了使用几何着色器生成与输入图元不同的图元。除此之外,几何着色器还可以为之后的渲染管线生成附加信息。几何着色器可以访问图元的所有顶点,可以很方便地基于整个图元的信息进行计算处理。

本例使用几何着色器计算图元的附加信息,然后在片段着色器中使用这些附加信息来绘制模型的线框。

下图使用这一技术渲染模型的线框。线框使用几何着色器计算出的信息绘制而成。

d3852ae0a8753f589245e262c0eb368e.png

有很多技术可以在着色后的表面上绘制线框。我们这里使用的技术来自NVIDIA在2007年出版的白皮书。我们使用几何着色器可以做到在一遍处理中同时绘制线框和模型。在这里,我们还对线框进行了简单的抗锯齿处理,最后得到的结果十分不错。

我们计算片段到最近的三角形边的距离,当这个距离在一个固定范围内,我们对这个片段着色时混合上边的颜色。否则,进行正常的着色。

我们使用下面的方法计算片段到边的距离。在几何着色器中,我们计算每个顶点到对边的距离(也就是多边形的高)。即下图中的hahbhc

21415176a437d95a3f11f99134e48249.png

我们可以利用三角形的内角通过余弦函数来计算hahbhc。比如,对于ha,我们可以使用下图的方法计算。

f5946debedc811d1b5df3a0a232f3a2d.png

hbhc也可以使用同样的方法计算得到。(需要注意

可能会大于90度,这时,我们应该使用180-
的正弦值来计算,而180-
的正弦值和
相同。)

计算出三角形的高之后,我们可以创建一个叫做edge-distance的向量。这个向量的x成分表示到a边的距离,y成分表示到b边的距离,z成分表示到c边的距离。OpenGL会自动对这些成分进行插值。对于位于顶点A处的片段,它的这个向量为(ha,0,0),对于B点处的片段,它的这个向量为(0,hb,0),对于C点处的片段,它的向量为(0,0,hc)。对于位于三角形中的片段,它的向量是经过插值后的数据,每个分量表示到各边的距离。

我们在屏幕空间计算这些数据。让OpenGL对这些数据进行线性插值。

在片段着色器,我们查找片段到三条边的最短距离,如果这个距离小于线段的宽度,我们就将片段颜色混合上线段颜色。在这里,我们使用smoothstep函数来对线段进行一定程度的反走样。

实现

我们需要设置视口矩阵(uniform变量ViewporMatrix),在几何着色器中,我们用它来计算顶点在屏幕空间下的坐标。

下面这些Uniform变量和线框相关:

  • Line.Width:线框的线段宽度。
  • Line.Color:线框的颜色。

我们需要采取下面的步骤为网格创建线框:

1. 使用下面的代码作为顶点着色器:

#version 400
layout (location = 0 ) in vec3 VertexPosition;
layout (location = 1 ) in vec3 VertexNormal;
out vec3 VNormal;
out vec3 VPosition;
uniform mat4 ModelViewMatrix;
uniform mat3 NormalMatrix;
uniform mat4 ProjectionMatrix;
uniform mat4 MVP;
void main()
{
	VNormal = normalize( NormalMatrix * VertexNormal);
	VPosition = vec3(ModelViewMatrix * vec4(VertexPosition,1.0));
	gl_Position = MVP * vec4(VertexPosition,1.0);
}

2. 使用下面的代码作为几何着色器:

#version 400
layout( triangles_adjacency ) in;
layout( triangle_strip, max_vertices = 15 ) out;
out vec3 GNormal;
out vec3 GPosition;
// Which output primitives are silhouette edges
flat out bool GIsEdge;
in vec3 VNormal[]; // Normal in camera coords.
in vec3 VPosition[]; // Position in camera coords.
uniform float EdgeWidth;
 // Width of sil. edge in clip cds.
uniform float PctExtend; // Percentage to extend quad
bool isFrontFacing( vec3 a, vec3 b, vec3 c )
{
	return ((a.x * b.y - b.x * a.y) +
		(b.x * c.y - c.x * b.y)
		+ (c.x * a.y - a.x * c.y)) > 0;
}
void emitEdgeQuad( vec3 e0, vec3 e1 )
{
	vec2 ext = PctExtend * (e1.xy - e0.xy);
	vec2 v = normalize(e1.xy – e0.xy);
	vec2 n = vec2(-v.y, v.x) * EdgeWidth;
	// Emit the quad
	GIsEdge = true;//This is part of the sil. edge
	gl_Position = vec4( e0.xy - ext, e0.z, 1.0);
	EmitVertex();
	gl_Position = vec4( e0.xy - n - ext, e0.z, 1.0 );
	EmitVertex();
	gl_Position = vec4( e1.xy + ext, e1.z, 1.0 );
	EmitVertex();
	gl_Position = vec4( e1.xy - n + ext, e1.z, 1.0 );
	EmitVertex();
	EndPrimitive();
}
void main()
{
	vec3 p0 = gl_in[0].gl_Position.xyz / gl_in[0].gl_Position.w;
	vec3 p1 = gl_in[1].gl_Position.xyz / gl_in[1].gl_Position.w;
	vec3 p2 = gl_in[2].gl_Position.xyz / gl_in[2].gl_Position.w;
	vec3 p3 = gl_in[3].gl_Position.xyz / gl_in[3].gl_Position.w;
	vec3 p4 = gl_in[4].gl_Position.xyz / gl_in[4].gl_Position.w;
	vec3 p5 = gl_in[5].gl_Position.xyz / gl_in[5].gl_Position.w;
	if( isFrontFacing(p0, p2, p4) ) {
		if( ! isFrontFacing(p0,p1,p2) )
			emitEdgeQuad(p0,p2);
		if( ! isFrontFacing(p2,p3,p4) )
			emitEdgeQuad(p2,p4);
		if( ! isFrontFacing(p4,p5,p0) )
			emitEdgeQuad(p4,p0);
	}
	// Output the original triangle
	GIsEdge = false;
	// This triangle is not part of an edge.
	GNormal = VNormal[0];
	GPosition = VPosition[0];
	gl_Position = gl_in[0].gl_Position;
	EmitVertex();
	GNormal = VNormal[2];
	GPosition = VPosition[2];
	gl_Position = gl_in[2].gl_Position;
	EmitVertex();
	GNormal = VNormal[4];
	GPosition = VPosition[4];
	gl_Position = gl_in[4].gl_Position;
	EmitVertex();
	EndPrimitive();
}

3. 使用下面的代码作为片段着色器:

#version 400
//*** Light and material uniforms go here ****
uniform vec4 LineColor; // The sil. edge color
in vec3 GPosition; // Position in camera coords
in vec3 GNormal; // Normal in camera coords.
flat in bool GIsEdge;
 // Whether or not we're drawing an edge
layout( location = 0 ) out vec4 FragColor;
vec3 toonShade( )
{
// *** 使用之前文章中的卡通着色代码 ***
}
void main()
{
	//If we're drawing an edge, use constant color,
	//otherwise, shade the poly.
	if( GIsEdge ) {
		FragColor = LineColor;
	} else {
		FragColor = vec4( toonShade(), 1.0 );
	}
}

原理

顶点着色器将位置和法线变化到相机空间后输出给几何着色器。gl_Position被设置为剪切坐标系下的位置坐标。我们将在几何着色器中使用它来确定屏幕空间坐标。

我们使用下面的代码定义几何着色器的输入和输出信息。

layout( triangles ) in;
layout( triangle_strip, max_vertices = 3 ) out;

我们并不改变三角形的几何结构,输入和输出的三角形图元是完全相同的。

我们的几何着色器有三个输出变量GNormal,GPosition和GEdgeDistance。前两个是相机坐标系下的法线和位置坐标。第三个存储了顶点到各边的距离,我们使用noperspective限定符定义它。

noperspective out vec3 GEdgeDistance;

noperspective限定符用来指明变量使用线性插值,而不是默认的透视校正插值。前面提到,我们是在屏幕空间进行计算,所以不应该使用透视校正插值。

在main函数中,我们将顶点的位置坐标使用视口矩阵从剪切空间变换到屏幕空间。(需要注意,由于剪切空间坐标是齐次坐标,我们需要将坐标除以w成分。)

接着,我们使用余弦定理计算出hahbhc

得到三个高后,我们首先设置第一个顶点的GEdgeDistance,保持变量GNormal,GPosition和gl_Position不变,然后调用EmitVertex函数输出顶点。对于三角形的其它两个顶点进行类似处理,然后调用EndPrimitive函数结束图元定义。

在片段着色器,我们对着色模型进行计算,并将计算结果存储在变量color中。在这一阶段,变量GEdgeDistance包含了片段到三角形各边的距离。我们将三个分量中最小的那个存储在变量d中。最后,我们使用smoothstep函数计算线框和着色模型计算出的颜色的混合比例。

float mixVal = smoothstep( Line.Width – 1, Line.Width + 1, d );

如果片段到三角形各边的最小距离小于Line.Width-1,那么smoothstep函数返回0,如果大于Line.Width,那么smoothstep函数返回1。如果在它们之间,返回一个平滑的过渡值。对于位于线框上的片段,我们得到0值,在线框外的片段,我们得到1值。在线框周围2像素返回内,我们得到一个在0到1之间的过渡值。我们使用得到的值对着色模型计算出的颜色和线框颜色进行混合。

最后,片段颜色使用mixVal作为混合参数混合得到。

其它

这一技术可以产生非常不错的效果,同时只有很少的缺点。它没有改变图元的几何结构,只是利用图元信息计算出附加的信息来完成线框的绘制。

这里使用的着色器代码可以不经修改地用在其它OpenGL程序上。它可以很方便地用来调试网格渲染的问题。

还可以通过两遍处理实现这一效果,第一遍渲染模型,第二遍处理渲进行多边形偏移(通过调用glPolygonOffset函数)后渲染线框。但这一技术需要找得到合适的多边形偏移值。关于这一技术,可以参考Real Time Rendering, third edition, by T Akenine-Moller, E Haines, and N Hoffman, AK Peters, 2008 第11.4.2章节。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值