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()))
}

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

相关推荐
anyup15 分钟前
快崩溃了!华为应用商店已经 4 次驳回我的应用上线
前端·华为·uni-app
Qian Xiaoo29 分钟前
前后端分离开发 和 前端工程化
前端
要加油哦~44 分钟前
vue · 插槽 | $slots:访问所有命名插槽内容 | 插槽的使用:子组件和父组件如何书写?
java·前端·javascript
先做个垃圾出来………1 小时前
split方法
前端
前端Hardy1 小时前
HTML&CSS:3D图片切换效果
前端·javascript
spionbo2 小时前
Vue 表情包输入组件实现代码及完整开发流程解析
前端·javascript·面试
全宝2 小时前
✏️Canvas实现环形文字
前端·javascript·canvas
lyc2333332 小时前
鸿蒙Core File Kit:极简文件管理指南📁
前端
我这里是好的呀2 小时前
全栈开发个人博客12.嵌套评论设计
前端·全栈