jit.gl のレンダリングパイプライン(簡易)まとめ #jitter #maxmsp

今月は今まで謎の存在だったjit.gl系の調査とCG系の勉強をしていました。ざっくり全体像が見えてきた気がしたのでjit.glで絵が出るまでの流れを一枚の絵にまとめてみました。

  • まずは”3Dモデル”ありき。読み込む、プログラムで作る
  • jit.genをVertex Shaderとして使うなら、上でつくった3Dモデルから頂点座標をテクスチャとして出力し利用する
  • モデルの表面に貼り付ける画像を読み込む、GPUで使う行列テクスチャをjitter系オブジェクトで計算しておく
  • 3Dモデルをどのようにカメラで撮影するかCameraやLightingの調整する
  • (上記の情報をDrawing Contextで統合)ShadingではGPU上のプログラマブルシェーダー(GLSL)を通して3Dモデルがどのように色になるのかを計算する
  • Post Processingでは画面全体のレンダリング画像に対してポストエフェクトを掛ける
  • jit.world でウィンドウに画面表示

実際は柔軟にパイプラインを組めるので上記に縛られる必要は無いのですが、jit.gl系はフラットにオブジェクトが並びすぎていてシンプルなレンダリングの流れがそもそも分かりにくいので整理しておきました。こちらの資料をもとに深めていただければと思います。(誰が・・・?)

おまけ:OpenGL 4系の勉強に役立った書籍達

GLSLのピクセルシェーダーで絵を描いて楽しいし上に、数学的な深みもかなりある良書
Jit.gl (gl-core) の裏で動いてるOpenGLへの理解の助けになります。またシェーダーはjit.gl.slabやjit.gl.shaderでそのまま参考になります。
HLSLとGLSLとは違う言語ですが、アルゴリズムが色々載ってて参考になる。ポストエフェクトはjit.gl.pixでも参考にしやすいです。
10年くらい前のゲームで使われているシェーダー技術がわかりやすく網羅・解説されています。CG業界は謎の英語専門用語が飛び交う世界で一見なんなのか分からない事も多くとても助かりました。コードは載って無いですが、頑張れば擬似的に真似できるかも?
ハードウェアアーキテクチャよりの話しが多めですがGPUについて理解が深まりました。低レイヤー・電気系の話は自分には結構むずい。
鈍器みたいな本、とりあえず買って家に置いとくと気合いが入る一冊(まだ一章しか読んでないけど、きっと凄いだろう・・・)

色1つの画面を作るのに、データ(画像・モデル)を別のソフトから持ってくるか数学的に演算で作成しておき、GPU上にテクスチャとして転送した上で、あらゆる数学的な計算しまくると絵がでる。ひたすら行列演算の話をしているんだなという事に気がつきました。

多分いまをときめくUnityやUnreal EngineでもGPUというハードウェア上でやれる事はそんなに変わらないと思うので、気合いと根性と数学力があればMax / openFrameworks / processing / touch designer 等のクリエイティブコーディング的な環境でも論理的には同じような絵は出せるんだろうなという気づきが得られて良かったです。でもむしろ3Dモデルの配置や制御だったり、アセットの購入やライブラリの充実などIDEとしての完成度や効率は段違いなんでしょうね・・・リアルタイムレンダリングはやはりゲーム業界が先頭に立っているんだろうな。

という訳で、また面白そうなトピックがあったらディグって行きたいと思います。ありがとうございました。

jit.gl.pass を深掘りする #jitter #maxmsp

はじめに

jit.gl系の画面出力にポストプロセスのエフェクトをかけられるjit.gl.passですが、ビジュアルプログラミングであるMaxにおいて、ほとんどパッチングをしないで高度な処理が走ってしまうため理解が難しい存在だと思いました。そんなjit.gl.passについて調べた結果をまとめておきます。

jit.gl.pass に対する様々な疑問点

便利なんだけど謎が多そう

jit.worldのコンテキストを設定するだけで、なぜレンダリング結果に対してポストエフェクトを掛けられるのか?

ソースは見つけられないのですが、GLSLの機能であるFBO(Frame Buffer Object)を使って一度ポストエフェクトを掛けない状態で3Dモデルやライティングを考慮したレンダリングした結果に対し、ポストエフェクトを掛けているんでしょう。(ソースが無いけど)そういうオブジェクトだから・・・みたいに理解しておきました。

DOFで深度を使えるの?

