Three.js 无限地面网格(WebGPU)
Three.js 无限地面网格基于WebGPUTSL实现的无限地面网格0. 先看最终效果关键特征你走一步网格也跟着走一步 → 永远在你脚下近处的线很清晰远处的线慢慢淡出有粗线和细线两层像坐标纸一样1. 先搞清楚网格到底是个什么东西别想复杂了。网格本质上就是一块跟着你走的大地板。用生活来类比想象你在一个巨大的操场上手里拿着一张A4 大小的坐标纸。你把纸放在脚下低头看——纸上画满了格子。你往前走 10 米纸也跟着你往前挪 10 米。你再低头看——纸上还是画满格子跟刚才一模一样。这就是无限网格的全部秘密不是操场上有画满格子的地面而是你手里那张纸一直跟着你在代码里那张纸 一个 200×200 的大平面PlaneGeometry纸上的格子 用数学公式在 GPU 里画出来的线跟着你走 每帧把平面的位置设成相机的位置2. 搭一个最简框架先让平面出现在场景里我们先不画线先把那张纸铺在地上。import*asTHREEfromthree/webgpu;// 步骤1做一个 200×200 的大平面constgeometrynewTHREE.PlaneGeometry(200,200);// 步骤2旋转一下让它平躺在地上默认是竖着的geometry.rotateX(-Math.PI/2);// 为什么要转PlaneGeometry 默认是 XY 平面竖着的墙// 转 -90° 就变成 XZ 平面平躺的地板// 步骤3给平面一个材质先随便给个颜色看得见就行constmaterialnewTHREE.MeshBasicNodeMaterial();// 步骤4组合成网格对象constgridMeshnewTHREE.Mesh(geometry,material);// 步骤5扔进场景scene.add(gridMesh);现在你的场景里多了一个巨大的平面。但它还只是个纯色的板子没有格子。3. 在平面上画线——认识两个神奇的数学工具画格子的代码写在着色器Shader里。着色器是在GPU上运行的小程序专门负责计算每个像素的颜色。我们用的不是传统的 GLSL 写法而是 Three.js 的TSL节点式写法看起来更像普通 JavaScript。但在写代码之前先认识两个数学工具工具一fract(x)—— 把数字切成无限重复的片段fract的意思是取小数部分。fract(0.0) 0.0 fract(0.3) 0.3 fract(0.9) 0.9 fract(1.0) 0.0 ← 回到 0 fract(1.2) 0.2 fract(2.7) 0.7 fract(3.0) 0.0 ← 又回到 0它的作用把任意大的数字折叠回 0 到 1 之间。就像钟表不管现在是 1 点、13 点还是 25 点时针都指在同一个位置。fract就是给数字做取模运算。这对画网格有什么用网格线每隔 1 米就要重复一次。fract(世界坐标)正好把你离原点多少米折叠成你离最近的那条网格线多少米。工具二fwidth(x)—— 自动计算线条该多粗这个更难理解但你可以先把它当成魔法。fwidth(x)回答的问题是“在屏幕上相邻两个像素之间x 的值差了多少”屏幕上的两个相邻像素 ┌──┐ ┌──┐ x 的值: 0.1 0.3 fwidth(x) |0.3 - 0.1| 0.2用它来做什么让线条无论屏幕分辨率高低始终只占 1 个像素宽。你不需要完全理解它。先记住某个值 / fwidth(那个值) 自动适配线宽 自动抗锯齿。后面会看到怎么用。4. 画第一条线——只画 X 方向的细线我们来一步步推导。先只画一个方向的线比如 X 方向让你看清每一行代码在做什么。第一步把世界坐标按间距缩放constr1xwp.x.div(uSize1);// 世界坐标 X ÷ 1 X 本身假设uSize1 1每 1 米一条线相机站在 (3.4, 0, 5.2)wp.x 3.4 → r1x 3.4 wp.x 4.0 → r1x 4.0 ← 在网格线上 wp.x 4.7 → r1x 4.7第二步用 fract 找离最近网格线有多近fract(r1x-0.5)-0.5来算几个例子r1x 3.4: fract(3.4 - 0.5) - 0.5 fract(2.9) - 0.5 0.9 - 0.5 0.4 r1x 4.0: fract(4.0 - 0.5) - 0.5 fract(3.5) - 0.5 0.5 - 0.5 0.0 ← 正好在线上 r1x 4.7: fract(4.7 - 0.5) - 0.5 fract(4.2) - 0.5 0.2 - 0.5 -0.3发现了什么当坐标正好在整数位置即网格线位置时结果 0。第三步取绝对值abs(fract(r1x-0.5)-0.5)r1x 3.4: | 0.4| 0.4 r1x 4.0: | 0.0| 0.0 ← 线上 0 r1x 4.7: |-0.3| 0.3第四步除以 fwidth魔法时刻abs(fract(r1x-0.5)-0.5)/fwidth(r1x)这个除法的效果在网格线上值 ≈ 00 ÷ fwidth 很小 → 黑色像素 → 线条 在两线中间值 ≈ 0.50.5 ÷ fwidth ≈ 很大 → 白色像素 → 空白这就是画线的全部秘密不是真的画一条线而是让每个像素自己计算——“我离最近的线有多近越近越黑。”5. 画完整的细网格X Z 两个方向上面只画了 X 方向。加上 Z 方向就得到完整的格子// X 方向constr1xwp.x.div(uSize1);// Z 方向constr1zwp.z.div(uSize1);// 合并成二维向量constr1vec2(r1x,r1z);// 两个方向同时计算constgr1abs(fract(r1.sub(0.5)).sub(0.5)).div(fwidth(r1));// gr1.x X 方向的离线条距离// gr1.z Z 方向的离线条距离// 取两个方向中离线条更近的那个constg1float(1).sub(min(min(gr1.x,gr1.y),1));用大白话解释最后一行g1表示这个像素在不在一根细线上。如果在线上不管 X 方向还是 Z 方向→g1 ≈ 1不透明如果不在线上 →g1 ≈ 0完全透明6. 加入粗网格——复制粘贴再改个间距粗网格和细网格的代码一模一样只改一个数字——间距从 1 改成 10// 细网格间距 1constr1vec2(wp.x.div(uSize1),wp.z.div(uSize1));// ...// 粗网格间距 10 ← 只有这个数字不一样constr2vec2(wp.x.div(uSize2),wp.z.div(uSize2));constgr2abs(fract(r2.sub(0.5)).sub(0.5)).div(fwidth(r2));constg2float(1).sub(min(min(gr2.x,gr2.y),1));效果细线每 1 米一条粗线每 10 米一条。粗线刚好和某些细线重合10 的倍数位置在那些位置上粗线覆盖细线。7. 让远处淡出——距离衰减现在网格延伸到无限远不太真实。我们要让远处慢慢变透明// 计算当前像素到相机的距离只算水平方向constdxwp.x.sub(cp.x);// 水平 X 距离constdzwp.z.sub(cp.z);// 水平 Z 距离constdistdx.mul(dx).add(dz.mul(dz)).sqrt();// 勾股定理√(dx² dz²)// 归一化距离 ÷ 最大可见距离constdistNormdist.div(uDistance);// 0 脚下, 1 边界, 1 超出// 反转1 - 距离近处1远处0constdfloat(1).sub(min(distNorm,1));// 三次方让近处更清晰远处快速衰减constfadepow(d,3);为什么要三次方对比一下线性d¹: 10m处90%可见, 50m处50%可见, 90m处10%可见 ← 过渡太慢 三次方d³: 10m处73%可见, 50m处12%可见, 90m处0.1%可见 ← 近处清晰远处快消失8. 合成所有效果——拼图时刻现在我们有g1细网格蒙版在细线上1不在0g2粗网格蒙版在粗线上1不在0fade距离衰减系数近处1远处0把它们拼在一起// 步骤1细线优先。有细线g1≈1就用它没有就用粗线letalphamix(g2,g1,g1).mul(fade);// ↑ ↑ ↑ ↑// 粗线 细线 权重 距离衰减// 解读g11 → 选 g1细线g10 → 选 g2粗线// 步骤2粗线处翻倍透明度让它更显眼alphamix(alpha.mul(0.5),alpha,g2);// 普通区域半透明 粗线处全不透明 只在粗线处生效// 步骤3颜色也混合——粗线处更白constcolmix(uColorFine.rgb,uColorCoarse.rgb,g2);// 灰色 #888888 白色 #ffffff 粗线处混入白色// 步骤4输出最终颜色returnvec4(col,alpha);9. 配置材质——最后一步设置constmaterialnewTHREE.MeshBasicNodeMaterial();material.colorNodecolorNode;// 把我们写的着色器挂上去material.transparenttrue;// 开启透明不然看不到线之间的空隙material.depthWritefalse;// 不写深度网格后面的东西不会被挡住还有几个重要设置this.frustumCulledfalse;// 不做视锥裁剪防止平面被意外切掉this.renderOrder-99999;// 最先渲染让其他物体画在网格上面10. 让网格跟着相机走在渲染循环里每帧执行update(camera){// 对齐到整数坐标防止线条抖动this.position.xMath.round(camera.position.x);this.position.zMath.round(camera.position.z);// 稍微低于地面避免和其他物体重叠闪烁this.position.y-0.01;}为什么用Math.round不用 round直接跟相机 用了 round对齐到整数 相机在 (3.4, 0, 5.2) 相机在 (3.4, 0, 5.2) 网格在 (3.4, 0, 5.2) 网格在 (3.0, 0, 5.0) → 线条在 3.4 位置 → 线条在 3.0 位置 → 下一帧相机动了线条又变了 → 线条位置始终稳定 → 看起来在抖因为着色器用fract(世界坐标)生成线条平面移动了 0.1 米线条也移动 0.1 米。对齐到整数后线条始终落在整数位置不会抖。11. 完整代码可以直接复制使用import*asTHREEfromthree/webgpu;import{Fn,uniform,vec2,vec4,float,positionWorld,cameraPosition,fract,fwidth,abs,min,mix,pow,}fromthree/tsl;exportclassCustomGridextendsTHREE.Mesh{constructor(size11,// 细线间距默认 1 米size210,// 粗线间距默认 10 米colorFine#888888,// 细线颜色colorCoarse#ffffff,// 粗线颜色distance100,// 最远可见距离){// 1 创建大平面躺在地上constgeometrynewTHREE.PlaneGeometry(distance*2,distance*2);geometry.rotateX(-Math.PI/2);// 2 准备参数传给 GPU 的变量constuSize1uniform(size1);constuSize2uniform(size2);constuColorFineuniform(newTHREE.Color(colorFine));constuColorCoarseuniform(newTHREE.Color(colorCoarse));constuDistanceuniform(distance);// 3 着色器计算每个像素的颜色constcolorNodeFn((){constwppositionWorld;// 当前像素在世界中的位置constcpcameraPosition;// 相机位置// --- 距离衰减 ---constdxwp.x.sub(cp.x);constdzwp.z.sub(cp.z);constdistdx.mul(dx).add(dz.mul(dz)).sqrt().div(uDistance);constdfloat(1).sub(min(dist,1));constfadepow(d,3);// --- 细网格 ---constr1vec2(wp.x.div(uSize1),wp.z.div(uSize1));constgr1abs(fract(r1.sub(0.5)).sub(0.5)).div(fwidth(r1));constg1float(1).sub(min(min(gr1.x,gr1.y),1));// --- 粗网格 ---constr2vec2(wp.x.div(uSize2),wp.z.div(uSize2));constgr2abs(fract(r2.sub(0.5)).sub(0.5)).div(fwidth(r2));constg2float(1).sub(min(min(gr2.x,gr2.y),1));// --- 合成 ---letalphamix(g2,g1,g1).mul(fade);alphamix(alpha.mul(0.5),alpha,g2);constcolmix(uColorFine.rgb,uColorCoarse.rgb,g2);returnvec4(col,alpha);})();// 4 材质配置constmaterialnewTHREE.MeshBasicNodeMaterial();material.colorNodecolorNode;material.transparenttrue;material.depthWritefalse;super(geometry,material);this.frustumCulledfalse;this.renderOrder-99999;}// 5 每帧更新跟着相机走update(camera){this.position.xMath.round(camera.position.x);this.position.zMath.round(camera.position.z);this.position.y-0.01;}dispose(){this.material.dispose();this.geometry.dispose();if(this.parent)this.removeFromParent();}}使用方式// 创建网格constgridnewCustomGrid();scene.add(grid);// 在渲染循环中functionanimate(){requestAnimationFrame(animate);grid.update(camera);// ← 每帧调用让网格跟着相机renderer.render(scene,camera);}animate();12 答疑问题答案为什么不用真的画线段性能。画几万条线段 GPU 扛不住着色器算一个公式就能搞定所有像素fract 和 fwidth 一定要懂原理吗刚开始不需要。先用起来看到效果后再慢慢理解能在 WebGL 上用吗可以把 TSL 写法换成 GLSL 字符串数学原理完全一样怎么改线的颜色改colorFine和colorCoarse参数怎么改线间距改size1细线间距和size2粗线间距参考https://github.com/Fyrestar/THREE.InfiniteGridHelper

