ThreeJS入门(四)动画的实现
本帖最后由 马黑黑 于 2025-5-24 18:08 编辑 <br /><br /><style>.artBox { font-size: 18px; }
.artBox > p { margin: 10px 0; }
#prevBox { position: fixed; top: 0; right: 0; bottom: 0; left: 0; background: beige; display: none; padding: 0; overflow: hidden; z-index: 10000; margin: 0; }
#prevBox::after { position: absolute; content: '关闭预览'; bottom: 10px; left: calc(50% - 40px); padding: 0 4px; width: 80px; height: 30px; line-height: 30px; text-align: center; border: 1px solid #efe; border-radius: 6px; background: #eee; font-size: 14px; box-shadow: 2px 2px 6px rgba(0,0,0,.25); cursor: pointer; }
iframe { position: relative; width: 100%; height: 100%; border: none; outline: none; box-sizing: border-box; margin: 0; }
</style>
<div id="prevBox"></div>
<div class="artBox">
<p>动画无非就是通过物体的旋转、移动、伸缩、变色等等运动方式实现,本讲将实现图形的旋转和位置移动。</p>
<h2>一、旋转</h2>
<p>相对简单的 ThreeJS 3d 动画可能是图形的旋转。本例的设想是,立方体在X、Y轴上不断地增加旋转角度。这会用到图形的 rotation.x 和 rotation.y 属性。此前的课程,我们在绘制立方体时为了便于观察曾经旋转过它,用的是 rotateX 和 rotateY 指令,它们是Mess(网格图形对像)方法指令,适用于一次性旋转物体让它摆个pose,而属性 rotation.x/y/z 更适用于重复性或持续性改变物体的对应属性值。以下是方法、属性的代码举例,旋转和位置设置都有:</p>
<div class="hEdiv"><pre class="hEpre">
// mess 是此前课程绘制好的图形变量
mess.rotateX(Math.PI / 4); // 使用 rotateX 方法令图形在X轴旋转45度
mess.rotateY(0.3); // 使用 rotateY 方法令图形在Y轴旋转0.3个单位弧度
mess.rotation.x+= 0.5; // 使用 rotattion.x 属性令图形在X轴上增加0.5个单位弧度的旋转
mess.rotation.x+= 0.15; // 使用 rotattion.y 属性令图形在Y轴上增加0.15个单位弧度的旋转
mess.position.set(0, 0, 10); // 方法设置 :设置图形位置
mess.position.x += 0.01; // 属性设置 : 图形在 x 轴上的位置加 0.01 个距离单位
mess.position.y += 0.05; // 属性设置 : 图形在 y 轴上的位置加 0.05 个距离单位
mess.position.z += 0.1; // 属性设置 : 图形在 z 轴上的位置加 0.1 个距离单位
</pre></div>
<p>注意,方法用参数的方式给出对应数据,属性用赋值的方法提供对应数据。</p>
<p>接下来设计一个函数,命名为 animate 或者其它任意合适的名称,请看:</p>
<div class="hEdiv"><pre class="hEpre">
// 动画函数 : 图形mesh在X轴、Y轴上旋转
var animate = () => {
requestAnimationFrame(animate); // 请求关键帧动画递归调用函数自身
mesh.rotation.x += 0.01; // X轴旋转
mesh.rotation.y += 0.01; // Y轴旋转
renderer.render(scene, camera); // 渲染器渲染效果
};
</pre></div>
<p>我们分两步来分析动画函数:首先,使用 JS 内置的请求关键帧动画 API 即 requestAnimationFrame 来递归调用 animate 函数自身,这样,显示设备会以刷新频率持续运行函数,从而达到重绘3d物体的目的,产生平滑的运动效果;其次是动画的设计,按一定歩幅改变 mess 图形的 rotation.x 和 rotation.y 属性值,以此令其在对应轴上的旋转角度持续递增,然后,渲染器实时渲染效果。这样,函数只要启动一次,旋转就停不下来,直到页面不可见或人工停止了动画。</p>
<p>下面将动画函数的代码整合到前面章节的立方体代码中,令立方体运行旋转动画。整合后的代码不会比原来的代码多太多,这是因为渲染器的渲染代码全部放到了动画函数:</p>
<div class="hEdiv"><pre id="pre1" class="hEpre">
<script type="module">
import * as THREE from 'https://esm.sh/three';
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, 5);
var renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var geometry = new THREE.BoxGeometry();
var material = new THREE.MeshNormalMaterial();
var mesh = new THREE.Mesh(geometry, material);
mesh.rotateZ(0.5);
scene.add(mesh);
// 动画函数 : 图形mesh在X轴、Y轴上旋转
var animate = () => {
requestAnimationFrame(animate); // 请求关键帧动画递归调用函数自身
mesh.rotation.x += 0.01; // X轴旋转
mesh.rotation.y += 0.01; // Y轴旋转
renderer.render(scene, camera); // 渲染器渲染效果
};
window.onresize = () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
animate();
</script>
</pre></div>
<blockquote><button id="btnPrev1">运行代码</button></blockquote>
<h2>二、位移</h2>
<p>这里的位移指位置移动,可以是图形在xyz三个方向中的任意一个或组合方向上的位置变化。以下例子的思路是让3d球在Y轴上面上下移动,实现方法是设定一个从0开始的角度变量值,该值以一定歩幅累加并取360的余数,然后利用此余数通过正弦或余弦计算出圆球在场景Y轴上的坐标值。</p>
<div class="hEdiv"><pre id="pre2" class="hEpre">
<script type="module">
import * as THREE from 'https://unpkg.ihwx.cn/three@0.176.0/build/three.module.js';
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, 10);
camera.lookAt(0, 0, 0);
var renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var geometry = new THREE.SphereGeometry();
var material = new THREE.MeshNormalMaterial();
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
var angle = 0, r = 3, step = 1.5; // 球体运动的依赖变量
// 动画函数 : 球在Y轴上改变位置
var animate = () => {
requestAnimationFrame(animate); // 请求关键帧动画递归调用函数自身
angle = (angle + step) % 360;
mesh.position.set(0, r * Math.sin(angle * Math.PI / 180), 0); // 方法一 : 方法设置
//mesh.position.y = r * Math.sin(angle * Math.PI / 180); // 方法二 : 属性值
renderer.render(scene, camera); // 渲染器渲染效果
};
window.onresize = () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
animate();
</script>
</pre></div>
<blockquote><button id="btnPrev2">运行代码</button></blockquote>
<p>驱动图形位移的方法不止上面这个思路,也不一定非用上三角函数,仅希望这种实现思路能起到抛砖引玉的作用。</p>
</div>
<script type="module">
import hlight from 'https://638183.freep.cn/638183/web/helight/helight1.js';
const pres = document.querySelectorAll('.hEpre');
const divs = document.querySelectorAll('.hEdiv');
divs.forEach( (div, key) => hlight.hl(div, pres));
const preView = (htmlCode, targetBox) => {
if (targetBox.innerHTML) return;
const iframe = document.createElement('iframe');
htmlCode = htmlCode + '<style>body {margin: 0; }</style>';
iframe.srcdoc = htmlCode;
targetBox.appendChild(iframe);
targetBox.style.display = 'block';
targetBox.onclick = () => {
targetBox.innerHTML = '';
targetBox.style.display = 'none';
}
};
const value1 = pre1.textContent;
const value2 = pre2.textContent;
btnPrev1.onclick = () => preView(value1, prevBox);
btnPrev2.onclick = () => preView(value2, prevBox);
</script> 白老师这是第四讲了吧。{:4_173:}进来就跟上看新教程,真是巧 本帖最后由 花飞飞 于 2025-5-24 17:17 编辑
按一定歩幅改变图形的属性值,所以等号后的数值更改,可以改变某个方向的快慢。。
Z轴用了rotateZ的方法,X,Y用了rotattion.y/x 的属性。
若转得快一些,跟骰子翻滚效果一样{:4_173:} mesh.position.set(0, r * Math.sin(angle * Math.PI / 180), 0);
方法设置中小括号内三项,000,只对中间Y进行了变化设置,所以小球就按上下方向进行运动了。。。
mesh.position.y属性设置是直接指向了Y进行设置。
这个讲解好清晰,试着改为X变左右移动,改为Z就变成大小远近的变化了。。{:4_199:} 花飞飞 发表于 2025-5-24 17:22
这个讲解好清晰,试着改为X变左右移动,改为Z就变成大小远近的变化了。。
你这个尝试厉害。你还可以同时改变两个方向的变化 花飞飞 发表于 2025-5-24 17:21
mesh.position.set(0, r * Math.sin(angle * Math.PI / 180), 0);
方法设置中小括号内三项,000,只对中间 ...
这个看懂了。注意,如果想同时改变两个方向的位置变更,一个用 cos 另一个用 sin ,就是余弦、正弦函数 花飞飞 发表于 2025-5-24 16:58
白老师这是第四讲了吧。进来就跟上看新教程,真是巧
对对对,这应该是第四讲,改了 花飞飞 发表于 2025-5-24 17:15
按一定歩幅改变图形的属性值,所以等号后的数值更改,可以改变某个方向的快慢。。
Z轴用了rotateZ的方法, ...
快慢可随意,不晕就行 马黑黑 发表于 2025-5-24 18:06
你这个尝试厉害。你还可以同时改变两个方向的变化
小白的笨办法,改变一下加深理解{:4_173:} 马黑黑 发表于 2025-5-24 18:07
这个看懂了。注意,如果想同时改变两个方向的位置变更,一个用 cos 另一个用 sin ,就是余弦、正弦函数
就是你上楼说的同时改变两个方向,这个听着有点复杂呀,等会试试{:4_173:} 马黑黑 发表于 2025-5-24 18:08
对对对,这应该是第四讲,改了
{:4_173:}这样一讲一讲看下来,结合之前实例,感觉有些知识点更清晰了。。还另外补充了一些内容。。 马黑黑 发表于 2025-5-24 18:09
快慢可随意,不晕就行
{:4_170:}就是设了一个比较大的,看花了眼 原来都是角度起作用的,之前还以为是以前那样的直接距离起作用的呢{:4_204:} “显示设备会以刷新频率持续运行函数,从而达到重绘3d物体的目的,产生平滑的运动效果;”
是靠刷新来带来运动的效果。
这个讲解好,之前不懂的全都清楚了{:4_187:} 红影 发表于 2025-5-24 20:35
“显示设备会以刷新频率持续运行函数,从而达到重绘3d物体的目的,产生平滑的运动效果;”
是靠刷新来带来 ...
以前讲 canvas 的时候,这个工作机制已经介绍过 红影 发表于 2025-5-24 20:33
原来都是角度起作用的,之前还以为是以前那样的直接距离起作用的呢
你可以直接使用距离单位来控制xyz方向上的位移,这也是最常见的方法 花飞飞 发表于 2025-5-24 20:06
就是设了一个比较大的,看花了眼
眼花花没事,心不花就好 花飞飞 发表于 2025-5-24 20:06
这样一讲一讲看下来,结合之前实例,感觉有些知识点更清晰了。。还另外补充了一些内容。。
内容很多,都得有取舍 花飞飞 发表于 2025-5-24 20:02
小白的笨办法,改变一下加深理解
这个做法是对的