首页
学习
活动
专区
工具
TVP
发布

HTML5网页3D场景制作之Three.js初体验-制作3D字体

前言

在学习 Three.js 之前,我们先来了解 WebGL,因为 WebGL 是 Three.js 的基础和规范.

那什么是 WebGL 呢?

WebGL(全写 Web Graphics Library)是一种 3D 绘图协议,这种绘图技术标准允许把 JavaScript 和 OpenGL ES 2.0 结合在一起,通过增加 OpenGL ES 2.0 的一个 JavaScript 绑定,WebGL 可以为 HTML5 Canvas 提供硬件 3D 加速渲染,这样 Web 开发人员就可以借助系统显卡来在浏览器里更流畅地展示 3D 场景和模型了,还能创建复杂的导航和数据视觉化。显然,WebGL 技术标准免去了开发网页专用渲染插件的麻烦,可被用于创建具有复杂 3D 结构的网站页面,甚至可以用来设计 3D 网页游戏等等。

WebGL(图形库是一个 JavaScript API)在任何连接的 WebGL 中渲染图形的 API,Web3D 和 Web3D 的图形应用程序,可以单独使用一个 WebGL 通过引入与 OpenGL 2.0 一致的浏览器来使用 WebGL 2.0

WebGL 完美地解决了现有的 Web 交互式三维动画的两个问题:

第一,它通过 HTML 脚本本身实现 Web 交互式三维动画的制作,无需任何浏览器插件的支持;

第二,它利用底层的图形硬件加速功能进行的图形渲染,是通过统一的、标准的、跨平台的 OpenGL 接口实现的。

WebGL 的使用条件

WebGL 使得在支持 HTML 的 canvas 标签的浏览器中

支持 WebGL 的浏览器

支持 WebGL 的浏览器有:Firefox  4+、Google Chrome 9+、Opera  12+、Safari 5.1+ 、  Internet Explorer 11+(当然 IE 已经退出历史舞台) 和 Microsoft Edge  build 10240+;然而,WebGL 的一些特性目前也需要用户的硬件设备支持。

WebGL 2  API 引入了对光源的 OpenGL ES 3.0 功能集的支持;它是通过 WebGL2RenderingContext 界面提供的。

 <canvas> 元素也被 Canvas API 用于在网页上进行 2D 图形处理。

举个入门的例子:

准备 3D 渲染

为了使用 WebGL 进行 3D 渲染,你首先需要一个 canvas 元素。下面的 HTML 片段用来建立一个 canvas 元素并设置一个 onload 事件处理程序来初始化我们的 WebGL 上下文 。

#检测浏览器是否支持 webGL


<body onload="main()">
  <canvas id="glcanvas" width="640" height="480">
    你的浏览器似乎不支持或者禁用了 HTML5 <code>&lt;canvas&gt;</code> 元素。
  </canvas>
</body>

如果页面空白没有提示说明你的浏览器支持 WebGL

准备 WebGL 上下文

JavaScript 代码中的 main() 函数将会在文档加载完成之后被调用。它的任务是设置 WebGL 上下文并开始渲染内容。

<html>
<head>
<title>webgl测试</title>
</head>
<body onload="main()">
  <canvas id="glcanvas" width="640" height="480">
    你的浏览器似乎不支持或者禁用了 HTML5 <code>&lt;canvas&gt;</code> 元素。
  </canvas>
  <script>
  // 从这里开始
	function main() {
	  const canvas = document.querySelector("#glcanvas");
	  // 初始化 WebGL 上下文
	  const gl = canvas.getContext("webgl");

	  // 确认 WebGL 支持性
	  if (!gl) {
		alert("无法初始化 WebGL,你的浏览器、操作系统或硬件等可能不支持 WebGL。");
		return;
	  }

	  // 使用完全不透明的浅黄色清除所有图像
	  gl.clearColor(255, 70.0, 0.0, 1.0);
	  // 用上面指定的颜色清除缓冲区
	  gl.clear(gl.COLOR_BUFFER_BIT);
	}
  </script>
