jit.gl.model のマテリアル・テクスチャマッピングについての調査報告 #jitter #maxmsp

3Dモデルを読み込むためのオブジェクト jit.gl.model のテクスチャマッピングについて調査したので書き残しておきます。上記は様々な苦労の果てに描画された至極単純なシェーディングの一枚・・・涙

jit.gl.modelの裏側では assimp

jit.gl.modelでglTFのpbrマテリアルモデルを読み込むとテクスチャ・マテリアルが反映された状態のモデルが読み込まれます。

ここでJitter大好きおじさん達からしたら、「えっ、jit.gl.materialもjit.gl.pbrも、ましてやjit.gl.textureでテクスチャ指定してないのに何なん?」ってなりますよね。私はなりました。

これに対しては Max8 の最近のアップデートに

と書いてありました。理由もよくわかりませんが、適応されるんだなと暗記します。モデル付属のテクスチャも自動で読んでくれています。

ちなみにjit.gl.modelはさまざまなモデルファイルに対応していますが、裏側ではassimpというC / C++ ライブラリで出来ているようなのです。glTFなど最近の3Dモデルも問題なく読み込めるようになったりするのはこのライブラリが進化しているからだと予想できます。adobeがsupportしてgithubもそこそこ活発なようなので今後も開発は続けられると予想されますね。

jit.gl.modelのマテリアル

jit.gl.modelを描画する際に jit.gl.meshと組み合わせたり、マテリアルやシェーディングを色々といじりたくなりますがこれがハイパー難解でした。色々あったのですが結論だけ用途に合わせていくつかのパターンで紹介します。

パターン1 : デフォルト pbr マテリアルを利用

@material_mode というアトリビュートがあるので、こちらを3:JMTL(Use Jitter Material)としておけば、自動設定されたCycling74オススメのpbr materialになります。単に3Dを表示させるだけならこれでいいでしょう。

ただし、現状(8.5.4)だと roughness / metalness などの jit.gl.pbr でいじれるようなアトリビュートは変更できません。モデルのテクスチャによって設定されています。

つまり色合いを調整したかったら3Dソフトに戻って変更してねという事です。私は3Dソフトについては素人なので具体的な方法は分かりません。

デフォルトpbrの問題は jit.gl.enviroment などを利用して環境マップを設定しないとめちゃ暗いなどの問題もあります。またMaxで3Dをやる薄い理由の一つでもある @outputmatrix 1 をした瞬間に利用できなくなり、デフォルトpbrを利用したオーディオリアクティブなメッシュなどを作り出すことはできなさそうです。(やり方見つけた方いたら教えてください)

パターン2 : jit.gl.shader (GLSL)マテリアルを利用

では自分好みのシェーディングをしたい場合はどうでしょうか

@material_mode 0~3 に設定しておきます。そうすると一旦テクスチャが反映されていない状態になります。

続いて jit.gl.shader に@name xxx と名前をつけ、jit.gl.model @shader xxx と指定します。この時 @materialで指定しないように注意して下さい。

具体的にどのような色を描画するかGLSLのシェーダーを書きます。サンプルプログラムを下の方に載せてあります。ポイントは ” <param name=”modelTexture” type=”int” default=”0″/> ” の部分で、modelTextureという変数のインデックスを変更する事で albedo / normal / roughness / metallic / ambient occlusion / height / emission /emvironment などのUVマップ完了後テクスチャにアクセスできます。modelTextureというのは私がつけた名前なので好きな変数名に変更できますが、変数がsampler2DRectと結びつく事でこの挙動を引き出しているのでしょう・・・また番号と内容の対応は要調査が必要です。

例えば modelTexture0 ~ 5 などを default = “0” ~ “5” のように作成すると全てのテクスチャにアクセスしながらシェーディングが行えます。

jit.gl.shader の例

