レンダリングの詳細
前回は単純な三角形の描画を行いました。そこで、レンダリングの大まかな流れを理解し、プログラムで使用しているクラスの関連も見ました。
この章では、OpenGL がどのように物事をレンダリングするかについて話し続けます。
三角形の代わりに四角形を描画し、各頂点の色などの追加データをメッシュに設定します。
プログラムの構成
Chapter-04の処理をクラス図にして、解析したいと思います。ここでクラスの関係を理解し、処理の流れを理解しておくとよいと判断しました。
クラス図に説明を説明を加えてみました。
ポイントとしては、SceneクラスはMeshクラスを保持していて、SceneRenderクラスで描画するときにSceneクラスからMeshを取得しているところです。
<SceneRender#render()>
public void render(Scene scene) {
shaderProgram.bind();
// ここがポイントの処理
scene.getMeshMap().values().forEach(mesh -> {
glBindVertexArray(mesh.getVaoId());
glDrawElements(GL_TRIANGLES, mesh.getNumVertices(), GL_UNSIGNED_INT, 0);
}
);
glBindVertexArray(0);
shaderProgram.unbind();
}
レンダリングの確認
前回説明があった描画するのに、OpenGLでは三角形を複数描く事で、いろんな描画を実現しています。
四角形であれば、三角形を2回描けばよいです。
単純にこれを考えると、複雑な、例えばイルカのようなモデルを描画するとなると、重複する三角形が出てきます。
その例が下のコードです。
float[] positions = new float[] {
-0.5f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, 0.5f, 0.0f,
0.5f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
}
四角を描くのであれば、二つの三角形の頂点データが必要になりますが、各頂点を描く時には、重複する部分が出てきてしまいます。
そこで、頂点と順序(Index)を示したデータがあれば、重複するのを防げます。
ドキュメントの記述では下のようになっています。
最終的には、重複した情報のためにさらに多くのメモリが必要になります。
しかし、大きな問題はこれではありません。最大の問題は、恥の頂点のためにシェーダーでプロセスを繰り返すことです。
ここで、インデックス バッファーが役に立ちます。四角形を描画するには、この方法で各頂点を一度だけ指定する必要があります: V1、V2、V3、V4)。
各頂点には、配列内の位置があります。V1 の位置は 0、V2 の位置は 1 などです。
プログラムの修正
chapter03のコードとchapter04のコードを比較します。変更点に注意しながら行います。
もともとの処理としては次の通りです。
Chapter03
- VAOのIDを生成して、作成するMeshクラス(インスタンス)で保持 ※なのでコンストラクタで処理を行っています。
- 位置VBOのIDを生成、VBOリストに追加 ※細かい処理は割愛します。
- SceneクラスでMeshクラスをマップで管理、この時にVAOをOpenGLにバインドして描画 ※呼び出し元での処理
Chapter04
- VAOのIDを生成して、作成するMeshクラス(インスタンス)で保持 ※なのでコンストラクタで処理を行っています。
- 位置VBOのIDを生成、VBOリストに追加 ※細かい処理は割愛します。
- 色VBOのIDを生成、VBOリストに追加、色バッファーをバッファーデータに追加 ※細かい処理は割愛します。
- インデックスVBOのIDを生成、VBOリストに追加、インデックスバッファーをバッファーデータに追加 ※細かい処理は割愛します。
- SceneクラスでMeshクラスをマップで管理、この時にVAOをOpenGLにバインドして描画 ※呼び出し元での処理
Meshクラス
コンストラクタ部分を抜き出します。見た目にコードの量が増えています。
単純にコードを追加した形となっています。具体的にはコメントの「// Color VBO」「// Index VBO」の部分です。
つまりは、位置VBOのみの登録しか行っていなかったが、他に色とインデックスVBOを追加したということです。
※コンストラクタの引数も変わっています。
Chapter03のコード
public class Mesh {
private int numVertices;
private int vaoId;
private List<Integer> vboIdList;
public Mesh(float[] positions, int numVertices) {
try (MemoryStack stack = MemoryStack.stackPush()) {
this.numVertices = numVertices;
vboIdList = new ArrayList<>();
vaoId = glGenVertexArrays();
glBindVertexArray(vaoId);
// Positions VBO
int vboId = glGenBuffers();
vboIdList.add(vboId);
FloatBuffer positionsBuffer = stack.callocFloat(positions.length);
positionsBuffer.put(0, positions);
glBindBuffer(GL_ARRAY_BUFFER, vboId);
glBufferData(GL_ARRAY_BUFFER, positionsBuffer, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
}
}
Chapter04のコード
public class Mesh {
private int numVertices;
private int vaoId;
private List<Integer> vboIdList;
public Mesh(float[] positions, float[] colors, int[] indices) {
try (MemoryStack stack = MemoryStack.stackPush()) {
numVertices = indices.length;
vboIdList = new ArrayList<>();
vaoId = glGenVertexArrays();
glBindVertexArray(vaoId);
// Positions VBO
int vboId = glGenBuffers();
vboIdList.add(vboId);
FloatBuffer positionsBuffer = stack.callocFloat(positions.length);
positionsBuffer.put(0, positions);
glBindBuffer(GL_ARRAY_BUFFER, vboId);
glBufferData(GL_ARRAY_BUFFER, positionsBuffer, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);
// Color VBO
vboId = glGenBuffers();
vboIdList.add(vboId);
FloatBuffer colorsBuffer = stack.callocFloat(colors.length);
colorsBuffer.put(0, colors);
glBindBuffer(GL_ARRAY_BUFFER, vboId);
glBufferData(GL_ARRAY_BUFFER, colorsBuffer, GL_STATIC_DRAW);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, false, 0, 0);
// Index VBO
vboId = glGenBuffers();
vboIdList.add(vboId);
IntBuffer indicesBuffer = stack.callocInt(indices.length);
indicesBuffer.put(0, indices);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboId);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
}
}
変更点の抜粋
以下のコードをchapter03に加えて、追加しただけです。
Chapter04
// Color VBO
vboId = glGenBuffers();
vboIdList.add(vboId);
FloatBuffer colorsBuffer = stack.callocFloat(colors.length);
colorsBuffer.put(0, colors);
glBindBuffer(GL_ARRAY_BUFFER, vboId);
glBufferData(GL_ARRAY_BUFFER, colorsBuffer, GL_STATIC_DRAW);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, false, 0, 0);
// Index VBO
vboId = glGenBuffers();
vboIdList.add(vboId);
IntBuffer indicesBuffer = stack.callocInt(indices.length);
indicesBuffer.put(0, indices);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboId);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL_STATIC_DRAW);
IAppLogicクラス(Mainクラス)
IAppLogicインターフェースを実装したクラス、つまりはMainクラスですが、このクラスの変更点を見てみましょう。
<Chapter03>
@Override
public void init(Window window, Scene scene, Render render) {
float[] positions = new float[]{
0.0f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f
};
Mesh mesh = new Mesh(positions, 3);
scene.addMesh("triangle", mesh);
}
<Chapter04>
public void init(Window window, Scene scene, Render render) {
float[] positions = new float[]{
-0.5f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.5f, 0.5f, 0.0f,
};
float[] colors = new float[]{
0.5f, 0.0f, 0.0f,
0.0f, 0.5f, 0.0f,
0.0f, 0.0f, 0.5f,
0.0f, 0.5f, 0.5f,
};
int[] indices = new int[]{
0, 1, 3, 3, 1, 2,
};
Mesh mesh = new Mesh(positions, colors, indices);
scene.addMesh("quad", mesh);
}
具体的には、コードが追加されている形での変更(更新)なのでその部分を抜粋すると次のようになります。
ズバリ、色VBOのデータ(配列)とインデックスVBOのデータ(配列)が追加されています。
float[] colors = new float[]{
0.5f, 0.0f, 0.0f,
0.0f, 0.5f, 0.0f,
0.0f, 0.0f, 0.5f,
0.0f, 0.5f, 0.5f,
};
int[] indices = new int[]{
0, 1, 3, 3, 1, 2,
};
頂点シェーダー (scene.vert)
シェーダーも同様に比較します。
<Chapter03>
#version 330
layout (location=0) in vec3 inPosition;
void main()
{
gl_Position = vec4(inPosition, 3.0);
}
<Chapter04>
#version 330
layout (location=0) in vec3 position;
layout (location=1) in vec3 color;
out vec3 outColor;
void main()
{
gl_Position = vec4(position, 1.0);
outColor = color;
}
フラグメントシェーダー(scene.flag)
<Chapter03>
#version 330
out vec4 fragColor;
void main()
{
fragColor = vec4(0.0, 0.0, 1.0, 1.0);
}
<Chapter04>
#version 330
in vec3 outColor;
out vec4 fragColor;
void main()
{
fragColor = vec4(outColor, 1.0);
}
中身を理解する
概要は大体理解できたので、実際にプロおグラムを実行して理解を深めます。
疑問点
- colors, indeciesの配列値の意味がわからない
- シェーダーと実行結果の関連が理解できてない
colors, indeciesの配列値の意味がわからない
問題のコードは次の部分です。positionsに関しては、描画する際の頂点を指定していて、順番にX,Y,Z座標になっています。
理解していること
理解していることを確認します。
positionの指定に関して
これは、次のような形で頂点を指定しています。
資格を描画するので点は4つ指定します。 ※V = 頂点
float[] positions = new float[]{
-0.5f, 0.5f, 0.0f, // V1
-0.5f, -0.5f, 0.0f,// V2
0.5f, -0.5f, 0.0f, // V3
0.5f, 0.5f, 0.0f, // V4
};
inndecieの指定に関して
四角形なので、頂点が4つ、つまり順番は0から3までになり、左回りに番号が振られているので
V1 = 0, V2 = 1, V3 = 2, V4 = 3となります。そして、三角形を2つ指定するので次のような形になります
- 三角形1 = V1, V2, V4
- 三角形2 = V4, V2, V3
三角形2に関しては、左回転で順番を読み込むので、v4がはじめに来ます。
インデックス番号で表現すると下のようになります。
- 0, 1, 3, 3, 1, 2
int[] indices = new int[]{ 0, 1, 3, 3, 1, 2, };
問題の部分
ズバリ、色の部分です。これは値を変更して実行してみたらわかりました。
どこかでグラデーションの設定がしてあると思われます。
各頂点の色を指定しています。なので下のような出力結果が得られます。
ポイントとしては、シェーダー(scene.flag)の中身を固定値ではなく入力値を返すように修正したため、colorsの配列もデータとして渡すようにコードを変更しているところです。
<scene.flag>
#version 330
in vec3 outColor; # 入力値
out vec4 fragColor;# 出力値
void main()
{
fragColor = vec4(outColor, 1.0);
}
色のデータ
float[] colors = new float[]{
1.5f, 0.0f, 0.0f, // 赤
0.0f, 1.0f, 0.0f, // 緑
0.0f, 0.0f, 1.0f, // 蒼
1.0f, 1.0f, 1.0f, // 白
}
これを色VBOとしてColorBufferに登録する
FloatBuffer colorsBuffer = stack.callocFloat(colors.length);
colorsBuffer.put(0, colors);
最終的なコード
public class Main implements IAppLogic {
// 省略
@Override
public void init(Window window, Scene scene, Render render) {
float[] positions = new float[]{
-0.5f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.5f, 0.5f, 0.0f,
};
float[] colors = new float[]{
0.5f, 0.0f, 0.0f,
0.0f, 0.5f, 0.0f,
0.0f, 0.0f, 0.5f,
0.0f, 0.5f, 0.5f,
};
int[] indices = new int[]{
0, 1, 3, 3, 1, 2,
};
Mesh mesh = new Mesh(positions, colors, indices);
scene.addMesh("quad", mesh);
}
// 省略
}
まとめ
Meshを作成するときに、頂点と、色、インデックスの指定を行い描画を行う処理順序が理解できた。
前回と比べて次の部分がキーポイントだった。
- 頂点VBO以外に、色VBOを追加している部分
- 頂点シェーダ(scene.vert)、フラグメントシェーダ(scene.frag)の出力を追加する
- MeshクラスがGPUに渡されて描画される(3章より)