react使用 three加载gltf模型,设置阴影,展示信息框。

1 加载模型

1.1 引入threejs

下载

npm install three

使用

import * as THREE from "three";

1.2 添加场景

const width = containerRef.current.offsetWidth;
const height = containerRef.current.offsetHeight;
//场景
const scene = new THREE.Scene();

1.3 添加相机

//相机
const camera = new THREE.PerspectiveCamera(
  75, // 视野角度
  width / height, //宽高比
  0.1, // 近裁剪面
  1000 // 远裁剪面
);
camera.position.set(-50, 70, 100);//相机位置
camera.lookAt(100, 0, 0);// 相机看向哪

1.4 渲染器

// 生成渲染器return (
    <div
      ref={containerRef}
      id="container"
      style={{ height: "100vh", width: "100vw" }}
    />
  );
const renderer = new THREE.WebGLRenderer({
   antialias: true, // 抗锯齿
   alpha: true, // 启用透明度
   physicallyCorrectLights: true,
 });
 renderer.setSize(width, height);

1.5 加入dom

react 文件

return (
  <div
    ref={containerRef}
    id="container"
    style={{ height: "100vh", width: "100vw" }}
  />
);
//加入dom     
containerRef.current.appendChild(renderer.domElement);

1.6 引入模型

引入加载器和控制器

import { GLTFLoader } from "three/examples/jsm/Addons.js";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";

//控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.update();

3d模型网站 sketchfab
我用的模型 https://skfb.ly/p7Wwv

//加载模型
 const loader = new GLTFLoader();
 loader.load(
   "public/distant_city/scene.gltf",
   (gltf) => {
     const model = gltf.scene;
     model.castShadow = true; //投射阴影
     model.receiveShadow = true; //接受阴影
     model.scale.set(0.5, 0.5, 0.5);
     scene.add(model);
     //动画
     const animate = () => {
       renderer.render(scene, camera);
       controls.update();
       requestAnimationFrame(animate);
     };
     animate();
   },
   function (xhr) {
     console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
   },
   function (error) {
     console.log(error, "An error happened");
   }
 );

2 添加全景背景

2.1 引入加载器

import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";

2.2 添加全景

在网站上找个全景模型下载到public文件夹里边
Poly Haven网站

//引入背景
const rgbeLoader = new RGBELoader();
rgbeLoader.load("public/london_city_sunny_street_1k.hdr", (texture) => {
  texture.mapping = THREE.EquirectangularReflectionMapping;
  // 创建背景
  scene.background = texture;
  // 创建用于环境映射的球体,就是当无限缩小模型之后,最后会出现一个球体
  const sphereGeometry = new THREE.SphereGeometry(500, 60, 40);
  const sphereMaterial = new THREE.MeshBasicMaterial({ map: texture });
  const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
  scene.add(sphere);
});

在这里插入图片描述

3 添加阴影

两个关键点,1 模型需要可以投射阴影和接受阴影,我的模型是一块建筑物,影子可能会投到相邻的建筑物上。2 需要有个平面能接受阴影。

3.1 添加灯光

灯光位置要远,范围也要广,才能照射到所有模型。

//平行光
// 创建光源并设置能投射阴影
const directionalLight = new THREE.DirectionalLight(0xffffff, 5);
directionalLight.position.set(-500, 500, -500);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 4000; // 清晰度,数值越大,影子越光滑
directionalLight.shadow.mapSize.height = 4000;
directionalLight.shadow.camera.near = 0.5; //离灯光多近产生阴影
directionalLight.shadow.camera.far = 5000; // 离灯光多远产生阴影
directionalLight.shadow.camera.left = -500; // 决定了阴影的产生范围
directionalLight.shadow.camera.right = 500;
directionalLight.shadow.camera.top = 500;
directionalLight.shadow.camera.bottom = -500;
scene.add(directionalLight);

3.2 添加阴影接受平面

plane.receiveShadow = true;

// 创建平面几何体
 const planeGeometry = new THREE.PlaneGeometry(400, 300);
 // 创建平面材质,设置接收阴影
 const planeMaterial = new THREE.MeshStandardMaterial({
   color: 0xd3d3d3,
   opacity: 0.4, // 透明度,取值范围 0(完全透明)到 1(完全不透明)
   transparent: true, // 启用透明度
 });
 // 创建平面网格
 const plane = new THREE.Mesh(planeGeometry, planeMaterial);
 plane.receiveShadow = true;
 // 旋转平面使其水平
 plane.rotation.x = -Math.PI / 2;
 // 调整平面位置使其在模型下方
 plane.position.y = 0;
 plane.position.z = -20;
 plane.position.x = 150;
 // 将平面添加到场景
 scene.add(plane);

