メインコンテンツまでスキップ

チュートリアルOpenGLアプリケーションのビルド

📚 Source Page

JUCEアプリケーションの高性能レンダリングライブラリとしてOpenGLを使い始める方法をご紹介します。オーディオアプリケーションやプラグインで、美しい2Dや3Dのグラフィックスをレンダリングします。

レベル上級

プラットフォーム:Windows, macOS, Linux, iOS, Android

クラス: OpenGLAppComponent,OpenGLContext,OpenGLShaderProgram,OpenGLHelpers,Matrix3D,Vector3D

スタート

このチュートリアルは、OpenGLグラフィックス・ライブラリの基本的な理解を前提としています。もしOpenGLまずはそれを読んでほしいhere.

このチュートリアルのデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP.プロジェクトを解凍し、最初のヘッダーファイルをProjucerで開く。

警告

このプロジェクトのPIPバージョンを使用する場合は、必ずResourcesフォルダを生成されたProjucerプロジェクトに追加します。

このステップでヘルプが必要な場合は、以下を参照してください。Tutorial: Projucer Part 1: Getting started with the Projucer.

デモ・プロジェクト

デモ・プロジェクトは、以下のスクリーンショットのように、Wavefrontの".obj "ファイルを解析して、標準的なOpenGLティーポット・オブジェクトを3Dグラフィックスで表示します:

デモプロジェクトのアプリウィンドウ
デモプロジェクトのアプリウィンドウ
注記

ここで紹介するコードは、大まかに以下のものと似ている。OpenGLAppExampleJUCE Examplesより。

OpenGLアプリの解剖

OpenGL APIは、多くの異なるプラットフォームやビルド環境で動作する強力で汎用性の高いライブラリですが、3Dレンダリングに関する原則は、すべてのアプリケーションで類似しています。ここで説明する用語のいくつかは、OpenGLがどのようにレンダリングルーチンを実行するかを理解するための基本的なものです:

  • GLコンテキスト:コンテキストは初期化段階で一度設定され、プラットフォーム固有の方法でグラフィックスレンダラーのGL設定を記述し、アプリケーション内で使用するために必要なOpenGL関数がロードされる。
  • 投影マトリックス:投影行列によって、3Dオブジェクトを2D平面に変換し、シーンをスクリーンにレンダリングすることができる。
  • ビュー行列:ビューマトリックスによって、3D環境で幾何学的な変換を行い、オブジェクトをシーン内に配置することができる。
  • シェーダー:オブジェクトの外観をカスタマイズするために、シェーダーを使用して、表面の光沢や反射、3Dオブジェクト上の光や影の見え方など、マテリアルの特性を記述します。
  • 頂点:シーン内でレンダリングしようとしている3Dオブジェクトを定義する3Dポイントを表します。頂点シェーダーで使用されます。
  • フラグメント:補間によって頂点と頂点の間に存在するピクセルを表す。フラグメントシェーダやピクセルシェーダで使用されます。
  • 属性:シェーダ言語で使用される色やテクスチャ座標などの頂点パラメータを記述する。
  • ユニフォーム:シェーダ言語で使用されるが、シェーダプログラム間で不変であるグローバルパラメータを記述する。
  • 変数:頂点シェーダプログラムとフラグメントシェーダプログラムの間で共有されるパラメータを記述します。
  • シェイプ:アプリケーションで最終的にレンダリングしたいポリゴンをカプセル化します。この場合はティーポットです。

OpenGLシェーディング言語

OpenGLシェーディング言語またはGLSLは、複数のオペレーティング・システムやハードウェア・グラフィックス・カード上のグラフィックス・レンダリング・パイプラインを直接制御できるCタイプの言語です。GLSLを使用すると、オブジェクトの外観を記述するシェーダーと呼ばれる小さなプログラムを書くことができます。OpenGLを使うか、スマートフォンやタブレットのような組み込みシステム専用に設計されたサブセット・ライブラリOpenGL ESを使うかによって、言語構文は変わりませんが、パフォーマンスを考慮する必要があります。

例として、このチュートリアルで使用する頂点シェーダーは次のようになります:

// OpenGL and OpenGL ES
attribute vec4 position;
attribute vec4 sourceColour;
attribute vec2 textureCoordIn;

uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;

varying vec4 destinationColour;
varying vec2 textureCoordOut;

