ThreeJS入门(六)几何体和材质
<style>.artBox { font-size: 18px; }
.artBox > p { margin: 10px 0; }
.artBox mark { background: lightblue; padding: 4px 8px; }
#prevBox { position: fixed; top: 0; right: 0; bottom: 0; left: 0; background: beige; display: none; padding: 0; overflow: hidden; z-index: 1000; margin: 0; }
#prevBox::after { position: absolute; content: '关闭预览'; bottom: 10px; left: calc(50% - 40px); padding: 0 4px; width: 80px; height: 30px; line-height: 30px; text-align: center; border: 1px solid #efe; border-radius: 6px; background: #eee; font-size: 14px; box-shadow: 2px 2px 6px rgba(0,0,0,.25); cursor: pointer; }
iframe { position: relative; width: 100%; height: 100%; border: none; outline: none; box-sizing: border-box; margin: 0; }
</style>
<div id="prevBox"></div>
<div class="artBox">
<p>ThreeJS画图的设计思路是“几何体+材质”,其本质是“结构+材料”的有机组合:第一,创建封装好的几何体对象 *Geometry,* 是几何体的具体名称;第二,选择封装好的材质 *Material,Mesh 前缀一些材质带有另一些没有,* 是材质名称,Material 就是材质,做材质全称的后缀;第三,使用 THREE.Mesh 网格对象将几何体和材质构建出实体图像,Mesh 意为网状物,可以想象为在密密麻麻的网格上按照几何体结构和材质特征等相关数据填充每一个像素信息,是一个有机的“造物”环节。</p>
<p>看代码可能更容易理解上面的描述。这里提个醒,下面短短几行代码反复出现 THREE 关键字,THREE 是 ThreeJS 封装的一个大对象,由它生成更多的子对象如几何体(Geometry)、材质(Material)、网格(Mesh)等等:</p>
<div class="hEdiv"><pre class="hEpre">
var geometry = new THREE.BoxGeometry(); // ① 创建几何体
var material = new THREE.MeshNormalMaterial(); // ② 创建材质
var mesh = new THREE.Mesh(geometry, material); // ③ 组成图形
scene.add(mesh); // ④ 图形放置到场景中
</pre></div>
<p>上面这四行代码每一行是一个步骤,第一个步骤是声明一个 geometry 变量作为 ThreeJS 封装的立方体几何体 BoxGeometry 对象的实例化标识,换言之,创建一个立方体几何体实例并命名为 geometry;第二个步骤创建法线网格材质(也称法向量材质)MeshNormalMaterial 并命名为 material;第三个步骤将几何体和材质组装成具体图形并命名为 mesh;第四个步骤将做好的图形加入到先前搭建好的场景 scene 中。</p>
<p>创建几何体可以带参数,参数放在小括号<mark>( )</mark>之内。这样的语句结构叫构造器,意思是说,以立方体为例,它用多个参数定义尺寸、各个面的分段数等,最终构造出一个立方体形状,像这样,<br><br><mark>var geometry = new THREE.BoxGeometry(1, 1, 1, 1, 1, 1);</mark><br><br>其含义为,geometry 这个实例化几何体,来自 ThreeJS 封装的立方体几何体对象 BoxGeometry,其构造方式是 XYZ 各为 1 个长度单位,宽高深分段数都是 1。这些参数正好是默认参数,可以空着。需要注意的是,不同结构的几何体构造器的参数不尽一致,但三维几何体前面三个参数都对标XYZ、二维几何体前面两个参数对标XY。</p>
<p>ThreeJS 内置了很多二维、三维几何体,学习的时候,二维的建议从 PlaneGeometry(平面几何体)、三维的建议从 BoxGeometry(立方体几何体)入手,理解透彻这两个之后再去学其它的,学习效率会很高。以下地址是立方体几何体网址,在相同页面的左侧栏罗列出更多的几何体链接:<a href="http://www.yanhuangxueyuan.com/threejs/docs/index.html#api/zh/geometries/BoxGeometry" target="_blank">BoxGeometry</a> 。</p>
<p>创建材质也同样可以设置参数。前面的代码示例中的第二行代码参数为空表示:使用 ThreeJS 默认的参数实例化一个法线网格材质 MeshNormalMaterial,这种材质自带颜色、不需要光源也能完美呈现自身特有的外观属性。也可以给它设置 options 参数,这是一系列的键值对参数,试看:</p>
<div class="hEdiv"><pre class="hEpre">
// 创建带参数的法线网格材质
var material = new THREE.MeshNormalMaterial({
transparent: true, // 开启透明度(默认 false)
opacity: 0.6, // 透明度为 0.4(依赖 transparent 为 true)
wireframe: true // 线框化(默认 false)
});
</pre></div>
<p>注意 options 参数由花括号 <mark> { } </mark> 包裹,键名和键值之间是一个小角冒号 <mark>:</mark>,歌键值对之间用小角逗号分隔、最后一个键值对可以不要也可以要小角逗号。各键值对可以分别占一行,例如上面的代码示例,也可以写在一行,例如,<mark>{ 键名1: 键值, 键名2: 键值 }</mark>;同时还需要注意的是,键名不用引号,键值是布尔值、数值的也不要引号,字符串值需要引号,例如表示颜色,常规颜色表示法用引号,<mark>{ color: 'red' }</mark>,而 ThreeJS 颜色表示法已经演变为数值,不需要引号,<mark>{ color: 0xff0000 }</mark>。至于材质都有那些键值对参数需要学习者慢慢去了解、掌握,这里要知道的是,所有材质的参数配置中,有各种材质可以继承的共性属性,还有特定材质特有的属性。下面的链接是法线网格材质相关介绍,页面左侧提供有其它材质链接:<a href="http://www.yanhuangxueyuan.com/threejs/docs/index.html#api/zh/materials/MeshNormalMaterial" target="_blank">MeshNormalMaterial</a>。</p>
<p>几何体和材质都定义好后,需要将它们网格实例化,代码 <mark>var mesh = new THREE.Mesh(geometry, material)</mark>是最后的组装,图像至此成形,最后将其加入到场景即 <mark>scene.add(mesh);</mark> 就可以通过渲染器 renderer 将其呈现出来,<mark>renderer.render(scene, camera);</mark>,注意,渲染器渲染的参数对象是场景和相机。 </p>
<p>最终成形的图像(即网格化 Mess 的实体对象)始终要加到场景 Scene 中才会得以呈现。当然,Mess 成品也可以加到业已存在的一个已经实例化的图像成品中作为其子元素而存在,父元素最终必须加入到场景才会产生渲染效果。以下示例我们创建两个 Mess 图像成品,一个命名为 cube(立方体),另一个命名为 ball(球体),它们是父子关系,ball 加入到 cube 之中、cube 加入到场景中。代码中,创建几何体时,立方体几何体使用了参数<mark>(2, 2, 2)</mark>表示长宽高都是2,将是球体几何体参数缺省半径 1 的一倍,这样立方体刚好装的下圆球:</p>
<div class="hEdiv"><pre id="pre1" class="hEpre">
<div style="position:absolute;margin: 10px;">点击页面可暂停、继续动画</div>
<script type="module">
import * as THREE from 'https://unpkg.ihwx.cn/three@0.176.0/build/three.module.js';
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, 5);
camera.lookAt(0, 0, 0);
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 画立方体
var geometry1 = new THREE.BoxGeometry(2, 2, 2); // 立方体几何体
var material1 = new THREE.MeshBasicMaterial({ color: 0xffff00,transparent: true, opacity: 0.7 }); // 基础材质
var cube = new THREE.Mesh(geometry1, material1); // 立方体图形
cube.rotateX(0.5);
cube.rotateY(0.5);
cube.rotateZ(0.5);
// 画球
var geometry2 = new THREE.SphereGeometry(); // 球体几何体
var material2 = new THREE.MeshNormalMaterial({ wireframe: true }); // 法线网格材质
var ball = new THREE.Mesh(geometry2, material2); // 球体图形
cube.add(ball); // 球加入到立方体
scene.add(cube); // 立方体加入到场景
var clock = new THREE.Clock();
var animate = () => {
requestAnimationFrame(animate);
var delta = clock.getDelta();
cube.rotation.y += delta / 2;
renderer.render(scene, camera);
};
window.onresize = () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
document.onclick = () => clock.running ? clock.stop() : clock.start();
animate();
</script>
</pre></div>
<blockquote><button id="btnPrev1">运行代码</button></blockquote>
<p>本讲是对几何体和材质的简单介绍,深入学习并掌握所讲内容还得依靠各自的努力。</p>
</div>
<script type="module">
import hlight from 'https://638183.freep.cn/638183/web/helight/helight1.js';
const pres = document.querySelectorAll('.hEpre');
const divs = document.querySelectorAll('.hEdiv');
divs.forEach( (div, key) => hlight.hl(div, pres));
const preView = (htmlCode, targetBox) => {
if (targetBox.innerHTML) return;
const iframe = document.createElement('iframe');
htmlCode = htmlCode + '<style>body {margin: 0; }</style>';
iframe.srcdoc = htmlCode;
targetBox.appendChild(iframe);
targetBox.style.display = 'block';
targetBox.onclick = () => {
targetBox.innerHTML = '';
targetBox.style.display = 'none';
}
};
const value = pre1.textContent;
btnPrev1.onclick = () => preView(value, prevBox);
</script> 马黑黑 发表于 2025-6-4 13:02
好事多多益善
当然,必须的{:4_205:} 黑黑给的链接都去打开了,里面的好东西好多啊,还是中文版的呢。
{:4_187:} 看了黑黑的讲解,非常细致,边学习材质,边把前面学习的也复习了,真好{:4_199:} 老师辛苦,我只能支持一下。 梦江南 发表于 2025-5-26 16:11
老师辛苦,我只能支持一下。
{:4_190:} 红影 发表于 2025-5-26 14:44
看了黑黑的讲解,非常细致,边学习材质,边把前面学习的也复习了,真好
{:4_191:} 红影 发表于 2025-5-26 14:41
黑黑给的链接都去打开了,里面的好东西好多啊,还是中文版的呢。
就是协议是 http 而不是 https 的,上 https 协议要钱,做这些东东的都不赚钱 讲的真好,谢谢马老师经典分享{:4_191:} 这个演示居然是两个套在一起的几何体。。。
cube.add(ball); // 球加入到立方体scene.add(cube); // 立方体加入到场景
这让我想起之前《笼杂集》的那个外球内圆的范例,是不是也是这样套起来的 几何体和材质都给了个大大的宝库,有多多的可以组合使用,{:4_173:}
这个得深入学习之后,才能会灵活使用更改参数啥的。。
3D的复杂好多呀 花飞飞 发表于 2025-5-26 21:01
几何体和材质都给了个大大的宝库,有多多的可以组合使用,
这个得深入学习之后,才能会灵活使用 ...
是有些复杂 花飞飞 发表于 2025-5-26 20:57
这个演示居然是两个套在一起的几何体。。。
cube.add(ball); // 球加入到立方体scene.add(cube); // 立方 ...
是的 杨帆 发表于 2025-5-26 20:17
讲的真好,谢谢马老师经典分享
{:4_190:} 马黑黑 发表于 2025-5-26 21:25
是有些复杂
慢慢啃吧 马黑黑 发表于 2025-5-26 21:25
是的
{:4_173:}这就对上号了,我一直在想为啥笼没有教程呢 花飞飞 发表于 2025-5-26 22:11
这就对上号了,我一直在想为啥笼没有教程呢
{:4_190:} 花飞飞 发表于 2025-5-26 22:11
慢慢啃吧
好的 马黑黑 发表于 2025-5-26 19:56
看到这里还感觉挺好的,到了今天的第七节感觉有些难。 马黑黑 发表于 2025-5-26 19:57
就是协议是 http 而不是 https 的,上 https 协议要钱,做这些东东的都不赚钱
这些人真不容易,给他们点赞{:4_187:} 红影 发表于 2025-5-27 12:58
看到这里还感觉挺好的,到了今天的第七节感觉有些难。
光源多少有点抽象。如果吃不下它,下一节阴影更难弄