jit.gl系を触っていて3DモデルのZバッファや深度情報を直接取得する方法は見かけた事がなかったのですが、jit.gl.passのfxサンプルの中には被写界深度エフェクトのように明らかに奥行き情報を使わないと掛けられないエフェクトが入っています。どうやってるのでしょうか。

ここはjit.gl.passのリファレンスを見ると、受け渡しできるテクスチャ NORMALS のなかに アルファチャンネルにdepth画像が入れられるよと書いてあります。3Dモデルなどを一度レンダリングして画面全体の絵をつくり、その時に利用した深度マップもポストプロセスに出力してくれるという事でしょう。

DoF(Depth of Field) jxpをみてみよう

jit.gl.pass のプリセットエフェクトである dof.jxp のXML定義を見ると

最終段階の mrt.dof.jxs にNORMALSテクスチャを渡しています。

さらにmrt.dof.jxsを見てみると

受け渡されたnormal テクスチャの 4番目 wチャンネルを参照して被写界深度エフェクトを掛けています。

ポストプロセスにおける Vertex Shader について

上記の jxp ファイルを見ると一つのDoFを実現するためにも複数のエフェクト(.jxs)を重ねがけしているように見えます。このエフェクトは画面全体のフラグメントシェーダー上の操作ではあります。ただここで .jxs ってVertex Shader も含まれてなかったっけ?という疑問が湧きました。

.jxsのソースをみると sh.passthru.xform.vp.glsl というVertex Shaderを参照しています。つまりjxpで複数のjxsを通過する際に毎回 vertex shaderも通っているということですね。

sh.passthru.xform.vp.glsl のコードを見てみると

ほとんど何もしないようなシェーダーになっており、受け渡しているだけです。

こういったパススルーするVertex Shaderを利用していればほとんど意識しないでフラグメントシェーダーの事だけ考えれば良いと言えそうです。

少し余談ですが jit.gl.slabも似たようなフラグメントシェーダーのみを対象としたシェーダーオブジェクトです。こちらもjxpと同様に何もしないVertexShaderのGLSL330 versionが書いてあるので新しいgl-coreの場合はこちらの方が参考になります。

genjit を読み込んで使えるらしい?

jit.gl.passのリファレンスを読むと、genjitも読める?的な事が書いてあり、色々試してみたんですが、genjitファイルをそのままは読めませんでした。jit.gl.pixから一旦.jxsを生成したら読み込むことはできました

  1. jit.gl.pixをexportcodeして.jxsを生成
  2. .jxpにfile=***.jxsのsubpassを追記
  3. jit.gl.passで.jxpを読み込み

paramも.jxsで定義してあれば、jit.gl.passに通ります。上記の画像は edge抽出シェーダーの後にjit.gl.pixで書いた四分割シェーダーをjit.gl.passで書いてみたサンプル画像です。

まとめ

jit.gl系オブジェクトの中で一番難解だと個人的に感じたjit.gl.passへの理解が深まり、Jitterにおけるレンダリングパイプラインの概要が掴めたような気がします。次回の記事ではその辺りをまとめてみたいと思います。

JXSから外部.vert / .frag などのシェーダーを読み込む #jitter #n4m #maxmsp

Jitter & GLSLで遊ぶのを効率化したい

昨日に引き続きMax8のシェーダーで遊んでいるんですが、JXSはXMLの中にGLSLを書くような形でVisual Studio Code のコード補完やらフォーマッターなどで扱いにくかったので、JXSと.vert / .fragを分離して扱う方法を模索しました。

※この記事はMax中級者以上向きにざっくり書いてるので分かりやすくはないです。

JXSから外部シェーダー(.vert / .frag)は読める

Cycling74のforumなどを探してみるとJXSから外部シェーダー読めるよと書いてあります。具体的な書き方は見つけられなかったんですが多分こんな感じという書き方をしたらいけました。
  <program name="vp" type="vertex" source="default.vert" />
  <program name="fp" type="fragment" source="default.frag" />

これで.vert / .frag をMax Projectのotherフォルダなどパスが通ってる所に置いておけば読めます。

シェーダーを切り替えたい ⇨ JXSの動的生成(ご参考)

ではシェーダーを色々書いて遊ぼうとして .frag を量産したとしても、上記のJXS上のファイル名をいちいち変更するのはめんどくさいです。XMLに詳しくないのですが動的に変数を与えて書き換えるという事は出来なさそうな・・・?

