Three.js 实战【3】—— 城市模型渲染

Three.js 实战【3】------ 城市模型渲染

初始化场景

初始化场景这一块就跳过了,可以参考上一篇文章:Three.js 实战【2】------ 船模型海上场景渲染,效果是一样的。

加载模型

这里我们找一个城市的模型下来,可以上这个网站下载个模型,这里以上海市模型下载地址为例。

先加载我们下载的模型到场景当中。

js 复制代码
const addCity = () => {
  const gltfLoader = new GLTFLoader();
  gltfLoader.load('./src/assets/glb/shanghai_city.glb', (gltf: any) => {
    model = gltf.scene.children[0];
    scene.add(model);
  });
};

在加载了第三方的模型发现模型并没有和坐标系的(0,0,0)保持重合,反而有偏移,这个时候我们可以利用Box3去拿到模型的包围盒,获取到模型的中心点坐标信息,再取反,就会得到模型中心点在3d世界的位置信息。(在上面scene.add(model))前调用这个toCenter方法进行居中。

  • Box3:表示三维空间中的一个轴对齐包围盒
  • expandByObject(model):扩展此包围盒的边界,使得对象及其子对象在包围盒内,包括对象和子对象的世界坐标的变换。
  • Vector3:三维向量表示的是一个有顺序的、三个为一组的数字组合(x,y,z)
  • negate:向量取反,即: x = -x, y = -y , z = -z。
js 复制代码
const toCenter = () => {
  const box = new THREE.Box3();
  box.expandByObject(model);
  console.log('box =====', box);
  const size = new THREE.Vector3();
  const center = new THREE.Vector3();
  console.log('size,center =====', size, center);
  // 获取包围盒的中心点和尺寸
  box.getCenter(center);
  box.getSize(size);
  // 将Y轴置为0
  model.position.copy(center.negate().setY(0));
};

添加材质

这里先抽离个创建材质的方法出来,这里就简单创建一个颜色的材质。

js 复制代码
const colorMaterial = (color: string) => {
  return new THREE.MeshPhongMaterial({
    color
  });
};

在前面加载的model模型部分,可以通过traverse方法去拿到模型里面所有的对象,可以这么理解。然后这里面的item.name和导入的模型是有关系的,可以打印出来看一下模型里面的name都是一些什么,再去进行匹配添加材质。

js 复制代码
const addCity = () => {
  const gltfLoader = new GLTFLoader();
  gltfLoader.load('./src/assets/glb/shanghai_city.glb', (gltf: any) => {
    model = gltf.scene.children[0];
    toCenter();
    model.traverse((item: { name: string, material: any }) => {
      console.log(' =====', item.name);
      if (item.name.includes('dongfangmingzhu')) {
        item.material = colorMaterial('#f40');
      } else if (item.name.includes('shanghaizhongxin')) {
        item.material = colorMaterial('#aa21ee');
      } else if (item.name.includes('huanqiujinrongzhongxin')) {
        item.material = colorMaterial('#008000');
      } else if (item.name.includes('jinmaodasha')) {
        item.material = colorMaterial('#0000ff');
      } else if (item.name.includes('Floor')) {
        item.material = colorMaterial('#ffffff');
      } else if (item.name.includes('city')) {
        item.material = colorMaterial('#867e76');
      }
    });
    scene.add(model);
  });
};

效果:

纹理

创建一个纹理:

  • wrapS:这个值定义了纹理贴图在水平方向上将如何包裹
  • wrapT:这个值定义了纹理贴图在垂直方向上将如何包裹
  • .repeat:决定纹理在表面的重复次数,两个方向分别表示U和V
js 复制代码
const ImageLoader = new THREE.ImageLoader();
let texture: { needsUpdate: boolean; };
ImageLoader.load(TextureImage, function (img: any) {
  texture = new THREE.Texture(img, {
    wraps: THREE.RepeatWrapping,
    repeat: new THREE.Vector2(10, 10),
    wrapT: THREE.RepeatWrapping
  });
  texture.needsUpdate = true;
});

将纹理添加到材质上,那么这个时候改一下前面那个创建材质的方法

  • map:纹理贴图
  • alphaMap:一张灰度纹理,用于控制整个表面的不透明度
  • aoMapIntensity:环境遮挡效果的强度。默认值为1。零是不遮挡效果
  • bumpMap:用于创建凹凸贴图的纹理
  • bumpScale:凹凸贴图会对材质产生多大影响。典型范围是0-1。默认值为1
js 复制代码
const colorMaterial = (color: string, texture: any) => {
  let options = {};
  if (texture) {
    options = {
      map: texture,
      alphaMap: texture,
      aoMapIntensity: 0,
      bumpMap: texture,
      bumpScale: 0
    };
  } else {
    options = {
      color,
      shininess: 100,
      flatShading: true
    };
  }
  return new THREE.MeshPhongMaterial(options);
};

效果示意:

视角限制

这个在上一个船的就解释过了,直接复制过来使用就好了,去掉了最大最小距离的配置(minDistance、maxDistance)

