3. グラデーション
既にあるコードを見てみましょう。
3.1 シェーダーの分解
vertex.glsl:
#version 450 core
layout(location = 0) in vec2 aPosition;
layout(location = 1) in vec2 aUv;
out vec2 vUv;
void main() {
vUv = aUv;
gl_Position = vec4(aPosition, 0.0, 1.0);
}
分解してみると、先ず最初に #version 450 core
があります。シェーダーがGLSLバージョン4.50のコアプロファイルを使用する事を指定しています。非推奨の機能は使用せず、コンパイルされた成果物を軽量に保ちます。
layout(location = 0) in vec2 aPosition;
layout(location = 1) in vec2 aUv;
此れらの行は、Cコードで提供する頂点シェーダーの入力属性を定義します:
// 頂点属性:位置(location 0)、UV(location 1)
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)0);
glEnableVertexAttribArray(0);
size_t offset = 2 * sizeof(float);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)offset);
glEnableVertexAttribArray(1);
layout(location = 0)
及び layout(location = 1)
の部分は、頂点属性のバインディング位置を明示的に割り当てます。2次元ベクトル(vec2
)は、aPosition
に位置 0 で割り当てられ、X及びY座標での頂点の位置を表します。位置 1 では、テクスチャ座標(UおよびV)をジオメトリにマッピングする為のもう一つの3次元ベクトル aUv
を割り当てます。最後に、in
キーワードは、此れらが頂点バッファからシェーダーに渡される入力変数である事を示します。
out vec2 vUv;
此れは出力(out
)変数 vUv
を vec2
型で宣言し、パイプラインの次のステージ(此の場合はフラグメントシェーダー)に渡されます。
void main() {
vUv = aUv;
gl_Position = vec4(aPosition, 0.0, 1.0);
}
此れはメイン関数であり、頂点シェーダーのエントリーポイントで、各頂点ごとに実行されます。vUv = aUv:
は入力テクスチャ座標 aUv
を出力変数 vUv
にコピーします。此れにより、テクスチャ座標が補間され、フラグメントシェーダーでテクスチャリングに使用されます。
glPosition = vec4(aPosition, 0.0, 1.0);
はビルトイン変数 glPosition
を設定し、クリップ空間での頂点の最終位置を決定します。aPosition
は vec2
なので、X及びY値をカバーし、従って z = 0.0
と w = 1.0
を設定します。
fragment.glsl:
#version 450 core
in vec2 vUv;
out vec4 out_color;
uniform vec3 uColorA;
uniform vec3 uColorB;
void main() {
vec3 blendedColor = mix(uColorA, uColorB, vUv.x);
out_color = vec4(blendedColor, 1.0);
}
次に、フラグメントシェーダーについて説明します。前述の通り、頂点シェーダーはフラグメントシェーダーにデータを渡します。従って、今回は in vec2 vUv;
が頂点シェーダーから入力されます。又、out vec4 out_color;
があり、此れは4次元ベクトル(vec4
)型の出力変数で、最終的なカラー(RGBA)を保持します。此の値はフレームバッファに書き込まれます。
uniform vec3 uColorA;
uniform vec3 uColorB;
此れら2つはユニフォーム変数で、其々3次元ベクトル(vec3
)型であり、RGBカラーを表します。ユニフォームはCコードで設定されるグローバル変数で、単一の描画呼び出しに置いて全ての頂点/フラグメントで一定に保たれます。
void main() {
vec3 blendedColor = mix(uColorA, uColorB, vUv.x);
out_color = vec4(blendedColor, 1.0);
}
此のメイン関数は、レンダリングされるプリミティブの各フラグメント(ピクセル)に対して実行されます。vec3 blendedColor = mix(uColorA, uColorB, vUv.x);
は mix
関数を使用して uColorA
と uColorB
の間を線形補間します。補間係数は vUv.x
で、UV座標のx成分(プリミティブ全体で0から1の範囲)です。
mix(a, b, t)
は以下を計算します:
a * (1 - t) + v * t
vUv.x = 0
の場合、blendedColor = uColorA
になります。vUv.x = 1
の場合、blendedColor = uColorB
になります。其の間の値では、2つの色を比例してブレンドします(つまり、中央(vUv.x = 0.5
)ではブレンドが50/50になります)。此れにより、uColorA
から uColorB
への滑らかなグラデーションが作成されます。
out_color = vec4(blendedColor, 1.0);
はブレンドされたRGBカラー(vec3
)をアルファ値 1.0
(完全に不透明)を追加して vec4
に変換します。此れは out_color
に割り当てられ、フラグメントの最終カラーとなります。
3.2 もう一つの色を追加
3番目の色:青を追加しましょう。
先ず、main.c
に以下を追加します:
glUniform3f(glGetUniformLocation(shaderProgram, "uColorC"), 0.0f, 0.0f, 1.0f); // 青
此れにより、フラグメントシェーダーで使用できる新しい変数が追加されます。
fragment.glsl
では、以下の宣言を追加します:
uniform vec3 uColorC;
次に、メイン関数内で vec3
を宣言し、blendedColor
の値をそこに移動させ、uColorC
とミックスします。そして、Y軸で補間を行いますが、青が支配的になり過ぎない様に少し抑えます。
void main() {
vec3 temp = mix(uColorA, uColorB, vUv.x);
vec3 blendedColor = mix(temp, uColorC, vUv.y * 0.95);
out_color = vec4(blendedColor, 1.0);
}
全て正しく行えば、結果は次の様になります:
次に進む前に、vUv.x
、vUv.y
、及び其れらに対する計算を試して、何が起こるかを理解して下さい。
3.3 シェーダーにカラー値を移動
Cで色を定義する唯一の問題は、テストす度ににコードベースを再コンパイルする必要がある事です。然し、uColorA
、uColorB
、及び uColorC
をフラグメントシェーダーに移動させると、コンパイルせずに直ぐにテスト出来できます。
そこで、main.c
では:
// ユニフォームを設定
//glUniform3f(glGetUniformLocation(shaderProgram, "uColorA"), 1.0f, 0.0f, 0.0f); // 赤
//glUniform3f(glGetUniformLocation(shaderProgram, "uColorB"), 0.0f, 1.0f, 0.0f); // 緑
//glUniform3f(glGetUniformLocation(shaderProgram, "uColorC"), 0.0f, 0.0f, 1.0f); // 青
そして、fragment.glsl
では:
#version 450 core
in vec2 vUv;
out vec4 out_color;
//uniform vec3 uColorA;
//uniform vec3 uColorB;
//uniform vec3 uColorC;
void main() {
vec3 uColorA = vec3(1.0, 0.0, 1.0); // 赤
vec3 uColorB = vec3(1.0, 1.0, 0.0); // 緑
vec3 uColorC = vec3(0.0, 1.0, 1.0); // 青
vec3 temp = mix(uColorA, uColorB, vUv.x);
vec3 blendedColor = mix(temp, uColorC, vUv.y * 0.95);
out_color = vec4(blendedColor, 1.0);
}
此れで、色を変更する度にウィンドウを閉じて再度開くだけで変更が確認出来ます。コンパイルは不要です!
確認の為に、1つの色を別の物に変更して下さい。Windowsの場合、x64\Debug
に移動し、glsltester.exe
をプロジェクトルートにコピーして下さい。差もないと動作しません。macOSの場合、~/Library/Developer/Xcode/DerivedData/glsltester-(貴方のID)/Build/Products/Debug
に移動し、glsltester
をプロジェクトルートにコピーして下さい。Unixユーザーの場合、build-unix.sh
スクリプトが既にバイナリをプロジェクトルートに出力している為、何もする必要はありません。
uColorA
を vec3(1.0, 0.0, 1.0)
、uColorB
を vec3(1.0, 1.0, 0.0)
、uColorC
を vec3(0.0, 1.0, 1.0)
に設定しましょう。コンパイルせずにGLSL シェーダーテスターを再起動すると、次の様になります:
3.4 カラーアニメーション
最後に、もっと面白い事をします。此れまで静的な色でしたが、回転させてより面白くしてみましょう。
然し、問題があります。変数をフラグメントシェーダーに移動した為、アニメーションが出来なくなりました。其処で、行った変更を元に戻します。
fragment.glsl
では:
#version 450 core
in vec2 vUv;
out vec4 out_color;
uniform vec3 uColorA;
uniform vec3 uColorB;
uniform vec3 uColorC;
void main() {
vec3 temp = mix(uColorA, uColorB, vUv.x);
vec3 blendedColor = mix(temp, uColorC, vUv.y * 0.95);
out_color = vec4(blendedColor, 1.0);
}
そして、main.c
では、3行のコメントを解除します:
// ユニフォームを設定
glUniform3f(glGetUniformLocation(shaderProgram, "uColorA"), 1.0f, 0.0f, 0.0f); // 赤
glUniform3f(glGetUniformLocation(shaderProgram, "uColorB"), 1.0f, 0.0f, 0.0f); // 緑
glUniform3f(glGetUniformLocation(shaderProgram, "uColorC"), 0.0f, 0.0f, 1.0f); // 青
次に、main.c
に math.h
及び vector.h
ヘッダーを追加し、whileループを以下の様に修正します:
#include <vector.h>
#include <math.h>
...
// 時間に基づいて色をアニメーション
float time = (float)glfwGetTime();
float speed = 1.0f; // アニメーションの速度
float phase = 2.0f * 3.14159f / 3.0f; // // 120度の位相差(3色用)
// uColorA: 赤 -> 緑 -> 青
Vector3 colorA;
colorA.r = sin(time * speed + 0.0f) * 0.5f + 0.5f; // 0.0 to 1.0
colorA.g = sin(time * speed + phase) * 0.5f + 0.5f;
colorA.b = sin(time * speed + 2.0f * phase) * 0.5f + 0.5f;
// uColorB: 緑 -> 青 -> 赤
Vector3 colorB;
colorB.r = sin(time * speed + 2.0f * phase) * 0.5f + 0.5f;
colorB.g = sin(time * speed + 0.0f) * 0.5f + 0.5f;
colorB.b = sin(time * speed + phase) * 0.5f + 0.5f;
// uColorC: 青 -> 赤 -> 緑
Vector3 colorC;
colorC.r = sin(time * speed + phase) * 0.5f + 0.5f;
colorC.g = sin(time * speed + 2.0f * phase) * 0.5f + 0.5f;
colorC.b = sin(time * speed + 0.0f) * 0.5f + 0.5f;
// ユニフォームを設定
glUniform3f(glGetUniformLocation(shaderProgram, "uColorA"), colorA.r, colorA.g, colorA.b); // 赤
glUniform3f(glGetUniformLocation(shaderProgram, "uColorB"), colorB.r, colorB.g, colorB.b); // 緑
glUniform3f(glGetUniformLocation(shaderProgram, "uColorC"), colorC.r, colorC.g, colorC.b); // 青
そして、値を使って試してみる宿題を出します。其々が何をするのか理解して下さい。
最終的な結果は次の様になります:
最終的な変更: DIFFをダウンロード
ファイル: a/main.c
前 | 新 | ||
---|---|---|---|
6 | #include <GLFW/glfw3.h> | 6 | #include <GLFW/glfw3.h> |
7 | #include <stdio.h> | 7 | #include <stdio.h> |
8 | #include <stdlib.h> | 8 | #include <stdlib.h> |
9 | 9 | #include <vector.h> | |
10 | 10 | #include <math.h> | |
11 | 11 | ||
12 | // ファイルパスを構築(プラットフォームに応じて) | 12 | // ファイルパスを構築(プラットフォームに応じて) |
13 | char *constructFilePath(const char *basePath) { | 13 | char *constructFilePath(const char *basePath) { |
222 | 224 | // シェーダープログラムを使用 | |
223 | 225 | glUseProgram(shaderProgram); | |
224 | // シェーダープログラムを使用 | 226 | |
225 | glUseProgram(shaderProgram); | 227 | // 時間に基づいて色をアニメーション |
226 | 228 | float time = (float)glfwGetTime(); | |
227 | 229 | float speed = 1.0f; // アニメーションの速度 | |
228 | 230 | float phase = 2.0f * 3.14159f / 3.0f; // // 120度の位相差(3色用) | |
229 | 231 | ||
230 | 232 | // uColorA: 赤 -> 緑 -> 青 | |
231 | 233 | Vector3 colorA; | |
232 | 234 | colorA.r = sin(time * speed + 0.0f) * 0.5f + 0.5f; // 0.0 to 1.0 | |
233 | 235 | colorA.g = sin(time * speed + phase) * 0.5f + 0.5f; | |
234 | 236 | colorA.b = sin(time * speed + 2.0f * phase) * 0.5f + 0.5f; | |
235 | 237 | ||
236 | 238 | // uColorB: 緑 -> 青 -> 赤 | |
237 | 239 | Vector3 colorB; | |
238 | 240 | colorB.r = sin(time * speed + 2.0f * phase) * 0.5f + 0.5f; | |
239 | 241 | colorB.g = sin(time * speed + 0.0f) * 0.5f + 0.5f; | |
240 | 242 | colorB.b = sin(time * speed + phase) * 0.5f + 0.5f; | |
241 | 243 | ||
242 | 244 | // uColorC: 青 -> 赤 -> 緑 | |
243 | 245 | Vector3 colorC; | |
244 | 246 | colorC.r = sin(time * speed + phase) * 0.5f + 0.5f; | |
245 | 247 | colorC.g = sin(time * speed + 2.0f * phase) * 0.5f + 0.5f; | |
246 | 248 | colorC.b = sin(time * speed + 0.0f) * 0.5f + 0.5f; | |
247 | 249 | ||
248 | 250 | // ユニフォームを設定 | |
249 | 251 | glUniform3f(glGetUniformLocation(shaderProgram, "uColorA"), colorA.r, colorA.g, colorA.b); // 赤 | |
250 | // ユニフォームを設定 | 252 | glUniform3f(glGetUniformLocation(shaderProgram, "uColorB"), colorB.r, colorB.g, colorB.b); // 緑 |
251 | glUniform3f(glGetUniformLocation(shaderProgram, "uColorA"), 1.0f, 0.0f, 0.0f); // 赤 | 253 | glUniform3f(glGetUniformLocation(shaderProgram, "uColorC"), colorC.r, colorC.g, colorC.b); // 青 |
252 | glUniform3f(glGetUniformLocation(shaderProgram, "uColorB"), 0.0f, 1.0f, 0.0f); // 緑 | 254 | |
253 | 255 | // クアッドを描画 | |
254 | 256 | glBindVertexArray(VAO); | |
255 | // クアッドを描画 | ||
256 | glBindVertexArray(VAO); |
ファイル: a/shader/fragment.glsl
前 | 新 | ||
---|---|---|---|
5 | 5 | ||
6 | uniform vec3 uColorA; | 6 | uniform vec3 uColorA; |
7 | uniform vec3 uColorB; | 7 | uniform vec3 uColorB; |
8 | 8 | uniform vec3 uColorC; | |
9 | 9 | ||
10 | void main() { | 10 | void main() { |
11 | vec3 blendedColor = mix(uColorA, uColorB, vUv.x); | 11 | vec3 temp = mix(uColorA, uColorB, vUv.x); |
12 | 12 | vec3 blendedColor = mix(temp, uColorC, vUv.y * 0.95); | |
13 | out_color = vec4(blendedColor, 1.0); | 13 | out_color = vec4(blendedColor, 1.0); |
14 | } | 14 | } |