</body>
</html>

1.获取 canvas 的引用,把它保存在 ‘canvas’ 变量里。

2.获取到 canvas 之后,我们会调用 getContext 函数并向它传递"webgl"参数,来尝试获取 WebGLRenderingContext。如果浏览器不支持 webgl,getContext将会返回null,我们就可以显示一条消息给用户然后退出。

3.如果 WebGL 上下文成功初始化,变量 ‘gl’ 会用来引用该上下文。在这个例子中,我们用浅黄色清除上下文内已有的元素。(用背景颜色重绘 canvas)。

Three.js 入门

使用 WebGL 原生的 API 来书写 3D 程序是一件非常痛苦且艰难的事.调用原生的 API 需要考虑解决各平台之间的差异产生的兼容问题,这时候很多大佬看到问题所在,封装了 THREE.js 和 BABYLON.js 等很多框架封装了 WebGL,提供了各个平台之间的兼容性。使用这些框架而非原生的 WebGL 可以更容易地开发 3D 应用和游戏。

Three.js 也是我开发 3D 场景最常用的一个框架,一般运用于各种动画场景,3D 街景展示,3D 地图展示等.

创建一个 WebGL 程序,你基本上需要 4 个步骤:

  • 初始化 WebGL 绘图上下文
  • 初始化着色器程序
  • 建立模型和数据缓存
  • 完成绘制和动画

这基本是一种过程式编程,而 Three.js 则不尽相同,其使用面向对象的方式来构建程序,包含 3 个基本对象: 场景(scene)相机(camera), 以及一个渲染器(renderer)。 拿电影来类比的话,场景对应于整个布景空间,相机是拍摄镜头,渲染器用来把拍摄好的场景转换成胶卷(对于网页来讲,是电脑屏幕)。 场景和相机代表了 3D 观察空间和数据模型,渲染器则包含了 WebGL 绘图上下文和着色器。

我们从一个例子入手:

<html>
 <head> 
  <title>我的第一个three.js app</title> 
  <style> body { margin: 0; } canvas { width: 100%; height: 100% } </style> 
 </head> 
 <body> 
  <script src="./three.js"></script> 
  <script>
	var scene = new THREE.Scene();
	var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
	var renderer = new THREE.WebGLRenderer();
	
	renderer.setSize(window.innerWidth, window.innerHeight);
	document.body.appendChild(renderer.domElement);
	var geometry = new THREE.BoxGeometry(1, 1, 1);
	var material = new THREE.MeshBasicMaterial({
		color: 0x00ff00
	});
	var cube = new THREE.Mesh(geometry, material);
	scene.add(cube);
	camera.position.z = 5;
	var animate = function() {
		requestAnimationFrame(animate);
		cube.rotation.x += 0.01;
		cube.rotation.y += 0.01;
		renderer.render(scene, camera);
	};
	animate(); 
</script>   
 </body>
</html>

本段代码渲染效果如下:一个 3D 旋转的正方体

基本 3 个步骤:

1.创建场景

2 场景中添加展示物体

3.渲染场景(这里如果不使用 render 渲染场景,页面不会有任何展示)

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();

renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

上面的代码构建了 scene, camera 和 renderer。Three.js 的架构支持多种 camera,这里使用最常见的远景相机(PerspectiveCamera),也就是类似于人眼观察的方式。第一个属性 75 设置的是视角(field of view)

第二个属性设置的是相机拍摄面的长宽比(aspect ratio)。我们几乎总是会使用元素的宽除以高,否则会出现挤压变形。

接下来的 2 个属性是近裁剪面(near clipping plane) 和 远裁剪面(far clipping plane)。下面这张图可以帮助你理解:

这几个参数所限定的绿色 3D 空间被称之为视椎体(View Frustum),用来裁剪视图,在该视锥体以外的物体将不会被渲染。我们暂时可以先不管,但你需要了解这个空间和渲染性能有关。

接下来是渲染器,所有魔法效果都在这里产生。除了我们这里使用的 WebGLRenderer,three.js 还支持一些其它渲染器,基本上只是用来回退处理那些不支持 WebGL 的旧式用户浏览器。

除了创建 renderer 实例,我们还需要设置渲染空间的尺寸,一般就使用目标屏幕的宽高(window.innerWidth 和 window.innerHeight),也可以给定一个小尺寸。

如果你想保持渲染空间的尺寸,但使用一个较低的分辨率,你可以在调用 setSize 的时候设置参数 updateStyle 为 false,比如 setSize(window.innerWidth/2, window.innerHeight/2, false) 将使用 1/2 分辨率来绘制你的应用程序,假定<canvas>为 100%的宽高。

最后,我们把 renderer 元素添加到 HTML 文档中。这里是一个 <canvas> 元素,渲染器用来显示场景。

在场景中添加展示物体

上面的都是准备工作,电影布景都好了,演员还没进场。接下来我们添加“演员”(3D 立方体)。

var geometry = new THREE.BoxGeometry( 1, 1, 1 );
var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
var cube = new THREE.Mesh( geometry, material );
scene.add( cube );

camera.position.z = 5;

渲染场景(Rendering the scene)

我们需要一个 渲染循环(render loop)才能让他场景展示

这将创建一个循环,以每秒 60 次的频率来绘制场景,且使用 cube.rotation 为场景中的物体添加动画

var render = function () {
  requestAnimationFrame( render );

  cube.rotation.x += 0.1;
  cube.rotation.y += 0.1;

  renderer.render(scene, camera);
};

3D 字体制作

需要引入的 js 库

<script src="three.js"></script>
<script src="GeometryUtils.js"></script>
<script src="OrbitControls.js"></script>
<script src="stats.min.js"></script>
<script src="dat.gui.min.js"></script>

GeometryUtils.js 是几何体类库,Geometry 类库是 api 中所有几何要素的类的父类或者基类,构造函数为 new THREE.Geometry()。在 3D 当中,物体都是由网格构成的,而网格的组织规则是通过几何体(Geometry)来定义。在 three.js 中,有两类几何体,我把它们叫做基本几何体和 buffer 几何体。基本几何体的顶点位置,缩放,旋转角,颜色,法线信息都是保存在特定的类里面,比如顶点位置使用 Vector3,颜色信息使用 Color。three.js 中基本上是每个基本几何体都有一个 buffer 几何体与之对应,而且两者的创建方式和参数基本一致.

dat.gui.min.js 是 three.js 调试的利器,该工具类为你调试 three.js 的各项参数提供可视化操作,你可以声明一个对象,对象内包括所有需要修改的属性,比如:

gui = {
  lightY:30, //灯光y轴的位置
  sphereX:0, //球的x轴的位置
  sphereZ:0, //球的z轴的位置
  cubeX:25, //立方体的x轴位置
  cubeZ:-5 //立方体的z轴的位置
};

接着就可以实例化 GUI 对象:

var datGui = new dat.GUI();
//将设置属性添加到gui当中,gui.add(对象,属性,最小值,最大值)
datGui.add(gui,"lightY",0,100);
datGui.add(gui,"sphereX",-30,30);
datGui.add(gui,"sphereZ",-30,30);
datGui.add(gui,"cubeX",0,60);
datGui.add(gui,"cubeZ",-30,30);

按照下面的方式就可以修改控制相关参数的变化

    //更新相关位置
    light.position.y = gui.lightY;
    sphere.position.x = gui.sphereX;
    sphere.position.z = gui.sphereZ;
    cube.position.x = gui.cubeX;
    cube.position.z = gui.cubeZ;

它最常用的方法就是添加控件 add(),监听控件 listen()

gui.add(obj, 'key').listen();

