p5.js備忘録(4)3Dモデルとシェーダー

スポンサーリンク

3Dモデル

3DCGツールで作成した3Dモデル(立体のキャラクターなどのデータ)を読み込んで表示できます

*p5.jsでは同じドメイン内からしか3Dモデルを読み込めません

  • loadModel(path, normalize, [successCallback], [failureCallback], [fileType])
    OBJファイルまたはSTLファイルから 3Dモデルを読み込みます
    preload()内に配置する必要があります
    サイズが大きい場合は第二引数にtrueを設定(p5に適したスケールに変更)
    scale() 関数で最終的なサイズにできます
    • path
      ロードするモデルのパス
    • normalize:
      true の場合読み込み時にモデルを標準化されたサイズにスケーリングします
    • successCallback
      モデルが読み込まれると呼び出される関数
      3Dモデルオブジェクトが渡されます
    • failureCallback:
      モデルの読み込みに失敗した場合、イベントエラーで呼び出されます
    • fileType
      モデルのファイル拡張子 「.stl・.obj」
  • model()
    3Dモデルを画面にレンダリングします
  • OBJファイル(.obj)
    ジオメトリと一部のマテリアルおよびテクスチャデータを格納するファイル形式
    p5.j​​s ではジオメトリのみ
  • STLファイル(.stl)
    ジオメトリに関する情報のみ格納するファイル形式
    p5.j​​sでは色付きのSTLファイルはサポートされていません
    *色付きの STLファイルは色のプロパティなしでレンダリングされます
let chair;
function preload() {
  chair = loadModel('chair.obj', true);
}
function setup() {
  createCanvas(windowWidth, windowHeight, WEBGL);
}
function draw() {
  background(200);
  scale(0.5);
  rotateX(frameCount * 0.01);
  rotateY(frameCount * 0.01);
  normalMaterial();
  model(chair);
}

無料で3Dモデルがダウンロードできるサイト

シェーダー(GLSL)入門

シェーダーはGPUで実行される「色の計算に特化した小さなプログラム」です。従来のp5.js(CPU)が一つずつ順番に描画するのに対し、シェーダー(GPU)は数百万ピクセルを同時に計算するため、圧倒的に高速で複雑な表現が可能です。

1. 二つの役割:Vertex & Fragment

GLSLでは、必ず以下の2つのファイルをセットで記述します。これが「形」と「色」の役割分担です。

頂点シェーダー (.vert)

役割:「どこに」描くかを決める。
3D空間の点を、画面上の2D座標に変換します。頂点ごとに1回実行されます。

フラグメントシェーダー (.frag)

役割:「どんな色で」塗るかを決める。
頂点に囲まれた中身(ピクセル)の色を決定します。全ピクセルで同時に実行されます。

2. 【なぜ?】座標を計算し直す理由

positionVec4.xy = positionVec4.xy * 2.0 - 1.0;

p5.jsの通常モード(Canvas)は左上が原点ですが、GPUの世界(WEBGL)は「中心が (0,0)、範囲は -1.0 〜 1.0」というルールがあります。この一文は、0〜1の座標系を、GPUが理解できる -1〜1 の世界へ引き伸ばすための「翻訳」作業です。

3. JavaScriptからデータを送る「Uniform」

CPU(JS)で計算した「時間」や「解像度」をGPUへ届けるための専用ポストがUniform(ユニフォーム)です。

// JavaScript (p5.js) theShader.setUniform(“u_time”, millis() / 1000.0);
theShader.setUniform(“u_resolution”, [width, height]);
// GLSL (.frag) uniform float u_time;
uniform vec2 u_resolution;

4. 実装のワークフロー

  1. preload()loadShader() でファイルを読み込む。
  2. setup()createCanvas(..., WEBGL) でWEBGLモードを起動。
  3. draw()shader(theShader) を適用し、setUniform でデータを送る。
⚠️ 誰もが通る「真っ暗な画面」の罠

