内容来源:老陈threejs入门教程
陈老师讲的还是很不错的,贴图材料可以加视频里的qq群,或者用我下载的这个(微云):https://share.weiyun.com/6uA7pnYc
<template>
<div class="container" id="container">
</div>
</template>
<script setup lang="ts">
// 引入threejs
import * as THREE from 'three';
// 导入轨道控制器,模块化开发导入的是jsm不是js
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { onMounted } from 'vue';
onMounted(() => {
// 创建场景
const scene = new THREE.Scene();
// 创建透视相机,通过相机来观察
const camera = new THREE.PerspectiveCamera(
75, // 角度
window.innerWidth / window.innerHeight, // 长宽比
1, // 近端
2000 // 远端
);
// 设置相机的位置
camera.position.set(-50, 50, 130);
// 设置摄像机视锥体的长宽比
camera.aspect = window.innerWidth / window.innerHeight;
// 更新摄像头投影矩阵
camera.updateProjectionMatrix();
// 将相机添加到场景中
scene.add(camera);
// 初始化渲染器
const renderer = new THREE.WebGLRenderer({
antialias: true // 抗锯齿
});
// 设置渲染的尺寸
renderer.setSize(window.innerWidth, window.innerHeight);
// 设置渲染的输出编码
renderer.outputEncoding = THREE.sRGBEncoding;
// 将wbgl渲染的canvas内容添加到dom元素中
document.getElementById('container')?.appendChild(renderer.domElement);
// 使用渲染器,通过相机将场景渲染进来
renderer.render(scene, camera);
// 监听平面大小的改变,修改相机的比例、渲染器的宽高
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// 创建轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 设置控制器阻尼,让物体拥有惯性,必须在动画循环里调用update()
controls.enableDamping = true;
// 添加坐标辅助器
const axesHelper = new THREE.AxesHelper(10);
scene.add(axesHelper);
// 创建一个渲染函数,当场景发生变化后重新渲染
const render = () => {
controls.update();
renderer.render(scene, camera);
// 使用浏览器自带的请求动画帧函数不断的进行渲染
requestAnimationFrame(render);
};
render();
/** ******************创建物体的相关逻辑开始**************************** */
/** ********************创建物体的相关逻辑结束********************************/
});
</script>
天空是通过创建一个大型的球体,然后添加天空贴图来实现的。
// 创建纹理加载器
const textureLoader = new THREE.TextureLoader();
// 创建天空球
const skyGeometry = new THREE.SphereGeometry(1000, 60, 60);
const skyTexture = textureLoader.load('../../../../public/textures/sky.jpg'); // 天空纹理
const skyMaterial = new THREE.MeshBasicMaterial({
map: skyTexture // 颜色贴图
});
const sky = new THREE.Mesh(skyGeometry, skyMaterial);
scene.add(sky);

将球体反转
将球体进行反转,使其球里面是亮的外面是黑的
// 反转球体,主要是反转z轴
skyGeometry.scale(1, 1, -1);

注: 天空贴图有点问题,不是完全的360度,会出现重合的问题

将天空贴图替换为视频纹理
图片中云是静态的,可以通过替换为视频纹理来解决这个问题。
// 添加视频纹理
const skyVideo = document.createElement('video');
skyVideo.src = '../../../../public/textures/sky.mp4';
skyVideo.loop = true; // 设置视频循环播放
// 添加鼠标点击事件,当鼠标点击时开始播放视频
window.addEventListener('click', () => {
if (skyVideo.paused && skyVideo.readyState === 4) {
skyVideo.play();
// 更新材质
skyMaterial.map = new THREE.VideoTexture(skyVideo);
skyMaterial.map.needsUpdate = true;
}
});

创建水面
// 导入水面
import { Water } from 'three/examples/jsm/objects/Water2';
// 创建水面
const waterGeometry = new THREE.CircleGeometry(300, 30);
const water = new Water(waterGeometry, {
// 材质宽高
textureWidth: 1024,
textureHeight: 1024,
// 水面颜色
color: 0xeeeeff,
// 水面流动的方向
flowDirection: new THREE.Vector2(1, 1),
// 波纹大小
scale: 1
});
// 水面旋转至水平
water.rotation.x = -Math.PI / 2;
// 添加水面
scene.add(water);
感觉我这个水面乖乖的

添加小岛
// 导入gltf模型加载库
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
// 导入模型解压库
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
// 添加小岛
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
// 设置路径
dracoLoader.setDecoderPath('../../../../public/draco/');
// 设置加载器
loader.setDRACOLoader(dracoLoader);
loader.load('../../../../public/island2.glb', gltf => {
scene.add(gltf.scene);
});

场景是黑色的是因为我们没有设置环境纹理,没有环境反射的光,所以看不见场景。另外我们的天空贴图只是普通的图片,是无法反射光的,还需要导入hdr的环境纹理
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader';
// 导入hdr环境纹理
const hdrLoader = new RGBELoader();
hdrLoader.loadAsync('../../../../public/050.hdr')
.then((texture) => {
// 设置纹理映射
texture.mapping = THREE.EquirectangularReflectionMapping;
// 设置环境背景和环境纹理
scene.background = texture;
scene.environment = texture;
});

