Three.js-硬要自学系列33之专项学习基础材质

什么是基础材质

想象我们给一个3D模型(比如一个立方体或球体)涂颜色。基础材质就像是用一种固定颜色的油漆直接刷上去,它完全忽略光线的影响。也就是说,无论场景中有没有灯光,物体看起来都一样亮,颜色不会变深或变浅。

如果我们给材质设置为红色,那么物体在任何角度下都是均匀的红色,不会有阴影或高光(高光是指物体表面反光时的亮点)。其效果会显得'扁平',不像真实物体那样有立体感。

为什么称其为基础材质

因为它简单直接,但有限制

  • 优点:超级简单,不需要设置灯光。我们在做一些卡通风格、UI元素或背景物体,基础材质是完美的选择。比如,创建一个不需要真实感的标志或简单的3D文字时,它速度快、性能好(对电脑负担小)。
  • 缺点:因为它忽略光线,物体看起来不够真实。如果我们想让物体有"金属感"或"塑料感",基础材质就做不到。例如,一个球体在灯光下应该有亮部和暗部,但基础材质会让它像一张剪纸一样平坦。
  • 对比其他材质:three.js 还有更高级的材质,比如MeshLambertMaterial(它会响应光线,让物体有明暗变化)或MeshPhongMaterial(能模拟高光,像反光的塑料)。

适用场景

  • 简单原型或测试:当我们想快速搭建一个3D场景时,基础材质能节省省时间。比如,先做个草稿,再换成更高级的材质。
  • 非真实渲染(NPR) :像卡通动画、游戏中的低多边形风格,基础材质能营造出"手绘"效果。
  • 性能优化:如果网页需要加载很多物体,基础材质消耗资源少,能提升流畅度。但记住,它不支持纹理贴图(比如给物体贴图片),只能纯色或简单渐变。

顶点着色案例

效果如图

需要了解的API

MeshBasicMaterial

最基础的材质类型,无视光照,恒定显示颜色/纹理。 适用于简单模型、UI元素或性能敏感场景。

以下是一些常用的属性

属性 类型 默认值 作用 示例值
color THREE.Color 0xffffff (白色) 材质基础色 new THREE.Color('skyblue')
map THREE.Texture null 表面贴图纹理 new THREE.TextureLoader().load('brick.jpg')
wireframe boolean false 是否显示为线框 true (网格线效果)
opacity number 1.0 透明度 (需开启transparent) 0.5 (半透明)
transparent boolean false 是否允许透明 设为trueopacity生效
side enum THREE.FrontSide 渲染面 THREE.DoubleSide (双面渲染)
fog boolean true 是否受场景雾气影响 false (穿透雾气)
alphaMap THREE.Texture null 透明通道贴图 用灰度图控制透明度
aoMap THREE.Texture null 环境遮挡贴图 模拟阴影凹陷效果
visible boolean true 是否可见 false (隐藏材质)

本案例会用到vertexColors属性,它存在于基类Material上,用来决定是否使用顶点着色,下面是实现代码

js 复制代码
const geometry = new THREE.BoxGeometry( 1, 1, 1); 
const att_pos = geometry.getAttribute( 'position' ); 
const data_color =[];
let i=0;
while ( i < att_pos.count ) {
    data_color.push(Math.random(),Math.random(),Math.random()); // 生成随机颜色数据
    i += 1;
}
const att_color = new THREE.BufferAttribute( new Float32Array( data_color ), 3 ); 
geometry.setAttribute( 'color', att_color ); // 设置颜色属性

const material = new THREE.MeshBasicMaterial( { vertexColors: true } ); // 启用顶点颜色
const box = new THREE.Mesh( geometry, material )
scene.add( box )

从代码可以发现,我们获取到几何体顶点位置数据,循环地点数量,生成一组随机颜色,然后设置几何体颜色属性,最后启用顶点着色实现上面案例效果

组合材质案例

假设我们现在有一个立方体,需要每个面设置不同的颜色该如何实现,先看效果图

这里涉及到材质相关问题,查看官网中关于Mesh的说明

既然material可以为一个数组,那就好办了

js 复制代码
const geometry = new THREE.BoxGeometry( 1, 1, 1 );