void main()
{
destinationColour = sourceColour;
textureCoordOut = textureCoordIn;
gl_Position = projectionMatrix * viewMatrix * position;
}

そして、このチュートリアルで使用するフラグメント・シェーダーは以下のようになる:

// OpenGL
varying vec4 destinationColour;
varying vec2 textureCoordOut;

void main()
{
vec4 colour = vec4(0.95, 0.57, 0.03, 0.7);
gl_FragColor = colour;
}

// OpenGL ES
varying lowp vec4 destinationColour;
varying lowp vec2 textureCoordOut;

void main()
{
lowp vec4 colour = vec4(0.95, 0.57, 0.03, 0.7);
gl_FragColor = colour;
}

ご覧の通り、シェーダーは非常に些細なもので、OpenGLシェーダーとOpenGL ESシェーダーの違いはごくわずかです。ここで使われているGLSLの型、変数、関数は以下の通りです:

  • vec2/vec42成分または4成分の浮動小数点ベクトルを表す。
  • mat44×4の浮動小数点型行列を表す。
  • lowpOpenGL ESの低精度データ型を指定する。
  • attribute頂点固有のパラメータを表す。
  • uniform: GL環境を記述するグローバルパラメータを表す。
  • varying頂点シェーダーとフラグメントシェーダー間で共有されるパラメータを表す。
  • gl_Position頂点シェーダーが頂点操作を実行するために変換された頂点位置。
  • gl_FragColorフラグメントシェーダーがフラグメント操作を実行するための色。
  • main()メイン関数は、頂点シェーダーやフラグメントシェーダーの計算を行う場所です。

OpenGLAppComponentクラス

JUCEではOpenGLAppComponentクラスはAudioAppComponentクラスを継承しているが、その代わりにグラフィカルなアプリに使用されている。を継承する場合はOpenGLAppComponentクラスには、オーバーライドしなければならない関数がいくつかある:

  • initialise()を使用します:この関数は、シェーダーなどのレンダリングに必要なGLオブジェクトを準備します。
  • render()を呼び出します:render関数はOpenGLレンダラーによって呼び出され、ここでOpenGLコンテキストを描画するための投影行列とビュー行列が計算される。
  • shutdown():この関数は、シェーダーなどのレンダリングに使用されたGLオブジェクトをクリアします。
  • shutdownOpenGL():サブクラスのデストラクタで、クラスが破壊される前にGLシステムをシャットダウンするために、この関数を呼び出す必要があります。

OpenGLの基本を学んだところで、ティーポットのレンダリングを実装してみよう!

投影行列とビュー行列の計算

投影行列とビュー行列の計算を切り離すために、これらの行列を後で使えるように返す2つのヘルパー関数を作成する。

まず、以下のようにフラストラムとスクリーン境界を使って投影行列を計算する:

    juce::Matrix3D getProjectionMatrix() const
{
auto w = 1.0f / (0.5f + 0.1f); // [1]
auto h = w * getLocalBounds().toFloat().getAspectRatio (false); // [2]

return juce::Matrix3D::fromFrustum (-w, w, -h, h, 4.0f, 30.0f); // [3]
}

フラストラムとは、多角形を2つの平行な平面でスライスして切り取った形状のことで、この2つの平行な平面を持つ多角形がフラストラムである。Matrix3DクラスはfromFrustum()行列を返す。上の関数では

  • [1]まず、近平面上のフラストラムの幅の半分を定義するために、width変数を宣言する。
  • [2]次に、画面比率とwidth変数に基づいて、ニアプレーン上のフラストラムの高さの半分を定義するheight変数を宣言する。
  • [3]最後にfromFrustum()関数を使い、幅、高さ、近平面距離、遠平面距離を引数にとり、投影行列を取得する。これにより、正射投影とは対照的に透視投影が得られる。

