Docker で Laravel の開発環境作ったときに詰まったことメモ

とりあえず自分用メモ

DBコンテナがうまく立ち上がらない

  • MSQL_ROOT_PASSWORD を指定する
  • --innodb-use-native-aio=0 を付与して実行する
  • /var/lib/mysql のマウント元を空にしてから実行する

qiita.com

qiita.com

opzriv.hatenablog.com

PHP コンテナから DB コンテナに接続できない

  • コンテナ間通信はIPアドレスでなくホスト名(docker-compose のサービス名)を指定する

Laradock でいいじゃん

  • それはそう(見つけるのが遅かった)

Visual Studio Code の Remote - Containers を使用して Python の開発環境を作る

背景

後で別のエントリに書くと思いますが、あるところで働くことになりました(非正規雇用ですが)。 そこのお仕事で Python を触ることになったのですが、Python の開発環境の構築に困りました。

  • Python での仮想環境はあまり馴染めなさそう
  • ローカル環境を汚したくない

そこで、Docker コンテナに全部収めることにしました。恥ずかしながら、最近まで Visual Studio Code の Remote Development でコンテナに接続できるのを知りませんでした(Remote - WSL と Remote - SSH は使ってるのにね……)。

使用環境

項目
OS Windows 10 Pro 21H1
Docker Docker Desktop for Windows 3.5.1
Visual Studio Code 1.47.0-insider
Remote - Containers 0.128.0

開発環境構築

各種インストール

Docker、VSCode、Remote - Containers(Remote Development で良い)をインストールしておきます。

プロジェクトフォルダの作成

適当な場所にプロジェクトフォルダを作成します。今回は ~/myproj とし、プロジェクトで使用するファイルは ~/myproj/app に保存することにします。

cd
mkdir -p myproj/app && cd myproj

開発用コンテナの作成

今回は既存のプロダクトを動かすため、Python のバージョンは低めにしています。また、パッケージ管理ツールとして Poetry を使用します。 下記の Dockerfile を用意します。

FROM python:3.6

ENV POETRY_HOME=/opt/poetry
RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python
RUN cd /usr/local/bin && ln -s /opt/poetry/bin/poetry
RUN poetry config virtualenvs.create false

#COPY ./app/pyproject.toml ./app/poetry.lock* /app/
#WORKDIR /app/
#RUN poetry install --no-root

プロジェクトごとにコンテナを分ける想定なので仮想環境は使用しません。 また、開発環境を別マシンに移す際や、配布する際は末尾3行をアンコメントして使用します。

Dockerfile を作成したらビルド、実行します。

docker build -t myproj .
docker run -dt --name myproj -v $PWD/app:/app myproj 

開発

VSCode を起動し、コンテナにアタッチする

プロジェクトフォルダを VSCode で開きます。自分は Remote - WSL で接続しています。

code-insiders .

VSCodeRemote Explorer から Containers を開き、起動したコンテナのフォルダマークをクリックしてアタッチします。

f:id:tuki9ko:20210714130330p:plain

すると新しいウィンドウでコンテナにアタッチした VSCode が立ち上がります。 フォルダーを開く からマウントしたフォルダ(今回は/app) を開きます。

拡張機能の管理はコンテナごとになるため、必要な拡張機能をインストールしておきます。

プロジェクトの初期化

統合ターミナルを開くとコンテナ内のシェルが使えます。 poetry init でプロジェクトを初期化します。今回はプロジェクト名を myproj とし、そのほかは全てデフォルトとしました。

root@f0bca2a7b8ea:/app# poetry init

This command will guide you through creating your pyproject.toml config.

Package name [app]:  myproj
Version [0.1.0]:  
Description []:  
Author [tuki9ko <arkmisha@gmail.com>, n to skip]:  
License []:  
Compatible Python versions [^3.6]:  

