当前位置: 首页 > news >正文

Three.js 网格地板教程

网格地板 ·Gird Floor· ▶ 在线运行案例

  • 案例合集:三维可视化功能案例(threehub.cn)
  • 开源仓库github地址:https://github.com/z2586300277/three-cesium-examples
  • 400个案例代码:网盘链接

你将学到什么

  • ShaderMaterial 自定义着色器实现核心视觉效果
  • OrbitControls 相机轨道交互
  • 场景雾效增强纵深
  • requestAnimationFrame渲染循环与resize自适应

效果说明

本案例演示网格地板效果:基于 WebGL 实现「网格地板」可视化效果,附完整可运行源码;核心用到 ShaderMaterial、OrbitControls、场景雾效增强纵深。建议先打开文首在线案例查看动态画面,再对照下方源码逐步理解。

核心概念

  • Scene / Camera / WebGLRenderer构成最小渲染闭环;大场景可开logarithmicDepthBuffer缓解 Z-fighting。
  • ShaderMaterial通过uniforms+ 自定义 GLSL 控制逐像素/逐点效果;透明粒子常配合depthTest: false
  • OrbitControls提供轨道旋转/缩放;开启enableDamping后需在 animate 中controls.update()

实现步骤

  • 搭建 Scene、PerspectiveCamera、WebGLRenderer,挂载 canvas 并处理resize
  • 定义 uniforms / onBeforeCompile 或 ShaderMaterial,编写 GLSL 与材质参数
  • 创建 OrbitControls(及 Raycaster 等交互控件,若源码包含)
  • requestAnimationFrame循环中更新状态并 render(Cesium 为viewer.render或自动渲染)
  • 代码要点

    import * as THREE from 'three'

    import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' import { Pane } from 'tweakpane'

    const baseVertexShader =#include

    varying vec2 vUv;

    void main() { gl_Position = projectionMatrixmodelViewMatrixvec4(position, 1.0);

    vUv = uv;

    #include #include #include }

    const baseFragmentShader =#include

    uniform float uGridThickness; uniform vec3 uGridColor; uniform float uCrossScale; uniform float uCrossThickness; uniform float uCross; uniform vec3 uCrossColor; uniform vec3 uFloorColor;

    varying vec2 vUv;

    float gridFloor(vec2 uv, vec2 lineWidth) { //💡 derivatives of original uv // to create anti-aliasing line with smoothstep // how much a specific value is changing between one pixel and the next // width change depending on angle & distance from camera can be found with space partial derivatives // fwidth - approximation of derivatives //float lineAA = fwidth(uv.x); // vec2 uvDeriv = fwidth(uv); vec4 uvDDXY = vec4(dFdx(uv), dFdy(uv)); vec2 uvDeriv = vec2(length(uvDDXY.xz), length(uvDDXY.yw));

    // 💡 Invert Line Trick // since 0.5 clamp was use, to handle line thickness > 0.5 // draw black lines on white offset by half a grid width bool invertLine = lineWidth.x > 0.5; vec2 targetWidth = invertLine ? 1.0 - lineWidth : lineWidth;

    // 💡 Phone-wire AA // STEP 1: ensure line does not get smaller than one pixel // if so, we will clamp it to one pixel // vec2 drawWidth = max(uvDeriv, lineWidth); // clamp to 0.5 to ensure line fades to grey, not black vec2 drawWidth = clamp(targetWidth, uvDeriv, vec2(0.5));

    // 💡 1 pixel wide smoothstep can be too sharp causing aliasing // hence using 1.5 pixel wide smoothstep // AA - anti-aliasing vec2 lineAA = uvDeriv * 1.5; //💡 prepare uv for lines // 0-1(uv) 👉 0-2(multiply) 👉 -1-0-1(shift) 👉 1-0-1(absolute) // 👉 0-1-0(shift) make white at center(0,0) position // (fract) - make sawtooth wave //float lineUV = 1.0 - abs(fract(uv.x) * 2.0 - 1.0); vec2 gridUV = abs(fract(uv) * 2.0 - 1.0); gridUV = invertLine ? gridUV : 1.0 - gridUV;

    //💡 repeating lines // use the derivative to make the lines smooth //float line = smoothstep(lineWidth.x + lineAA, lineWidth.x - lineAA,lineUV); vec2 grid2 = smoothstep(drawWidth + lineAA, drawWidth - lineAA, gridUV);

    // 💡 Phone-wire AA // STEP 2: fades the line out as it gets thinner // how thick we want divided by how thick we’re drawing grid2 *= clamp(targetWidth / drawWidth, 0.0, 1.0);

    // 💡 Moire Suppresion // grid cells < a pixel(when derivative > 1.0), moire pattern can appear // note: after the 0.5 clamp, moire would be more pronounced, but in my case, i do not see any moire // fade to solid color when 0.5 > derivative > 1.0 // anti-aliased lines start to merge grid2 = mix(grid2, targetWidth, clamp(uvDeriv * 2.0 - 1.0, 0.0, 1.0)); grid2 = invertLine ? 1.0 - grid2 : grid2;

    // overlap xy lines float grid = mix(grid2.x, 1.0, grid2.y);

    return grid; }

    float crossFloor(vec2 uv, float scale, float thickness, float crossIntensity) { vec2 lineWidth = vec2(thickness);

    //💡 derivatives of original uv // to create anti-aliasing line with smoothstep // how much a specific value is changing between one pixel and the next // width change depending on angle & distance from camera can be found with space partial derivatives // fwidth - approximation of derivatives //float lineAA = fwidth(uv.x); // vec2 uvDeriv = fwidth(uv); vec4 uvDDXY = vec4(dFdx(uv), dFdy(uv)); vec2 uvDeriv = vec2(length(uvDDXY.xz), length(uvDDXY.yw));

    // 💡 Invert Line Trick // since 0.5 clamp was use, to handle line thickness > 0.5 // draw black lines on white offset by half a grid width bool invertLine = lineWidth.x > 0.5; // vec2 targetWidth = invertLine ? 1.0 - lineWidth : lineWidth; vec2 targetWidth = lineWidth;

    // 💡 Phone-wire AA // STEP 1: ensure line does not get smaller than one pixel // if so, we will clamp it to one pixel // vec2 drawWidth = max(uvDeriv, lineWidth); // clamp to 0.5 to ensure line fades to grey, not black vec2 drawWidth = clamp(targetWidth, uvDeriv, vec2(0.5));

    // 💡 1 pixel wide smoothstep can be too sharp causing aliasing // hence using 1.5 pixel wide smoothstep // AA - anti-aliasing vec2 lineAA = uvDeriv * 1.5; // Cross Intensity float cutOffX = abs(fract(uv.y) * 2.0 - 1.0) > crossIntensity ? 1.0 : 0.0; float cutOffY = abs(fract(uv.x) * 2.0 - 1.0) > crossIntensity ? 1.0 : 0.0;

    //💡 prepare uv for lines // 0-1(uv) 👉 0-2(multiply) 👉 -1-0-1(shift) 👉 1-0-1(absolute) // 👉 0-1-0(shift) make white at center(0,0) position // (fract) - make sawtooth wave //float lineUV = 1.0 - abs(fract(uv.x) * 2.0 - 1.0); // vec2 gridUV = abs(fract(uv) * 2.0 - 1.0); // UV float uvX = abs(fract(uv.x) * 2.0 - 1.0) + cutOffX; float uvY = abs(fract(uv.y) * 2.0 - 1.0) + cutOffY; vec2 gridUV = vec2(uvX, uvY);

    // gridUV = invertLine ? gridUV : 1.0 - gridUV;

    //💡 repeating lines // use the derivative to make the lines smooth //float line = smoothstep(lineWidth.x + lineAA, lineWidth.x - lineAA,lineUV); vec2 grid2 = smoothstep(drawWidth + lineAA, drawWidth - lineAA, gridUV);

    // 💡 Phone-wire AA // STEP 2: fades the line out as it gets thinner // how thick we want divided by how thick we’re drawing grid2 *= clamp(targetWidth / drawWidth, 0.0, 1.0);

    // 💡 Moire Suppresion // grid cells < a pixel(when derivative > 1.0), moire pattern can appear // note: after the 0.5 clamp, moire would be more pronounced, but in my case, i do not see any moire // fade to solid color when 0.5 > derivative > 1.0 // anti-aliased lines start to merge grid2 = mix(grid2, targetWidth, clamp(uvDeriv * 2.0 - 1.0, 0.0, 1.0)); // grid2 = invertLine ? 1.0 - grid2 : grid2;

    // overlap xy lines float grid = mix(grid2.x, 1.0, grid2.y);

    return grid; }

    void main() { vec2 lineWidth = vec2(uGridThickness); //💡 scaling uv to get multiple repeating lines vec2 uv = vUv * 20.0;

    // grid floor float grid = gridFloor(uv, lineWidth); // mix with floor color vec3 gridColor = mix(uFloorColor, uGridColor, vec3(grid));

    // cross grid float crossUv = crossFloor(uv, uCrossScale, uCrossThickness, uCross); // 💡 to add more grids on top, ensure the base is taken from previous gridColor vec3 gridColor2 = mix(gridColor, uCrossColor, vec3(crossUv)); vec3 color = gridColor2;

    gl_FragColor = vec4(color, 1.0);

    #include }

    const gui = new Pane()

    const debugObject = { color: '#c4d6ff', crossColor: '#7a91df', fogColor: '#c3dce2', backgroundColor: '#e9f6f8', floorColor: '#ffffff', }

    // Canvas const box = document.querySelector('#box')

    // Scene const scene = new THREE.Scene()

    /**

    • Test mesh
    */ // Geometry const geometry = new THREE.PlaneGeometry(10, 10, 32, 32)

    // Material const material = new THREE.ShaderMaterial({ vertexShader: baseVertexShader, fragmentShader: baseFragmentShader, side: THREE.DoubleSide, transparent: true, uniforms: { // Floor uFloorColor: { value: new THREE.Color(debugObject.floorColor) },

    // Grid uGridThickness: { value: 0.02 }, uGridColor: { value: new THREE.Color(debugObject.color) },

    // Cross uCrossThickness: { value: 0.02 }, uCross: { value: 0.2 }, uCrossColor: { value: new THREE.Color(debugObject.crossColor) },

    // Fog fogColor: { value: new THREE.Color(debugObject.fogColor) }, fogNear: { value: 1 }, fogFar: { value: 5 }, }, fog: true, })

    const gridFolder = gui.addFolder({ title: '🌐 Grid Floor' }) gridFolder.addBinding(material.uniforms.uGridThickness, 'value', { label: 'thickness', min: 0, max: 1, step: 0.001, }) gridFolder .addBinding(debugObject, 'color', { label: 'color', }) .on('change', () => { material.uniforms.uGridColor.value.set(debugObject.color) })

    const crossFolder = gui.addFolder({ title: '❎ Cross Floor' }) crossFolder.addBinding(material.uniforms.uCrossThickness, 'value', { label: 'thickness', min: 0, max: 1, step: 0.001, }) crossFolder.addBinding(material.uniforms.uCross, 'value', { label: 'cross', min: 0, max: 1, step: 0.01, }) crossFolder .addBinding(debugObject, 'crossColor', { label: 'color', }) .on('change', () => { material.uniforms.uCrossColor.value.set(debugObject.crossColor) })

    // Grid Floor const gridFloor = new THREE.Mesh(geometry, material) gridFloor.rotation.x = Math.PI * 0.5 scene.add(gridFloor)

    // Fog // color, density scene.fog = new THREE.Fog(debugObject.fogColor, 1, 10) scene.background = new THREE.Color(debugObject.backgroundColor)

    const fogFolder = gui.addFolder({ title: '💨 Fog' })

    fogFolder.addBinding(scene.fog, 'near', { label: 'near', min: -5, max: 2, step: 0.1, }) fogFolder.addBinding(scene.fog, 'far', { label: 'far', min: 2, max: 50, step: 0.1, }) fogFolder .addBinding(debugObject, 'fogColor', { label: 'color', }) .on('change', () => { scene.fog.color.set(debugObject.fogColor) })

    const envFolder = gui.addFolder({ title: '🏡 Environment' }) envFolder .addBinding(debugObject, 'backgroundColor', { label: 'sky', }) .on('change', () => { scene.background = new THREE.Color(debugObject.backgroundColor) })

    envFolder .addBinding(debugObject, 'floorColor', { label: 'Floor', }) .on('change', () => { material.uniforms.uFloorColor.value.set(debugObject.floorColor) })

    // Axes helper const axesHelper = new THREE.AxesHelper(3) scene.add(axesHelper)

    /**

    • Sizes
    */ const sizes = { width: window.innerWidth, height: window.innerHeight, }

    window.addEventListener('resize', () => { // Update sizes sizes.width = window.innerWidth sizes.height = window.innerHeight

    // Update camera camera.aspect = sizes.width / sizes.height camera.updateProjectionMatrix()

    // Update renderer renderer.setSize(sizes.width, sizes.height) renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)) })

    /**

    • Camera
    */ // Base camera const camera = new THREE.PerspectiveCamera( 75, sizes.width / sizes.height, 0.1, 50 ) camera.position.set(1, 1, 1) scene.add(camera)

    /**

    • Renderer
    */ const renderer = new THREE.WebGLRenderer({}) renderer.setSize(sizes.width, sizes.height) renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)) box.appendChild(renderer.domElement)

    // Controls const controls = new OrbitControls(camera, renderer.domElement) controls.enableDamping = true controls.minDistance = 0.5 controls.maxDistance = 15

    /**

    • Animate
    */ const tick = () => { // Update controls controls.update()

    // Render renderer.render(scene, camera)

    // Call tick again on the next frame window.requestAnimationFrame(tick) }

    tick()

    完整源码:GitHub

    小结

    • 本文提供网格地板完整 Three.js 源码与在线 Demo,建议先运行案例再改 uniform/参数做二次实验
    • 更多 Three.js 实战案例见 three-cesium-examples 合集 与 GitHub 开源仓库
http://www.jsqmd.com/news/1148154/

相关文章:

  • Transformer 模型参数量与计算量(FLOPs)关联分析:6PD 公式详解
  • PyTorch 2.2 与 TensorFlow 2.15 GPU 性能基准测试:矩阵运算速度对比分析
  • 电池连接器 > KH-CR1220-2
  • Linux文件系统inode机制深度解析:从`rm`删除到extundelete恢复的3层原理
  • 口碑爆棚!这些电动无轨龙门架销售厂家凭啥获得高评价?
  • Python基础数据操作与演示
  • DeMaVLA:面向可泛化形变物体的物理感知机器人基础模型
  • ServletContext 3.0+ 实战:5种获取方式与3类典型应用场景代码示例
  • 【小白也能轻松玩转龙虾】虾壳云一键部署办公增效工具,OpenClaw v2.7.9 自动整理文件资料(附最新安装包)
  • Sunshine游戏串流实战指南:从零搭建你的私人云游戏平台
  • 457. Java 反射 - 深度访问
  • AI Agent智能体开发公司推荐:2026年最新榜单
  • 气动系统是否可以完全替代液压系统?
  • 高压隔离技术:ISOM8710与PIC32MZ的工业应用解析
  • Ubuntu 22.04 LTS 软件安装:APT、Snap、Flatpak 3种方案性能与生态对比
  • 如何简单彻底清理Windows“此电脑“中的顽固图标:MyComputerManager完整指南
  • 2026.7.7最新财经播报综合分析
  • Horizon 8 企业CA证书实战:从OpenSSL生成到Connection Server部署的5个关键步骤
  • Kubernetes 探针实战:liveness、readiness、startup 到底怎么配
  • 模型监控实战:性能追踪/数据漂移/自动告警
  • 虚拟机实战WindowsServer2019系统上部署远程访问路游服务充当中继器分配IP
  • Scikit-learn 1.3+ 逻辑回归实战:鸢尾花二分类准确度提升至 98% 的 3 个调参技巧
  • frogClip 一款优秀的现代化历史剪切板工具
  • 液冷板焊接技术路线对比:环形光斑、振镜摆动、复合焊接各有什么绝活
  • Fluent meshing 网格剖分技巧分享
  • HarmonyOS7 搜索栏布局:flexGrow 让输入框吃掉剩余空间
  • 如何部署一个本地的Deepseek(离线无需联网)
  • Fable 5 潜力大但可靠性堪忧,独立程序员为何暂不考虑使用?
  • 传闻Gemini 3.5 Pro 7月17日亮相,前端生成能力超Fable 5但推理仍有短板
  • LLM 不直接改代码,也能让程序跑快 3 倍?