次に、回転行列を使ってビュー行列を計算し、下図のようにティーポットをアニメーション化します:

    juce::Matrix3D getViewMatrix() const
{
auto viewMatrix = juce::Matrix3D::fromTranslation ({ 0.0f, 0.0f, -10.0f }); // [4]
auto rotationMatrix = viewMatrix.rotation ({ -0.3f,
5.0f * std::sin ((float) getFrameCounter() * 0.01f),
0.0f }); // [5]

return viewMatrix * rotationMatrix; // [6]
}
  • [4]まず、ベクトルで平行移動した同一行列を作成し、その行列を10単位分シーンに押し戻します。こうすることで、ティーポットが画面のちょうど中心に来るが、少し離れてしまう。
  • [5]次に、先に定義した行列から回転行列を作成し、レンダリングフレームカウンターに応じてティーポットをy軸周りに回転させます。これはまた、sin関数の割合で回転の方向を前後に変える。
  • [6]最後に、行列を乗算して回転を適用し、ビュー行列を返す。

数学的計算部分は完了したので、次にシェーダープログラムを書き始めることができる。

OpenGLシェーダーを書く

まず、チュートリアルのコード・ベース全体で使用する便利なメンバ変数を定義することから始めましょう:

    juce::String vertexShader;
juce::String fragmentShader;

std::unique_ptr shader;
std::unique_ptr shape;
std::unique_ptr attributes;
std::unique_ptr uniforms;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};

ここでは、このGLコンテキストで使用するシェイプ、アトリビュート、ユニフォームへのいくつかのポインターとOpenGLShaderProgramシェーダープログラムを管理するオブジェクトです。また、次のステップで示すように、バーテックスシェーダーとフラグメントシェーダーを定義するための2つのcharポインターを持っています:

    void createShaders()
{
vertexShader = R"(
attribute vec4 position;
attribute vec4 sourceColour;
attribute vec2 textureCoordIn;

uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;

varying vec4 destinationColour;
varying vec2 textureCoordOut;

void main()
{
destinationColour = sourceColour;
textureCoordOut = textureCoordIn;
gl_Position = projectionMatrix * viewMatrix * position;
})";

fragmentShader =
#if JUCE_OPENGL_ES
R"(varying lowp vec4 destinationColour;
varying lowp vec2 textureCoordOut;)"
#else
R"(varying vec4 destinationColour;
varying vec2 textureCoordOut;)"
#endif
R"(
void main()
{)"
#if JUCE_OPENGL_ES
R"( lowp vec4 colour = vec4(0.95, 0.57, 0.03, 0.7);)"
#else
R"( vec4 colour = vec4(0.95, 0.57, 0.03, 0.7);)"
#endif
R"( gl_FragColor = colour;
})";

の中でcreateShaders()関数では、まず先に示したシェーダーを改行を挿入してcharポインターにコピーする。この関数は後でinitialise()の機能である。OpenGLAppComponent.頂点シェーダは、基本的に、"gl_Position "変数を変換行列、すなわち射影行列とビュー行列の積に設定することによって、形状内の各頂点の位置を設定します。フラグメントシェーダに関しては、ピクセルの色は "gl_FragColor "変数を指定された色に設定することで指定されます。

後半戦createShaders()関数を使って、現在のGLコンテキスト内に新しいシェーダープログラムを作成します。[1]そして以下のように初期化を行う:

        std::unique_ptr newShader (new juce::OpenGLShaderProgram (openGLContext));   // [1]
juce::String statusText;

if (newShader->addVertexShader (juce::OpenGLHelpers::translateVertexShaderToV3 (vertexShader)) // [2]
&& newShader->addFragmentShader (juce::OpenGLHelpers::translateFragmentShaderToV3 (fragmentShader))
&& newShader->link())
{
shape .reset();
attributes.reset();
uniforms .reset();

shader.reset (newShader.release()); // [3]
shader->use();

shape .reset (new Shape());
attributes.reset (new Attributes (*shader));
uniforms .reset (new Uniforms (*shader));

statusText = "GLSL: v" + juce::String (juce::OpenGLShaderProgram::getLanguageVersion(), 2);
}
else
{
statusText = newShader->getLastError(); // [4]
}
}
  • [2]まず頂点シェーダを追加し、次にフラグメントシェーダを追加して、コンパイルされたシェーダを1つのプログラムにリンクしようとします。
  • [3]シェーダーのコンパイルとリンクが成功したら、シェイプ、アトリビュート、ユニフォームのポインターをクリアし、新しく作成したシェーダーをシェーダープログラムのポインターに割り当て、シェイプ、アトリビュート、ユニフォームのポインターに新しいオブジェクトをインスタンス化します。
  • [4]オプションで、シェーダーのコンパイルに失敗したときのために、初期化ステータスを記録しておくことができます。