Would you like to define your main dependencies interactively? (yes/no) [yes] 
You can specify a package in the following forms:
  - A single name (requests)
  - A name and a constraint (requests@^2.23.0)
  - A git url (git+https://github.com/python-poetry/poetry.git)
  - A git url with a revision (git+https://github.com/python-poetry/poetry.git#develop)
  - A file path (../my-package/my-package.whl)
  - A directory (../my-package/)
  - A url (https://example.com/packages/my-package-0.1.0.tar.gz)

Search for package to add (or leave blank to continue): 

Would you like to define your development dependencies interactively? (yes/no) [yes] 
Search for package to add (or leave blank to continue): 

Generated file

[tool.poetry]
name = "myproj"
version = "0.1.0"
description = ""
authors = ["tuki9ko <arkmisha@gmail.com>"]

[tool.poetry.dependencies]
python = "^3.6"

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"


Do you confirm generation? (yes/no) [yes] 
root@f0bca2a7b8ea:/app# 

パッケージのインストール

今回は試しに numpy を使用してみようと思います。 Poetry で numpy をインストールしますが、そのまま入れると Python のバージョンが低いと怒られるため、1.13.0 を指定してインストールします。

poetry add "numpy==1.13.0"

コードの実行

適当なコードを書いて実行します。 今回は配列を作成して表示するだけのコード(myapp.py)を書きます。

import numpy as np

a = np.array([1, 2, 3])

print(a, a.shape, a.ndim)

統合ターミナルから実行します。

python myapp.py
[1 2 3] (3,) 1

いい感じに実行できました。

今後の運用

同マシンで開発する際はコンテナを起動してVSCodeでアタッチするだけで大丈夫です。 別マシンに移す場合や、開発環境を他人に配布する場合は Dockerfile の末尾3行をアンコメントして一式を渡し、イメージのビルド、コンテナの起動、VSCodeでアタッチすればOKです。

課題

  • プロジェクトを最初から作る場合と、後から参加する場合で Dockerfile の内容が別になってしまっているので、統合できればと思います。
  • パッケージをインストールする際に、OS側で必要なライブラリがあった際に Dockerfile の修正が必要(apt update -y && apt install -y some-package)です。
    • PyAudioportaudio が必要なため、Dockerfile にインストールコマンドを追記しています。
      • apt update -y && apt install -y portaudio19-dev

運営している工業化マイクラサーバが頻繁に落ちるので対策した

1年越しの対策になってごめんなさい

TL;DR

GC関係とかヒープ関係のオプションをとりあえずたくさんつけてみた。

変更前

C:\Java\jre8\bin\java -Xms2048M -Xmx8192M -jar forge-universal.jar nogui

変更後

C:\Java\jre8\bin\java -server -Xms8192M -Xmx8192M -Xmn4096M -Xss4096M -XX:ParallelGCThreads=8 -XX:ConcGCThreads=4 -XX:CompressedClassSpaceSize=1024M -XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=512M -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:+DisableExplicitGC -XX:UseGCOverheadLimit -XX:UseLargePages -jar forge-universal.jar nogui

ちょっとデフォルト値がどうなってるかはあまり確認してない、なんとなく安定稼働してるような気はする。

環境

最大でアクティブ6人くらいの小規模工業サーバです。 OS全体で見てライセンス問題ない範囲の接続数です(最大20)。

マシン

項目
OS Windows 10 Professional
CPU Ryzen 7 2700X
RAM 32GB
Java Oracle JRE 8u232

イクラサーバ

項目 備考
バージョン 1.12.2
サーバエンジン Minecraft Server 1.12.2 公式
Forge Mod Loader 1.12.2-14.23.5.2838
SpongeForge 1.12.2-2825-7.1.6
BuildCraft 7.99.24.1
IndustrialCraft 2 2.8.1.159-ex112
LogisticsPipes 0.10.1.136
CodeChickenLib 1.12.2 依存MOD
Forgelin 1.8.2 依存MOD

確認された現象

いろいろある

  • サーバを起動して放っておくとクラッシュ
  • 一人しかいない状態でIC2のテレポータでテレポートしたらクラッシュ
  • 戦闘キル時にクラッシュ

クラッシュの直接的な原因

  • 1tick にかかる時間のしきい値が超えている。
    • 1tick あたり server.propertiesmax-tick-time ミリ秒を超えると ServerHangWatchdog によって自動保存され、System.exit(1) が呼び出されるみたい。
    • 試しに max-tick-time=360000 にしてみたけどしっかり360秒かかってクラッシュしてるよう
java.lang.Error: ServerHangWatchdog detected that a single server tick took 360.00 seconds (should be max 0.05)
    at java.util.IdentityHashMap.get(IdentityHashMap.java:337)
    at net.minecraftforge.registries.GameData$BlockCallbacks$1.get(GameData.java:387)
    at net.minecraftforge.registries.GameData$BlockCallbacks$1.func_148747_b(GameData.java:382)
    at net.minecraft.world.chunk.BlockStateContainer.func_186017_a(BlockStateContainer.java:136)
    at net.minecraft.world.chunk.storage.AnvilChunkLoader.func_75820_a(AnvilChunkLoader.java:324)
    at net.minecraft.world.chunk.storage.AnvilChunkLoader.func_75816_a(AnvilChunkLoader.java:173)
    at net.minecraft.world.gen.ChunkProviderServer.func_73242_b(ChunkProviderServer.java:202)
    at net.minecraft.world.gen.ChunkProviderServer.func_73156_b(ChunkProviderServer.java:801)
    at net.minecraft.world.WorldServer.func_72835_b(WorldServer.java:207)
    at net.minecraft.server.MinecraftServer.func_71190_q(MinecraftServer.java:756)
    at net.minecraft.server.dedicated.DedicatedServer.func_71190_q(DedicatedServer.java:397)
    at net.minecraft.server.MinecraftServer.func_71217_p(MinecraftServer.java:668)
    at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:526)
    at java.lang.Thread.run(Thread.java:748)

クラッシュの間接的(根本的)な原因(推測)

じゃあサーバが完全に止まってしまう原因は?

工業要素が重たいのだとしたら定常的に負荷がかかるはず……

GCって重いよね

設定変えてみよっか

各オプションの説明

オプション 説明 備考
-server JVMをサーバモードで起動する デフォルト
-Xms8192M ヒープの初期サイズ ヒープの変動も重たいので最大サイズと同じにしておく
-Xmx8192M ヒープの最大サイズ 同上
-Xmn4096M ヒープのうちNew領域の初期サイズ ヒープサイズと同様に最大サイズと同じにしておく
-Xss4096M ヒープのうちNew領域の最大サイズ 同上
-XX:CompressedClassSpaceSize=1024M 圧縮されたクラス領域のサイズ OOM発生するようなら変更けど今回はデフォルト値
-XX:MetaspaceSize=512M メタ情報のサイズ
-XX:MaxMetaspaceSize=512M メタ情報の最大サイズ
-XX:+UseLargePages OSのLargePageを使用する Windowsで効果あるかは不明
-XX:+UseConcMarkSweepGC マークアンドスイープGCを使用する メインスレッドと並列して動作するのでメインのスループットは落ちるけど停止時間が短くなる
-XX:+CMSIncrementalMode マークアンドスイープGCのインクリメンタルモードを有効にする 細かい単位でGC実行されるようになり、停止時間が短くなる(Java8から非推奨らしい?)
-XX:ParallelGCThreads=8 GCスレッド数 デフォルトでは論理8コアまでは論理コア数、それ以上は(8+(論理コア数+8)*5/8)らしい?多すぎても少なすぎても効果が薄くなる
-XX:ConcGCThreads=4 マーク付けを行うスレッド数 デフォルトは(ParallelGCThreads+2)/4。多ければ多いほど強い?
-XX:+DisableExplicitGC System.gc()によるメジャーGCを無効化する
-XX:+UseGCOverheadLimit GCのオーバーヘッド制限を無効化する GC overhead limit exceededが発生しなくなる

参考文献

2021-06-29追記

また ServerHangWatchdog に捕まった。なんで Thread.sleep() !?

java.lang.Error: ServerHangWatchdog detected that a single server tick took 360.00 seconds (should be max 0.05)
    at java.lang.Thread.sleep(Native Method)
    at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:530)
    at java.lang.Thread.run(Thread.java:748)

日本語ドメインからローマ字ドメインへリダイレクトしようとしたときに詰まった話

TL;DR

Webサーバで日本語ドメインの設定を行う際はPunycodeで取り扱う。

背景

年始にフリーランスをしていくにあたって「きたきつね.com」という屋号で開業したので、せっかくならドメインとって今のサイトにリダイレクトしよう!って思ったところから始まりました。

ドメイン取得

本当はローマ字ドメインにしたかったのですが、kitakitsune.comkitakitune.com は使われていたので、せっかくだし試しに日本語ドメインを使ってみよう!って思って きたきつね.com を取得してみました。

DNS設定~既存サイト(Apache2)の設定したが……

ネームサーバを利用しているものに向けて、Aレコード書いて、mod_rewriteを使って既存のドメインに向ける設定を行いました。 なぜかRewriteを無視してきました。 間違いないはずなのにどうして……(その後半年くらい放置しました)

再挑戦

そしてついこの間、屋号を名乗るタイミングがあったため再挑戦することにしました。 マルチバイトだとApache嫌がるのかなぁ……とか思いながら日本語ドメインについて調べてたところ、ついに見つけました

ネームサーバへの問い合わせやWebへのアクセスはソフトウェア側で、従来のドメイン名と互換性のあるPunycode形式で行われるので、サーバなどは従来のものをそのままご利用いただけます。 - 日本語JPドメイン名とは - 日本語.jp

これもしかしてPunycodeで記述すればうまくいくのでは……うまくいった!!!!

晴れて きたきつねどっとこむきたきつね.com でもアクセスできるようになりましたまる

SSL証明書が無いので警告出る...…vulpes-vulpes-schrencki.comと更新月合わせたいから待ってね……

クラウド未経験だけどAWS 認定 クラウドプラクティショナーを受験してみた

経緯

実は4か月と少し前、2020年12月に前職を退職しました。その後どうしようかぼんやりしながらほぼ肩書だけのフリーランスみたいなことをしていました。
そして最近やっぱりクラウド業界に飛び込んでみたい!ということでとりあえずクラウドの先駆者にしてシェア1位のAWSを始めて見ることにしました。
とりあえずその辺にあったチュートリアルを動かしてみるものの、あらかじめ記述されているコードを実行するだけなのでイマイチ実感が沸かない……(入門には良さそう)。 そこで、AWSについての学習、就活の視野を広げる、就活での武器を手に入れるといった理由からまずは認定資格を取得しようと考えました。

AWS 認定 クラウドラクティショナーについて

AWS 認定 クラウドラクティショナー試験は、AWS クラウドの知識とスキルを身に付け、全体的な理解を効果的に説明できる個人が対象です。
(中略)
AWS 認定 クラウドラクティショナーは、アソシエイト認定または専門知識認定を取得するために推奨される任意のステップです。 - AWS 認定 クラウドプラクティショナー - AWS

他の試験を受ける前に、AWSの基礎を知っておきましょうみたいな感じらしいです。
内容についてはクラウドの特徴と利点、AWSの各サービスについての説明、コスト・料金・請求について、アカウントの管理、ドキュメント・サポートなどなど技術的なことからマネジメントや営業まで幅広く、AWSとはなんたるやを知るにはもってこいの資格です。

前提知識

公式サイトによると

  • テクノロジー、マネジメント、販売、購買、ファイナンスの分野で最低6カ月のAWSクラウドの使用経験がある
  • IT サービスのベーシックな知識と、AWS クラウドプラットフォームにおけるそれらのサービスの使用に関する知識がある

とのことです。私はどちらの経験・知識も持ち合わせていませんでしたが、前職で一般的なSI案件の業務を2年間、情シスの業務を2年と8か月従事していました。開発だけでなくサーバ・NASの選定・セットアップ、スイッチのセットアップ、仮想基盤構築、ストレージ・SAN構築などの仮想化も齧っていました。また、結構前ですが、IPA応用情報技術者試験を合格しています。

勉強方法

入門書

まずは以前翔泳社のキャンペーンで安く入手していたAWSクラウドの基本と仕組み - Amazonをさらっと読破しました。

一般的なクラウドコンピューティングの概要、AWSクラウドの基礎知識、概念、テクノロジー、セキュリティ、代表的なサービスなどについて理解できる。付録では、AWSの料金、セミナー・イベントおよびトレーニング、AWS認定、アカウント設定などについて解説。

一般的なクラウドの特徴・利点、AWSクラウドの特徴・主要サービスなどを概要レベルで網羅しています。一方でそれぞれの内容についてはあまり深く掘り下げていないため、これ一冊では試験の勉強をするには不足していることが多いです。入門、きっかけにはよいと思います。
1日2章ずつ、3日に分けて読みました。

公式のe-Learning

公式で公開されているクラウドラクティショナーレベルの知識について学習できるコースを受講しました。 - AWS Cloud Practitioner Essentials (Japanese)(日本語字幕版) - AWS training and certification

基本的には各単元について動画を見て、そのあとテキストを読むといった形式です。動画は3人の講師が交代で英語で講義を行いますが、日本語字幕もあるので安心です。途中ユニークな演出もありなかなか面白いです。ものによってはより詳しく解説しているページへのリンクが張ってあったりします。

自分はとにかく早く受験したかったため、コースの動画およびテキストをひととおり1周するだけにしました。よりしっかり勉強するのであれば、コース内にあるリンク先についても目を通しておいたほうが良いと思います。
コースの最後に認定テストがあり、本番と同じく65問の問題を解きます。この時のスコアは83%でした。
確か4,5日くらいに分けて受講しました。

模擬試験問題

模擬試験問題集(この問題だけで合格可能!AWS 認定クラウドプラクティショナー 模擬試験問題集(7回分455問) - Udemy)を購入し、ひとまずクラウドラクティショナーレベルの試験5回分について解きました。こちらは基礎レベルの問題が2回分、あまり出題されない高難易度な応用レベルの問題が3回分、アソシエイトレベルの問題が2回分あり、本番に近い内容が収録されています。

基礎レベルはまぁまぁでしたが応用レベルはぼろぼろでした。また、基礎応用ともに知らないサービス、単語、内容がたくさん出てきてかなりあせりました(おそらく前述のセミナーを隅々までしっかり受講していれば網羅できていたかと思います…)。簡単な解説もあるため、知らなかったものについては解説で知識を得たり、AWS公式サイトで確認しました。不足していた分を補ったあともう一度試験を受け、すべての試験で90%以上のスコアをとれるようになりました。
2日で1周、不足していた内容を勉強しなおしてまた2日で1周で合計2周しました。

受験した感想

合格をいただきました……はずです(正式な結果は後日届くようです)(一応画面ではそう表示されてたはずですが正式な結果が来るまでちょっと不安です)
本番でも「ん?」となる問題が何問かあり、やはり勉強不足を少し感じました。実はあまり試験勉強が得意ではなく、無理のないようにゆっくりさくさくやっていましたが、もう少しじっくり勉強したほうが良かったかな……ってなりました。

一応「AWSクラウドでしょ?知ってる知ってる」みたいなところから成長はできたかな……と感じます。今までは実際に触ってみようにも行き当たりばったり過ぎて結局何もできなかったところがあるので、これから次の段階(おそらくソリューションアーキテクト アソシエイト)に向けて少しずつ実際に触って動かしてみようと思います。

TypeScript+three.js+three-vrmでVRMをブラウザ上でレンダリングする

経緯

よくうちの子の資料として VRoidHub に VRM モデルをアップロードして見てもらっていた。しかし第三者にダウンロードされることがあるということで、現在は暫定的に静止画のみになってしまいました。
これでは資料として使えないので、自前でVRMモデルを表示できるツールが必要になったのがきっかけです。

対象読者

  • npmを触ったことがある
  • TypeScriptに興味がある
  • VRMに興味がある

環境構築

使用するパッケージ

今回使用するパッケージは以下の通りです。

  • typescript: 4.2.3
  • webpack: 5.28.0
  • webpack-cli: 4.6.0
  • webpack-dev-server: 3.11.2
  • ts-loader: 8.1.0
  • three: 0.127.0
  • @types/three: 0.126.2
  • @pixiv/three-vrm: 0.6.2

パッケージインストール

npm init
npm i -D typescript webpack webpack-cli webpack-dev-server ts-loader @types/three
npm i three @pixiv/three-vrm

package.json は以下の通りになります。 scirptsにコンパイル、ビルド、開発サーバ起動コマンドを追記しています。

{
  "name": "vrm_viewer",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "tsc": "tsc",
    "build": "webpack",
    "watch": "webpack --watch",
    "start": "webpack serve --config ./webpack.config.js"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "ts-loader": "^8.1.0",
    "typescript": "^4.2.3",
    "webpack": "^5.28.0",
    "webpack-cli": "^4.6.0",
    "webpack-dev-server": "^3.11.2"
  },
  "dependencies": {
    "@pixiv/three-vrm": "^0.6.2",
    "@types/three": "^0.126.2",
    "three": "^0.127.0",
  }
}