stats.min.js 是 three.js 的性能监测插件.我们可以通过 stats 知道实时的 FPS 信息,从而更好地监测动画渲染效果,具体的使用在下面例子中展现

OrbitControls 是相机控件,通过 OrbitControls.js 可以对 Three.js 的三维场景进行缩放、平移、旋转操作,本质上改变的并不是场景,而是相机的参数,相机的位置角度不同,同一个场景的渲染效果是不一样,比如一个相机绕着一个场景旋转,就像场景旋转一样。它的源码在Three.js\examples\js\controls目录下的OrbitControls.js文件

了解了相关插件的作用,接下来进入实操:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ThreeJS的3D文字场景</title>
    
	<script src="https://johnson2heng.github.io/three.js-demo/lib/three.js"></script>
	<!--几何体类库-->
	<script src="https://johnson2heng.github.io/three.js-demo/lib/js/utils/GeometryUtils.js"></script>
	<!--相机控件-->
	<script src="https://johnson2heng.github.io/three.js-demo/lib/js/controls/OrbitControls.js"></script>
	<!--性能控件-->
	<script src="https://johnson2heng.github.io/three.js-demo/lib/js/libs/stats.min.js"></script>
	<!--可视化调试-->
	<script src="https://johnson2heng.github.io/three.js-demo/lib/js/libs/dat.gui.min.js"></script>
	
	<style type="text/css">
        html, body {
            margin: 0;
            height: 100%;
        }

        canvas {
            display: block;
        }

    </style>
</head>
<body onload="draw();">
</body>

