当前位置: 首页 > article >正文

Three学习入门(四)

9-Three.js 贴图与材质学习指南

环境准备

<!DOCTYPE html>
<html>
<head>
    <title>Three.js Texture Demo</title>
    <style> body { margin: 0; } </style>
</head>
<body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r155/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.155.0/examples/js/controls/OrbitControls.js"></script>
    <script src="./main.js"></script>
</body>
</html>

基础材质类型

// 基础材质
const basicMaterial = new THREE.MeshBasicMaterial({
    color: 0x00ff00,
    wireframe: false
});

// 标准物理材质
const standardMaterial = new THREE.MeshStandardMaterial({
    color: 0xffffff,
    metalness: 0.7,
    roughness: 0.3
});

核心知识点

1. 纹理加载

const textureLoader = new THREE.TextureLoader();
const colorMap = textureLoader.load('textures/wood_diffuse.jpg');
const normalMap = textureLoader.load('textures/wood_normal.jpg');

// 带加载管理的示例
const manager = new THREE.LoadingManager();
manager.onProgress = (url, loaded, total) => {
    console.log(`加载进度: ${loaded}/${total}`);
};

const customLoader = new THREE.TextureLoader(manager);

2. 材质属性配置

const material = new THREE.MeshStandardMaterial({
    map: colorMap,        // 基础颜色贴图
    normalMap: normalMap, // 法线贴图
    displacementMap: textureLoader.load('height.png'),
    displacementScale: 0.1,
    roughnessMap: textureLoader.load('roughness.jpg'),
    metalness: 0.5,
    roughness: 0.3
});

3. 环境贴图

const cubeLoader = new THREE.CubeTextureLoader()
    .setPath('textures/cubemap/');

const envMap = cubeLoader.load([
    'px.jpg', 'nx.jpg',
    'py.jpg', 'ny.jpg',
    'pz.jpg', 'nz.jpg'
]);

material.envMap = envMap;
material.envMapIntensity = 1.5;

完整示例代码

let scene, camera, renderer;

function init() {
    // 场景设置
    scene = new THREE.Scene();
    camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    // 材质创建
    const material = createComplexMaterial();
    
    // 几何体
    const sphere = new THREE.Mesh(
        new THREE.SphereGeometry(3, 64, 64),
        material
    );
    scene.add(sphere);

    // 灯光
    const light = new THREE.PointLight(0xffffff, 1000);
    light.position.set(10, 10, 10);
    scene.add(light);

    // 相机控制
    const controls = new THREE.OrbitControls(camera, renderer.domElement);
    camera.position.z = 10;
    
    animate();
}

function createComplexMaterial() {
    const loader = new THREE.TextureLoader();
    return new THREE.MeshStandardMaterial({
        map: loader.load('diffuse.jpg'),
        normalMap: loader.load('normal.jpg'),
        roughnessMap: loader.load('roughness.jpg'),
        metalness: 0.5,
        roughness: 0.3,
        aoMap: loader.load('ao.jpg'),
        displacementMap: loader.load('height.png'),
        displacementScale: 0.1
    });
}

function animate() {
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
}

init();

关键知识要点

材质类型对比

材质类型特性性能光照支持
MeshBasicMaterial基础颜色显示
MeshPhongMaterial镜面高光
MeshStandardMaterial基于物理的渲染(PBR)
MeshPhysicalMaterial扩展PBR功能(透明涂层等)

常用贴图类型

  1. 颜色贴图 (map) - 基础表面颜色
  2. 法线贴图 (normalMap) - 表面细节凹凸
  3. 环境光遮蔽贴图 (aoMap) - 环境光遮蔽效果
  4. 粗糙度贴图 (roughnessMap) - 表面光滑程度
  5. 金属度贴图 (metalnessMap) - 金属质感表现
  6. 置换贴图 (displacementMap) - 真实几何形变

