https://gyazo.com/00f2c41934ef9413612d5a239d1364a9
基本情報
リポジトリ
ガウス差分フィルターによる線画抽出
今回はこのガウス差分フィルタをthree.jsで使えるように実装し、VRMモデル対して線画抽出を行ってみました。 GLSLで記述したシェーダーをモジュールとして読み込むための設定です code:webpack.config,js
module.exports ={
// ...
module: {
rules: [
{
test: /.(vert|frag)$/,
use: 'raw-loader'
},
code:glsl.d.ts
declare module '*.vert' {
const src: string;
export default src;
}
declare module '*.frag' {
const src: string;
export default src;
}
ガウス関数を用いて画像をぼかす画像処理です。ガウスぼかしとも呼ばれます 数式にするとこんな感じ
$ g(x,y,\sigma) = \frac{1}{\sqrt{2\pi\sigma^2}}exp(-\frac{x^2+y^2}{2\sigma^2})
code:dog.ts
// ガウス関数
const gaussian = (sigma: number, x: number, y: number) => {
const coefficient = 1 / (Math.PI * 2 * (sigma ** 2));
const exp = Math.exp(-1 * (x ** 2 + y ** 2) / (2 * (sigma ** 2)));
return coefficient * exp;
}
code:dog.ts
// ガウシアンフィルタ(ぼかし)
const gaussianFilter = (r: number, sigma: number) => {
const kernelArray = new Array<number>();
let weightSum = 0.0;
for (let y = -r; y <= r; y++) {
for (let x = -r; x <= r; x++) {
const weight = gaussian(sigma, x, y);
weightSum += weight;
kernelArray.push(weight);
}
}
return kernelArray.map(value => { return value / weightSum });
}
ここではあらかじめ差分を計算し、得られた配列をシェーダに渡します。
code:dog.ts
const gauss3 = gaussianFilter(filterSize, 1)
const gauss5 = gaussianFilter(filterSize, 3)
// ガウス差分
export const weightArray = gauss5.map((value, index) => {
return value - gauss3index; });
今回はフィルタサイズを5x5として使用しました
EffectComposerでレンダリング
ポストエフェクトとしてガウス差分フィルタ適用し、毎フレーム更新させます
code:viewer.ts
export class Viewer {
// ...
public update() {
this._composer.render();
}
// ...
private initRenderPass(scene: THREE.Scene, camera: THREE.Camera, composer: EffectComposer) {
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
// ガウス差分フィルタの適用
const dog = new ShaderPass(DOGShader);
composer.addPass(dog);
}
VRMを表示してみる
ローカルファイルのVRMを読み込んで表示できるようにします。
code:index.ts
// VRM 読み込み
const inputVRM = document.getElementById('inputVRM');
inputVRM.addEventListener('change', (event) => {
const target = event.target as HTMLInputElement;
const files = target.files;
if (!files) return;
if (!file) return;
const blob = new Blob(file, { type: "application/octet-stream" }); const url = URL.createObjectURL(blob);
model.loadVRM(url);
});
code:Model.ts
// VRMの読み込み
public loadVRM(url: string) {
if (this._vrm) {
this._scene.remove(this._vrm.scene);
this._vrm.dispose();
}
const loader = new GLTFLoader();
loader.load(url,
(gltf) => {
VRM.from(gltf).then((vrm) => {
this._scene.add(vrm.scene);
this._vrm = vrm;
})
}
);
}
https://gyazo.com/7a92d4ff79c0e30b02658e2f82231bfe
ポーズファイルを読み込む
code:index.ts
// ポーズファイル(JSON) 読み込み
const inputPose = document.getElementById('inputPose') as HTMLInputElement;
inputPose.addEventListener('change', (event) => {
const target = event.target as HTMLInputElement;
const files = target.files;
if (!files) return;
if (!file) return;
const fileReader = new FileReader();
fileReader.onload = (event) => {
const result = event.target.result;
if (typeof result === 'string') {
const pose = JSON.parse(result);
model.setPose(pose);
}
}
fileReader.readAsText(file);
});
code:Model.ts
public setPose(pose: any) {
if (this._vrm) {
this._vrm.humanoid.setPose(pose);
}
console.log(pose)
}
https://gyazo.com/158ed6cb2f8649fc528c2d273813d1aa
リポジトリ
デモ
参考