You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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被移除,固定功能管线的点大小控制和抗锯齿逻辑不再生效。要实现圆形顶点,必须通过着色器程序自定义点的形状。

具体修改步骤

  1. 更新顶点着色器,传递点大小并输出点内坐标
    修改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;
    }
    

    注:如果不需要纹理功能,可直接删除原有的纹理相关输入输出。

  2. 修改片元着色器,实现圆形点渲染
    更新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);
    }
    
  3. 渲染时设置点大小
    在调用glDrawArrays(GL_POINTS, ...)之前,通过uniform传递点的大小:

    // 初始化着色器时保存pointSize的uniform位置
    GLint pointSizeLoc = glGetUniformLocation(shaderProgram, "pointSize");
    // 设置点大小,示例为10.0
    glUniform1f(pointSizeLoc, 10.0f);
    
  4. 保留混合设置
    你的初始化代码中已经启用了GL_BLEND和对应的混合函数,这是实现圆形边缘透明平滑的必要条件,无需修改。

可选兼容方案(不推荐)

如果暂时不想修改着色器,可以将compatibleContext设为true,使用兼容性模式创建OpenGL 4.0上下文,此时GL_POINT_SMOOTH可能仍然生效。但该方案依赖旧特性,不利于长期维护,建议优先使用着色器方案。


内容的提问来源于stack exchange,提问作者DavidH

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 04:32:06