2011/12/10
Android 3.x RenderScript で 3D モデルのライティング
RenderScript で光源やカメラ位置を使った描画方法を取り上げて欲しいとの
メールをいただいたので試してみました。

OpenGL ES 2.0 なのでこれらのパラメータの反映はシェーダー任せです。
演算は RenderScript よりも GLSL の範疇になるかと思いますが、
複数の ConstantBuffer の作製や PixelShader への渡し方などを参考にして
いただければと思います。
●必要なデータ構造の定義と初期化
RenderScript で、必要な情報の構造体を宣言します。
内容は Java, RenderScript, Shader でそれぞれ共有します。
ジオメトリ関連
描画オブジェクトのマテリアル
光源。Intensity は Color に事前乗算しておく。
Java 上でインスタンスを作って初期化します。
カメラは座標と注視点を登録しています。
テクスチャを使わないのでマテリアルカラーを設定。
光源を設定
作成した Constant Buffer をシェーダーで読めるようにします。
Builder で必要な数だけ addConstant() して Uniform を予約します。
インスタンスは ProgramVertex の bindConstants() で渡します。
必要な constnat buffer だけ渡しています。
VertexShader は GeometryConstant と LightConstant。
PixelShader (FragmentShader) も同様です。
MaterialConstant/LightConstant の 2つです。
Shader と同じように、RenderScript にもアクセスするデータを渡します。
GeometryConstant/LightConstant の 2つ。
●RenderScript
ViewMatrix (WorldSpace To CameraSpace) の作成と ProjectionMatrix
との前計算を行なっています。
CameraPosition/CameraAim, ProjectionMatrix を元に、
PView (ProjectionMatrix * ViewMatrix) を求めます。
RenderScript は C言語ながら、上記のようにシェーダーのような vector 記法
が使えます。例えば float4 で宣言した変数も .xyz をつけることで float3
の部分アクセスができます。
swizzle もできました。使いやすいです。
RenderScritp に LookAt 関数があるかと思ったらなかったので作ります。
これ以外に、サンプルの script では効果がわかりやすいように
カメラ位置と点光源のアニメーションを行なっています。
●Shader
シェーダーでは与えられたパラメータを元に座標と色の計算を行なっています。
ScreenSpace だけでなく WorldSpace での座標と法線の算出。
MaterialConstant と LightConstant を用いてライティング。
●ソースコード
モデルデータの読み込みは以前のエントリと同じ物を使っています。
・flatlib_ap02d.zip ソースコード
●最適化
無駄が多いので最適化の余地があります。
ConstantBuffer は更新するデータ量が少なくなるように分割できます。
今回は Java, RenderScript, Shader でバッファを共有していますが不要な
パラメータがあります。RenderScript で書き換えるものと Shader だけが
参照するものをわけると転送量を減らせます。
それでも ConstantBuffer (Uniform) は頂点書き換えと異なり PushBuffer
を経由するため lock が発生しないため効率は良いです。
PixelShader (FragmentShader) での演算は負荷が重いので、出来る限り
シェーダーの外に出した方が良いです。
MaterialColor * LightColor はプリミティブ単位で求まるので前計算可能です。
頂点単位で十分なものは VertexShader に分散させることができます。
関連エントリ
・Android 3.x RenderScript (7) RenderScript Compute の速度
・Android 3.x RenderScript (6) Graphics まとめ
・Android 3.x RenderScript (5) 任意の 3D モデルを描画する
・Android 3.x RenderScript (4) script で頂点を書き換える
・Android 3.x RenderScript (3) 独自シェーダーの割り当てとメッシュの描画(2D)
・Android 3.x RenderScript (2) 描画と Allocation
・Android 3.x RenderScript (1)
メールをいただいたので試してみました。