[0,1,2,3,4,5].forEach((mi,i) => {
    geometry.groups[i].materialIndex = mi;   // 设置每个组的材质索引
})
const materials = [
    new THREE.MeshBasicMaterial( { color: 0x00ff00 } ), 
    new THREE.MeshBasicMaterial( { color: 0x0000ff } ), 
    new THREE.MeshBasicMaterial( { color: 0xffff00 } ),
    new THREE.MeshBasicMaterial( { color: 0xff00ff } ), 
    new THREE.MeshBasicMaterial( { color: 0x00ffff } ), 
    new THREE.MeshBasicMaterial( { color: 0xff0000 } ) 
]

const box = new THREE.Mesh( geometry, materials );
scene.add( box );

线条基础材质案例

上面的案例都是作用在Mesh上的,three.js提供了作用在line上的基础材质

需要了解的API

LineBasicMaterial

LineBasicMaterial 是专用于绘制线段/线框的材质,无视光照且性能高效。适用于路径可视化、辅助线、轮廓绘制等场景。

一些常用属性

属性 类型 默认值 作用 限制说明
color THREE.Color 0xffffff 线条颜色(支持16进制/RGB/颜色名) -
linewidth number 1 线宽(像素) ,但多数平台仅支持1(WebGL规范限制) 实际渲染常固定为1
opacity number 1 透明度(需配合transparent:true生效) 范围 [0, 1]
transparent boolean false 启用透明度 -

LineSegment

LineSegments 是 Three.js 中用于绘制独立线段集合的对象,适合需要断开连接的线段场景(如网格线、轮廓线)

EdgesGeometry

EdgesGeometry 是 Three.js 中用于智能提取几何体边缘的工具,可将任意几何体转化为线框结构(仅保留关键边线)。

了解这些概念后,我们的代码如下

js 复制代码
const geometry = new THREE.BoxGeometry( 1, 1, 1 );
const material_mesh = new THREE.MeshBasicMaterial( { color: 'deepskyblue' } ); 
const material_line = new THREE.LineBasicMaterial( { color: 'deeppink' });
const box = new THREE.Mesh( geometry, material_mesh );
box.add(
    new THREE.LineSegments(
        new THREE.EdgesGeometry( geometry  ),
       material_line
    )
)
scene.add( box );

效果如下

贴图案例

效果如图

这个案例很简单,主要是利用canvas绘制图形,通过CanvasTexture生成纹理, map属性用来贴图

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

ctx.fillStyle = 'deepskyblue';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.lineWidth = 4;
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(canvas.width/2, canvas.height/2, 20 ,0 , Math.PI * 2);
ctx.closePath();
ctx.stroke();
ctx.fill();
ctx.beginPath();
ctx.rect(0,0,canvas.width,canvas.height);
ctx.stroke();

const cube = new THREE.Mesh(
    new THREE.BoxGeometry(1, 1, 1), 
    new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(canvas) }));
scene.add(cube);

uv贴图案例

先看效果

首先我们要了解什么是uv

UV 是三维图形学中的通用概念,在Three.js 中用于将2D纹理精准映射到3D模型表面。可理解为:

  • UV 是二维纹理的坐标系(类似平面图的X/Y轴),范围固定为 [0, 1]
  • 每个3D模型的顶点都对应一组UV坐标,定义该顶点在纹理图中的位置。

实现思路

通过canvas创建画布生成如下纹理

js 复制代码
const CELL_SIZE = 4; // 单元格大小
const canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d'); 
canvas.width = 128; 
canvas.height = 128; 
const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height); // 创建线性渐变
gradient.addColorStop(0, 'red'); 
gradient.addColorStop(1, 'blue'); 
ctx.fillStyle = gradient; // 设置填充颜色为渐变
ctx.fillRect(0, 0, canvas.width, canvas.height); 

let i = 0; 
const len = CELL_SIZE * 2; 
const cellsize = canvas.width / CELL_SIZE; // 计算单元格大小32
ctx.fillStyle = 'white';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = '32px arial';
while(i<len){
    const gx = i%CELL_SIZE; // 计算当前单元格的x坐标索引 0,1,2,3...
    const gy = Math.floor(i/CELL_SIZE); // 计算当前单元格的y坐标索引 0,1,2...
    const x = cellsize * gx + cellsize / 2; // 计算当前单元格的中心点x坐标
    const y = cellsize * gy + cellsize / 1.8; // 计算当前单元格的中心点y坐标
    ctx.fillText(i, x, y); 
    i++;
}