优化建议

  1. 使用压缩纹理格式(.basis, .ktx2)
  2. 合理设置纹理分辨率(避免超大尺寸)
  3. 复用纹理对象
  4. 使用mipmap提高渲染质量
  5. 对不可见物体禁用纹理更新

常见问题排查

  1. 纹理不显示:检查文件路径和格式

  2. 法线贴图效果异常:确认材质normalScale设置

  3. 性能低下:检查纹理尺寸是否过大

  4. 环境贴图失效:确认场景背景设置

  5. PBR材质暗淡:确保场景中有足够光照

    纹理贴图代码:

import * as THREE from 'three';

// 初始化场景、相机、渲染器
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 创建平面几何体
const planeGeometry = new THREE.PlaneGeometry(2, 2);

// 定义全局变量 plane
let plane;

// 加载纹理并创建材质和网格
const textureLoader = new THREE.TextureLoader();
textureLoader.load(
  '../public/assets/texture/2.jpg',
  (texture) => {
    const planeMaterial = new THREE.MeshBasicMaterial({ map: texture });
    plane = new THREE.Mesh(planeGeometry, planeMaterial); // 将 plane 赋值给全局变量
    scene.add(plane);

    // 调整相机位置
    camera.position.z = 5;

    // 启动动画循环
    animate();
  },
  (xhr) => {
    console.log('加载进度:', (xhr.loaded / xhr.total * 100) + '%');
  },
  (err) => {
    console.error('纹理加载失败:', err);
  }
);