という訳でJXS自体を動的に生成すれば良いと思いつき、こういう時に役立つ node.scriptオブジェクトことNode.js先生にお願いしました。MaxにNode.jsくっついて本当に良かった・・・

node.js / node for max のJXS生成サンプルコード

const fs = require('fs');
const MaxAPI = require('max-api');

const generateJXS = (vertexShaderFilename, fragmentShaderFilename, outputJxsFilename) => {
  const jxsTemplate = `
  <jittershader name="default">
    <description>Default Slab </description>
    <param name="scale" type="float" default="1.0" />
    <param name="tex0" type="int" default="0" />
    <param name="modelViewProjectionMatrix" type="mat4" state="MODELVIEW_PROJECTION_MATRIX" />
    <param name="textureMatrix0" type="mat4" state="TEXTURE0_MATRIX" />
    <param name="position" type="vec3" state="POSITION" />
    <param name="texcoord" type="vec2" state="TEXCOORD" />
    <param name="u_resolution" type="vec2" state="TEXDIM0"/>
    <param name="color" type="vec4" state="COLOR" />
    <param name="u_time" type="float"/>
    <language name="glsl" version="1.5">
      <bind param="scale" program="fp" />
      <bind param="tex0" program="fp" />
      <bind param="modelViewProjectionMatrix" program="vp" />
      <bind param="textureMatrix0" program="vp" />
      <bind param="position" program="vp" />
      <bind param="texcoord" program="vp" />
      <bind param="color" program="vp" />
      <bind param="u_resolution" program="fp"/>
      <bind param="u_time" program="fp"/>
      <program name="vp" type="vertex" source="${vertexShaderFilename}" />
      <program name="fp" type="fragment" source="${fragmentShaderFilename}" />
    </language>
  </jittershader>
  `;

  fs.writeFileSync(outputJxsFilename, jxsTemplate.trim());
  MaxAPI.post(`JXS file '${outputJxsFilename}' generated with '${vertexShaderFilename}' and '${fragmentShaderFilename}'.`);
  MaxAPI.outlet('read',outputJxsFilename);
};

MaxAPI.addHandler("generate_jxs", (vertexShaderFilename, fragmentShaderFilename, outputJxsFilename) => {
  generateJXS(vertexShaderFilename, fragmentShaderFilename, outputJxsFilename);
});

.vert / .frag などを引数で渡して generate_jxs 関数をMaxからコールすれば、アウトレットから今つくった.jxsファイルを出力します。後段に jit.gl.slab を接続しておけば自動的にシェーダーが切り替わるという訳です。

folderオブジェクトでVSCodeで書いた.frag名を取得し umenuにセット、umenuからの出力を利用したりすればシェーダー切り替えを簡略化する事は難しくないでしょう。ただしparam など独自変数をjit.gl.slabへ伝えたい場合はparam / bind / uniform などの書き換えが必要ですので、こんな方法もあるんかなという程度あくまで JXS / node.scriptのサンプルとしてご利用ください。上手くつくればShaderToyのコードなんかも動くと思います。

default.vert(ご参考)

#version 410

in vec3 position;
in vec2 texcoord;
in vec4 color;

out jit_PerVertex {
    vec2 texcoord;
    vec4 color;
} jit_out;
uniform mat4 modelViewProjectionMatrix;
uniform mat4 textureMatrix0;


void main(void) {
    gl_Position = modelViewProjectionMatrix*vec4(position, 1);
    jit_out.texcoord = vec2(textureMatrix0*vec4(texcoord, 0., 1.));
    jit_out.color = color;
}

リアルタイムグラフィックスの数学 ― GLSLではじめるシェーダプログラミング のサンプルコードで試してみた

ちょいちょい勉強しているこちらの書籍 ⇨ リアルタイムグラフィックスの数学 ― GLSLではじめるシェーダプログラミング の.frag のサンプルコードを利用して切り替えテストしてみました。

無事に.fragをさくさく切り替えるという事ができました。

(余談ですが)#version 330 core のサンプルを #version 410に置換して利用したのですが、他は何も変更しないで動きました。GLSL3以上は後方互換性が担保されているのでしょうか?

Happy Patching!

M2 MacのJXS(GLSL4.1) 覚書 #maxmsp #jitter #glsl

はじめに

