three.js 02-03 之自定义形状

59 篇文章 2 订阅
59 篇文章 6 订阅

    上一篇,我们已经学习了如何使用 Three.js 提供的几种常用的几何对象及网格。今天,我们来重点学习一下,如何通过自定义顶点和面来构建自己的几何体,并用它来创建网格。我们先来一个完整的例子,代码如下:

<!DOCTYPE html>
<html>
<head>
    <title>示例 02.03 - 自定义形状</title>
	<script src="../build/three.js"></script>
	<script src="../build/js/controls/OrbitControls.js"></script>
	<script src="../build/js/libs/stats.min.js"></script>
	<script src="../build/js/libs/dat.gui.min.js"></script>
	<script src="../jquery/jquery-3.2.1.min.js"></script>
    <style>
        body {
            /* 设置 margin 为 0,并且 overflow 为 hidden,来完成页面样式 */
            margin: 0;
            overflow: hidden;
        }
		/* 统计对象样式 */
		#Stats-output {
			position: absolute;
			left: 0px;
			top: 0px;
		}
    </style>
</head>
<body>

<!-- 用于 WebGL 输出的 Div -->
<div id="WebGL-output"></div>
<!-- 用于统计 FPS 输出的 Div -->
<div id="Stats-output"></div>

<!-- 运行 Three.js 示例的 Javascript 代码 -->
<script type="text/javascript">

	var scene;
	var camera;
	var render;
	var controls;
	var stats;
	
	var guiControls;
	var gui;
	
	var plane;
	var mesh;
	var controlPoints = [];

    // 当所有元素加载完毕后,就执行我们 Three.js 相关的东西
    $(function() {
		stats = initStats();
		
		scene = new THREE.Scene();
		camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); // 2147483647
		camera.position.set(-20, 25, 20);
		camera.lookAt(new THREE.Vector3(5, 0, 0));
		render = new THREE.WebGLRenderer( {antialias: true} ); // antialias 抗锯齿
		render.setSize(window.innerWidth, window.innerHeight);
		render.setClearColor(0xEEEEEE);
		render.shadowMap.enabled = true; // 允许阴影投射
		$('#WebGL-output')[0].appendChild(render.domElement);
		window.addEventListener('resize', onWindowResize, false);
		controls = new THREE.OrbitControls(camera, render.domElement);
		
		scene.add(new THREE.AxisHelper(20));// 加入坐标轴
		
		// 加入一个平面
		var planeGeometry = new THREE.PlaneGeometry(60, 40, 1, 1);
		var planeMaterial = new THREE.MeshLambertMaterial( {color: 0xFFFFFF} );
		plane = new THREE.Mesh(planeGeometry, planeMaterial);
		plane.rotation.x = -0.5 * Math.PI; // 沿着 X轴旋转-90°
		plane.position.x = 0;
		plane.position.y = 0;
		plane.position.z = 0;
		plane.receiveShadow = true; // 几何平面接收阴影
		scene.add(plane);
		
		// 加入一个环境光源
		var ambientLight = new THREE.AmbientLight(0x090909);
		scene.add(ambientLight);
		
		// 加入一个聚光灯光源
		// 注:基础材质 MeshBasicMaterial 不会对光源产生反应,因此要改用 MeshLambertMaterial 或 MeshPhongMaterial 材质才有效果
		var spotLight = new THREE.SpotLight( 0xFFFFFF);
		spotLight.position.set(-40, 40, 50);
		spotLight.castShadow = true; // 光源产生阴影
		spotLight.shadow.mapSize.width = 1024; // 必须是 2的幂,默认值为 512
		spotLight.shadow.mapSize.height = 1024; // 必须是 2的幂,默认值为 512
		scene.add(spotLight);
		
		guiControls = new function() {
			// 克隆处理函数
			this.clone = function() {
				var cloneGeometry = mesh.children[0].geometry.clone();
				var materials = [
                    new THREE.MeshLambertMaterial({opacity: 0.6, color: 0xff44ff, transparent: true}),
                    new THREE.MeshBasicMaterial({color: 0x000000, wireframe: true})

                ];
				var cloneMesh = THREE.SceneUtils.createMultiMaterialObject(cloneGeometry, materials);
				cloneMesh.children.forEach(function(e){
					e.castShadow = true;
				});
				
				cloneMesh.translateX(5);
				cloneMesh.translateZ(5);
				cloneMesh.name = "cloneMesh";
				scene.remove(scene.getObjectByName("cloneMesh"));
				scene.add(cloneMesh);
			}
		}
		
		function addControl(x, y, z) {
			var controlPoint = new function() {
				this.x = x;
				this.y = y;
				this.z = z;
			};
			return controlPoint;
		}
		controlPoints.push(addControl(3, 5, 3));
		controlPoints.push(addControl(3, 5, 0));
		controlPoints.push(addControl(3, 0, 3));
		controlPoints.push(addControl(3, 0, 0));
		controlPoints.push(addControl(0, 5, 0));
		controlPoints.push(addControl(0, 5, 3));
		controlPoints.push(addControl(0, 0, 0));
		controlPoints.push(addControl(0, 0, 3));
		
		gui = new dat.GUI();
		gui.add(guiControls, 'clone');
		for (var i = 0; i < 8; i++) {
			var folder = gui.addFolder('Vertices ' + (i+1));
			folder.add(controlPoints[i], 'x', -10, 10);
			folder.add(controlPoints[i], 'y', -10, 10);
			folder.add(controlPoints[i], 'z', -10, 10);
		}
		
		addGeomtries();
		
		renderScene();
    });
	
	/** 初始化 stats 统计对象 */
	function initStats() {
		stats = new Stats();
		stats.setMode(0); // 0 为监测 FPS;1 为监测渲染时间
		$('#Stats-output').append(stats.domElement);
		return stats;
	}
	
	/** 渲染场景 */
	function renderScene() {
		stats.update();
		updateMesh(); // 更新物体
		//rotateMesh(); // 旋转物体
		requestAnimationFrame(renderScene);
		render.render(scene, camera);
	}
	
	/** 当浏览器窗口大小变化时触发 */
	function onWindowResize() {
		camera.aspect = window.innerWidth / window.innerHeight;
		camera.updateProjectionMatrix();
		render.setSize(window.innerWidth, window.innerHeight);
	}
	
	/** 添加物体 */
	function addGeomtries() {
		// 通过一系列点创建物体
		var vertices = [
            new THREE.Vector3(1, 3, 1),
            new THREE.Vector3(1, 3, -1),
            new THREE.Vector3(1, -1, 1),
            new THREE.Vector3(1, -1, -1),
            new THREE.Vector3(-1, 3, -1),
            new THREE.Vector3(-1, 3, 1),
            new THREE.Vector3(-1, -1, -1),
            new THREE.Vector3(-1, -1, 1)
        ];
        var faces = [
            new THREE.Face3(0, 2, 1),
            new THREE.Face3(2, 3, 1),
            new THREE.Face3(4, 6, 5),
            new THREE.Face3(6, 7, 5),
            new THREE.Face3(4, 5, 1),
            new THREE.Face3(5, 0, 1),
            new THREE.Face3(7, 6, 2),
            new THREE.Face3(6, 3, 2),
            new THREE.Face3(5, 7, 0),
            new THREE.Face3(7, 2, 0),
            new THREE.Face3(1, 3, 4),
            new THREE.Face3(3, 6, 4),
        ];
        var geom = new THREE.Geometry();
        geom.vertices = vertices;
        geom.faces = faces;
        geom.computeFaceNormals();
		
		var materials = [
			new THREE.MeshLambertMaterial( {color: 0x44ff44, opacity: 0.6, transparent: true} ),
			new THREE.MeshBasicMaterial( {color: 0x000000, wireframe: true} )
		];
		// 创建多材质对象
		mesh = THREE.SceneUtils.createMultiMaterialObject(geom, materials);
		mesh.traverse(function(e){
			e.castShadow = true;
		});
		mesh.name = geom.type + "-" + 4;
		scene.add(mesh);
	}
	
	/** 更新物体 */
	function updateMesh() {
		mesh.children.forEach(function(e) {
			for (var i = 0; i < 8; i++) {
				e.geometry.vertices[i].x = controlPoints[i].x;
				e.geometry.vertices[i].y = controlPoints[i].y;
				e.geometry.vertices[i].z = controlPoints[i].z;
			}
			e.geometry.verticesNeedUpdate = true;
			e.geometry.computeFaceNormals();
		});
	}
	
	/** 转动物体 */
	rotationSpeed = 0.02
	function rotateMesh() {
		// 遍历整个场景
		scene.traverse(function(e) {
			if (e instanceof THREE.Mesh && e != plane) {
				e.rotation.x += rotationSpeed;
				e.rotation.y += rotationSpeed;
				e.rotation.z += rotationSpeed;
			}
		});
	}

