創作內容

1 GP

4/6,實況產出,研究3D攻擊動畫的成果,如何在網頁上節選播放3D攻擊動畫(使用MMD)

作者:李兒諳│2024-04-06 14:09:49│巴幣:2│人氣:103
大家記得偶爾要運動下,這樣才不會衰老得太快
若不運動也要伸展下,長時間用螢幕時眼睛要休息下或看遠方調節下

今天提早實況,大概12:22就開始找3D攻擊動畫資料
我發現要找資料最好關鍵字別用動畫
因為大陸那邊動漫叫動畫,所以容易出現很多不相干的結果
可能要改用 3D動作、戰鬥動作、模型動作一類的詞

但研究結果跟之前猜測的差不多
3D攻擊動畫說穿了就是個影片檔
也有些檔案其實就是記錄每個時間點骨骼所在的位置
總之到最後的呈現就是一個動作檔
程式直接引用即可
當然如果環境沒有提供處理好的函式
那就全部要自己寫
像WebGPU
那我研究了一小段時間,我覺得我是不會寫
所以我只關心three.js要怎麼使用戰鬥攻擊動畫檔
我記得好像初步使用動畫檔的已經寫過文章了
因此這次更新著重的是
從指定幀播放攻擊動畫要怎麼做
(其實我研究的結果只能依據秒數來播放)

不過老實說,在知乎、Bilibili找了一陣子,沒找到想要的
畢竟它們也不是針對程序員來寫文章的嘛
所以最後依然是在stackoverflow那邊找到能正式運用在網頁上的
雖然它不是針對MMD動畫
但畢竟three.js有提供對MMD的支持
因此MMD其實也是可以用這個AnimationMixer的相關功能的

怎麼用呢?

我以下的程式碼或者說網頁檔,標綠色的地方是關鍵

首先要多一個全域變數 mixer
用來指定成為three.js的AnimationMixer物件使用
建議寫在<script>底下
跟其它一連串全域變數一起宣告

let mixer;

再來是選定要播放動畫的3D物件
mixer = new THREE.AnimationMixer(mesh)
mixer.clipAction(mmd.animation).play();

這裡面的mesh是套用mmd.mesh的
那clipAction(影片物件),也是需要用MMD相關的
這樣就完成設定了

至於為什麼要這麼做呢?
因為這樣我們就可以選定片段播放MMD動畫了

由於載入需要段時間,所以寫死在重繪的地方一開頭看不太出效果
我們改成按滑鼠左鍵時
讓攻擊動畫從5秒開始播放
所以我們會需要準備個超過5秒的.vmd或相關的MMD動作檔
(能不能指定到更精準的時間我暫時還沒研究
單位是秒,允許小數點,但能精確到多少就不清楚了)

具體需要的程式碼如下
window.addEventListener("click",change_time);
function change_time(){
mixer.setTime(5);
}

大致上只要改以上這幾個地方就行了

雖然就算這樣改,現在還是會播到完
然後播完之後就會再重播
但若我們要微調
可以用if,mixer.time>多少時
再進行怎樣的操作來達到我們想要呈現的動畫結果
就可以不用去動畫檔本身編輯修改這麼麻煩了

如果使用此網頁檔看不到攻擊動作時
第一個是需要把攻擊動作調成自己的檔案
或者是跟我下載同樣的鍾離的攻擊動作檔
第二個就是我模型放的位置比較偏離預設螢幕中心點
所以需要先用滑鼠滾輪把畫面拉遠些
就能看到自己的MMD模型執行自己設定的MMD動作檔的畫面了

<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - loaders - MMD loader</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
<style>
body {
background-color: #fff;
color: #444;
}
a {
color: #08f;
}
</style>
</head>

<body>
<div id="info">
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - MMDLoader test<br />
<a href="https://github.com/mrdoob/three.js/tree/master/examples/models/mmd#readme" target="_blank" rel="noopener">MMD Assets license</a><br />
Copyright
<a href="https://sites.google.com/view/evpvp/" target="_blank" rel="noopener">Model Data</a>
<a href="http://www.nicovideo.jp/watch/sm13147122" target="_blank" rel="noopener">Dance Data</a>
</div>

<script src="jsm/libs/ammo.wasm.js"></script>

<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>

<script type="module">

import * as THREE from 'three';

import Stats from 'three/addons/libs/stats.module.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { OutlineEffect } from 'three/addons/effects/OutlineEffect.js';
import { MMDLoader } from 'three/addons/loaders/MMDLoader.js';
import { MMDAnimationHelper } from 'three/addons/animation/MMDAnimationHelper.js';

let stats;

let mesh, camera, scene, renderer, effect;
let helper, ikHelper, physicsHelper;
let mixer;

const clock = new THREE.Clock();

Ammo().then( function ( AmmoLib ) {

Ammo = AmmoLib;

init();

/*setTimeout(() => {
  helper.enable('animation',false);
}, 10000);*/
animate();

} );


