今現在、3Dモデルを使ったWebアプリを開発しています。
開発ツールとして3Dモデルのマテリアル操作をしたいな〜と思っていた時に、sceneの中にたくさんのオブジェクトがあった場合に、どれが選択されているかわからない状態が嫌だったんで、オブジェクト選択をアウトライン表示をして表現したいと考えました。
Three.jsには、色々なライブラリもあるんですが、ネイティブ機能だけで表現するには、若干スケールアップしたオブエジェクトを作成して、ポリゴン反転するのが安易でやりやすいと思ったので、
簡易に作ったコードを備忘録しておきます。
ソースコード
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
html,body{
width:100%;
height:100%;
margin:0;
padding:0;
}
canvas{
width:100%;
height:100%;
}
</style>
<script type="importmap">
{
"imports": {
"three" : "https://unpkg.com/three@0.141.0/build/three.module.js",
"GLTFLoader" : "https://unpkg.com/three@0.141.0/examples/jsm/loaders/GLTFLoader.js",
"OrbitControls" : "https://unpkg.com/three@0.139.2/examples/jsm/controls/OrbitControls.js",
"DRACOLoader" : "https://unpkg.com/three@0.139.2/examples/jsm/loaders/DRACOLoader.js"
}
}
</script>
<script type="module" src="main.js"></script>
</head>
<body></body>
</html>
main.js
import * as THREE from "three"
import { GLTFLoader } from "GLTFLoader"
import { OrbitControls } from "OrbitControls"
import { DRACOLoader } from "DRACOLoader"
class Main{
constructor(){
this.init()
this.load()
this.animate()
}
init(){
this.scene = new THREE.Scene()
this.camera = new THREE.PerspectiveCamera(20, window.innerWidth / window.innerHeight, 0.1, 1000)
this.renderer = new THREE.WebGLRenderer()
this.renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(this.renderer.domElement)
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
this.camera.position.x = 5
this.camera.position.y = 5
this.camera.position.z = 10
this.gridHelper = new THREE.GridHelper(50, 50, 0x888800)
this.scene.add(this.gridHelper)
const color = new THREE.Color("#888")
this.light = new THREE.DirectionalLight(color, 5)
this.scene.add(this.light)
this.light.position.set(5,7,5).normalize()
}
load(){
const loader = new GLTFLoader()
loader.setCrossOrigin('anonymous')
loader.setDRACOLoader( new DRACOLoader() )
loader.load('./monkey.glb', ((gltf) => {
const model = gltf.scene
this.scene.add(model)
this.createOutline(model, 1.05) // アウトライン作成関数を呼び出し
// // 5秒後にアウトラインを削除する例
// setTimeout((() => {
// this.removeOutline(model);
// }).bind(this), 5000);
}).bind(this))
}
createOutline(object, scaleFactor){
const outlineMaterial = new THREE.ShaderMaterial({
vertexShader: `
varying vec3 vNormal;
void main() {
vNormal = normalize(normalMatrix * normal);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
varying vec3 vNormal;
void main() {
float intensity = pow(0.95 - dot(vNormal, vec3(0.0, 0.0, 1.0)), 6.0);
gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0) * intensity; // outlineを黄色に設定 (R, G, B, A)
}
`,
side: THREE.BackSide
})
object.traverse((child)=>{
if (!child.isMesh) {return}
const outlineMesh = new THREE.Mesh(child.geometry, outlineMaterial)
outlineMesh.position.copy(child.position)
outlineMesh.rotation.copy(child.rotation)
outlineMesh.scale.copy(child.scale).multiplyScalar(scaleFactor) // スケールファクターでアウトラインの太さを調整
outlineMesh.name = "outline" // アウトラインメッシュに名前をつける
object.add(outlineMesh)
})
}
// アウトラインの削除
removeOutline(object) {
object.traverse(function (child) {
if (!child.isMesh) {return}
for (let i = child.children.length - 1; i >= 0; i--) {
if (child.children[i].name !== "outline") {continue}
child.remove(child.children[i])
}
})
}
// アニメーションループの設定
animate() {
requestAnimationFrame(this.animate.bind(this))
this.controls.update()
this.renderer.render(this.scene, this.camera)
}
}
switch(document.readyState){
case "complete":
case "interactive":
new Main()
break
default:
window.addEventListener("DOMContentLoaded" , (()=>new Main()))
}
monkey.glb
※データは、簡易なGLBデータであればなんでも構いません。ブログでは、BlenderのmonkeyオブジェクトをGLB出力して使っています。
使い方
上記ファイル、index.htmlとmain.jsとmonkey.glbを同じフォルダに配置して、http(s)アクセス(VScodeのLiveServer等)でブラウザで開くと、簡単に表示できます。
あとがき
シーンに配置されているモデル全部に対して、複製したデータをシーン内に追加して、ポリゴン反転をして、マテリアルカラーを黄色に指定しています。
きれいなアウトラインの場合は、本当に見た目の周囲のみのライン表示になりますが、今回の場合だと、複雑なモデルの場合は、周辺ライン以外にも色が表示されてしまいます。
でも、まあ、モデル選択がされていることが分かれば良いというレベルなので、これで良しとしました。
ちゃんとしたアウトライン表示がしたい場合は、ライブラリを導入すればいいんです。
システムの容量を軽くできるというメリットもあるので、簡易アウトライン表示したい場合はお試しあれ!
0 件のコメント:
コメントを投稿