// 定义动画函数
function animate() {
  requestAnimationFrame(animate);
  if (plane) { // 确保 plane 已定义
    plane.rotation.x += 0.01;
    plane.rotation.y += 0.01;
  }
  renderer.render(scene, camera);
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

10-纹理颜色空间、雾效及基础概念

目录

  1. Three.js 基础知识
  2. 纹理的颜色空间
  3. 雾效 (Fog)
  4. 示例代码

核心概念

  1. 场景 (Scene):所有 3D 对象的容器。
  2. 相机 (Camera):用于定义观察场景的视角。
  3. 渲染器 (Renderer):负责将场景渲染到屏幕上。
  4. 物体 (Object3D):场景中的基本元素,可以是模型、灯光等。
  5. 材质 (Material):定义物体表面的外观。
  6. 几何体 (Geometry):定义物体的形状。
  7. 灯光 (Light):为场景提供光照。
  8. 纹理 (Texture):为物体表面添加图像。

纹理的颜色空间

在 Three.js 中,纹理的颜色空间处理非常重要,因为它会影响最终的渲染效果。

sRGB 与线性颜色空间

  • sRGB:标准的颜色空间,用于显示器和图像。
  • 线性颜色空间:用于光线计算,确保物理正确的光照效果。

在 Three.js 中设置纹理颜色空间

Three.js 默认使用 sRGB 颜色空间。如果需要调整,可以通过 Texture 对象的属性进行设置。

示例:加载纹理并设置颜色空间
<!DOCTYPE html>
<html>
<head>
    <title>Three.js 纹理颜色空间示例</title>
    <style>
        body { margin: 0; }
        canvas { display: block; }
    </style>
</head>
<body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script>
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        const renderer = new THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.body.appendChild(renderer.domElement);
        
        // 加载纹理
        const textureLoader = new THREE.TextureLoader();
        const texture = textureLoader.load('path/to/texture.jpg', function(texture) {
            // 设置纹理的颜色空间
            texture.encoding = THREE.sRGBEncoding; // 或 THREE.LinearEncoding
        });
        
        const geometry = new THREE.BoxGeometry();
        const material = new THREE.MeshBasicMaterial({ map: texture });
        const cube = new THREE.Mesh(geometry, material);
        scene.add(cube);
        
        camera.position.z = 5;
        
        function animate() {
            requestAnimationFrame(animate);
            cube.rotation.x += 0.01;
            cube.rotation.y += 0.01;
            renderer.render(scene, camera);
        }
        animate();
    </script>
</body>
</html>

雾效 (Fog)

雾效可以为场景添加真实的氛围,模拟远距离物体的模糊效果。

Three.js 中的雾效类型

  1. 线性雾 (Linear Fog):雾的密度随距离线性变化。
  2. 指数雾 (Exponential Fog):雾的密度随距离指数变化。
  3. 指数平方雾 (Exponential Squared Fog):雾的密度随距离平方变化。

示例:添加雾效

<!DOCTYPE html>
<html>
<head>
    <title>Three.js 雾效示例</title>
    <style>
        body { margin: 0; }
        canvas { display: block; }
    </style>
</head>
<body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script>
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        const renderer = new THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.body.appendChild(renderer.domElement);
        
        // 添加雾效
        scene.fog = new THREE.FogExp2(0x87CEEB, 0.0025);
        
        const geometry = new THREE.BoxGeometry();
        const material = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
        const cube = new THREE.Mesh(geometry, material);
        scene.add(cube);
        
        camera.position.z = 5;
        
        function animate() {
            requestAnimationFrame(animate);
            cube.rotation.x += 0.01;
            cube.rotation.y += 0.01;
            renderer.render(scene, camera);
        }
        animate();
    </script>
</body>
</html>

示例代码

1. 创建场景并添加物体

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

camera.position.z = 5;

function animate() {
    requestAnimationFrame(animate);
    cube.rotation.x += 0.01;
    cube.rotation.y += 0.01;
    renderer.render(scene, camera);
}
animate();

2. 加载并设置纹理

const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load('path/to/texture.jpg', function(texture) {
    texture.encoding = THREE.sRGBEncoding;
});

const material = new THREE.MeshBasicMaterial({ map: texture });

3. 添加雾效

scene.fog = new THREE.FogExp2(0x87CEEB, 0.0025);

总结

通过以上内容,您可以了解 Three.js 的基础概念、纹理颜色空间的处理以及雾效的添加方法。希望这些知识点对您有所帮助!

### 图片示例

#### 纹理颜色空间对比
![sRGB vs Linear](https://example.com/srgb-vs-linear.png)

#### 雾效效果
![Fog Effect](https://example.com/fog-effect.png)

---

### 代码示例

#### 创建场景并添加物体
```javascript
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

camera.position.z = 5;

function animate() {
    requestAnimationFrame(animate);
    cube.rotation.x += 0.01;
    cube.rotation.y += 0.01;
    renderer.render(scene, camera);
}
animate();
加载并设置纹理
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load('path/to/texture.jpg', function(texture) {
    texture.encoding = THREE.sRGBEncoding;
});

const material = new THREE.MeshBasicMaterial({ map: texture });
添加雾效
scene.fog = new THREE.FogExp2(0x87CEEB, 0.0025);

总结

通过以上内容,您可以了解 Three.js 的基础概念、纹理颜色空间的处理以及雾效的添加方法。希望这些知识点对您有所帮助!

以下是关于在Three.js中使用glTF模型的学习指南,包含知识点、代码实现和资源建议:

11-Three.js glTF 模型学习指南

📚 知识点思维导图

glTF基础
glTF是什么
glTF的优势
glTF文件结构
.gltf/json
.bin/二进制
贴图资源
Three.js集成
GLTFLoader
加载流程
初始化加载器
加载模型
添加到场景
模型控制
位置/旋转/缩放
动画控制
最佳实践
模型优化
错误处理
性能优化

🛠️ 学习步骤

  1. 环境准备

    • 安装Node.js
    • 创建项目文件夹
    mkdir threejs-gltf-demo && cd threejs-gltf-demo
    npm init -y
    npm install three @types/three three-orbitcontrols
    
  2. 基础Three.js场景搭建

    <!-- index.html -->
    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="UTF-8">
      <title>glTF Demo</title>
      <style>body { margin: 0; }</style>
    </head>
    <body>
      <script src="main.js"></script>
    </body>
    </html>
    
  3. 核心代码实现(自己要在src的同级目录下新建一个models然后里面放上glb即可)

    import * as THREE from 'three';
    import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
    import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
    
    // 1. 初始化场景
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    const renderer = new THREE.WebGLRenderer({ antialias: true });
    
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setClearColor(0xeeeeee); // 设置背景颜色
    document.body.appendChild(renderer.domElement);
    
    // 2. 添加光源
    const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); // 环境光
    const directionalLight = new THREE.DirectionalLight(0xffffff, 1); // 平行光
    directionalLight.position.set(5, 5, 5);
    scene.add(ambientLight, directionalLight);
    
    // 3. 加载glTF模型
    const loader = new GLTFLoader();
    let model;
    
    // 本地模型路径
    const modelUrl = './models/DragonAttenuation.glb';
    
    loader.load(
      modelUrl,
      (gltf) => {
        model = gltf.scene;
        scene.add(model); // 将模型添加到场景中
        console.log('模型加载成功');
      },
      undefined,
      (error) => {
        console.error('加载错误:', error);
      }
    );
    
    // 4. 相机控制
    camera.position.z = 1; // 设置相机初始位置
    const controls = new OrbitControls(camera, renderer.domElement); // 添加轨道控制器
    controls.enableDamping = true; // 启用阻尼效果
    
    // 5. 动画循环
    function animate() {
      requestAnimationFrame(animate);
      controls.update(); // 更新控制器
      renderer.render(scene, camera); // 渲染场景
    }
    animate();
    
    // 6. 窗口响应
    window.addEventListener('resize', () => {
      camera.aspect = window.innerWidth / window.innerHeight; // 更新相机宽高比
      camera.updateProjectionMatrix(); // 更新相机投影矩阵
      renderer.setSize(window.innerWidth, window.innerHeight); // 更新渲染器大小
    });
    

📌 关键知识点

  1. GLTFLoader使用要点

    // 带进度回调的加载示例
    loader.load(
      'model.gltf',
      (gltf) => {
        // 成功回调
      },
      (xhr) => {
        // 进度回调
        console.log(`${(xhr.loaded / xhr.total * 100)}% loaded`);
      },
      (error) => {
        // 错误处理
      }
    );
    
  2. 模型操作示例

    // 调整模型属性
    if(model) {
      model.position.set(0, 0, 0);
      model.rotation.y += 0.01;
      model.scale.set(1, 1, 1);
    }
    
    // 遍历模型子元素
    model.traverse((child) => {
      if (child.isMesh) {
        child.castShadow = true;
        child.receiveShadow = true;
      }
    });
    
  3. 优化建议

    • 使用Draco压缩:
    import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
    
    const dracoLoader = new DRACOLoader();
    dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/');
    
    const loader = new GLTFLoader();
    loader.setDRACOLoader(dracoLoader);
    

🖼️ 示例模型资源

推荐使用官方示例模型:

  1. Damaged Helmet
  2. Flight Helmet

⚠️ 常见问题

  1. 跨域问题:建议使用本地服务器运行

    npx serve
    
  2. 模型不显示:检查控制台错误、确认模型路径、确认光源设置

  3. 性能优化:使用压缩格式、合并网格、简化材质

📁 项目结构建议

project-root/
├── index.html
├── main.js
├── models/
│   └── your_model.gltf
├── textures/
└── package.json

完整实现需要:

  1. 将glTF模型文件放入models目录
  2. 安装依赖后通过本地服务器运行
  3. 根据模型实际尺寸调整相机位置

需要测试模型可以先用Three.js示例模型:

loader.load(
  'https://threejs.org/examples/models/gltf/Duck/glTF/Duck.gltf',
  // 其他参数保持不变
);

没有模型怎么办?需要的可以私聊波珠,点赞收藏加关注!

以下是获取 glTF 模型的推荐资源和详细方法,帮助你快速找到可用的测试模型:


🌟 免费 glTF 模型资源网站

(可直接下载使用,无需注册或商业授权)

  1. 官方示例模型

    • Khronos Group 官方示例库
      • 包含经典模型(如 Damaged Helmet、Flight Helmet 等)
      • 直接下载 .gltf + .bin + 贴图文件组合
  2. Sketchfab 免费专区

    • Sketchfab Free Models
      • 搜索时勾选 “Downloadable”“glTF” 格式
      • 推荐模型:
        • Sci-Fi Helmet
        • Low Poly Wolf
  3. Three.js 官方示例模型

    • 在线路径(直接用于代码测试):

      // 示例:加载 Three.js 自带的鸭子模型
      loader.load(
        'https://threejs.org/examples/models/gltf/Duck/glTF/Duck.gltf',
        (gltf) => { /*...*/ }
      );
      
  4. 其他资源库

    • Poly Haven(CC0 授权,需转换格式)
    • Clara.io(部分免费)

🛠️ 快速获取测试模型的步骤

  1. 手动下载模型到本地

    • 从 Khronos 官方库 下载 ZIP

    • 解压后找到 glTF 文件夹(通常包含 .gltf + .bin + 贴图)

    • 在项目中创建 models 目录,将文件放入其中

    • 代码中修改路径:

      loader.load('./models/YourModel/glTF/Model.gltf', (gltf) => { /*...*/ });
      

⚠️ 注意事项

  1. 文件结构
    glTF 模型通常由多个文件组成,确保所有文件保持相对路径不变:

    models/
    └── DamagedHelmet/
        ├── glTF/                  ← 使用这个文件夹
        │   ├── DamagedHelmet.gltf
        │   ├── DamagedHelmet.bin
        │   └── textures/           ← 贴图文件夹
    
  2. 跨域问题
    如果直接打开本地 HTML 文件加载模型会报错,需启动本地服务器:

    npx serve
    

    然后访问 `http://localhost:5000


🎨 最终效果参考

在这里插入图片描述
在这里插入图片描述

加上环境贴图则是这个效果。

12-Three.js 光线投射 (Raycasting) 实现3D交互

📚 核心知识点

1. 光线投射原理

屏幕点击位置
标准化设备坐标NDC
相机生成射线
检测与场景物体交集
返回相交对象信息

2. Three.js 实现步骤

  1. 坐标转换:将鼠标位置转换为标准化设备坐标(NDC)
  2. 射线生成:通过相机和鼠标位置创建射线
  3. 碰撞检测:检测射线与场景物体的交叉点
  4. 交互处理:根据检测结果执行操作

3. 关键API

  • THREE.Raycaster
  • raycaster.setFromCamera()
  • raycaster.intersectObjects()

4. 应用场景

  • 3D物体点击选中
  • 鼠标悬停高亮
  • 第一人称射击游戏
  • VR/AR中的交互

🛠️ 完整代码实现

import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

// 初始化场景
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth/innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 添加测试物体
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ 
  color: 0x00ff00,
  metalness: 0.3,
  roughness: 0.8
});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

// 添加光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 5, 5);
scene.add(ambientLight, directionalLight);

// 相机位置
camera.position.z = 5;
const controls = new OrbitControls(camera, renderer.domElement);

// 初始化Raycaster
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();

// 鼠标移动事件监听
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('click', onClick);

function onMouseMove(event) {
  // 转换鼠标坐标到标准化设备坐标 [-1, 1]
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  
  // 更新射线
  raycaster.setFromCamera(mouse, camera);
  
  // 检测相交物体
  const intersects = raycaster.intersectObjects(scene.children);
  
  // 悬停效果
  scene.children.forEach(obj => {
    if (obj.isMesh) {
      obj.material.emissive.setHex(0x000000);
    }
  });
  
  if (intersects.length > 0) {
    intersects[0].object.material.emissive.setHex(0x444444);
  }
}

function onClick(event) {
  raycaster.setFromCamera(mouse, camera);
  const intersects = raycaster.intersectObjects(scene.children);
  
  if (intersects.length > 0) {
    // 点击效果:随机改变颜色
    intersects[0].object.material.color.setHex(Math.random() * 0xffffff);
  }
}

// 动画循环
function animate() {
  requestAnimationFrame(animate);
  controls.update();
  renderer.render(scene, camera);
}
animate();

// 窗口响应
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

在这里插入图片描述

📌 关键代码解析

1. 坐标转换公式

mouse.x = (event.clientX / windowWidth) * 2 - 1;
mouse.y = -(event.clientY / windowHeight) * 2 + 1;

2. 射线检测优化

// 只检测可交互物体
const interactiveObjects = [cube, sphere]; 
raycaster.intersectObjects(interactiveObjects);

// 性能优化:节流检测
let lastCheck = 0;
function throttleCheck() {
  const now = Date.now();
  if (now - lastCheck > 100) {
    checkIntersections();
    lastCheck = now;
  }
}

3. 高级交互示例

// 显示点击位置标记
const markerGeometry = new THREE.SphereGeometry(0.05, 16, 16);
const markerMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const marker = new THREE.Mesh(markerGeometry, markerMaterial);
scene.add(marker);

function onClick() {
  const intersects = raycaster.intersectObjects(scene.children);
  if (intersects.length > 0) {
    // 在交点处显示标记
    marker.position.copy(intersects[0].point);
    marker.visible = true;
    
    // 添加点击动画
    new TWEEN.Tween(marker.scale)
      .to({ x: 1.5, y: 1.5, z: 1.5 }, 300)
      .easing(TWEEN.Easing.Exponential.Out)
      .start();
  }
}

⚠️ 常见问题解决方案

  1. 检测不到物体

    • 确保物体matrixWorld已更新:object.updateMatrixWorld()
    • 检查物体是否在场景中
    • 确认物体visible属性为true
  2. 坐标偏移问题

    // 处理CSS偏移
    const rect = renderer.domElement.getBoundingClientRect();
    mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
    mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
    
  3. 性能优化建议

    • 使用intersectObject替代intersectObjects检测单个物体
    • 减少检测频率(使用节流)
    • 使用简化碰撞体(如BoxHelper

🖼️ 效果示意图

在这里插入图片描述

通过光线投射实现物体悬停高亮和点击变色效果

13-补间动画(Tween Animation)知识点与代码示例

1.1 什么是补间动画?

补间动画(Tween Animation)是一种在动画的起始状态和结束状态之间自动生成中间帧的动画技术。开发者只需要定义动画的起始状态和结束状态,系统会自动计算并生成中间的状态,从而实现平滑的动画效果。

1.2 补间动画的常见属性

  • 位移(Translation):改变对象的位置。
  • 缩放(Scale):改变对象的大小。
  • 旋转(Rotation):改变对象的旋转角度。
  • 透明度(Alpha):改变对象的透明度。

1.3 补间动画的优缺点

  • 优点:
    • 简单易用,只需定义起始和结束状态。
    • 适用于简单的动画效果。
  • 缺点:
    • 无法处理复杂的交互和动画逻辑。
    • 动画效果较为单一,缺乏灵活性。

1.4 补间动画的实现方式

  • XML定义:通过XML文件定义动画效果,然后在代码中加载并应用。
  • 代码实现:直接在代码中定义动画效果并应用。

2. 代码示例

2.1 XML定义补间动画

2.1.1 定义动画XML文件(res/anim/scale_anim.xml
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <scale
        android:fromXScale="1.0"
        android:toXScale="2.0"
        android:fromYScale="1.0"
        android:toYScale="2.0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:duration="1000" />
</set>
2.1.2 在代码中加载并应用动画
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ImageView imageView = findViewById(R.id.imageView);
        Animation scaleAnimation = AnimationUtils.loadAnimation(this, R.anim.scale_anim);
        imageView.startAnimation(scaleAnimation);
    }
}

2.2 代码实现补间动画

import android.view.animation.Animation;
import android.view.animation.ScaleAnimation;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ImageView imageView = findViewById(R.id.imageView);
        ScaleAnimation scaleAnimation = new ScaleAnimation(
                1.0f, 2.0f, // 起始和结束的X缩放比例
                1.0f, 2.0f, // 起始和结束的Y缩放比例
                Animation.RELATIVE_TO_SELF, 0.5f, // 缩放中心点X坐标
                Animation.RELATIVE_TO_SELF, 0.5f  // 缩放中心点Y坐标
        );
        scaleAnimation.setDuration(1000); // 动画持续时间
        imageView.startAnimation(scaleAnimation);
    }
}