TypeScript コンパイラの設定

コンパイラの設定ファイル(tsconfig.json)を作成します

npx tsc --init

tsconfig.json を編集します

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "lib": [
        "dom",
        "es2019"
    ],
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": [
    "./ts/**/*.ts"
  ]
}

参考 tsconfig.jsonの全オプションを理解する(随時追加中) - Qiita @ryokkke

webpack の設定

webpack.config.jsを作成し、編集します。

const path = require('path');

module.exports = {
    // production: 最適化
    // development: ソースマップ有効
    mode: 'development',

    // エントリーポイント
    entry: {
        main: './ts/index.ts'
    },

    output: {
        path: path.join(__dirname, "dist"),
        filename: "index.js"
    },

    devtool: 'source-map',

    // 開発サーバの設定
    devServer: {
        open: true,
        openPage: "index.html",
        contentBase: path.join(__dirname, "dist"),
        watchContentBase: true,
        port: 8080
    },

    module: {
        rules: [{
            // *.tsを
            test: /\.ts$/,
            // TypeScript コンパイルする
            use: 'ts-loader'
        }]
    },

    // import文で *.ts ファイルを解決する
    resolve: {
        modules: [
            "node_modules",
        ],
        extensions: [
            '.ts',
            '.js'
        ]
    }
};