<jittershader name="simplematerial">
	<description>
	simple material for 3d model
	</description>
    <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="modelTexture" type="int" default="0"/>
	<language name="glsl" version="4.1">
		<bind param="width" program="fp" />
		<bind param="height" program="fp" />
		<bind param="threshold" program="fp" />
        <bind param="modelViewProjectionMatrix" program="vp" />
        <bind param="textureMatrix0" program="vp" />
        <bind param="position" program="vp" />
        <bind param="texcoord" program="vp" />
        <bind param="modelTexture" program="fp" />
		<program name="vp" type="vertex">
<![CDATA[
  
#version 410
 
in vec3 position; 
in vec2 texcoord; 

uniform mat4 modelViewProjectionMatrix;
uniform mat4 textureMatrix0;
 
out vec2 texcoord0;

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

]]>
</program>
		<program name="fp" type="fragment">
<![CDATA[

#version 410

in vec2 texcoord0;

// samplers
uniform sampler2D modelTexture;

out vec4 outColor;

// entry point
void 
main()
{
    outColor = texture(modelTexture,texcoord0).xyzw;
}  
]]>
		</program>
	</language>
</jittershader>

パターン2 : 他のモデルのテクスチャをみてみよう

jit.gl.shader で様々なテクスチャを引いてこれる仕様を見つけるのが非常に困難で疲れた・・・のでもう少しお付き合いください。ひとつ車のモデルの例で見てみましょう。この画像自体はパターン1のデフォルトpbrシェーダーで描画されてます。やはり暗めですね。

この車のモデルには複数の元テクスチャがあるのですが、jit.gl.shaderで参照できるUVマップ後のテクスチャと比較してみましょう。

before(合成される前のテクスチャファイル)

after (jit.gl.model と jit.gl.shaderの組み合わせで取得できるテクスチャ)

なんと5種類しかないんですね。これは画像として用意されてる元のテクスチャをjit.gl.modelで読み込んだ瞬間に3D空間で使いやすい形式に再合成されているということですね。枚数が減ったからといって無駄になっているテクスチャはなさそうです。これを見つけるのに2日は費やしました。

パターン3(微妙):jit.gl.material や jit.gl.pbrを利用する

例えば、jitterのパターンとして jit.gl.model @outputomatrix 1 として内部のモデル行列をjit.gl.meshに送って描画する事があると思います。その時のテクスチャマッピングはjit.gl.meshに対してjit.gl.materialやjit.gl.pbr を接続し、jit.gl.textureなどを通して別途テクスチャを読み込み、マテリアルを反映させます。

ここで問題があります。パターン2の画像のように jit.gl.modelが1つのUVマップ後テクスチャを生成するのに複数の元テクスチャを利用していると、jit.gl.meshにおいて綺麗にテクスチャマッピんグをする事はできません。jit.gl.material / jit.gl.pbrに対して複数の元テクスチャを読み込みむ事ができないためですね。

この手法で上手く行く可能性があるのは元テクスチャとUVマップ後のテクスチャが1:1対応している場合なのかなと予想します。上記の車のテクスチャの例を見ると、8枚の画像から5枚のテクスチャを生成しており、albed以外でも複数の元テクスチャを参照してテクスチャマッピングをしているので様々3Dモデルを動的に読み込んで表現するのであればパターン3はオススメできません。

パターン2であれば、Vertex Shaderも使える!!!ので頂点・メッシュに対して変形処理を書くことができます。

まとめ

jit.gl.modelの難解なテクスチャマッピングの仕様を調査してみました。冒頭の画像はjit.gl.shaderでようやくシェーディングを描き始める事ができた画像という感じです。

昔JitterのVJを見る機会がありましたが jit.gl.gridshapeかjit.gl.platoなどのプリミティブな図形が利用されがちなのが理解できてきました。モデル読み込み&マテリアル適応の概念が難しすぎる。継承・関係性の仕様が隠蔽されていて厳しい!