好玩的代码示例:

import * as THREE from 'three';
import * as TWEEN from '@tweenjs/tween.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
import { GlitchPass } from 'three/addons/postprocessing/GlitchPass.js';

// 初始化场景
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);

// 添加银河系背景
const starGeometry = new THREE.BufferGeometry();
const starPositions = [];
for (let i = 0; i < 5000; i++) {
  starPositions.push(
    (Math.random() - 0.5) * 2000,
    (Math.random() - 0.5) * 2000,
    (Math.random() - 0.5) * 2000
  );
}
starGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starPositions, 3));
const starMaterial = new THREE.PointsMaterial({ color: 0xFFFFFF, size: 0.7 });
const stars = new THREE.Points(starGeometry, starMaterial);
scene.add(stars);

// 创建炫光立方体
const geometry = new THREE.IcosahedronGeometry(1, 3);
const material = new THREE.MeshStandardMaterial({
  color: 0x00ff00,
  metalness: 0.9,
  roughness: 0.1,
  emissive: 0x00ff00,
  emissiveIntensity: 0.5
});
const cube = new THREE.Mesh(geometry, material);
cube.castShadow = true;
cube.receiveShadow = true;
scene.add(cube);

// 添加粒子环绕系统
const particleGeometry = new THREE.BufferGeometry();
const particleCount = 2000;
const posArray = new Float32Array(particleCount * 3);
for (let i = 0; i < particleCount * 3; i++) {
  posArray[i] = (Math.random() - 0.5) * 5;
}
particleGeometry.setAttribute('position', new THREE.BufferAttribute(posArray, 3));
const particleMaterial = new THREE.PointsMaterial({
  size: 0.005,
  color: 0x00ffff,
  transparent: true,
  blending: THREE.AdditiveBlending
});
const particleMesh = new THREE.Points(particleGeometry, particleMaterial);
cube.add(particleMesh);