コーディング

Hello, World

ひとまず構築した環境でコーディングできるか試してみます。
./ts/index.ts を作成し、下記のコードを記述します。

window.addEventListener("DOMContentLoaded", () =>{
    console.log("Hello, World");
});

次に下記コマンドを実行し、./dist/index.js が生成されることを確認します。

npm run build

./dist/index.html を作成し、下記のコードを記述します。VSCodeなどEmmetが使えるエディタであれば、html:5[TAB]と入力すると一発でひな形が作れます。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="./index.js"></script>
    <title>Document</title>
</head>
<body>
</body>
</html>

webpack-dev-server を使用して動作確認します。下記コマンドを実行します。

npm run start

するとブラウザが立ち上がります。今回はDOMロード時にログを出力するという動作なので、F12キーで開発者ツールを開き、コンソールを確認すると、正しく出力されているはずです。

f:id:tuki9ko:20210406135205p:plain
helloworld

three.js を使用する準備

それでは本題のVRMモデルをブラウザ上でレンダリングするためのコードを書いていきます。

import * as THREE from "three";
import { OrbitControls } from "three-orbitcontrols-ts";

// レンダラーの設定
const renderer = new THREE.WebGLRenderer({
    antialias: true,
    alpha: true,
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);

// カメラの設定
const camera = new THREE.PerspectiveCamera(
    35,
    window.innerWidth / window.innerHeight,
    0.1,
    1000,
);
camera.position.set(0, 1.1, 3);

// カメラコントロールの設定
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0.85, 0);
controls.update();