<script>
    var renderer;

    function initRender() {
        renderer = new THREE.WebGLRenderer({antialias: true});
        //renderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0)); //设置背景颜色
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.body.appendChild(renderer.domElement);
    }

    var camera;
    function initCamera() {
        camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
        camera.position.set(0, 200, 500);
    }

    var scene;

    function initScene() {
        scene = new THREE.Scene();
    }

    var light;

    function initLight() {
        scene.add(new THREE.AmbientLight(0x404040));

        light = new THREE.DirectionalLight(0xffffff);
        light.position.set(1, 1, 1);
        scene.add(light);
    }

    function initModel() {

        //轴辅助 (每一个轴的长度)
        object = new THREE.AxesHelper(50);
        scene.add(object);
    }

    //生成模型
    function createMesh(geom) {

        //设置当前的模型矩阵沿xy轴偏移,让图片处于显示中心
        geom.applyMatrix(new THREE.Matrix4().makeTranslation(-250, -100, 0));

        // 创建法向量纹理
        var meshMaterial = new THREE.MeshNormalMaterial({
            flatShading: THREE.FlatShading,
            transparent: true,
            opacity: 0.9
        });

        //  创建一个线框纹理
        var wireFrameMat = new THREE.MeshBasicMaterial();
        wireFrameMat.wireframe = true;

        // 创建模型
        var mesh = THREE.SceneUtils.createMultiMaterialObject(geom, [meshMaterial, wireFrameMat]);

        return mesh;
    }

    //初始化性能插件
    var stats;

    function initStats() {
        stats = new Stats();
        document.body.appendChild(stats.dom);
    }

    //用户交互插件 鼠标左键按住旋转,右键按住平移,滚轮缩放
    var controls;

    function initControls() {

        controls = new THREE.OrbitControls(camera, renderer.domElement);

        // 如果使用animate方法时,将此函数删除
        //controls.addEventListener( 'change', render );
        // 使动画循环使用时阻尼或自转 意思是否有惯性
        controls.enableDamping = true;
        //动态阻尼系数 就是鼠标拖拽旋转灵敏度
        //controls.dampingFactor = 0.25;
        //是否可以缩放
        controls.enableZoom = true;
        //是否自动旋转
        controls.autoRotate = false;
        //设置相机距离原点的最远距离
        controls.minDistance = 20;
        //设置相机距离原点的最远距离
        controls.maxDistance = 10000;
        //是否开启右键拖拽
        controls.enablePan = true;
    }

    //生成gui设置配置项
    var gui;
    var text1,text2;
    function initGui() {
        //声明一个保存需求修改的相关数据的对象
        gui = {
            size: 90,
            height: 90,
            bevelThickness: 2,
            bevelSize: 0.5,
            bevelEnabled: true,
            bevelSegments: 3,
            curveSegments: 12,
            steps: 1,
            fontName: "helvetiker",
            fontWeight:"bold",
            weight: "normal",
            font:null,
            style:"italics",
            changeFont:function () {
                //创建loader进行字体加载,供后面的模型使用
                var loader = new THREE.FontLoader();
                loader.load( './3d/js/Microsoft YaHei_Regular.json', function ( response ) {
                    gui.font = response;
                    gui.asGeom();
                } );
            },
            asGeom: function () {
                // 删除掉原来的旧模型
                scene.remove(text1);
                scene.remove(text2);
                // 重新创建模型
                var options = {
                    size: gui.size,
                    height: gui.height,
                    weight: gui.weight,
                    font: gui.font,
                    bevelThickness: gui.bevelThickness,
                    bevelSize: gui.bevelSize,
                    bevelSegments: gui.bevelSegments,
                    bevelEnabled: gui.bevelEnabled,
                    curveSegments: gui.curveSegments,
                    steps: gui.steps
                };

                text1 = createMesh(new THREE.TextGeometry("InfoQ 15周年庆!", options));
                text1.position.z = -100;
                text1.position.y = 100;
                scene.add(text1);

                text2 = createMesh(new THREE.TextGeometry("\n无限生长\n未来可期!", options));
                scene.add(text2);
            }
        };
        var datGui = new dat.GUI();
        //将设置属性添加到gui当中,gui.add(对象,属性,最小值,最大值)
        datGui.add(gui, 'size', 0, 200).onChange(gui.asGeom);
        datGui.add(gui, 'height', 0, 200).onChange(gui.asGeom);
        datGui.add(gui, 'fontName', ['gentilis', 'helvetiker', 'optimer']).onChange(gui.changeFont);
        datGui.add(gui, 'fontWeight', ['regular', 'bold']).onChange(gui.changeFont);
        datGui.add(gui, 'bevelThickness', 0, 10).onChange(gui.asGeom);
        datGui.add(gui, 'bevelSize', 0, 10).onChange(gui.asGeom);
        datGui.add(gui, 'bevelSegments', 0, 30).step(1).onChange(gui.asGeom);
        datGui.add(gui, 'bevelEnabled').onChange(gui.asGeom);
        datGui.add(gui, 'curveSegments', 1, 30).step(1).onChange(gui.asGeom);
        datGui.add(gui, 'steps', 1, 5).step(1).onChange(gui.asGeom);

        //调用生成一次图形
        gui.changeFont();
    }

    function render() {
        renderer.render(scene, camera);
    }

    //窗口变动触发的函数
    function onWindowResize() {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        render();
        renderer.setSize(window.innerWidth, window.innerHeight);

    }

    function animate() {
        //更新控制器
        controls.update();
        render();

        //更新性能插件
        stats.update();
        requestAnimationFrame(animate);
    }

	//渲染到页面
    function draw() {
        initRender();
        initScene();
        initCamera();
        initLight();
        initModel();
        initControls();
        initStats();
        initGui();

        animate();
        window.onresize = onWindowResize;
    }
</script>
</html>

实例当中所用到的字体可通过http://gero3.github.io/facetype.js/转换

效果如下:

总结

这只是 three.js 的基本用法,想更好地掌握和运用 three.js,创建更多炫酷的 3D 场景,需要拥有丰富的空间思维和空间想象力.three.js 还有更多强大的 3D 场景待你发掘......

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/d3690f759c2c538d19e35df4d
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券