// 动态光源配置
const pointLight = new THREE.PointLight(0x00ff00, 1, 100);
pointLight.position.set(5, 5, 5);
pointLight.castShadow = true;
scene.add(pointLight);

// 后期处理配置
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
const bloomPass = new UnrealBloomPass(
  new THREE.Vector2(window.innerWidth, window.innerHeight),
  1.5, 0.4, 0.85
);
composer.addPass(bloomPass);
const glitchPass = new GlitchPass();
glitchPass.goWild = false;
composer.addPass(glitchPass);

// 镜头光晕特效
const textureLoader = new THREE.TextureLoader();
const flareTexture = textureLoader.load('./textures/lensflare/lensflare0.png');
const lensFlare = new THREE.Sprite(new THREE.SpriteMaterial({
  map: flareTexture,
  color: 0xffffff,
  transparent: true,
  blending: THREE.AdditiveBlending
}));
lensFlare.scale.set(2, 2, 1);
lensFlare.position.copy(pointLight.position);
scene.add(lensFlare);

// 相机控制
camera.position.z = 7;
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

// 光线投射交互
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();

window.addEventListener('mousemove', onMouseMove);
window.addEventListener('click', onClick);

function onMouseMove(event) {
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

  raycaster.setFromCamera(mouse, camera);
  const intersects = raycaster.intersectObjects([cube]);

  scene.traverse(obj => {
    if (obj.isMesh && obj !== cube) obj.material.emissive.setHex(0x000000);
  });

  if (intersects.length > 0) {
    intersects[0].object.material.emissive.setHex(0xff00ff);
    bloomPass.strength = 2.0;
  } else {
    bloomPass.strength = 1.5;
  }
}

