Three.js渲染场景及模型

今天来分享一篇自己的得意之作,哈哈,也不算是作品吧,就是自己当时学习Three.js框架时,自己写的一个小demo。当时学习这个老不容易了,因为算是几年前学习了,网上资料不全,官网Api也不是给我这样的凡人写的,只能靠着那些残缺的小视频,小实例来学习。现在经常不用怕越来越模糊了,今天正好借着CSDN这个平台发一下小笔记,避免一个demo丢失。

主要内容:

  1. 模型的加载(glb/fbx)
  2. 模型加载监听
  3. 添加镜子
  4. 移动动画(TWEEN)插件
  5. 创建2d标签
  6. 添加控制器
  7. 模型的环境周围添加贴纸
  8. 添加海洋效果
  9. 简单的交互效果
<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>立体图形</title>
	<script src="./3d/libs/three.js"></script>
	<script src="./3d/libs/js/controls/OrbitControls.js"></script>
	<script src="./3d/libs/js/loaders/FBXLoader.js"></script>
	<script src="./3d/libs/js/loaders/GLTFLoader.js"></script>
	<script src="./3d/libs/js/objects/Reflector.js"></script>
	<script src="./3d/libs/js/lib/inflate.module.min.js"></script>
	<script src="./3d/libs/tween.min.js"></script>
	<script src="./3d/libs/CSS2DRenderer.js"></script>
	<script src="./3d/examples/js/postprocessing/EffectComposer.js"></script>
	<script src="./3d/examples/js/postprocessing/RenderPass.js"></script>
	<script src="./3d/examples/js/postprocessing/OutlinePass.js"></script>
	<script src="./3d/examples/js/shaders/CopyShader.js"></script>
	<script src="./3d/examples/js/shaders/FXAAShader.js"></script>
	<script src="./3d/examples/js/postprocessing/shaderPass.js"></script>
	<script src="./3d/examples/js/objects/Water.js"></script>
	<script src="./3d/examples/js/objects/Sky.js"></script>
</head>
<style>
	/**{ margin: 0; padding: 0;}*/
	#threeContainer {
		width: 100%;
		height: 100%;
		margin: 0;
		padding: 0;
		position: absolute;
		top: 0;
		left: 0;
		overflow: hidden;
	}
	#cover {
		width: 100%;
		height: 100%;
		position: absolute;
		top: 0;
		align-items: center;
		left: 0;
		z-index: 999;
		background-color: rgba(0, 0, 0, 0.5);
	}
	#cover div {
		height: 30px;
		width: 100%;
		margin: 0 5px;
		text-align: center;
		position: relative;
		transition: all 0.5s;
		color: #ffffff;
		border: 1px solid rosybrown;
	}
	#cover div span {
		height: 100%;
		width: 0;
		position: absolute;
		top: 0;
		left: 0;
		z-index: 1;
		display: block;
		opacity: 0.7;
		background-color: rgb(219, 53, 53);
	}
	.label {
		padding: 10px;
		background-color: rgba(236, 216, 34, 0.5);
		color: #03548f;
		display: block;
		border: 2px solid #cc99c5;
		border-bottom-left-radius: 20px;
	}
	.operation{
		width: 100%;
		position: absolute;
		top: 5px;
		display: flex;
		justify-content: start;
		align-items: center;
		left: 0;
		z-index: 40;
		background-color: rgba(55, 135, 150 , 0.3);
	}
	.select{
		margin: 5px 10px;
		width: 100px;
		height: 25px;
		display: inline-block;
		color: #ffffff;
		background-color: sienna;
	}
	.go{
		width: 60px;
		height: 25px;
		cursor: pointer;
		line-height: 25px;
		color: #03548f;
		font-size: 13px;
		text-align: center;
		background-color: burlywood;
	}
</style>