今回の調査で自分でシェーダーを描きさえすれば好きなモデルを使えそうでという事で一歩前進したでしょうか。ぶっちゃけJitterは仕様を調べるのが大変すぎて何もつくれていません。MaxへのLoveで突き進んできましたが、これ以上理解が難しい問題があったら大人しくUnityかUnreal Engine使おうと思います。

厳しい

リンク

3Dモデルはこちらからお借りしました ⇨ glTF Sample Model – https://github.com/KhronosGroup/glTF-Sample-Models

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におけるレンダリングパイプラインの概要が掴めたような気がします。次回の記事ではその辺りをまとめてみたいと思います。

jit.gl.shader multi textureの書き方 & vertex shader で波形表示サンプル #jitter #maxmsp #glsl

本日はjit.gl.shaderに2つ目のテクスチャを突っ込んで波形表示するサンプルを作ってみました。1つはカラフルな色のシェーダーを、もう一つはjit.catch~ で作った波形のテクスチャです。

波形のテクスチャ生成

ほとんどjit.catch~のhelpを流用してるだけなので簡単です。

続いてシェーダーですが、JXSの仕様通りにつくればいけると思いきや、罠がありました。

state = “TEXTURE0” とか書けってdocumentに書いてあったのでハマった・・・

結果

vertex shaderは0中心に振動するとか戻ってくる処理(サンプルのように)ウネウネさせるくらいじゃないと、CPUが管理している本来のモデルの位置からズレちゃうので使いにくそうだなと思いました。

本で読んだ感じだと、PBOとテッセレーションシェーダーと組み合わせたりしてパーティクル作ったりとかそのような使い方が良さそうな。

また今回書いた程度の処理ならぶっちゃけjit.gen使ってCPU上で書いちゃうのもありかもしれません。activity monitor と睨めっこしてCPU / GPU 比較的余裕ありそうなな方に担当してもらうと。

jit.gl.shader色々調べてみましたが、まだまだGLSL4.1の勉強という感じですね。

code

<jittershader name="fill-flat-quads">
	<description>Default Shader </description>
    <!-- JXS Document https://docs.cycling74.com/max8/tutorials/jitterchapter99_appendixc -->
    <!-- Vertex -->
	<param name="position" type="vec3" state="POSITION" />
	<param name="modelViewProjectionMatrix" type="mat4" state="MODELVIEW_PROJECTION_MATRIX" />
	<param name="color" type="vec4" state="COLOR" />
    <!-- Texture -->
    <param name="texcoord" type="vec2" state="TEXCOORD" />
    <!-- Texture1 -->
	<param name="tex0" type="int" default="0" />
    <param name="textureMatrix0" type="mat4" state="TEXTURE0_MATRIX" />
    <param name="texdim0" type="vec2" state="TEXDIM0" />
    <!-- Texture2 -->
 	<param name="tex1" type="int" default="1" />
    <param name="textureMatrix1" type="mat4" state="TEXTURE1_MATRIX" />
    <param name="texdim1" type="vec2" state="TEXDIM1" />
    <!-- my uniform -->
    <param name="u_time" type="float" />
	<param name="k" type="float" default="4" />
	<param name="velocity" type="float" default="1" />
	<param name="amp" type="float" default="1" />
	<language name="glsl" version="4.1">
		<bind param="position" program="vp" />
		<bind param="modelViewProjectionMatrix" program="vp" />
		<bind param="textureMatrix0" program="vp" />
        <bind param="tex0" program="fp" />
        <bind param="texdim0" program="fp" />
		<bind param="textureMatrix1" program="vp" />
        <bind param="tex1" program="vp" />
        <bind param="texdim1" program="vp" />
		<bind param="color" program="vp" />
		<bind param="texcoord" program="vp" />
		<bind param="u_time" program="fp"/>
		<bind param="k" program="vp" />
		<bind param="velocity" program="vp" />
		<bind param="amp" program="vp" />
		<program name="vp" type="vertex">
