上篇文章では、Android における OpenGL や座標マッピングについて紹介しました。OpenGL ES 環境では、投影とカメラビューを通じて、表示される描画オブジェクトが目に見える実物により近くなります。この表示方法は、描画オブジェクトの座標を数学的に変換することで実現されます。ここでは、投影とカメラビューに関連する知識を紹介し、文中のコード例の変更は前の記事を参考にしてください。
主要内容は以下の通りです:
- 投影タイプ
- 投影の定義
- カメラビューの定義
- 投影とカメラビューの適用
- 実行効果
投影タイプ#
OpenGL には主に 2 種類の投影モードがあり、それぞれは以下の特徴を持っています:
- 透視投影:人間の目の習慣に合い、近くは大きく、遠くは小さく見える効果を持ちます。
- 正射影:すべての物体が投影平面上で元のサイズを保持します。
透視投影の視景体は切頭円錐体であり、正射影の視景体は直方体です。透視投影と正射影の示意図は以下の通りです:
両者に対応する行列計算関数は以下の通りです:
// 透視投影行列
Matrix.frustumM(float[] m, int offset, float left, float right, float bottom, float top, float near, float far);
// 正射影行列
Matrix.orthoM(float[] m, int offset, float left, float right, float bottom, float top, float near, float far);
上記の関数パラメータの中で、m は対応する投影行列データを格納するために使用され、near と far はそれぞれ視景体の近い平面と遠いスクリーン距離を示し、left、right、top、bottom は遠い平面のパラメータに対応します。
投影の定義#
前の小節の内容に基づき、ここでは透視投影を使用し、投影行列をMatrix.frustumM()で填充します。以下のようになります:
private val projectionMatrix = FloatArray(16)
override fun onSurfaceChanged(unused: GL10, width: Int, height: Int) {
GLES20.glViewport(0, 0, width, height)
val ratio: Float = width.toFloat() / height.toFloat()
Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1f, 1f, 3f, 7f)
}
上記のコードは、投影行列projectionMatrixを填充しました。その変化は以下のアニメーションを参考にしてください:

カメラビューの定義#
カメラビューは、その名の通りカメラの視点から特定の物体を観察することを意味します。Matrix.setLookAtMメソッドを使用してビュー行列を填充します。重要なパラメータは主にカメラの位置、ターゲットの位置、カメラの上方向ベクトルです。その後、投影行列とビュー行列を結合してvPMatrixを作成します。以下のようになります:
override fun onDrawFrame(gl: GL10?) {
// 現在のフレームを描画し、具体的な内容をレンダリング処理します
Log.d(tag, "onDrawFrame")
// カメラの位置を設定(ビュー行列)
Matrix.setLookAtM(viewMatrix,0,
0.0f,0.0f,5.0f, // カメラの位置
0.0f,0.0f,0.0f, // ターゲットの位置
0.0f,1.0f,0.0f) // カメラの上方向ベクトル
// 投影とビューの変換を計算
Matrix.multiplyMM(vPMatrix,0,projectionMatrix,0,viewMatrix,0)
// 具体的な描画
triangle.draw(vPMatrix)
}
上記の例では、カメラの位置 z 座標は near と far の間にしか存在できず、つまり 3 と 7 の間にいる必要があり、その範囲外からは観察できません。以下のアニメーションを参考にしてください:

投影とカメラビューの適用#
投影とビューの変換に適応するために、前回のシェーダーコードを以下のように変更します:
// default
attribute vec4 vPosition;
void main() {
gl_Position = vPosition;
}
// 投影とビューの変換を使用
uniform mat4 uMVPMatrix;
attribute vec4 vPosition;
void main() {
gl_Position = uMVPMatrix * vPosition;
}
前の小節で計算したvPMatrix行列をシェーダーに渡すだけです:
fun draw(mvpMatrix: FloatArray) {
// attribute変数のアドレスインデックスを取得
// 頂点シェーダーのvPositionメンバーへのハンドルを取得
positionHandle = GLES20.glGetAttribLocation(programHandle, "vPosition").also {
// 頂点属性を有効にする、デフォルトは無効
GLES20.glEnableVertexAttribArray(it)
GLES20.glVertexAttribPointer(
it,
COORDINATE_PER_VERTEX,
GLES20.GL_FLOAT,
false,
vertexStride,
vertexBuffer
)
}
// フラグメントシェーダーのvColorメンバーへのハンドルを取得
colorHandler = GLES20.glGetUniformLocation(programHandle, "vColor").also {
GLES20.glUniform4fv(it, 1, color, 0)
}
// 形状の変換行列へのハンドルを取得
vPMatrixHandle = GLES20.glGetUniformLocation(programHandle, "uMVPMatrix")
// 投影とビューの変換をシェーダーに渡す
GLES20.glUniformMatrix4fv(vPMatrixHandle, 1, false, mvpMatrix, 0)
// 三角形を描画
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount)
GLES20.glDisableVertexAttribArray(positionHandle)
}
これにより、コードを改造して投影とカメラビューを適用し、横縦画面の切り替えによる変形問題を解決しました。この変形は、OpenGL で動画をレンダリングする際の動画比率など、他の分野にも応用できます。
実行効果#
前回の記事の実行効果と比較できます。実行効果は以下の通りです:
キーワード【OpenGL】を返信するとソースコードを取得できます。上文中に登場したアニメーションプログラムはキーワード【OTUTORS】を返信すると取得できます。