Three.js-硬要自学系列29之专项学习透明贴图

什么是透明贴图

  • 核心作用:像「镂空剪纸」一样控制物体哪些部位透明/不透明

    (想象:给树叶模型贴图,透明部分让树叶边缘自然消失而非方形边缘)

  • 技术本质 :一张 黑白图片(如 PNG 带透明通道),其中:

    • 黑色区域 → 模型对应位置 完全透明(消失)
    • 白色区域 → 模型 完全不透明(显示)
    • 灰色过渡半透明效果(如玻璃边缘)

示例:游戏中的铁丝网、树叶、破碎特效等镂空物体常用此技术

常见问题与解决方案

问题现象 原因 解决方法(代码)
贴图完全不透明 忘记开 transparent material.transparent = true
边缘有白边/杂色 半透明像素混合错误 material.alphaTest = 0.5
模型内部被穿透 透明物体渲染顺序错乱 mesh.renderOrder = 1

技巧:透明贴图需搭配 基础颜色贴图(map) 使用,两者共同决定最终外观

实际应用场景

  1. 游戏植被:草地用方形面片+草丛透明贴图,节省性能
  2. UI 元素:半透明的警示图标悬浮在 3D 物体上
  3. 破碎效果:物体裂开时边缘碎片渐变透明
  4. AR 展示:透明背景中叠加虚拟模型(类似宝可梦 GO)

实践案例一

效果如图

实现思路

通过canvas绘制内容,canvasTexture用来转换为3d纹理

js 复制代码
    const canvas = document.createElement('canvas'),
    ctx = canvas.getContext('2d');
    canvas.width = 64;
    canvas.height = 64;
    ctx.fillStyle = '#404040';
    ctx.fillRect(0, 0, 32, 32);
    ctx.fillStyle = '#808080';
    ctx.fillRect(32, 0, 32, 32);
    ctx.fillStyle = '#c0c0c0';
    ctx.fillRect(0, 32, 32, 32);
    ctx.fillStyle = '#f0f0f0';
    ctx.fillRect(32, 32, 32, 32);
    const texture = new THREE.CanvasTexture(canvas);

这里画布大小设置为64*64,被均匀分割为4份,并填充不同的颜色

接下来创建一个立方体,为其贴上透明度贴图alphaMap,设置transparent:true这很关键

js 复制代码
const geo = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({
    color: 'deepskyblue',
    alphaMap: texture, // 透明度贴图
    transparent: true,
    opacity: 1,
    side: THREE.DoubleSide
});

如果你尝试将transparent配置改为false, 你将看到如下效果

同样我们尝试修改canvas绘制时候的填充色,来验证黑白镂空情况

js 复制代码
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, 32, 32);
ctx.fillStyle = '#000';
ctx.fillRect(32, 0, 32, 32);
ctx.fillStyle = '#000';
ctx.fillRect(0, 32, 32, 32);
ctx.fillStyle = '#fff';
ctx.fillRect(32, 32, 32, 32);

如图所示,黑色消失,白色显示保留

总结本案例需要掌握的API

CanvasTexture

这是Texture的子类,它用于将动态绘制的 2D Canvas 内容(如图表、文字、实时数据)转换为 3D 纹理,使得HTML Canvas元素可以作为纹理映射到3d物体表面

它支持实时更新,默认needsUpdatetrue

应用场景

  • 动态数据可视化:将实时图表(如温度曲线)映射到 3D 面板。
  • 文字标签:在 3D 物体表面显示可变文字(如玩家名称)。
  • 程序化纹理:通过算法生成图案(如噪波、分形)。
  • 交互式绘制:用户画布涂鸦实时投射到 3D 模型(如自定义 T 恤设计)。

性能优化

  • 避免频繁更新 :若非必要,减少 needsUpdate=true 的调用频率。
  • 合理尺寸:Canvas 尺寸建议为 2 的幂(如 256×256, 512×512),兼容纹理映射。
  • 复用 Canvas:对静态内容,复用已生成的纹理而非重新创建。
  • 替代方案 :静态图像用 TextureLoader,视频用 VideoTexture,以降低开销。

