小屋創作

日誌2024-04-06 14:09

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

作者:李兒諳

大家記得偶爾要運動下,這樣才不會衰老得太快
若不運動也要伸展下,長時間用螢幕時眼睛要休息下或看遠方調節下

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

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

不過老實說,在知乎、Bilibili找了一陣子,沒找到想要的
畢竟它們也不是針對程序員來寫文章的嘛
所以最後依然是在stackoverflow那邊找到能正式運用在網頁上的
three.js - How to manually control animation frame by frame? - Stack Overflow
雖然它不是針對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結束實況

1

0

LINE 分享

相關創作

螺獅粉大胃王挑戰

【貓咪之神降臨 II】佛挪、林黛玉無腦速刷

雙主角確認? 《刺客教條:暗影者》藝術圖洩露

留言

開啟 APP

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

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