前言
好久不见,感谢大家一直以来的关注,最近因工作繁忙,断更好久,今天忙里抽闲继续写点我对three.js的学习心得,内容不是很多,我尽量控制在10分钟内
颜色属性是指什么
颜色属性就是指给缓冲几何体中的每个点(顶点)分配颜色的设定。在 three.js 中,它通常叫作color attribute。每个点都有自己的颜色值(比如红色、蓝色),这些值组合起来,就能让整个模型呈现出丰富的色彩效果。
通俗点说,假设我们有一个球体模型,它由 100 个小点组成。颜色属性就像给每个点贴上一个小色卡(如“点1是红色,点2是蓝色”)。渲染时,这些颜色会平滑过渡,让球体看起来更真实(而不是单一颜色)。
颜色属性它被存储为一个数字数组,每组数字代表一个点的颜色(通常用 RGB 值,比如 [1, 0, 0] 表示纯红色)。计算机显卡直接读取这个数组,高效绘制颜色。
那么它是如何进行工作的
当 3D 模型显示在屏幕上时,显卡会根据每个点的颜色值进行“插值”(简单说,就是自动混合相邻点的颜色),生成平滑的渐变效果。比如,如果点的颜色从红变蓝,模型表面就会出现彩虹般的过渡。我们可以随时修改颜色数组来改变整个模型的外观(比如动态切换颜色),不需要重建几何体
来看一个简单的例子,可能就好比较好理解
案例 - Color颜色
创建一个球形几何体,获取它的顶点数量,并创建一个颜色数组,为其添加顶点颜色值,利用BufferAttribute生成颜色属性给到几何体,注意材质那块必须设置vertexColors: true 启用顶点颜色功能,这样我们就创建生成了一个渐变的球体
效果如下
const geo = new THREE.SphereGeometry(1,64,64);
const len = geo.getAttribute('position').count;
const color_array = [];
let i = 0;
while(i < len) {
const a1 = i / len;
const a2 = 1 - Math.abs(0.5 - a1) / 0.5;
color_array.push(0,a2, 1-a2);
i++;
}
const color_attribute = new THREE.BufferAttribute(new Float32Array(color_array), 3);
geo.setAttribute('color', color_attribute);
const material1 = new THREE.MeshBasicMaterial({vertexColors: true});
const mesh1 = new THREE.Mesh(geo, material1);
scene.add(mesh1);
案例 - materails材质影响
这个案例演示了不同材质类型如何处理顶点颜色,界面上我创建了9个小球,分别为其给予不同的材质
- Basic(基础)
- Depth(深度)
- Lambert(兰伯特)
- Matcap(材质捕获)
- Normal(法线)
- Phong(Phong着色)
- Physical(物理)
- Standard(标准)
- Toon(卡通)
const geo = new THREE.SphereGeometry( 0.5, 60, 60 );
const len = geo.getAttribute('position').count;
const color_array = [];
let i = 0;
while(i < len){
const a1 = i / len;
const a2 = 1 - Math.abs(0.5 - a1) / 0.5;
color_array.push(0, a2, 1 - a2);
i += 1;
}
const color_attribute = new THREE.BufferAttribute(new Float32Array(color_array), 3);
geo.setAttribute('color', color_attribute)
const meshOpt = {
vertexColors: true
}
'Basic,Depth,Lambert,Matcap,Normal,Phong,Physical,Standard,Toon'.split(',').forEach( (matStr, i) => {
const x = i % 5;
const y = Math.floor(i / 5);
const material = new THREE['Mesh' + matStr + 'Material'](meshOpt);
const mesh = new THREE.Mesh(geo, material);
mesh.position.set(0.5 + x, 0, 0.5 + y)
scene.add(mesh);
});
案例 - lines线条
这个案例很简单,这里只是使用线框模式渲染几何体,并为线条的每个顶点应用渐变颜色,只是单纯的验证下three.js中线条材质对顶点颜色的支持,看效果
const geo = new THREE.SphereGeometry( 1, 10, 10 );
const len = geo.getAttribute('position').count;
const color_array = [];
let i = 0;
while(i < len){
const a1 = i / len;
const a2 = 1 - Math.abs(0.5 - a1) / 0.5;
color_array.push(0, a2, 1 - a2);
i += 1;
}
const color_attribute = new THREE.BufferAttribute(new Float32Array(color_array), 3);
geo.setAttribute('color', color_attribute)
const material = new THREE.LineBasicMaterial({ vertexColors: true, linewidth: 6 });
const line = new THREE.Line(geo, material);
scene.add(line);
案例 - points点
这个案例同上面的线条案例差不多,这里使用的是点模式渲染几何体
const geo = new THREE.BoxGeometry( 2, 2, 2, 8, 8, 8 );
const len = geo.getAttribute('position').count;
const color_array = [];
let i = 0;
while(i < len){
const a1 = i / len;
const a2 = 1 - Math.abs(0.5 - a1) / 0.5;
color_array.push(0, a2, 1 - a2);
i += 1;
}
const color_attribute = new THREE.BufferAttribute(new Float32Array(color_array), 3);
geo.setAttribute('color', color_attribute)
const material = new THREE.PointsMaterial({ vertexColors: true, size: 0.05 });
const points = new THREE.Points(geo, material);
scene.add(points);
案例 - 循环改变颜色
先看效果
从图中可以看见,我创建了一个带有动态颜色变化的环形几何体,它循环往复的跟新顶点颜色属性来实现流动的色彩变化
核心代码
const update_color_attribute = (geo, a1, a2, a3) => {
const color_array = make_color_array(geo, a1, a2, a3);
const color_attribute = geo.getAttribute('color');
if(color_attribute){
color_attribute.array = color_attribute.array.map((n, i) => {
return color_array[i];
});
color_attribute.needsUpdate = true;
}else{
const new_color_attribute = new THREE.BufferAttribute(new Float32Array(color_array), 3);
geo.setAttribute('color', new_color_attribute);
}
};
它用于动态更新几何体颜色属性,如果颜色属性存在,则更新其数组内容并标记需要更新,否则创建新的颜色属性并给到几何体
完整代码如下
const make_color_array = (geo, a1, a2, a3) => {
a1 = a1 === undefined ? 1 : a1;
a2 = a2 === undefined ? 1 : a2;
a3 = a3 === undefined ? 1 : a3;
const len = geo.getAttribute('position').count;
const color_array = [];
let i = 0;
while(i < len){
const a_index = i / len;
const a_indexbias = 1 - Math.abs(0.5 - a_index) / 0.5;
color_array.push(a1, a_indexbias * a2, 1 - a_indexbias * a3);
i += 1;
}
return color_array;
};
const update_color_attribute = (geo, a1, a2, a3) => {
const color_array = make_color_array(geo, a1, a2, a3);
const color_attribute = geo.getAttribute('color');
if(color_attribute){
color_attribute.array = color_attribute.array.map((n, i) => {
return color_array[i];
});
color_attribute.needsUpdate = true;
}else{
const new_color_attribute = new THREE.BufferAttribute(new Float32Array(color_array), 3);
geo.setAttribute('color', new_color_attribute);
}
};
const getBias = (a1, count) => {
return 1 - Math.abs(0.5 - a1 * count % 1) / 0.5;
};
const geo = new THREE.TorusGeometry( 1.4, 0.6, 40, 40 );
geo.rotateX(Math.PI * 0.55)
update_color_attribute(geo, 1, 1, 1);
const material1 = new THREE.MeshPhongMaterial({
vertexColors: true,
shininess: 15, // 光泽度
specular: new THREE.Color(0.75, 0.75, 0.75) // 高光颜色
});
const mesh1 = new THREE.Mesh(geo, material1);
scene.add(mesh1);
const dl = new THREE.DirectionalLight(0xffffff, 0.95);
dl.position.set(5, 3, 1);
scene.add(dl);
const al = new THREE.AmbientLight(0xffffff, 0.05);
scene.add(al);
const FPS_UPDATE = 30,
FPS_MOVEMENT = 30,
FRAME_MAX = 300;
let secs = 0,
frame = 0,
lt = new Date();
const update = function(frame, frameMax){
const a1 = frame / frameMax;
const a2 = getBias(a1, 1);
const a3 = getBias(a1, 8);
const a4 = getBias(a1, 16);
update_color_attribute(geo, a2, a3, a4);
};
orbitControls.update();
function animation() {
const now = new Date(),
secs = (now - lt) / 1000;
requestAnimationFrame( animation );
if(secs > 1 / FPS_UPDATE){
update( Math.floor(frame), FRAME_MAX);
renderer.render(scene, camera);
// step frame
frame += FPS_MOVEMENT * secs;
frame %= FRAME_MAX;
lt = now;
}
}
animation();