gombeのブログ

マイコンの電子工作系PIC32/KiCad/C/C++/3D/

esp32でMMDで躍らせる

esp32でMMD? どうやって?

esp32はおなじみの方が多いと思いますが、wifi, bluetoothモジュールの中にdual coreのプロセッサが入っています。これをうまく使って、ソフトウェアレンダリングで3Dモデルを躍らせようというプロジェクトです。

www.youtube.com

github.com

はじめに

esp32に含まれているCPUにはFPUというFloating Point Unitがあります。これにより浮動小数点を含む3Dの演算をまあまあの速度で動かすことができます。ソフトウェアレンダリングはラスタライズをCPUで行います。単純作業の繰り返しになるまで三角形を分解してから最後に描画するといった流れでしょうか。

では実際にどのようにして躍らせているのでしょうか?

実は踊ってるように見えて実際踊ってるのですが、頂点グループをまとめたボーンを動かしていることにすぎません。このような技術をSkeletal animationと呼んだりします。MMDではFK, IKと物理演算をうまく組み合わせることでリアルな動きを表現しています。

ラスタライズ(レンダリング

レンダリングは3Dにおいて最も基本となる部分です。目視で確認するためには最も早く実装すべき機能でしょう。すでにラスタライズについては以前の記事で書いていますのでここでは深く触れませんが、ラインスキャン法で3D描画しています。z-bufferの前身ともいわれる方法で、z-buffer法より少ないメモリでそれに近いパフォーマンスを引き出すことができます。ただ、スキャンライン法は1ラインずつの処理ですが、それではさすがに遅い場合は複数ライン描画するといった解決方法があります。もちろん全画面描画できればz-buffer法と同じでパフォーマンスも高いのですが、メモリが足りなくてそんなことできないので、24行など細かい単位で転送をかけます。z-bufferについては1転送分のみ確保すればいいのですが、video bufferについては2転送分確保します。これはDouble bufferingするためです。描画タスクと並行して内部描画が可能になります。

FK, IK

FK, IK について深く知らない方も多くはないでしょうが、私たちの体はFK、IKによって制御されていると説明しても大きな不自然な点はないことと気づくと思います。

FKはForward Kinematicsと呼ばれている技術で、私たちの骨の接続に注目すると木構造になっていることから親ボーンが動くと子ボーンも動くことを計算上で実現する方法です。実際私たちの体もひじを動かすと、それにつながっている手首も動きますね?正確に言うならば、手首の位置に注目すると、ひじの位置を中心とした回転運動をするはずです。これらの運動を算術的に解き、変換行列を求めることでFKを実現します。

IK (Inverse Kinematics)も自然な考え方です。私たちの頭の中で足の動きを考えてください。ここに足のつま先を持ってくる→足首の関節→ひざの関節→...と意識が伝搬して足が動くでしょう。これはIKの最も基本となる考え方です。私たちはIKにより足を動かしてるといってもおかしくはないでしょう。わざわざ毎回膝をどれだけまげて歩くなどは考えていないと思います。つまりIKはFKと逆の手順で膝や足首、足の運動を解くことができます。目標に対してどのように運動するかを考えることでその姿勢を求めることができるのです。

実はIKの実装にはFKの実装が欠かせません。今回はIKをCCD-IKを用いて実装しました。CCD-IKの最も基本はFKに基づくIKへのフィードバックです。実際に動かしてみてそれを基にIKにどう反映するのかを考えます。実際にモデルにてIKをどう応用するのかは難しい分野で、動きが自然に実現される必要があります。実際、これらの機能は負荷が大きいことでも知られています。詳しいtechnicalな部分はまたあとで解説しましょう。

まとめとesp32 for MMDの展望

いまはようやく20fpsを超える程度で動き出したばっかりです。実際3Dを動かすためには様々な処理が必要です。アニメーションを通してどのようにMMDモデルを動かすかのみではなく、一般的にコンピュータグラフィックスという分野がどれだけ幅に広がっているのか、レンダリングのみではなく、FKやIKも含まれますので、アニメーションの基礎を学ぶことができました。

で、これからどうしていくかは不透明ですが、実装したい項目はいくつかあります。

- 表情の追加(歌わせたいって言われた)

- 物理演算(性能的に厳しそ)

- 高速化(どうやるかはわからん。Fix-Point演算やTextureのAffine化の対応など)

- 複数モデル対応(データ構造どうしようって思って。z-bufferと違ってすべての頂点を解析する必要があるから。。)

IoTに適したMMUに対応したOS、LiteBSD

IoTが広まってきている中、esp32やraspberry piなど様々なプラットフォームでIoTが加速しています。今回はその第3の選択としてLiteBSDを紹介します。

まずは比較から。

f:id:electgombe:20180719172914j:image

LiteBSDはUnix系のOSで、4.4BSDとモダンなシステムです。RaspberryPiではLinuxが載っており、IoTシステムの先駆けとなり、esp32は小型なwifiとして組み込まれることも多くなりました。LiteBSDではPIC32MZのチップ+SDカードの構成で動作します。省スペースに機能を集約でき、周辺機器が豊富なPICを搭載することで低コストにIoTデバイスを作ることが可能になります。OSは迅速なソフトウェア開発を強力に支援します。

f:id:electgombe:20180718075511j:image

OSが載っていることは非常に重要です。ソフトウェアを開発する上で、重要なツールセットが一つになっているだけでなく、マルチプロセス内でメモリ保護機能が強力に働くからです。MMUユニットはメモリのアクセスを制限します。アクセス違反を検出することができます。メモリ空間を効率良く利用できます。

f:id:electgombe:20180718173131j:image

ここでPIC32MZシリーズである理由について少し述べましょう。PIC32MZにはマイコンならではの豊富なペリフェラルと強力なMMUがついている珍しいマイコンであることが理由です。※最近は秋月電子でも144/100/64ピンのパッケージが出ましたね。SPI/UART以外にもADCも入っていて、汎用IOも多くあります。これが組み込む上で、接続を容易にします。

つまりRaspberry PI、ESP32などではIOの制限が厳しい一方でPIC32MZシリーズではそのような制約がゆるいということです。

これからも様々なデバイスがIoTに結びつくようになると考えられます。適材適所でデバイスを選定することが大事だと考えます。

※ esp32にもついてますが、簡易的で全ての空間をサポートしきれません。ましてやアプリケーションを動かせません。PIC32MZには完全なMMUユニットがついてるのです!

Native-C やること(やるとは言っていない)

またこのネタですが、以前から気がついてたのですが、システム関数をコールするときにブロッキングな関数(例えばgetchar)だと動作を割り込ませることが難しいことが分かっています。

回避としてはノンブロッキングな関数のみとし、その他を静的ライブラリとしてリンキングするのが良さそうです。ただユーザーエリアが少なくなってしまうのでそのあたりの関係上ブレークチェック(MachiKania方式)も合わせて持っておくといいかもしれません。

 

そのほかの実装項目もたくさんあります。

## float周りの実装

math, printfの対応など 現在はまだ四則演算しかできないし、表示もできません。FPUないから遅いけど、なんんちゃってfpuのエミュレーションでもいいなと考えてるところ。

CP1動かそうとして例外発生>アセンブリで書いたエミュレーションコードでエミュレートし、その後復帰>OK何もなかった。いいね?

ハードウェアでFP対応してると高速ですが、対応してなくても実行は可能です。いつか実装するかも?

 

## ファイルIOの改善

jsonパーサーを用意してセーブやロードを容易にする。 構造体は実装済です。なのでバイナリレベルのロードはできるのですが、テキストで扱えるようにしたいです。エディタで中身見れれば随分と楽ですので。

 

## exampleの追加

ゲームばっかりでなく、浮動小数点数など生かした3Dエンジンを組み込んだり、でもやっぱり容量が足りなそうだからcrossコンパイルかハイブリッドになりそう。

ちなみにクロスコンパイルだと容量は最大1/10くらい小さくなります。このシステムもぎゅうぎゅうに詰めてこのサイズですから。グラフィックモードだとかなりきついですね。

 

## asのバグ修正

なんか最適化レベル上げた時にバグる現象を直さんと。どこでバグってるのかもまだわかってません。 最適化レベルで挙動が変わる系のやつほんと嫌い。

Native-C 開発状況と今後について

Native Cとは

以前少し紹介したCのオンボード開発環境です。KM-BASICのCバージョンですね。最近も開発はしています。時間の確保が難しいのですが、がんばります。

システム関数群の実装

ファイルアクセス等も割り込みを用いたシステムコールを用いることが多いのですが、今回はシステムサイズや応答の問題で、関数呼び出しによる実現をしています。次期のチップはもう少し大きいのでBSDライクにできればいいのですが、、その時はまたいろいろ考えましょう。

強制終了の実装

開発中にアプリケーションが無限ループで止めたい場合が多々あります。Native系だとこれらの場合に止める手段として、割り込みを用いることが一般的です。psがないので常にフォアグラウンドの処理を止めればいいのですが、この処理を止めるにしてもシステム関数内で処理が止められると例えばFILEの入出力中だとファイルが破壊されてしまう可能性があります。これを防ぐためにRAM実行中のみシステム割り込みによる終了ができるようにしました。EPCが割り込み前のPCですが、これがRAMの物理アドレス内かどうか判定します。強制終了はEPCを強制的に終了アドレスにセットすることによって実現します。この中ならファイルが開いているときは全てのファイルをシステムで閉じればいいので安全に利用ができます。

Graphics modeについて

以前からシステムにはあったのですが、関数テーブルに追加して利用可能になりました。このビデオシステムではMachiKania同様にTextとGraphicモードがあります。これらのモードはそれぞれ長所短所があって、Graphicモードは当然ながら多彩なグラフィック表現が可能で、その分グラフィックテーブルとして28KB、RAMが使用されます。TextモードではPCGを利用することで図の表示が可能ですが、8x8(8x6にも設定可能)のマスごとにしか色が設定できず、更にPCGは256種類までしか設定できません。RAMはPCG利用時は2KB消費します。(ユーザーフォント領域)

サンプルとして動画プレイヤーを作りました。

続きを読む

あけおめことよろ 新規プロジェクトの紹介

あけましておめでとう🌅⛩🎍

今年もよろしくお願いします。

最近新しいプロジェクトを始めました。MachiKaniaのハードでCコンパイラテキストエディタなどを乗せたネイティブコンパイラの開発をしています。

このシステムは編集、コンパイルや実行に必要な一通りのアプリケーションを搭載しています。ここで一連のフローを示します。

1、プログラム(.c)を作成

2、コンパイルする(.c→.s)

このコンパイルアセンブリリストを生成します。(.s)

3、アセンブリする。(.s→.o)

2、3はすべてのソースコードに対して行います。

4、リンクする(全ての.o→実行ファイル)

5、実行(a.outフォーマットローダを使う。実行に関して、ここで解説しています

実行時の各セクションも確認しましょう。

.text

実行コードが入ります。静的です。

.data

0以外の初期値を持つデータ(変数)がここに入ります。静的です。

.bss

0に初期化するデータ(変数)がここに入ります。実行前に初期化します。静的です。この領域はa.outフォーマットの中には入りません。dataのサイズを減らすためのセクションです。

.heap

heapに使います。下に向かって伸びます。動的に変わります。

stack

stackに使います。上に向かって伸びます。動的に変わります。

このstackを使い果たすとstackオーバーフローとなり、heapを侵食します。MMU(memory management unit)があればいろんなことできそうなんですがね、、まあないものはしょうがないです。

RetroBSDでは128kのRAMを搭載しているので様々なアプリをRAMで実行できるのですが、今回のプロジェクトは64kBをターゲットとしているので結構厳しいです。コンパイル時はFlashで実行します。(つまりシステムにビルドインする)動作も50MHz付近なのでshもFlashで動かす予定です。(スワップ実装するのも大変だから、、)

ファイルシステムもFatファイルシステムにしました(FatFS)。内蔵Flashのストレージも作ってます。(80kBだけど減るかも、、それと大きいものコンパイルするときはアセンブリリストが長くなり、容量足りなくなるから、そういう場合はSDカード用意してね)

ってことで現在テトリスまで動きました。本家のソースコードはほとんどいじってません。(PS2キーボード化と、乱数を実装と音声部削除)コンパイルはオフスクリーンで3秒位です。RAMは13kBくらいしか使っていないので残り45KBもあります。音声など実装できると思います。Tスピンなどもできるかもしれません。ハードドロップ、ホールドも欲しいなぁ。

http://www.ze.em-net.ne.jp/~kenken/tetris/index.html

操作はキーボードに移しました。

ソースコード

Hackadayにも載せました

SPIx3でNTSC

PIC32でNTSCシグナルの生成に成功(?)したのでメモ。

こちらがけんけんさんがやってくださったものです。よく書けてるのでこっち見てください。

PIC32MK でNTSCシグナルを生成するとき、普通だったらタイミングに沿ってCPUで出力します。ただ、リソースの問題で、CPUの使用率は60%を越えるため、アプリケーションに割ける時間が少なくなってしまいます。

今回はSPIという通信規格で、シグナルを生成することに成功しました。SPIは、本来クロックに従ってデータ出力をします。その際、1ビットずつシフトするため、負荷が理論上は1ドット16サイクル3バイト出力と、減るはずです。今はまだCで書いているため重いですが、最適化次第でさらに軽くなると思います。

仮に1ビットだとすると以下のように使います。

続きを読む

マイコンで3D表示(図解)

3D表示に関してここにまとめておきます。

1、座標変換

3DのCGの世界ではカメラを回すのではなく、世界を回します。また、世界を歪めます(透視投影)。しかし線形変換では透視は与えられないので別の方法を使います。

透視はzで割ることで与えられます。しかし実際にはzは重なり計算に用いられるので代わりにwを使います。そのため通常はカメラの位置は焦点になります。投影行列*カメラ行列が基本的な変形式となり、そこに必要に応じてオブジェクト回転などを入れます。

ここで透視投影と平行投影の違いを見てみましょう。

平行投影の場合、以下のような表示となります。物体の距離にかかわらず等しい大きさで描画されます。これはzの値を破棄することで投影することができます。

また、今回は透視投影をします。これは完全にカメラに視点をもたせ、そこからの空間の広がりを描画します。

ここで描画空間も確認しておきましょう。描画空間とはこの中の点を描画する、すなわちクリップする範囲を定めるための空間です。視点に近い方ではnearを用いて遠いところではfarでクリッピングします。

2,描画の方針

上の図でも説明がありますが、少ないメモリで3D計算をする場合はzバッファの値も少ないため、工夫して節約する必要があります。そのため、奥行きの情報、またピクセルの情報は一ライン分のみ保持して、一気に描画します。実際には、ピクセルの情報のみ2ライン分保持することもあります。(ダブルバッファリング)

zスパンの計算は普通に奥行きを計算して手前側を描画するだけに過ぎません。このため手前から描画することで二重の描画を防ぎ、速度を向上させることもできます。

3、三角形の形をあらかじめ計算

三角形を描くためには、あらかじめ辺の傾き(増加量)を計算しておくと高速です。

開始点vx1,vx2を一定の増加量ずつ足しあわせていきます。三角形には変曲点がありますのでその文を考慮しておきましょう。この場合ではvx2が途中で変化します。忘れずに実装しましょう。同様にzバッファやw補間、uv補間もします。

三角形内も補間したい場合は、まず辺で補間し、その両端を更に補間をかけます。いずれも増加量を予め計算しておくことで高速に求めることができます

テクスチャマッピングについて知るべきこと

テクスチャマッピングで何を注意する必要があるかというと歪みの補正です。下の図を見てください。

これを三角ポリゴンに分解して、以下のようになります。

気づいていらっしゃるかもしれませんが、手前の三角系のほうが、大きく表示されています。これは点を保管するだけでは、奥行きの情報まで補間しないと正しいテクスチャマッピングができないということを暗示しています。そのためマッピングの際は予め1/wを計算しておき、その値で描く要素をかけて、その1/wを補間した結果で割ることで補正できます。

https://github.com/elect-gombe/esp32_mmd