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

如何在OpenGL中绘制带圆角的矩形?

OpenGL绘制带圆角矩形的实现方案

用户需求

想要绘制一个带圆角的矩形(示例如下):
带圆角的矩形

倾向使用**立即模式(Immediate Mode)**实现,也接受着色器方案,希望实现如下形式的方法:

public static void drawRectWithRoundedCorners(float x, float y, float width, float height, float radius, Color color) {
    // code
}

曾尝试用纹理实现,但存在边角拉伸问题,因此放弃该方案。


方案一:立即模式实现(旧版OpenGL固定管线)

立即模式通过glBegin()/glEnd()直接提交顶点,核心思路是把圆角矩形拆分为4条直线边 + 4个1/4圆弧,逐个绘制填充。注意:该模式在OpenGL 3.0+核心模式中已被废弃,仅适用于旧版API(如OpenGL ES 1.x)。

代码实现:

import static org.lwjgl.opengl.GL11.*;

public static void drawRectWithRoundedCorners(float x, float y, float width, float height, float radius, Color color) {
    // 限制圆角半径不超过矩形最小边的一半,避免异常图形
    radius = Math.min(radius, Math.min(width, height) / 2f);
    
    // 设置绘制颜色
    glColor4f(color.getRed() / 255f, color.getGreen() / 255f, 
              color.getBlue() / 255f, color.getAlpha() / 255f);
    
    // 计算四个圆角的圆心坐标
    float tlCenterX = x + radius;
    float tlCenterY = y + height - radius;
    float trCenterX = x + width - radius;
    float trCenterY = y + height - radius;
    float blCenterX = x + radius;
    float blCenterY = y + radius;
    float brCenterX = x + width - radius;
    float brCenterY = y + radius;
    
    // 圆弧分段数,数值越高圆角越平滑
    int arcSegments = 16;
    float angleStep = (float)(Math.PI / 2) / arcSegments;

    // 绘制顶部直线区域
    glBegin(GL_QUADS);
    glVertex2f(trCenterX, y + height);
    glVertex2f(tlCenterX, y + height);
    glVertex2f(tlCenterX, tlCenterY);
    glVertex2f(trCenterX, trCenterY);
    glEnd();

    // 绘制右侧直线区域
    glBegin(GL_QUADS);
    glVertex2f(x + width, trCenterY);
    glVertex2f(x + width, brCenterY);
    glVertex2f(brCenterX, brCenterY);
    glVertex2f(trCenterX, trCenterY);
    glEnd();

    // 绘制底部直线区域
    glBegin(GL_QUADS);
    glVertex2f(tlCenterX, y);
    glVertex2f(brCenterX, y);
    glVertex2f(brCenterX, brCenterY);
    glVertex2f(tlCenterX, blCenterY);
    glEnd();

    // 绘制左侧直线区域
    glBegin(GL_QUADS);
    glVertex2f(x, tlCenterY);
    glVertex2f(x, blCenterY);
    glVertex2f(blCenterX, blCenterY);
    glVertex2f(tlCenterX, tlCenterY);
    glEnd();

    // 绘制左上1/4圆弧(扇形填充)
    glBegin(GL_TRIANGLE_FAN);
    glVertex2f(tlCenterX, tlCenterY);
    for (int i = 0; i <= arcSegments; i++) {
        float angle = (float)(Math.PI / 2) + i * angleStep;
        float px = tlCenterX + (float)Math.cos(angle) * radius;
        float py = tlCenterY + (float)Math.sin(angle) * radius;
        glVertex2f(px, py);
    }
    glEnd();

    // 绘制右上1/4圆弧
    glBegin(GL_TRIANGLE_FAN);
    glVertex2f(trCenterX, trCenterY);
    for (int i = 0; i <= arcSegments; i++) {
        float angle = (float)Math.PI + i * angleStep;
        float px = trCenterX + (float)Math.cos(angle) * radius;
        float py = trCenterY + (float)Math.sin(angle) * radius;
        glVertex2f(px, py);
    }
    glEnd();

    // 绘制右下1/4圆弧
    glBegin(GL_TRIANGLE_FAN);
    glVertex2f(brCenterX, brCenterY);
    for (int i = 0; i <= arcSegments; i++) {
        float angle = (float)(Math.PI * 3 / 2) + i * angleStep;
        float px = brCenterX + (float)Math.cos(angle) * radius;
        float py = brCenterY + (float)Math.sin(angle) * radius;
        glVertex2f(px, py);
    }
    glEnd();

    // 绘制左下1/4圆弧
    glBegin(GL_TRIANGLE_FAN);
    glVertex2f(blCenterX, blCenterY);
    for (int i = 0; i <= arcSegments; i++) {
        float angle = i * angleStep;
        float px = blCenterX + (float)Math.cos(angle) * radius;
        float py = blCenterY + (float)Math.sin(angle) * radius;
        glVertex2f(px, py);
    }
    glEnd();
}

