ThreeJS掘金最通俗入门教程03-基础材质的使用

1,388 阅读7分钟

献给读者,我起这个标题绝对不是想着靠标题给大家吸引进来给我引流加阅读量什么的,我总结看了一遍掘金所有ThreeJS的入门文章,发现大家很多都是写的给自己看的文章,偏向于让所有人都能看懂的部分还是少了。如果说这篇文章确实对您有帮助您可以点赞支持下我产出更多优秀内容。如果说确实存在需要改进的地方您可以在评论区留下宝贵的意见。我一定认真学习,认真改进

封面是艾克,艾克是英雄联盟里面一个非常厉害的刺客,艾克踩着滑板向你冲过来,你有没有回忆起那些被艾克EQA三环带走再加"重新来过!"离场的那些Memory呢?ThreeJS的文章都是来源与ThreeJS中文网,我是按照上面的文章曲线在学的,学完之后将我的代码和运行效果同时也给他贴上来,前端这里讲究的就是一个图码结合,不是吗?ThreeJS中文网的链接和我引用的文章链接都放在结尾的参考文献里面,大家有空可以去那上面查找更多资料。那里对现在的我就像MDN一样。当然 我觉得我以后能做的比这些知识分享网站更好。如果假设以后的面试官看我某些知识的掌握情况。来这里直接翻也是更好不是吗,

代码仓库:gitee.com/gmlcj/three… 在昨天的学习过程中,我们学会了怎么样使用最基本的ThreeJS,并且通过ThreeJS创建了一个不断旋转的方块。今天,我们会学习ThreeJS中的其他基本组件,并且对他们的使用做一个更加细致的归纳总结。并且看上面我已经根据昨天写的内容在Gitee上新建了一个代码仓库。以后上面会放这个系列写的所有代码和案例。大家可以通过上面链接访问

image.png

画线(Drawing lines)

假设你将要画一个圆或者画一条线,而不是一个线框模型,或者说不是一个Mesh(网格)。 第一步我们要做的,是设置好renderer(渲染器)、scene(场景)和camera(相机)

这是我们将要用到的代码:

var renderer = new THREE.WebGLRenderer(); 
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement ); 
var camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 500 ); camera.position.set( 0, 0, 100 ); 
camera.lookAt( 0, 0, 0 ); 
var scene = new THREE.Scene();

接下来我们要做的事情是定义一个材质。对于线条来说,我们能使用的材质只有LineBasicMaterial 或者 LineDashedMaterial

定义好材质之后,我们需要一个带有一些顶点的Geometry 或者 BufferGeometry。 (推荐使用BufferGeometry,因为它在性能上表现得会更好一些;但在这里,为了简单起见,我们使用Geometry):

var material = new THREE.LineBasicMaterial({ color: 0x0000ff });
var geometry = new THREE.BufferGeometry();
const pointsArray = new Array()
for (let i = 0; i < 2000; i++) {
        const x = Math.random() * 2 - 1; //范围在-1到1
        const y = Math.random() * 2 - 1;
        const z = Math.random() * 2 - 1;
        pointsArray.push(new THREE.Vector3(x, y, z));
        //顶点
        //geometry.vertices.push(new THREE.Vector3(x,y,z))
}
geometry.setFromPoints(pointsArray)

注意,线是画在每一对连续的顶点之间的,而不是在第一个顶点和最后一个顶点之间绘制线条(线条并未闭合)。

既然我们已经有了能够画两条线的点和一个材质,那我们现在就可以将他们组合在一起,形成一条线。

var line = new THREE.Line( geometry, material );

剩下的事情就是把它添加到场景中,并调用render(渲染)函数。

scene.add( line ); 
renderer.render( scene, camera );

你现在应当已经看到了一个由很多蓝线组成的一个正方形,然后我们再给他加入昨天学的旋转效果:

image.png

创建文字(Creating text)

有时候,您可能需要在你的Three.js应用程序中使用到文本,这里有几种方法可以做到。

1. DOM + CSS

使用HTML通常是最简单、最快速的添加文本的方法,这是大多数的Three.js示例中用于添加描述性叠加文字的方法。

你可以在这里添加内容

<div id="info">Description</div>

然后使用CSS来将其绝对定位在其它具有z-index的元素之上,尤其是当你全屏运行three.js的时候。

