Archives

September 2007 の記事

Bluetooth Keyboard RBK-2000BT2 の日本語キーボード化カスタマイズを
下記エントリで紹介しました。
em1key Bluetooth keyboard RBK-2000BTII の日本語カスタマイズ例
さらにカスタマイズを行い、任意に ON/OFF できる設定ファイルを
作ってみました。

scriptcommand_RBK2K_JP.txt

上記ファイルをダウンロードしたのち、ファイル名を scriptcommand.txt
にリネームして em1key でご利用ください。

ファイルの 290行目 以降にある CSW_~ のシンボルが、各種設定の
有効・無効を切り替えるスイッチになっています。TRUE で有効、
FALSE で無効です。

このファイルは RBK-2000BT2 向けの設定のみ抽出しているので、
それ以外の余計な機能は含まれていません。機種を問わず利用しやすく
なっていると思います。またカスタマイズの参考用にどうぞ。


前回の記事からさらに、下記のカスタマイズが追加されています。
もちろん個別に ON/OFF できます。

● SHIFT+[0] で '_'

SHIFT+[0] キーで '_' を打てるようにします。


● [/?] を本来の位置に配置する

[/?] キーが遠くに離れているので、つい '/' を打とうとしてカーソルが
上に移動してしまいます。そこで次のような置き換えも行ってみました。

キーボード右下のキー群
[.>] [↑] [Sft] [/?]
[←] [↓] [→] [Del]

  ↓

[.>] [/?] [Sft] [\|]
[←] [↓] [↑] [→ ]

これで、'/' や '?' を本来のキー位置で打てるようになります。
その代わりカーソルキーの配列が横一列になってしまいます。
でも実はこれ、vi カーソルと全く同じ並びなので
個人的にはかえって都合が良かったりします。

無くなった [Delete] キーは Ctrl+[BS] で入ります。


作者が実際に使っている設定は、em1key デフォルトの
scriptcommand.txt に RBK2K 用のキー入れ替えを追加したものです。
内蔵キーボード用の設定と外部キーボード用のカスタマイズを共存
させています。こちらもアップロードしておきました。
追加部分はファイルの最後のみで、完全に独立しています。
個別に ON/OFF できるようなスイッチは特に設けていません。
scriptcommand_default_RBK2K_JP.txt


日本語キーボード化の次は 親指シフト 設定も試してみよう、
と思っていたら、すでに遠藤さんが設定を作成しており
こちらのエントリで公開されていました! たいへんありがたいです。

遠藤諭の東京カレー日記 Advanced/W-ZERO3[es]で親指シフト(追加)


DX10/D3D10 では Effect(fx)/HLSL のプリプロセッサが拡張されている
ことを下記のエントリで書きました。
Direct3D 10 HLSL の関数型マクロ定義

その後もう少しだけ調べてみましたが、残念ながらまだ C/C++ と
完全互換というわけにはいかないようです。
もしかしたら BOOST_PP でも動くんじゃないか、と思って試したら
甘かったです。


●引数無しの関数型マクロが定義できない

#define NAME_A() 0x40f

上記のような引数無しの関数型マクロ名がエラーになります。
ダミーでも NAME_A(_r) のように何か引数を与えないとだめでした。


●意味のない # 行が無視されない

例えば単独で出てくる行頭の '#' のみの行がエラーになります。


●#if 等で bit 演算できない

#if BITFLAG & 4

こんな形の bit 判定はエラーになります。| や ^ 等もだめ。
一般的な四則演算や論理演算はもちろん大丈夫です。


Effect(fx)/HLSL 単体でここまで凝った使い方をすることはあまり
無いかもしれません。
ただ、定数シンボルなどを共通化するために C++ ソースとヘッダを
共有する可能性はあるので、この辺の互換性には注意しておきたい
ところです。


内蔵 Preprocessor は動的コンパイルでは必要になります。
でも、もし完全に事前コンパイルしてもいいのならば、普通に C++ の
プリプロセッサを通した方が良いかもしれません。
従来 DirectX8(vsa/psa)~DirectX9 の時はいろいろ機能的に
物足りなかったので、例えば下記のような感じで Makefile を
作っていました。

# Makefile
# nmake 用

PROFILE	= fx_4_0
FXINCLUDE = -I.

.fx.fxo:
	cl /E $< $(FXINCLUDE) > $*.tmp
	fxc /T$(PROFILE) /Fo$@ $*.tmp

.SUFFIXES:	.fx .fxo

sysdef.fx というファイルが存在する状態で sysdef.fxo を作りたければ
コマンドラインから

nmake sysdef.fxo

とキータイプするだけです。Makefile はカレントに必要です。
nmake.exe や cl.exe、fxc.exe 等にパスを通しておく必要があります。

VisualStudio は Common~\IDE と VC\bin の2箇所にパスが通って
いれば OK です。例えば 32bit(x86) + VS2005 でパスを通しておく
フォルダは下記の通り。

%DXSDK_DIR%Utilities\Bin\x86
C:\Program Files\Microsoft Visual Studio 8\Common7\IDE
C:\Program Files\Microsoft Visual Studio 8\VC\bin

VisualStudio にこだわる必要は無く、gcc など他の C/C++
プリプロセッサやコマンドでも全く同じように使えます。
Makefile の書式は make コマンドによって若干異なります。
ちょっと混用して gmake + cl だとこんな感じでしょうか。

# gmake の場合
PROFILE	= fx_4_0
FXINCLUDE = -I.

%.fxo: %.fx
	cl /E $< $(FXINCLUDE) > $*.tmp
	fxc /T$(PROFILE) /Fo$@ $*.tmp



SDK マニュアルを見ていて HLSL Grammar の Operators に
'##' や '#@' という演算子を発見しました。
これは本来 #define マクロの引数に適用される演算子です。

これまで Effect fx/HLSL のプリプロセッサは非常に制限された
ものだと思っていました。例えば

・#define も基本的に定数や #if 用。
・引数を伴う関数型のマクロ定義は出来ない。

などなど。
もしやと思って実際に試してみたら、なんと関数型の引数を持ったマクロも
普通に定義して使うことが出来ました。

例えば上記演算子を使ってみるとこんな感じです。

#define	VARNAME(_n,_m)     _n##_m
#define	SEMANTIC_TEX(_id)  TEXCOORD##_id

float VARNAME(light,ambient)= 1.0f;

