前回のレッスンでは、オブジェクトをさまざまな色に着色する方法を学びました。 しかし、ある種のリアリズムを実現するには、たくさんの色が必要です。 前回は、三角形の頂点をペイントしましたが、同じ方法で行けば、画像を表示するには頂点が多すぎます。 興味を持ってください、猫の下で。
パート1.はじめに
パート2.基本的な照明
パート3. 3Dモデルをダウンロードする
パート4.高度なOpenGL機能
パート5.高度な照明
プログラマーやアーティストはテクスチャを使用することを好みます 。 テクスチャは、オブジェクトにディテールを追加するために使用される2D画像(1Dおよび3Dテクスチャも存在します)です。 テクスチャとは、家に貼り付けられたレンガの絵(たとえば)が貼られた紙で、家はレンガでできているように見えると考えてください。
テクスチャは、写真に加えて、シェーダーに送信された大量のデータを保存できますが、この質問は別のレッスンに残します。 以下では、前回のレッスンの三角形にくっついたレンガの壁のテクスチャを見ることができます。
テクスチャを三角形にスナップするには、三角形の各頂点に、この頂点がテクスチャのどの部分に属しているかを伝える必要があります。 したがって、各頂点には、テクスチャの一部に関連付けられたテクスチャ座標が必要です。
テクスチャ座標は、x軸とy軸に沿って0〜1です(2Dテクスチャを使用します)。 テクスチャ座標を使用してテクスチャの色を取得することをサンプリングと呼びます。 テクスチャ座標は、テクスチャの左下隅の(0、0)で始まり、右上隅の(1、1)で終わります。 以下の画像は、三角形にテクスチャ座標を適用する方法を示しています。
三角形に3つのテクスチャ座標を指定しました。 三角形の左下隅をテクスチャの左下隅に対応させるため、三角形の左下の頂点に(0、0)を渡します。 したがって、 (1、0)を右下の頂点に渡します。 三角形の上部の頂点は、テクスチャの上部の中央部分に対応する必要があるため、上部の頂点(0.5、1.0)をテクスチャ座標として渡します。
その結果、三角形のテクスチャ座標は次のようになります。
GLfloat texCoords[] = { 0.0f, 0.0f, // 1.0f, 0.0f, // 0.5f, 1.0f // };
テクスチャサンプリングは、さまざまな方法を使用して実行できます。 私たちの仕事は、OpenGLにサンプリング方法を伝えることです。
テクスチャラッピング
多くの場合、テクスチャ座標は(0,0)と(1,1)の間にありますが、テクスチャ座標がこの間隔を超えるとどうなりますか? OpenGLのデフォルトの動作は画像を繰り返すことです(実際、浮動小数点数の全体は単に無視されます)が、他のオプションもあります:
- GL_REPEAT :テクスチャの標準的な動作。 テクスチャを復活させます。
- GL_MIRRORED_REPEAT :_GL REPEATに似ていますが、このモードでは画像が反映されます。
- GL_CLAMP_TP_EDGE : 0から1の間の座標をバインドします。 その結果、境界外の座標はテクスチャの境界にバインドされます。
- GL_CLAMP_TO_BORDER :範囲外の座標は、ユーザー定義の境界線色を提供します。
スパンを超えるテクスチャ座標を使用する場合、これらのオプションはそれぞれ異なって表示されます。 以下の画像は、違いを完全に示しています。
上記の各オプションは、** glTextParameter ***関数を使用して、軸上で設定できます( s 、 t (および3Dテクスチャを使用する場合はr )、 x 、 y 、およびzと同等です):
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
最初の引数は、テクスチャがアタッチされる目的を決定します。2Dテクスチャを使用するため、値はGL_TEXTURE_2Dになります。 2番目の値は、設定する特定のパラメーターをOpenGLに伝えるために必要です。 WRAPオプションを設定し、 S軸とT軸の値を指定します。 最後の引数は、選択されたラッピング方法を渡します。 この場合、 GL_MIRRORED_REPEATを使用します 。
GL_CLAMP_TO_BORDERを選択した場合、まだ境界線の色を指定する必要があります。 これはglTextParameterの代替としてfvによって行われ、オプションとしてGL_TEXTURE_BORDER_COLORを渡し、色の値として浮動小数点数の配列を渡します。
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f }; glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
テクスチャフィルタリング
テクスチャ座標は解像度に依存しませんが、任意の浮動小数点値を取ることができるため、OpenGLはテクスチャのどのピクセル( テクセルとも呼ばれる)を課す必要があるかを理解する必要があります。 低解像度のテクスチャを大きなオブジェクトに適用する場合、この問題はより深刻になります。 OpenGLにはテクスチャをフィルタリングするオプションがあることを既に推測しているかもしれません。 使用可能なオプションはいくつかありますが、最も重要なのはGL_NEARESTとGL_LINEARのみです。
GL_NEAREST ( 最近傍フィルターとも呼ばれます )は、OpenGLの標準的なフィルター方法です。 OpenGLをインストールすると、テクスチャ座標に最も近いピクセルが選択されます。 下には、4ピクセルとテクスチャ座標を示す十字が表示されています。 左上のテクセルの中心はテクスチャ座標に最も近いため、サンプルの色として選択されます。
GL_LINEAR ( (bi)線形フィルタリングとも呼ばれます )。 テクスチャ座標に最も近いテクセルから補間された値を取ります。 テクセルがテクスチャ座標に近いほど、このテクセルの色係数は大きくなります。
以下に、隣接するピクセルの色を混合する例を示します。
しかし、選択したフィルター効果の視覚効果は何ですか? これらのメソッドが大きなオブジェクトの小さな解像度のテクスチャでどのように機能するかを見てみましょう(テクスチャは個々のテクセルが見えるように増加されています):
ミップマップ
たくさんのオブジェクトがあり、それぞれにテクスチャがアタッチされた大きな部屋があると想像してください。 一部のオブジェクトは観察者に近く、一部のオブジェクトは観察者から遠く、高解像度テクスチャが各オブジェクトにアタッチされています。 オブジェクトがオブザーバーから遠く離れている場合、処理する必要があるフラグメントはわずかです。 OpenGLでは、多数のテクスチャピクセルを考慮する必要がある場合、高解像度テクスチャからフラグメントに適切な色を取得するのが困難です。 この動作により、小さなオブジェクトでアーティファクトが生成されます。もちろん、小さなオブジェクトでの高解像度テクスチャの使用に関連するメモリの過剰な浪費も発生します。
この問題を解決するために、OpenGLはミップマップと呼ばれる技術を使用します。これは、後続の各テクスチャが過去の半分のサイズであるテクスチャイメージのセットを提供します。 ミップマップの根底にある考え方は非常に単純です。オブザーバーから一定の距離を置いた後、OpenGLは現在の距離でより良く見える別のミップマップテクスチャを使用します。 オブジェクトがオブザーバーから離れるほど、テクスチャーの解像度の違いに気づきにくくなるため、使用されるテクスチャーは少なくなります。また、ミップマップにはパフォーマンスを向上させる優れた機能があり、これは決して不要ではありません。 ミップマップの例を詳しく見てみましょう。
各画像のミップマップテクスチャのセットを作成するのはかなり面倒ですが、幸いなことに、OpenGLはテクスチャの作成後にglGenerateMipmapsを呼び出すことでそれらを生成できます。 すぐに例が表示されます。
レンダリングプロセス中にミップマップレベルを切り替えると、OpenGLは2つのレベル間の鋭いエッジなどのアーティファクトを表示する場合があります。 テクスチャでフィルタリングを使用できるように、 NEARESTおよびLINEARフィルタリングを使用してレベルを切り替えるミップマップのさまざまなレベルでフィルタリングを使用することもできます。 ミップマップのレベル間のフィルタリング方法を示すために、標準の方法を次の4つの設定のいずれかに置き換えることができます。
- _GL_NEARESET_MIPMAP NEAREST:ピクセルサイズに一致する最も近いミップマップを選択し、最近傍補間を使用してテクスチャをサンプリングします。
- _GL_LINEAR_MIPMAP NEAREST:最も近いミップマップを選択し、線形補間を使用してサンプリングします。
- _GL_NEAREST_MIPMAP LINEAR :2つの最も近いミップマップ間の線形補間と線形補間を使用したテクスチャのサンプリング。
テクスチャフィルタリングと同様に、 glTexParameteri関数を使用してフィルタリング方法を設定できます。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
よくある間違いは、ミップマップのフィルタリング方法を増加フィルターとして設定することです。 ミップマップは主にテクスチャの削減に使用されるため、これは効果を与えません。 テクスチャを増やすとミップマップが使用されないため、フィルタオプションを渡すと、ミップマップはGL_INVALID_ENUMエラーを生成します。
テクスチャの読み込みと作成
テクスチャの使用を開始する前に、アプリケーションにテクスチャをロードする必要があります。 テクスチャ画像は無制限の数の形式で保存できます。各形式には独自の構造とデータの順序があります。どのように画像をアプリケーションに転送しますか? 1つの解決策は、たとえば.PNGなどの便利な形式を使用して、大きなバイト配列にイメージをロードする独自のシステムを作成することです。 独自のイメージダウンローダーを作成するのは圧倒的な仕事ではありませんが、特に多くのファイル形式を使用する場合は、それでもかなり面倒です。
もう1つの解決策は、既製のライブラリを使用して画像をアップロードすることです。これは、多くのさまざまな一般的な形式をサポートしており、私たちにとっては大変な作業です。 たとえば、 SOIL 。
土壌
SOILはSimple OpenGL Image Libraryの略で、最も一般的な画像形式をサポートし、使いやすく、こちらからダウンロードできます 。 他のほとんどのライブラリと同様に、自分で.libファイルを生成する必要があります。 / projectsフォルダーにあるプロジェクトの1つを使用して(プロジェクトのバージョンがVSのバージョンより低いかどうか心配する必要はありません。それらを新しいバージョンに変換するだけで、ほとんどの場合これで機能します)、それに基づいて独自のプロジェクトを作成します。 また、 srcフォルダーの内容をincludeフォルダーに追加します。 また、 SOIL.libをリンカー設定に追加し、コードの先頭に#include <SOIL.h>を追加することを忘れないでください。
現在のテクスチャセクションでは、 木製コンテナの画像を使用します。 SOILを介して画像をアップロードするには、 SOIL_load_image関数を使用します。
int width, height; unsigned char* image = SOIL_load_image("container.jpg", &width, &height, 0, SOIL_LOAD_RGB);
関数の最初の引数は、画像ファイルの場所です。 2番目と3番目の引数は、イメージサイズが配置されるintへのポインターです:幅と高さ。 テクスチャを生成するために必要になります。 4番目の引数は画像チャネルの数ですが、0のままにします。最後の引数は、SOILに画像のロード方法を指示します。RGB画像情報のみが必要です。 結果は、バイトの大きな配列に格納されます。
テクスチャ生成
OpenGLの他のオブジェクトと同様に、識別子はテクスチャを参照します。 作成しましょう:
GLuint texture; glGenTextures(1, &texture);
glGenTextures関数は、生成するテクスチャの数を最初の引数として、これらのテクスチャの識別子が格納されるGLuint配列(この場合、これは1つのGLuint )を2番目の引数として受け取ります。 他のオブジェクトと同様に、テクスチャを使用する関数が使用するテクスチャを認識できるように、オブジェクトをアタッチします。
glBindTexture(GL_TEXTURE_2D, texture);
テクスチャをスナップした後、事前に読み込まれた画像を使用してテクスチャデータの生成を開始できます。 テクスチャはglTexImage2Dを使用して生成されます :
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image); glGenerateMipmap(GL_TEXTURE_2D);
この関数にはかなりの数の引数があるため、順番に見てみましょう。
- 最初の引数は、テクスチャターゲットを説明します。 GL_TEXTURE_2Dの値を設定することにより、テクスチャがこのターゲットに関連付けられていることを関数に通知しました(他のターゲットGL_TEXTURE_1DおよびGL_TEXTURE_3Dが関与しないようにするため)。
- 2番目の引数は、突然自分でミップマップを生成したい場合に、テクスチャを生成するミップマップのレベルを示します。 OpenGLでミップマップ生成を残すため、0を渡します。
- 3番目の引数は、OpenGLにテクスチャを保存する形式を指示します。 画像にはRGB値のみがあるため、テクスチャにはRGB値のみを保存します。
- 4番目と5番目の引数は、結果のテクスチャの幅と高さを指定します。 これらの値は、画像の読み込み中に早く取得しました。
- 6番目の引数は常に0である必要があります(引数は古くなっています)。
- 7番目と8番目の引数は、元の画像の形式とデータ型を記述します。 RGB値を読み込んでバイト(char)で保存したので、これらの値を渡します。
- 最後の引数は画像データそのものです。
glTexImage2Dを呼び出した後、現在バインドされているテクスチャに画像がバインドされます。 確かに、テクスチャには基本的なイメージしかありません。ミップマップを使用する場合は、ミップマップのレベルを単純に増やすだけで、同じ方法でイメージを設定する必要があります。 さて、または、テクスチャを生成した後にglGenerateMipmapを呼び出すことができます。 この関数は、現在バインドされているテクスチャに必要なすべてのミップマップを自動的に生成します。
テクスチャとミップマップの生成が終了したら、ロードされたイメージに割り当てられたメモリを解放し、テクスチャオブジェクトを解放することをお勧めします。
SOIL_free_image_data(image); glBindTexture(GL_TEXTURE_2D, 0);
テクスチャ生成プロセス全体は次のようになります。
GLuint texture; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); // ( ) ... // int width, height; unsigned char* image = SOIL_load_image("container.jpg", &width, &height, 0, SOIL_LOAD_RGB); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image); glGenerateMipmap(GL_TEXTURE_2D); SOIL_free_image_data(image); glBindTexture(GL_TEXTURE_2D, 0);
テクスチャを適用する
以降の章では、 Hello Triangleチュートリアルの最後の部分からglDrawElementsで描かれた四辺形を使用します。 テクスチャのサンプリング方法をOpenGLに指示する必要があるため、テクスチャ座標を追加して頂点データを更新します。
GLfloat vertices[] = { // // // 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // };
追加の属性を追加した後、OpenGLに新しい形式を再度通知する必要があります。
。
glVertexAttribPointer(2, 2, GL_FLOAT,GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat))); glEnableVertexAttribArray(2);
8 * sizeof(GLfloat)の下の最後の2つの属性のステップ値も調整したことに注意してください。
次に、頂点シェーダーを変更して、テクスチャー座標を属性として取得し、フラグメントシェーダーに渡すようにする必要があります。
#version 330 core layout (location = 0) in vec3 position; layout (location = 1) in vec3 color; layout (location = 2) in vec2 texCoord; out vec3 ourColor; out vec2 TexCoord; void main() { gl_Position = vec4(position, 1.0f); ourColor = color; TexCoord = texCoord; }
フラグメントシェーダーは、 TexCoordを入力変数として受け入れる必要もあります。
フラグメントシェーダーもテクスチャオブジェクトにアクセスできる必要がありますが、フラグメントシェーダーに渡す方法は? GLSLには、 samplerと呼ばれるテクスチャオブジェクトの組み込みデータ型があります。これは、末尾にテクスチャ型、つまり、 sampler1D、sampler3D、この場合はsampler2Dがあります。 後でテクスチャを渡す均一なsmpler2Dを宣言するだけで、フラグメントシェーダにテクスチャを追加できます。
#version 330 core in vec3 ourColor; in vec2 TexCoord; out vec4 color; uniform sampler2D ourTexture; void main() { color = texture(ourTexture, TexCoord); }
テクスチャカラーをサンプリングするには、GLSLの組み込みテクスチャ関数を使用します。この関数は、テクスチャサンプラを最初の引数として、テクスチャ座標を2番目の引数として使用します。 次に、 テクスチャ関数は、以前に設定したテクスチャパラメータを使用して色の値をサンプリングします。 このフラグメントシェーダーの結果は、(補間された)テクスチャ座標で(フィルター処理された)テクスチャカラーになります。
glDrawElementsを呼び出す前にテクスチャをバインドするためだけに残り、自動的にフラグメントシェーダーサンプラーに渡されます。
glBindTexture(GL_TEXTURE_2D, texture); glBindVertexArray(VAO); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); glBindVertexArray(0);
すべてを正しく行った場合、次の画像が表示されます。
四角形が完全に黒または白の場合、どこかで間違えています。 シェーダーログを確認し、コードをソースと比較します。
よりカラフルな効果を得るために、結果のテクスチャカラーと頂点カラーを混ぜることができます。 ブレンドするには、フラグメントシェーダーの色を単純に乗算します。
Color = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0f);
このようなものを取得する必要がありますか?
テクスチャブロック
あなたは不思議に思うかもしれません:「 glUniformを使用して値を割り当てなかったのに、なぜsampler2Dは変数均一なのですか?」。 glUniform1iを使用すると、1つのフラグメントシェーダーで複数のテクスチャを使用できるように、位置値をテクスチャサンプラーに割り当てることができます。 テクスチャの場所は、多くの場合、テクスチャブロックと呼ばれます。 デフォルトのテクスチャユニットは0です。これは、現在のアクティブなテクスチャユニットを意味するため、前のセクションで場所を指定する必要はありません。
テクスチャユニットの主な目標は、シェーダーで複数のテクスチャを使用する機能を提供することです。 テクスチャブロックをサンプラーに渡すことにより、一致するテクスチャブロックをアクティブにするまで、一度に複数のテクスチャをスナップできます。 glBindTextureと同様に、使用するテクスチャブロックを渡すglActivateTextureでテクスチャをアクティブ化できます。
glActiveTexture(GL_TEXTURE0); // glBindTexture(GL_TEXTURE_2D, texture);
テクスチャブロックをアクティブにした後、その後glBindTextureを呼び出すと 、このテクスチャがアクティブなテクスチャブロックにバインドされます。 GL_TEXTURE0ブロックはデフォルトで常にアクティブになっているため、前の例でテクスチャブロックをアクティブにする必要はありませんでした。
OpenGLは少なくとも16のテクスチャユニットをサポートします。これはGL_TEXTURE0 - GL_TEXTURE15で取得できます。 これらは順番に宣言されているため、次のようにして取得することもできます: GL_TEXTURE8 = GL_TEXTURE0 + 8 。 これは、テクスチャブロックを反復処理する必要がある場合に便利です。
いずれにしても、フラグメントシェーダーを変更して別のサンプラーを受け入れる必要があります。
#version 330 core ... uniform sampler2D ourTexture1; uniform sampler2D ourTexture2; void main() { color = mix(texture(ourTexture1, TexCoord), texture(ourTexture2, TexCoord), 0.2); }
最終結果は、2つのテクスチャの組み合わせです。 GLSLには、2つの入力値を取り、3番目の値に基づいてそれらを補間する組み込みのミックス関数があります。 3番目の値が0.0の場合、 1.0が 2番目の場合、この関数は最初の引数を返します。 0.2の値は、最初の入力色の80%と2番目の入力色の20%を返します。
次に、別のテクスチャをロードして作成する必要があります。 次の手順を既に理解しています。 必ず別のテクスチャオブジェクトを作成し、イメージをロードして、 glTexImage2Dを使用して最終的なテクスチャを生成してください 。 2番目のテクスチャでは、これらのレッスンを学習する際に顔画像を使用します。
2番目のテクスチャ(および最初のテクスチャ)を使用するには、両方のテクスチャを対応するテクスチャブロックにリンクし、どのサンプラーがどのテクスチャブロックを参照するかを示すことにより、レンダリング手順をわずかに変更する必要があります。
glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture1); glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture1"), 0); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, texture2); glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture2"), 1); glBindVertexArray(VAO); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); glBindVertexArray(0);
glUniform1iを使用して、均一サンプラーのテクスチャブロックの位置を設定したことに注意してください。 glUniform1iを介してそれらをインストールすることにより、均一なサンプラーが正しいテクスチャブロックに対応するようになります。 その結果、次の結果が得られます。
, ! , OpenGL 0.0 Y , 0.0 Y. , Devil Y . SOIL . SOIL SOIL_load_OGL_texture , SOIL_FLAG_INVERT_Y , . , OpenGL, SOIL_load_image .
2 :
- Y ( Y 1)
- Y , TexCoord TexCoord = vec2(texCoord.x, 1.0f — texCoord.y);. 。
— , . , , — , , OpenGL.
Y :
演習
.