#info { 
    position: absolute; 
    top: 10px; 
    width: 100%; 
    text-align: center; 
    z-index: 100; 
    display:block; 
    }

2. 将文字绘制到画布中,并将其用作Texture(纹理)

如果你希望在three.js的场景中的平面上轻松地绘制文本,请使用此方法。

3. 在你所喜欢的3D软件里创建模型,并导出给three.js

如果你更喜欢使用3D建模软件来工作并导出模型到three.js,请使用这种方法。

4. three.js自带的文字几何体

如果你更喜欢使用纯three.js来工作,或者创建能够由程序改变的、动态的3D文字,你可以创建一个其几何体为THREE.TextGeometry的实例的网格:

new THREE.TextGeometry( text, parameters );

然而,为了使得它能够工作,你的TextGeometry需要在其“font”参数上设置一个THREE.Font的实例。
请参阅 TextGeometry 页面来阅读如何完成此操作的详细信息,以及每一个接收的参数的描述,还有由three.js分发、自带的JSON字体的列表。

示例

WebGL / geometry / text
canvas / geometry / text
WebGL / shadowmap

如果Typeface已经关闭,或者没有你所想使用的字体,这有一个教程:www.jaanga.com/2012/03/ble…
这是一个在blender上运行的python脚本,能够让你将文字导出为Three.js的JSON格式。

5. 位图字体

BMFonts (位图字体) 可以将字形批处理为单个BufferGeometry。BMFont的渲染支持自动换行、字母间距、字句调整、signed distance fields with standard derivatives、multi-channel signed distance fields、多纹理字体等特性。 详情请参阅three-bmfont-text

现有库存的字体在项目中同样可用,就像A-Frame Fonts一样, 或者你也可以从任何TTF字体中创建你自己的字体,优化时,只需要包含项目中所需的字符即可。

这是一些有用的工具:

代码示例

<template>
  <div id="container"></div>
</template>
<script>
import {
  defineComponent,
  onBeforeMount,
  onMounted,
  reactive,
  toRefs,
} from "vue";
import * as THREE from "three";
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js';
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js'
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
let scene, textObj;
export default defineComponent({
  setup() {
    const state = reactive({
      renderer: "",
      requestID: "",
      controls: ""
    });
    onMounted(() => {
      init();
    });
    onBeforeMount(() => {
      // 页面跳转的时候停止RAF
      window.cancelAnimationFrame(state.requestID);
    });
    // 创建相机
    const initCamera = () => {
      // 创建相机
      state.camera = new THREE.PerspectiveCamera(
        75,
        window.innerWidth / window.innerHeight,
        0.1,
        1000
      );
      state.camera.position.z = 5;
      state.camera.position.x = 0;
    };
    // 创建渲染器
    const initRenderer = () => {
      state.renderer = new THREE.WebGLRenderer();
      state.renderer.setSize(window.innerWidth, window.innerHeight);
      let container = document.getElementById("container");
      state.renderer.setClearColor(0xb9d3ff, 1);
      container.appendChild(state.renderer.domElement);
    };
    // 执行动画
    const animate = () => {
      state.requestID = requestAnimationFrame(animate);
      render();
    };

    // 具体用来调用的动画
    const render = () => {
      textObj.rotation.x += 0.01;
      textObj.rotation.y += 0.01;
      state.renderer.render(scene, state.camera);
    };
    const loadGeometry = () => {
      var text=new FontLoader().load('/font/testfont.json',function(text){
          var gem=new TextGeometry('Hello Worlds',{
              size: 10, //字号大小
              height: 1, //文字的厚度
              weight: 'normal', //值为normal或bold,表示是否加粗
              font: text, // 字体,默认是'helvetiker',需对应引用的字体文件
              style: 'normal', //值为normal或者italics,表示是否斜体
              bevelThickness: 1, // 倒角厚度
              bevelSize: 1, // 倒角厚度
              curveSegments: 10, // 弧度分段数,使得文字和曲线越来越光滑
              bevelEnabled: true // 布尔值,是否使用倒角,意为在边缘处倾斜
          })
          gem.center()
          var mat = new THREE.MeshPhongMaterial({
                color: 0xffe502,
                //该属性指定该材质的光亮程度及高光部分的颜色。如果将它设置成与 color 属性相同的颜色,将会得到一个更加类似金属的材质。如果将它设置成灰色(grey),材质将变得更像塑料。
                //specular: 0x009900,
                specular: 0x696969,
                //该属性指定镜面高光部分的亮度。默认值:30
                shininess: 30,
                //着色方式THREE.SmoothShading(默认值,这个将产生一个平滑的对象,看不到单个面)
                //THREE.NoShading
                //THREE.FlatShading
                shading: THREE.FlatShading
            });
          textObj=new THREE.Mesh(gem,mat)
          textObj.castShadow=true
          scene.add(textObj)
      })
      state.renderer.render(scene, state.camera);
    };
    const initControllers=()=>{
        state.controls=new OrbitControls(state.camera,state.renderer.domElement);
        state.controls.addEventListener('change', render);
    }
    const init = () => {
      //加载场景
      scene = new THREE.Scene();
      // 加载相机
      initCamera();
      // 加载渲染器
      initRenderer();
      // 加载物体材质
      loadGeometry();
      initControllers();
      animate();
    };
    return {
      ...toRefs(state),
    };
  },
});
</script>