<![CDATA[
#version 410

//in : Each instance can have different values
in vec2 texcoord;
in vec3 position;
in vec4 color;

//Textures are placed in GPU memory.
uniform sampler2DRect tex1;
uniform vec2 texdim1;

//uniform : Keeps the same value for all instances
uniform mat4 modelViewProjectionMatrix;
uniform mat4 textureMatrix0;

uniform float k; //wave count
uniform float velocity;
uniform float amp;
uniform float u_time;

//out : Structural input to next shader stage
out jit_PerVertex {
	vec4 color;
    vec2 texcoord;	
} jit_out;

void main() {

	vec4 pos = vec4(position, 1.);

	/*
	// sin wave
	float u = k * (pos.x - velocity * u_time);
	pos.y += amp * sin(u);
	vec3 n = vec3(0);
	n.xy = normalize(vec2(-k * amp * cos(u),1));
	*/	

	float temp = (pos.x + 1)/2;
	pos.y += texture(tex1,vec2(temp*texdim1.x,0)).r * 2;
	//pos.z += texture(tex1,vec2(temp*texdim1.x,0)).r * 2;

    //Convert vertex vectors to view positions
	gl_Position = modelViewProjectionMatrix * pos;
	jit_out.color = color;

    //Determine the reference coordinates of the texture for each vertex (by creating them in the vertex shader, linear completion can be applied).
    jit_out.texcoord = vec2(textureMatrix0*vec4(texcoord, 0, 1.));
}
]]>
		</program>
<!--		
<program name="gp" type="geometry">
<![CDATA[
#version 410

layout (triangles) in;

layout (triangle_strip, max_vertices=15) out;

in jit_PerVertex {
	vec4 color;	
    vec2 texcoord;	
} jit_in[];

out jit_PerVertex {
	vec4 color;	
    vec2 texcoord;	
} jit_out;

float offset = 10.0;
vec4 offsets[4] = vec4[](vec4(-offset, offset, 0, 0), vec4(offset, offset, 0, 0), vec4(-offset, -offset, 0, 0), vec4(offset, -offset, 0, 0));

void main() {

    // Drawing of original primitives
	for(int i = 0; i < 3; i++) {
		jit_out.color = jit_in[i].color;
		jit_out.texcoord = jit_in[i].texcoord;
		gl_Position = gl_in[i].gl_Position;
		EmitVertex();
	}
	EndPrimitive();

    // Drawing of primitives to be replicated
	for(int j = 0; j < 4; j++) {
		for(int i = 0; i < 3; i++) {
			jit_out.color = jit_in[i].color;
			jit_out.texcoord = jit_in[i].texcoord;
			gl_Position = gl_in[i].gl_Position + offsets[j];
			EmitVertex();
		}
		EndPrimitive();
	}
}


]]>
</program>
-->
		<program name="fp" type="fragment">
<![CDATA[
#version 410

//Textures are placed in GPU memory.
uniform sampler2DRect tex0;

//in : Receiving structure from previous stage.
in jit_PerVertex {
	vec4 color;
    vec2 texcoord;	
} jit_in;

//Color output of this pixel for video output
out vec4 fragColor;

void main() {

    //Refer to the appropriate texture position for each pixel
	fragColor = texture(tex0,jit_in.texcoord.xy);
}	
]]>
		</program>
	</language>
</jittershader>

jit.gl.shader 向けのGLSLシェーダー(JXS)サンプル #jitter #glsl #maxmsp

jit.gl.shaderのデフォルトサンプルを開くと、なぜかテクスチャマッピングのコードが1mmも書いてなくて、ジオメトリーシェーダーが書いてあるだけで非常に参考にしにくかったので調べてみました。

  • テクスチャマッピング
  • ジオメトリシェーダーによるモデルの複製
  • 英語コメントいっぱい書いた(日本語は書いた瞬間にMaxが落ちる・・・)

JXSの書き方はMax特有のものですが、GLSL(vertex / geometory / fragment)のシェーダー自体は他の環境でも参考になると思うのでどうぞ。