// シーンの設定
const scene = new THREE.Scene();

// ライトの設定
const light = new THREE.DirectionalLight(0xffffff);
light.position.set(1, 1, 1).normalize();
scene.add(light);

// グリッドを表示
const gridHelper = new THREE.GridHelper(10, 10);
scene.add(gridHelper);
gridHelper.visible = true;

// 座標軸を表示
const axesHelper = new THREE.AxesHelper(0.5);
scene.add(axesHelper);

// レンダリング
tick();

function tick(){
    requestAnimationFrame(tick);
    renderer.render(scene, camera);
}

ここまででいったん実行すると、床面にグリッドと原点に座標軸が表示されます。基本的なマウス操作もすることができます。

  • 左クリック: カメラ回転
  • 右クリック: カメラ移動
  • ホイール: カメラズーム

f:id:tuki9ko:20210406135506p:plain
X-Z平面グリッドと原点の座標軸が表示された

VRMレンダリング

それでは実際に VRMレンダリングします。今回はキツネツキ様の右近ちゃん(VRoidHub)のVRMを使用します。 ktntk_ukon.vrmにリネームして./dist/modelsに配置します。

VRM は GLTF というフォーマットをベースにしており、three.js では GLTFLoader を使用して読み込みます。GLTFLoader は Promise に対応していないため、Promise を返すラッパークラスを作ります。