相変わらず趣味的にMax8を触っているのですが、ChatGPTが出てからプログラミングのラストワンマイルが近くなったような感じ(いやMaxはビジュアル言語なので全然恩恵を受けてないんですが・・・)で周辺環境のNode.jsとglslをもうちょい深掘りしてみようかなと。今回はMax8のシェーダーJXSについてです。

M2 Mac & Max8で使えるOpenGL4.1?

最近のGL3化されたMaxを起動して、jit.worldなんかを起動した時にOpenGLの対応バージョンがMax Console(Cmd + m)で表示されるんですが、なんかGLSL4.1って書いてあるんですね。GL3化の名の通りGLSL3.3あたりだと思っていたんですが、比較的新しいバージョンが使えるなら嬉しいが本当か?

JXS(GLSL)で確かめてみる → GLSL4.1のコンパイル通る

適当なサンプルファイルのバージョン指定に410と書いてコンパイルが通りました。実際にChatGPT先生に410から使える文法を聞いたところdouble型が追加されていたのでやってみたら使えました。GLSL自体の知見はほとんどないので330との違いはどれくらいあるのかはよく分からないです。

(余談)jit.gl.pixのexportcodeで生成されるglslは100系列の古いコードでした。

JXSのサンプルコード(トップの画像)

<jittershader name="simpleShader">
    <description>Default Shader </description>
    <param name="position" type="vec3" state="POSITION" />
    <param name="modelViewProjectionMatrix" type="mat4" state="MODELVIEW_PROJECTION_MATRIX" />
    <param name="color" type="vec4" state="COLOR" /> 
    <param name="dim" type="vec2" state="TEXDIM0"/>
    <param name="testvalue" type="float" default="0.5" max="1" min="0"/>
    <param name="scale" type="float" default="1" max="10" min="-10"/>
	<param name="mycolor" type="vec4" default="1 0.2 0.5 1" />
    <language name="glsl" version="4.1">
        <bind param="position" program="vp" />
        <bind param="modelViewProjectionMatrix" program="vp" />
        <bind param="color" program="vp" />
        <bind param="testvalue" program="fp" />
        <bind param="scale" program="fp" />
        <bind param="dim" program="fp"/>
        <bind param="mycolor" program="fp"/>
        <program name="vp" type="vertex">
<![CDATA[
#version 410
uniform mat4 modelViewProjectionMatrix;
in vec3 position;
in vec4 color;
out jit_PerVertex {
	vec2 texcoord;
	vec4 color;
} jit_out;
void main() {   
    gl_Position = modelViewProjectionMatrix * vec4(position, 1.);   
    jit_out.texcoord = position.xy;
    jit_out.color = color;
}
]]>
        </program>
        <program name="fp" type="fragment">
<![CDATA[
#version 410
const float PI = 3.14159265358979323846;
uniform float  testvalue;
uniform vec2 dim;
uniform float scale;
uniform vec4 mycolor;
in jit_PerVertex {
	vec2 texcoord;
	vec4 color;
} jit_in;
layout (location = 0) out vec4 outColor;
void main() {

    vec2 pos = gl_FragCoord.xy / dim;
	pos = (pos*2-vec2(1,1))*scale;
	pos = (pos + vec2(1,1))/2;
    outColor = mycolor*vec4(pos.xy,testvalue,1);
}   
]]>
        </program>
    </language>
</jittershader>

解説

  • vertex shader と fragment shaderがwrapされたようなXMLです。
  • 上記をテキストファイルにsampleShader.jxsなど保存しjit.gl.slab にread(読み込ませ)で使います。
  • state = “TEXDIM0” のお陰で自動的に dim(変数名は自分で設定できる)に解像度が入ります。
  • testvalue / mycolorなどの変数のつくり方を参考にしたら、jit.gl.pixでいうparamと同じように外部設定できるパラメーターをつくれます。
  • colorはgl_colorから設定する色が入力されるようですが使えたり使えなかったりで挙動がかなり怪しいので、mycolorなど別の変数を定義して色入力したほうが良さそうです。
  • それ以外はGLSL4.1の文法を参考にできそうです。
  • (感想)jit.gl.pixは便利関数などを用意して使いやすくしてくれたんだな〜と思った

Max Code

JXSファイルはCopy Compressedに含められなかったので上記を別途保存~読み込みしてください

