はじめに
現在のp5.js2.3.1において通常の3D描画に使われているシェーダーを見てみよう。
使うのはWebGLのgetShaderSourceという関数。これは現状動いているプログラムにアタッチされているシェーダーから、ソースをまるごと取得するための関数。
もっともまずはアタッチされているシェーダーを取らないといけないけど。
この二段構え。
function setup() {
createCanvas(400, 400, WEBGL);
console.clear();
const gl = drawingContext;
noStroke();
fill(255);
lights();
background(0);
sphere(40);
const pg = gl.getParameter(gl.CURRENT_PROGRAM);
const shaders = gl.getAttachedShaders(pg);
const vs = gl.getShaderSource(shaders[0]);
const fs = gl.getShaderSource(shaders[1]);
console.log(vs);
console.log(fs);
}
プログラムはgetParameterで取得できる。それにはプログラムが動いている必要がある。単純にスフィアをライティングで描画するとそれができる。そして描画の後も特にプログラムが切れることは無いので、そのまま取得できる。ちょっとvsだけ見てみようか。
#version 300 es
#define WEBGL2
#define VERTEX_SHADER
precision highp float;
#ifdef WEBGL2
#define IN in
#define OUT out
#ifdef FRAGMENT_SHADER
out vec4 outColor;
#define OUT_COLOR outColor
#endif
#define TEXTURE texture
#else
#ifdef FRAGMENT_SHADER
#define IN varying
#else
#define IN attribute
#endif
#define OUT varying
#define TEXTURE texture2D
#ifdef FRAGMENT_SHADER
#define OUT_COLOR gl_FragColor
#endif
#endif
vec4 getTexture(in sampler2D content, vec2 coord) {
vec4 color = TEXTURE(content, coord);
if (color.a > 0.) color.rgb /= color.a;
return color;
}
precision highp int;
IN vec3 aPosition;
IN vec3 aNormal;
IN vec2 aTexCoord;
IN vec4 aVertexColor;
#ifdef AUGMENTED_HOOK_getWorldInputs
uniform mat4 uModelMatrix;
uniform mat4 uViewMatrix;
uniform mat3 uModelNormalMatrix;
uniform mat3 uCameraNormalMatrix;
#else
uniform mat4 uModelViewMatrix;
uniform mat3 uNormalMatrix;
#endif
uniform mat4 uProjectionMatrix;
uniform bool uUseVertexColor;
uniform vec4 uMaterialColor;
OUT vec3 vNormal;
OUT vec2 vTexCoord;
OUT vec3 vViewPosition;
OUT vec3 vAmbientColor;
OUT vec4 vColor;
struct Vertex {
vec3 position;
vec3 normal;
vec2 texCoord;
vec4 color;
};
void HOOK_beforeVertex() {}
Vertex HOOK_getObjectInputs(Vertex inputs) { return inputs; }
Vertex HOOK_getWorldInputs(Vertex inputs) { return inputs; }
Vertex HOOK_getCameraInputs(Vertex inputs) { return inputs; }
void HOOK_afterVertex() {}
void main(void) {
HOOK_beforeVertex();
Vertex inputs;
inputs.position = aPosition;
inputs.normal = aNormal;
inputs.texCoord = aTexCoord;
inputs.color = (uUseVertexColor && aVertexColor.x >= 0.0) ? aVertexColor : uMaterialColor;
#ifdef AUGMENTED_HOOK_getObjectInputs
inputs = HOOK_getObjectInputs(inputs);
#endif
#ifdef AUGMENTED_HOOK_getWorldInputs
inputs.position = (uModelMatrix * vec4(inputs.position, 1.)).xyz;
inputs.normal = uModelNormalMatrix * inputs.normal;
inputs = HOOK_getWorldInputs(inputs);
#endif
#ifdef AUGMENTED_HOOK_getWorldInputs
// Already multiplied by the model matrix, just apply view
inputs.position = (uViewMatrix * vec4(inputs.position, 1.)).xyz;
inputs.normal = uCameraNormalMatrix * inputs.normal;
#else
// Apply both at once
inputs.position = (uModelViewMatrix * vec4(inputs.position, 1.)).xyz;
inputs.normal = uNormalMatrix * inputs.normal;
#endif
#ifdef AUGMENTED_HOOK_getCameraInputs
inputs = HOOK_getCameraInputs(inputs);
#endif
// Pass varyings to fragment shader
vViewPosition = inputs.position;
vTexCoord = inputs.texCoord;
vNormal = inputs.normal;
vColor = inputs.color;
gl_Position = uProjectionMatrix * vec4(inputs.position, 1.);
HOOK_afterVertex();
}
fsも同様に見られるが、長すぎるので自分でチェックしてください...なお文字列なのでp5のテキストセーブ関数で普通に取得することもできます。興味のある人は取得してみましょう。
#defineでINとOUTをエイリアスで使ってます。まあなんか美学があるんでしょう(よくわかんないです)。フラグメントどうのこうののところもよくわかんないです。でもなんか色々やってるんでしょう。
行列計算で行列を左から掛けてるのはあんま好きくないですね...やっぱ数式が右に流れていくので、左から掛け算されているのを見るとちょっと違和感がね...まあ仕方ないっすね。
これできちんと3D描画になっています。自分の素人仕事よりずっと複雑な処理をしています。プロの仕事です。やはり難しいことはプロに任せるのが一番です。
余談:前回のmodifyでちょっと遊ぶ
紙数が余ったので余談。baseMaterialShaderのmodifyにはレファレンス:
には書いてないことがいくつかあるんで、それを紹介します。前回vertexDeclarationsとfragmentDeclarationsは無いじゃないか、と愚痴を言いましたが、きちんと読んだら下の方に書いてありました。
You can also add a declarations key, where the value is a GLSL string declaring custom uniform variables, globals, and functions shared between hooks. To add declarations just in a vertex or fragment shader, add vertexDeclarations and fragmentDeclarations keys.
しかしサンプルに出てこないので...分かりませんね...なおこれを記述するとどこに追記されるかというと、メイン関数の近く、before何とかのちょっと上の方です。ちなみにdeclarationsに書いた場合、両方に同じ記述がされます。ですからvaryingの場合declarationsは使えず、vertexとfragmentに分けて書く必要があります。uniformも、それぞれの箇所に記述する必要がありますが、実は宣言する方法も一応、あります。
記述例:
myShader = baseMaterialShader().modify({
// Manually specifying a uniform and attribute
declarations:'// ほげほげ~',
uniforms:{'float uFish':4.0, 'mat4 uMat4':[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]},
// 何これ。ていうかバリューの仕事が分からん。バリューなにすんの?これだと両方に出るんだけど。分かった。これその値がそのまま使われる。だから4.0ってやるとvsとfs双方で4.0が使われる。
// デフォルトユニフォームっていうらしいです。もちろんuniformでもあるので後からいじることもできるのでしょう。
varyingVariables:['float vTime', 'vec4 vKing', 'vec3 vQueen'], // 冗談に思えるが、これでvaryingできるんだと。うへ~うへ~~~~~うへ~~~~ありえねぇ
vertexDeclarations: 'uniform float time; in vec3 aSubPosition; in float aNoiseValue; out float vNoiseValue;',
fragmentDeclarations: 'in float vNoiseValue;',
'Vertex getWorldInputs': `(Vertex inputs) {
inputs.position = (0.5-0.5*sin(time))*aSubPosition + inputs.position*(0.5+0.5*sin(time));
return inputs;
}`,
'void afterVertex': `() { vNoiseValue = aNoiseValue; }`,
'vec4 getFinalColor': `(vec4 color, vec2 texCoord) { color.rgb *= vNoiseValue * 4.0; return color; }`
});
反映の様子(一部)
#define AUGMENTED_HOOK_getFinalColor
uniform float uFish;
uniform mat4 uMat4;
// ほげほげ~
in float vNoiseValue;
IN float vTime;
IN vec4 vKing;
IN vec3 vQueen;
varyingVariables
配列で指定します。これのように、宣言部分をセミコロン無しで用意すると、そのままvsとfsにOUTとINで分散して記述されます。
uniforms
uniformも扱えますが制約があります。なんと両方で同じ内容が記述されます。つまり重複uniformです。エラーは出ませんが、コードによっては無駄でしょう。なお、宣言はここにあるようにさっきと同じく型と名前をキーとして記述、valueですが、これはデフォルトの値です。uniformに詳しい人は知っていますがuniform変数というのはプログラムがその内容を記憶する変数(UBOの場合は外部バッファが記憶するものを流用する)ですから、デフォルトの値を予め設定しておいて使おうというわけですね。constとの違いはuniformなので書き換えできる点です。もちろん、両方で同じ値がセットされます。それゆえvaryingとは違ってオブジェクト形式となっています。
通常通り個別にuniformを扱いたい場合は、vertexDeclarations/fragmentDeclarationsを使いましょう。(当然ですが、その場合デフォルト値は自身で設定することになります。)
おわりに
p5の1系はすでに新機能の追加や更新は凍結されています。
Because p5.js v1 has been feature-frozen for over a year, each patch contains mostly documentation updates. In this upcoming patch, there are 3 non-documentation changes. Please check them out, try to find any side-effects, and report them as bugs!
そしてp5.js web editorのデフォルトはすでに2系に移行し、主に教師向けのマイグレーションガイドが付記されています。
Sets p5.js v2 to default in version dropdown, and add "Default" and "Not Maintained" labels. Also updates copy in Preferences modal.
ここまでお読みいただいてありがとうございました。