OpenGL は強力なクロスプラットフォームAPIであり、さまざまなプログラミング環境でシステムのハードウェアに非常に密接にアクセスできます。
それで、なぜあなたはそれを使うべきですか?
2Dと3Dの両方のグラフィックスに非常に低レベルの処理を提供します。一般に、これにより、インタープリター型または高級プログラミング言語が原因で発生する問題を回避できます。ただし、さらに重要なのは、主要な機能であるGPUへのハードウェアレベルのアクセスも提供することです。
GPUは多くのアプリケーションを大幅に高速化できますが、コンピューターでは非常に特殊な役割を果たします。 GPUコアは実際にはCPUコアよりも低速です。同時アクティビティのない特にシリアルなプログラムを実行すると、ほとんどの場合 常に GPUコアではCPUコアよりも遅くなります。主な違いは、GPUが大規模な並列処理をサポートしていることです。一度に数百のコアで効果的に実行されるシェーダーと呼ばれる小さなプログラムを作成できます。これは、他の方法では信じられないほど反復的なタスクを実行し、それらを同時に実行できることを意味します。
この記事では、OpenGLを使用してコンテンツを画面にレンダリングする簡単なAndroidアプリケーションを構築します。始める前に、あなたがすでにに精通していることが重要です Androidアプリケーションの作成に関する知識 いくつかのCのようなプログラミング言語の構文。このチュートリアルのソースコード全体は GitHubで入手可能 。
OpenGLの能力を実証するために、Androidデバイス用の比較的基本的なアプリケーションを作成します。現在、Android上のOpenGLは、OpenGL for Embedded Systems(OpenGL ES)と呼ばれるサブセットで配布されています。基本的に、これはOpenGLの簡略版と考えることができますが、必要なコア機能は引き続き利用できます。
基本的な「HelloWorld」を作成する代わりに、マンデルブロ集合ジェネレーターという一見単純なアプリケーションを作成します。ザ・ マンデルブロ集合 の分野に基づいています 複素数 。複雑な分析は美しく広大な分野であるため、その背後にある実際の数学よりも視覚的な結果に焦点を当てます。
ギリシャの金融危機の説明OpenGLを使用すると、マンデルブロ集合ジェネレーターの構築が思ったより簡単になります。 つぶやき
アプリケーションを作成するときは、適切なOpenGLサポートを備えたユーザーにのみ配布されるようにします。マニフェスト宣言とアプリケーションの間で、マニフェストファイルでOpenGL2.0の使用を宣言することから始めます。
MainActivity
この時点で、OpenGL2.0のサポートはどこにでもあります。 OpenGL 3.0と3.1は互換性が増していますが、どちらかを書くと省略されます デバイスの約65% 、したがって、追加機能が必要であると確信できる場合にのみ決定を下してください。これらは、バージョンをそれぞれ「0x000300000」と「0x000300001」に設定することで実装できます。
このOpenGLアプリケーションをAndroidで作成する場合、通常、サーフェスの描画に使用される3つの主要なクラスがあります。GLSurfaceView
の拡張機能です。 GLSurfaceView.Renderer
、およびの実装 MainActivity
。そこから、図面をカプセル化するさまざまなモデルを作成します。
FractalGenerator
、GLSurfaceView
と呼ばれますこの例では、基本的にpublic class FractalGenerator extends Activity { private GLSurfaceView mGLView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //Create and set GLSurfaceView mGLView = new FractalSurfaceView(this); setContentView(mGLView); } //[...] @Override protected void onPause() { super.onPause(); mGLView.onPause(); } @Override protected void onResume() { super.onResume(); mGLView.onResume(); } }
をインスタンス化するだけです。そして、グローバルな変更をすべてルーティングします。基本的に定型コードになる例を次に示します。
GLSurfaceView
これは、他のアクティビティレベル修飾子(など)を配置するクラスにもなります。 没入型フルスクリーン )。
1つ深いクラスでは、setEGLContextClientVersion(int version)
の拡張があり、これがプライマリビューとして機能します。このクラスでは、バージョンを設定し、レンダラーを設定し、タッチイベントを制御します。コンストラクターでは、OpenGLバージョンを次のように設定するだけで済みます。 public FractalSurfaceView(Context context){ super(context); setEGLContextClientVersion(2); mRenderer = new FractalRenderer(); setRenderer(mRenderer); }
また、レンダラーを作成して設定します。
setRenderMode(int renderMode)
さらに、レンダリングモードなどの属性を次のように設定できます。 RENDERMODE_WHEN_DIRTY
。マンデルブロ集合の生成には非常にコストがかかる可能性があるため、 requestRender()
、初期化時および明示的な呼び出しが行われたときにのみシーンをレンダリングします GLSurfaceView
。設定のその他のオプションは、 onTouchEvent(MotionEvent event)
火 。
コンストラクターを取得したら、少なくとも1つの他のメソッドをオーバーライドする必要があります。 {a, 0, 0, 0, 0, b, 0, 0, 0, 0, c, 0, v_a, v_b, v_c, 1}
、一般的なタッチベースのユーザー入力に使用できます。レッスンの主な焦点ではないため、ここではあまり詳しく説明しません。
最後に、レンダラーに移動します。レンダラーでは、照明やシーンの変更のほとんどの作業が行われます。まず、グラフィックスの世界でマトリックスがどのように機能し、動作するかを少し調べる必要があります。
OpenGLは、マトリックスの使用に大きく依存しています。行列は、一般化されたシーケンスを表すための素晴らしくコンパクトな方法です。 座標の変更 。通常、これらを使用すると、任意の回転、拡張/収縮、および反射を行うことができますが、少し精巧に行うことで、平行移動も行うことができます。基本的に、これはすべて、簡単に実行できることを意味します 合理的 カメラの移動やオブジェクトの成長など、必要な変更を行います。沿って 行列にベクトルを掛ける 座標を表すことで、新しい座標系を効果的に作成できます。
ザ・ マトリックス OpenGLが提供するクラスは、必要な行列を計算するための既成の方法をいくつか提供しますが、それらがどのように機能するかを理解することは、単純な変換を使用する場合でも賢明なアイデアです。
まず、座標を処理するために4次元のベクトルと行列を使用する理由を説明します。これは実際には、変換を実行できるように座標の使用を微調整するという考えに戻ります。3D空間での変換は、3次元だけでは不可能ですが、4次元を追加すると機能が有効になります。
これを説明するために、非常に基本的な一般的なスケール/変換行列を使用できます。
プライベートエクイティファンドの構造図
重要な注意として、OpenGLマトリックスは列単位であるため、このマトリックスはonSurfaceCreated(GL10 gl, EGLConfig config)
として記述されます。これは、通常の読み取り方法に垂直です。これは、乗算で列として表示されるベクトルが行列と同じ形式になるようにすることで合理化できます。
このマトリックスの知識があれば、レンダラーの設計に戻ることができます。通常、このクラスでは、モデル、ビュー、プロジェクションの3つの行列の積から形成される行列を作成します。これは、適切にはMVPMatrixと呼ばれます。あなたは詳細についてもっと学ぶことができます ここに 、より基本的な変換セットを使用するため、マンデルブロ集合は2次元のフルスクリーンモデルであり、実際にはカメラのアイデアは必要ありません。
まず、クラスを設定しましょう。を実装する必要があります 必要な方法 レンダラーインターフェイスの場合:onSurfaceChanged(GL10 gl, int width, int height)
、onDrawFrame(GL10 gl)
、およびpublic class FractalRenderer implements GLSurfaceView.Renderer { //Provide a tag for logging errors private static final String TAG = 'FractalRenderer'; //Create all models private Fractal mFractal; //Transformation matrices private final float[] mMVPMatrix = new float[16]; //Any other private variables needed for transformations @Override public void onSurfaceCreated(GL10 unused, EGLConfig config) { // Set the background frame color GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); //Instantiate all models mFractal = new Fractal(); } @Override public void onDrawFrame(GL10 unused) { //Clear the frame of any color information or depth information GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); //Create a basic scale/translate matrix float[] mMVPMatrix = new float[]{ -1.0f/mZoom, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f/(mZoom*mRatio), 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, -mX, -mY, 0.0f, 1.0f}; //Pass the draw command down the line to all models, giving access to the transformation matrix mFractal.draw(mMVPMatrix); } @Override public void onSurfaceChanged(GL10 unused, int width, int height) { //Create the viewport as being fullscreen GLES20.glViewport(0, 0, width, height); //Change any projection matrices to reflect changes in screen orientation } //Other public access methods for transformations }
。フルクラスは次のようになります。
checkGLError
提供されているコードで使用されている2つのユーティリティメソッドloadShaders
もあります。およびpublic Fractal() { // initialize vertex byte buffer for shape coordinates ByteBuffer bb = ByteBuffer.allocateDirect( // (# of coordinate values * 4 bytes per float) squareCoords.length * 4); bb.order(ByteOrder.nativeOrder()); vertexBuffer = bb.asFloatBuffer(); vertexBuffer.put(squareCoords); vertexBuffer.position(0); // initialize byte buffer for the draw list ByteBuffer dlb = ByteBuffer.allocateDirect( // (# of coordinate values * 2 bytes per short) drawOrder.length * 2); dlb.order(ByteOrder.nativeOrder()); drawListBuffer = dlb.asShortBuffer(); drawListBuffer.put(drawOrder); drawListBuffer.position(0); // Prepare shaders int vertexShader = FractalRenderer.loadShader( GLES20.GL_VERTEX_SHADER, vertexShaderCode); int fragmentShader = FractalRenderer.loadShader( GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode); // create empty OpenGL Program mProgram = GLES20.glCreateProgram(); // add the vertex shader to program GLES20.glAttachShader(mProgram, vertexShader); // add the fragment shader to program GLES20.glAttachShader(mProgram, fragmentShader); // create OpenGL program executables GLES20.glLinkProgram(mProgram); }
デバッグとシェーダーの使用を支援します。
このすべてにおいて、プログラムのさまざまな部分をカプセル化するために、コマンドのチェーンを行に渡し続けます。私たちはついに私たちのプログラムが実際に何を書くことができるようになりました しますか 、理論的な変更を加える方法の代わりに。これを行うときは、シーン内の特定のオブジェクトに表示する必要のある情報を含むモデルクラスを作成する必要があります。複雑な3Dシーンでは、これは動物ややかんの場合がありますが、はるかに単純な2Dの例としてフラクタルを実行します。
モデルクラスでは、クラス全体を記述します。使用する必要のあるスーパークラスはありません。コンストラクターと、でパラメーターを受け取るある種の描画メソッドがあれば十分です。
とはいえ、本質的に定型的なものである必要がある変数はまだたくさんあります。 Fractalクラスで使用される正確なコンストラクターを見てみましょう。
static float squareCoords[] = { -1.0f, 1.0f, 0.0f, // top left -1.0f, -1.0f, 0.0f, // bottom left 1.0f, -1.0f, 0.0f, // bottom right 1.0f, 1.0f, 0.0f }; // top right private final short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices
かなり一口ですね。幸い、これはプログラムの一部であり、モデルの名前を保存して、まったく変更する必要はありません。クラス変数を適切に変更すれば、これは基本的な形状でうまく機能するはずです。
この一部について説明するために、いくつかの変数宣言を見てみましょう。
squareCoords
(-1,-1)
では、正方形のすべての座標を指定します。画面上のすべての座標は、(1,1)
のグリッドとして表されることに注意してください。左下とdrawOrder
右上にあります。
0
では、正方形を構成する三角形に基づいて座標の順序を指定します。特に一貫性と速度のために、OpenGLは三角形を使用してすべてのサーフェスを表します。正方形を作成するには、対角線(この場合は2
からByteBuffers
)を切り取って、2つの三角形を作成します。
これらの両方をプログラムに追加するには、最初にそれらを生のバイトバッファーに変換して、配列の内容をOpenGLインターフェイスに直接インターフェイスする必要があります。 Javaは、OpenGLの実装が使用するポインタベースのC配列と直接互換性のない追加情報を含むオブジェクトとして配列を格納します。これを改善するには、loadShader(int type, String shaderCode)
配列のrawメモリへのアクセスを格納するが使用されます。
頂点と描画順序のデータを入力したら、シェーダーを作成する必要があります。
モデルを作成するときは、頂点シェーダーとフラグメント(ピクセル)シェーダーの2つのシェーダーを作成する必要があります。すべてのシェーダーは、GLシェーディング言語(GLSL)で記述されています。これは、Cベースの言語であり、 組み込み関数 、 変数修飾子 、 プリミティブ 、および デフォルトの入力/出力 。 Androidでは、これらはレンダラーの2つのリソースメソッドの1つであるconst
を介して最終的な文字列として渡されます。まず、さまざまなタイプの修飾子について説明します。
uniform
:任意の最終変数を定数として宣言できるため、その値を保存して簡単にアクセスできます。 πのような数値は、シェーダー全体で頻繁に使用される場合、定数として宣言できます。実装によっては、コンパイラが変更されていない値を定数として自動的に宣言する可能性があります。varying
:均一変数は、単一のレンダリングに対して定数として宣言されている変数です。これらは基本的に、シェーダーへの静的引数として使用されます。attribute
:変数が可変として宣言され、頂点シェーダーに設定されている場合、フラグメントシェーダーで線形補間されます。これは、あらゆる種類の色のグラデーションを作成するのに役立ち、深さの変更に対して暗黙的に行われます。vec2
:属性は、シェーダーへの非静的引数と考えることができます。これらは、頂点固有であり、頂点シェーダーにのみ表示される入力のセットを示します。さらに、追加された他の2つのタイプのプリミティブについて説明する必要があります。
vec3
、vec4
、mat2
:指定された次元の浮動小数点ベクトル。mat3
、mat4
、x
:指定された次元の浮動小数点行列。ベクトルには、そのコンポーネントy
、z
、w
、およびr
からアクセスできます。またはg
、b
、a
、およびvec3 a
。また、複数のインデックスを持つ任意のサイズのベクトルを生成できます:a.xxyz
、vec4
の場合a
を返しますmat2 matrix
の対応する値を使用します。
行列とベクトルは配列としてインデックス付けすることもでき、行列は1つのコンポーネントのみを持つベクトルを返します。これは、matrix[0].a
の場合、matrix[0][0]
を意味しますは有効で、vec2 a = vec2(1.0,1.0); vec2 b = a; b.x=2.0;
を返します。これらを操作するときは、オブジェクトではなくプリミティブとして機能することを忘れないでください。たとえば、次のコードについて考えてみます。
a=vec2(1.0,1.0)
これはb=vec2(2.0,1.0)
を残しますおよびb
は、オブジェクトの動作から期待されるものではなく、2行目でa
が得られます。 private final String vertexShaderCode = 'attribute vec4 vPosition;' + 'void main() {' + ' gl_Position = vPosition;' + '}';
へのポインタ。
マンデルブロ集合では、コードの大部分はフラグメントシェーダーにあります。フラグメントシェーダーは、すべてのピクセルで実行されるシェーダーです。名目上、頂点シェーダーは、色や深さの変更など、頂点ごとに設定される属性を含め、すべての頂点で機能します。フラクタル用の非常に単純な頂点シェーダーを見てみましょう。
gl_Position
この中で、gl_Position
頂点の座標を記録するためにOpenGLによって定義された出力変数です。この場合、vPosition
を設定した各頂点の位置を渡します。ほとんどのアプリケーションでは、MVPMatrix
を乗算します。 fragmentShaderCode
によって、頂点を変換しますが、フラクタルは常にフルスクリーンである必要があります。すべての変換は、ローカル座標系で行われます。
フラグメントシェーダーは、セットを生成するためのほとんどの作業が行われる場所になります。 precision highp float; uniform mat4 uMVPMatrix; void main() { //Scale point by input transformation matrix vec2 p = (uMVPMatrix * vec4(gl_PointCoord,0,1)).xy; vec2 c = p; //Set default color to HSV value for black vec3 color=vec3(0.0,0.0,0.0); //Max number of iterations will arbitrarily be defined as 100. Finer detail with more computation will be found for larger values. for(int i=0;i4.0){ //The point, c, is not part of the set, so smoothly color it. colorRegulator increases linearly by 1 for every extra step it takes to break free. float colorRegulator = float(i-1)-log(((log(dot(p,p)))/log(2.0)))/log(2.0); //This is a coloring algorithm I found to be appealing. Written in HSV, many functions will work. color = vec3(0.95 + .012*colorRegulator , 1.0, .2+.4*(1.0+sin(.3*colorRegulator))); break; } } //Change color from HSV to RGB. Algorithm from https://gist.github.com/patriciogonzalezvivo/114c1653de9e3da6e1e3 vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); vec3 m = abs(fract(color.xxx + K.xyz) * 6.0 - K.www); gl_FragColor.rgb = color.z * mix(K.xxx, clamp(m - K.xxx, 0.0, 1.0), color.y); gl_FragColor.a=1.0; }
を設定します以下に:
fract
コードの多くは、セットがどのように機能するかについての数学とアルゴリズムにすぎません。 abs
、mix
、sin
、clamp
、およびdot
のいくつかの組み込み関数の使用に注意してください。これらはすべて、ベクトルまたはスカラーを操作し、ベクトルを返します。またはスカラー。さらに、draw
ベクトル引数を取り、スカラーを返すが使用されます。
シェーダーを使用できるように設定したので、最後のステップはpublic void draw(float[] mvpMatrix) { // Add program to OpenGL environment GLES20.glUseProgram(mProgram); // get handle to vertex shader's vPosition member mPositionHandle = GLES20.glGetAttribLocation(mProgram, 'vPosition'); mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, 'uMVPMatrix'); //Pass uniform transformation matrix to shader GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0); //Add attribute array of vertices GLES20.glEnableVertexAttribArray(mPositionHandle); GLES20.glVertexAttribPointer( mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer); // Draw the square GLES20.glDrawElements( GLES20.GL_TRIANGLES, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, drawListBuffer); // Disable vertex array GLES20.glDisableVertexAttribArray(mPositionHandle); FractalRenderer.checkGlError('Test'); }
を実装することです。モデルの関数:
uniform
この関数は、attribute
を含むすべての引数をシェーダーに渡します。変換行列とdouble
ポジション。
プログラムのすべての部分を組み立てた後、最終的にそれを実行することができます。適切なタッチサポートが処理されている場合、絶対に魅惑的なシーンがペイントされます。
もう少しズームインすると、画像の内訳に気づき始めます。
g ++コンパイルc ++
これは、その背後にあるセットの計算とはまったく関係がなく、OpenGLでの数値の格納方法と処理方法とはすべて関係があります。 float
の最近のサポート精度が向上し、OpenGL2.0はprecision highp float
s以外のものをネイティブにサポートしていません。 double
で利用可能な最高精度のフロートとして特別に指定しましたシェーダーにありますが、それでも十分ではありません。
この問題を回避するための唯一の方法は、 エミュレートする float
sは2つのGLSurfaceView
sを使用します。この方法は、実際には、ネイティブに実装された方法の実際の精度の1桁以内に収まりますが、速度にはかなり厳しいコストがかかります。より高いレベルの精度が必要な場合、これは読者の練習問題として残されます。
いくつかのサポートクラスを使用すると、OpenGLは複雑なシーンのリアルタイムレンダリングをすばやく維持できます。 Renderer
で構成されるレイアウトの作成、その