图形学【免费下载链接】webgl-fundamentalsWebGL lessons that start with the basics项目地址https://gitcode.com/gh_mirrors/we/webgl-fundamentals点击查看免费下载本指南源于 webgl-fundamentals 项目 QA 系列中的经典问题当三角形的部分顶点落在裁剪空间clip space之外时如何在片段着色器中丢弃这些被裁剪的片段。文章将给出两种完整可运行的实现方案——基于 varying 插值的逐像素判定以及更高效的整三角形剔除方案并深入讲解裁剪空间的数学原理与 WebGL 渲染管线的数据流向读者读完后可以在自己的 WebGL 项目中直接套用这两种着色器模板。问题场景为什么片段着色器看不到顶点在 WebGL 中一个三角形经过顶点着色器后会被光栅化rasterize成一堆覆盖的像素随后每个像素都会调用一次片段着色器来询问颜色。正如 WebGL 工作原理 中所讲片段着色器默认只能拿到极少的逐像素信息当前像素在画布/帧缓冲上的屏幕坐标即gl_FragCoord以及深度缓冲值。顶点本身并不会自动传到片段着色器。因此如果你在片段着色器中尝试访问某个顶点的裁剪状态会直接撞上数据隔离的墙。想要在片段着色器里拿到顶点信息唯一的途径是在顶点着色器中把数据算好通过 varying 传递并插值到片段着色器。前置知识如何判断一个顶点被裁剪要检测裁剪先要明确裁剪的数学定义。顶点着色器输出的gl_Position处于裁剪空间clip spaceWebGL 之后会自动将xyz除以w得到位于 -1 到 1 之间的标准设备坐标NDC。这个自动除以w的行为在 3D 透视教程 中有详细推导gl_Position vec4(position.xyz, zToDivideBy)与手动除法完全等价。一个顶点只有同时满足以下条件才位于可见区域内不会被裁剪-w x w -w y w -w z w用 GLSL 的内建函数可以把它写成一句布尔表达式bool vertexClipped(vec4 clipspace) { return any(lessThan(clipspace.xyz, -clipspace.www)) || any(greaterThan(clipspace.xyz, clipspace.www)); }lessThan/greaterThan对vec3逐分量比较返回bvec3any只要有一个分量为 true 就整体为 true。只要x、y、z中任意一个分量超出[-w, w]范围这个顶点就被判定为裁剪。方案一用 varying 把裁剪标志插值给片段着色器最简单直接的思路是在顶点着色器中计算当前顶点是否被裁剪把结果作为 varying 传出varying 在光栅化时会被线性插值因此只要三角形任一顶点被裁剪插值结果必然大于 0片段着色器据此discard。完整的可运行示例见 示例 1varying 插值判定核心着色器代码如下// 顶点着色器 attribute vec4 position; uniform mat4 matrix; varying float clipped; void main() { gl_Position matrix * position; clipped ( any(lessThan(gl_Position.xyz, -gl_Position.www)) || any(greaterThan(gl_Position.xyz, gl_Position.www)) ) ? 1.0 : 0.0; }// 片段着色器 precision highp float; varying float clipped; void main() { if (clipped 0.0) { discard; } gl_FragColor gl_FrontFacing ? vec4(1, 0, 0, 1) : vec4(0, 0, 1, 1); }这段代码的运作原理非常巧妙顶点着色器中被裁剪的顶点把clipped设为1.0未被裁剪的设为0.0光栅化时WebGL 会按 着色器与 GLSL 中描述的 varying 机制对三角形三个顶点的值做插值。如果三个顶点全部在可见区域内三角形内所有像素的clipped都是0.0只要任一顶点被裁剪靠近该顶点的像素插值结果就会大于0.0片段着色器中用clipped 0.0而不是 1.0判断正是利用了插值特性——绝大多数像素得到的都是介于 0 和 1 之间的中间值。方案二整三角形剔除让被裁剪的三角形根本不参与光栅化varying 方案虽然正确但存在一个效率问题它仍然会光栅化那些部分被裁剪的三角形只是事后在片段着色器里逐像素discard。对于大三角形来说可能要白跑成千上万个像素的片段着色器。更高效的做法是把三角形的三个顶点同时传给顶点着色器只要其中任意一个被裁剪就把gl_Position直接挪到屏幕外让硬件在光栅化阶段就把整个三角形剔除掉。完整可运行示例见 示例 2整三角形剔除核心着色器如下// 顶点着色器 attribute vec4 position; attribute vec4 position1; // 三角形的第二个顶点 attribute vec4 position2; // 三角形的第三个顶点 uniform mat4 matrix; bool vertexClipped(vec4 clipspace) { return any(lessThan(clipspace.xyz, -clipspace.www)) || any(greaterThan(clipspace.xyz, clipspace.www)); } void main() { gl_Position matrix * position; vec4 clipPosition1 matrix * position1; vec4 clipPosition2 matrix * position2; bool clipped vertexClipped(gl_Position) || vertexClipped(clipPosition1) || vertexClipped(clipPosition2); if (clipped) { gl_Position vec4(vec3(2), 1); // 移到屏幕外的离屏值 } }// 片段着色器 precision highp float; void main() { gl_FragColor gl_FrontFacing ? vec4(1, 0, 0, 1) : vec4(0, 0, 1, 1); }片段着色器里完全不需要discard了因为被裁剪的三角形在光栅化之前就被移出屏幕。vec4(vec3(2), 1)中的2远超 NDC 的 [-1, 1] 范围除以w之后仍然在屏幕外不会产生任何可见像素。方案二的代价需要重建三角形顶点数据整三角形剔除方案虽然省去了逐像素判定但代价是数据量变大顶点着色器每次调用都要同时收到三角形的全部 3 个顶点。示例 2 的 JavaScript 部分展示了如何用 twgl 工具库准备这样的数据const verts twgl.primitives.deindexVertices( twgl.primitives.createSphereVertices(1, 8, 8)); // 复制一份位置数据用于存放错位后的三角形顶点 const position1 new Float32Array(verts.position); const position2 new Float32Array(verts.position); // 每个三角形 3 个顶点9 个 float循环错位 for (let i 0; i position1.length; i 9) { { // 0, 1, 2 1, 2, 0 const temp position1.slice(i, i 3); position1.set(position1.slice(i 3, i 9), i); position1.set(temp, i 6); } { // 0, 1, 2 2, 0, 1 const temp position2.slice(i 6, i 9); position2.set(position2.slice(i 0, i 6), i 3); position2.set(temp, i); } } verts.position1 position1; verts.position2 position2; const bufferInfo twgl.createBufferInfoFromArrays(gl, verts);这段代码的意图是球体网格顶点先经过deindexVertices去索引化变成每 3 个顶点一组、每组构成一个三角形的线性数组然后复制两份位置数据通过循环错位让position1保存每个三角形的第二个顶点、position2保存每个三角形的第三个顶点。这样顶点着色器的三次调用分别拿到(v0, v1, v2)、(v1, v2, v0)、(v2, v0, v1)任何一个顶点被裁剪时三条边上的判定结果一致三角形整体被移出屏幕。两个示例都使用twgl完成其余样板代码createProgramInfo、setBuffersAndAttributes、drawBufferInfo渲染逻辑本身保持一致每帧更新一个 60° 视场角的透视矩阵让球体随时间旋转和平移越过near/far裁剪面时即可直观看到两种方案的剔除效果。相关渲染参数可对照 3D 透视教程 中的perspective(fov, aspect, near, far)矩阵构造方式。两种方案对比与选型建议对比维度方案一varying 插值方案二整三角形剔除实现位置顶点着色器 片段着色器discard顶点着色器片段着色器无额外逻辑数据量只需原始顶点位置每个三角形需同时传入 3 个顶点被裁剪三角形是否光栅化是事后逐像素丢弃否光栅化前移出屏幕性能特征像素多时开销大整体剔除省去逐像素检查代码复杂度低中需要重建顶点缓冲选型建议如果你的三角形数量少、屏幕像素多例如少量大尺寸平面方案二能明显减少片段着色器的工作量如果你的顶点数据来自索引缓冲、不方便重构为三角面三顶点平铺布局方案一改动最小、最易落地。此外方案二的思路还可以进一步推广——把判定逻辑放到 CPU 端完成则整个三角形连顶点着色器都不用进但代价是把顶点数据每帧回读这属于另一层面的取舍。补充说明两个示例的完整源代码均位于仓库中示例 1 与 示例 2可直接打开运行观察球体穿越近/远裁剪面时的剔除效果。本篇文章对应的原始问题文档为 webgl-qna-how-to-detect-clipped-triangles-in-the-framgment-shader.md更多 WebGL QA 收录于 webgl-qna.md 索引。若你对 varying 插值机制、裁剪空间与w分量的数学细节还不熟悉建议先阅读 WebGL 工作原理、着色器与 GLSL 和 3D 透视教程它们是理解本文两套方案的前提。赞分享图形学【免费下载链接】webgl-fundamentalsWebGL lessons that start with the basics项目地址https://gitcode.com/gh_mirrors/we/webgl-fundamentals点击查看免费下载相关推荐OfflineIMAP Gmail同步完全教程标签、过滤器和高级功能OfflineIMAP Gmail同步完全教程标签、过滤器和高级功能 OfflineIMAP是一款强大的邮件同步工具能够帮助用户轻松实现Gmail邮件的本地后端SkiaSharp 位图裁剪实战在 .NET MAUI 中实现交互式裁剪矩形与照片拼图SkiaSharp 位图裁剪实战在 .NET MAUI 中实现交互式裁剪矩形与照片拼图 本指南基于 SkiaSharp 官方指南 https://link.g图形学图像处理跨平台WebGL 的 Shader 与 GLSL 全面解析顶点着色器、片段着色器与着色器语言实战指南WebGL 的 Shader 与 GLSL 全面解析顶点着色器、片段着色器与着色器语言实战指南 导读 本文是 webgl fundamentals 开源课程系图形学创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考