</script>
</body>
</html>
    这个示例的框架其实跟我们前面讲过的其他示例框架基本一样,都有一个 renderScene() 函数。在这个例子中,对于场景中立方体的任何一个顶点,无论何时,只要你修改了右上角这个属性面板中下拉列表里的值,场景中的立方体都会按照修改后的位置正确地渲染出来。为了实现这个目的,在我们的 updateMesh() 函数中,我们把场景里的这个立方体 mesh 的每一个 vertices 属性都更新一遍。更新后我们需要告诉 geometry 对象,这些顶点需要更新,也就是将 geometry 的 verticesNeedUpdate 属性设置为 true。最后我们需要通过调用 geometry 的 computeFaceNormals() 函数重新计算侧面,从而完成整个模型的更新。

    其中,我们还用到了一个 geometry 对象的 clone() 函数(克隆、复制函数)。通过它我们可以创建出一个 geometry 的副本,赋予不同的材质后,我们就可以用这个副本创建出一个不同的网格对象。在这个示例中,你可以通过点击右上角界面中的 clone 按钮来试验这个特性。

    另外,比较细心的读者可能会发现,我们的立方体同时使用了两种不同的材质。这是通过使用 SceneUtils.createMultiMaterialObject() 函数来达到这个目的。在这个函数里,Three.js 库创建的不是一个 THREE.Mesh 实例,而是为每个指定的材质创建一个实例(此处是两个实例),并把这些实例存放在一个名叫 Group 的组里。这样使得我们可以像使用 Scene 对象那样使用这个 Group 组对象,我们可以向其添加网格、通过名字查找网格等。例如,要为组中的每个子对象添加阴影,我们可以像下面这样做:

mesh.children.forEach(function(e){e.castShadow = true});
最后,关于此示例中的其他一些细节方面,请读者多研究几遍,我在代码中已经做了足够的注释。

未完待续···

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
对整threeJS体系进行全面剖析。整理出全面的教学大纲,涵盖内容面非常广。此教学版本为threeJS107版本。关于版本不建议大家使用低于90的版本学习。以下是课程目录1-ThreeJS概览(基本图形简介,什么是点线面如何绘制点线面,什么是材质,什么是几何体,什么是相机,什么是渲染器,什么是场景)2-相机和渲染器(详解相机类型,渲染器如何使用,针对不同场景怎么用,怎么调效果,怎么渲染,怎么输出画布,如何解决透明问题等等)3-创建平面几何(常见的几何体如何使用,如何使用简单的几何体绘制出自定义自己想要的几何体,关于几何体的性能剖析,如何解决性能,几何体的渲染原理)4-高级图形算法常见库(求直线的斜率  计算线段与圆的交点 计算线段的长度 判断折线是否在多边形内 等等)5-sprite精灵(怎么让一个图标永远朝向屏幕,精灵的属性,精灵材质原理等,广告提示框必用)6-骨骼游戏动画(什么是模型动画,常见游戏案例,如何让人头进行各种攻击动作)7-3d模型加载(常见模型格式,如何渲染不同格式,不同格式的特点,什么格式性能优越,模型渲染异常,贴图不显示等问题详解)8-高阶动态纹理(你所不知道的纹理用法,我说你不知道,你肯定不知道)9-漫游轨迹以及其动画路径(怎么绘制贝塞尔曲线,如何使用曲线上的路径,跟随路径移动的原理,相机如何运动,物体如何运动)10-着色器(什么是着色器。初识着色器基础,着色器材质怎么用,怎么使用着色器库)11-常见渲染以及透明度问题12-对象拾取以及拖拽(3d世界里面如何拖拽物体,拖拽的原理,mousemove mouseon等的事件效果)13-世界坐标以及组的问题(什么是相对坐标,什么是世界坐标,什么是当前坐标,怎么转化父子坐标系,组的优化,为什么用组,组的优势)14-指定对象旋转中心(什么是物体的几何体中心,如何改变中心,如何绕轴转动)15-层级对象渲染(多个场景一键切换,切换的优势,针对大项目的用法)16-拓展了解系列(不定期不断更新案例,各种酷炫效果bloom,halo等,以及各种3d图表,粒子案例等,不断构建你的3d实践能力)

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值