异次元

 找回密码
 立即注册
KURUN CLOUD 美国CN2GIA服务器轻云互联 美国香港CN2GIA服务器会员请立即修改密码
查看: 97|回复: 0

how to create basic 360 landing using three.js, nuxt.js and vue3

[复制链接]

4

主题

7

帖子

74

积分

注册会员

Rank: 2

积分
74
发表于 2024-9-1 20:54:02 | 显示全部楼层 |阅读模式

大家好,我喜歡分享如何使用 Three.js、Nuxt.js 和 Vue 3 創建基本的 360 度著陸頁。


安裝新的 Nuxt.js 項目
[Bash shell] 纯文本查看 复制代码
npx nuxi@latest init simple-app
cd simple-app
npm i



安裝 Three.js
[Bash shell] 纯文本查看 复制代码
npm i three -D



現在我們準備好了,讓我們為 360 度著陸頁建立一個新元件。
[Bash shell] 纯文本查看 复制代码
npx nuxi add component Landing



現在我們將像這樣建立元件內容。
[TypeScript] 纯文本查看 复制代码
<script setup lang="ts">
import {
PerspectiveCamera,
Scene,
SphereGeometry,
TextureLoader,
MeshBasicMaterial,
Mesh,
WebGLRenderer,
MathUtils,
} from "three";

const container = ref()

const props = defineProps({
    panoramaUrl:{
        type: String,
        default: "img/hdri.webp"
    },
});

let camera, scene, renderer;
let isUserInteracting = false,
    onPointerDownMouseX = 0, onPointerDownMouseY = 0,
    lon = 0, onPointerDownLon = 0,
    lat = 0, onPointerDownLat = 0,
    phi = 0, theta = 0;

camera = new PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1100);
scene = new Scene();
const geometry = new SphereGeometry(1000, 100, 100);
const reader = new FileReader();

const onWindowResize= () => {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
}

const onPointerDown = (event) => {
    if (event.isPrimary === false) return;
    isUserInteracting = true;
    onPointerDownMouseX = event.clientX;
    onPointerDownMouseY = event.clientY;
    onPointerDownLon = lon;
    onPointerDownLat = lat;
    document.addEventListener('pointermove', onPointerMove);
    document.addEventListener('pointerup', onPointerUp);
}

const onPointerMove = (event) => {
    if (event.isPrimary === false) return;
    lon = (onPointerDownMouseX - event.clientX) * 0.1 + onPointerDownLon;
    lat = (event.clientY - onPointerDownMouseY) * 0.1 + onPointerDownLat;
}

const onPointerUp = () => {
    if (event.isPrimary === false) return;
    isUserInteracting = false;
    document.removeEventListener('pointermove', onPointerMove);
    document.removeEventListener('pointerup', onPointerUp);
}

const onDocumentMouseWheel = (event) => {
    const fov = camera.fov + event.deltaY * 0.05;
    camera.fov = MathUtils.clamp(fov, 10, 75);
    camera.updateProjectionMatrix();
}

const animate = () => {
    requestAnimationFrame(animate);
    update();
}

const update = () => {
    if (isUserInteracting === false) {
        lon += 0.1;
    }

    lat = Math.max(- 85, Math.min(85, lat));
    phi = MathUtils.degToRad(90 - lat);
    theta = MathUtils.degToRad(lon);
    const x = 500 * Math.sin(phi) * Math.cos(theta);
    const y = 500 * Math.cos(phi);
    const z = 500 * Math.sin(phi) * Math.sin(theta);
    camera.lookAt(x, y, z);
    renderer.render(scene, camera);
}

const dragover = (event) => {
    event.preventDefault();
    event.dataTransfer.dropEffect = 'copy';
}

const dragenter = () => {
    document.body.style.opacity = 0.5;
}

const dragleave = () => {
    document.body.style.opacity = 1;
}

const load1 = (event) => {
    material.map.image.src = event.target.result;
    material.map.needsUpdate = true;
}

const drop = () => {
    event.preventDefault();
    reader.addEventListener('load', load1);
    reader.readAsDataURL(event.dataTransfer.files[0]);
    document.body.style.opacity = 1;
}

onMounted(() => {
        init();
        animate();

        function init() {
            // invert the geometry on the x-axis so that all of the faces point inward
            geometry.scale(- 1, 1, 1);
            const texture = new TextureLoader().load(props.panoramaUrl);
            const material = new MeshBasicMaterial({ map: texture });
            const mesh = new Mesh(geometry, material);
            scene.add(mesh);
            renderer = new WebGLRenderer();
            renderer.setPixelRatio(window.devicePixelRatio);
            renderer.setSize(window.innerWidth, window.innerHeight);
            container.value.appendChild(renderer.domElement);
            container.value.style.touchAction = 'none';
            container.value.addEventListener('pointerdown', onPointerDown);
            document.addEventListener('wheel', onDocumentMouseWheel);
            document.addEventListener('dragover', dragover);
            document.addEventListener('dragenter', dragenter);
            document.addEventListener('dragleave', dragleave);
            document.addEventListener('drop', drop);
            window.addEventListener('resize', onWindowResize);
        }
});

onUnmounted(() => {
    reader.removeEventListener('load', load1);
    container.value.replaceWith(container.value.cloneNode(true));
    document.removeEventListener('wheel', onDocumentMouseWheel);
    document.removeEventListener('dragover', dragover);
    document.removeEventListener('dragenter', dragenter);
    document.removeEventListener('dragleave', dragleave);
    document.removeEventListener('drop', drop);
    window.removeEventListener('resize', onWindowResize);
});
</script>

<template>
    <div class="max-w-full h-screen w-full overflow-hidden">
        <div ref="container"></div>
    </div>
</template>


注意: 您可以在下面的連結中的 Three.js 官方文件中找到有關程式碼各部分的資訊。
Three.js 文檔


現在,我們需要在 public 資料夾中建立名為 img 的資料夾,然後建立使用 HDRI 模式的影像。您可以創建自己的圖像,也可以從本部分的任何資源中找到一個圖像。我基本上使用一個名為 HDRi Haven 的網站,並將圖像轉換為 WebP 格式來縮小圖像的總大小。在我的情況下,我不在乎陰影、反射和其他複雜的效果。
現在,我們的公用資料夾中有圖片 img/hdri.webp,我們準備在 index.vue 頁面中使用我們的元件。
注意: 如果我們在應用程式中使用 SSR,我們可能會遇到一些渲染問題。因此,當我們像下面這樣呼叫元件時,我們將使用 <ClientOnly>。


[HTML] 纯文本查看 复制代码
<ClientOnly>
    <Landing/>
    <template #fallback>
      <!-- this will be rendered on server side -->
      <p>Landing is loading...</p>
    </template>
  </ClientOnly>


就是這樣!我們現在有了一個基本的 360 度著陸頁。


discourse.threejs.org

sumer5020
高级模式
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|异次元 | 繁体中文 本站支持IPv6访问

GMT+9, 2024-11-25 10:28 , Processed in 0.165800 second(s), 25 queries .

Powered by Discuz! X3.4

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表