G
N
I
D
A
O
L

3D网站LOGO动画


相关技术和实现分析

  • 3D模型
  • 帧动画
  • threejs
  1. 推荐用blender创建3d模型,k帧实现从上到下翻转的帧动画
  2. threejs 中执行帧动画,并关联滚动条
  3. threejs 模型材质

Blender 建模

logo模型用的是glb格式,也可以使用已有模型
导出logo模型可以压缩一下,占用资源更小

Blender 动画k帧

用一个空立方体关联logo主体,k帧主要就是立方体的翻转动画,logo是静止的,自转的动画这块可以代码中实现
整个动画总共做了14个关键帧




导出带动画模型(导出时勾选动画信息)

编码

创建场景

<div id="container">
 <div id="bg"></div>
</div>
import * as THREE from 'three';

// ...


// 基础数据
const container = document.getElementById('container');
const winHeight = 12000; // 页面高度,可按实际获取
container.style.height = winHeight + 'px';

// antialias 抗锯齿属性; alpha 透明背景
const renderer = window.WebGLRenderingContext
  ? new THREE.WebGLRenderer({ antialias: true, alpha: true })
  : new THREE.CanvasRenderer();

//设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// 添加webgl渲染的canves内容
document.getElementById('bg').appendChild(renderer.domElement);

// 创建场景
const scene = new THREE.Scene();

scene.background = null;
// 创建相机
const camera = new THREE.PerspectiveCamera(
  50,
  window.innerWidth / window.innerHeight,
  0.0001, // 近端视角尽量小 可避免相机穿模
  1000
); //透视相机(角度,宽高比,近端,远端)

// 相机位置
const baseCameraPos = { x: 0, y: 0, z: 1.25 };

camera.position.set(baseCameraPos.x, baseCameraPos.y, baseCameraPos.z);

function render() {
  requestAnimationFrame(render);
  
	// 使用渲染器,通过相机,将场景渲染进来
  renderer.render(scene, camera);
}

render()

材质准备

材质数据

貌似不同版Threejs 材质参数设置表现不太一样,可以具体情况调整


// LOGO材质
const glassMaterial = new THREE.MeshPhysicalMaterial({
  color: 0x3069c7, // 颜色
  metalness: 0, // 金属贴图
  roughness: 0.4, // 表层粗糙程度 磨砂质感
  transparent: false, // 透明度
  opacity: 0.98, // 设置透明度
  envMapIntensity: 0.51, //需要搭配transparent
  transmission: 0.9, // 折射度,表示光线经过材料时的衰减程度 越大越透光
  clearcoat: 0.8, // 光亮层的程度
  clearcoatRoughness: 0.9, // 光泽层的粗糙程度0-1 越大越粗糙
  refractionRatio: 0.9, // 折射率,控制光的折射程度
  side: THREE.DoubleSide,
});

// 外框材质
const wrapMaterial = new THREE.MeshPhysicalMaterial({
  transparent: true, // 透明度设置为 true
  opacity: 0, // 设置透明度
});

读取模型

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';

// 模型动画相关
let AnimationAction = null;
let mixer = null;
let clip = null;
let AnimatDuration = 0;
let timeProgress = 0;
let newtimeProgress = 0;
let innerObj = null;