では、頂点、アトリビュート、ユニフォーム、シェイプを表す便利な構造を定義してみよう。

頂点構造体

頂点を表現するためには、以下のように4つの重要な変数が必要である:

    struct Vertex
{
float position[3];
float normal[3];
float colour[4];
float texCoord[2];
};
  • 位置:位置の配列は、3D局所空間における頂点の位置を表します。
  • 法線:法線配列は、隣接する面の法線から計算された当該頂点の法線ベクトルの方向を表す。
  • 色:色の配列は頂点の色を RGBA フォーマットで表します。
  • テクスチャ座標:テクスチャを使用する場合、その頂点で使用するテクスチャの2D座標を表します。

属性構造体

アトリビュート構造体は、基本的に複数のOpenGLShaderProgram::Attributeオブジェクトをまとめ、保存する属性はここで定義される:

        std::unique_ptr position, normal, sourceColour, textureCoordIn;

アトリビュートは頂点シェーダプログラムの頂点パラメータを記述するためのものなので、これらは先に定義したVertex構造体の変数に正確に対応しています。

予想通り、次のステップで定義したプライベート・ヘルパー関数を呼び出し、引数としてシェーダープログラムを渡すことで、コンストラクタ内でこれらのアトリビュートを作成する:

        explicit Attributes (juce::OpenGLShaderProgram& shaderProgram)
{
position .reset (createAttribute (shaderProgram, "position"));
normal .reset (createAttribute (shaderProgram, "normal"));
sourceColour .reset (createAttribute (shaderProgram, "sourceColour"));
textureCoordIn.reset (createAttribute (shaderProgram, "textureCoordIn"));
}

ヘルパー関数は順番にOpenGLShaderProgram::Attributeコンストラクタで新しいオブジェクトをインスタンス化する:

    private:
static juce::OpenGLShaderProgram::Attribute* createAttribute (juce::OpenGLShaderProgram& shader,
const juce::String& attributeName)
{
using namespace ::juce::gl;

if (glGetAttribLocation (shader.getProgramID(), attributeName.toRawUTF8()) < 0)
return nullptr;

return new juce::OpenGLShaderProgram::Attribute (shader, attributeName.toRawUTF8());
}

しかし、上記ではまず、シェーダープログラム内に属性が存在するかどうかをglGetAttribLocation()関数を呼び出します。返された数値が-1であれば、属性のインスタンス化を中止する。

の中でenable()関数を呼び出すことで、すべての属性が(存在するかどうかをチェックした後に)有効になります。glVertexAttribPointer()そしてglEnableVertexAttribArray()の機能を以下に示す:

        void enable()
{
using namespace ::juce::gl;

if (position.get() != nullptr)
{
glVertexAttribPointer (position->attributeID, 3, GL_FLOAT, GL_FALSE, sizeof (Vertex), nullptr);
glEnableVertexAttribArray (position->attributeID);
}

if (normal.get() != nullptr)
{
glVertexAttribPointer (normal->attributeID, 3, GL_FLOAT, GL_FALSE, sizeof (Vertex), (GLvoid*) (sizeof (float) * 3));
glEnableVertexAttribArray (normal->attributeID);
}

if (sourceColour.get() != nullptr)
{
glVertexAttribPointer (sourceColour->attributeID, 4, GL_FLOAT, GL_FALSE, sizeof (Vertex), (GLvoid*) (sizeof (float) * 6));
glEnableVertexAttribArray (sourceColour->attributeID);
}

if (textureCoordIn.get() != nullptr)
{
glVertexAttribPointer (textureCoordIn->attributeID, 2, GL_FLOAT, GL_FALSE, sizeof (Vertex), (GLvoid*) (sizeof (float) * 10));
glEnableVertexAttribArray (textureCoordIn->attributeID);
}
}

についてglVertexAttribPointer()関数は、頂点属性データの配列を、インデックス、サイズ、保持するデータのタイプなどの情報とともに定義します。最後の引数は、構造体内で以前に定義された他の属性に関して、データのオフセットを累積的に指定することに注意してください。次にglEnableVertexAttribArray()関数を使うと、実際の配列がコンテキスト内で使えるようになる。