js 复制代码
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.maxPolarAngle = Math.PI * 0.45;
// controls.minDistance = 5.0;
// controls.maxDistance = 15.0;
controls.update();

完整代码

html 复制代码
<!-- 组件 -->
<script lang="ts" setup>
import * as THREE from 'three';
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls';
import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader';
import TextureImage from '@/assets/image/4.jpg';

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.001, 2000);
camera.position.set(2, 2, 2);
scene.add(camera);

// 环境光
const ambientLight = new THREE.AmbientLight('white', 0.5);
ambientLight.position.set(1, 1, 1);
scene.add(ambientLight);

const light = new THREE.DirectionalLight('#fff', 1);
light.position.set(1, 10, 1);
camera.add(light);

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);

const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

const axesHelper = new THREE.AxesHelper(1);
scene.add(axesHelper);

// 加载模型
let model: {
  scale: { set: (arg0: number, arg1: number, arg2: number) => void; };
  rotation: { z: number; };
  position: { z: number; };
  material: any;
};

const addCity = () => {
  const gltfLoader = new GLTFLoader();
  gltfLoader.load('./src/assets/glb/shanghai_city.glb', (gltf: any) => {
    model = gltf.scene.children[0];
    toCenter();
    model.traverse((item: { name: string, material: any }) => {
      console.log(' =====', item.name);
      if (item.name.includes('dongfangmingzhu')) {
        item.material = colorMaterial('#f40', null);
      } else if (item.name.includes('shanghaizhongxin')) {
        item.material = colorMaterial('#d90000', null);
      } else if (item.name.includes('huanqiujinrongzhongxin')) {
        item.material = colorMaterial('#008000', null);
      } else if (item.name.includes('jinmaodasha')) {
        item.material = colorMaterial('#0000ff', null);
      } else if (item.name.includes('Floor')) {
        item.material = colorMaterial('#ffffff', texture);
      } else if (item.name.includes('city')) {
        item.material = colorMaterial('#ffffff', texture);
      }
    });
    scene.add(model);
  });
};

const toCenter = () => {
  const box = new THREE.Box3();
  box.expandByObject(model);
  console.log('box =====', box);
  const size = new THREE.Vector3();
  const center = new THREE.Vector3();
  console.log('size,center =====', size, center);
  // 获取包围盒的中心点和尺寸
  box.getCenter(center);
  box.getSize(size);
  // 将Y轴置为0
  model.position.copy(center.negate().setY(0));
};


// 图片加载器
const ImageLoader = new THREE.ImageLoader();
let texture: { needsUpdate: boolean; };
ImageLoader.load(TextureImage, function (img: any) {
  texture = new THREE.Texture(img, {
    wraps: THREE.RepeatWrapping,
    repeat: new THREE.Vector2(10, 10),
    wrapT: THREE.RepeatWrapping
  });
  texture.needsUpdate = true;
});

const colorMaterial = (color: string, texture: any) => {
  let options = {};
  if (texture) {
    options = {
      map: texture,
      alphaMap: texture,
      aoMapIntensity: 0,
      bumpMap: texture,
      bumpScale: 0
    };
  } else {
    options = {
      color,
      shininess: 100,
      flatShading: true
    };
  }
  return new THREE.MeshPhongMaterial(options);
};


// 渲染
const render = () => {
  renderer.render(scene, camera);
  requestAnimationFrame(render);
};

onMounted(() => {
  document.getElementById('home')?.appendChild(renderer.domElement);
  addCity();

  render();
});
</script>

<template>
  <div id="home" class="w-full h-full"></div>
</template>
相关推荐
子燕若水14 小时前
“Daz to Unreal”将 G8 角色(包括表情)从 daz3d 导入到 UE5。在 UE5 中,我发现使用某个表情并与闭眼混合后,上眼睑出现了问题
3d·ue5
zhu_zhu_xia21 小时前
JS通过GetCapabilities获取wms服务元数据信息并在SuperMap iClient3D for WebGL进行叠加显示
javascript·3d·webgl
星空寻流年1 天前
css3新特性第七章(3D变换)
前端·css·3d
Dnn011 天前
修改el-select背景颜色
css·elementui·vue3
在下胡三汉1 天前
Google Store 如何利用 glTF 3D 模型改变产品教育
3d
Hali_Botebie1 天前
【激光雷达3D(6)】3D点云目标检测方法;CenterPoint、PV-RCNN和M3DETR的骨干网络选择存在差异
网络·目标检测·3d
whuzhang162 天前
3DGS之齐次坐标
人工智能·3d·自动驾驶
90后小陈老师2 天前
WebXR教学 05 项目3 太空飞船小游戏
windows·3d·web·js
艾恩小灰灰2 天前
CSS中的`transform-style`属性:3D变换的秘密武器
前端·css·3d·css3·html5·web开发·transform-style
工业3D_大熊3 天前
HOOPS Exchange 与HOOPS Communicator集成:打造工业3D可视化新标杆!
3d·hoops·3d数据格式转换·3d模型可视化·bim技术数字解决方案·3d模型桌面可视化