void VS_Main( uint vid : SV_VertexID,
			out float4 opos : SV_POSITION,
			out float2 ouv : SEMANTIC_TEX(0) )
{
  :

この場合 VARNAME(light,ambient) は「lightambient」と
連結された変数名になるし、SEMANTIC_TEX(0) は TEXCOORD0 に
置き換わります。

#@ は文字定数 '~' への変換に相当します。例として次のように
記述してみます。

#define LCHAR(_n)	#@_n
int id= LCHAR(Z);

LCHAR(Z) は 'Z' に置き換わりました。つまり

int id= 'Z';

と等価です。

引数の文字列定数化を行う、単一の '#' 演算子はありませんでした。

使えないと思っていたのでかなり驚きました。思い込んでいただけで
しょうか。
SDK Sample fx から "define" を検索しても、やはり引数を持った
関数型の define は全く使われていないようです。

試しに DirectX SDK 2006 October の fxc.exe (DX9向け) で実行したら
エラーになりました。
同じ 2006 October の fxc10.exe ではコンパイルが通ります。
D3D10 で HLSL 用プリプロセッサの機能拡張が行われたことは間違い
なさそうです。

Preprocessor は HLSL コンパイラ DLL 呼び出しとは別なので、/LD
オプションは使えませんでした。
D3D10 core API 側の Preprocessor が呼ばれると拡張タイプになり、
D3DX9 側の Preprocessor だと従来どおりの簡易型になるのではない
かと考えられます。


結論
・Direct3D10 の HLSL/Effect(fx) では、従来使えなかった関数型の
 マクロ定義が使える
・さらにマクロ引数の演算子 ## と #@ も使える


パナソニック、「Let'snote」全モデルでSanta Rosa搭載~Turbo Memory搭載「R7 プレミアムエディション」も

最初はそろっていた番号が徐々にずれ始め、
ばらばらにカウントされていた番号が
7 でまた一斉に揃う。
これを見てすぐ歴代 DirectX API の番号を思い浮かべた人は
たぶん ほとんどいないと思います。


2007/09/27
帯域不足

同じ周波数帯を使う Bluetooth と 2.4GHz の無線LAN (11b/g) では
干渉があることは良く知られています。マウスなど他にも同じ周波数
を使う機器もあるし、同じプロトコル同士でも距離とチャンネルに
よっては、同じように干渉することもあるようです。

人間の音声も視聴可能な周波数と発声可能な周波数は限られているため、
狭い場所に同時に多人数が押し込められた状態では、それぞれの
個々が個別にコミュニケーションをとると同様に干渉が発生します。

お互いに周りのノイズに打ち勝つためには信号レベルを上げる必要があり、
徐々に大声になってさらに周囲のノイズが増大する悪循環に陥るようです。

ひとつの区切りの中に大勢の人がいる状態では、
会議のようにどこかで同期をとる、一定の距離をあける、
発言人数を制限する、等の対策が必要となるようです。
音声以外の他のコミュニケーション手段も併用する、等の工夫も
何かできないものかと昨日の夜のイベントでふと思いました。


もう十分きれいになったから、これ以上いったいハードに何が
必要なんだ、とゲーム開発の現場でもよく聞く話です。
進化する必要が本当にあるのかと。
画面の見た目の変化はある一定以上まではわかりやすいですが、
それを超えると素人目にはあんまり区別がつかないかもしれません。

従来そのままのデータや作り方を続けている限り、ハード機能や
世代が変わっても映像的にはさほど変化が無いというのが1つ。

もう1つは、前処理で作っておくフェイク手法がかなり発達しており、
作業工程も確立していること。プリレンダリングしたり前処理で焼き付けたり、
ムービーを併用していたり。変化しない限り、動かない限りは
遜色ないクオリティを出すことができます。

本来のインタラクティブ性を完全に再現するにはシミュレーションが
必要ですが、これはまだまだパワーが足りません。
物理エンジンにしろアニメーションにしろ、今ようやく本格的な
活用が始まったばかりです。

shader が登場し、ソフトウエア技術者の工夫と努力が可能になって、
GPU のパワーが上がった今行われているのは、フェイクを徐々に
本来のものに置き換える動きだとといえるかもしれません。

見た目ではほとんど変わらなくても、ライティングがリアルタイムに
なって影も動的生成になって、やっと自由に動かせるようになって
きました。
でもまだまだパワーが足りず十分ではありません。
もっともっと欲しいのです。メモリもパワーも。

●流体力学による煙、炎、水

というわけで・・このセッションでは、煙等の流体を GPU 上で
シミュレートする手法について丁寧かつ実践的に詳しく解説が
行われました。シミュレートだけでなく動的なインタラクションと
レンダリングも含まれており、説得力のある煙となっています。
表現上、そしてパフォーマンス的に問題となる点にもきちんと
踏み込んでおり、良く考えられたわかりやすいものでした。
ただしシミュレートはまだ空間上非常に限定された領域に限られ
ています。

注目したのは、ボリュームテクスチャへのレンダリングが実用的に
なる(なっている)という点。これはかなり応用できそうですね。

ボリュームの多用はメモリ容量が心配ですが、毎年発売される
GPU のスペックを見る限り、間違いなく時間が解決するでしょう。


●いつまでもプレイできる (今日と明日のためのゲームアート)

スペックの予測を立てるためのデータとその理由が流暢に
語られます。流暢過ぎるために若干流れの趣旨がつかみづらい
ことと、少々冗長に感じてしまう部分があります。
というのも、対象が純粋な技術者ではなく、アーティストや
プロデューサーを含めた幅広いものだったからのようです。

昔作られたゲームのグラフィックスを、時代に合わせて改善する
手間のかからない いくつかのテクニックが紹介されます。
とはいっても具体的な手法そのものにフォーカスした内容では
なく、最新 GPU によってパワーがあまってもこんな感じで
さまざまな「工夫が可能なのだ」ということ。
あきらめるな、ということです。


●高度なスキンレンダリング

GeForce8800 のデモのひとつ、リアルな皮膚表現の解説でした。
GPU Gems3 の表紙でもおなじみのあれです。
プログラマはもちろん、むしろCGデザイナー(アーティスト)の方
が見ても大変参考になる内容だったと思います。
効果の比較もわかりやすく、なぜそのような処理が必要なのか、
なぜこのような構造になっているのか、さまざまな工夫とともに
詳しく説明されました。

テクスチャスペースにおけるライティングやエフェクトは非常に
魅力的なものです。メモリさえあれば。
メモリ容量の増加によって、できることや可能性が増えている
ことが良くわかります。


●みんなのためのシェーダつくり (一般向けのシェーダ)

FX Composer2 の紹介です。
さまざまなフィードバックにより、さまざまな用途、さまざまな
目的に利用できる完成度の高いツールに進化しています。
基本的には拡張性とカスタマイズ性を持った、開発に必要な
機能を統合するための入れ物的なポジションです。
豊富なシェーダーライブラリから、目玉のシェーダーデバッガまで
ありとあらゆる。実演も行われました。

これまでこの手のツールに消極的だったのは、機能や使い勝手
以前のところに理由がありました。例えば NVIDIA 以外の GPU で
の動作確認。使えない機能があるのではないかという危惧です。
本格的にコンテンツパイプラインに組み込むならば検証して
おきたいところです。
D3D10 以降では基本的に機能差が無い前提なので、もしかしたら
特に問題ないのかもしれません。



Direct3D や Shader 関連は HYPERでんち のページでまとめています。
blog に書いた内容もある程度分類して参照できるようにしていますので、
HYPERでんち の方もぜひこの blog (ホイール欲しい ハンドル欲しい) と
合わせてご覧ください。

HYPERでんち


RBK-2000BT2 の日本語キーボード化カスタマイズ例です。
リュウド RBK-2000BTII

RBK-2000BT2 は英語キーボードですが WindowsMobile ではデフォルトで
日本語キーボードとして認識されます。そのためもともとキー刻印を
無視して日本語配列で使いたい方にとってはうってつけなのですが、
キーの数が少ないので入力できない文字が存在します。

em1key でこれらのキー割り当てをカスタマイズしてみました。
キー刻印を完全に無視していますので、キーを見ないで打つ方
向けです。

(1) 入力できない [\|] キーを右下の [del] キーに割り当てる

(2) (1) の設定で Delete キーがなくなってしまうので Ctrl+[bs]
  に [Delete] を割り当てる

(3) 入力できない文字 [_] を Ctrl+[:*] に割り当てる。(暫定仕様)

(4) Ctrl と Capslock を入れ替え
  (em1key デフォルト設定が元から持っている機能、お好みで)

(5) Esc キーが小さくて押しづらいので、Esc と [全/半] を入れ替え
  (em1key デフォルト設定が元から持っている機能、お好みで)

(6) (5) の設定で[全/半] が遠くなってしまうので、スペースキー横の
  [alt] でも IME の On/Off 可能にする。

この設定でもまだ入力できない文字 Ctrl+[_] があります。
[_] キーの割り当てに関しては再考の余地ありです。


em1key デフォルトの設定ファイルの一番最後に後述の設定を
追加しています。また、func TABLE_FUNC 0 の中の

SETSYSFLAG SYSFLAG_CTRLSWAP
SETSYSFLAG SYSFLAG_ESCSWAP

の2行を有効にしています。(この2行はお好みで)

追加する設定
# [半] ([全/半]キー処理用)
func TABLE_ALLMODE	VK_DBE_SBCSCHAR
		TESTSYSFLAG	SYSFLAG_SCANVALID
		IF_TRUE	20
		TESTSYSFLAG	SYSFLAG_ESCSWAP
		IF_TRUE	30
	LABEL	20
		# ESC swap しない
		EXIT		FALSE
	LABEL	30
		#
		# ESC キーとして実行
		IF_UP	10
		# down
		RAWDOWN		VK_ESCAPE
		EXIT		TRUE
	LABEL	10
		# up
		RAWUP		VK_ESCAPE
		EXIT		TRUE
endfunc

# [全] ([全/半]キー処理用)
func TABLE_ALLMODE	VK_DBE_DBCSCHAR
		TESTSYSFLAG	SYSFLAG_SCANVALID
		IF_TRUE	20
		TESTSYSFLAG	SYSFLAG_ESCSWAP
		IF_TRUE	30
	LABEL	20
		# ESC swap しない
		EXIT		FALSE
	LABEL	30
		#
		# ESC キーとして実行
		IF_UP	10
		# down
		RAWDOWN		VK_ESCAPE
		EXIT		TRUE
	LABEL	10
		# up
		RAWUP		VK_ESCAPE
		EXIT		TRUE
endfunc

# [DEL] ([\|]キーに変更)
func TABLE_ALLMODE	VK_DELETE
		TESTSYSFLAG	SYSFLAG_SCANVALID
		IF_TRUE	20
		# [\|]
		IF_UP	40
		RAWDOWN	VK_BACKSLASH
		EXIT		TRUE
	LABEL	40
		RAWUP	VK_BACKSLASH
		EXIT		TRUE
	LABEL	20
		EXIT	FALSE
endfunc

# [BS] (Ctrl+BS で Delete)
func TABLE_ALLMODE	VK_BACK
		TESTSYSFLAG	SYSFLAG_SCANVALID
		IF_TRUE		20
		IF_CTRL		10
	LABEL	20
		EXIT	FALSE
		# [DELETE]
	LABEL	10
		IF_UP	40
		RAWUP	VK_CONTROL
		RAWDOWN	VK_DELETE
		EXIT		TRUE
	LABEL	40
		RAWUP	VK_CONTROL
		RAWUP	VK_DELETE
		EXIT		TRUE
endfunc

# [:*] (Ctrl+[:*]で _)
func TABLE_ALLMODE	VK_APOSTROPHE
		TESTSYSFLAG	SYSFLAG_SCANVALID
		IF_TRUE		20
		IF_CTRL		10
	LABEL	20
		EXIT	FALSE
		# [_]
	LABEL	10
		IF_UP	40
		RAWUP	VK_CONTROL
		RAWDOWN	VK_SHIFT
		RAWDOWN	VK_OEM_102
		EXIT		TRUE
	LABEL	40
		RAWUP	VK_CONTROL
		RAWUP	VK_OEM_102
		RAWUP	VK_SHIFT
		EXIT		TRUE
endfunc

# [ALT] (IME On/Off)
func TABLE_ALLMODE	VK_MENU
		IF_UP	40
		RAWUP	VK_LMENU
		WINCMD	WINCMD_IMESW	# IME の On/Off
		EXIT		TRUE
	LABEL	40
		EXIT		TRUE
endfunc



Playstation3 に折りたたみ携帯型 Bluetooth キーボードを
登録してみました。こないだ届いたこれです。
リュウド RBK-2000BTII

PS3 は Bluetooth を内蔵しているので、Bluetooth 周辺機器を
登録することが出来ます。そもそも標準のコントローラや
専用リモコンが Bluetooth らしいです。

登録メニューには、今のところ
 ・BDリモートコントローラ
 ・ヘッドセット
 ・キーボード/マウス
の3つの登録メニューがあります。(今後のファームアップデートで
増えるかもしれません。)

試したことは無いですが、ボイスチャット用のヘッドセットも無線で
できるなら結構便利かもしれません。

PS3 Linux 上でもきちんと Bluetooth は見えているので、Linux 上
ならもっといろいろなデバイスがつながるかもしれません。
実際 debian の yaegashi さんが Wii コントローラをつないでいます。
ほげめも Wii Remote on PS3 Linux


●機器登録

Bluetooth キーボード RBK-2000BT2 の登録手順は下記の通りです。
PS3 のクロスメディアバーでの操作です。

(1) 周辺機器設定 → Bluetooth機器登録 → キーボード/マウスの登録

(2) [検索開始] を選択する機器検索を始めるので、
  RBK-2000BT2 側も [Fn] + [~`] を長押しして認識モードにする。
  (キーボード右上の Bluetooth LED が点灯)

(3) 検索してみつかった機器がリストアップされます。
  RBK-2000BT2 は PS3 上で「 Bluetooth Keyboard 」と
  表示されました。これを選択。

(4) 画面にパスキーが数字で表示されるので、RBK-2000BT2 側でも
  この数字を入力します。
  数字キーで入力し、最後に [Enter] を押します。
  この [Enter] を忘れがちなので注意。

(5) 「登録が完了しました」と表示されておしまいです。


以後、クロスメディアバー上の操作がキーボードだけでできるよう
になります。カーソルキーが使えます。


●キーボード言語選択

そのままでも使えますが、WindowsMobile への登録時と同じように
デフォルトでは日本語キーボードとして認識しています。PS3 では
これを簡単に切り替えることが出来ます。

(1) 周辺機器設定 → キーボードのタイプ

(2) 「英語キーボード」を選ぶ (US側)

これで ASCII(英語)キーボードでも、キーボードの刻印通りに
文字入力できるようになります。


●キーボードによるクロスメディアバーの操作

カーソルキー、Enterキー、ESCキーでほとんどの操作が出来るようです。
オプション表示の(△) キーは [F1] でいけます。
残念ながら (PS) ボタンは見つかりませんでした。
キーボードをつなぐことによって、文字入力は各段に使いやすくなります。
ただしこれらの操作は、必ずしもゲームで使えるわけではないようです。

・カーソルキー
・(○) [Enter]
・(×) [ESC]
・(△) [F1]


●再認識

PS3 本体の電源を入れた後、キーボード側の何らかのキーを押すと
再認識されてすぐ使えるようになります。いちいち PS ボタンを
押さなくてもよく、なかなか便利です。


ついこの間 Folding@Home で 1PETA 超えた
と思ったら、もう 1264TFLOPS です。
Playstation3 だけでも 1000TFLOPS いってます。
かなり増えていますね。

Fonding@Home Client statistics by OS

OS Type           Current TFLOPS   Active CPUs  Total CPUs 
Windows           164              172703       1798091 
Mac OS X/PowerPC  8                9391         105647 
Mac OS X/Intel    13               4188         23712 
Linux             36               21449        245348 
GPU               43               732          4246 
PLAYSTATIONR3     1000             40305        255338 
Total             1264             248768       2432382 

Last updated at Sun, 23 Sep 2007 09:49:22  


また 何か届きました。

RBK-2000BT2

折りたためる Bluetooth キーボードです。
リュウド RBK-2000BTII

折りたたんだ時はちょうど EM・ONE (標準バッテリー)と同じくらいの
厚みです。電池込みで実測 209g

RBK-2000BT2

開いたところ。

RBK-2000BT2とEM・ONE

大きさ比べ。

RBK-2000BT2とTK-UP84CP

↓上が FILCO パピヨン

RBK-2000BT2とパピヨン

パピヨンよりもキーが大きく、間に隙間も無いので一般的なキーボードに
近い感覚です。パピヨンと違い数字段にもずれがありません。
キータッチもパンタグラフ式のおかげかストロークがスムーズです。

正式対応しているだけあって EM・ONE とのペアリングも問題なくすぐ
使えました。マニュアル通りです。復帰時も認識に数秒かかりますが、
それ以外は何もせずに使えるようになるのでこれは便利ですね。
WM 端末は外付けキーボード端末として利用しているので、これから
じっくり使い込んでみるつもりです。

キートップの刻印は ASCII(英語)配列ですが日本語キーボードとして
認識されます。もしキーの刻印と入力文字を合わせたい場合、または
ASCII 配列派の方は em1key + asciipatchwm で対応できるかと思います。

em1key
asciipatchwm

自分は普段 JIS 配列で使っているのでこのままで十分そうです。
ただ日本語キーボードとしてみた場合キーが足りないので、
[¥|] と [_] が打てませんでした。
この辺は em1key のカスタマイズで何とかしようと思います。

よくみると左上に小さい [Esc] キーがありますね。
もし無理やり親指シフトで使うとしたら、パピヨン改 と同じように
alt を割り当てる形になるでしょうか。この場合親指がちょっと窮屈な
感じです。


WM6版の EM・ONE S01SH2 きましたね。
イー・モバイル、Windows Mobile 6搭載の「EM・ONE α」
NEWS | eMobile
しかも従来機種もアップグレード可能!
予想していなかったのでこれはうれしい。
最近稼働率が低かったのでちょうど良いです。


WindowsCE(PocketPC) の OS アップグレードといえば、以前
Toshiba GENIO e550 初代 と iPAQ h3630 で経験したことがあります。
両方とも PocketPC(PPC2000) から PocketPC2002 への変更でした。

CPU が ARM に一本化されたり、ボタンレイアウトが iPAQ 準拠に
なったりと、いろいろ変化があったあのときです。
特に GENIO はすぐアップグレードがあることを前提に発売したような
感じで、ARM 採用など最初から 2002 向けのデザインでした。
PPC2000 のまま使ったのはたぶん半年に満たないです。

そのわりに RAM 増設のために本体も送ったりと GENIO のアップ
グレードは結構大掛かりでした。(増設無しも可能)
EM・ONE はもともと本体性能がいいので余裕でしょう。

iPAQ は CD-ROM が送られてきて自分で更新できました。
英語版では ROM が少なかったので、ROM に入りきらなかった一部の
ソフトがダウンロードインストールになっていたようです。
その後しばらく MS のサイトから TerminalServicesClient だけ個別
ダウンロードできたのは、、iPAQ の ROM が少なかったおかげです。


前回迷路を作成するシェーダーを作りました。
Direct3D ShaderModel4.0 Shaderで迷路作成

でも作成した迷路が本当に端から端までつながっているのか、
壁がどっかでつながっていたりしないか確認するのが大変なので、
今度は自分で探索させてみました。

ss04 maze 128x128

まず迷路を作り、その後色のついたピクセルが勝手に歩き回ります。

キャプチャだけだと良くわからないので、これも実際に走らせて
ぜひ動いているところをご覧ください。
動いているところだったら、何をやっているのか一目瞭然だと思います。

今回は動きがわかりやすいように 128x128 の初期データが入ってます。
ss03(前回)の initdata.png を使うと 512x512 の迷路になります。
拡大しないとわからないですが暇な時にじっくり見るのにお勧めです。


基本的にすべての探索点は同じアルゴリズムで動いています。
どれも右側の壁伝いに歩いてすべての通路を歩きつくそうとします。
ただしお互いにも衝突するので、狭い路地から出られなくなったり
ショートカットして違う分岐に進んだりと結構ランダムに散ら
ばってくれるようです。

壁伝いに動くため、壁を見失うとその場でくるくる回ってしまい
ます。誰かが助けに来てくれるまでそのままです。

探索点はランダムに左上 (1,1) の点から生み出されます。


シェーダーは下記の 2つです。
 ・maze.fx (迷路生成、前回のものと互換性あり)
 ・walk.fx (迷路探索)

cpp 側のソースを見るとわかりますが、シェーダーを適用して
Draw( 4, 0 ) で四角形を1つ描いているだけになっています。

処理は全部 PixelShader です。迷路のデータを元画像として
レンダリングすると次のシーンが出来上がるわけです。
使っている画像フォーマットも通常の 32bit (R8G8B8A8_UNORM) です。

walk.fx での内部的なピクセルの意味は次の通り。

R= 0.00 路地
   0.25 衝突判定用予約点
   0.50 探索点
   1.00 壁
G= 向き (探索点と予約点のみ) 0.25単位で 上下左右
B= 向き確定フラグ (探索点のみ)
A= 探索点の色

フレームバッファに転送する際に内部ワーク状態を消すようにした
ので、ちらつきも無く見やすくなっています。その代わり判定
している様子など内部状態は見えなくなりました。

walk.fx で使っている乱数は1つだけ。探索点の生成と探索点自身
の色決定です。移動時の判定順は次の通り。

(1) 未確定で右側が空白なら優先して向きを変える(確定)
(2) 前が空白ならそのまま確定
(3) 右も前も埋まっているなら左に向きを変える(未確定)

単なる画像処理なので探索点はいくつでも構わないし、何千個
あっても、数万個あっても処理速度はほぼ一定です。
動的分岐とリソースアクセス遅延によるばらつきが出る可能性が
ありますが、ほぼ完全に面積だけで速度が決定します。

複数同時に動く可能性があるので、衝突判定はかなり厳密です。
Shader が入力可能なのは 1つ前のシーンだけなので、同じ空白を
複数のピクセルが同時に移動可能と判断してしまう可能性が
あるからです。

この同期サイクルのせいで、1pixel 歩くまでに 3フレームも
費やしています。迷路生成よりも判定が長いのは、確定判定が
追加されたからです。まず maze と違い足跡を正確に消す必要が
あります。さらに右を向いた瞬間もと来た場所が空きになるので、
さらに右手に回れると判断してしまう(戻ってしまう)ことも防いでいます。

これらの判定のおかげでピクセルが同時にいっぺんに移動しても
問題なく処理可能となりました。すり抜けも起こらないでしょう。

迷路の生成 maze.fx は ss03 と同じものですが、乱数計算に
偏りのバグがあったので一部修正しました。これはそのまま
ss03 で使うことも出来ます。


ダウンロードはこちらです。
wheelhandle_ss04t.zip
ソースだけでなく実行ファイルも入っています。


2D 迷路を作成する Shader を作ってみました。
これは実際に見た方が早いと思うので、環境がある方はぜひ走らせて、
動いているところをご覧ください。

64x64 で作成中
maze 64x64

512x512 で作成中の一部
maze 512x512

前のフレームの画像をもとに Shader で発展させていくだけで迷路が
出来上がります。

同時に移動した場合の成長点同士の衝突判定を行う都合上、壁が 1dot
成長するために 2フレームかかります。
その代わり平行していくつでも成長点を設けることができ、
同時に複数の壁を伸ばすことができます。
画像1枚をレンダリングしているだけなので、成長点がいくつあっても
処理速度はほぼ一定です。

アーカイブ内の initdata.png が初期画像です。任意のサイズを与える
ことができます。また白黒2値の絵を描いておくとそれをもとに発展
させます。最外周には必ず白枠が必要です。

生成処理は PixelShader 1つで完結しています。
最適化は全くしておらず、命令数は 625 slot もあります。
ほとんど if 文の塊で、調べたら動的分岐が 9段もネストしていました。
temp register を 27個も使っているので並列度も低いです。
ループを unroll しているのは最適化のためではなく、ループを中断
終了するためです。

sampler を使わずに Buffer から読み込んで整数処理すれば、条件判断
も簡略化できるためずっと小さくなるでしょう。

乱数テーブルのみ CPU で生成しています。本当はこれもシェーダーで
やるべきところなのですが今回は見送りました。

成長点はランダムで壁が変質して出来ます。各ピクセルの意味は R で
判断しており、次の 4つの状態があります。

R = 状態
	0.0  = 空白 (黒)
	1.0  = 固定壁 (白)
	0.5  = 成長点 (壁相当)
	0.25 = 成長予約席 (空白相当)

成長点のみ G B A も意味を持ちます。

R = 0.5 (成長点)
G = 向き (0.0~0.25=上, 0.25~0.50=下, 0.50~0.75=左, 0.75~1.00=右)
B = 直進制限長
A = 回転タイムアウト


最後になかなか埋まらない隙間が出来るのは壁から成長点を作る判定が
単なる乱数だからです。全く発展できない場所にもどんどん成長点を
作ってしまい、それがちらついてノイズに見えてしまいます。
必要な場所に生成して、不要な場所には成長点を作らないようにすれば
隙間も埋まるしちらつきも減ると思います。
成長点の生成ノイズが縞々に見えるのは、乱数の質が悪い証拠です。

実行ファイルとソースのアーカイブはこちら。シェーダーも入ってます。
wheelhandle_ss03t.zip

動作には Vista + DirectX10 (August2007 Runtime) が必要です。


ここ数日ポイントの低いデータしか来なくなって、1日の平均が大幅に
下がっていました。チーム順位もすっかり変動が無くなっていて、
こまめなチェックも飽きが出はじめていた、ちょうどそんな頃。
たまたま全体の stat を見ていたら歴史的瞬間(?)が待っていたという
のがこれ。
Folding@Home 1PFLOPS 達成?
また止めるタイミングを逃してしまいました。

その後1日経過してさらにスコアは上昇しています。

Folding@Home Client statistics by OS

OS Type            Current TFLOPS    Active CPUs    Total CPUs 
Windows            163               171778         1793194 
Mac OS X/PowerPC   7                 9311           105296 
Mac OS X/Intel     13                4120           23248 
Linux              36                21427          244481 
GPU                42                704            4187 
PLAYSTATIONR3      793               35552          246939 
Total              1054              242892         2417345 

Last updated at Mon, 17 Sep 2007 02:51:22  

見所は Current TFLOPS の Total です。
これは参加している全計算機の演算能力の合計で、約 24万 CPU/GPU が
実稼動していて総計 1054 TFLOPS をたたき出していることになります。
1PETA FLOPS を超えています。
そのうち PS3 は 35552台。1台あたりおおよそ 22GFLOPS くらいです。
理論的なピーク値ではなくて、実稼動でこれだけ出ているのだから
かなりのものです。

PS3 は Version 1.2 への upgrade の影響が大きいとは思いますが、
さらにポイントが低い代わりに演算効率の良いデータが増えたのか、
涼しくなって純粋に参加台数が増えたのか、その両方かもしれません。

ちなみに GPU はもっと上を行っており、処理効率が非常に高くおよそ
58GFLOPS にも達しています。アルゴリズム的に相性の良いプログラム
ではさすがに爆発的なパワーを発揮しますね。
現状 ATI X1 系でしか動いていないのは、もしかしたら分岐のコストに
原因があるのかもしれません。R600 の HD 2 系ではこの辺若干仕様が
逆行しているため、今度は G80 系の方が中心になる可能性はあります。
ともあれ汎用化の道をたどって進化している GPU なので、今後 D3D10
クラスで動くようなれば、さらに高度かつ高速な運用が見込めるでしょう。

ゲーム機にビデオカードと、今家にはいったい何 GFLOPS 分くらいの
プロセッサがあるのでしょうか・・。

Folding@Home
Wikipedia Folding@Home
Wikipedia Folding@Home jp


あまり実用性が無いサンプルですが、Shader で HELLO WORLD を
作ってみました。

ss02t

ポイントは直接 Buffer に strcpy で文字列を書き込んでいるところ。
つまり Shader で文字列をそのまま表示しています。

描画文字列の書き込み
const char*	Message= "HELLO WORLD!";

g_iStringBuffer->Map( D3D10_MAP_WRITE_DISCARD, 0, &ptr );
strcpy( ptr, Message );	// ←ここ
g_iStringBuffer->Unmap();

描画文字列の表示も、Draw() に strlen() した文字列長をそのまま
渡しているだけです。

g_iDevice->Draw( strlen( Message ), 0 );	// ←ここ

StringBuffer は Shader の IA に渡しています。つまり Vertex 相当です。
1文字が 1頂点で、書き込まれているのはアスキーコードのみ。
stride size (1頂点のサイズ) はわずか 1byte です。

UINT	vSize= sizeof(char);	// ←ここ
UINT	vOffset= 0;
g_iDevice->IASetVertexBuffers( 0, 1, &g_iStringBuffer, &vSize, &vOffset )

InputLayout も DXGI_FORMAT_R8_UINT の1つだけ。

static const D3D10_INPUT_ELEMENT_DESC	_inputDesc[]= {
{ "CODE", 0,  DXGI_FORMAT_R8_UINT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0, },
};

もちろん Message は任意の文字列にできます。
が、付属の適当フォントがだめだめなので小文字とか はみ出します。
もう少し実用的にするなら D3D10_MAP_WRITE_DISCARD でなく
D3D10_MAP_WRITE_NO_OVERWRITE にして、バッファ管理
してあげる必要があるでしょう。
あとシェーダーでごまかしていますが、ステート周りなども未設定です。
(終了も手抜き)

詳しくは下記ソースをご覧ください。
wheelhandle_ss02t.zip

D3D10/DX10 の ジオメトリシェーダーに頼りまくってます。


久しぶりに stats を開いたら
いつのまにか総計が 997TFLOPS になっていました。
以前の 1PETA ショック があるので期待せずに見守っていたら、、
ついに 1000TFLOPS を達成したようです!

Folding@Home Client statistics by OS

OS Type            Current TFLOPS*   Active CPUs    Total CPUs 
Windows            163               171862         1792463 
Mac OS X/PowerPC   7                 9080           105018 
Mac OS X/Intel     13                4052           23160 
Linux              36                21284          244203 
GPU                42                717            4189 
PLAYSTATIONR3      741               34784          245854 
Total              1002              241779         2414887 

Last updated at Sun, 16 Sep 2007 03:01:58  
DB date 2007-09-16 03:03:06  



ShaderModel 4.0 では、Shader 内で Texture の画像サイズを簡単に
参照することができます。自前でフィルタリングする場合など、UV 値の
計算に使えます。
3.0 以前のように、わざわざテクスチャサイズを外部から constant で
渡さなくてすむわけです。

取得するには Texture のオブジェクトに対して GetDimensions します。
August 2007 SDK のマニュアルを見るとちょっと説明が不完全で、
引数も足りず使い方もいまいちよくわかりません。メソッド名も微妙に
間違っているようです。

実際にコンパイルしてエラーメッセージから調べてみました。
GetDimensions() が正しい名前で、後述するいくつかのオーバーロードが
あるようです。
例えば Texture2D の場合次のようになります。

Texture2D    diffuseTexture;
  :

float4 PS_Main( GS_OUTPUT In ) : SV_Target
{
    float2 size;
    float  level;
    diffuseTexture.GetDimensions( 0, size.x, size.y, level );
     :
}

最初の 0 は参照する MipLevel です。
次の2つが受け取る width と height で、texture の pixel サイズに
相当します。例えば 1024 x 1024 のテクスチャを渡すと、

 size.xy= float2( 1024, 1024 )

が入ります。最後は MipLevel の個数が返ります。

// Texture2D の場合
GetDimensions( in uint level, out uint width, out uint height, out uint levels );
GetDimensions( in uint level, out float width, out float height, out float levels );
GetDimensions( out uint width, out uint height );
GetDimensions( out float width, out float height );

2D の場合上記 4つのオーバーロードがあり、uint でも受け取れるし
miplevel が不要なら width と height だけ受け取ることもできるようです。
2D 以外は下記のとおり。

// Texture1D の場合
GetDimensions( in uint level, out uint width, out uint levels );
GetDimensions( in uint level, out float width, out float levels );
GetDimensions( out uint width );
GetDimensions( out float width );

// Texture3D の場合
GetDimensions( in uint level, out uint width, out uint height, out uint depth, out uint levels );
GetDimensions( in uint level, out float width, out float height, out float depth, out float levels );
GetDimensions( out uint width, out uint height, out uint depth );
GetDimensions( out float width, out float height, out float depth );

// TextureCube の場合
GetDimensions( in uint level, out uint width, out uint height, out uint levels );
GetDimensions( in uint level, out float width, out float height, out float levels );
GetDimensions( out uint width, out uint height );
GetDimensions( out float width, out float height );

Cube にも height がありますね。事実上 2DArray 相当だからでしょうか。


昨日の記事で Quadro FX 1700 と 570 の違いに関して
 >おそらく Core/Shader クロックでも違いがあるのでしょう。
こんなことを書きましたが間違いでした。
すみません訂正いたします。
ELSA の製品情報を見たら core クロックは同一でした。

ELSA NVIDIA Quadro シリーズ

なので、おそらく sp 数による性能の差別化だと思われます。


ようやく ShaderModel 4.0 対応 Quadro の 1000系がでてきました。
NVIDIA、ミッドレンジ/エントリー向け「Quadro FX」のラインナップを拡充

1000系はメモリが少なかったので、しっかり 512MB あるのはいい感じです。
ただ 1700/570 も全体的にメモリの転送性能が下がっていますね。
メモリだけ見ると Shader3.0 時代の Quadro の方がバス幅も転送レートも
高かったようです。

そのため記事のスペックだけ見てると、一見 570 との差が少なくなり
FX570 がお得に見えてしまいます。でも公式の比較表をみると
実行性能でもきちんと差別化がはかられているようです。
おそらく Core/Shader クロックでも違いがあるのでしょう。

NVIDIA Quadro FX Product Comparison

この上の FX 4600/5600 とは性能も(値段も)差がありすぎるので、
3000番台も復活してくれるといいのですが。

HYPERでんちの GPU 表も更新しました。
DirectX10 GPU

表を見て気がついた疑問。Desktop の Quadro FX1700 や FX570
よりも Mobile の Quadro FX1600M や FX570M の方が転送レートが
高いのはなぜだろう。



ブエルタ、やっとペタッキが勝った!
復活の勝利、ペタッキがブエルタ通算18勝目を飾る


Autodesk Maya 2008 来ました。
保守に入っていればすぐにダウンロードすることができます。
これまでは 7.0、8.0、8.5 のようにナンバリングされていましたが
今回から 2008 です。略して Maya08。Maya8.0 に似てますが違います。

インストールフォルダも 2008 になりました。
起動時の Output Window にはまだ Maya9.0 と表示されています。
内部的には本当は Version 9.0 ?・・いえいえ
API の Version 番号は一気に増えて 200800 になっていました。
以前は ~ 700、800、850 だったので、内部的にも本当に 2008 に
なっているみたいです。

 API 850 → 200800

いろんな機能追加があると思いますが、D3D 関連だと

・mentalray の dds 入力サポート
・hlslShader の追加

あたりが主なトピックでしょうか。
Maya 自体は dds に対応していたものの mentalray では使えなかった
ので、うっかり dds を貼ったシーンをそのままレンダリングして
落ちたり・・。それを回避するために事前に texture path を置き
換える mel を作ったり、なんてことが過去にはありました。

ビューポートで描画できるハードウエアシェーダーは 4.5 あたりから
ついており、今までも CgFx 等を使って表示させることができました。
今度は HLSL の対応です。Direct3D の Effect (fx) フォーマットの
ファイルを読み込み、マテリアルとして適用することができます。
実際に使ってみました。

 ・Plug-in マネージャーで hlslShader.mll を有効にする
 ・Panel の Renderer を Default (High Quality でない) にする
 ・Panel の Shading から Hardware Textureing を ON にする

これで hlslShader のノードを作って描画確認できます。

とりあえず
C:\Program Files\Autodesk\Maya2008\devkit\plug-ins\
に入っている、Maya_fixedFunction.fx を読み込ませると簡単な
描画テストができるようです。

CgFx と違い、OpenGL ではなく本当に Direct3D を使っています。
ついに Maya 上で Direct3D を扱えるようになったわけですね。
API 的には class MD3D9Render が追加されており、ここから
IDirect3DDevice9 のインターフェースを取得することができるようです。

基本的には

World
WorldInverse
View
ViewInverse
Projection
WorldView
WorldViewProjection

などの古くから使われている一般的な Semantic に対応しています。
また Annotation を使った DXSAS にも対応しているようです。
(DXSAS = DirectX Standard Annotations and Semantics)
ただし Maya 内のライトとのリンクはまだ行われておらず、
シェーダーに入力されるパラメータの一種として設定しなければ
ならないようです。

実際に hlslShader を使いながらの開発はまだしないかもしれま
せんが、D3D のインターフェースを直接扱えるようになった点は
かなり大きな変化だと思います。

残念ながら D3D10 ではありません。Direct3D 10 だと Vista に
限定されてしまうし、まだ仕方ないのかもしれません。


Buffer への書き込みに DISCARD + Map() を使うことで、ドライバが
バッファの Rename を行うことができます。つまりアプリケーション
側では同一のバッファのつもりでも、実際はぜんぜん違うメモリに
書き込んでいたりするわけです。
これは最適化のためで、フレームバッファと同じようにドライバ側で
勝手に SwapChain 化してくれるようなもの。

その代わり Rename 数の上限によってバッファが枯渇する可能性が
あるそうです。(関連は下記エントリ)
Gamefest Japan 2007 (2) Direct3D 10

この Rename の状況を軽く調べてみました。


●同じバッファが割り当てられているかどうか確認する方法

(1) Map() した時のアドレス値 (*ppData の値) を比較する。
(2) D3D10_MAP_WRITE_DISCARD かつ D3D10_CPU_ACCESS_WRITEのみ
  であるにもかかわらず、敢えて前回書き込んだ値を読み出してみる。

(1) だけでも十分かもしれませんが念のため (2) も調べました。


●条件

ConstantBuffer を作成してみます。Map() でアクセスするには
下記の指定が必要です。

 ・D3D10_USAGE_DYNAMIC
 ・D3D10_CPU_ACCESS_WRITE
 ・D3D10_BIND_CONSTANT_BUFFER

使用した GPU は GeForce8800GTS です。


●未使用バッファ 16byte

16byte のバッファを作って、毎フレーム Map() で書き込んでみました。
このバッファは特に ~SetConstantBuffer() していないので、Busy に
なることはないはずです。

左から
・アドレス
・Bufferサイズ
・これから書き込む値
・書き込み前に読み出したメモリの値
と並んでいます。

addr     size    write     read
073eb400 16byte (00000000) 00000000
073eb800 16byte (00000001) 00000000
073eba00 16byte (00000002) 00000000
073ebc00 16byte (00000003) 00000000
073ebe00 16byte (00000004) 00000000
073ec000 16byte (00000005) 00000000
073ec200 16byte (00000006) 00000000
073ec400 16byte (00000007) 00000000 (A)
073ec600 16byte (00000008) 00000000
~
073f4600 16byte (00000042) 00000000
073f4800 16byte (00000043) 00000000
073ec400 16byte (00000044) 00000007 (B) 一周 (A)で書き込んだ 7
073ec600 16byte (00000045) 00000008
~
073f4600 16byte (0000007f) 00000042
073f4800 16byte (00000080) 00000043
073ec400 16byte (00000081) 00000044 (C) 一周 (B)で書き込んだ 44
073ec600 16byte (00000082) 00000045
073ec800 16byte (00000083) 00000046
~

アドレスは 512byte 単位で割り振られています。60個程度のバッファが
順次割り当てられ、繰り返し利用されているように見えます。
未使用バッファでも毎回違うアドレスになるのは予想外でした。


●未使用バッファ 512byte

512byte のバッファではずれが小さくなっています。どちらにしろ
未使用バッファなのでたまたまかもしれません。

addr     size     write     read
0727b600 512byte (00000000) 00000000
0727b800 512byte (00000001) 00000000
0727b600 512byte (00000002) 00000000
0727b800 512byte (00000003) 00000001
0727b600 512byte (00000004) 00000002
~


●実バッファ 1088byte

実際に ~SetConstantBuffer() してシェーダー参照されている
描画に使っているバッファを見てみます。

    addr     size   write read
*L1 0754b000 1088byte (0) 0
*L1 0754b000 1088byte (1) 1
*L1 0754b000 1088byte (2) 2
*L1 0754b000 1088byte (3) 3
*L1 0754b000 1088byte (4) 4
~

ここでの write は、1つ前のフレームで書き込んだ値です。
アドレスも、前回書き込んだ値も一致しています。
同じバッファが割り当てられています。
このプログラムは厳密に CPU と GPU の同期を管理しているため、
次の Map() までに描画が完了してしまっている可能性があります。


●実バッファ 1088byte ×2

1フレーム中、同じバッファに2度転送を行ってみました。
L0/L1 のペアで1フレームです。

    addr     size   write read
*L0 0735b000 1088byte (0) 0
*L1 0735b500 1088byte (1) 0
*L0 0735b000 1088byte (2) 1
*L1 0735b500 1088byte (3) 2
*L0 0735b000 1088byte (4) 3
*L1 0735b500 1088byte (5) 4
~

今度はそれぞれ異なるバッファが割り当てられています。
参照されている Buffer は実際の描画が完了するまで Busy に
なり、アクセスから保護されている様子がわかります。
やっと予想した動作が見えてきました。


●実バッファ 1088byte ×20

今度は1フレーム中、同じバッファに20回書き込みます。

    addr     size   write read
*L0 0719b000 1088byte (0) 0
*L1 0719b500 1088byte (1) 0
*L2 0719ba00 1088byte (2) 0
~
*L17 071a0500 1088byte (17) 0
*L18 071a0a00 1088byte (18) 0
*L19 071a0f00 1088byte (19) 0
*L0 0719b000 1088byte (20) 1
*L1 0719b500 1088byte (21) 2
*L2 0719ba00 1088byte (22) 3
~

Rename の様子が良くわかります。
ちなみに各 Map() と Map() の間に Draw を挟んでいないので、
本来は最後の Map() 以外は破棄してかまわない状況です。
今のところ Map( DISCARD ) を実行した数分だけバッファが
割り当てられているようです。
Busy の情報はリネーム元のバッファ1つ分しか持っていない
のでしょうか。


●実バッファ 1088byte ×20000

2000回では変化無かったので、一気に 20000回行きます。

*L0 071fb000 1088byte (0) 0
*L1 071fb500 1088byte (1) 0
~
*L3916 0aee5c00 1088byte (3916) 0
*L3917 0aee6100 1088byte (3917) 0
*L3918 071fb000 1088byte (3918) 1
*L3919 071fb500 1088byte (3919) 2
*L3920 071fba00 1088byte (3920) 3
~

20000回を待たずに再利用されています。
やっぱり本当は使っていないことがばれているのかもしれません。
ここまでで考えられるバッファサイズはおよそ 8Mbyte でしょうか。
動きは見えましたが、完全に捕まえるためにはきちんとテスト
プログラムを作った方がよさそうです。ちょっと試したくらいでは
まだわからないですね。


引き続き Microsoft Gamefest Japan 2007 の話題です。
関連エントリ
Gamefest Japan 2007 (1) Direct3D 10 と Meltdown
Microsoft Gamefest Japan 2007

Direct3D 10 に直接関連するのは下記の 2コマです。

1. Windows Vista グラフィックス 開発の手ほどき: Direct3D 10 and 10.1
2. Windows to Reality : D3D10 グラフィックスのゲームへの投入

メインはたぶん 2. の方でしょう。内容は、D3D9 から D3D10 へ
の移行を促すためのアドバイスです。

・9 から 10 に移行しても API を変えただけでは必ずしも速くならないこと
・どうして API や仕組みを変えたのか
・理由を踏まえてどう使えばいいのか
・Direct3D9 から移行しつつ 10 で想定した使い方を引き出す手法

とはいえ Direct3D10 の SDK に触れたことが無いと、少々
ぴんとこない内容だったかもしれません。
そのため前提となる知識として、Direct3D10 を解説した 1. の
「Windows Vista グラフィックス 開発の手ほどき: Direct3D 10 and 10.1」
が入ります。

逆にすでに D3D10 上で設計し、実際に開発をしている人に
とっては、移行時のテクニックも不要なので参考になる点は
少なかったかもしれません。
現在 D3D9 を使っている人、または移行中の人がターゲットです。


D3D9 から D3D10 への設計の変更や API の大きな変更は、
技術者が新たに使い方を覚え直す必要があり、また移植性や
ソースの互換性を失うリスクがあります。
それでもやる価値があると判断したわけで、その一番の目的は
パフォーマンスの向上でした。

ところが D3D10 の機能変更の意義と、新しい設計の意味を
きちんと理解して使わなければパフォーマンスがさっぱり
あがらなかったものと考えられます。
つまり単なる移植で D3D9 から機械的に API を置き換えた
だけではだめだということです。

そのポイントをいくつか絞って紹介しており、比較的大きな
変更をしなくても D3D10 の設計思想に合わせられる折衷案も
合わせて提示されています。

最終的な結論は、やっぱり D3D10 の機能を活用し、
そのために新たに設計しなおすことが一番とのこと。


以下いくつか気になった点など

バッファを Map(Lock) して CPU がアクセスする場合、
DISCARD や NO_OVERWRITE 指定は高速だということが良く知ら
れています。その理由は GPU/CPU が同期待ちをする必要が
無いからで、特に DISCARD はドライバが必要に応じて
Buffer Rename を行います。

この Rename 数には上限があって、バッファが枯渇する
可能性があることが述べられていました。
なるほど、そこまで考えたことはありませんでした。
ConstantBuffer は DISCARD アクセスしかできないので、
更新手段を選択する判断材料になりそうです。

UpdateSubresourece() に関する説明は、メモリコピーの負荷が
アプリケーション側しか考慮していないように見えたので
少々わかりにくかったかもしれません。

Clear~() 系はフレームバッファ全体なので、ステートに
基づいたクリアが必要ならシェーダーを作ってポリゴンを
書けとのこと。

cbuffer より tbuffer の方がランダムアクセスに適している
場合があるそうです。index によるアクセスなど。
これは意外でした。
そもそも cbuffer が速くないのか、
インデックスのアドレッシングが苦手なのか、
texture cache のおかげなのか、
いつか検証してみたいポイントです。

[unroll(n)] は n 回までのアンロールを強制して、最適化
のヒントに使える、と書かれています。
でも以前試した限りでは、回数指定をつけると n 回で
ループ自体を打ち切ってしまうのでプログラムの動作その
ものが変化してしまいます。もしかしたらこれは意図した
動作では無いのかもしれません。

ConstantBuffer は自前で管理せよとのこと。
この辺は構造からしてやっぱり、といった感じ。

Direct3D 10.1 に関しては特に目新しい情報はありませんでした。
ShaderModel4.1 の変更は思ったより小さいかもしれません。
単に 10.1 用の新しいリソース命令が追加されただけ、とか
十分ありえます。ps1.0→1.3 と同じような感じで。


Microsoft の Gamefest Japan 2007 に参加してきました。
Direct3D 10 に直接関連するセッションは 2つ。

2007/09/06
1. Windows Vista グラフィックス 開発の手ほどき: Direct3D 10 and 10.1
2. Windows to Reality : D3D10 グラフィックスのゲームへの投入

DirectX のイベントといえば Meltdown です。
ここしばらくの間、Meltdown は単独開催ではなく CEDEC 協賛
セッションとして行われてきた印象があります。
調べてみました。
実際に Meltdown という名前で CEDEC の中で行われたのは
2回だけだったようです。

●Meltdown Tokyo 1997
●Meltdown Tokyo 98 SUMMER
●Meltdown Tokyo 1999
●Meltdown Tokyo 2000
●Meltdown Tokyo 2001
●CEDEC 2002 DirectX Day
 http://www.microsoft.com/japan/msdn/directx/techarts_dx8.aspx
●Meltdown in CEDEC 2003
 http://www.microsoft.com/japan/msdn/directx/meltdown2003/default.aspx
●CEDEC Meltdown 2004
●Micrtosoft Game Developers Day CEDEC 2005
●2006 無し

Meltdown の初期のころは互換性テストが目的で、developer
は各社の新しいビデオカードにいち早く触れる良い機会でも
ありました。
初参加はたぶん 1997 頃だと思います。Mystique や
PERMEDIA2 など、DirectX にあわせた 2世代目 GPU が揃って
登場してきたあたりです。

開発中でまだドライバが不安定なものもありましたが、
それまで使ってきたビデオカードと比べると、どれも非常に
パフォーマンスがあがっています。
そして Direct3D との相性もよくなり、対応機能が増えている
ことに素直に感動してました。
が、それも最後のブースを見るまでのこと。

たまたま最後にスケジューリングしていたのは、当時はまだ
無名だった NVIDIA です。横倒しで机の上にむき出しのまま
組まれた PC に不安を覚えながら、プロトタイプの RIVA128 で
手持ちのプログラムを起動してびっくり。
口からでた感想は一言 「今までで一番速い」。


CEDEC と一緒になってからは、互換性よりも新しい概念である
shader をどう活用するのか、という点にフォーカスが変わります。
ちょうど shader によってリアルタイムCG は非常にホットな
時代を迎えています。
必然的に現行または将来可能となる技術的な内容が多く、
トレンドを反映した高度な shader 論と、急激に変化する
API や GPU 機能の解説が多くなります。
密度の高い内容となりました。


多少落ち着いてきた 2004 年あたりからは、また若干方向に
変化が見られます。急に高度化した内容に対するより戻しか、
開発者向けの救済路線が徐々に登場してきます。
XNA というキーワードが出たものの一体それは何なのか、
ミドルウエアなのか、リソースマネージャなのか、
グループウエアなのか。
「開発コストを下げるため」の1点以外良くわからない混乱の
時期でもありました。


XNA の路線が固まってからは現状のとおりです。
Gamefest Japan 2007 もこの流れを反映しており、おそらく
開催の中心は XNA にあったものと思われます。

最初にあげた Direct3D 10 の 2つのセッションに関しては、
未経験者に対する API や違いの紹介、Direct3D 9 から
移行する方への変化の説明、乗り換え時の注意点等といった、
ポイントを絞った最小限のものとなっていました。
表現など shader 技術的な話ではなく、まずは API の乗換えが
最重要課題なのかもしれません。

内容については次回でもう少し詳しく書いてみます。


届きました。
Princeton PTM-UBT3S 1

プリンストンの超小型 Bluetooth アダプタです。
Princeton PTM-UBT3S

確かに小さいです。出っ張り部分は約 8mm。想像よりは出っ張る感じ。
Princeton PTM-UBT3S 2
Princeton PTM-UBT3S 3

早速ドライバを入れて、emobile EM・ONE をペアリングして、
Bluetooth でダイヤルアップできるようになりました。
USB 直結よりは遅くなるけどやっぱり無線は便利です。

あっ
Princeton PTM-UBT3S 4

USB コネクタから引き抜こうとしたら早速壊れました。
せっかくなので裏の写真も。
Princeton PTM-UBT3S 5

コネクタの真裏に基板が収まっていました。

組み立てたら一応動いたようです。
コネクタから引き抜く時はくれぐれもご注意ください。


前回の続きです。
3D一般 ノーマルマップの互換性問題(1) ノーマルマップとは


●ノーマルマップに格納されるベクトルの座標系

ハイトマップからノーマルを生成する場合を考えてみます。
nVIDIA の Photoshop dds Plug-in にこの機能がついており
簡単に生成することができます。同様のツールは他にもあるし
いまどきのシェーダーならリアルタイムに生成するかもしれません。

まず基準となる座標系を決めておきます。例えば U-V を X-Y
平面とし、uv が左上原点の場合軸の向きを次の方向に想定します。

 U = X軸 + 方向
 V = Y軸 - 方向

右手座標系なら Z軸は手前(カメラ方向)が + です。
法線は -1.0~+1.0 の範囲の値を取るので、色情報として格納する
ために 0~1.0 に変換します。
8bit の場合はこれが 0~255 に相当します。

 R= X * 0.5 + 0.5
 G= Y * 0.5 + 0.5
 B= Z * 0.5 + 0.5

平面は常に手前の Z+ 方向を向いているので、法線の Z軸が負に
なることはなく常に 0.5~1.0 の範囲となります。

そのため画素値には必ず「青色」が一定量含まれており、作られた
ノーマルマップは一般的に青色に見えます。

完全に滑らかで凹凸の無い平面の場合は、どの法線も手前を
向いているので (0,0,1) になります。カラーにすると

 R= 0.5 = 128
 G= 0.5 = 128
 B= 1.0 = 255

の真っ青です。この X-Y 平面に配置した右手系ノーマルマップの
ことを別名「青マップ」と呼ぶことがあります。

一般的なタンジェントスペース用のノーマルマップのデータは
その多くが「青マップ」で、上記に近い座標系が使われる
ことが多いようです。

余談ですが、GeForce3 で私がはじめて作ったノーマルマップ用の
描画シェーダーは、何故か X-Z 平面で作っていて「緑マップ」
になっていました。当時ほかのツールなどの知識が皆無で、
何が標準なのか全く知らなかったので・・。

nVIDIA の Photoshop dds plug-in 等では、これらの軸を
入れ替えたり反転させたりと簡単に設定できるようです。
便利になりました。

TS (TangentSpace) 専用で用いる場合 Zが常に正なので
0~1.0 をそのままテクスチャに格納することもあります。

符号が不要なので、ATI の 3Dc や NVIDIA の Hi/Low フォーマット
など、法線圧縮を行う場合 X Y のみ格納します。
(Z は Z= sqrt(1-X*X-Y*Y) )

このように 2D で作成したノーマルマップのことを以下の説明で
「プレーンノーマルマップ」と呼ぶことにします。


●固定の座標系を持ったノーマルマップを任意の面に貼る

ノーマルマップは基準となる座標系に作られるので、必要に
応じて別の空間に変換する必要が生じます。

例えば X-Y 平面に作られた青マップを何も考えずに水面に貼ると、
法線は真横を向いているため真上の光源で暗くなってしまいます。
貼られた面のジオメトリに合わせて座標系を変換しなければ
なりません。

オブジェクトのローカル座標とノーマルマップが貼られた
UV を基準にした接空間(TangentSpace) の変換マトリクスを、
法線と同じように頂点単位で格納しておきます。回転なので
3x3 でよく、これはオブジェクト空間における接空間の 3つの
ベクトル軸に相当します。

 X = U = Tangent
 Y = V = Binormal
 Z = W = Normal

光源はワールド座標系に配置するので、ノーマルマップを用いて
光源計算を行う場合は下記の変換をたどります。
もちろん光源を逆変換してもかまいません。

 ノーマルマップの基準座標系
  ↓
 オブジェクトの座標系
  ↓
 ワールド座標系

TangentSpace 用のノーマルマップ (以下 TS Normalmap) は
データ作成時に決められた基準平面を持っており、自由に変換
できるので任意のポリゴン面に貼り付けることができます。
マップなど広い面積に適用する場合には、データを使いまわす
ことができるため TS Normalmap が必要です。


●オブジェクトスペースのノーマルマップとは

オブジェクトの表面の法線を、そのままテクスチャに書き込んだ
のが ObjectSpace (OS) のノーマルマップです。
(以下 OS Normalmap )
基準の面を持たず、オブジェクトに貼り付けた状態で正しい
ジオメトリとなります。

オブジェクトの形状に強く依存しているため、貼られた
テクスチャを他の形状のモデルや他の面に動かすことができません。

オブジェクトそのものの法線をそのまま表しているため、
すべての方向を向く可能性があります。X Y Z はどれも
-1.0 ~ +1.0 の値を取り、カラーで見るとさまざまな色が
混じった虹色です。

描画時は接空間との座標変換が不要で、TS Normalmap の描画と
比較すると頂点データサイズ減り GPU の負荷も低くなります。

データの流用ができないため、マップのように広い面積には
使えず、オブジェクトのような固定物に向いています。

キャラクタのような骨変形する物体に OS Normalmap を用いる
場合は、不可能ではないですがかなり注意が必要です。
基準となる形状を保存しなければならないので、ターゲットの
実機では使えても 3Dツール等では使えません。
以前のプロジェクトでは GPU が非力なので、少しでも処理を
稼ぐために OS も併用しました。
(この苦労話も機会があればいつか・・)


● OS Normalmap と TS Normalmap の相互変換

OS Normalmap と TS Normalmap は座標系が違うだけです。
それぞれ基準となる座標系さえわかれば相互変換は簡単にできます。
ハードウエアでもできます。

使い勝手や利便性を考えると、今の GPU なら TS Normalmap を
選ぶことになるでしょう。


● TS Normalmap は TangentSpace に依存する

TS Normalmap をどのようにポリゴン面に貼り付けるのか、
それを決定するのは頂点に埋め込まれた接空間マトリクス
(TangentSpace) です。

また TS Normalmap を作るツールも、UV と頂点座標から求めた
TangentSpace を元にデータを作成します。

TS Normalmap はオブジェクト空間に依存しない代わりに、
TangentSpace に強く依存してしまいます。

ノーマルマップを生成するツールの TangentSpace の算出と、
実際に描画するデータ内の TangentSpace が一致して
いなければ正しい結果になりません。

水面や垂直の壁など、わかりやすい平面の場合はあまり問題が
起きませんが、球状の物体など共有頂点でソフトエッジの
場合(または同じスムージンググループの場合)
TangentSpace はどうなるのでしょうか。


●本来の法線

ポリゴンで表現されたオブジェクトは、本来の形状を複数の
平面で近似したものです。平面故に曲面の表現は完全には
一致しません。見た目で誤差を埋めるために

・頂点の色を補間する
・光源計算の結果を補間する
・補間した結果を描き込んだ滑らかに見えるテクスチャを貼る

等の処理(ごまかし)が必要だったわけです。

頂点は離散的に本来の形状をサンプリングした座標値で、
同じように本来の形状の面の向きを、頂点位置でサンプリング
したのが頂点法線です。
共有される面法線の単なる平均ではなく、その点にあるべき
実際の面の傾きを表現しているといえます。


●共有頂点の TangentSpace

接空間も実形状の表面をなぞる向きに配置されていると考えられ
ます。法線の補間と同じように接空間も頂点間で補間します。

例えば球のノーマルマップを作成して 12分割程度のローポリゴン
モデルに適用することを考えます。

共有頂点のソフトエッジなら、接空間も補完によって法線同様
球状の表面に配置されます。補間された接空間と実モデルの法線
に差が無ければ、得られたノーマルマップはすべて (0,0,1) の
ベクトルを持つ真っ青でプレーンなマップになります。


●スムース補間問題

頂点法線が期待している形状と、接空間が作る表面形状にずれが
生じるとどうなるでしょうか。この状態でノーマルマップを作ると、
空間のずれをノーマルマップ側で吸収しようとします。
つまり

 ローポリ側の形状がノーマルマップに漏れ出している

わけです。
頂点法線と接空間を別計算で求め、お互いに整合性をとらない
場合に発生します。

接空間側で丸めるか、ノーマルマップ側で丸めるかの違いです。
ノーマルマップを作る上での考え方の違いといえるかもしれません。

ただし、このように漏れのある TangentSpace にはプレーン
ノーマルマップを貼ることができません。プレーンノーマルマップ
には、期待する形状の誤差を埋めてくれる情報が含まれていない
からです。


●UV ハードエッジ問題

UV は矩形のテクスチャをサンプリングする座標で 0~1 の範囲を
持った有限の値です。法線とは違い、シリンダなどに貼ると
必ず一周するつなぎ目ができます。

法線は共有し、かつソフトエッジだとしても (または同一の
スムージンググループに属しているとしても) 数値上 UV の値は
共有されていない非連続の状態ができます。

この条件において、法線ではつながっているにもかかわらず
接空間を UV と同じように非連続とみなしてしまうソフトが
あります。

つまり UV 値が非連続の場合、強制的に接空間だけ
ハードエッジになってしまうわけです。

例えば 3ds max 7 が生成するノーマルマップ用の接空間は、
UV の切れ目を常にハードエッジとみなしていました。

そのためシリンダや球面などの UV の接合面は、ノーマルマップ
側に曲面が描きこまれており、真っ青でプレーンな青マップに
なりません。部分的に UV の切れ目でグラデーションが発生して
しまいます。

3ds max 7 の中で閉じている分には同じ TangentSpace を使う
ため問題はありませんが、他のレンダラに持っていくと意図
した描画にならず切れ目が発生してしまうことがあります。

またこの場合もプレーンノーマルマップを貼る場合、切れ目の
部分だけおかしなことになります。
これも形状漏れの一種といえるでしょう。


●くるくるUV問題

単なるただの壁面に見えても、そこには作りこまれた職人の業が
あります。わずかなテクスチャを使い、リピートさせたり
回転させたり反転させたり、さまざまな貼り方を用いて
単調な繰り返しにならないように工夫されているのです。

技巧的なデザイナーは緻密に作りこまれた UV 値を持った
データを仕上げます。
ドット職人のドット技に似ているかもしれません。

ところがこのようなデータに対する耐性は多くのツールが
持っていません。

日本のゲーム会社でデザイナーが作ったデータの UV 値は、
反転は当たり前、回転も当たり前、テクスチャに対する UV の
オーバーラップもリピートも当たり前に使われていることが
あります。

複雑な UV を持ったデータでも、TangentSpace を求めて
ノーマルマップを貼ることができるかどうかが問題です。

レンダラによっては、UV を反転しただけでノーマルマップの
凸凹が逆になってしまうものもあるようです。

flip された UV で接空間が逆向きになり、共有時に相殺
されてベクトルが 0 に近づいてしまうこともあります。

ツールによって対応度が異なっており、例えば下記のような
問題があるようです。

・対策無しに補間してベクトルが 0 に近づいてしまう
   さらにその状態を無理やり正規化するので
   ねじれが発生するしそもそもつながっていない。

・ベクトルが一致しないため異なる頂点とみなして補間しない
   ハードエッジ化

上記問題が発生するため、もしかしたら開発チームによっては
UV 反転や回転を使ったデータの製作を禁止しているかもしれません。
伝え聞いた話では、デザイナーが手で修正してノーマルマップの
色を力技で書き換えているところもあったそうです。

UE3 や CryEngine など、有名どころではしっかり対応
しているのではないでしょうか。


私が くるくるUV問題 に遭遇したのは 5年ほど前で、実際に
開発していたコンシューマのプロジェクトでした。
(2004年に実際に発売されました)
その時はデザイナーの要望に応えつつ次のように対処しました。

・TangentSpace の符号が反転しているケースでも、
 共有頂点なら符号を残したまま同一直線状と見なして
 補間する

2年前に作った DirectX9 用エンジン (HYPERでんち のトップ絵等)
では、データ exporter が法線とは別に TangentSpace 専用の
スムージンググループを求めています。


●問題を踏まえて

このように、結構ツールによって生成される TS Normalmap
には差が生じています。

原因は特殊な UV 条件を想定していないことと、 ツール内で
完結している分には問題が表面化しないためだと考えられます。

次回はさらに、各種ツールの現状をいくつか取り上げてみたいと
思ってます。


3D系ツールが吐き出すノーマルマップには、互換性が無い
ことが多いのです。

 ・スムース補間問題
 ・UVハードエッジ問題
 ・くるくるUV問題

DirectX8 の時代、ノーマルマップが出始めたころはまだツール
など存在せず、ほぼ自前でデータを作るしかありませんでした。
それゆえ互換性などの問題は皆無だったのですが、今になって
またデータ製作時にいろいろと不便が生じています。

というわけで、ノーマルマップ互換性問題についていろいろ
取り上げてみたいと思います。


●そもそもノーマルマップとは

リアルタイム系レンダリングでは、ノーマルマッピングはもう
ごく普通のありふれた手法となりました。
HW Shader が出始めたころに比べたらツール環境も雲泥の差で、
さまざまなツールが対応を謳っており、当たり前のように作成
できて当たり前のようにプレビューできます。

そのノーマルマップの使い方も、当初は表面に単純にでこぼこを
貼るだけの Bump Map 手法の一種、とそんな認識でしか
ありませんでした。

ところがハイポリの陰影をローポリで丸ごと再現してしまう、
目からうろこの応用方法が登場し、その威力がいくつかの最先端
ゲームで実証されると一気に広まりました。

ノーマルマップの現在の主な活用方法は 2種類あります。

 (A) 表面にでこぼこをつける BumpMap としての活用
 (B) 別の形状を再現し、見た目のポリゴン数を上げる目的

上は高周波成分に対する情報の寄与、下は低周波成分に対しての
ディテールアップといえるかもしれません。

その原理は単純なもので、あらかじめ法線情報をテクセル単位で
格納しておくだけです。マッピングによって、ポリゴンの各
ピクセルがそれぞれ異なる方向を向いていることに
「してしまう」のです。

このテクセル単位の法線を使って光源計算を行うと、単なる
ポリゴンの平面にまるで凸凹があるかのような陰影がつきます。

(A) も (B) も描画時は特に区別なく、シェーダーもプログラム
も同一のものが使えます。

これらは基本的にはデータの生成方法の違いで、ツールの進化
とデータ作成手段の開拓がもたらしたものです。

それゆえ、プログラマ的にはたいした大きなトピックが無く、
ほとんど CGデザイナーに依存してしまっている開発チームも
あるかもしれません。これもあとで説明する互換性問題が
なかなか表面化しない原因の1つではないかと考えられます。


●ノーマルマップの特徴と違い

ノーマルマップの利点は動的なライティングにあります。
テクスチャに書き込んだ陰影と違い、光源の位置と向き、
またカメラの位置によって陰やハイライトが動きます。

同時にノーマルマップを使うことは、結果として
 「ピクセル単位のライティング」
につながります。

見た目を考えるとこのピクセル単位のライティングは
大きな効果の1つだといえるかもしれません。

(1) 頂点単位ライティング(頂点ごとに法線)
(2) ピクセル単位ライティング(補間された法線でライティング)
(3) ピクセル単位に法線をマッピングする

(1) はグーローシェーディングで (2) はフォンシェーディング
に相当します。shader によって (2) と (3) はほぼ同時期に
実現可能となりました。

むしろ (2) のまじめな 法線の補間+正規化 よりも
ObjectSpace のノーマルマップを貼った方が実現は簡単です。


●ゲームの従来の手法

従来の頂点単位の ライティング+グーローシェーディングは、

 ポリゴンの割がはっきり見えてしまい、
 グラデーションもきれいならず、
 暗部などテクスチャの絵の色がつぶれてしまい、

視覚効果としてマイナスになってしまうことがありました。

マップなど固定物は光源の位置がほとんど変わらないので、
リアルタイムにライティングしないで、描き込んだテクスチャ
を用意した方がクオリティがあがって見えることがあります。

リアルタイムにライトを適用するのもエフェクト用の
瞬間的な点光源だけにして、あとはあらかじめデータに埋め
込んでおきます。明るさや影は頂点カラーに格納し、
テクスチャに余裕があれば重ねて影やライトマップにします。

ライティングしないのでスペキュラは出ませんが、代わりに
環境マップがよく用いられていました。

ノーマルマップでピクセル単位のライティングが可能に
なったこと、基本的に何らかのライティングしないと効果が
無いことから、データの作り方も大きく変化しています。


●ノーマルマップの威力

オブジェクトの形状を丸ごとノーマルマップに焼きこんでしまう
ことができます。

たとえばハイポリゴンで作ったノーマルマップを同じ形の
ローポリゴンモデルに貼り付けてしまいます。

ライティングを行うと、あたかもハイポリの形状がそこに
あるかのように錯覚してしまうわけです。

リアルタイムレンダリングでは頂点数の増加に比例して演算
負荷が上昇します。ノーマルマップの場合ピクセル面積が
一定なら、元のハイポリ形状は何百万ポリゴンだろうと一向に
構いません。

それででほとんど同等のクオリティを表現できるなら
非常に強力かつ便利な技術となるわけです。

ただし輪郭の形状は変わらないので、シルエットはローポリ
のままです。ローポリとハイポリの形状に極端な違いが
ある場合は視差による矛盾も大きくなってしまいます。
さすがに本物のディスプレースメントマップには適いません。

より改善された手法としてパララックスマップやレリーフ
マップがあります。これらもノーマルマップ自体は併用して
いますが、またいずれ機会があれば説明してみます。


●ライティングしないと意味が無い

ノーマルマップは法線情報なので、ライティングをしないと
意味がなくなってしまいます。光源がなければバンプ効果も
見えません。

光源の影響が弱い暗いシーンや固定のアンビエントライト
だけのシーンでは、単なるローポリゴンのマットな物体に
なりがちです。

従来の手法で作ったデータはテクスチャにも陰影が描き込ま
れているので、単純にノーマルマップを適用しただけでは
陰が重なってしまいます。

またノーマルマップを使うと単純に使用するテクスチャ量が
増えます。全体で使えるテクスチャメモリの制約を受けて、
解像度に制限が発生してしまうかもしれません。

ミップマップなどベクトル値に対するフィルタリングも
問題を含んでいます。たとえば一般のテクスチャツールで自動
生成されるミップマップはベクトルを徐々に打ち消し、平均化
してしまいます。
かといって縮小されたエリアで正規化をかけると、不連続な
ベクトルがハイライトのノイズを際立たせてしまい、逆効果に
なることもあります。

従来はあまりきちんとリアルタイムでライティングしない
ことが多かったので、設定がうまく決まらなければ手で描いた
方が楽だといわれてしまうかもしれません。

このように、使う場合にはそれなりのデメリットも発生します。
使えるテクスチャ解像度が下がり、かつきれいに効果が
見えないのなら使う意味がなくなってしまいます。

原理を把握し、それぞれの意味を理解た上でデータを作れるか
どうか。これがノーマルマップを活用する最大のポイント
かもしれません。

長くなってしまったので次回に続きます。


ほとんどのゲーム開発では、一般的にタスクシステムと呼ばれる
独自のフレームワークを使用しています。
あまりドキュメント化されたり表に出ることが無いものの、
プロジェクトを通じてプログラマ間に秘伝的に伝播していくので
実に多くのバリエーションがあります。

プロジェクトの数だけ存在するといっても良いかもしれません。
基本構造と目的はほぼ似通っており、セガタスクと呼ばれる
こともあります。

OS のタスク同様、処理ごとに CPU 時間を細かく分割していく
仕組みです。ゲーム開発で特徴的なのは、すべての処理を
フレーム単位で管理することです。
1フレームの時間は約 16.7~33.3msec と決まっているので、
この中でそのフレームに必要なタスクを、全部一巡しなければ
なりません。

タスクシステムの利点、またはその利用目的は、大きく分けて
2種類あると考えています

 ・タスク数の動的な増減への対処
 ・モジュール分割や作業分担といった開発時の利便性のため

ゲーム中必要な処理は能動的に変化します。例えばオブジェクト
数の変化 - 追加や削除など、動的な処理単位の変化を効率よく
管理することが求められます。

オブジェクトだけでなく、ゲームやシステム全体の動作自身も
タスク化することができます。各種モジュールやサービスなども、
ゲーム進行やモードの遷移にあわせて組み換えが可能となる
わけです。


オブジェクトのような細かい単位と、ゲームシーン等の大きな
処理単位では、規模も目的も異なります。

これらの処理単位を同じ枠組みを使って同一に扱うこともあれば、
グループ毎に別の管理機構を用いることもあります。親子関係、
階層構造を持たせて凝った構造にしているものもあります。
この辺の考え方や設計はそのプロジェクトの方針に依存します。


ゲームの開発規模も、今ではプログラマで10数人規模とかなり
大きくなっています。オブジェクトの増減といったプログラム
処理上の都合だけでなく、分業化におけるフレームワーク
としても非常に重要な役割を持っています。
ある意味人間も管理するわけです。

各タスクは独立しているため作業分担が比較的容易で、システム
がきちんとしていれば、お互いの干渉衝突を最小限にしつつ
開発を進められることになります。
(でも終盤はそうも言っていられません)


上で述べたように、1フレームの処理を切り分けながら処理を
組み立てていきます。各タスクは1フレーム内で順番依存を
持っており、実行順をある程度明確にしなければなりません。

さまざまな手法がありますが、例えば各タスクに割り当てた
プライオリティ値を元に、実行順番を決めるものもあります。

システム系モジュールや作業分担時のタスクは、動的な追加
削除はあっても、動的にプライオリティが変わることが
ほとんどありません。

そういう enum や define 値で順番を決められたタスクも、
動的なリストで追加時にソートされ、リストをたどって仮想関数
呼び出しが行われています。
これを static にできないか考えてみました。
(ちなみに筆者は script ベースのまた違う手法を使っています。)

いわゆる TypeList です。
今回はソートのために Binary Tree を作ってみました。

各 Task は任意の priority 値と呼び出し口 Func() だけ定義
すればよく、Func() が static なら基底クラスも不要です。
各 task は別ソースだとして

class DrawBegin_Task {
public:
    enum {
        priority= 3000,
    };
    static void Func()
    {
    }
};

class DrawEnd_Task {
public:
    enum {
        priority= 5000,
    };
    static void Func()
    {
    }
};

class Input_Task {
public:
    enum {
        priority= 1000,
    };
    static void Func()
    {
        //..
    }
};

こんな形で呼び出します。

template<typename A>
struct task {
    enum {
        value= A::priority,
    };
    static void	Exec()
    {
        A::Func();
    }
};

typedef	TS::Array<
            task<DrawBegin_Task>,
            task<DrawEnd_Task>,
            task<Input_Task>
        > TaskList;

void MainLoop
{
    TS::ExecTask<TS::Sort<TaskList>::result>::Exec();
}

実際の実行順は TaskList 上の順番とは異なり、priority 番号
の小さい順に並び替えられます。

そのため一度 TaskList に登録してしまえば、各処理ごとに
priority を自由にいじって処理の挿入先を変更することができます。
これはコンパイル時に決定します。
画面をクリアするだけとか Flip するだけの単純な task は
inline 展開が可能です。(__forceinline が必要でした)

とはいえ、もともと 1フレームに数回程度、非常に実行頻度の低い
部分なので static 化したとしてもパフォーマンス的には全く
変わらないでしょう。

動的な変更を行うオブジェクト管理は、これらの下に設けることに
なります。

上の例では static な関数しか呼べませんが、各タスクの
インスタンスも管理したいならこんな感じになるでしょうか。
それぞれ _TaskBase を継承しておきます。
ついでに priority も外部からも指定できるようにしておきます。

class _TaskBase {
};

static _TaskBase*  _InstanceTable[ TS::Length<TaskList>::value ];

template<typename A, int pri= A::priority>
struct itask {
    enum {
        value= pri,
    };
    static void	Exec();
};

template<typename A>
RegisterInstance( A* _instance )
{
    _InstanceTable[TS::Index<TaskList,itask<A> >::value]= _instance;
}

template<typename A, int pri>
void  itask<A,pri>::Exec()
{
    _TaskBase* _inst= _InstanceTable[TS::Index<TaskList,itask<A> >::value];
    reinterpret_cast<A*>(_inst)->Func();
}

class Camera_Task : public _TaskBase {
public:
    enum {
        priority= 2000,
    };
    void Func()
    {
        //..
    }
};

使い方は TaskList に登録して、RegisterInstance() を
呼び出すだけです。

typedef	TS::Array<
            itask<DrawBegin_Task>,
            itask<DrawEnd_Task>,
            itask<Input_Task>,
            itask<Camera_Task>
        >    TaskList;

void MainLoop
{
    RegisterInstance( new Camera_Task );

    TS::ExecTask<TS::Sort<TaskList>::result>::Exec();
}

あとは MainLoop から priority 順に呼び出してくれます。
この場合も仮想関数は不要で、呼び出しは static の例と同じように
静的に行われます。
またインスタンスを持たないものは RegisterInstance() する必要が
無く、Func() も static のままで構いません。
(ここは本当は何らかのエラー判定が欲しい部分です。)

一応実際に試したのでソースをのせてみます。
(確認はVisualStudio2005のみ)

StaticTaskSystem.h

wheelhandle_ss01t.zip 実際に使用した例のアーカイブ

本当はもっといろいろ改良が必要だと思います。


Ascii 遠藤さんの 「遠藤諭の東京カレー日記」
再び em1key 関連のお話が載りました。

Advanced W-ZERO3 [es] 親指(続編)
キーボードの限界小ささ(1)

今度はそれぞれキーボードに合わせてカスタマイズした、
実際の設定ファイルが公開されています。
em1key のカスタマイズ情報はそれほど多くないので、
実際に使っている方の実例として参考になります。

em1key v1.26 で Advanced W-ZERO3[es] に(一応)対応しましたが
内蔵キーボードとの切り分け共存が 100% できているわけではなく、
Fn を押した記号と親指シフトの定義が干渉することがあるようです。

とりあえず oyayubiwm の方に機能の有効・無効をいつでも
切り替えられる機能がついているので、こちらでの対処を
お願いします。具体的な操作を質問いただいたので、
下記ページにも追記しておきました。
oyayubiwm

em1key v1.26 では他にも変更点がありました。
ドキュメントへの追記と script マニュアルの更新をすっかり
忘れていまして、あわてて更新をしておきました。

script 命令の ifpc, ifce, ifsw, ifnsw ~ endif が
ネスト対応になっています。(いまのところ 32段まで)

ifpc
ifce
ifsw <symbol>
ifnsw <symbol>
endif
define <symbol> <int>

これらの命令は C 言語でのプリプロセッサに相当します。
script ファイルを em1key が読み込んだ時点で処理されるので、
if ~ 等で除外されたブロックはバイトコード化されず、
メモリにも常駐せず、負担にならないようになっています。
動的な分岐は IF_~ 系の命令です。

em1key スクリプトマニュアル v1.26.0
em1key カスタマイズ情報


引き続き HLSL/Effect のコンパイル周りの話です。
core API 側の Shader Compiler で #include に対応するには
ID3D10Include の準備が必要です。
これはもともと Direct3D9 の D3DX にあったもので、
機能も使い方もいっしょのようです。

ただ、D3DX10 の Shader Compiler API を使う分には内部で
勝手に include 処理をやってくれますし、下記エントリで書いた
ように D3DX 側 API を使った方が良いので、普通に使う分には
あまり気にする必要は無いかもしれません。
>・Direct3D 10 Shader4.0 APIによってコンパイラが違う

一応試してみました。

class fxInclude : public ID3D10Include {
public:
    fxInclude()
    {
    }
    ~fxInclude()
    {
    }
    virtual HRESULT __stdcall  Open(
                D3D10_INCLUDE_TYPE inctype,
                LPCSTR filename,
                LPCVOID parentdata,
                LPCVOID* ppdata,
                UINT* pbyte )
    {
        UINT   bsize= __FileSize( filename );
        void*  ptr= __Alloc( bsize );
        if( ptr ){
            if( __Load( ptr, filename, bsize ) ){
                *ppdata= ptr;
                *pbyte= bsize;
                return	S_OK;
            }
            __Free( ptr );
        }
        return	E_FAIL;
    }
    virtual HRESULT __stdcall  Close( LPCVOID ppdata )
    {
        __Free( const_cast( ppdata ) );
        return  S_OK;
    }
};

使っているところ。

ID3D10Blob* blobEffect= NULL;
ID3D10Blob* blobError= NULL;
fxInclude  incFunc;
D3D10CompileEffectFromMemory(
    memory,
    size,
    fxname,
    cpp_defines,// def
    &incFunc,	// inc
    0,		// HLSLflag
    0,		// FXflag
    &blobEffect,
    &blobError
);

#include の度にファイル名を持って callback されるので、
必要なメモリ領域を作ってあげるだけです。
不要になったら Close() を呼び出してくれます。

ID3D10Include を自前で用意する目的として考えられるのは、
include path 検索への対応でしょうか。

もしくは File にこだわらず、特定の include キーワードに
反応して別のコマンドを挿入したり pragma のように動作を
変えることも出来そうです。
自分で Effect のテキストを読み進める処理が不要なので、
意外に使えるかもしれません。

というわけで

 #include "output_disassemble"
 #include "output_preprocess"

と記述したシェーダーは、デバッグテスト用に disassemble や
preprocess 結果をファイルに書き出すようにしてみました。

テスト&デバッグ中は毎回出力して欲しいけど、完成した
シェーダーの場合は余計なファイルを作られても困ります。

今までは debug build の場合無条件に両方書き出していたので、
不要なファイルが多数生成されていました。これで、完成した
シェーダーは余計なことをしなくて済むようになります。
あまりきれいな方法じゃないけどちょっと便利になりました。


再び HLSL (Effect) のコンパイル関連です。
Direct3D 10 Shader4.0 APIによってコンパイラが違う
で少々調べたので、もう少し説明してみます。
マニュアルに詳しく書かれていないこと、わかりにくいこと、
実際に試したことなどなどをメモしていきます。

API まとめ
・Compile 系 API  HLSL/fx → Blob のバイトコード

ID3D10Effect/D3D10CompileEffectFromMemory()
ID3D10Shader/D3D10CompileShader()
D3DX10/D3DX10CompileFromFile()
D3DX10/D3DX10CompileFromMemory()
D3DX10/D3DX10CompileFromResource()
   
・Create 系 API  バイトコード → インターフェース

ID3D10Device::CreateVertexShader()
ID3D10Device::CreatePixelShader()
ID3D10Device::CreateGeometryShader()
ID3D10Device::CreateGeometryShaderWithStreamOutput()
ID3D10Effect/D3D10CreateEffectFromMemory()
ID3D10Effect/D3D10CreateEffectPoolFromMemory()
D3DX10/D3DX10CreateEffectFromFile()
D3DX10/D3DX10CreateEffectFromMemory()
D3DX10/D3DX10CreateEffectFromResource()
D3DX10/D3DX10CreateEffectPoolFromFile()
D3DX10/D3DX10CreateEffectPoolFromMemory()
D3DX10/D3DX10CreateEffectPoolFromResource()
D3DX10/D3DX10CreateAsync~

・Disassemble 系 API  インターフェース → Blob の disassemble リスト

ID3D10Shader/D3D10DisassembleShader()
ID3D10Effect/D3D10DisassembleEffect()
D3DX10/D3DX10DisassembleShader()
D3DX10/D3DX10DisassembleEffect()

・Preprocessor 系 API  HLSL/fx → Blob のテキスト

ID3D10Shader/D3D10PreprocessShader()
D3DX10/D3DX10PreprocessShaderFromFile()
D3DX10/D3DX10PreprocessShaderFromMemory()
D3DX10/D3DX10PreprocessShaderFromResource()

必要な機能がほとんどそろっています。fxc.exe はコマンド
ライン引数のパースだけで、あとは D3DX10 を呼び出している
だけのようです。


D3DX10 の D3DX10Create~ は HLSL からのコンパイルだけでなく、
コンパイル済みデータ(fxo)を受け取ることも出来ます。


Preprocessor は Include や Macro 等のテキスト処理を行い、
結果を返してくれる便利な関数です。まず、シェーダー
コンパイル時にマクロの適用がきちんと行われているか確認
することができます。

またシェーダーにこだわらず、script 系言語の汎用のテキスト
処理フィルタとして流用できるかもしれません。

ただ一旦パーサにかけたテキストを再構築して出力している
らしく、無駄な空白や空行が除去され各キーワードがスペース
区切りに置き換わっています。
わかりやすいし処理しやすいものの、#line 等のディレクティブも
失われてしまいます。
本当のプリプロセッサとして活用する場合は、エラー時の行番号
を求めることが出来ず、デバッグしづらくなるのが難点でしょう。


Disassemble/Preprocessor など Blob のテキストで返す API は
'\0' 終端でかつ、ID3D10Blob::GetBufferSize() が '\0' を
含めたサイズを返す点に注意です。

例えばマニュアルの D3D10DisassembleEffect の説明には下記の
プログラム例が載っています。(下記はマニュアルからの引用)

LPCSTR commentString = NULL;
ID3D10Blob* pIDisassembly = NULL;
char* pDisassembly = NULL;
if( pVSBuf )
{
    D3D10DisassembleEffect( (UINT*)
    	l_pBlob_Effect->GetBufferPointer(),
        l_pBlob_Effect->GetBufferSize(), TRUE, commentString,
	&pIDisassembly );
    if( pIDisassembly )
    {
        FILE* pFile = fopen( "effect.htm", "w" );
        if( pFile)
        {
            fputs( (char*)pIDisassembly->GetBufferPointer(), pFile );
            fclose( pFile );
        }
    }
}

もしこれを次のように書くと、テキストファイルの終端に '\0' が
含まれてしまいます。
(__Write は指定バイト数ファイルに書き込む仮想 API とする)

__Write( pIDisassembly->GetBufferPointer(),
		pIDisassembly->GetBufferSize() );



D3DX10CompileFromFile() は開始関数名の指定があるので、一見
Shader コンパイル専用の関数に見えます。実際は Effect(fx) の
コンパイルも可能です。この場合関数名 pFunctionName には NULL
を渡します。


Compile 系 API に渡す D3D10_SHADER_MACRO は、#define ~ に
相当する定義リストを渡すためのものです。この定義リストは
定義内容を文字列で渡す必要があり、かつ NULL 終端です。
マニュアルの D3D10_SHADER_MACRO の記述例では size [1] の配列を
使っていてこの NULL 終端が含まれていないので注意です。

以下マニュアルより引用
D3D10_SHADER_MACRO Shader_Macros[1] = { "zero", "0"  };

実際に使う場合はこんな感じです。

D3D10_SHADER_MACRO Shader_Macros[]= {
	{  "zero", "0"  },
	{  "TEXLAYER", "2"  },
	{  "POINTLIGHT", "8"  },
	{  NULL, NULL  },
};


HYPERでんち の方もいくつか更新しました。
シェーダーの世代ごとの違い
DirectX SDK バージョン一覧