添加平行光
添加平行光,让模型更亮一点。如果只添加平行光不添加环境光也可以看见模型,但是平行光照射不到的地方,模型依然是黑色的,因此环境光还是很重要的。
// 添加平行光
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(100, 100, 10);
scene.add(light);
总感觉怪怪的,好像是电脑渲染的问题吧,显得有点呆😳

完整代码
<template>
<div class="container" id="container">
</div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue';
// 引入threejs
import * as THREE from 'three';
// 导入轨道控制器,模块化开发导入的是jsm不是js
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
// 导入水面
import { Water } from 'three/examples/jsm/objects/Water2';
// 导入gltf模型加载库
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
// 导入模型解压库
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader';
onMounted(() => {
// 创建场景
const scene = new THREE.Scene();
// 创建透视相机,通过相机来观察
const camera = new THREE.PerspectiveCamera(
75, // 角度
window.innerWidth / window.innerHeight, // 长宽比
1, // 近端
2000 // 远端
);
// 设置相机的位置
camera.position.set(-50, 50, 130);
// 设置摄像机视锥体的长宽比
camera.aspect = window.innerWidth / window.innerHeight;
// 更新摄像头投影矩阵
camera.updateProjectionMatrix();
// 将相机添加到场景中
scene.add(camera);
// 初始化渲染器
const renderer = new THREE.WebGLRenderer({
antialias: true, // 抗锯齿
logarithmicDepthBuffer: true // 对数深度缓冲区
});
// 设置渲染的尺寸
renderer.setSize(window.innerWidth, window.innerHeight);
// 设置渲染的输出编码
renderer.outputEncoding = THREE.sRGBEncoding;
// 将wbgl渲染的canvas内容添加到dom元素中
document.getElementById('container')?.appendChild(renderer.domElement);
// 使用渲染器,通过相机将场景渲染进来
renderer.render(scene, camera);
// 监听平面大小的改变,修改相机的比例、渲染器的宽高
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// 创建轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 设置控制器阻尼,让物体拥有惯性,必须在动画循环里调用update()
controls.enableDamping = true;
// 添加坐标辅助器
// const axesHelper = new THREE.AxesHelper(100);
// scene.add(axesHelper);
// 创建一个渲染函数,当场景发生变化后重新渲染
const render = () => {
controls.update();
renderer.render(scene, camera);
// 使用浏览器自带的请求动画帧函数不断的进行渲染
requestAnimationFrame(render);
};
render();
/** ******************创建物体的相关逻辑开始**************************** */
// 创建纹理加载器
const textureLoader = new THREE.TextureLoader();
// 创建天空球
const skyGeometry = new THREE.SphereGeometry(1000, 60, 60);
const skyTexture = textureLoader.load('../../../../public/textures/sky.jpg'); // 天空纹理
const skyMaterial = new THREE.MeshBasicMaterial({
map: skyTexture // 颜色贴图
});
// 反转球体,主要是反转z轴
skyGeometry.scale(1, 1, -1);
const sky = new THREE.Mesh(skyGeometry, skyMaterial);
scene.add(sky);
// 添加视频纹理
const skyVideo = document.createElement('video');
skyVideo.src = '../../../../public/textures/sky.mp4';
skyVideo.loop = true; // 设置视频循环播放
// 添加鼠标点击事件,当鼠标点击时开始播放视频
window.addEventListener('click', () => {
if (skyVideo.paused && skyVideo.readyState === 4) {
skyVideo.play();
// 更新材质
const texture = new THREE.VideoTexture(skyVideo);
skyMaterial.map = texture;
skyMaterial.map.needsUpdate = true;
}
});
// 导入hdr环境纹理
const hdrLoader = new RGBELoader();
hdrLoader.loadAsync('../../../../public/050.hdr')
.then((texture) => {
// 设置纹理映射
texture.mapping = THREE.EquirectangularReflectionMapping;
// 设置环境背景和环境纹理
scene.background = texture;
scene.environment = texture;
});
// 添加平行光
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(100, 100, 10);
scene.add(light);
// 创建水面
const waterGeometry = new THREE.CircleGeometry(300, 30);
const water = new Water(waterGeometry, {
// 材质宽高
textureWidth: 1024,
textureHeight: 1024,
// 水面颜色
color: 0xeeeeff,
// 水面流动的方向
flowDirection: new THREE.Vector2(1, 1),
// 波纹大小
scale: 1
});
// 水面旋转至水平
water.rotation.x = -Math.PI / 2;
// 调整水面的位置,使水面能够掩盖一部分模型
water.position.y = 3;
// 添加水面
scene.add(water);
// 添加小岛
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
// 设置路径
dracoLoader.setDecoderPath('../../../../public/draco/');
// 设置加载器
loader.setDRACOLoader(dracoLoader);
loader.load('../../../../public/island2.glb', gltf => {
scene.add(gltf.scene);
});
/** ********************创建物体的相关逻辑结束********************************/
});
</script>