RSSurfaceView に script (*.rs) を登録すると毎フレーム root() 関数が
呼び出され、描画はその中で行います。
描画に使う各種リソースは Java で生成し script に渡します。
script ではこれらのデータやパラメータを組み合わせた描画が可能です。
ジオメトリを計算してアニメーションを行ったり、データを動的に書き換えて
表示内容を更新することもできます。
// rendermesh.rs #pragma version(1) #pragma rs java_package_name(jp.flatlib.ap02) #include "rs_graphics.rsh" rs_mesh mesh; // class Mesh rs_program_vertex vsh; // class ProgramVertex rs_program_fragment psh; // class ProgramFragment rs_program_store dsbstate; // class ProgramStore rs_program_raster rstate; // class ProgramRaster rs_sampler sampler; // class Sampler rs_allocation texture; // class Allocation int root() // 毎フレーム呼ばれる { rsgClearColor( 0.5f, 0.5f, 0.0f, 0.0f ); rsgBindSampler( psh, 0, sampler ); rsgBindTexture( psh, 0, texture ); rsgBindProgramFragment( psh ); rsgBindProgramVertex( vsh ); rsgBindProgramStore( dsbstate ); rsgBindProgramRaster( rstate ); rsgDrawMesh( mesh ); // ポリゴンを描画する return 33; // interval 33msec }
コメント部分 ‘// class’ の後ろは対応する Java class 名です。
Mesh : 頂点とインデックスの集合。描画するポリゴンの集まり。 ProgramVertex : 頂点シェーダー。座標の変換を記述するプログラム。GLSL ProgramFragment : ピクセルシェーダー。描画する色を生成するプログラム。GLSL ProgramStore : レンダーステート。D3Dの DepthStencilState + BlendState ProgramRaster : レンダーステート。D3Dの RasterizerState Sampler : テクスチャフィルタ等。D3Dの SamplerState Allocation : Buffer, Texture, Uniform 等、script/HW が扱うメモリ。
script の Reflection class が作られるので、Java から script の変数に
容易にアクセスすることができます。
// java ScriptC_rendermesh script= new ScriptC_rendermesh( mRS, mRes, R.raw.rendermesh ); ~ mScript.set_mesh( mesh ); mScript.set_vsh( vsh ); mScript.set_psh( psh ); mScript.set_dsbstate( ProgramStore.BLEND_NONE_DEPTH_NONE( mRS ) ); mScript.set_rstate( ProgramRaster.CULL_NONE( mRS ) ); mScript.set_sampler( Sampler.WRAP_LINEAR( mRS ) ); mScript.set_texture( texture ); mRS.bindRootScript( script );
● Mesh
任意形状の描画には Mesh を使います。
Mesh は 頂点の Allocation と インデックス Allocation の集合です。
TriangleMeshBuilder を使うと Allocation を意識せずに簡単に Mesh を
作ることができます。
// java Mesh.TriangleMeshBuilder builder= new Mesh.TriangleMeshBuilder( mRS, 2, Mesh.TriangleMeshBuilder.COLOR ); // 4頂点の定義 builder.setColor( 1f, 0f, 0f, 1f ).addVertex( 0.0f, 0.0f ); builder.setColor( 0f, 1f, 0f, 1f ).addVertex( 200.0f, 0.0f ); builder.setColor( 0f, 0f, 1f, 1f ).addVertex( 0.0f, 200.0f ); builder.setColor( 1f, 1f, 1f, 1f ).addVertex( 200.0f, 200.0f ); // index x 2 の定義 (2ポリゴン) builder.addTriangle( 0, 2, 1 ); builder.addTriangle( 1, 2, 3 ); Mesh mesh= builder.create( true );
その代わり TriangleMeshBuilder では頂点形式が固定であまり自由度がありません。
上のサンプルでは頂点要素は「2D頂点座標(xy) + 頂点カラー」ですが頂点カラーが
float4 (F32_4) で格納されてしまいます。
頂点形式の自由度が欲しい場合、また頂点データを script で書き換える場合は
Mesh.AllocationBuilder を使います。(後述)
● シェーダーファイル GLSL
シェーダーは OpenGL ES の GLSL ES でテキストファイルです。
そのまま res/raw 以下のフォルダに入れておきます。
リソース名には拡張子がつかないので、拡張子が無くても区別できるファイル名に
しておきます。
// mesh2dc_vsh.vsh varying vec4 ocolor; void main() { gl_Position.xy= ATTRIB_position.xy * UNI_Transform.xy + UNI_Transform.zw; gl_Position.zw= vec2( 0.0, 1.0 ); ocolor.xyzw= ATTRIB_color.xyzw; }
入力頂点形式 (attribute) や Uniform の宣言は不要です。
renderscript.ProgramVertex が内部の情報を元にヘッダを挿入してくれるからです。
命名ルールは下記のようになっています。Element 名に prefix が付きます。
ATTRIB_<頂点Element> attribute UNI_<Constant> uniform UNI_<Texture> texture
varying (In/Out) は自分で宣言します。
// mesh2dc_psh.psh precision mediump float; varying vec4 ocolor; void main() { gl_FragColor.xyzw= ocolor.xyzw; }
Java, RenderScript(C言語), GLSL と複数の言語を併用することになるので
慣れないと混乱しがちです。
例として vector や matrix の表現をあげると下記の通り。
RenderScript GLSL Java Element ----------------------------------------------------- float float float F32 float2 vec2 Float2 F32_2 float3 vec3 Float3 F32_3 float4 vec4 Float4 F32_4 rs_matrix3x3 mat3 Matrix3f MATRIX_3X3 rs_matrix4x4 mat4 Matrix4f MATRIX_4X4
C言語ベースの RenderScript, hlsl, Cg, OpenCL 等は比較的似ています。
この中では GLSL が特殊です。いろいろと。
● Uniform の宣言、データ構造
シェーダーで用いる Constant Buffer (Uniform) は好きなように作ることができます。
Element を組み立てるよりも RenderScript の reflection を使った方が簡単です。
(1) RenderScript/GLSL が参照するリソースの構造宣言は RenderScript (*.rs) で行う。
(2) RenderScript で宣言された構造は Reflection class として Java で参照できる。ScriptFiled_ が付く。
(3) リソースの生成は Java で行う。
(4) 生成したリソースの書き込みは下記の通り。
・Java 生成 → script で参照
・Java 生成 → WH で参照
・Java 生成 → script/WH 両方で参照
・Java 生成 → scriptで書き換え → WH で参照
今回の例では 2D のトランスフォーム用に float4 を渡したいので、
RenderScript で下記の宣言を行います。
これで ScriptField_ConstantType2D が作られます。
// renderscript typedef struct ConstantType2D { float4 Transform; } ConstantType2D_t; ConstantType2D_t* ConstantType2D_n; // unuse
ConstantType2D_n はダミーです。
script から参照していないと Reflection class ができないためです。
Java では ScriptField_ConstantType2D をインスタンス化するだけで
ハードウエアに設定可能なメモリ領域 (Allocation) が作られます。
データの書き込みも ScriptField_ConstantType2D を通じて行うことができます。
// java // メモリ領域作成 ScriptField_ConstantType2D vconst2d= new ScriptField_ConstantType2D( mRS, 1, Allocation.USAGE_GRAPHICS_CONSTANTS ); // 値の書き込み (HW に転送可能) vconst2d.set_Transform( 0, new Float4( 2.0f/mWidth, -2.0f/mHeight, -1.0f, 1.0f ), true );
script 側でも参照したり書き換えたい場合は、Allocation.USAGE_SCRIPT を追加し
script 内で受け取るポインタを宣言しておきます。
次に vconst2d.getAllocation() をそのポインタに bind_ します。
● ProgramVertex / ProgramFragment
リソースとして配置したシェーダーを読み込むコード。
// java public static String LoadResource( Context context, int resid ) { InputStream is= context.getResources().openRawResource( resid ); byte[] buf= null; try { int size= is.available(); buf= new byte[size]; is.read( buf ); is.close(); }catch( IOException e ){ } return new String( buf ); }
シェーダーを読み込んで ProgramVertex/ProgramFragment を作ります。
// java // mesh から頂点フォーマット (Element) を取り出す Element vtype= mesh.getVertexAllocation( 0 ).getType().getElement(); // VertexShader ProgramVertex.Builder vsh_builder= new ProgramVertex.Builder( mRS ); // シェーダーファイル読み込み vsh_builder.setShader( LoadResource( mContext, R.raw.mesh2dc_vsh ) ); vsh_builder.addConstant( vconst2d.getType() ); // Uniform 登録 vsh_builder.addInput( vtype ); // Attribute 登録 ProgramVertex vsh= vsh_builder.create(); vsh.bindConstants( vconst2d.getAllocation(), 0 ); // Uniform のメモリ領域を登録
頂点シェーダーの入力頂点形式 (attribute) は Mesh と一致させておく必要が
あります。上の例のように Mesh から vtype を取り出すことができます。
なお TriangleMeshBuilder を使った場合の Element 名は固定です。
順番と名称は次の通り: ‘position’, ‘color’, ‘texture’, ‘normal’
// java // PixelShader ProgramFragment.Builder psh_builder= new ProgramFragment.Builder( mRS ); // シェーダーファイル読み込み psh_builder.setShader( LoadResource( mContext, R.raw.mesh2dc_psh ) ); ProgramFragment psh= psh_builder.create();
Texture を用いる場合は psh_builder で AddTexture() します。
VertexShader と同じように uniform ヘッダが挿入されます。
● ProgramStore / ProgramRaster / Sampler
よく使う Object が予め用意されているので、既存のものを渡すだけで十分です。
今回の例では特に設定せず、デフォルトのまま使用しています。
●ソースコード
テクスチャを使わない 2D mesh 描画のコードはこちら。
// rendermesh.rs #pragma version(1) #pragma rs java_package_name(jp.flatlib.ap02a) #include "rs_graphics.rsh" typedef struct ConstantType2D { float4 Transform; } ConstantType2D_t; ConstantType2D_t* ConstantType2D_n; // unuse rs_program_vertex vsh; rs_program_fragment psh; rs_mesh mesh; int root() { rsgClearColor( 0.5f, 0.5f, 0.0f, 0.0f ); rsgBindProgramFragment( psh ); rsgBindProgramVertex( vsh ); rsgDrawMesh( mesh ); return 33; }
// SimpleView.java package jp.flatlib.ap02a; import android.content.Context; import android.content.res.Resources; import android.renderscript.RSSurfaceView; import android.renderscript.RenderScriptGL; import android.renderscript.Float4; import android.renderscript.Allocation; import android.renderscript.ProgramVertex; import android.renderscript.ProgramFragment; import android.renderscript.Mesh; import android.view.SurfaceHolder; import java.io.InputStream; import java.io.IOException; public class SimpleView extends RSSurfaceView { private RenderScriptGL mRS; private Resources mRes; private Context mContext; private float mWidth; private float mHeight; private ScriptField_ConstantType2D a_vconst2d; public SimpleView( Context context ) { super( context ); createRS(); } public static String LoadResource( Context context, int resid ) { InputStream is= context.getResources().openRawResource( resid ); byte[] buf= null; try { int size= is.available(); buf= new byte[size]; is.read( buf ); is.close(); }catch( IOException e ){ } return new String( buf ); } private void createRS() { if( mRS != null ){ return; } RenderScriptGL.SurfaceConfig sc= new RenderScriptGL.SurfaceConfig(); mRS= createRenderScriptGL( sc ); mContext= getContext(); mRes= mContext.getResources(); } private void setupRS() { createRS(); ScriptC_rendermesh mScript= new ScriptC_rendermesh( mRS, mRes, R.raw.rendermesh ); // Mesh Mesh.TriangleMeshBuilder builder= new Mesh.TriangleMeshBuilder( mRS, 2, Mesh.TriangleMeshBuilder.COLOR ); builder.setColor( 1f, 0f, 0f, 1f ).addVertex( 10.0f, 10.0f ); builder.setColor( 0f, 1f, 0f, 1f ).addVertex( 280.0f, 10.0f ); builder.setColor( 0f, 0f, 1f, 1f ).addVertex( 10.0f, 280.0f ); builder.setColor( 1f, 1f, 1f, 1f ).addVertex( 280.0f, 280.0f ); builder.addTriangle( 0, 2, 1 ); builder.addTriangle( 1, 2, 3 ); Mesh mesh= builder.create( true ); // Uniform a_vconst2d= new ScriptField_ConstantType2D( mRS, 1, Allocation.USAGE_GRAPHICS_CONSTANTS ); a_vconst2d.set_Transform( 0, new Float4( 2.0f/mWidth, -2.0f/mHeight, -1.0f, 1.0f ), true ); // VertexShader ProgramVertex.Builder vsh_builder= new ProgramVertex.Builder( mRS ); vsh_builder.setShader( LoadResource( mContext, R.raw.mesh2dc_vsh ) ); vsh_builder.addConstant( a_vconst2d.getType() ); vsh_builder.addInput( mesh.getVertexAllocation( 0 ).getType().getElement() ); ProgramVertex vsh= vsh_builder.create(); vsh.bindConstants( a_vconst2d.getAllocation(), 0 ); // PixelShader ProgramFragment.Builder psh_builder= new ProgramFragment.Builder( mRS ); psh_builder.setShader( LoadResource( mContext, R.raw.mesh2dc_psh ) ); ProgramFragment psh= psh_builder.create(); // Global mScript.set_vsh( vsh ); mScript.set_psh( psh ); mScript.set_mesh( mesh ); mRS.bindRootScript( mScript ); } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); createRS(); } @Override public void onDetachedFromWindow() { if( mRS != null ){ mRS= null; destroyRenderScriptGL(); } } @Override public void surfaceChanged( SurfaceHolder holder, int format, int w, int h ) { super.surfaceChanged( holder, format, w, h ); mWidth= w; mHeight= h; setupRS(); } }
最終的にはこれだけです。
● Mesh.AllocationBuilder
Mesh.TriangleMeshBuilder では制限があるので、頂点形式を自由に宣言して
script で書き換えたい場合は Mesh.AllocationBuilder を使います。
Android SDK のサンプルはどれも PointSprite なので、IndexBuffer の使い方が
わからず理解に時間がかかりました。やっと出来るようになりました。
次はそのあたりを動かしてみます。「Android 3.x RenderScript (4) script で頂点を書き換える」
関連エントリ
・Android 3.x RenderScript (2) 描画と Allocation
・Android 3.x RenderScript (1)