の中でdisable()関数を呼び出すことで、正反対のことを行う。glDisableVertexAttribArray()関数をすべての属性に適用する:

        void disable()
{
using namespace ::juce::gl;

if (position.get() != nullptr) glDisableVertexAttribArray (position->attributeID);
if (normal.get() != nullptr) glDisableVertexAttribArray (normal->attributeID);
if (sourceColour.get() != nullptr) glDisableVertexAttribArray (sourceColour->attributeID);
if (textureCoordIn.get() != nullptr) glDisableVertexAttribArray (textureCoordIn->attributeID);
}

ユニフォームの構造

ユニフォームの構造も同様にOpenGLShaderProgram::Uniformオブジェクトを、ここで定義されているのと同じ方法で使用する:

        std::unique_ptr projectionMatrix, viewMatrix;

これらは、先に頂点シェーダープログラムで定義した行列変数に正確に対応しています。

予想通り、次のステップで定義したプライベート・ヘルパー関数を呼び出し、引数としてシェーダープログラムを渡すことで、コンストラクタ内でこれらのアトリビュートを作成する:

        explicit Uniforms (juce::OpenGLShaderProgram& shaderProgram)
{
projectionMatrix.reset (createUniform (shaderProgram, "projectionMatrix"));
viewMatrix .reset (createUniform (shaderProgram, "viewMatrix"));
}

ヘルパー関数は順番にOpenGLShaderProgram::Uniformコンストラクタで新しいオブジェクトをインスタンス化する:

    private:
static juce::OpenGLShaderProgram::Uniform* createUniform (juce::OpenGLShaderProgram& shaderProgram,
const juce::String& uniformName)
{
using namespace ::juce::gl;

if (glGetUniformLocation (shaderProgram.getProgramID(), uniformName.toRawUTF8()) < 0)
return nullptr;

return new juce::OpenGLShaderProgram::Uniform (shaderProgram, uniformName.toRawUTF8());
}
};

しかし、上記ではまず、シェーダプログラムにユニフォームが存在するかどうかをglGetUniformLocation()関数を使用する。もし返された数値が-1であれば、ユニフォームのインスタンス化を中止する。

シェイプ・ストラクチャー

Shape構造体は、OpenGL用語でティーポットオブジェクトを定義する場所です。メンバ変数は、ティーポットモデルのWavefront Objファイルと、すぐ下のサブ構造体として定義された頂点バッファの配列を格納するために使用されます:

        WavefrontObjFile shapeFile;
juce::OwnedArray vertexBuffers;

まず、頂点バッファがどのように定義されているかを見てみましょう。このバッファには、メッシュ内のインデックスの総数と、後のレンダリングに備えるための頂点バッファとインデックスバッファが含まれています:

            GLuint vertexBuffer, indexBuffer;
int numIndices;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VertexBuffer)
};

このクラスのコンストラクタは、次のようにして頂点バッファを初期化します:

            explicit VertexBuffer (WavefrontObjFile::Shape& aShape)
{
using namespace ::juce::gl;

numIndices = aShape.mesh.indices.size(); // [1]

glGenBuffers (1, &vertexBuffer); // [2]
glBindBuffer (GL_ARRAY_BUFFER, vertexBuffer);

juce::Array vertices;
createVertexListFromMesh (aShape.mesh, vertices, juce::Colours::green); // [3]

glBufferData (GL_ARRAY_BUFFER, // [4]
static_cast (static_cast (vertices.size()) * sizeof (Vertex)),
vertices.getRawDataPointer(), GL_STATIC_DRAW);

glGenBuffers (1, &indexBuffer); // [5]
glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData (GL_ELEMENT_ARRAY_BUFFER,
static_cast (static_cast (numIndices) * sizeof (juce::uint32)),
aShape.mesh.indices.getRawDataPointer(), GL_STATIC_DRAW);
}
  • [1]まず、描画したいメッシュのインデックスの数を取得する。
  • [2]次に、単一頂点バッファのバッファオブジェクト名をglGenBuffers()関数を使い、頂点属性をそれにバインドする。glBindBuffer()関数である。
  • [3]以下に定義するヘルパー関数を使って、ティーポットのメッシュから頂点リストを作成する。
  • [4]次に、頂点リストを頂点バッファにコピーするためにglBufferData()関数である。
  • [5]最後に、インデックス・バッファについても同様に、バッファ・オブジェクト名を生成し、頂点配列のインデックスをそれにバインドし、インデックス・バッファにインデックスをコピーします。