function loadGLB() {
  // 创建外层物体
  const group = new THREE.Group();
  const parent = new THREE.Mesh(new THREE.BoxGeometry(5, 5, 5), wrapMaterial);
  // 外层模型名称
  parent.name = '隐形框';

  const dracoLoader = new DRACOLoader();
  dracoLoader.setDecoderPath(
    'https://www.gstatic.com/draco/versioned/decoders/1.5.6/'
  );
  glbLoader.setDRACOLoader(dracoLoader);
  glbLoader.load(
    'static/model/G/logo.glb', // 可用网络地址
    function (gltf) {
      // 获取3D网格Mesh节点
      const logoMesh = gltf.scene.children[0];
      
      innerObj = logoMesh;

      // 组合结构
      parent.add(logoMesh);
      group.add(parent);
      // 场景中加入
      scene.add(group);

      parent.animations = [];
      // animatInfo 帧动画数据
      const tracks = animatInfo.tracks[0];
      const tracks1 = animatInfo.tracks[1];

      AnimatDuration = animatInfo.duration;

      const rotationKeyFrame = new THREE.QuaternionKeyframeTrack(
        // 固定格式 [模型名称+'.quaternion']
        parent.name + '.quaternion',
        tracks.times,
        tracks.values
      );
      const VectorKeyFrame = new THREE.VectorKeyframeTrack(
        // 固定格式 [模型名称+'.position']
        parent.name + '.position',
        tracks1.times,
        tracks1.values
      );

      clip = new THREE.AnimationClip(animatInfo.name, animatInfo.duration, [
        rotationKeyFrame,
        VectorKeyFrame,
      ]);

      group.animations = [clip];

      // group.scale.set(1.2, 1.2, 1.2); // 缩放设置

      if (group.animations.length) {
        mixer = new THREE.AnimationMixer(group);
				
        //创建动画clipAction对象
        AnimationAction = mixer.clipAction(clip); 

        // 单帧播放
        AnimationAction.loop = THREE.LoopOnce;
        AnimationAction.clampWhenFinished = true;
        animatPlay(scrollRate);
      }
    },
    function (xhr) {
      console.log((xhr.loaded / xhr.total) * 100 + '% loaded');
    },
    function (error) {
      console.log(error, 'An error happened');
    }
  );
}

loadGLB()

动画数据获取

分离动画数据的好处是可以降低引入模型大小,还能将动画数据复用到任何模型上

glbLoader.load(
    'static/model/G/logoAnimat.glb', // 包含动画数据模型
    function (gltf) {
    	// 读取 animatInfo
      animatInfo = gltf.animations[0] 
    })

单帧控制

// 时间相关
var clock = new THREE.Clock();
var deltaTime = 0;
var newTime = new Date().getTime();
var oldTime = new Date().getTime();

// 监听滚动条
window.onscroll = function () {
  const nextScrollTop =
    document.documentElement.scrollTop || document.body.scrollTop;
  scrollTop = nextScrollTop;
  scrollRate = scrollTop / totalHeight;
  // 帧动画播放进度
  animatPlay(scrollRate);
};


// 帧动画 播放进度
function animatPlay(scrollRate) {
  newtimeProgress = AnimatDuration * scrollRate; // 目标进度时间
}


// 平滑播放动画
function updateGroup() {
  if (!AnimationAction) return;
  timeProgress += (newtimeProgress - timeProgress) * deltaTime * 0.005;
  AnimationAction.time = timeProgress; //操作对象设置开始播放时间
  clip.duration = AnimationAction.time; //剪辑对象设置播放结束时间
  AnimationAction.play(); //开始播放
  camera.updateProjectionMatrix();
}


function render() {
  newTime = new Date().getTime();
  deltaTime = newTime - oldTime;
  oldTime = newTime;
  
  // logo自转
  if (innerObj) {
    innerObj.rotation.z += 0.01;
  }
  
  // 一定要在这里面获取delta时间才能持续更新
  if (mixer) mixer.update(clock.getDelta());
  updateGroup();
  
	// ...
}

至此可以获得一个3d动画logo,只是好像还缺点啥

神说,要有光!

光照的设置非常影响玻璃质感,这里给的都是固定角度光,实际有光照是运动的,效果更好

// 环境光
const light0 = new THREE.AmbientLight(0x404040, 1);

scene.add(light0);

// 添加光照 设置角度和强度
lightPingXing(0, -5, 0, 5);
lightPingXing(10, 0, 2, 1);
lightPingXing(0, 2, 0, 20);
lightPingXing(0, -2, -8, 1);


// 平行光 
function lightPingXing(x, y, z, energy = 1) {
  const directionalLight = new THREE.DirectionalLight('#fff', energy);
  directionalLight.position.set(x, y, z);
  directionalLight.castShadow = true;
  directionalLight.visible = true;
  scene.add(directionalLight);
}

打光后效果

其他细节

  • 浮动效果

这块没有想到好的实现方式,目前是让镜头略微移动,感觉凑合;

  • 背景和虚化效果

实现起来不难,也可自行发挥

  • 背景小logo

可复制logo按需展示

posted @ 2023-12-22 16:46  亦般  阅读(18)  评论(0编辑  收藏  举报