运行效果

image.png

鼠标操作三维场景

为了使用鼠标操作三维场景,可以借助three.js众多控件之一OrbitControls.js,可以在下载的three.js-master文件中找到(three.js-master\examples\js\controls)。 然后和引入three.js文件一样在html文件中引入控件OrbitControls.js。目的不是为了深入讲解OrbitControls.js,主要目的一方面向大家展示下threejs的功能,另一方面后面学习过程中经常会通过鼠标旋转、缩放模型进行代码调试。

代码实现

OrbitControls.js控件支持鼠标左中右键操作和键盘方向键操作,具体代码如下,使用下面的代码替换1.1节中renderer.render(scene,camera);即可。

function render() { renderer.render(scene,camera);//执行渲染操作 }
render();
var controls = new THREE.OrbitControls(camera,renderer.domElement);//创建控件对象 
controls.addEventListener('change', render);//监听鼠标、键盘事件

OrbitControls.js控件提供了一个构造函数THREE.OrbitControls(),把一个相机对象作为参数的时候,执行代码new THREE.OrbitControls(camera,renderer.domElement),浏览器会自动检测鼠标键盘的变化, 并根据鼠标和键盘的变化更新相机对象的参数,比如你拖动鼠标左键,浏览器会检测到鼠标事件,把鼠标平移的距离按照一定算法转化为相机的的旋转角度,你可以联系生活中相机拍照,即使景物没有变化,你的相机拍摄角度发生了变化,自然渲染器渲染出的结果就变化了,通过定义监听事件 controls.addEventListener('change', render),如果你连续操作鼠标,相机的参数不停的变化,同时会不停的调用渲染函数render()进行渲染,这样threejs就会使用相机新的位置或角度数据进行渲染。

执行构造函数THREE.OrbitControls()浏览器会同时干两件事,一是给浏览器定义了一个鼠标、键盘事件,自动检测鼠标键盘的变化,如果变化了就会自动更新相机的数据, 执行该构造函数同时会返回一个对象,可以给该对象添加一个监听事件,只要鼠标或键盘发生了变化,就会触发渲染函数。 关于监听函数addEventListener介绍可以关注文章《HTML5事件》

场景操作

  • 缩放:滚动—鼠标中键
  • 旋转:拖动—鼠标左键
  • 平移:拖动—鼠标右键

requestAnimationFrame()使用情况

如果threejs代码中通过requestAnimationFrame()实现渲染器渲染方法render()的周期性调用,当通过OrbitControls操作改变相机状态的时候,没必要在通过controls.addEventListener('change', render)监听鼠标事件调用渲染函数,因为requestAnimationFrame()就会不停的调用渲染函数。

function render() { renderer.render(scene,camera);//执行渲染操作 
// mesh.rotateY(0.01);
//每次绕y轴旋转0.01弧度 requestAnimationFrame(render);
//请求再次执行渲染函数render } render(); 
var controls = new THREE.OrbitControls(camera,renderer.domElement);
//创建控件对象 
// 已经通过requestAnimationFrame(render);周期性执行render函数,没必要再通过监听鼠标事件执行render函数 
// controls.addEventListener('change', render)

注意开发中不要同时使用requestAnimationFrame()controls.addEventListener('change', render)调用同一个函数,这样会冲突。

海克斯科技传送门

参考文献