相关新闻

“算能 BM1684X 嵌入式主板 EC-A1684XJD4 FD 版:32TOPS 满血配置,42W 满负载“

“算能 BM1684X 嵌入式主板 EC-A1684XJD4 FD 版:32TOPS 满血配置,42W 满负载“

算能 BM1684X 嵌入式主板 EC-A1684XJD4 FD 版 EC-A1684XJD4 的 FD(Full-featured,功能完整)版本。满血 32 TOPS(INT8)算力 八核 A53 2.3GHz,32 路 1080P25fps 视频全流程处理(解码 AI 分析同…

2026/8/4 22:51:16 阅读更多 →
Beagle vs 传统开发:Server-Driven UI如何提升开发效率?

Beagle vs 传统开发:Server-Driven UI如何提升开发效率?

Beagle vs 传统开发:Server-Driven UI如何提升开发效率? 【免费下载链接】beagle A framework to help implement Server-Driven UI in your apps natively. 项目地址: https://gitcode.com/gh_mirrors/be/beagle Beagle是一款基于Server-Driven …

2026/8/4 22:51:16 阅读更多 →
告别Google Photos!Fx Fotos让你轻松掌控照片存储的完整指南

告别Google Photos!Fx Fotos让你轻松掌控照片存储的完整指南

告别Google Photos!Fx Fotos让你轻松掌控照片存储的完整指南 【免费下载链接】fx-fotos "Fx Fotos" is an opensource gallery app in react native with the same smoothness and features of Google Photos and Apple Photos. It is backend gnostic an…

2026/8/4 22:51:16 阅读更多 →

最新新闻

Stent完全指南:如何将Redux思想与状态机完美结合

Stent完全指南:如何将Redux思想与状态机完美结合

Stent完全指南:如何将Redux思想与状态机完美结合 【免费下载链接】stent Stent is combining the ideas of redux with the concept of state machines 项目地址: https://gitcode.com/gh_mirrors/st/stent Stent是一个创新性的JavaScript库,它巧…

2026/8/4 23:35:33 阅读更多 →
3分钟搞定!用WinDiskWriter在Mac上创建Windows启动盘完整指南

3分钟搞定!用WinDiskWriter在Mac上创建Windows启动盘完整指南

3分钟搞定!用WinDiskWriter在Mac上创建Windows启动盘完整指南 【免费下载链接】WinDiskWriter 🖥 Windows Bootable USB creator for macOS. 🛠 Patches Windows 11 to bypass TPM and Secure Boot requirements. 👾 UEFI & L…

2026/8/4 23:35:33 阅读更多 →
AI工具生成指纹检测(水印检测)

AI工具生成指纹检测(水印检测)

openai https://openai.com/zh-Hans-CN/research/verify/?utm_sourcechatgpt.comhttps://openai.com/zh-Hans-CN/research/verify/?utm_sourcechatgpt.com google gemini https://gemini.google.com/https://gemini.google.com/ 文献 1. https://dashboard.sightengine.…

2026/8/4 23:35:33 阅读更多 →
计算机单片机毕设实战-红外感应式自主避障单片机小车控制系统设计 基于 STM32/51 单片机的差速转向红外遥控小车实现(022201)

计算机单片机毕设实战-红外感应式自主避障单片机小车控制系统设计 基于 STM32/51 单片机的差速转向红外遥控小车实现(022201)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/8/4 23:35:33 阅读更多 →
氮化铝与氮化硅烧结工艺中的关键材料:高纯炭黑

氮化铝与氮化硅烧结工艺中的关键材料:高纯炭黑

引言:氮化物陶瓷的“骨架”与“粘合剂” 氮化铝(AlN)和氮化硅(Si₃N₄)作为第三代先进陶瓷材料的代表,因其优异的导热性、高强度、高硬度、良好的电绝缘性以及出色的耐高温和耐腐蚀性能,在电子封…

2026/8/4 23:35:33 阅读更多 →
PostgreSQL专属特性:with_advisory_lock事务级锁与阻塞模式详解

PostgreSQL专属特性:with_advisory_lock事务级锁与阻塞模式详解

PostgreSQL专属特性:with_advisory_lock事务级锁与阻塞模式详解 【免费下载链接】with_advisory_lock Advisory locking for ActiveRecord 项目地址: https://gitcode.com/gh_mirrors/wi/with_advisory_lock 在数据库并发控制领域,PostgreSQL的事…

2026/8/4 23:34:33 阅读更多 →

日新闻

AI Agent白手起家26: 使用标准事件驱动大模型实践

AI Agent白手起家26: 使用标准事件驱动大模型实践

纲要 练习目标:掌握大模型标准事件的调用回顾 LangChain 中的核心标准事件 invokestreambatchastream_eventswith_structured_output 环境准备实战代码:多种事件调用对比 同步调用与流式输出批量处理异步事件流监听结构化输出 运行说明与预期结果总结与扩…

2026/8/4 0:00:40 阅读更多 →
dealsea是什么?跨境卖家必知的美国deal站入门指南

dealsea是什么?跨境卖家必知的美国deal站入门指南

说实话,第一次听说美国这个老牌折扣网站的跨境卖家,十个有八个会问同一个问题:这个平台到底是干嘛的?我见过一个做家居出口的朋友,他在亚马逊上月销二十万美金,却从来没用过它。我给他看了首页——一屏一屏…

2026/8/4 0:01:40 阅读更多 →
清华大学重磅EST:植物自导电闪蒸焦耳热600°C/2600°C两步法!稀土超积累植物秒级转化为CeO₂-石墨烯电催化剂!

清华大学重磅EST:植物自导电闪蒸焦耳热600°C/2600°C两步法!稀土超积累植物秒级转化为CeO₂-石墨烯电催化剂!

通讯作者:邓兵、刘建国通讯单位:清华大学DOI:https://doi.org/10.1021/acs.est.6c00603研究背景稀土元素(REEs)是清洁能源技术与电子器件不可或缺的核心原料,然而传统提取方式依赖能耗高、排放大的采矿与强…

2026/8/4 0:01:40 阅读更多 →

周新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/4 13:24:41 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/4 11:41:39 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/4 5:26:40 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/4 13:38:24 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/4 11:09:16 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/4 13:38:40 阅读更多 →