<body>
	<div id="threeContainer"></div>
	<div class="label"></div>
	<div id="cover"></div>
	<div class="operation">
		<select class="select">
			<option>请选择场景</option>
			<option>天空</option>
			<option>星空</option>
			<option>海洋</option>
			<option>平面</option>
		</select>
		<div class="go">点击前进</div>
	</div>
	<script>
		//初始化
		var initial = {
			container: document.getElementById("threeContainer"),
			cover: document.getElementById("cover"),
			clock : new THREE.Clock(),
			scene: null,  //场景
			camera: null,	//相机
			renderer: null,	//渲染
			labelRenderer: null,	//2d渲染
			group: null,		//组一
			group1: null,		//组二
			group3: null,       //组三
			point: null,		//点光源
			ambient: null,		//环境光
			directional: null,	//平行光
			controls: null,		//鼠标控制器
			model: null,		//保存模型
			parent: null,		//保存点击的上一个模型
			defaultMaterial: {
				material_1: new THREE.MeshPhongMaterial({ color: 0x21b9f3, transparent: true, opacity: 0.5 }),
				material_2: new THREE.MeshPhongMaterial({ color: 0x162bef, transparent: true, opacity: 0.5  })
			},
			isFlat: false,		//判断是否要执行点击事件
			composer: null,		//利用插件EffectCompose,给模型添加轮廓线
			water : null        //创建水
		}
		//渲染场景相机
		function rendering() {
			this.clock = new THREE.Clock();
			this.scene = new THREE.Scene();
			this.scene.fog = new THREE.Fog(0xffffff,500,10000);
			this.group = new THREE.Group();
			this.group.name = "group";
			this.group1 = new THREE.Group();
			this.group1.name = "group1";
			this.group3 = new THREE.Group();
			this.group3.name = "group3";
			this.camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 10000);
			this.camera.position.set(100, 100, 100);
			this.camera.lookAt(this.scene.position);
			this.renderer = new THREE.WebGLRenderer({ alpha: true });
			this.renderer.setPixelRatio(window.devicePixelRatio);
			this.renderer.setSize(window.innerWidth, window.innerHeight);
			this.renderer.setClearColor(0xcccccc);
			this.renderer.shadowMap.enabled = true;
			this.container.appendChild(this.renderer.domElement)
		}
		//制作灯光
		function lighting() {
			this.point = new THREE.PointLight(0xfffaaf); 
			this.point.position.set(50, 40, 50);
			this.point.name = "point"
			this.ambient = new THREE.AmbientLight(0xffffff, 0.5);
			this.ambient.name = "ambient"
			this.directional = new THREE.DirectionalLight(0xffffff);
			this.directional.name = "directional"
		}
		//往场景中添加东西
		function adding() {
			this.scene.add(this.camera);
			this.scene.add(this.point);
			this.scene.add(this.ambient);
			this.scene.add(this.directional);
		}
		//制作平面矩形
		function CreatPlane() {
			let plane = new THREE.PlaneBufferGeometry(5000, 5000);
			let planeMaterial = new THREE.MeshLambertMaterial({
				color: 0xfffaaa,
				transparent: true,
				opacity: 0.2,
				fog:true
			})
			let mesh = new THREE.Mesh(plane, planeMaterial);
			mesh.position.y = 0;
			mesh.rotation.x = -Math.PI / 2;
			mesh.name = "plane";
			mesh.receiveShadow = true;
			this.group.add(mesh);
			this.scene.add(this.group);
		}
		//添加镜子
		function Reflector() {
			let ReflectorGeometry = new THREE.PlaneBufferGeometry(5000, 5000);
			let ReflectorMaterial = new THREE.Reflector(ReflectorGeometry, {
				clipBias: 1,
				textureWidth: window.innerWidth * window.devicePixelRatio,
				textureHeight: window.innerHeight * window.devicePixelRatio,
				color: 0xffffff,
				recurstion: 1,
				fog:true
			})
			ReflectorMaterial.name = "Reflector";
			ReflectorMaterial.rotation.x = -Math.PI / 2;
			ReflectorMaterial.position.y = -0.2;
			ReflectorMaterial.receiveShadow = true;
			this.group.add(ReflectorMaterial)
			this.scene.add(this.group);
		}
		//加载模型--glb
		function loaders_glb(url) {
			let self = this;
			const div = document.createElement("div");
			const span = document.createElement("span");
			let loader = new THREE.GLTFLoader();
			loader.load("./3d/models/"+url, function (object) {
				self.model = object.scene;
				self.model.traverse(function (child) {
					child.castShadow = true;
				})
				self.model.scale.set(6,6,6);
				self.model.position.y = 0.1;
				self.model.name = "model"
				self.scene.add(self.model);
				DisplayLabel(self.model)
				// label_sprite(self.model.children[0])   //添加sprite标签
			}, onProgress)
			function onProgress(xhr) {
				let percent = parseInt(xhr.loaded / xhr.total * 100);
				if (percent < 100) {
					self.cover.style.display = "flex"
					div.innerHTML = "加载中 ..." + percent + "%";
					span.style.cssText = "width:" + percent + "%;"
				} else {
					self.cover.style.display = "none"
					div.innerText = "";
					span.style.cssText = "width:0px;"
				}
				div.appendChild(span);
			}
			self.cover.appendChild(div)
		}
		//加载模型--fbx
		function loaders_fbx(url) {
			let self = this;
			const div = document.createElement("div");
			const span = document.createElement("span");
			let loader = new THREE.FBXLoader();
			loader.load("./3d/models/"+url, function (object) {
				self.model = object;
				self.model.traverse(function (child) {
					child.castShadow = true;
				})
				self.model.scale.set(6,6,6);
				self.model.position.y = 0.1;
				self.model.name = "model"
				self.scene.add(self.model);
				DisplayLabel(self.model)
				// label_sprite(self.model.children[0])   //添加sprite标签
			}, onProgress)
			function onProgress(xhr) {
				let percent = parseInt(xhr.loaded / xhr.total * 100);
				if (percent < 100) {
					self.cover.style.display = "flex"
					div.innerHTML = "加载中 ..." + percent + "%";
					span.style.cssText = "width:" + percent + "%;"
				} else {
					self.cover.style.display = "none"
					div.innerText = "";
					span.style.cssText = "width:0px;"
				}
				div.appendChild(span);
			}
			self.cover.appendChild(div)
		}
		//鼠标点下
		function onmousedown(event) {
			initial.isFlat = true
		}
		//鼠标移动
		function onmousemove(event) {
			initial.isFlat = false
		}
		//鼠标点起
		function onmouseup(event) {
			const { offsetX, offsetY } = event;
			if (initial.isFlat) {
				raycaster.call(initial, offsetX, offsetY)
			}
		}
		//添加辅助线
		function axisHeiper(initial) {
			var axisHelper = new THREE.GridHelper(500, 500);
			axisHelper.position.y = 0.01;
			initial.scene.add(axisHelper);
		}
		//点击获取模型
		function raycaster(offsetX, offsetY) {
			let self = this;
			let x = (offsetX / window.innerWidth) * 2 - 1;
			let y = (offsetY / window.innerHeight) * 2 - 1;
			var raycaster = new THREE.Raycaster();
			var mousePoint = new THREE.Vector2(x, -y);
			self.composer = new THREE.EffectComposer(self.renderer);
			let renderPass = new THREE.RenderPass(self.scene, self.camera);
			let selectedObjects = [];
			self.composer.addPass(renderPass);
			let outlinePass = new THREE.OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), self.scene, self.camera);
			outlinePass.edgeStrength = 5;   //包围线的浓度
			outlinePass.edgeGlow = 0.5;	    //边缘线的范围
			outlinePass.edgeThickness = 2;	//边缘线的浓度
			outlinePass.pulsePeriod = 2;	//包围线的闪烁频率
			outlinePass.visibleEdgeColor.set("#25afc2");	//包围线的颜色
			outlinePass.hiddenEdgeColor.set("#190a05");		//被遮挡的边界线的颜色
			self.composer.addPass(outlinePass);
			var effectFXAA = new THREE.ShaderPass(THREE.FXAAShader);
			effectFXAA.uniforms["resolution"].value.set(1 / window.innerWidth, 1 / window.innerHeight);
			effectFXAA.renderToScreen = true;
			self.composer.addPass(effectFXAA);
			raycaster.setFromCamera(mousePoint, self.camera);
			let intersects = raycaster.intersectObjects(self.scene.children, true);
			intersects = intersects.filter(intersects => intersects.object.parent.name != null&&intersects.object.parent.name === 'model');
			if (intersects.length != 0) {
				if (parent != null) {
					parent.material = self.defaultMaterial.material_1
				}
				self.defaultMaterial.material_1 = intersects[0].object.material;
				intersects[0].object.material = self.defaultMaterial.material_2;
				parent = intersects[0].object;
				selectedObjects.push(intersects[0].object);
				outlinePass.selectedObjects = selectedObjects
				tween.call(self, intersects[0], 3000)
			} else {
				if (parent == null) {
					return;
				}
				parent.material = self.defaultMaterial.material_1
				parent = null;
				self.composer = null;
				self.composer = null;
				self.model.children.forEach(function(ment,i){
					if(ment.name === "label"){
						self.model.remove(ment)
					}
				})
			}
		}
		//移动动画,创建间隔
		function tween(obj, times) {
			let self = this;
			var bs = obj.point.clone();
			bs.multiplyScalar(2);
			let tween = new TWEEN.Tween(self.camera.position)
				.to({ x: bs.x, y: bs.y, z: bs.z }, times)
				.easing(TWEEN.Easing.Quintic.InOut)
				.onUpdate(function (o) {
					if (o == 1) {
						create_div(self.model, obj)
					}
				})
				.start()
		}
		//创建2d标签
		function create_div(model, obj) {
			model.children.forEach(function(ment,i){
				if(ment.name === "label"){
					model.remove(ment)
				}
			})
			let label = document.createElement("div");
			label.className = "label";
			document.body.appendChild(label);
			let { x, y, z } = obj.object.geometry.boundingSphere.center;
			let labelObject = new THREE.CSS2DObject(label);
			labelObject.name = "label";
			label.innerHTML = obj.object.name;
			labelObject.position.set(x, y, z);
			model.add(labelObject);
		}
		//渲染2d标签
		function render_css(initial) {
			initial.labelRenderer = new THREE.CSS2DRenderer();
			initial.labelRenderer.setSize(window.innerWidth, window.innerHeight);
			initial.labelRenderer.domElement.style.position = "absolute";
			initial.labelRenderer.domElement.style.left = "0px";
			initial.labelRenderer.domElement.style.top = "0px";
			initial.labelRenderer.domElement.style.zIndex = "5";
			initial.container.appendChild(initial.labelRenderer.domElement);
		}
		//添加控制器
		function controls() {
			this.controls = new THREE.OrbitControls(this.camera, this.labelRenderer.domElement);
			this.controls.minDistance = 10;
			this.controls.maxDistance = 800;
			this.controls.target.set(0, 0, 0);
			this.controls.enabled = true;
			this.controls.addEventListener("change",render,false)
		}
		//创建sprite精灵标签
		function label_sprite(obj) {
			let text = obj.name == undefined ? obj.object.name : obj.name;
			let geom = obj.geometry == undefined ? obj.object.geometry : obj.geometry;
			geom.computeBoundingSphere();
			let {x,y,z} = geom.boundingSphere.center.clone().multiplyScalar(3);
			var sprite = makeTextSprite(text, {});
			sprite.center = new THREE.Vector2(0, 0);
			initial.group1.children.push(sprite);
			initial.scene.add(initial.group1);
			sprite.position.set(x,y,z);
		}
		function makeTextSprite(text, params) {
			let fontsize = params.hasOwnProperty("fontsize") ? params.fontsize : 10;
			let borderColor = params.hasOwnProperty("borderColor") ? params.borderColor : "#00aaf2";
			let backgroundColor = params.hasOwnProperty("backgroundColor") ? params.backgroundColor : "#000000";
			let borderWidth = 3;
			const canvas = document.createElement("canvas");
			const ctx = canvas.getContext("2d");
			ctx.save();
			ctx.fillStyle = backgroundColor;
			ctx.font = fontsize + "px 微软雅黑";
			let mic = ctx.measureText(text);
			let width = mic.width + 10;
			let height = (fontsize + (borderWidth * 2));
			canvas.style.cssText = "width:" + width + "px;height:" + height + "px";
			ctx.fillText(text, borderWidth, fontsize + borderWidth);
			ctx.strokeStyle = borderColor;
			ctx.lineWidth = borderWidth;
			ctx.beginPath();
			ctx.moveTo(0, 0);
			ctx.lineTo(width, 0);
			ctx.lineTo(width, height);
			ctx.lineTo(0, height);
			ctx.closePath();
			ctx.stroke();
			ctx.restore();
			let texture = new THREE.Texture(canvas);
			texture.needsUpdate = true;
			texture.wrapT = texture.wrapT = THREE.RepeatWrapping;
			let spriteMaterial = new THREE.SpriteMaterial({ map: texture });
			let sprite = new THREE.Sprite(spriteMaterial);
			sprite.scale.set(60, 20, 1);
			return sprite;
		}
		//页面变动执行方法
		function onResize() {
			let width = window.innerWidth;
			let height = window.innerHeight;
			initial.camera.aspect = width / height;
			initial.camera.updateProjectionMatrix();
			initial.renderer.setSize(width, height);
			initial.labelRenderer.setSize(width, height);
		}
		//点击显示标签
		function DisplayLabel(model){ 
			model.children.forEach((child,index) =>{
				let obj = child;
				// label_sprite(obj);
			})
		}
		//创建贴纸
		function texture(initial,url){
			let path = `./3d/assets/${url}/`;
			let format = ".jpg"
			let ulrs = [
				path+"px"+format,path+"nx"+format,
				path+"py"+format,path+"ny"+format,
				path+"pz"+format,path+"nz"+format
			]
			let CubeTexture = new THREE.CubeTextureLoader().load(ulrs);
			initial.scene.background = CubeTexture;
			return CubeTexture;
		}
		//添加海洋的方法
		function water(initial){
			let waterPlane = new THREE.PlaneBufferGeometry(10000,10000);
			initial.water = new THREE.Water(waterPlane,{
				textureWidth:512,
				textureHeight:512,
				waterNormals:new THREE.TextureLoader().load("./3d/assets/waternormals.jpg",function(texture){
					texture.wrapT = texture.wrapS = THREE.RepeatWrapping
				}),
				sunColor:0xffffff,
				waterColor:0x03548f,
				distortionScale:3.7,
				fog:initial.scene.fog != undefined
			})
			initial.water.rotation.x = -Math.PI / 2;
			initial.water.position.y = 1;
			initial.water.name = "water";
			initial.group.add(initial.water);
			initial.scene.add(initial.group3);
		}
		//选择场景贴纸方法
		function select_texture(initial){
			let select = document.getElementsByClassName("select")[0];
			select.onchange=function(val){
				var v = val.target.value;
				if(v === "天空"){
					texture(initial,"skyboxsun25deg")
				}else if(v === "星空"){
					texture(initial,"MilkyWay")
				}else if(v === "海洋"){
					water(initial)
				}else if(v === "平面"){
					CreatPlane.call(initial);
				}else{
					alert("抱歉,暂时没有")
				}
			}
		}
		//页面渲染
		function render() {
			let time = new THREE.Clock();
			let self = this;
			if(this.labelRenderer && this.labelRenderer.render){
				self.labelRenderer.render(self.scene, self.camera);
				self.renderer.render(self.scene, self.camera)
				self.camera.lookAt(self.scene.position)
				TWEEN.update()
				if (self.composer) {
					// self.composer.render();
				}
				if(self.water){
					self.water.material.uniforms['time'].value += 1.0 / 30.0;
				}
			}
		}
		function animate() {
			requestAnimationFrame(animate);
			render.call(initial);
		}
		//所有的点击方法
		function pageClick(initial){
			let go = document.getElementsByClassName("go")[0]
			go.onclick = function(){
				
			}
		}
		//初始化方法
		function init() {
			rendering.call(initial);    //渲染场景相机
			render_css(initial)			//渲染2d场景
			lighting.call(initial);		//设置灯光
			adding.call(initial);		//添加方法
			CreatPlane.call(initial);	//创建平面
			Reflector.call(initial);	//创建镜子
			controls.call(initial);		//创建控制器
			animate.call(initial);		//定时渲染
			// loaders_glb.call(initial,"gltf/model.glb");		//加载模型  call确定方法中的this指向,url为模型的地址
			// loaders_fbx.call(initial,"fbx/model.fbx");
			// axisHeiper(initial);		//创建辅助线
			pageClick(initial)     	    //所有的点击方法`

			select_texture(initial)

			initial.labelRenderer.domElement.addEventListener('mousedown', onmousedown, false)
			initial.labelRenderer.domElement.addEventListener('mousemove', onmousemove, false)
			initial.labelRenderer.domElement.addEventListener('mouseup', onmouseup, false)
			window.addEventListener("resize", onResize, false)
		}
		init()
	</script>
</body>

</html>

各位同胞如何有学Three、cesium这样的群记得拉我一下哦,嘻嘻。

我对三维挺感兴趣的,但是自己学习比较困难,而且里面的绘制扇形啥的,还需要数学,计算机图形学的底子。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值