方案二:着色器实现(现代OpenGL核心模式)

现代OpenGL推荐使用着色器方案,通过片元着色器判断像素是否在圆角区域内,无需拆分图形,绘制效率更高。

1. 顶点着色器

#version 330 core
layout (location = 0) in vec2 aPos;

uniform mat4 model;
uniform mat4 projection;

void main() {
    gl_Position = projection * model * vec4(aPos, 0.0, 1.0);
}

2. 片元着色器

#version 330 core
out vec4 FragColor;

uniform vec2 rectSize;
uniform float radius;
uniform vec4 color;

void main() {
    // 转换为矩形局部坐标系(以矩形中心为原点)
    vec2 localPos = gl_FragCoord.xy - vec2(rectSize.x / 2, rectSize.y / 2);
    vec2 halfRect = rectSize / 2.0 - vec2(radius);

    // 判断像素是否在圆角外
    vec2 dist = abs(localPos) - halfRect;
    float outside = length(max(dist, 0.0)) + min(max(dist.x, dist.y), 0.0);

    if (outside > 0.0) discard; // 丢弃圆角外的像素
    FragColor = color;
}

3. Java方法实现

import static org.lwjgl.opengl.GL30.*;
import org.joml.Matrix4f;
import org.joml.Vector3f;

// 假设已提前初始化VAO、VBO和着色器程序
private static int shaderProgram;
private static int vao;

public static void drawRectWithRoundedCorners(float x, float y, float width, float height, float radius, Color color) {
    radius = Math.min(radius, Math.min(width, height) / 2f);
    
    // 绑定着色器程序并设置Uniform参数
    glUseProgram(shaderProgram);
    glUniform2f(glGetUniformLocation(shaderProgram, "rectSize"), width, height);
    glUniform1f(glGetUniformLocation(shaderProgram, "radius"), radius);
    glUniform4f(glGetUniformLocation(shaderProgram, "color"),
                color.getRed()/255f, color.getGreen()/255f,
                color.getBlue()/255f, color.getAlpha()/255f);
    
    // 设置模型矩阵(平移到目标位置)
    Matrix4f model = new Matrix4f().translate(new Vector3f(x, y, 0.0f));
    int modelLoc = glGetUniformLocation(shaderProgram, "model");
    glUniformMatrix4fv(modelLoc, false, model.get(new float[16]));
    
    // 绘制矩形(VAO中存储了矩形的6个顶点,由两个三角形组成)
    glBindVertexArray(vao);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    glBindVertexArray(0);
}

注意事项

  • 立即模式仅适用于旧版OpenGL项目,现代项目优先选择着色器方案。
  • 必须限制圆角半径不超过矩形最小边的一半,否则会出现图形异常。
  • 着色器方案中,局部坐标的计算需根据实际MVP矩阵调整,确保坐标转换逻辑正确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 23:10:26