3.3 渲染器设置阴影

渲染器

// 阴影设置
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 阴影类型,可根据需要选择

3.4 模型添加阴影

 (gltf) => {
 const model = gltf.scene;
 model.castShadow = true; //投射阴影
 model.receiveShadow = true; //接受阴影
 model.scale.set(0.5, 0.5, 0.5);
 scene.add(model);
//遍历一下子模型
 model.traverse(function (child) {
   if (child.isMesh) {
     child.castShadow = true; //模型投射阴影
     child.receiveShadow = true; // 接受阴影
     child.material.emissive = child.material.color;
     child.material.emissiveMap = child.material.map;
   }
 });
 }

在这里插入图片描述

4 添加点击事件

//鼠标点击
  const onMouseDown = (event) => {
  const mouse = new THREE.Vector2();
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  console.log("x,y", mouse.x, mouse.y);

  const raycaster = new THREE.Raycaster();
  raycaster.setFromCamera(mouse, camera);

  const intersects = raycaster.intersectObjects(scene.children);

  if (intersects.length > 0) {
    const object = intersects[0].object;
    console.log(object.name, "112");
    showInfo(object.name);
  }
};
document.addEventListener("mousedown", onMouseDown, false);
//展示信息
const showInfo = (message) => {
  const infoBox = document.createElement("div");
  infoBox.innerHTML = message;
  infoBox.style.position = "absolute";
  infoBox.style.top = "10%";
  infoBox.style.left = "50%";
  infoBox.style.transform = "translate(-50%, -50%)";
  infoBox.style.backgroundColor = "white";
  infoBox.style.padding = "10px";
  infoBox.style.border = "1px solid black";

  document.body.appendChild(infoBox);

  // 一段时间后隐藏信息框
  setTimeout(() => {
    document.body.removeChild(infoBox);
  }, 1000);
};

效果:
在这里插入图片描述

5 全部代码


