今天来分享一篇自己的得意之作,哈哈,也不算是作品吧,就是自己当时学习Three.js框架时,自己写的一个小demo。当时学习这个老不容易了,因为算是几年前学习了,网上资料不全,官网Api也不是给我这样的凡人写的,只能靠着那些残缺的小视频,小实例来学习。现在经常不用怕越来越模糊了,今天正好借着CSDN这个平台发一下小笔记,避免一个demo丢失。
主要内容:
- 模型的加载(glb/fbx)
- 模型加载监听
- 添加镜子
- 移动动画(TWEEN)插件
- 创建2d标签
- 添加控制器
- 模型的环境周围添加贴纸
- 添加海洋效果
- 简单的交互效果
<!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这样的群记得拉我一下哦,嘻嘻。
我对三维挺感兴趣的,但是自己学习比较困难,而且里面的绘制扇形啥的,还需要数学,计算机图形学的底子。