如何在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
相关产品推荐
相关产品推荐

