本稿は「Basic Gradient Shader based on pixel coordinates」ページを翻訳したものです。
目次
ピクセル座標にもとづいた基本的なグラデーションシェーダー
次のコードは、キャンバスのピクセル位置にもとづいたグラデーションを作成します。ここまで学んだ知識を使って、実験してみましょう。
使うのは次の知識です。
.jsファイル
// このサンプルでは、p5スケッチからシェーダーに値を送る
// この値は'uniform'変数と呼ばれる
// この作業は、p5.ShaderのsetUniform()メソッドで行う
// https://p5js.org/reference/#/p5.Shader/setUniform
// シェーダー変数
let theShader;
function preload() {
// シェーダーをロードする
theShader = loadShader('uniform.vert', 'uniform.frag');
}
function setup() {
// シェーダーを動作させるにははWEBGLモードが必要
createCanvas(windowWidth, windowHeight, WEBGL);
noStroke();
}
function draw() {
// アクティブなシェーダーをtheShaderに設定する
shader(theShader);
// ここでsetUniform()を使ってuniform変数をシェーダーに送っている
// setUiform()は賢くて、送信している変数の種類を理解するので、
// (processingと異なり)キャストする必要がない
theShader.setUniform("u_resolution", [width, height]);
// この2つは実際には使用していない
theShader.setUniform("u_time", millis() / 1000.0);
theShader.setUniform("u_mouse", [mouseX, map(mouseY, 0, height, height, 0)]);
// rect()で画面にジオメトリを与える
rect(500, 500, 10000, 10000);
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}
.vertファイル
頂点シェーダーファイルはこれまでと同じです。
// グラフィックカードにシェーダーのレンダリング方法を知らせる定義が必要
#ifdef GL_ES
precision mediump float;
#endif
// ピクセルの位置を取得し、シェーダーをシェイプに正しくマッピングするために必ず含める
attribute vec3 aPosition;
void main() {
// w成分として1.0を追加して、位置データをvec4にコピーする
vec4 positionVec4 = vec4(aPosition, 1.0);
// 出力がキャンバスにフィットするようスケーリングする
positionVec4.xy = positionVec4.xy * 2.0 - 1.0;
// 頂点情報を、フラグメントシェーダに送る
gl_Position = positionVec4;
}
.fragファイル
キャンバスのサイズがvec2 u_resolutionというuniformとして渡されます。これはsketch.jsファイルで設定したものです。
theShader.setUniform(“u_resolution”, [width, height]);
uniformにはキャンバスの幅と高さが含まれています。 u_resolution.xには幅が、u_resolution.yには高さが含まれています。
uniform vec2 u_resolution;
main()関数では、gl_FragCoord.xy(フラグメント座標)からキャンバス上の各ピクセルの位置を取得します。gl_FragCoordは、頂点シェーダーファイルのgl_Positionで与えられた情報を自動的に使用します。
ピクセル(gl_FragCoord.xy)のx,y位置をキャンバスの幅と高さ(u_resolution.xy) で割ることで、0.0-1.0の範囲に正規化された座標が得られます。したがってst.xはx軸で0から1まで変化し、st.yはy軸で0から1に変化します。
変数をstにしているのは、シェーダーを記述するときの標準的な慣習からです。
シェーダーでピクセルの位置を使用する場合には、シェーダーをこのようにして開始するよう習慣づけるべきです。
vec2 st = gl_FragCoord.xy/u_resolution.xy;
コードはつねに、gl_FragColorにピクセルのカラーを設定して終わります。これを記述しないとコードは実行されません。st.xを入力に使用すると、x軸での黒から赤へのグラデーションが作成できます。
// R = x軸でのピクセル位置で決まる(0から1), G = 0, B = 0, A = 1
gl_FragColor = vec4(st.x,0.0,0.0,1.0);
また、x軸でのピクセル位置に応じて緑のグラデーションを作ることもできますし、st.xとst.y両方を使用してピクセルの色を決めることもできます。ただしgl_FragColorは1度に1つしかアクティブにできないので、設定するgl_FragColorは1つだけにします。
// 1度にアクティブにできるgl_FragColorは1つだが、コメントアウトして試してみよう。
// 緑チャンネル
gl_FragColor = vec4(0.0,st.x,0.0,1.0);
// x位置とy位置両方
gl_FragColor = vec4(st.x,st.y,0.0,1.0);
.fragファイル全体は次のようになります。
// グラフィックカードにシェーダーのレンダリング方法を知らせる定義が必要
#ifdef GL_ES
precision mediump float;
#endif
// このサンプルでは、対象ピクセルがキャンバスのどこにあるかが知りたいので、キャンバスのサイズが必要になる
// これは、sketch.jsファイルからuniformとして送られてくる
uniform vec2 u_resolution;
void main() {
// ピクセルの位置を解像度(キャンバスのサイズ)で割って、キャンバス上での正規化された位置を得る
vec2 st = gl_FragCoord.xy/u_resolution.xy;
// 赤のグラデーションとして、x軸のピクセル位置を使う。
// 位置が0.0に近いほど、黒くなる(st.x = 0.0)
// 位置が幅(1.0として定義)に近いほど、赤くなる(st.x = 1.0)
gl_FragColor = vec4(st.x,0.0,0.0,1.0); // R,G,B,A
// 1度にアクティブにできるgl_FragColorは1つだが、コメントアウトして試してみよう。
// 緑チャンネル
//gl_FragColor = vec4(0.0,st.x,0.0,1.0);
// x位置とy位置両方
//gl_FragColor = vec4(st.x,st.y,0.0,1.0);
}
以下は訳者による記述です。
図を使って整理してみる
シェーダーの世界は不慣れで難しいので、ここで整理しておきましょう。
sketch.jsでは、シェイプ(ジオメトリ)が描かれます。この頂点のデータは自動的に頂点シェーダーに送られます(attribute変数のaPosition)。頂点シェーダーではこれを加工してgl_Positionに割り当てます。すると頂点の情報が自動的にフラグメントシェーダーに送られます。
sketch.jsではまた、作成したシェーダーのsetUniform()メソッドを使って、フラグメントシェーダーにキャンバスの幅と高さ(上図では400と300)を送っています(u_resolution)。フラグメントシェーダーでは、GLSLで計算できるよう、ピクセルのキャンバス上での位置を表すgl_FragCoord.xyを、送られてきたu_resolutionのxyで割って、ピクセルの位置が0-1の間におさまるようにしています(正規化)。
具体的にキャンバスが400 x 300の場合で言うと、左上隅(0,0)にあるピクセルは(0,0)、右上隅の(400,0)は(1,0)、左下隅の(0,300)は(0,1)、右下隅の(400,300)は(1,1)に置き換えることができます(GLSLとp5.jsのキャンバスでは座標システムが異なるので注意が要ります。GLSLではyの正方向は上です)。
そして最後、gl_FragColorにvec4(st.x,0.0,0.0,1.0)を割り当てます。これは0-1の範囲で変化するRGBAの並びで、BとGは0、Aは1.0と固定されていて、Rはst.xなのでRだけが変化します。たとえばst.xが0のときは(0,0,0,1.0)なので完全に不透明な黒で、st.xが0.1のときは(0.1,0,0,1.0)なので完全に不透明な黒よりは少し赤い黒になります。st.xはx軸沿いに0から1まで変化するので、黒から赤に変化するグラデーションになります。