----------begin_max5_patcher----------
1013.3ocwX00iahCE84jeEV7b1HLXHPepakVspRSkVo4wUqFQ.mTOErQFmNY
1p9eesuFxjYBCwMgvpHAxla7ky8b+jeLel2ZwdZiG5Cn+FMa1OlOaFrkYiYs
qm4UksOuLqADyqh1zjsk5sv9LEcuB1WRyJ51juqRrSURUveA2tKq.DTr9weK
rSRqXpmqo12AOOz+z9n5LU9WY7sOHo4J6SCIKiVfH9oK8WfBCMWCBV5e3un0
Ki2o1.yd+b9bykENBt06TJA2YXDG4c3kUlUQUT4CTd15R.M9uGFWmw2NHNIo
q.rkPfaGt1OPwW.P4zmz.3DRLWTJj0r7uQknOlKpzuar0rRl5Yje+VkfdrJ3
2kcKYMJuEtaABwIla3njg45QzDTKo0TdABXTT0yfIwcOB+KyyljF.3k.t13v
zwGuaJE5yvYVLJ8h7sOkc2HjUY.LiGHxNJ1x2fudj++SzcSdVI0YxNJ4BSi0
B1VxN.O9n8QlZY8SLdg3ImY7vf2EN5i6AMIJY6M76ffKA.EIBtk.DYr+QPq4
Yd9w1yaGwx3p9wt+oXO3nL4m.o.axHRD3bFFbanqM0Ma2w7ND0vUaxxeUvV+
LXkn.jJrGT4e7w0v9WPPrA.CD9dbnKWw0w9vA96RVV4.VIbhs5r0JYY9vnIH
DtTjU.obbMtE+9wsmsvDNH5nBS2h5RWiuvwfLdJ49TaNr3nIl6Un0le8S88D
Qf8Gl5W7l6C5JDaYeaBARvjUyx3grsboY8NIEc+m+xec2ef9XAqBg8CHvEmy
5uZvj9aKenUMmMweWjgspV.dxsGMkYqOXL1vJonFVUcI89ulUPkKebei6svN
VFk1pg9s8ysZ7GYYfjhlQ0PX2am4x5lw1LSJfvU2fF2Thsa+EZJibQssZ5U3
7fDShm3AxLt1OIjkE+JQ480Ifis10k7yEiQJwd6Fz9pdzyJ5Ksucvb7m2c+c
HxRL59LSjsys2gI8ULLL9PwvWWsS6snDbsR3MnOIJKFp2P6Wg.rAQXb2zKm2
T.RnGHl+1O+BnDy9u19zH1Iy6HstRYnWTTAsQw3540E7iDJ1JSuTfqJJbhzi
YbpIQQly.eF8XFHXTTz4.jYpqiDRH00p.u3qSy9Sns7b540HrhUTKz4aac6S
SfoFH31dFe6BbBDgE+xhSsU3qBBqb2TM8JNv+V3dfcITa0H3dPb283pzSfCg
zcILuNKm4PBNmlFkzgNX5HigdRcPQc4lutbEXWhzFCVJ1EiW2qyIEkypq+NU
1zJMnDc6IOJf.ujEvRF2tLBVJoem0IOA1ISp6TPoaSvLkfooh8w1dPfuWijq
mt2l+PCOsJgVeLMfzT2NnOzgz7eN++Pgxt6I
-----------end_max5_patcher-----------

外部リンク

jit.gl.lua で jit.gl.slab シェーダーをかけるサンプル:#jitter #max6

スクリーンショット 2014-05-28 22.31.18

jit.gl.luaの内部で、シェーダーをかけるサンプルを書いてみました。

[javascript]

autowatch = 1
gc = 1

local mov = jit.new("jit.qt.movie",this.drawto)
mov.adapt = 1;
mov:read("bball.mov");

local frame = jit.matrix()

local plane = jit.new("jit.gl.videoplane",this.drawto);
plane.scale = 0.8
plane.automatic = 1;

local slab = jit.new("jit.gl.slab",this.drawto)
slab.file = "td.kaleido.jxs";
slab:param("div",4)

function draw()

mov:matrixcalc(frame.name,frame.name)

slab:jit_matrix({frame.name,frame.name})
slab:draw()

plane:jit_gl_texture(slab.out_name)
plane:draw();

end

[/javascript]

ちなみに初めは、jit.gl.pixを使おうと思ったのですが、.genjitを読みこませようとすると

jit_xml_document: error reading file at byte offset 0 / not well-formed (invalid token)

というエラーがでてしまい、上手く読み込ませる事ができなかったです。