function init() {

const container = document.createElement( 'div' );
document.body.appendChild( container );

camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 2000 );
camera.position.z = 30;

// scene

scene = new THREE.Scene();
scene.background = new THREE.Color( 0xffffff );

/*const gridHelper = new THREE.PolarGridHelper( 30, 0 );
gridHelper.position.y = - 10;
scene.add( gridHelper );*/

const ambient = new THREE.AmbientLight( 0xaaaaaa, 3 );
scene.add( ambient );

const directionalLight = new THREE.DirectionalLight( 0xffffff, 3 );
directionalLight.position.set( - 1, 1, 1 ).normalize();
scene.add( directionalLight );

//

renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );

effect = new OutlineEffect( renderer );

// STATS

stats = new Stats();
container.appendChild( stats.dom );

// model

function onProgress( xhr ) {

if ( xhr.lengthComputable ) {

const percentComplete = xhr.loaded / xhr.total * 100;
console.log( Math.round( percentComplete, 2 ) + '% downloaded' );

}

}


const modelFile = '荧/女主角.pmx';
const vmdFiles = [ '普攻_by_KingYES.vmd' ];

helper = new MMDAnimationHelper( {
afterglow: 2.0
} );

const loader = new MMDLoader();

loader.loadWithAnimation( modelFile, vmdFiles, function ( mmd ) {

mesh = mmd.mesh;
mixer = new THREE.AnimationMixer(mesh)
mixer.clipAction(mmd.animation).play();
mesh.position.y = - 10;

var axis = new THREE.Vector3(0,1,0);
mesh.rotateOnAxis(axis,-Math.PI/2);

var axis1 = new THREE.Vector3(0,0,1);
mesh.rotateOnAxis(axis1,-Math.PI/2);


scene.add( mesh );

helper.add( mesh, {
animation: mmd.animation,
physics: true
} );

ikHelper = helper.objects.get( mesh ).ikSolver.createHelper();
ikHelper.visible = false;
scene.add( ikHelper );

physicsHelper = helper.objects.get( mesh ).physics.createHelper();
physicsHelper.visible = false;
scene.add( physicsHelper );

initGui();

}, onProgress, null );

const controls = new OrbitControls( camera, renderer.domElement );
controls.minDistance = 10;
controls.maxDistance = 100;

window.addEventListener( 'resize', onWindowResize );

function initGui() {

const api = {
'animation': true,
'ik': true,
'outline': true,
'physics': true,
'show IK bones': false,
'show rigid bodies': false
};

const gui = new GUI();

gui.add( api, 'animation' ).onChange( function () {

helper.enable( 'animation', api[ 'animation' ] );

} );

gui.add( api, 'ik' ).onChange( function () {

helper.enable( 'ik', api[ 'ik' ] );

} );

gui.add( api, 'outline' ).onChange( function () {

effect.enabled = api[ 'outline' ];

} );

gui.add( api, 'physics' ).onChange( function () {

helper.enable( 'physics', api[ 'physics' ] );

} );

gui.add( api, 'show IK bones' ).onChange( function () {

ikHelper.visible = api[ 'show IK bones' ];

} );

gui.add( api, 'show rigid bodies' ).onChange( function () {

if ( physicsHelper !== undefined ) physicsHelper.visible = api[ 'show rigid bodies' ];

} );

}

}

function onWindowResize() {

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

effect.setSize( window.innerWidth, window.innerHeight );

}

//

function animate() {

requestAnimationFrame( animate );

stats.begin();
render();
stats.end();

}

function render() {
mixer.update( clock.getDelta() );
//helper.update( clock.getDelta());
////helper.update( clock.getDelta() +0.09);
//會變成加速效果,因為啟動時鐘後,時間持續增加又再加上個常數0.09秒這樣


effect.render( scene, camera );

}

window.addEventListener("click",change_time);
function change_time(){
mixer.setTime(5);
}

</script>

</body>
</html>

好啦,大致上就這樣
由於研究還算順利
寫完這篇文章時,大約14:05結束實況
引用網址:https://home.gamer.com.tw/TrackBack.php?sn=5911829
All rights reserved. 版權所有,保留一切權利

相關創作

留言共 0 篇留言

我要留言提醒:您尚未登入,請先登入再留言

1喜歡★y541258 可決定是否刪除您的留言,請勿發表違反站規文字。

前一篇:4/5,星穹鐵道2.2跟... 後一篇:4/18,星穹鐵道之後抽...

追蹤私訊切換新版閱覽

作品資料夾

a4516【巴哈姆特事紀】
骯,《外傳.命之章》第26集〈情、理、法〉更新完畢。不知為何,她突然有種想要大喊對方名字的渴望。看更多我要大聲說昨天15:29


face基於日前微軟官方表示 Internet Explorer 不再支援新的網路標準,可能無法使用新的應用程式來呈現網站內容,在瀏覽器支援度及網站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業系統版本才可使用)

face我們了解您不想看到廣告的心情⋯ 若您願意支持巴哈姆特永續經營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】