I.はじめに
このチュートリアルでは、GLEWおよびGLFWライブラリを使用します。このチュートリアルを通じて、 OpenGL がどのように三角形を描画するかを
簡単かつ深く理解できます。 OpenGL が何であるかわからない場合は、OpenGL を読んで理解を深めることができます。
2. 基本的な機能とステートメントの紹介
以下の関数を読むことで、OpenGL の基本的な機能の概要を脳に残すことができます。(深く理解する必要はありません。このセクションは無視してかまいません)
- glfwInit()
glfw を初期化し、いくつかのヌル ポインターを割り当てます。 - glfwWindowHint(名前, 値)
glfw ライブラリのいくつかの属性を設定します。name は属性フィールド、value は設定値です。
GLFW_CONTEXT_VERSION_MAJOR は、OpenGL のメジャー バージョン番号を参照します。
GLFW_CONTEXT_VERSION_MINOR は、OpenGL のマイナー バージョン番号を参照します。
GLFW_OPENGL_PROFILE はレンダリング モードを指し、GLFW_OPENGL_CORE_PROFILE はコア モードです。
GLFW_RESIZABLE はウィンドウが調整可能であることを示し、GL_FALSE は調整可能でないことを示します。 - glfwCreateWindow(幅、高さ、タイトル、NULL、NULL)
glfw ウィンドウを作成します。パラメータは、幅、高さ、ウィンドウ タイトルです。GLFWwindow* タイプを返します。 - glfwMakeContextCurrent(ウィンドウ)
ウィンドウのコンテキストを現在のスレッドのプライマリ コンテキストとして設定するように GLFW に指示します。 - glfwSetKeyCallback(ウィンドウ、コールバック)
GLFW を介して関数を適切なコールバックに登録します。このコールバックは関数名です。 - glewExperimental = GL_TRUE
OpenGL 関数ポインターを管理するときに、GLEW がより最新の手法を使用するようにします. GL_FALSE に設定すると、OpenGL のカーネル モードを使用するときに問題が発生する可能性があります. - glewInit()
グリューを初期化します。 - glfwGetFramebufferSize(window, &width, &height);
ウィンドウ window の幅と高さのサイズを取得します。 - glViewport(x、y、幅、高さ)
ビューポートの位置を設定します。x と y はウィンドウ内のビューポートの左下隅の位置を表し、幅と高さはビューポートのサイズを表します。 - glCreateShader(シェーダー)
シェーダー シェーダーを作成し、シェーダーへの参照を返します。
シェーダー値は GL_VERTEX_SHADER と GL_FRAGMENT_SHADER です。 - glShaderSource(shader, n, &shaderSource, NULL)
シェーダー シェーダーのソース コードをバインドします。n はソース コード内の文字列の数、shaderSource はソース コードの文字列です。 - glCompileShader(シェーダー)
シェーダー シェーダーをコンパイルします。 - glGetShaderiv(シェーダー, GL_COMPILE_STATUS, &success)
シェーダが正常にコンパイルされたかどうかを確認します。成功は GLint 型です。 - glGetShaderInfoLog(シェーダー、長さ、NULL、infoLog)
エラー メッセージを取得し、infoLog 文字配列に格納します。ここで、長さは配列の長さです。 - glCreateProgram()
シェーダー プログラムを作成し、その参照を返します。 - glAttachShader(シェーダープログラム、シェーダー)
シェーダー プログラム shaderProgram にシェーダー shader を追加します。 - glLinkProgram(シェーダープログラム)
シェーダー プログラムをリンクします。 - glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success)
シェーダ プログラムのリンクが成功したかどうかを確認します。 - glGetProgramInfoLog(shaderProgram, length, NULL, infoLog)
リンクされたシェーダー プログラムを取得する - glUseProgram(シェーダープログラム)
シェーダー プログラムをアクティブにします。その後、すべてのシェーダー呼び出しとレンダリング呼び出しでこのプログラム オブジェクトが使用されます。 - glDeleteShader(シェーダー)
シェーダー オブジェクトを削除します。シェーダー オブジェクトをプログラム オブジェクトにリンクした後、シェーダー オブジェクトを削除します。 - glGenVertexArrays(n, &VAO)
頂点配列オブジェクト (VAO) への参照を返します。また、n≧2 の場合は参照の配列を返します。 - glGenBuffers(1, &FEB)
Vertex Buffer Objects (VBO) への参照を返します。また、n≧2 の場合は参照の配列を返します。 - glBindVertexArray(VAO)
VAO をバインドします。 - glBindBuffer(GL_ARRAY_BUFFER, VBO)
VBO をバインドします。 - glBufferData(GL_ARRAY_BUFFER, sizeof(頂点), 頂点, GL_STATIC_DRAW)
ユーザー定義データを現在バインドされているバッファーにコピーする専用の関数。
最初のパラメータはターゲット バッファのタイプ、2 番目のパラメータは転送されるデータのサイズをバイト単位で指定、3 番目のパラメータは送信する実際のデータ、4 番目のパラメータはグラフィックス カードが特定のデータをどのように管理するかを指定します。 . GL_STATIC_DRAW
: データは変更されないか、ほとんど変更されません。
GL_DYNAMIC_DRAW: データが大幅に変更されます。
GL_STREAM_DRAW : データは描画されるたびに変化します。 - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0)
頂点バッファー オブジェクトと頂点属性をリンクします。 - glEnableVertexAttribArray(0)
頂点属性を有効にする引数として頂点属性の位置の値を取ります (頂点属性はデフォルトで無効になっています)。 - glfwWindowShouldClose(ウィンドウ)
GLFW が終了するように求められたかどうかを確認します。 - glfwPollEvents()
イベントをチェック - glClearColor(0.2f, 0.3f, 0.3f, 1.0f)
画面をクリアするために使用する色を設定します。 - glClear(GL_COLOR_BUFFER_BIT)
画面のカラー バッファをクリアします。 - glfwSwapBuffers(ウィンドウ)
ウィンドウのバッファをスワップします。 - glDeleteVertexArrays(1, &VAO)
対応する VAO オブジェクトを削除します。 - glDeleteBuffers(1, &VBO)
の VBO オブジェクトを削除します。 - glfwTerminate()
GLFW によって割り当てられた空きメモリ
3. 重要事項説明モジュール
OpenGL レンダリング プログラムの一般的なフレームワークは固定されています。このセクションでは、完全で独立した各モジュールについて学習します。
これらの基本的なモジュールを学習したら、モジュールをつなぎ合わせて完全な OpenGL プログラムを作成できます。
記事の全体的な文脈について混乱している場合は、記事の目次を参照してください。
断片化されたモジュール コードに頭を悩ませている場合は、読み進めることができます。各段階で完全なコードを示します。
モジュール コードを読んで混乱した場合は、読むのをやめないでください。モジュール コードの後に説明するセクションがあるからです。
1 GLFW および GLEW ヘッダー ファイルへの参照
マクロ定義は、 glew が静的にリンクされていることを示しています。
// GLEW的导入
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW的导入
#include <GLFW/glfw3.h>
OpenGL のプリミティブ型
OpenGL を使用する場合は、OpenGL で定義されたプリミティブ型を使用することをお勧めします。たとえば、float を使用する場合、接頭辞 GL を追加します (したがって、GLfloat と書きます)。int、uint、char、bool などについても同様です。OpenGL によって定義されたこれらの GL プリミティブ型のメモリ レイアウトはプラットフォームに依存せず、int などのプリミティブ型はオペレーティング システムによってメモリ レイアウトが異なる場合があります。GL プリミティブ型を使用すると、プログラムがさまざまなプラットフォームで一貫して動作することが保証されます。
2 GLFWの初期化と設定
2.1 モジュールコード
// 初始化 GLFW
glfwInit();
// 设置GLFW的使用 OpenGL的版本 为3.3
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
// 设置GLFW使用OpenGL的 核心模型
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// 设置GLFW窗口大小的可调整性为:否
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
// 创建一个宽为WIDTH、高为HEIGHT、标题为"LearnOpenGL"的窗口
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
// 将窗口window的上下文设置为当前线程的主上下文
glfwMakeContextCurrent(window);
2.2 実行結果と完全なコード
GLEW と GLFW を正しくインポートし、GLFW を初期化して構成したら、プログラムを実行すると、以下に示すような真っ白な GLFW ウィンドウが表示されます。
ここまでの完全なコードは次のとおりです。
#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
const GLuint WIDTH = 800, HEIGHT = 600;
int main()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE,GL_FALSE);
GLFWwindow* window = glfwCreateWindow(WIDTH,HEIGHT,"LearnOpenGL",NULL,NULL);
if (window == NULL)
{
// 当window为NULL时说明GLFW窗口创建失败
std::cout << "Failed to create GLFW window" << std::endl;
// 窗口创建失败则释放GLFW分配的内存,结束程序返回-1表示错误。
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
// 循环避免程序结束、窗口关闭
while (1);
}
3 GLEW の初期化と設定
// 设置GLEW为现代化的
glewExperimental = GL_TRUE;
// 初始化GLEW
glewInit();
4 ビューポート構成
4.1 モジュールコード
// 视口位置和大小的设置
glViewport(x, y, width, height);
この時点でプログラムを実行すると、上記と同じ白いウィンドウしか表示されないことがわかります。ビューポートは、レンダリングされたイメージが表示される場所です。イメージがない理由は、 OpenGL レンダリング関数を呼び出していないためです。レンダリング関数は、OpenGL の状態に基づいて対応する操作を実行する状態適用関数です。
4.2 レンダリング関数の紹介
本当にいくつかの写真が必要なので、「スーパークラス」でレンダリング関数が導入されました。
glClear(GL_COLOR_BUFFER_BIT)
その機能は、特定の色を使用して画面のカラー バッファーをクリアすることです。そのパラメーターは、状態アプリケーション関数であるバッファーの種類を示し、OpenGL の状態に基づいて対応するレンダリング操作を実行します。画面を真っ白にすれば十分だと思うかもしれませんが、なぜ特定の色を指定するのでしょうか。
すべてのプログラム ウィンドウの背景が白であるとは限らないため、暗い黒や青緑の場合もあります。そのため、「画面を更新する」ために特定の背景色を指定する必要があります。別の質問があるかもしれませんが、特定の色を指定しましたか? これは、OpenGL の関数が状態適用関数と状態設定関数に分かれているためです. いわゆる特定の色は、実際には画面のカラー キャッシュをクリアするために使用される色を指します. この色は OpenGL の状態であるため、状態設定を使用する必要があります特定の色を指定する関数。状態設定関数は次のとおりです。
glClearColor(0.2f, 0.3f, 0.3f, 1.0f)
その役割は、画面をクリアするために使用される特定の色を設定すること、つまり OpenGL の状態を変更することであり、4 つのパラメーターは RGBA です。
知っておくべきこと: 最近の画面イメージは、ダブル バッファリングを使用してレンダリングされます。画面にコンテンツを表示する必要があるときに、コンテンツが画面に 1 つずつ描画されると、描画速度が同期されず、画面イメージのティアリングが発生します。解決策は、事前に提示する必要がある完全な画像を生成し、表示する画像全体をグラフィックス カードのバッファーに格納し、画面の内容を交換する必要がある場合は、バッファー内の完全な画像を表示することです。オン、これはダブル バッファリング テクノロジーです。OpenGL でバッファを交換するステップでは、関数を使用して明示的に指定する必要があります。
glfwSwapBuffers(window)
これは、ウィンドウ window の 2 つのバッファーを交換する状態アプリケーション関数です。これらの 2 つのバッファは、ダブル バッファリング テクノロジのフロント バッファとバック バッファを指します: フロント バッファは画面にコンテンツを表示するために使用され、バック バッファは描画に使用されます. 各フレームが開始されると、2 つのバッファがスワップされるため、バック バッファは新しいコンテンツを再び描画できます。
4.3 実行結果と完全なコード
上記のコードを使用すると、次の図のウィンドウ コンテンツを取得できます。
上の図の対応するコードは次のとおりです。
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
#include <iostream>
const GLuint WIDTH = 800, HEIGHT = 600;
int main()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE,GL_FALSE);
GLFWwindow* window = glfwCreateWindow(WIDTH,HEIGHT,"LearnOpenGL",NULL,NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glewExperimental = true;
glewInit();
GLint width, height;
glfwGetFramebufferSize(window, &width, &height);
glViewport(0, 0, 100, 100);
while (1)
{
// 指定清空屏幕颜色缓冲的颜色(改变状态后其会保存于OpenGL中)
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
// 清空屏幕颜色缓冲,这里生成的屏幕画面还存储于后缓冲区中
glClear(GL_COLOR_BUFFER_BIT);
// 交换前缓冲区和后缓冲区
glfwSwapBuffers(window);
}
glfwTerminate();
return 0;
}
4.4 拡張
1. glClear() は、ビューポートではなくウィンドウ全体用です.上記のコードに示されているように、glViewport(0, 0, 100, 100) によって設定されたビューポートのサイズは非常に小さいですが、クリアする青緑色画面のカラー キャッシュがまだウィンドウ全体でいっぱいです。
2.コメントにあるように、glClearColor は OpenGL の状態を設定し、その状態は OpenGL に保存されます. 一度設定すれば十分だと思うかもしれませんが、これは OpenGL の初心者によくある間違った考えです。将来、レンダリング コードがどんどん大きくなり、レンダリングの状況がますます増える場合、画面のカラー バッファをクリアするために使用する色を何度も変更することがあります. このとき、必要なすべての Color を判断して使用する必要があります. したがって、各フレームはレンダリングに必要なさまざまな状態を設定する必要があり、これを行う必要があります。
3.上記のコードで "glClearColor(0.2f, 0.3f, 0.3f, 1.0f);" と "glClear(GL_COLOR_BUFFER_BIT);" を削除すると、真っ黒なウィンドウが表示されます。構成されていないバック バッファーは純粋な黒の画像であるため、これは簡単に理解できます。そのため、フロント バッファーとバック バッファーの両方が純粋な黒の画像になるようにバッファーが常に交換されます。
4.上記のコードのループ全体を次のコードに置き換えると、点滅するウィンドウが表示されます。
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
while (1)
{
// 交换前缓冲区和后缓冲区
glfwSwapBuffers(window);
}
分かりやすいです。"glClearColor(0.2f, 0.3f, 0.3f, 1.0f);" と "glClear(GL_COLOR_BUFFER_BIT);" を渡して、青緑色の背景の画像をバック バッファーに格納します。フロント バッファ コンテンツ (レンダリング命令がないためすべて白)、最初にスワップすると、バック バッファの青緑色の背景がフロント バッファにスワップされ、この時点でバック バッファのコンテンツはスワップされません。設定済み (実行せず、画面の色をクリアすることで、バックバッファーへの書き込みなし) がデフォルトの black です。そのため、後続のフレームバッファのスワップごとに、画面上でシアンと黒が交互に表示され続け、画面がちらつきます。
5.上記のループ コードを次のコードに変更すると、ウィンドウのちらつきが停止することがわかります。
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glfwSwapBuffers(window);
glClear(GL_COLOR_BUFFER_BIT);
while (1)
{
// 交换前缓冲区和后缓冲区
glfwSwapBuffers(window);
}
これは、最初のスワップ後にフロント バッファーがシアンの背景に置き換えられ、2 番目の glClear によってバック バッファーもシアンの背景に置き換えられるためです。すると、ループ内でスワップする前後のバッファが同じ色になるので、ちらつきがなくなります。(この話は、デフォルトバッファの背景が黒であることを物語っています。レンダリング機能が実行されていない場合、前後のバッファの内容は変化しません。)
5 シェーダー シェーダーの作成とコンパイル
OpenGL で記述できる 3 つのシェーダーがあります。
頂点シェーダー VetrtexShader
フラグメント シェーダー FragmentShader
ジオメトリ シェーダー GeometryShader
頂点シェーダーとフラグメント シェーダーは、OpenGL がデフォルトでこれら 2 種類のシェーダーを提供しないため、記述しなければならないものです。デフォルトのシェーダー。
5.1 モジュールコード
// 顶点着色器源代码
const GLchar* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 position;\n"
"void main()\n"
"{\n"
"gl_Position = vec4(position.x, position.y, position.z, 1.0);\n"
"}\0";
// 片段着色器源代码
const GLchar* fragmentShaderSource = "#version 330 core\n"
"out vec4 color;\n"
"void main()\n"
"{\n"
"color = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
// 顶点着色器的创建和编译
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
// 顶点着色器编译状态检查
GLint success;
GLchar infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// 片段着色器的创建和编译
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
// 片段着色器编译状态检查
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
5.2 VertexShader シェーダー ソース コード
#version 330 core
layout(location = 0) in vec3 position;
void main()
{
gl_Position = vec4(position.x, position.y, position.z, 1.0);
}
最も単純な頂点シェーダー コードを上に示します.まず、シェーダーが使用する OpenGL のバージョンを宣言します.330 は OpenGL3.3 のバージョンに対応します.
vec3 position は xyz コンポーネントを持つオブジェクトの定義を表し、in キーワードはこのオブジェクトが頂点シェーダーに入力されることを表し、layout(location = 0) はこの入力オブジェクトの位置識別子を表します。
gl_Position は、レンダリングする必要がある頂点の位置座標を参照します. 頂点シェーダーでは、どの頂点をレンダリングする必要があるかを伝える必要があります. つまり、 gl_Position に値を割り当てます.
頂点シェーダーの入力は特殊で、頂点データから直接入力を受け取ります。
5.3 FragmentShader シェーダーのソース コード
#version 330 core
out vec4 color;
void main()
{
color = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
最も単純なフラグメント シェーダー コードを上に示します。OpenGLバージョンもフラグメント シェーダーで宣言する必要があります。
タイプ vec4 のカラー オブジェクトはフラグメント シェーダーで定義され、out キーワードで出力変数として宣言されます。フラグメント シェーダは、ラスタライズ後に各ピクセルのカラー値を出力する必要があることに注意してください。つまり、RGBA 4 コンポーネント型、つまり vec4 型のオブジェクトをフラグメント シェーダーで定義し、out キーワードを使用して出力変数として宣言し、main 関数で値を代入する必要があります。ここでは、出力カラー値は 1 色に固定されています。
5.4 シェーダーの作成とコンパイル
シェーダーのソース コードを用意するだけでは不十分です。プログラムでシェーダー オブジェクトを作成し、シェーダー オブジェクトに対応するソース コードをバインドしてコンパイルする必要があります。具体的なプロセスは次のとおりです。
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
まず、「glCreateShader」を使用して、対応するシェーダー オブジェクトへの参照を返します。ここで渡されるパラメーターは「GL_VERTEX_SHADER」であるため、頂点シェーダーの参照が返されます。フラグメント シェーダを作成する場合は、「GL_FRGEMENT_SHADER」を渡します。
次に、「glShaderSource」関数を使用して、シェーダー ソース コード vertexShaderSource をシェーダー オブジェクト vertexShader にバインドします。パラメーター 1 は、ソース コードに文字列のみが含まれていることを示します。
最後に、シェーダー オブジェクトに含まれるシェーダー ソース コードは、「glCompileShader」関数を使用してコンパイルされます。
5.5 シェーダ ソース コードのコンパイルの検出
シェーダーのソース コードは、さまざまな問題が発生する可能性があるため、毎回正常にコンパイルされるわけではありません。シェーダーは GPU でコンパイルされるため、エラーが発生しても Vis Stdio では表示されないため、シェーダーのコンパイル ステータスを手動で取得する必要があります。具体的なプロセスは次のとおりです。
GLint success;
GLchar infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
success を使用してコンパイルが成功したかどうかを記録し、infoLog を使用してエラー メッセージを記録します。「glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success)」は、シェーダー vertexShader が正常にコンパイルされたかどうかを変数 success に格納します。コンパイルエラーが発生した場合、「glGetShaderInfoLog(vertexShader, 512, NULL, infoLog)」は、シェーダー vertexShader のコンパイルエラー情報を infoLog に保存します。
6 シェーダープログラムの作成
モジュールコード
// 创建着色器程序
GLuint shaderProgram = glCreateProgram();
// 添加顶点着色器和片段着色器到程序中
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
// 链接着色器程序中的着色器
glLinkProgram(shaderProgram);
// 检查程序链接状态
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
// 如果链接失败则获取错误信息存储于infoLog中
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
// 着色器程序中的着色器链接成功后就可以删除了,节省显存
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
レンダリング パイプラインはシェーダーのセット全体で構成されており、カスタム シェーダーの一部だけでは十分ではありません。シェーダー プログラムを作成する必要があります. シェーダー プログラムを作成した後, カスタマイズできない多くのシェーダーが含まれています. このシェーダー プログラムにカスタム シェーダーを追加し、シェーダー プログラム内の各シェーダーをリンクします. このようにして、完全なシェーダー プログラム。
結果はプログラムオブジェクトであり、新しく作成されたプログラムオブジェクトをパラメーターとして glUseProgram 関数を呼び出して、プログラムオブジェクトをアクティブにすることができます。
glUseProgram(shaderProgram)
glUseProgram 関数呼び出しの後、すべてのシェーダー呼び出しとレンダリング呼び出しで、このシェーダー プログラム オブジェクトが使用されます。
7頂点入力
OpenGL のシェーダーは順次実行され、各シェーダーは前のシェーダーの出力を入力として受け取ります。しかし、5.2 では、頂点シェーダーの入力は特別であると述べました。頂点データから入力を直接読み取ります。
頂点データとは?頂点データは、頂点のさまざまな情報を格納するデータで、最初はメモリ上に存在します. GPU 内の頂点シェーダーは、レンダリング時に頂点データを必要とするため、メモリ内の頂点データをビデオ メモリに転送する必要があります.
やらなければならないことは、頂点データをグラフィックカードに送信し、頂点データを解析するように OpenGL を構成することです。
頂点シェーダーは、頂点データを格納するために GPU にメモリを作成しました。Vertex Buffer Objects (VBO) を介してこのメモリを管理する必要があります。
モジュールコード
// 定义顶点数据(三个顶点,每个顶点包含x y z属性)
GLfloat vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
// 创建VBO为缓冲类型的对象
GLuint VBO;
glGenBuffers(1, &VBO);
// 绑定VBO为GL_ARRAY_BUFFER缓冲类型
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 将我们定义的数据vertices复制到GL_ARRAY_BUFFER缓冲类型当前绑定的缓冲对象VBO上
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
最初に、頂点データを定義します。このデータは任意のカスタム フォームにすることができます。この例では、頂点データを 9 つの GLfloat 型変数を含む配列として定義します。それぞれが 3 つの座標 xyz を持つ 3 つの頂点として理解されるべきだと思います。
頂点シェーダーは、ビデオ メモリ内にバッファー領域を作成しました。このバッファー領域は、頂点データを格納するために特別に使用されます。頂点データに対応するバッファーの種類は GL_ARRAY_BUFFER です。
バッファ タイプの VBO オブジェクトを作成し、オブジェクト VBO のバッファ タイプを GL_ARRAY_BUFFER にバインドします。
GL_ARRAY_BUFFER のバインドは、バッファー オブジェクト VBO が頂点属性データに使用され、頂点シェーダーが頂点データを必要とするときに VBO から読み取ることを示します。
glBufferData は、対応するバッファ タイプにバインドされたバッファ オブジェクトにデータをコピーします。
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW) 関数を使用して、GL_ARRAY_BUFFER バッファ タイプにバインドされたバッファ オブジェクト VBO に頂点データ頂点をコピーします。
上記は少し厄介だと思いますか? 頂点データを VBO に直接コピーしてみませんか? OpenGL ステート マシンに基づいて動作するため、頂点データをビデオ メモリ VBO に直接コピーすることはできず、OpenGL モデルは、ビデオ メモリに対応するどの VBO (複数の VBO を定義できます) が頂点データを格納する必要があるかを知りません。そこでこの関数はGL_ARRAY_BUFFERのバッファ型にバインドされたバッファオブジェクトを対象としてデータのコピー操作を行います。
上記の操作の後、頂点シェーダーは、定義された VBO の頂点データを読み取ることを認識します。
8 リンク頂点アトリビュート
頂点シェーダーを使用すると、頂点属性の形式で任意の入力を指定できます。これにより非常に柔軟になりますが、入力データのどの部分が頂点シェーダーのどの頂点属性に対応するかを手動で指定する必要があることを意味します。したがって、OpenGL がレンダリング前に頂点データを読み取る方法を指定する必要があります。
モジュールコード
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
上記のモジュールはglVertexAttribPointer 関数を使用して、OpenGL に頂点データの解析方法を通知します。
VBO に渡すのは float 型の配列で、これには float 型の 9 つの変数が含まれています. 3 つの変数ごとに頂点の属性を表すことがわかっています. 次に、これを OpenGL に伝える必要があります.
OpenGL では、頂点位置属性 VertexPosition の位置値レイアウトを 0、頂点カラー属性 VertexColor の位置値レイアウトを 1 と規定しています。頂点位置属性のインデックス値は 0、色のインデックス値は 1 で、指定された属性です。
したがって、上記の頂点シェーダーには定義があります: layout(location = 0), location = 0 は、この入力属性が頂点位置属性であることを意味し、ここで最初のパラメーターが 0 であることは、この関数によって解析されるデータがvertex Position 属性なので、解析された頂点位置属性は、頂点シェーダーの location=0 の属性に入力されます。
glVertexAttribPointer 関数の最初のパラメータは、設定したい頂点属性を示します. 今設定したいのは頂点の位置属性なので、0 を渡します. 2 番目のパラメーターは、頂点属性のサイズを指定します。渡す各頂点の属性は 3 つの GLfloat 値で構成されているため、各頂点属性のサイズは 3 です。つまり、3 つの値で構成されています。3 番目のパラメーターは、データの型、つまり、各値の型を指定します。対応する型 GLfloat を渡します。4 番目のパラメーターは、データを標準化するかどうかを定義します.GL_TRUE に設定すると、すべてのデータが [-1, 1] にマップされます.5 番目のパラメーターは Stride と呼ばれ、頂点属性の連続するグループ間の間隔を示します。それぞれの異なる頂点属性には 3 つの GLfloat の「距離」があるため、3* sizeof(GLfloat) を渡します。
このパラメータは何の役にも立たないと思うかもしれませんが、結局のところ、各頂点には 3 つの GLfloat が含まれているので、3 つの GLfloat の違いはありませんか? 実際、これはこの例のためのものであり、各頂点に 3 つの GLfloat 値を追加して、その色の RGB 値を表すとどうなるでしょうか? このとき、頂点位置の読み取りを構成すると、各頂点には 6 つの GLfloat 値が含まれ、最初の 3 つの変数は位置のままで、2 ですが、ステップ サイズは 6 GLfloat になり、結局、頂点属性は次の頂点から始まります。属性 はじめはGLfloat値6個の差があったので、今でもダメだと思っていませんか?
最後のパラメータの型が GLvoid* になっているので、この変な型変換を行う必要があり、これは buffer 内の実際の読み込みデータの位置のオフセットを示しています. 役に立たないと思うかもしれませんが、上記 3 色を追加した後、各頂点には 6 つの GLfloat 属性値が含まれます.色を読み取る場合は、各頂点属性の 3 番目の値から開始する必要があります.これを指定するには? このパラメーターを使用してオフセットを指定するだけです。具体的な値は (GLvoid*)(3* sizeof(GLfloat)) です。
glEnableVertexAttribArray(0) の機能は、場所 = 0 で頂点の属性値を有効にすることです。これは、頂点属性がデフォルトで無効になっているためです (パフォーマンスを節約するため)。
9 VAOの使用
将来のアプリケーションでは、レンダリング シーンに多くのオブジェクトが存在する可能性があります. オブジェクトをレンダリングする前に、glVertexAttribPointer 関数を再利用して頂点データの解析方法を指定する必要があります. 結局、前に異なる解析方法で他のオブジェクトをレンダリングする可能性があります.それ。将来、各オブジェクトが位置の解決方法だけでなく、色やテクスチャの解決方法も設定する必要がある場合、非常に面倒になることは間違いありません。オブジェクトの解析方法 (位置、色、テクスチャの解析を含む) を格納する変数を作成し、解析方法を構成する必要があるときに直接使用する方法はありますか? これはもちろんVAOです。VAO は頂点データの位置をビデオ メモリ、つまり VBO に記録することに
注意してください。したがって、VAO を使用する順序は、VAO と VBO を作成し、VAO をバインドしてから VBO をバインドし、VBO を設定してから VAO を設定する必要があります。まさに頂点データと頂点データの解析方法が VAO に記録されているため、レンダリング ループでは、VAO をバインドしてグラフィックスを直接レンダリングするだけで済みます。レンダリングされたグラフィックスの頂点データと対応する解析方法は、VAO を介して見つけることができます。
モジュールコード
// 创建VAO并绑定
// 创建VAO、VBO
GLuint VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
// 绑定VAO
glBindVertexArray(VAO);
// 绑定VBO
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 将顶点数据vertices复制到GL_ARRAY_BUFFER绑定的对象VBO中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 设置解析顶点数据的方式到绑定VertexArray的VAO中
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
// 启用顶点属性0号位置
glEnableVertexAttribArray(0);
// 解绑VBO(因为暂时不需要再向这个VBO传入数据了)
glBindBuffer(GL_ARRAY_BUFFER, 0);
// 解绑VAO(因为暂时不需要再配置这个VAO的解析方式了)
// 要用的时候再绑定需要的VAO
glBindVertexArray(0);
上記のコードは glBindVertexArray(VAO) を使用して、定義した VAO オブジェクトを Vertex Array Object (VAO) にバインドします。次に、「glVertexAttribPointer」関数を使用して解析メソッドを設定します。「glVertexAttribPointer」関数は、設定された解析メソッドを現在頂点配列オブジェクトにバインドされている VAO オブジェクトに自動的にバインドします。そのため、VAO を使用して解決方法を保存します。解析方法が設定されている場合は、当面VAOの解析方法を指定する必要がないのでアンバインドし、「glVertexAttribPointer」関数が設定した解析方法に影響を与えないように注意してください。バインド解除後の解析方法 VAO にはまだ存在します。
VAO の解析メソッドを使用する必要がある場合は、OpenGL が VAO の解析メソッドを使用するようにバインドする必要があります。
VAO 用か VBO 用かに関係なく、それらを対応する型にバインドしてから、対応する型を設定して、VAO と VBO を設定するという目的を達成することに注意してください。VAO と VBO を設定したら、後で間違った設定を避けるために、 VAO と VBO のバインドを解除する必要があります。
10 レンダリング モジュール
モジュールコード
// 只要窗口不应关闭就继续循环
while (!glfwWindowShouldClose(window))
{
// 检查窗口事件
glfwPollEvents();
// 清空屏幕缓存
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 使用着色器程序进行渲染
glUseProgram(shaderProgram);
// 绑定VAO指定解析顶点属性的方式
glBindVertexArray(VAO);
// 绘制图形(基于VBO、VAO调用函数)
glDrawArrays(GL_TRIANGLES, 0, 3);
// 使用完VAO后应该解绑VAO
glBindVertexArray(0);
// 交换前后缓冲区
glfwSwapBuffers(window);
}
// 渲染程序结束,释放GPU中VAO和VBO的内存
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
// 释放GLFW申请的内存
glfwTerminate();
return 0;
4. 実行結果と完全なコード
レンダリング結果: 三角形
上記のモジュール コードを順番につなぎ合わせ、プログラムを実行すると、次の結果が得られます。
完全なコード
#include <iostream>
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
// 定义窗口大小
const GLuint WIDTH = 800, HEIGHT = 600;
// 定义字符串:着色器源代码(这种形式会自动拼接,分行只是好看)
const GLchar* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 position;\n"
"void main()\n"
"{\n"
"gl_Position = vec4(position.x, position.y, position.z, 1.0);\n"
"}\0";
const GLchar* fragmentShaderSource = "#version 330 core\n"
"out vec4 color;\n"
"void main()\n"
"{\n"
"color = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
int main()
{
// GLFW的初始化
glfwInit();
// GLFW的配置
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
// 创建glfw窗口并设置窗口的状态为当前线程的主状态
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
glfwMakeContextCurrent(window);
// 设置glew更加现代化
glewExperimental = GL_TRUE;
// 初始化glew
glewInit();
// 定义视口左下角在窗口的位置 和 视口的大小
glViewport(0, 0, 800, 600);
// 顶点着色器的创建、绑定源代码、编译
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
// 检查顶点着色器编译是否成功
GLint success;
GLchar infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success)
{
// 着色器源代码编译失败则获取原因并打印
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// 片段着色器的创建、绑定源代码、编译
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
// 检查片段色器编译是否成功
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)
{
// 着色器源代码编译失败则获取原因并打印
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// 创建着色器程序、添加着色器后链接着色器
GLuint shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
// 检查链接是否成功
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
// 链接失败则获取原因并打印
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
// 链接着色器后就删除着色器,节省显存
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// 定义顶点属性数据(格式任意自定义)
GLfloat vertices[] = {
-0.5f, -0.5f, 0.0f, // Left
0.5f, -0.5f, 0.0f, // Right
0.0f, 0.5f, 0.0f // Top
};
// 创建VAO、VBO
GLuint VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
// 绑定VAO
glBindVertexArray(VAO);
// 绑定VBO
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 将顶点数据vertices复制到GL_ARRAY_BUFFER绑定的对象VBO中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 设置解析顶点数据的方式到绑定VertexArray的VAO中
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
// 启用顶点属性0号位置
glEnableVertexAttribArray(0);
// 解绑VBO(因为暂时不需要再向这个VBO传入数据了)
glBindBuffer(GL_ARRAY_BUFFER, 0);
// 解绑VAO(因为暂时不需要再配置这个VAO的解析方式了)
// 要用的时候再绑定需要的VAO
glBindVertexArray(0);
// 渲染循环
while (!glfwWindowShouldClose(window))
{
// 检查窗口事件
glfwPollEvents();
// 清空屏幕缓存
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 使用着色器程序
glUseProgram(shaderProgram);
// 需要使用VAO解析顶点数据
glBindVertexArray(VAO);
// 根据传入VBO显存中的顶点数据和VAO解析方式渲染
glDrawArrays(GL_TRIANGLES, 0, 3);
// 使用完解除VAO绑定
glBindVertexArray(0);
// 交换缓冲区
glfwSwapBuffers(window);
}
// 删除VAO、VBO释放显存
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
// 释放glfw申请的内存
glfwTerminate();
return 0;
}