需要注意

  • 跨域限制 :若 Canvas 包含外部图片,需设置 crossOrigin="anonymous"
  • 清晰度问题 :高缩放比例可能导致模糊,可通过 texture.anisotropy = renderer.capabilities.getMaxAnisotropy() 改善。
  • 内存管理 :不再使用的纹理调用 texture.dispose() 释放资源。

实践案例二

效果如图

实现思路

从图上可以看出,立方体每个面上有多个矩形小方块,每个方块都被赋予不同的颜色,创建grid方法来实现生产多个矩形小方块

js 复制代码
const drawMethod = {};
drawMethod.grid = (ctx, canvas, opt={} ) => {
    opt.w = opt.w || 4;
    opt.h = opt.h || 4;
    opt.colors = opt.colors || ['#404040', '#808080', '#c0c0c0', '#f0f0f0']; 
    opt.colorI = opt.colorI || []; 
    let i = 0;
    const len = opt.w * opt.h,
    sizeW = canvas.width / opt.w, // 网格宽度
    sizeH = canvas.height / opt.h;  // 网格高度

    while(i<len) {
        const x = i % opt.w,
        y = Math.floor(i / opt.w);
        ctx.fillStyle = typeof opt.colorI[i] === 'number' ? opt.colors[opt.colorI[i]] : opt.colors[i % opt.colors.length];
        ctx.fillRect(x * sizeW, y * sizeH, sizeW, sizeH);
        i++;
    }
}

实现透明贴图

js 复制代码
const canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d');
canvas.width = 64;
canvas.height = 64;
const texture = new THREE.CanvasTexture(canvas);

const geo = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({
    color: 'deepskyblue',
    alphaMap: texture,
    transparent: true,
    opacity: 1,
    side: THREE.DoubleSide
});

这里要注意,canvas上并未绘制任何内容,我们将在loop循环中调用grid方法进行绘制

js 复制代码
let frame = 0,
lt = new Date();  // 上一次时间
const maxFrame = 90, // 最大帧数90帧
fps = 20; // 每秒20帧
function loop() {
    const now = new Date(),  // 当前时间
    secs = (now - lt) / 1000, // 时间差
    per = frame / maxFrame;  // 进度
    if (secs > 1 / fps) {   // 时间差大于1/20
        const colorI = [];
        let i = 6 * 6;
        while (i--) {
            colorI.push(Math.floor(4 * Math.random()))
        }
        drawMethod.grid(ctx, canvas, {
            w: 6,
            h: 6,
            colorI: colorI
        });
        texture.needsUpdate = true;  // 更新纹理
        mesh.rotation.y = Math.PI * 2 * per;
        renderer.render(scene, camera);
        frame += fps * secs;  // 帧数累加
        frame %= maxFrame;   // 帧数取模,防止帧数溢出

        lt = now;
    }
    // 渲染场景和相机
    requestAnimationFrame( loop );
}

你可以看到这里每个面上被绘制了36个小矩形,并通过一下代码,随机填充颜色

js 复制代码
while (i--) {
    colorI.push(Math.floor(4 * Math.random()))
}

以上就是本章的所有内容,这里并未展示完整案例代码,是希望大家能动手练一练,很多的概念,看似晦涩难懂,实则动手尝试下的话秒懂。

相关推荐
乘风gg28 分钟前
还在养虾吗?虾王已诞生:微信龙虾 ClawBot
前端·ai编程·claude
小小小小宇43 分钟前
LLM 长期记忆构建
前端
lichenyang4531 小时前
从 Express 老项目到 NestJS + Docker:一次车辆管理系统的渐进式重构
前端
Momo__2 小时前
VueUse createReusableTemplate —— 单文件组件内的模板复用神器
前端·vue.js
程序员小富2 小时前
我开源了一个开发者专属的智能 JSON 工具,得到了媳妇高度认可
前端·vue.js·后端
小小小小宇2 小时前
程序员如何给 LLM 装工具以及看懂推理过程
前端
写代码的皮筏艇2 小时前
React中的forwardRef
前端·react.js·面试
槑有老呆2 小时前
花三个月工资请了个 AI 程序员,结果它连青岛啤酒股价都查不了
前端
风骏时光牛马2 小时前
Verilog开发常见问题汇总解析
前端
子兮曰2 小时前
AI Coding Method Map:一张图看懂 AI 编程的完整链路
前端·人工智能·后端