OpenGL ES 2.0 なのでこれらのパラメータの反映はシェーダー任せです。
演算は RenderScript よりも GLSL の範疇になるかと思いますが、
複数の ConstantBuffer の作製や PixelShader への渡し方などを参考にして
いただければと思います。
●必要なデータ構造の定義と初期化
RenderScript で、必要な情報の構造体を宣言します。
内容は Java, RenderScript, Shader でそれぞれ共有します。
ジオメトリ関連
// RenderScript typedef struct GeometryConstant { rs_matrix4x4 Projection; // Projection Matrix rs_matrix4x4 World; // World Matrix rs_matrix4x4 PView; // RS → Shader 用 float4 CameraPosition; // (x y z -) カメラ位置 float4 CameraAim; // (x y z -) 注視点 } GeometryConstant_T;
描画オブジェクトのマテリアル
// RenderScript typedef struct MaterialConstant { float4 AmbientColor; // (r g b -) float4 DiffuseColor; // (r g b -) float4 SpecularColor; // (r g b p) } MaterialConstant_T;
光源。Intensity は Color に事前乗算しておく。
// RenderScript typedef struct LightConstant { float4 AmbientLightColor; // (r g b -) float4 DirectionalLightColor; // (r g b -) float4 PointLightColor; // (r g b -) float4 DirectionalLightDirection; // (x y z -) float4 PointLightPosition; // (x y z -) } LightConstant_T;
Java 上でインスタンスを作って初期化します。
カメラは座標と注視点を登録しています。
// Java // Geometry Uniform ScriptField_GeometryConstant geometry_const= new ScriptField_GeometryConstant( mRS, 1, Allocation.USAGE_SCRIPT|Allocation.USAGE_GRAPHICS_CONSTANTS ); // Projection Matrix Matrix4f projection= new Matrix4f(); projection.loadPerspective( 30.0f, mWidth/mHeight, 0.1f, 100.0f ); geometry_const.set_Projection( 0, projection, true ); // Camera Position geometry_const.set_CameraPosition( 0, new Float4( 0.0f, 0.0f, 3.0f, 0.0f ), true ); geometry_const.set_CameraAim( 0, new Float4( 0.0f, 0.0f, 0.0f, 0.0f ), true ); // World Matrix Matrix4f world= new Matrix4f(); world.loadIdentity(); geometry_const.set_World( 0, world, true );
テクスチャを使わないのでマテリアルカラーを設定。
// Java // Material Uniform ScriptField_MaterialConstant material_const= new ScriptField_MaterialConstant( mRS, 1, Allocation.USAGE_SCRIPT|Allocation.USAGE_GRAPHICS_CONSTANTS ); material_const.set_AmbientColor( 0, new Float4( 0.3f, 0.3f, 0.3f, 1.0f ), true ); material_const.set_DiffuseColor( 0, new Float4( 1.0f, 0.9f, 0.9f, 1.0f ), true ); material_const.set_SpecularColor( 0, new Float4( 1.0f, 1.0f, 0.4f, 30.0f ), true );
光源を設定
// Light Uniform ScriptField_LightConstant light_const= new ScriptField_LightConstant( mRS, 1, Allocation.USAGE_SCRIPT|Allocation.USAGE_GRAPHICS_CONSTANTS ); light_const.set_AmbientLightColor( 0, new Float4( 0.3f, 0.3f, 0.3f, 1.0f ), true ); light_const.set_DirectionalLightColor( 0, new Float4( 1.0f, 1.0f, 1.0f, 1.0f ), true ); light_const.set_PointLightColor( 0, new Float4( 0.0f, 1.0f, 1.0f, 1.0f ), true ); light_const.set_DirectionalLightDirection( 0, new Float4( 0.57f, -0.57f, -0.57f, 0.0f ), true ); light_const.set_PointLightPosition( 0, new Float4( 0.0f, 0.2f, 0.0f, 0.0f ), true );
作成した Constant Buffer をシェーダーで読めるようにします。
Builder で必要な数だけ addConstant() して Uniform を予約します。
インスタンスは ProgramVertex の bindConstants() で渡します。
必要な constnat buffer だけ渡しています。
VertexShader は GeometryConstant と LightConstant。
// Java // VertexShader ProgramVertex.Builder vsh_builder= new ProgramVertex.Builder( mRS ); vsh_builder.setShader( mRes, R.raw.mesh3d_vsh ); vsh_builder.addConstant( geometry_const.getType() ); vsh_builder.addConstant( light_const.getType() ); vsh_builder.addInput( model.getElement() ); ProgramVertex vsh= vsh_builder.create(); vsh.bindConstants( geometry_const.getAllocation(), 0 ); vsh.bindConstants( light_const.getAllocation(), 1 );
PixelShader (FragmentShader) も同様です。
MaterialConstant/LightConstant の 2つです。
// Java // PixelShader ProgramFragment.Builder psh_builder= new ProgramFragment.Builder( mRS ); psh_builder.setShader( mRes, R.raw.mesh3d_psh ); psh_builder.addConstant( material_const.getType() ); psh_builder.addConstant( light_const.getType() ); ProgramFragment psh= psh_builder.create(); psh.bindConstants( material_const.getAllocation(), 0 ); psh.bindConstants( light_const.getAllocation(), 1 );
Shader と同じように、RenderScript にもアクセスするデータを渡します。
GeometryConstant/LightConstant の 2つ。
// Java mScript.bind_vconst( geometry_const ); mScript.bind_light( light_const );
●RenderScript
ViewMatrix (WorldSpace To CameraSpace) の作成と ProjectionMatrix
との前計算を行なっています。
CameraPosition/CameraAim, ProjectionMatrix を元に、
PView (ProjectionMatrix * ViewMatrix) を求めます。
// RenderScript rs_matrix4x4 viewMatrix; LookAt( &viewMatrix, vconst->CameraPosition.xyz, vconst->CameraAim.xyz ); rsMatrixLoadMultiply( &vconst->PView, &vconst->Projection, &viewMatrix );
RenderScript は C言語ながら、上記のようにシェーダーのような vector 記法
が使えます。例えば float4 で宣言した変数も .xyz をつけることで float3
の部分アクセスができます。
float4 vec; float3 pos= vec.xyz;
swizzle もできました。使いやすいです。
float4 d= vec.wzyx; float4 e= vec.yyyy; float4 f; f.zw= vec.xy;
RenderScritp に LookAt 関数があるかと思ったらなかったので作ります。
// RenderScript static void LookAt( rs_matrix4x4* view, float3 pos, float3 aim ) { rsMatrixLoadIdentity( view ); float3 up= { 0.0f, 1.0f, 0.0f }; float3 pz= normalize( aim - pos ); float3 px= normalize( cross( pz, up ) ); float3 py= normalize( cross( px, pz ) ); view->m[0]= px.x; view->m[4]= px.y; view->m[8]= px.z; view->m[1]= py.x; view->m[5]= py.y; view->m[9]= py.z; view->m[2]= -pz.x; view->m[6]= -pz.y; view->m[10]= -pz.z; rsMatrixTranslate( view, -pos.x, -pos.y, -pos.z ); }
これ以外に、サンプルの script では効果がわかりやすいように
カメラ位置と点光源のアニメーションを行なっています。
●Shader
シェーダーでは与えられたパラメータを元に座標と色の計算を行なっています。
ScreenSpace だけでなく WorldSpace での座標と法線の算出。
// GLSL varying vec3 onormal; varying vec3 oeye; varying vec3 opos; void main() { vec4 wpos= UNI_World * vec4( ATTRIB_position.xyz, 1.0 ); gl_Position= UNI_PView * wpos; onormal.xyz= mat3(UNI_World) * ATTRIB_normal.xyz; oeye.xyz= UNI_CameraPosition.xyz - wpos.xyz; opos.xyz= wpos.xyz; }
MaterialConstant と LightConstant を用いてライティング。
// GLSL precision mediump float; varying vec3 onormal; varying vec3 oeye; varying vec3 opos; void main() { vec3 normal= normalize( onormal.xyz ); // AmbientLight lowp vec3 diffuse= UNI_AmbientLightColor.xyz * UNI_AmbientColor.xyz; // DirectinalLight float dd= clamp( dot( normal.xyz, -UNI_DirectionalLightDirection.xyz ), 0.0, 1.0 ); diffuse.xyz+= UNI_DirectionalLightColor.xyz * UNI_DiffuseColor.xyz * dd; float ds= pow( clamp( dot( normal.xyz, normalize( oeye.xyz - UNI_DirectionalLightDirection.xyz ) ), 0.0, 1.0 ), UNI_SpecularColor.w ); lowp vec3 specular= UNI_SpecularColor.xyz * ds; // PointLight vec3 lightvec= UNI_PointLightPosition.xyz - opos.xyz; vec3 lightdir= normalize( lightvec.xyz ); float dl= dot( lightvec.xyz, lightvec.xyz ) * 10.0; float pd= clamp( dot( normal.xyz, lightdir.xyz ), 0.0, 1.0 ) / dl; diffuse.xyz+= UNI_PointLightColor.xyz * UNI_DiffuseColor.xyz * pd; gl_FragColor.xyz= diffuse.xyz + specular.xyz; gl_FragColor.w= 1.0; }
●ソースコード
モデルデータの読み込みは以前のエントリと同じ物を使っています。
・flatlib_ap02d.zip ソースコード
●最適化
無駄が多いので最適化の余地があります。
ConstantBuffer は更新するデータ量が少なくなるように分割できます。
今回は Java, RenderScript, Shader でバッファを共有していますが不要な
パラメータがあります。RenderScript で書き換えるものと Shader だけが
参照するものをわけると転送量を減らせます。
それでも ConstantBuffer (Uniform) は頂点書き換えと異なり PushBuffer
を経由するため lock が発生しないため効率は良いです。
PixelShader (FragmentShader) での演算は負荷が重いので、出来る限り
シェーダーの外に出した方が良いです。
MaterialColor * LightColor はプリミティブ単位で求まるので前計算可能です。
頂点単位で十分なものは VertexShader に分散させることができます。
関連エントリ
・Android 3.x RenderScript (7) RenderScript Compute の速度
・Android 3.x RenderScript (6) Graphics まとめ
・Android 3.x RenderScript (5) 任意の 3D モデルを描画する
・Android 3.x RenderScript (4) script で頂点を書き換える
・Android 3.x RenderScript (3) 独自シェーダーの割り当てとメッシュの描画(2D)
・Android 3.x RenderScript (2) 描画と Allocation
・Android 3.x RenderScript (1)
2011/12/02
Android 用ゲームパッド BUFFALO Zeemote JS1 H
Android 用のゲームコントローラ Zeemote JS1 H を試してみました。
Zeemote の特徴は Bluetooth によるワイヤレス接続であることと、
通信に HID ではなく SPP を使うことです。
Bluetooth さえあれば Android 2.1 以降のほとんどの端末で使えるようです。
・BUFFALO Zeemote JS1 H (BSGPJS1)
見た目はヌンチャクのようですが非常に小さくモーションセンサーはありません。
アナログスティックx1 + デジタル 4ボタンのシンプルな構成です。
●3つのモード
Bluetooth の HID または SPP プロファイルで接続されています。
下記のように 3つの動作モードを持っています。
電源投入時に押していたボタン (A)~(C) によってモードが決まります。
(A)/(B) は通常の Bluetooth 入力デバイスとして認識されるので、
HID に対応していれば Android に限らず使うことができます。
実際に Windows PC に接続して 3ボタンマウスの代わりに使えました。
キーボードモードではカーソルキーや Enter/ESC として利用できます。
Android の場合 HID 対応デバイスは限られているので、(A)/(B) のモードが
使えるかどうかは端末や OS のバージョン依存となります。
(C) のジョイスティックモードでつなぐには専用の SDK を利用した
Zeemote 対応アプリが必要です。
対応アプリケーションであれば (C) のモードでつながるので HID に
対応していない端末でも使用することができます。
●ペアリング
ペアリングを行う方法は二種類あります。
(1) Android の設定画面で自分でペアリングする
(2) "JS1 Quick Start Application" を使う (Android Market から入手)
基本的には Bluetooth の設定画面でペアリングできます。
固定キー 0000 を入力するだけです。
ただ試したところ、一部の端末では固定キーの入力ができないことが
あるようです。
HTC EVO 3D (ISW12HT) はペアリング時にキー入力画面になりませんでした。
このような場合 (2) の方法が使えます。
1. "JS1 Quick Start Application" を Android Market からダウンロードして
インストールする。
2. Zeemote をジョイスティックモードにする
(電源を切った状態で (C) を押しながら (D)ボタン長押しで電源を入れる)
3. "JS1 Quick Start Application" を起動する。
4.「JS1に接続する」を押す
5.「ペアリングを自動で行う」にチェックが入った状態で「接続」を押す
6. もしここでエラーが出たら Manual #3 に変更して接続する。
接続ができたらアプリを終了して構いません。
これでペアリング登録された状態になっています。
●対応アプリケーション
動作テストには上で説明した "JS1 Quick Start Application" 及び
R-TYPE Lite/R-TYPE を使いました。
色々試してみたところ、対応アプリケーション (Zeemote SDK) は
どうやら Bluetooth のペアリング情報を見て判別しているようです。
1. Bluetooth でペアリングされたデバイスを列挙する
2. "Zeemote JS1 H" が含まれていればデバイス選択画面を表示し接続を行う
また各アプリが起動後にあらためて Zeemote との再接続を行います。
例えばポインターモードで OS を操作していても、アプリ起動後の再接続を
行ったタイミングで (C) のジョイスティックモードに切り替わります。
●接続してみた端末
手持ちの端末をつないでみました。
(A)/(B) のポインター/キーボードモードが使えるかどうかは端末依存です。
(C) のジョイスティックモードはすべての機種で使うことができました。
●Android 3.x HID と Zeemote
ICONIA TAB A500 Android 3.2 は (A) のポインターモードできちんと
つながります。
マウスカーソルが表示されてタッチの代わりに使えるだけでなく、
B ボタンがバックボタンになるので快適にリモート操作できました。
HDMI で大画面につないだ場合のリモコンとしても使えそうです。
Optimus Pad L-06C Android 3.1 は、マウスカーソルが出るものの
B ボタンがバックとして機能しませんでした。
端末依存なのか OS バージョンによる違いなのかはわかりません。
●ゲームで使った Zeemote
アナログスティックなので 3D 系のゲームに向いているかもしれません。
R-TYPE 等の 2D ゲームでデジタル方向キーの代わりに使った場合は
ストロークの深さが若干気になります。
完全に倒しこまなくても反応するため、ボタンを離したつもりでも
ニュートラルに戻すまでの間よけいに動いてしまうからです。
使っているうちに慣れてきて両手で操作したら微調整しやすくなりました。
外装はすべらないラバー状になっていますがゴミを吸いやすく
剥がれやすいのが気になりました。
●開発者から見た Zeemote
Android 3.1 以降の端末は USB のゲームコントローラに対応しています。
USB Host があれば Xbox360 や PS3 のコントローラをつなぐことが出来ました。
ただ問題もあります。
USB HOST と USB 端子兼用の端末が多く、開発時に USB ケーブルでデバッガを
繋いでいるとゲームコントローラを使うことができません。
そのため気に入っているのが ICONIA TAB A500 で、Micro USB 端子の他に
独立したフルサイズの USB HOST コネクタを備えているのでたいへん重宝しています。
Zeemote は Bluetooth 接続なのでこのような問題が起こりません。
デバッガをつないでいても使えるのは魅力的です。
関連エントリ
・Android 3.1 と GamePad のイベントの詳細 (2)
・Android 3.1 と GamePad のイベントコード
Zeemote の特徴は Bluetooth によるワイヤレス接続であることと、
通信に HID ではなく SPP を使うことです。
Bluetooth さえあれば Android 2.1 以降のほとんどの端末で使えるようです。
・BUFFALO Zeemote JS1 H (BSGPJS1)
見た目はヌンチャクのようですが非常に小さくモーションセンサーはありません。
アナログスティックx1 + デジタル 4ボタンのシンプルな構成です。
●3つのモード
Bluetooth の HID または SPP プロファイルで接続されています。
下記のように 3つの動作モードを持っています。
(A) ポインターモード HID マウスエミュレーション (B) キーボードモード HID キーボードエミュレーション (C) ジョイスティックモード SPP 専用のジョイスティックモード
電源投入時に押していたボタン (A)~(C) によってモードが決まります。
(A)/(B) は通常の Bluetooth 入力デバイスとして認識されるので、
HID に対応していれば Android に限らず使うことができます。
実際に Windows PC に接続して 3ボタンマウスの代わりに使えました。
キーボードモードではカーソルキーや Enter/ESC として利用できます。
Android の場合 HID 対応デバイスは限られているので、(A)/(B) のモードが
使えるかどうかは端末や OS のバージョン依存となります。
(C) のジョイスティックモードでつなぐには専用の SDK を利用した
Zeemote 対応アプリが必要です。
対応アプリケーションであれば (C) のモードでつながるので HID に
対応していない端末でも使用することができます。
●ペアリング
ペアリングを行う方法は二種類あります。
(1) Android の設定画面で自分でペアリングする
(2) "JS1 Quick Start Application" を使う (Android Market から入手)
基本的には Bluetooth の設定画面でペアリングできます。
固定キー 0000 を入力するだけです。
ただ試したところ、一部の端末では固定キーの入力ができないことが
あるようです。
HTC EVO 3D (ISW12HT) はペアリング時にキー入力画面になりませんでした。
このような場合 (2) の方法が使えます。
1. "JS1 Quick Start Application" を Android Market からダウンロードして
インストールする。
2. Zeemote をジョイスティックモードにする
(電源を切った状態で (C) を押しながら (D)ボタン長押しで電源を入れる)
3. "JS1 Quick Start Application" を起動する。
4.「JS1に接続する」を押す
5.「ペアリングを自動で行う」にチェックが入った状態で「接続」を押す
6. もしここでエラーが出たら Manual #3 に変更して接続する。
接続ができたらアプリを終了して構いません。
これでペアリング登録された状態になっています。
●対応アプリケーション
動作テストには上で説明した "JS1 Quick Start Application" 及び
R-TYPE Lite/R-TYPE を使いました。
色々試してみたところ、対応アプリケーション (Zeemote SDK) は
どうやら Bluetooth のペアリング情報を見て判別しているようです。
1. Bluetooth でペアリングされたデバイスを列挙する
2. "Zeemote JS1 H" が含まれていればデバイス選択画面を表示し接続を行う
また各アプリが起動後にあらためて Zeemote との再接続を行います。
例えばポインターモードで OS を操作していても、アプリ起動後の再接続を
行ったタイミングで (C) のジョイスティックモードに切り替わります。
●接続してみた端末
手持ちの端末をつないでみました。
(A)/(B) のポインター/キーボードモードが使えるかどうかは端末依存です。
(C) のジョイスティックモードはすべての機種で使うことができました。
Galaxy S2 (SC-02C) Android 2.3 EVO 3D (ISW12HT) Android 2.3 Desire (X06HT) Android 2.2 IDEOS Android 2.2 ICONIA TAB (A500) Android 3.2 Optimus Pad (L-06C) Android 3.1 LifeTouch NOTE (NA75W) Android 2.2
●Android 3.x HID と Zeemote
ICONIA TAB A500 Android 3.2 は (A) のポインターモードできちんと
つながります。
マウスカーソルが表示されてタッチの代わりに使えるだけでなく、
B ボタンがバックボタンになるので快適にリモート操作できました。
HDMI で大画面につないだ場合のリモコンとしても使えそうです。
Optimus Pad L-06C Android 3.1 は、マウスカーソルが出るものの
B ボタンがバックとして機能しませんでした。
端末依存なのか OS バージョンによる違いなのかはわかりません。
●ゲームで使った Zeemote
アナログスティックなので 3D 系のゲームに向いているかもしれません。
R-TYPE 等の 2D ゲームでデジタル方向キーの代わりに使った場合は
ストロークの深さが若干気になります。
完全に倒しこまなくても反応するため、ボタンを離したつもりでも
ニュートラルに戻すまでの間よけいに動いてしまうからです。
使っているうちに慣れてきて両手で操作したら微調整しやすくなりました。
外装はすべらないラバー状になっていますがゴミを吸いやすく
剥がれやすいのが気になりました。
●開発者から見た Zeemote
Android 3.1 以降の端末は USB のゲームコントローラに対応しています。
USB Host があれば Xbox360 や PS3 のコントローラをつなぐことが出来ました。
ただ問題もあります。
USB HOST と USB 端子兼用の端末が多く、開発時に USB ケーブルでデバッガを
繋いでいるとゲームコントローラを使うことができません。
そのため気に入っているのが ICONIA TAB A500 で、Micro USB 端子の他に
独立したフルサイズの USB HOST コネクタを備えているのでたいへん重宝しています。
Zeemote は Bluetooth 接続なのでこのような問題が起こりません。
デバッガをつないでいても使えるのは魅力的です。
関連エントリ
・Android 3.1 と GamePad のイベントの詳細 (2)
・Android 3.1 と GamePad のイベントコード