import * as dat from "dat.gui";
import { useEffect, useRef } from "react";
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import { GLTFLoader } from "three/examples/jsm/Addons.js";
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";
function ThreeContainer() {
  const isContainerRunning = useRef(false);
  const containerRef = useRef(null);

  useEffect(() => {
    if (isContainerRunning.current == false && containerRef.current) {
      isContainerRunning.current = true;
      const width = containerRef.current.offsetWidth;
      const height = containerRef.current.offsetHeight;
      //场景
      const scene = new THREE.Scene();
      //相机
      const camera = new THREE.PerspectiveCamera(
        75, // 视野角度
        width / height, //宽高比
        0.1, // 近裁剪面
        1000 // 远裁剪面
      );
      camera.position.set(-50, 70, 100);
      camera.lookAt(100, 0, 0);
      // 生成渲染器
      const renderer = new THREE.WebGLRenderer({
        antialias: true, // 抗锯齿
        alpha: true, // 启用透明度
        physicallyCorrectLights: true,
      });
      renderer.setSize(width, height);
      // 阴影设置
      renderer.shadowMap.enabled = true;
      renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 阴影类型,可根据需要选择

      //控制器
      const controls = new OrbitControls(camera, renderer.domElement);
      controls.update();
      //加入dom
      containerRef.current.appendChild(renderer.domElement);
      //引入背景
      const rgbeLoader = new RGBELoader();
      rgbeLoader.load("public/london_city_sunny_street_1k.hdr", (texture) => {
        texture.mapping = THREE.EquirectangularReflectionMapping;
        // 创建背景
        scene.background = texture;
        // 创建用于环境映射的球体
        const sphereGeometry = new THREE.SphereGeometry(500, 60, 40);
        const sphereMaterial = new THREE.MeshBasicMaterial({ map: texture });
        const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
        scene.add(sphere);
      });
      //加载模型
      const loader = new GLTFLoader();
      loader.load(
        "public/distant_city/scene.gltf",
        (gltf) => {
          const model = gltf.scene;
          model.castShadow = true; //投射阴影
          model.receiveShadow = true; //接受阴影
          model.scale.set(0.5, 0.5, 0.5);
          scene.add(model);

          model.traverse(function (child) {
            if (child.isMesh) {
              child.castShadow = true; //模型投射阴影
              child.receiveShadow = true;
              child.material.emissive = child.material.color;
              child.material.emissiveMap = child.material.map;
            }
          });
          //动画
          const animate = () => {
            renderer.render(scene, camera);
            controls.update();
            requestAnimationFrame(animate);
          };
          animate();
        },
        function (xhr) {
          console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
        },
        function (error) {
          console.log(error, "An error happened");
        }
      );
      //平行光
      // 创建光源并设置能投射阴影
      const directionalLight = new THREE.DirectionalLight(0xffffff, 5);
      directionalLight.position.set(-500, 500, -500);
      directionalLight.castShadow = true;
      directionalLight.shadow.mapSize.width = 4000; // 清晰度,数值越大,影子越光滑
      directionalLight.shadow.mapSize.height = 4000;
      directionalLight.shadow.camera.near = 0.5; //离灯光多近产生阴影
      directionalLight.shadow.camera.far = 5000; // 离灯光多远产生阴影
      directionalLight.shadow.camera.left = -500; // 决定了阴影的产生范围
      directionalLight.shadow.camera.right = 500;
      directionalLight.shadow.camera.top = 500;
      directionalLight.shadow.camera.bottom = -500;
      scene.add(directionalLight);
      // 创建平面几何体
      const planeGeometry = new THREE.PlaneGeometry(400, 300);
      // 创建平面材质,设置接收阴影
      const planeMaterial = new THREE.MeshStandardMaterial({
        color: 0xd3d3d3,
        opacity: 0.4, // 透明度,取值范围 0(完全透明)到 1(完全不透明)
        transparent: true, // 启用透明度
      });
      // 创建平面网格
      const plane = new THREE.Mesh(planeGeometry, planeMaterial);
      plane.receiveShadow = true;
      // 旋转平面使其水平
      plane.rotation.x = -Math.PI / 2;
      // 调整平面位置使其在模型下方
      plane.position.y = 0;
      plane.position.z = -20;
      plane.position.x = 150;
      // 将平面添加到场景
      scene.add(plane);
      //鼠标点击
      const onMouseDown = (event) => {
        const mouse = new THREE.Vector2();
        mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
        mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
        console.log("x,y", mouse.x, mouse.y);

        const raycaster = new THREE.Raycaster();
        raycaster.setFromCamera(mouse, camera);
        //console.log("raycaster", raycaster);

        const intersects = raycaster.intersectObjects(scene.children);
        //console.log("intersects", intersects);

        if (intersects.length > 0) {
          const object = intersects[0].object;
          console.log(object.name, "112");
          showInfo(object.name);
        }
      };
      document.addEventListener("mousedown", onMouseDown, false);
      //展示信息
      const showInfo = (message) => {
        const infoBox = document.createElement("div");
        infoBox.innerHTML = message;
        infoBox.style.position = "absolute";
        infoBox.style.top = "10%";
        infoBox.style.left = "50%";
        infoBox.style.transform = "translate(-50%, -50%)";
        infoBox.style.backgroundColor = "white";
        infoBox.style.padding = "10px";
        infoBox.style.border = "1px solid black";

        document.body.appendChild(infoBox);

        // 一段时间后隐藏信息框
        setTimeout(() => {
          document.body.removeChild(infoBox);
        }, 1000);
      };
      /* 
      // 创建一个 dat.GUI 对象
      const gui = new dat.GUI();
      // 模型位置
      const ModelPosition = gui.addFolder("模型位置");
      ModelPosition.add(scene.position, "x", -3, 3).name("x轴");
      ModelPosition.add(scene.position, "y", -3, 3).name("y轴");
      ModelPosition.add(scene.position, "z", -3, 3).name("z轴");
      //缩放
      const ModelScale = gui.addFolder("模型缩放");
      ModelScale.add(scene.scale, "x", 1, 2).name("缩放");
      //旋转
      const ModelRotation = gui.addFolder("旋转");
      ModelRotation.add(scene.rotation, "y", -2 * Math.PI, 2 * Math.PI).name(
        "y轴旋转"
      ); 
      */
      // 为点光源的亮度创建控制器
      // const lightFolder = gui.addFolder("灯光");
      //lightFolder.add(pointLight, "intensity", 0, 500).name("点光源"); // 亮度范围从 0 到 5 可调节
      //lightFolder.add(directionalLight, "intensity", 0, 10).name("平行光");
    }
  }, []);

  return (
    <div
      ref={containerRef}
      id="container"
      style={{ height: "100vh", width: "100vw" }}
    />
  );
}

export default ThreeContainer;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值