import { GLTFLoader, GLTF } from 'three/examples/jsm/loaders/GLTFLoader'

export class PromiseGLTFLoader extends GLTFLoader{
    promiseLoad(url: string, onProgress?: ((event: ProgressEvent<EventTarget>) => void) | undefined){
        return new Promise<GLTF>((resolve, reject) => {
            super.load(url, resolve, onProgress, reject);
        });
    }
};

読み込んだ GLTF をもとに VRM インスタンスを作成し、シーンにロードします。

// モデルをロードする
async function loadModel (url: string, onProgress?: (progress: ProgressEvent<EventTarget>) => void){
    try{
        const loader = new PromiseGLTFLoader();
        const gltf = await loader.promiseLoad(
            url,
            progress =>{
                if(typeof onProgress === 'function') onProgress(progress)
            },
        );
        const vrm = await VRM.from(gltf);
        scene.add(vrm.scene);
        vrm.scene.rotation.y = Math.PI;
    } catch(e){
        console.error(e);
    }
}

最後に作成した関数に URL を渡してあげれば VRM が描画されます。

loadModel(
    './models/ktntk_ukon.vrm',
    progress =>{
        console.log(
            'Loading model...',
            100.0 * (progress.loaded / progress.total),
            '%',
        )
    }
);

f:id:tuki9ko:20210406135601p:plain
右近ちゃん(かわいい)

今回はひとまずここまでとし、BlendShape やポーズの変更についてはまた別な記事にまとめようと思います。

近況とか

今年もアドベントカレンダーの季節で、苫小牧高専のやつは例年通り行っているようですが、今年は書けなさそうなので見送りです。(老害だしね(?))

 

実は直近で大きなイベントがあり、多分ひと段落ついたらそれについてここだったりTwitterだったりで報告すると思います。

 

書くことないならないなりにそろそろ技術ブログの一本でも書いたらどうなのっていうのはそれはそうなんですけど……もう少ししたら本気出す。