function onClick() {
  raycaster.setFromCamera(mouse, camera);
  const intersects = raycaster.intersectObjects([cube]);

  if (intersects.length > 0) {
    // 点击爆炸粒子效果
    new TWEEN.Tween(particleMesh.material)
      .to({ size: 0.02 }, 300)
      .easing(TWEEN.Easing.Exponential.Out)
      .onComplete(() => {
        particleMesh.material.size = 0.005;
      })
      .start();

    // 颜色渐变
    new TWEEN.Tween(cube.material.color)
      .to({ r: Math.random(), g: Math.random(), b: Math.random() }, 500)
      .start();
  }
}

// 动画循环
const clock = new THREE.Clock();
function animate() {
  requestAnimationFrame(animate);

  const time = clock.getElapsedTime();

  // 立方体动态旋转
  cube.rotation.x = time * 0.5;
  cube.rotation.y = time * 0.3;

  // 粒子系统动画
  const positions = particleMesh.geometry.attributes.position.array;
  for (let i = 0; i < positions.length; i += 3) {
    positions[i] += Math.sin(time + i) * 0.001;
    positions[i + 1] += Math.cos(time + i) * 0.001;
  }
  particleMesh.geometry.attributes.position.needsUpdate = true;

  // 光源颜色变化
  pointLight.color.setHSL(Math.sin(time * 0.5) * 0.5 + 0.5, 1, 0.5);

  // 更新光晕位置
  lensFlare.position.copy(pointLight.position);

  // 更新后期处理
   TWEEN.update();
  controls.update();
  composer.render();
}
animate();

