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)変数 vUvvec2 型で宣言し、パイプラインの次のステージ(此の場合はフラグメントシェーダー)に渡されます。

void main() {
  vUv = aUv;
  gl_Position = vec4(aPosition, 0.0, 1.0);
}

此れはメイン関数であり、頂点シェーダーのエントリーポイントで、各頂点ごとに実行されます。vUv = aUv: は入力テクスチャ座標 aUv を出力変数 vUv にコピーします。此れにより、テクスチャ座標が補間され、フラグメントシェーダーでテクスチャリングに使用されます。

glPosition = vec4(aPosition, 0.0, 1.0); はビルトイン変数 glPosition を設定し、クリップ空間での頂点の最終位置を決定します。aPositionvec2 なので、X及びY値をカバーし、従って z = 0.0w = 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 関数を使用して uColorAuColorB の間を線形補間します。補間係数は 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 に割り当てられ、フラグメントの最終カラーとなります。

シェーダー1

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);
}

全て正しく行えば、結果は次の様になります:

シェーダー2

次に進む前に、vUv.xvUv.y、及び其れらに対する計算を試して、何が起こるかを理解して下さい。

3.3 シェーダーにカラー値を移動

Cで色を定義する唯一の問題は、テストす度ににコードベースを再コンパイルする必要がある事です。然し、uColorAuColorB、及び 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 スクリプトが既にバイナリをプロジェクトルートに出力している為、何もする必要はありません。

uColorAvec3(1.0, 0.0, 1.0)uColorBvec3(1.0, 1.0, 0.0)uColorCvec3(0.0, 1.0, 1.0) に設定しましょう。コンパイルせずにGLSL シェーダーテスターを再起動すると、次の様になります:

シェーダー3

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.cmath.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); // 青

そして、値を使って試してみる宿題を出します。其々が何をするのか理解して下さい。

最終的な結果は次の様になります:

シェーダー4

最終的な変更: 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// ファイルパスを構築(プラットフォームに応じて)
13char *constructFilePath(const char *basePath) { 13char *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
6uniform vec3 uColorA; 6uniform vec3 uColorA;
7uniform vec3 uColorB; 7uniform vec3 uColorB;
8 8uniform vec3 uColorC;
9 9
10void main() { 10void 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}