OpenGL线宽
Posted
技术标签:
【中文标题】OpenGL线宽【英文标题】:OpenGL Line Width 【发布时间】:2010-08-14 16:54:31 【问题描述】:在我的 OpenGL 应用程序中,它不会让我绘制大于 10 像素宽的线。有没有办法让它绘制超过十个像素?
void OGL_Renderer::drawLine(int x, int y, int x2, int y2, int r, int g, int b, int a, int line_width)
glColor4ub(r, g, b, a);
glLineWidth((GLfloat)line_width);
glBegin(GL_LINES);
glVertex2i(x, y);
glVertex2i(x2, y2);
glEnd();
glLineWidth(1.0f);
【问题讨论】:
呃..什么?也许你应该添加一个代码 sn-p... 【参考方案1】:我建议使用Shader,它会沿着线条(甚至是线条循环)生成triangle primitives。 任务是生成粗线带,尽可能少的 CPU 和 GPU 开销。这意味着要避免在 CPU 以及几何着色器(或曲面细分着色器)上计算多边形。
直线的每一段由一个四边形组成,由 2 个三角形图元分别代表 6 个顶点。
0 2 5
+-------+ +
| / / |
| / / |
| / / |
+ +-------+
1 3 4
在必须找到斜接的线段和必须将四边形切割到斜接的线段之间。
+----------------+
| / |
| segment 1 / |
| / |
+--------+ |
| segment 2
| |
| |
+-------+
用线条的角点创建一个数组。第一个点和最后一个点定义线带的起点和终点切线。所以需要在行前加1点,行后加1点。当然,通过比较索引为 0 和数组的长度来识别数组的第一个和最后一个元素是很容易的,但是我们不想在着色器中进行任何额外的检查。 如果必须画一个线循环,则必须将最后一个点添加到数组头部,并将第一个点添加到它的尾部。
点数组存储到Shader Storage Buffer Object。我们利用的好处是,SSBO 的最后一个变量可以是一个可变大小的数组。在旧版本的 OpenGL(或 OpenGL ES)中,可以使用 Uniform Buffer Object 甚至 Texture。
着色器不需要任何顶点坐标或属性。我们只需要知道线段的索引。坐标存储在缓冲区中。为了找到索引,我们使用当前正在处理的顶点的索引 (gl_VertexID
)。
要绘制带有N
点(N-1
段)的线带,需要处理6*(N-1)
顶点。
我们必须创建一个“空”Vertex Array Object(没有任何顶点属性规范):
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
并绘制2*(N-1)
三角形(6*(N-1)
顶点):
glDrawArrays(GL_TRIANGLES, 0, 6*(N-1));
对于SSBO中的坐标数组,使用了vec4
的数据类型(请相信我,你不想使用vec3
):
layout(std430, binding = 0) buffer TVertex
vec4 vertex[];
;
计算顶点坐标所属的线段的索引以及2个三角形中点的索引:
int line_i = gl_VertexID / 6;
int tri_i = gl_VertexID % 6;
由于我们正在绘制N-1
线段,但数组中的元素数量为N+2
,因此在顶点着色器中处理的每个顶点都可以访问从vertex[line_t]
到vertex[line_t+3]
的元素。 vertex[line_t+1]
和vertex[line_t+2]
分别是线段的起点和终点坐标。计算斜接需要vertex[line_t]
和vertex[line_t+3]
。
线条的粗细应该以像素为单位设置(uniform float u_thickness
)。坐标必须从模型空间转换到窗口空间。为此,必须知道视口的分辨率 (uniform vec2 u_resolution
)。不要忘记perspective divide。线条的绘制甚至可以在透视投影中使用。
vec4 va[4];
for (int i=0; i<4; ++i)
va[i] = u_mvp * vertex[line_i+i];
va[i].xyz /= va[i].w;
va[i].xy = (va[i].xy + 1.0) * 0.5 * u_resolution;
斜接以及起点和终点切线是根据点之间的向量计算的。测试顶点着色器中的点是否相等或长度为零的向量会浪费性能。由顶点设置来处理正确的点列表。 然而,如果一个点的前导点和后继点相等,则斜接计算甚至可以工作。在这种情况下,线的末端垂直于线段或切线:
vec2 v_line = normalize(va[2].xy - va[1].xy);
vec2 nv_line = vec2(-v_line.y, v_line.x);
vec2 v_pred = normalize(va[1].xy - va[0].xy);
vec2 v_succ = normalize(va[3].xy - va[2].xy);
vec2 v_miter1 = normalize(nv_line + vec2(-v_pred.y, v_pred.x));
vec2 v_miter2 = normalize(nv_line + vec2(-v_succ.y, v_succ.x));
在最终的顶点着色器中,我们只需要根据tri_i
计算v_miter1
或v_miter2
。通过斜接、线段的法向量和线的粗细(u_thickness
),可以计算出顶点坐标:
vec4 pos;
if (tri_i == 0 || tri_i == 1 || tri_i == 3)
vec2 v_pred = normalize(va[1].xy - va[0].xy);
vec2 v_miter = normalize(nv_line + vec2(-v_pred.y, v_pred.x));
pos = va[1];
pos.xy += v_miter * u_thickness * (tri_i == 1 ? -0.5 : 0.5) / dot(v_miter, nv_line);
else
vec2 v_succ = normalize(va[3].xy - va[2].xy);
vec2 v_miter = normalize(nv_line + vec2(-v_succ.y, v_succ.x));
pos = va[2];
pos.xy += v_miter * u_thickness * (tri_i == 5 ? 0.5 : -0.5) / dot(v_miter, nv_line);
最后必须将窗口坐标转换回剪辑空间坐标。从窗口空间转换为标准化设备空间。必须扭转视角鸿沟:
pos.xy = pos.xy / u_resolution * 2.0 - 1.0;
pos.xyz *= pos.w;
着色器可以生成以下多边形(用glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
渲染)
(使用默认模式 - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
)
对于以下简单的演示程序,我使用GLFW API 创建一个窗口,GLEW 用于加载 OpenGL,GLM -OpenGL Mathematics 用于数学运算。我不提供函数CreateProgram
的代码,它只是从顶点着色器和片段着色器源代码中创建一个程序对象:
#include <vector>
#include <string>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <gl/gl_glew.h>
#include <GLFW/glfw3.h>
std::string vertShader = R"(
#version 460
layout(std430, binding = 0) buffer TVertex
vec4 vertex[];
;
uniform mat4 u_mvp;
uniform vec2 u_resolution;
uniform float u_thickness;
void main()
int line_i = gl_VertexID / 6;
int tri_i = gl_VertexID % 6;
vec4 va[4];
for (int i=0; i<4; ++i)
va[i] = u_mvp * vertex[line_i+i];
va[i].xyz /= va[i].w;
va[i].xy = (va[i].xy + 1.0) * 0.5 * u_resolution;
vec2 v_line = normalize(va[2].xy - va[1].xy);
vec2 nv_line = vec2(-v_line.y, v_line.x);
vec4 pos;
if (tri_i == 0 || tri_i == 1 || tri_i == 3)
vec2 v_pred = normalize(va[1].xy - va[0].xy);
vec2 v_miter = normalize(nv_line + vec2(-v_pred.y, v_pred.x));
pos = va[1];
pos.xy += v_miter * u_thickness * (tri_i == 1 ? -0.5 : 0.5) / dot(v_miter, nv_line);
else
vec2 v_succ = normalize(va[3].xy - va[2].xy);
vec2 v_miter = normalize(nv_line + vec2(-v_succ.y, v_succ.x));
pos = va[2];
pos.xy += v_miter * u_thickness * (tri_i == 5 ? 0.5 : -0.5) / dot(v_miter, nv_line);
pos.xy = pos.xy / u_resolution * 2.0 - 1.0;
pos.xyz *= pos.w;
gl_Position = pos;
)";
std::string fragShader = R"(
#version 460
out vec4 fragColor;
void main()
fragColor = vec4(1.0);
)";
GLuint CreateSSBO(std::vector<glm::vec4> &varray)
GLuint ssbo;
glGenBuffers(1, &ssbo);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo );
glBufferData(GL_SHADER_STORAGE_BUFFER, varray.size()*sizeof(*varray.data()), varray.data(), GL_STATIC_DRAW);
return ssbo;
int main(void)
if ( glfwInit() == 0 )
return 0;
GLFWwindow *window = glfwCreateWindow( 800, 600, "GLFW OGL window", nullptr, nullptr );
if ( window == nullptr )
glfwTerminate();
retturn 0;
glfwMakeContextCurrent(window);
if ( glewInit() != GLEW_OK )
return 0;
GLuint program = CreateProgram(vertShader, fragShader);
GLint loc_mvp = glGetUniformLocation(program, "u_mvp");
GLint loc_res = glGetUniformLocation(program, "u_resolution");
GLint loc_thi = glGetUniformLocation(program, "u_thickness");
glUseProgram(program);
glUniform1f(loc_thi, 20.0);
GLushort pattern = 0x18ff;
GLfloat factor = 2.0f;
glm::vec4 p0(-1.0f, -1.0f, 0.0f, 1.0f);
glm::vec4 p1(1.0f, -1.0f, 0.0f, 1.0f);
glm::vec4 p2(1.0f, 1.0f, 0.0f, 1.0f);
glm::vec4 p3(-1.0f, 1.0f, 0.0f, 1.0f);
std::vector<glm::vec4> varray1 p3, p0, p1, p2, p3, p0, p1 ;
GLuint ssbo1 = CreateSSBO(varray1);
std::vector<glm::vec4> varray2;
for (int u=-8; u <= 368; u += 8)
double a = u*M_PI/180.0;
double c = cos(a), s = sin(a);
varray2.emplace_back(glm::vec4((float)c, (float)s, 0.0f, 1.0f));
GLuint ssbo2 = CreateSSBO(varray2);
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glm::mat4(project);
int vpSize[2]0, 0;
while (!glfwWindowShouldClose(window))
int w, h;
glfwGetFramebufferSize(window, &w, &h);
if (w != vpSize[0] || h != vpSize[1])
vpSize[0] = w; vpSize[1] = h;
glViewport(0, 0, vpSize[0], vpSize[1]);
float aspect = (float)w/(float)h;
project = glm::ortho(-aspect, aspect, -1.0f, 1.0f, -10.0f, 10.0f);
glUniform2f(loc_res, (float)w, (float)h);
glClear(GL_COLOR_BUFFER_BIT);
glm::mat4 modelview1( 1.0f );
modelview1 = glm::translate(modelview1, glm::vec3(-0.6f, 0.0f, 0.0f) );
modelview1 = glm::scale(modelview1, glm::vec3(0.5f, 0.5f, 1.0f) );
glm::mat4 mvp1 = project * modelview1;
glUniformMatrix4fv(loc_mvp, 1, GL_FALSE, glm::value_ptr(mvp1));
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo1);
GLsizei N1 = (GLsizei)varray1.size()-2;
glDrawArrays(GL_TRIANGLES, 0, 6*(N1-1));
glm::mat4 modelview2( 1.0f );
modelview2 = glm::translate(modelview2, glm::vec3(0.6f, 0.0f, 0.0f) );
modelview2 = glm::scale(modelview2, glm::vec3(0.5f, 0.5f, 1.0f) );
glm::mat4 mvp2 = project * modelview2;
glUniformMatrix4fv(loc_mvp, 1, GL_FALSE, glm::value_ptr(mvp2));
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo2);
GLsizei N2 = (GLsizei)varray2.size()-2;
glDrawArrays(GL_TRIANGLES, 0, 6*(N2-1));
glfwSwapBuffers(window);
glfwPollEvents();
glfwTerminate();
return 0;
【讨论】:
现代 OpenGL 的最佳答案【参考方案2】:你可以试着画一个四边形。使其与您希望的线条一样宽,使其长度与所需的线条宽度一样高,然后旋转并将其定位到线条的位置。
【讨论】:
我该怎么做?就像我给你四个点(x1,y1,x2,y2),我想用这四个点画一条大于十像素的线。 你会调用 glVertex2D() 四次,你的“线”的“角”。对于垂直/水平线来说特别简单,对角线不太难,但在角度上有点棘手(只是近似值,或使用三角)。您也许可以使用视图矩阵做一些技巧,但仅绘制四边形可能更容易(即使您使用 trig)。【参考方案3】:啊,现在我明白你的意思了:
-
一个一个地画一个正方形。
计算线的长度和方向
将其拉伸到 x 的长度
平移到 startpos 并旋转到 line_orientation
或:
-
获取线向量:v :(x2 - x1, y2 - y1)
标准化 v:n
3- 获得向量的正交(法线):o(在 2d 中很容易)
直线的终点和起点加减o得到4个角点
用这些点画一个四边形。
【讨论】:
【参考方案4】:你不能这样做是有道理的。来自 glLineWidth 参考:
支持的宽度范围和范围内支持的宽度之间的大小差异可以通过调用带有参数GL_LINE_WIDTH_RANGE和GL_LINE_WIDTH_GRANULARITY的glGet来查询。
【讨论】:
没有GL_LINE_WIDTH_RANGE
,但有GL_ALIASED_LINE_WIDTH_RANGE
和GL_SMOOTH_LINE_WIDTH_RANGE
。
@plasmacel 是正确的。引用 khronos.org“在 OpenGL 1.2 中,标记 GL_LINE_WIDTH_RANGE
和 GL_LINE_WIDTH_GRANULARITY
被 GL_ALIASED_LINE_WIDTH_RANGE
、GL_SMOOTH_LINE_WIDTH_RANGE
和 GL_SMOOTH_LINE_WIDTH_GRANULARITY
替换。保留旧名称是为了向后兼容,但不应在新代码。”以上是关于OpenGL线宽的主要内容,如果未能解决你的问题,请参考以下文章