// 窗口响应
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
  composer.setSize(window.innerWidth, window.innerHeight);
});

在这里插入图片描述

3. 总结

补间动画是一种简单易用的动画技术,适用于实现基本的动画效果。通过XML定义或代码实现,开发者可以轻松地创建位移、缩放、旋转和透明度变化的动画效果。然而,对于更复杂的动画需求,建议使用属性动画(Property Animation)或其他更高级的动画技术。


http://www.kler.cn/a/614083.html

相关文章:

  • spring boot jwt生成token
  • WEB安全--SQL注入--无列名注入
  • K8S学习之基础五十三:k8s配置jenkins中的harbor
  • Redis的三种集群模式
  • 力扣HOT100之普通数组:189. 轮转数组
  • 【qt】文件类(QFile)
  • 1、SQL注入攻击的防范
  • 1921.消灭怪物的最大数量
  • -JavaEE 应用Servlet 路由技术JDBCMybatis 数据库生命周期
  • Gateway实战(二)、负载均衡
  • 【Java】JVM
  • pytest-xdist 进行高效并行自动化测试
  • LeetCode hot 100—LRU缓存
  • SQL 通用表表达式(CTE )
  • MetInfo6.0.0目录遍历漏洞原理分析
  • C++11QT复习 (三)
  • QT mingw编译器使用gdb调试
  • 2025 年前端新趋势:拥抱 Web Component 与性能优化
  • aioredis.from_url函数详解
  • 7.1 分治-快排专题:LeetCode 75. 颜色分类