シェーダーは「インク」のようなものです。設定しただけでは何も映りません。draw() の最後で rect(0,0,width,height) などの「キャンバスとなる形状」を描画するのを忘れないようにしましょう!その形に対して、初めてシェーダーが反映されます。

let theShader;
function preload() {
  theShader = loadShader('shader.vert', 'shader.frag');
}
function setup() {
  createCanvas(windowWidth, windowHeight, WEBGL);
  pixelDensity(1);
  noStroke();
}
function draw() {
  shader(theShader); 
  theShader.setUniform("u_resolution", [width, height]);
  theShader.setUniform("u_time", millis() / 1000.0);
//シェーダーを任意の形状に適用、形状を指定しない限りシェーダーは機能しない
  rect(0,0,width, height);
}
function windowResized(){
  resizeCanvas(windowWidth, windowHeight);
}

2つのテンプレート(備忘録)

シェーダーには、「最初から0〜1に整っている住所」を使うパターンと、「実際のピクセル数」から計算するパターンの2つがあります。

割合(vTexCoord)頂点シェーダーから届く「割合」を使う
計算がシンプルで、p5.jsと相性がいい
uv = vTexCoord * 2.0 - 1.0
ピクセル(gl_FragCoord)画面の「ドットの数」を直接使う
他のサイト(ShaderToyなど)のコードを移植しやすい
uv = (gl_FragCoord.xy * 2.0 – uResolution.xy) / min

vTexCoord を使う「モダン・スタイル」

p5.jsが最初から「ここが端っこだよ」と教えてくれる数値を使う、一番スマートな書き方です。

//shader.vert(頂点シェーダー)
precision highp float;
attribute vec3 aPosition;
attribute vec2 aTexCoord;
varying vec2 vTexCoord; // フラグメントシェーダーに送る「連絡通路」

void main() {
  vTexCoord = aTexCoord; // 0.0〜1.0の住所をそのまま渡す
  vec4 positionVec4 = vec4(aPosition, 1.0);
  
  // キャンバスを画面いっぱいに広げるおまじない
  positionVec4.xy = positionVec4.xy * 2.0 - 1.0;
  gl_Position = positionVec4;
}
//shader.frag(フラグメントシェーダー)
precision highp float;
precision highp int;
varying vec2 vTexCoord;
uniform vec2 uResolution;
uniform float uTime;

void main() {
  // 1. 0〜1を「-1〜1」の範囲に直す(真ん中を 0,0 にする)
  vec2 uv = vTexCoord * 2.0 - 1.0;
  
  // 2. 画面の比率(アスペクト比)を直す
  uv.x *= uResolution.x / uResolution.y;

  // ここから魔法を書く
  float d = length(uv);
  gl_FragColor = vec4(vec3(step(0.3, d)), 1.0);
}

gl_FragCoord を使う「クラシック・スタイル」

「左から何ピクセル目、下から何ピクセル目」という生のデータを、自分で割って計算するタフな書き方です。

//shader.vert(頂点シェーダー)

attribute vec3 aPosition;
void main() {
  vec4 positionVec4 = vec4(aPosition, 1.0);
  // 画面の真ん中を (0,0) にする魔法!
  positionVec4.xy = positionVec4.xy * 2.0 - 1.0;
  gl_Position = positionVec4;
}
//shader.frag(フラグメントシェーダー)
precision highp float;
uniform vec2 uResolution;
uniform float uTime;

void main() {
  // 1. ピクセル座標(0〜1920など)を一気に「-1〜1」の割合に変換!
  // min(x, y) で割ることで、短い方の辺を基準にして比率を合わせる
  vec2 uv = (gl_FragCoord.xy * 2.0 - uResolution.xy) / min(uResolution.x, uResolution.y);

  // ここから魔法を書く
  float d = length(uv);
  gl_FragColor = vec4(vec3(step(0.3, d)), 1.0);
}

見つける😊
*OpenProcessingは、Processingの作品を公開したり共有したりできるサイトです

スポンサーリンク