OpenGL 4.0下如何渲染圆形顶点?解决升级后顶点变方形问题
问题
我有一个基于MFC的Windows桌面SDI应用程序,采用文档-视图(Doc-View)框架构建,在主窗口(MainFrame)中显示基于现代OpenGL的3D图形,用于渲染由指定点(顶点)、线条和曲线构成的相对简单的对象/形状。
所有常用的OpenGL操作由一组专用类库(如Camera、PipelineManager、ShaderManager、Quaternion等)管理。
原本使用的OpenGL版本为3.0,通过GLDisplay类定义:
bool Initialize(HWND wnd, int bitsPerPixel = 32, int major = 3, int minor = 0, bool compatibleContext = false, bool fullscreen = false);
Initialize()的实现如下(注意GL_POINT_SMOOTH的使用):
bool GLDisplay::Initialize(HWND wnd, int bitsPerPixel, int major, int minor, bool compatibleContext, bool fullscreen ) { //first get the device context using the existing window handle m_deviceContext = GetDC(wnd); //if this is the first time we are creating a screen //then setup the window properties and get the extension function if (s_isFirstInit) { //set the properties of the RGBA color modes and buffers (only done ONCE) s_pixelFormatClass.nSize = sizeof(PIXELFORMATDESCRIPTOR); s_pixelFormatClass.nVersion = 1; s_pixelFormatClass.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER; s_pixelFormatClass.iPixelType = PFD_TYPE_RGBA; //size of the depth testing buffer. s_pixelFormatClass.cDepthBits = 32; s_pixelFormatClass.cStencilBits = 32; //set pixel format so that the device and render context can work together s_pixelFormat = ChoosePixelFormat(m_deviceContext, &s_pixelFormatClass); SetPixelFormat(m_deviceContext, s_pixelFormat, &s_pixelFormatClass); //variables to store properties of the window HGLRC tempRenderContext; //create a temporary OpenGL rendering context using device context //this context is only used to acquire the extension //function to enable creating a MODERN OpenGL context tempRenderContext = wglCreateContext(m_deviceContext); //enable the temporary rendering context wglMakeCurrent(m_deviceContext, tempRenderContext); //acquire extension function for modern OpenGL (only done ONCE) s_wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC) wglGetProcAddress("wglCreateContextAttribsARB"); //destroy the temporary context wglDeleteContext(tempRenderContext); } //set the pixel format so that the device and render context SetPixelFormat(m_deviceContext, s_pixelFormat, &s_pixelFormatClass); //set OpenGL into core or compatibility mode int profile; if (compatibleContext) { profile = WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; } else { profile = WGL_CONTEXT_CORE_PROFILE_BIT_ARB; } //set the attributes for creating a modern OpenGL context //the final 0 value in the array indicates the end of the array int attribs[] = { WGL_CONTEXT_MAJOR_VERSION_ARB, major, WGL_CONTEXT_MINOR_VERSION_ARB, minor, WGL_CONTEXT_PROFILE_MASK_ARB, profile, 0 }; //create the modern OpenGL context using the extension function m_renderContext = s_wglCreateContextAttribsARB(m_deviceContext, NULL, attribs); //enable the final rendering context wglMakeCurrent(m_deviceContext, m_renderContext); //only initialize GLEW ONCE when the FIRST window is created if (s_isFirstInit) { //set GLEW library flag to combat potential errors for core mode glewExperimental = GL_TRUE; //enable blending for transparency (for text rendering) glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); s_isFirstInit = false; } //allow for vertices to be rounded glEnable(GL_LINE_SMOOTH); glEnable(GL_POINT_SMOOTH); return true; }
应用程序还使用两个相对简单的着色器:
Main.vert:
#version 150 in vec3 vertexIn; in vec4 colorIn; in vec2 textureIn; out vec3 vertexOut; out vec4 colorOut; out vec2 textureOut; //send a combined model-view-projection matrix into the shader (mvp) //instead of multiplying projection * view * model for each vertex, //therefore allowing for less calculations uniform mat4 mvp; void main(void) { colorOut = colorIn; textureOut = textureIn; gl_Position = mvp * vec4(vertexIn, 1.0f); }
Main.frag:
#version 150 in vec3 vertexOut; in vec4 colorOut; in vec2 textureOut; out vec4 pixelColor; uniform bool isTextured; uniform sampler2D textureImage; void main(void) { if(isTextured) { vec2 uv; uv.x = textureOut.x; uv.y = 1 - textureOut.y; pixelColor = colorOut * texture(textureImage, uv); } else { pixelColor = colorOut; } }
使用上述配置时,我可以渲染出指定大小的圆形顶点。但将OpenGL版本从3.0改为4.0后,顶点被渲染为方形,请问需要进行哪些修改或添加哪些代码才能继续渲染圆形顶点?
解决方案
核心原因
OpenGL 3.2+的核心模式下,GL_POINT_SMOOTH被移除,固定功能管线的点大小控制和抗锯齿逻辑不再生效。要实现圆形顶点,必须通过着色器程序自定义点的形状。
具体修改步骤
更新顶点着色器,传递点大小并输出点内坐标
修改Main.vert,适配GLSL 4.0版本,添加点大小的uniform变量,并利用gl_PointCoord输出点内的纹理坐标:#version 400 in vec3 vertexIn; in vec4 colorIn; out vec4 colorOut; out vec2 pointUV; uniform mat4 mvp; uniform float pointSize; // 控制顶点的显示大小 void main(void) { colorOut = colorIn; gl_Position = mvp * vec4(vertexIn, 1.0f); gl_PointSize = pointSize; // 获取点内的坐标,范围为[0,1],中心是(0.5,0.5) pointUV = gl_PointCoord; }注:如果不需要纹理功能,可直接删除原有的纹理相关输入输出。
修改片元着色器,实现圆形点渲染
更新Main.frag,通过点内坐标判断当前像素是否在圆内,超出范围则丢弃,同时添加边缘平滑实现抗锯齿:#version 400 in vec4 colorOut; in vec2 pointUV; out vec4 pixelColor; void main(void) { // 计算当前像素到点中心的距离 float dist = length(pointUV - vec2(0.5)); // 距离超过0.5则丢弃像素(只保留圆形区域) if(dist > 0.5) discard; // 用smoothstep实现边缘平滑过渡,消除锯齿 float alpha = smoothstep(0.5, 0.48, dist); pixelColor = vec4(colorOut.rgb, colorOut.a * alpha); }渲染时设置点大小
在调用glDrawArrays(GL_POINTS, ...)之前,通过uniform传递点的大小:// 初始化着色器时保存pointSize的uniform位置 GLint pointSizeLoc = glGetUniformLocation(shaderProgram, "pointSize"); // 设置点大小,示例为10.0 glUniform1f(pointSizeLoc, 10.0f);保留混合设置
你的初始化代码中已经启用了GL_BLEND和对应的混合函数,这是实现圆形边缘透明平滑的必要条件,无需修改。
可选兼容方案(不推荐)
如果暂时不想修改着色器,可以将compatibleContext设为true,使用兼容性模式创建OpenGL 4.0上下文,此时GL_POINT_SMOOTH可能仍然生效。但该方案依赖旧特性,不利于长期维护,建议优先使用着色器方案。
内容的提问来源于stack exchange,提问作者DavidH