頂点リストからメッシュを作成するヘルパー関数は以下のように定義されている:

        static void createVertexListFromMesh (const WavefrontObjFile::Mesh& mesh, juce::Array& list, juce::Colour colour)
{
auto scale = 0.2f; // [6]
WavefrontObjFile::TextureCoord defaultTexCoord { 0.5f, 0.5f };
WavefrontObjFile::Vertex defaultNormal { 0.5f, 0.5f, 0.5f };

for (auto i = 0; i < mesh.vertices.size(); ++i) // [7]
{
const auto& v = mesh.vertices.getReference (i);
const auto& n = i < mesh.normals.size() ? mesh.normals.getReference (i) : defaultNormal;
const auto& tc = i < mesh.textureCoords.size() ? mesh.textureCoords.getReference (i) : defaultTexCoord;

list.add ({ { scale * v.x, scale * v.y, scale * v.z, },
{ scale * n.x, scale * n.y, scale * n.z, },
{ colour.getFloatRed(), colour.getFloatGreen(), colour.getFloatBlue(), colour.getFloatAlpha() },
{ tc.x, tc.y } }); // [8]
}
}
  • [6]まず、メッシュのスケール、デフォルトのテクスチャ座標、デフォルトの法線ベクトルについて、いくつかのローカル変数を定義します。
  • [7]次に、メッシュの各頂点について、頂点の位置、法線ベクトル、テクスチャ座標への参照を取得し、先ほど定義した新しい頂点オブジェクトを作成する。
  • [8]作成された頂点オブジェクトに対して、位置ベクトルと法線ベクトルをスケーリングし、ダミーの緑色を割り当て、最後に頂点リストに追加します。

デストラクタでは、頂点バッファとインデックス・バッファを削除するためにglDeleteBuffers()関数を各変数に使用する:

            ~VertexBuffer()
{
using namespace ::juce::gl;

glDeleteBuffers (1, &vertexBuffer);
glDeleteBuffers (1, &indexBuffer);
}

についてbind()関数は、シェイプが描画されるときに呼び出され、頂点バッファとインデックスバッファをバインドします。glBindBuffer()関数である:

void bind()
{
using namespace ::juce::gl;

glBindBuffer (GL_ARRAY_BUFFER, vertexBuffer);
glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
}

さて、シェイプコンストラクタに戻って、ティーポットのバイナリデータをWavefrontObjFile変数にロードしてみましょう:

        Shape()
{
auto dir = juce::File::getCurrentWorkingDirectory();

int numTries = 0;

while (! dir.getChildFile ("Resources").exists() && numTries++ < 15)
dir = dir.getParentDirectory();

if (shapeFile.load (dir.getChildFile ("Resources").getChildFile ("teapot.obj")).wasOk())
for (auto* s : shapeFile.shapes)
vertexBuffers.add (new VertexBuffer (*s));
}
警告

に "teapot.obj "ファイルが存在することを確認する。Resourcesフォルダに保存する。

ロードに成功すれば、WavefrontObjFileオブジェクトに含まれるすべての形状を繰り返し処理し、新しいVertexBufferオブジェクトを作成して頂点バッファ配列に追加することができます。

最後にdraw()関数が呼び出される。render()の機能である。OpenGLAppComponent後に定義する:

        void draw (Attributes& glAttributes)
{
using namespace ::juce::gl;

for (auto* vertexBuffer : vertexBuffers)
{
vertexBuffer->bind();

glAttributes.enable();
glDrawElements (GL_TRIANGLES, vertexBuffer->numIndices, GL_UNSIGNED_INT, nullptr);
glAttributes.disable();
}
}

メンバ変数配列の各頂点バッファに対して、まずbind()関数を呼び出して、頂点とインデックスのバッファをGLコンテキストにバインドします。次にenable()関数を定義する。最後にglDrawElements関数は、アトリビュートを無効にして空にする前に、頂点バッファに含まれる3つの頂点のすべてのセットを三角形として描画します。

すべてをまとめる

これで、ティーポットをレンダリングするためのすべてのコンポーネントが揃った。

前述したように、このアプリはOpenGLAppComponentクラスはMainContentComponentクラスである:

class MainContentComponent   : public juce::OpenGLAppComponent
{
public:

クラスのコンストラクタでは、ウィンドウのサイズを通常どおりsetSize()関数である:

    MainContentComponent()
{
setSize (800, 600);
}

クラスのデストラクタでは、クラスが破壊される前にOpenGLシステムがシャットダウンされていることを確認するためにshutdownOpenGL()関数である:

    ~MainContentComponent() override
{
shutdownOpenGL();
}

前述したようにOpenGLAppComponentクラスは、グラフィックス・アプリケーションの実装を容易にするために、スタートアップ関数とシャットダウン関数を提供します。このクラスではinitialise()関数を呼び出す。createShaders()このように頂点シェーダーとフラグメントシェーダーを準備するために、先に定義した:

    void initialise() override
{
createShaders();
}

に関してはshutdown()関数では、メンバ変数へのポインタをすべてNULLに設定することで、漏れがないようにしている:

    void shutdown() override
{
shader .reset();
shape .reset();
attributes.reset();
uniforms .reset();
}

次に、実際のレンダリングは[OpenGLAppComponent::render()](https://docs.juce.com/master/classOpenGLAppComponent.html#af4e76e80318343d5302be7af6bf4cdbc "Called to render your openGL.")機能については後述する:

    void render() override
{
using namespace ::juce::gl;

jassert (juce::OpenGLHelpers::isContextActive());

auto desktopScale = (float) openGLContext.getRenderingScale(); // [1]
juce::OpenGLHelpers::clear (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId)); // [2]

glEnable (GL_BLEND); // [3]
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

glViewport (0,
0,
juce::roundToInt (desktopScale * (float) getWidth()),
juce::roundToInt (desktopScale * (float) getHeight())); // [4]

shader->use(); // [5]

if (uniforms->projectionMatrix.get() != nullptr) // [6]
uniforms->projectionMatrix->setMatrix4 (getProjectionMatrix().mat, 1, false);

if (uniforms->viewMatrix.get() != nullptr) // [7]
uniforms->viewMatrix->setMatrix4 (getViewMatrix().mat, 1, false);

shape->draw (*attributes); // [8]

// Reset the element buffers so child Components draw correctly
glBindBuffer (GL_ARRAY_BUFFER, 0); // [9]
glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0);
}
  • [1]まず、GLコンテキストがアクティブであることを確認する。[OpenGLHelpers::isContextActive()](https://docs.juce.com/master/classOpenGLHelpers.html#aeb9c20e316e3aa59046a4fe4cb2562f8 "Returns true if the current thread has an active OpenGL context.")関数を使って、レンダリング表示のスケールファクターを取得できるようにする。
  • [2]適切なルック&フィールの色で背景を塗ることで、ディスプレイをクリアにすることができます。
  • [3]これはチュートリアルの範囲を超えていますがglEnable()関数は "GL_BLEND "オプションを有効にし、計算されたフラグメントの色とカラーバッファ値をブレンドします。ブレンド方法はglBlendFunc()関数で透明度の計算を指定する。
  • [4](その)glViewport()関数は、幅と高さにレンダリング表示倍率を掛けることによって、デバイス画面に対するGLウィンドウのビューポートを設定します。
  • [5]を呼び出すことでuse()関数でシェーダー・ポインターを指定し、このGLコンテキストで使用したいシェーダーを指定します。
  • [6]投影行列をヘルパー関数から一様変数として設定し、シェーダを計算します。
  • [7]また、シェーダーを計算するヘルパー関数から、ビュー行列を一様変数として設定します。
  • [8]最後にdraw()関数は、GLコンテキストと引数として指定された属性内でティーポットをレンダリングするために、shapeポインタ上で先に定義された。
  • [9]また、頂点属性と頂点配列のインデックスを空にしておく。glBindBuffer()関数をGLコンテキスト上で実行する。
注記

接頭辞が "gl "である関数はすべて、JUCEライブラリではなく、開発マシンのOpenGLライブラリに含まれています。

概要

このチュートリアルでは、OpenGL JUCE アプリケーションのセットアップ方法を学びました。特に

  • の機能を学ぶ。OpenGLAppComponentクラスである。
  • WavefrontのObjファイルをOpenGLレンダラーにロードした。
  • 計算された投影行列とビュー行列。
  • を設定した。OpenGLContextそしてシェーダーを使ってレンダリングする。

こちらも参照