サンプル

<jittershader name="fill-flat-quads">
	<description>Default Shader </description>
    <!-- JXS Document https://docs.cycling74.com/max8/tutorials/jitterchapter99_appendixc -->
    <!-- Vertex -->
	<param name="position" type="vec3" state="POSITION" />
	<param name="modelViewProjectionMatrix" type="mat4" state="MODELVIEW_PROJECTION_MATRIX" />
	<param name="color" type="vec4" state="COLOR" />
    <!-- Texture -->
	<param name="tex0" type="int" default="0" state="TEXTURE0" />
    <param name="textureMatrix0" type="mat4" state="TEXTURE0_MATRIX" />
    <param name="texcoord" type="vec2" state="TEXCOORD" />
    <param name="texdim" type="vec2" state="TEXDIM0" />
	<language name="glsl" version="4.1">
		<bind param="position" program="vp" />
		<bind param="modelViewProjectionMatrix" program="vp" />
		<bind param="textureMatrix0" program="vp" />
		<bind param="color" program="vp" />
		<bind param="texcoord" program="vp" />
        <bind param="tex0" program="fp" />
        <bind param="texdim" program="fp" />
		<program name="vp" type="vertex">
<![CDATA[
#version 410

//in : Each instance can have different values
in vec2 texcoord;
in vec3 position;
in vec4 color;

//uniform : Keeps the same value for all instances
uniform mat4 modelViewProjectionMatrix;
uniform mat4 textureMatrix0;

//out : Structural input to next shader stage
out jit_PerVertex {
	vec4 color;
    vec2 texcoord;	
} jit_out;

void main() {	
    //Convert vertex vectors to view positions
	gl_Position = modelViewProjectionMatrix * vec4(position, 1.);
	jit_out.color = color;

    //Determine the reference coordinates of the texture for each vertex (by creating them in the vertex shader, linear completion can be applied).
    jit_out.texcoord = vec2(textureMatrix0*vec4(texcoord, 0, 1.));
}
]]>
		</program>
		<program name="gp" type="geometry">
<![CDATA[
#version 410

layout (triangles) in;

layout (triangle_strip, max_vertices=15) out;

in jit_PerVertex {
	vec4 color;	
    vec2 texcoord;	
} jit_in[];

out jit_PerVertex {
	vec4 color;	
    vec2 texcoord;	
} jit_out;

float offset = 8.0;
vec4 offsets[4] = vec4[](vec4(-offset, offset, 0, 0), vec4(offset, offset, 0, 0), vec4(-offset, -offset, 0, 0), vec4(offset, -offset, 0, 0));

void main() {

    // Drawing of original primitives
	for(int i = 0; i < 3; i++) {
		jit_out.color = jit_in[i].color;
		jit_out.texcoord = jit_in[i].texcoord;
		gl_Position = gl_in[i].gl_Position;
		EmitVertex();
	}
	EndPrimitive();

    // Drawing of primitives to be replicated
	for(int j = 0; j < 4; j++) {
		for(int i = 0; i < 3; i++) {
			jit_out.color = jit_in[i].color;
			jit_out.texcoord = jit_in[i].texcoord;
			gl_Position = gl_in[i].gl_Position + offsets[j];
			EmitVertex();
		}
		EndPrimitive();
	}
}


]]>
		</program>
		<program name="fp" type="fragment">
<![CDATA[
#version 410

//Textures are placed in GPU memory.
uniform sampler2DRect tex0;

//in : Receiving structure from previous stage.
in jit_PerVertex {
	vec4 color;
    vec2 texcoord;	
} jit_in;

//Color output of this pixel for video output
out vec4 fragColor;

void main() {

    //Refer to the appropriate texture position for each pixel
	fragColor = texture(tex0,jit_in.texcoord.xy);
}	
]]>
		</program>
	</language>
</jittershader>

参考

Appendix C: The JXS File Format