资讯详情

OpenGL ES圆形绘制:从三角扇到抗锯齿的完整实践

📅 2026/10/14 18:00:28 | 华诺云谱 👁 阅读
OpenGL ES圆形绘制:从三角扇到抗锯齿的完整实践
简介这份资源以 Android 平台上的 OpenGL ES 图形绘制为主题适合刚接触图形渲染或需要快速实现基础几何绘制的移动开发者。内容围绕圆形展开覆盖圆点、圆环线条和填充圆饼三类绘制效果可用于自定义控件、可视化界面或图形学入门练习。压缩包共 567 个文件大小约 9.68MB内部以 xml 布局与配置、flat 资源文件、json 数据描述、png 图片资源以及少量 java、gradle 与 glsl 着色器文件为主整体属于完整的 Android 工程结构便于直接导入查看和运行。目前已有 794 人学习下载。通过该资源可以了解 OpenGL ES 顶点坐标设置、绘制模式选择以及着色器在圆形渲染中的基本用法同时观察一个工程内多种资源类型的组织方式对梳理 Android 图形开发流程具有一定参考价值。1. 用 OpenGL ES 绘制圆形一个看似简单却卡住很多人的起点很多刚从三角形、矩形过渡到圆形的开发者第一反应是把顶点坐标直接写死在数组里然后复制粘贴三角形那套渲染逻辑。结果屏幕上一片空白或者出现一个诡异的椭圆、一个边缘被撕碎的锯齿状多边形。我见过不少人在这一步卡上一两天最后发现根本不是 API 用错而是对「圆形到底该怎么用三角形逼近」这件事没有概念。OpenGL ES 只认三角形圆形只是无数个等腰三角形拼出来的近似结果分割数、坐标生成方式、shader 里的插值行为每一个环节都在影响最终渲染效果。这篇文章从 shader 与顶点数据两个角度把这个方案拆开讲先聊清楚原理再给可以直接落地的最小实现最后讲几个真正会浪费你时间的坑。适合刚学完基础三角形渲染、准备往复杂图形走的开发者。2. 圆形绘制的核心扇区拆解与片元着色器的配合2.1 从三角扇到顶点坐标为什么 gl_Position 的 w 分量会捣乱绘制圆形的常见做法是使用GL_TRIANGLE_FAN图元模式把圆心作为首个顶点圆环上按角度均匀取若干顶点依次连接成一个个三角形扇面。顶点数量越多圆越平滑但顶点缓冲区和顶点着色器的计算压力也会等比例上升。一个直径占屏幕三分之一的圆分割 64 段肉眼已经看不出区别分割 128 段则纯属性能浪费。移动端 GPU 的顶点处理能力虽然不弱但片元着色器的开销往往更大圆形的片元覆盖面积大过度分割带来的收益远低于想象。顶点坐标生成的核心思路是遍历0到segmentCount的整数索引把每个索引映射到圆周上的角度再用三角函数算出该角度对应的 x 和 y 偏移量以圆心坐标为中心做偏移累加。角度从 0 到 2π 均匀分布首尾两个顶点位置相同这是三角扇闭合所必需的缺失最后一个重复顶点会导致圆环出现一条裂缝。示例代码如下// C 侧生成圆形顶点数据stride 为 3 个 floatx, y, 固定 z0 std::vectorfloat genCircleVertices(float cx, float cy, float radius, int segments) { std::vectorfloat verts; verts.reserve((segments 2) * 3); // 第一个顶点圆心 verts.push_back(cx); verts.push_back(cy); verts.push_back(0.0f); // 圆环顶点从 0 到 2π最后一个顶点与第一个环上顶点重合 for (int i 0; i segments; i) { float angle 2.0f * M_PI * i / segments; verts.push_back(cx radius * cosf(angle)); verts.push_back(cy radius * sinf(angle)); verts.push_back(0.0f); } return verts; }顶点数量是segments 2比直觉上的segments 1多一个。原因是圆心已经占用一个顶点环上需要segments 1个顶点才能首尾闭合其中最后一个顶点与索引 1 的顶点坐标完全一致。如果你在渲染时发现圆环上有一个缺口多半是这里少算了一个顶点。圆心的 z 固定为 0意味着所有顶点处于同一个平面后续通过 uniform 传入视口变换矩阵时这个平面会被整体映射到投影空间。如果你在屏幕上看到的是椭圆而不是正圆问题几乎一定出在投影矩阵的宽高比处理上而不是顶点坐标本身。顶点坐标进入渲染管线后需要经过顶点着色器处理才能变成真正用于光栅化的裁剪坐标。此时有一个容易被忽略的细节gl_Position 输出的其实是齐次坐标它的 w 分量会被用来做透视除法。如果你在顶点着色器里直接做正交投影w 固定为 1.0透视除法不起作用画面正常。但一旦你引入透视投影矩阵w 就不再是 1顶点坐标会被除以 w导致圆的尺寸、位置与你的预期产生偏离。很多人写投影矩阵时偷懒把 w 分量填成 1则会直接导致透视效果失效。2.2 片元着色器里的距离判据纯数学方法绘制抗锯齿圆除了用三角形扇逼近圆形还有另一种常见方案绘制一个恰好覆盖圆形范围的矩形在片元着色器里逐像素计算该像素到圆心的距离距离小于半径则输出圆色否则丢弃或输出背景色。这种做法的优势在于不需要生成大量扇形顶点矩形只需 4 个顶点、6 个索引GPU 的顶点处理负载可以忽略不计瓶颈完全转移到片元着色器的数学计算上。片元着色器里的核心判据是length(uv - center) radius。这里的 uv 是片元在局部坐标系中的坐标通过矩形的顶点数据插值而来。这个方案对很多开发者来说是黑匣子不知道 uv 怎么来也不知道为什么插值出来的坐标是连续的。实际上顶点着色器输出到片元着色器的 varyin 变量在光栅化阶段会自动按片元位置做透视校正插值所以你不需要在每个片元重新计算坐标只需要接收插值结果即可。示例实现如下precision mediump float; varying vec2 v_uv; // 矩形范围内的归一化坐标范围 0~1 uniform vec2 u_center; // 圆心在 uv 空间中的位置例如 (0.5, 0.5) uniform float u_radius; // 半径在 uv 空间中的大小例如 0.4 void main() { float dist distance(v_uv, u_center); // 硬边缘距离大于半径直接丢弃片元 // if (dist u_radius) { discard; } // 抗锯齿边缘距离在 [radius-0.5px, radius0.5px] 之间平滑过渡 float aaWidth 0.5 / 600.0; // 假设视口宽 600半个像素对应 uv 宽度 float alpha 1.0 - smoothstep(u_radius - aaWidth, u_radius aaWidth, dist); gl_FragColor vec4(1.0, 0.4, 0.2, alpha); }这段代码里我故意保留了硬边缘的 discard 逻辑注释掉之后使用抗锯齿版本。三种写法各有适用场景后面细聊。smoothstep 接收三个参数第一个和第二个参数定义过渡区间第三个参数是原始距离值。当 dist 小于radius - aaWidth时alpha 为 1当 dist 大于radius aaWidth时alpha 为 0中间区间做平滑插值产生视觉上的边缘羽化效果。纯矩形加距离判据的方案顶点数量从数百个骤降到 4 个对复杂 UI 场景例如地图上的圆点标记、按钮的高亮光圈能明显降低每帧的顶点提交开销。但它的局限性在于圆形中心的 uv 坐标很难精确对应到屏幕像素如果矩形被拉伸成非正方形圆形会变成椭圆。此时你需要把 uv 坐标乘以矩形宽高比做修正或者直接传入分辨率 uniform 做像素级计算。简单场景下三角扇方案更直观距离判据方案更优雅选择依据是你的性能预算和圆的密度。3. 用三角扇绘制圆形从 cpp 侧数据到 GLSL 的完整链路3.1 顶点缓冲区与属性绑定布局是新手最容易出错的地方确定了三角扇方案后下一步是建立 OpenGL ES 可用的顶点缓冲区。你需要先创建 VBO 和 VAO把顶点数据上传到 GPU 显存然后告诉 OpenGL 每个顶点属性的排列方式。属性布局描述的是「每几个 float 是一个顶点每个顶点中的偏移量分别对应哪些属性」。这里最常见的低级错误是 stride 和 offset 填错导致渲染结果出现错位整个圆变成一团乱麻。示例代码如下GLuint vbo, vao; glGenVertexArrays(1, vao); glGenBuffers(1, vbo); glBindVertexArray(vao); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, verts.size() * sizeof(float), verts.data(), GL_STATIC_DRAW); // 顶点位置属性location 0 glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);这段代码里3 * sizeof(float)是 stride表示每个顶点占用 3 个 float 的大小偏移量是 0因为位置属性从每个顶点的第一个 float 开始。如果你的顶点数据包含了颜色或纹理坐标stride 要改成所有属性字节数之和偏移量依次累加。改动之后顶点着色器里的 attribute 声明要与 stride 布局严格对应。很多开发者在只使用位置属性时会把 stride 写成0意思是「紧密排列」。这在顶点数据确实紧密排列时也能工作但一旦后续往同一个 VBO 追加了颜色数据stride 为 0 就导致所有顶点都只读取前三个 float画面直接错乱。我习惯上即使只有一个属性也把 stride 写完整这是一个很好的习惯能避免后期扩展数据结构时踩坑。绑定完属性之后还需要 prepare shader 程序。你需要编译顶点着色器和片元着色器链接成 program然后通过glGetUniformLocation获取统一变量地址。常见做法是把 shader 编译封装成一个工具函数失败时打印日志避免每次写重复的胶水代码。我一般会把编译日志输出封装成模板调试时直接看 logcat 里的报错信息。3.2 顶点着色器MVP 矩阵、位置变换与 gl_PointSize 的取舍顶点着色器要做的事是把模型坐标系中的圆形顶点变换到裁剪坐标系。由于圆的顶点坐标已经是以圆心为中心的局部坐标你需要在 CPU 端拼好模型矩阵、视图矩阵和投影矩阵作为 uniform 传入。这段逻辑大多数人抄来抄去却很少搞清楚顺序gl_Position 的右乘顺序是projection * view * model * localPos颠倒顺序的结果是图形被强行扭曲。uniform mat4 u_mvp; attribute vec4 a_position; void main() { gl_Position u_mvp * a_position; }这段代码只有两行但有一个关键细节a_position 是 vec4而你在 cpp 里传入的顶点数据是 vec3。OpenGL ES 的顶点属性绑定规则是shader 声明 vec4、数据只有 3 个分量时缺失的 w 分量自动补 1。所以这里不会报错但你要意识到 w 被补成了 1。如果你的 cpp 侧数据直接传了 4 个分量stride 和 offset 都要同步调整否则后面全乱。出于性能考虑有人会把 MVP 矩阵的乘法从顶点着色器挪到 CPU 端先算好最终矩阵再传入单个 uniform。在顶点数极少比如只有 4 个顶点的矩形方案时意义不大在顶点数上千时CPU 端预先计算可以减少 GPU 的每顶点矩阵乘法开销。不过移动端 GPU 处理 4x4 矩阵乘 vec4 的效率很高这个优化优先级很低先把功能跑通最重要。也有一种极简思路不使用 MVP 矩阵直接在顶点着色器里把坐标换算到归一化设备坐标NDC。如果圆形的绘制范围固定可以手工算出每个顶点在 NDC 中的位置存进 VBOshader 里直接赋值给 gl_Position。这种做法省掉了 uniform 上传和矩阵运算但牺牲了灵活性——圆形不能移动、不能缩放。适合静态背景装饰元素。常见做法是先用矩阵方案跑通确认渲染效果后再决定是否对静态元素做特化优化这是一条可行路径。gl_PointSize 属于另一种极端把每个顶点渲染成一个方形点在片元着色器里裁成圆形。使用GL_POINTS图元在顶点着色器里设置 gl_PointSize。这种方式适合散点图、粒子效果绘制大量半径固定的小圆时性能极高因为不需要生成三角形。它的上限是单个点的大小受实现限制通常在 64 到 256 像素之间大圆无法用它绘制所以并不适用于「绘制圆形」这个标题下的通用场景。3.3 绘制调用与双缓冲刷新知道每一条指令在干什么顶点缓冲区和着色器就绪以后每帧绘制调用的格式基本是固定的清屏、绑定 shader、绑定 VAO、上传 uniform、调用 glDrawArrays、交换缓冲区。很多初学者把这步写成一坨不知道哪里出了问题其实每个调用都有明确职责。glViewport(0, 0, screenWidth, screenHeight); glClearColor(0.0f, 0.5f, 0.6f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(program); // 上传圆心、半径等 uniform glUniform2f(centerLocation, 0.0f, 0.0f); glUniform1f(radiusLocation, 0.8f); glBindVertexArray(vao); glDrawArrays(GL_TRIANGLE_FAN, 0, vertexCount); eglSwapBuffers(display, surface);glViewport 设置渲染区域一般和屏幕尺寸一致不一致时画面会被裁剪或拉伸圆形会变形。我遇到过一次诡异问题画面下半部分永远是黑的排查了半天发现是 glViewport 的 y 和 height 写反了这在竖屏和横屏切换时最容易发生。glDrawArrays 的第一个参数是图元类型第二个是起始顶点索引第三个是顶点数量。这里第一次写时容易把第三个参数误写成 segmentCount少算了一个圆心顶点导致整个圆被逆时针旋转了一个小角度边缘出现一个微小的缺口。另一个值得说清的细节是 glUniform 上传的时机。uniform 是 program 级别的状态只要在 glUseProgram 之后上传一次即可。有些开发者把 uniform 上传放在每帧循环里而且每次循环都重新计算 MVP 矩阵这是多余开销。如果圆形是静态的矩阵可以只在初始化时计算一次如果圆形跟随手势移动才需要在 touch 事件里更新并重新上传。区分静态与动态能省掉大量无效计算也让代码结构更清晰。4. 圆形的纹理映射与采样边界绕开 UV 坐标的常见误区4.1 用纹理绘制圆形圆形 UI 控件最常见的实现方式实际开发中大量圆形元素根本不是用数学几何方式渲染的而是直接贴一张圆形的纹理图。比如头像、按钮背景、地图标记点纹理方案更简单也更容易做出复杂视觉效果。做法是创建一个纹理把圆形的 alpha 通道存进去然后在矩形上采样该纹理得到透明边缘的圆形视觉。这个方案的顶点数据非常简单两个三角形组成一个矩形纹理坐标 0 到 1 均匀映射。precision mediump float; varying vec2 v_texCoord; uniform sampler2D u_texture; uniform vec4 u_tintColor; // 叠加色调例如白色表示不调色 void main() { vec4 texColor texture2D(u_texture, v_texCoord); // 用纹理的 alpha 通道控制圆形边缘 gl_FragColor vec4(texColor.rgb * u_tintColor.rgb, texColor.a * u_tintColor.a); }这段着色器里圆形效果完全由纹理内容的 alpha 决定GPU 不需要做距离计算。纹理方案的美术成本远高于纯 shader 方案但优势是边缘可以做出半透明渐变、描边、阴影、内发光等复杂效果纯数学方案实现这些会非常繁琐。如果你的圆形 UI 元素种类很多纹理图集会占用不少显存可以用压缩纹理格式缓解。纹理采样最常遇到的坑是边缘漏边矩形四个角的采样点紧贴纹理边缘如果纹理 mipmap 或过滤模式设置不当边缘会出现一圈半透明的杂色。解决方式是给圆形纹理加一圈透明描边或者将纹理环绕模式设置为GL_CLAMP_TO_EDGE并配合线性过滤。还有一种更隐蔽的问题帖纹理的矩形顶点坐标和纹理坐标的方向关系。OpenGL ES 的纹理坐标系原点在左下角而图片解码出来的像素数据首行在顶部如果你直接用解码后的缓冲区创建纹理且不做翻转贴出来的圆形会上下颠倒。常见做法是在顶点数据里把纹理坐标的 y 翻转或者在上传纹理时用GL_UNPACK_FLIP_Y_WEBGL控制各平台 API 支持不同需要单独验证。4.2 小圆与大圆的 LOD 策略什么时候该切换渲染方案圆形渲染存在一个容易被忽略的性能分层问题小圆直径小于 32 像素和大圆直径超过屏幕三分之二的 GPU 开销分布完全不同。小圆用纹理方案时纹理取样开销占比高用三角扇方案时顶点数如果保持 64顶点处理占比反而低但可能出现锯齿。大圆用三角扇时即使分割到 128 段边缘锯齿依然能被看出来尤其在做慢速旋转时摩尔纹非常明显。我一般在同一套渲染器里按半径动态选择方案半径小于某个阈值时走纹理方案或点精灵半径较大时走三角扇并提高 segmentCount。切割阈值取决于设备像素密度建议运行测帧率实测定。类似这种对同一对象多版本实现的做法在图形学里叫 LODLevel of Detail。移动端功耗敏感合理切换可以显著降低渲染开销。需要注意的是切换方案时圆心位置、半径、颜色这些逻辑参数要保持一致否则画面会出现跳变。另一种提升大圆质量的方法是在片元着色器里做多重采样抗锯齿MSAA之外的 FXAA 或形态学抗锯齿。不过对大多数圆形场景MSAA 已经足够。OpenGL ES 3.0 支持在 EGL 配置里请求 MSAA比如请求 4 倍采样然后正常绘制所有图元即可光栅化阶段自动对边缘做平滑处理。这是移动端最成熟的抗锯齿方案付出的代价是内存带宽和功耗上升对低端机型影响明显。5. 画圆形必踩的 5 个坑从空白屏幕到诡异的椭圆5.1 分割数与角度偏移圆环缺口的真相现象绘制出来的圆形不闭合有一道从圆心到圆环边缘的缝隙或者圆环上多了一个三角形凸起。分割数越大缝隙越窄但始终存在。原因典型错误有两个。一是顶点数少算了一个圆环上的顶点只生成到segments - 1没有闭合到起点。二是角度偏移写法写错循环从 1 到 segments导致第一个与最后一个顶点不重合。根本问题是三角扇要求首尾顶点坐标完全一致任何偏移都会留下一条不可见的裂缝片元填充时因为三角形不闭合而显示背景色。解决遍历范围写0到segments共segments 1个环上顶点。角度使用2 * PI * i / segments其中i 0和i segments指向同一点。我把这个检查项写进了自己的代码模板每次生成顶点数据后先打印首尾两个顶点的坐标肉眼确认一致再继续。5.2 glDrawArrays 的 vertexCount 填错导致圆形缺角现象圆形渲染出来少了四分之一到三分之一但没有裂缝边缘平滑闭合。原因glDrawArrays的第三个参数传入的是索引 0 开始的顶点个数不是分割数。如果你习惯性地填了segments实际上少画了一个环上顶点整个圆从某个角度被截掉一块。诡异的是截掉的部分并不总是一个三角形因为三角扇中共享圆心的覆盖方式会漏掉一段向心区域。解决vertexCount 必须等于segments 2。我通常在生成顶点数组的同时记录顶点数量绘制时直接使用记录值而不是临时计算避免下次修改 segments 时漏改 glDrawArrays。这道坑真的能卡掉不少人因为它不报错、不闪黑屏只是圆形缺了一块不仔细看根本发现不了。5.3 宽高比变形圆形变椭圆现象圆形渲染成椭圆横轴和纵轴长度不同切换横竖屏时椭圆程度随之变化。原因MVP 矩阵中的投影矩阵没有考虑视口宽高比。正交投影的 left/right/top/bottom 如果设置对称但数值未按宽高比缩放NDC 中 x 和 y 的缩放比例不同圆形在屏幕上就会被拉伸。透视投影同理宽高比参数设置错误也会导致圆变成椭圆。解决在计算投影矩阵时使用实际视口宽高比。正交投影可以用left -aspect、right aspect、bottom -1.0、top 1.0aspect 等于 screenWidth 除以 screenHeight。横竖屏切换时重新创建投影矩阵并重新上传 uniform。作为额外排障手段可以在 CPU 端计算圆上某点的 NDC 坐标手动验证 x 和 y 的缩放关系确认矩阵正确后再上 GPU。5.4 屏幕边缘被裁剪圆的半径看似缩小了现象圆形没有变形但整体偏小尤其当圆心靠近视口边缘时有一侧被屏幕裁剪掉看起来像半径变化。原因圆心坐标处于 NDC 范围之外时超出视锥体的部分被裁剪是正确行为。常见误判是开发者把圆心坐标设置成窗口像素坐标比如直接传了(200, 300)而 MVP 矩阵期望的是局部坐标或 NDC 坐标数值超出 [-1, 1] 的边界自然被裁剪掉。解决圆心坐标先映射到 NDC 或局部坐标系再做矩阵变换。窗口像素坐标到 NDC 的换算公式是ndcX pixelX / screenWidth * 2 - 1ndcY 同理Y 轴还需要翻转方向。这个换算最容易在竖屏、横屏来回切换时出错建议把换算独立成函数写单元测试验证边界值。5.5 抗锯齿无效边缘锯齿明显但是 MSAA 没生效现象代码里配置了 4x MSAA但实际渲染出来的圆形边缘依然有明显锯齿放大后能看到阶梯状像素。原因部分设备的 EGL 配置请求了 MSAA但 GL surface 实际创建失败随后 fallback 到非 MSAA 配置而代码没有检测配置是否成功。还有一种情况是你在 framebuffer 对象FBO上离屏渲染没有为 FBO 配置 multisample renderbufferMSAA 只在默认帧缓冲上生效离屏路径完全没走抗锯齿。解决EGL 配置选择后通过eglGetConfigAttrib读取实际的EGL_SAMPLES数值确认大于等于请求值。对于 FBO 离屏渲染使用glRenderbufferStorageMultisampleAPPLEiOS或glRenderbufferStorageMultisampleES 3.0创建带样本数的 renderbuffer并在绘制完成后做 resolve。移动端不同厂商实现差异较大最简单的验证方式是截帧放大查看边缘像素的覆盖率肉眼判断是否真的做了多重采样。6. 进阶动态圆心、半径动画与画面调试技巧圆形渲染的进阶需求集中在动态变化圆心跟随手指移动、半径随滚动缩放、颜色渐变等。动态更新不必重新生成 VBO只需要修改 uniform 或者每帧更新一个很小的缓冲区。圆心可以通过 uniform 传入在顶点着色器里加到顶点位置上这样 VBO 里存的是未偏移的圆形坐标任何位置变化都不需要碰 VBO代价是每帧多一次 uniform 上传性价比极高。半径动画同样不需要重新生成顶点数据把半径作为 uniform 传入顶点着色器乘以顶点位置即可。这样可以做出呼吸灯效果、点击扩散波纹效果顶点数量固定片元开销也固定。示例逻辑如下// 顶点着色器伪代码 uniform float u_radiusScale; // position.xy 是单位圆坐标半径 1实际半径通过 scale 控制 gl_Position u_mvp * vec4(a_position.xy * u_radiusScale, 0.0, 1.0);shader 里做归一化处理的顶点数据把半径计算放到了 GPU 侧CPU 只需要更新一个 float 的 uniform比重新生成坐标序列简单得多。但要小心精度问题如果 scale 过大或者过小float 精度会导致顶点位置轻微抖动。把单位圆坐标的半径设为 1乘以 scale 之后数值范围可能在 0.0001 到 1000 之间此时建议用 double 只在 CPU 端计算矩阵shader 内仍然用 floatopenGL ES 3.0 的 mediump float 在部分低端 GPU 上精度不稳定必要时改用 highp。调试方面的习惯也值得提一下。新手面对空白屏幕时最有效的排障路径是逐步简化而不是反复检查代码找 bug。把 MVP 矩阵固定为单位矩阵如果圆心设成 (0,0)、半径 0.5此时圆形应该占满屏幕中央画面正常就说明 VBO 和 shader 没问题问题定位在矩阵运算。再进一步直接在 CPU 端打印几个顶点的变换后坐标验证数据在进入 shader 前就正确。切忌在没跑通最小示例前直接引入缩放、旋转、色彩混合等复杂功能出问题时变量太多。混合模式也常常是「圆形看起来不对」的元凶如果开启了 blending 却没有设置 blend func半透明边缘的叠加效果会非常诡异边缘出现黑边或亮边。圆形的边缘羽化用glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)是标准方案但如果你使用了预乘 alpha 纹理则需要GL_ONE, GL_ONE_MINUS_SRC_ALPHA。不匹配时半透明像素会出现色偏。这个细节要格外注意因为它不产生明显错误只让圆形边缘看起来脏脏的。还有一个容易误导新人的点很多自定义圆形控件会被系统 UI 层覆盖看起来像「没画出来」其实是绘制顺序或深度缓冲的问题。如果你启用了深度测试却没有正确设置深度值圆形可能与背景深度相同被 z-fighting 吃掉。排查时先关掉深度测试和混合用纯色背景渲染排除状态干扰后再逐步开启。写到最后我还有一个习惯想分享每次写圆形渲染相关的代码我都会先在白纸上画出圆心、半径、分割数对应的坐标草图然后手动算出前三个顶点和最后两个顶点的坐标。这些值会直接写进测试用例渲染结果与测试预期一致了再开始做动画和交互。如果你也被圆形边缘的锯齿困扰过或者遇到过圆形缺口但找不到原因大概率是某一节里提到的细节没注意到。希望这篇笔记能帮你少走一段弯路。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