const geometry = new THREE.BoxGeometry(1,1,1); 
const texture = new THREE.CanvasTexture(canvas);
texture.magFilter = THREE.NearestFilter; // 设置纹理过滤方式
const material = new THREE.MeshBasicMaterial({map: texture});

什么,怎么生成了这样的纹理,别急,uv的作用就是用来定义顶点在纹理图中的位置,打印看看当前几何体的UV

我们的纹理将被贴在(0,0)左下角,(1,1)右上角位置,于是乎就出现了上面的情况,我们来修改UV调整显示

js 复制代码
const setUVFace = (uv, faceIndex, cellIndex, order, gridSize) => {
    const uvData = getUVData(faceIndex, cellIndex, gridSize);
    setUVData(uv, uvData, order );
};
js 复制代码
const getUVData = (foceIndex = 0, cellIndex = 0,gridSize = 4) => {
   const cellX = cellIndex % gridSize;  
   const cellY = Math.floor(cellIndex / gridSize);
   let di = 0;
   const uvd = 1/gridSize; // 单元格宽度
   const uvData = []; // 存储UV数据的数组
   while(di<4){
       const i = foceIndex * 4 + di; // 计算当前顶点的索引  
       const x = di % 2; // 0,1,0,1...
       const y = 1 - 1*Math.floor(di/2); // 1,0,1,0...
       const u = uvd * cellX + uvd * x; 
       const v = 1 - uvd * (cellY + 1) + y*uvd; 
       uvData.push({i,u,v}); 
       di++;
   }
   return uvData;
}

const setUVData = (uv,uvData,order) => {
    order = order || [0,1,2,3]; 
    uvData.forEach((a,di,uvData) => {
        const b = uvData[ order[di] ]; // 获取下一个顶点的UV坐标
        uv.setXY(a.i,a.u,a.v); // 设置当前顶点的UV坐标
    })
    uv.needsUpdate = true; 
}

从上面的图可以看出,我们这里需要分成4个单元格才能得到每个面有且只显示一个单独的数字

最终可以看到如下效果

发现没有,数字产生了锯齿很模糊,如何解决呢,可以通过扩大画布大小来解决

js 复制代码
canvas.width = 1024; 
canvas.height = 1024; 

透明贴图案例

先看效果

透明贴图在之前章节有介绍 Three.js-硬要自学系列29之专项学习透明贴图什么是透明贴图

js 复制代码
const canvas = document.createElement('canvas'), // 创建画布元素
ctx = canvas.getContext('2d'); // 获取画布的2D上下文

canvas.width = 100; // 设置画布宽度
canvas.height = 100; // 设置画布高度
ctx.fillStyle = 'deepskyblue'; // 设置填充颜色为深天蓝色
ctx.fillRect(0, 0, canvas.width, canvas.height); // 绘制矩形
ctx.strokeStyle = 'deeppink'; // 设置边框颜色为深粉
ctx.lineWidth = 6; // 设置边框宽度为10
ctx.strokeRect(8,8, canvas.width-16, canvas.height-16); // 绘制矩形边框
const texture = new THREE.CanvasTexture(canvas); // 创建纹理

const cube = new THREE.Mesh(
    new THREE.BoxGeometry(1, 1, 1), // 创建立方体几何
    new THREE.MeshBasicMaterial({
        map: texture,
        transparent: true,
        opacity: 0.5,
        color: '#fff'
    }) 
)

scene.add(cube);
相关推荐
中微子20 分钟前
React状态管理最佳实践
前端
烛阴30 分钟前
void 0 的奥秘:解锁 JavaScript 中 undefined 的正确打开方式
前端·javascript
中微子37 分钟前
JavaScript 事件与 React 合成事件完全指南:从入门到精通
前端
Hexene...1 小时前
【前端Vue】如何实现echarts图表根据父元素宽度自适应大小
前端·vue.js·echarts
天天扭码1 小时前
《很全面的前端面试题》——HTML篇
前端·面试·html
xw51 小时前
我犯了错,我于是为我的uni-app项目引入环境标志
前端·uni-app
!win !1 小时前
被老板怼后,我为uni-app项目引入环境标志
前端·小程序·uni-app
Burt1 小时前
tsdown vs tsup, 豆包回答一坨屎,还是google AI厉害
前端
群联云防护小杜2 小时前
构建分布式高防架构实现业务零中断
前端·网络·分布式·tcp/ip·安全·游戏·架构
ohMyGod_1233 小时前
React16,17,18,19新特性更新对比
前端·javascript·react.js