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

jit.gl.model / jit.gl.shader を使って初音ミク(はちゅねみく)さんをメッシュに分解するエフェクト #jitter #maxmsp #glsl

メッシュ分解

スクリーンショット 2014-08-08 00.18.16ジオメトリシェーダを使ったエフェクトのテストです。メッシュが法線ベクトルの方向に飛んでいきます。Tell Your Worldで、もっとカッコよく使われていましたね。

Before はちゅねみくさん

スクリーンショット 2014-08-07 23.35.04

After はちゅねみくさん

スクリーンショット 2014-08-07 23.35.23モデルは、note.xさんで配布されているものを利用させて頂いております。実は、この処理は無理にシェーダでやらなくても、jit.genとjit.gl.mesh等々を使えば、CPU上でできたりします。

Before ちび初音ミク

スクリーンショット 2014-08-07 23.34.23

After ちび初音ミク

スクリーンショット 2014-08-07 23.34.26あれっデブミクさんになってしまった・・・メッシュが分解されて飛ぶ処理だと思っていたのですが、分解されてない?!処理を見なおさなければ。モデルは、モデモデさんで配布されているのを使わせて頂きました!

ジオメトリシェーダ

スクリーンショット 2014-08-07 23.41.14この画像では、元のはちゅねみくさんのモデルデータを保持しつつ、分解したトライアングルも描画しています。ジオメトリシェーダを使ってGPU上でメッシュの数を2倍にして表示するとできます。この処理はジオメトリシェーダらしいエフェクトと言えるかもしれません。

インタラクティブに音に反応


せっかくMax上で動作しているので、音に合わせてインタラクティブに変化させてみました。

JSX / GLSLコード

[html]
<param name="size" type="float" default="0." />
<language name="glsl" version="1.2">
<bind param="tex0" program="fp" />
<bind param="size" program="gp" />
[/html]
Maxのためのパラメーター、バインドの指定です。size変数でメッシュの位置を制御しています。tex0は、jit.gl.textureから渡されるテクスチャです。jit.gl.modelにjit.gl.shaderを適応した場合に、テクスチャをjit.gl.textureで別途設定してあげる必要があります。

Vertex Shader

[c]
#version 120

varying vec2 VTexCoord0;
varying vec3 VEyeNormal;
varying vec3 VEyeVertex;

void main()
{
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
VTexCoord0 = vec2(gl_TextureMatrix[0]*gl_MultiTexCoord0);
VEyeNormal = vec3(gl_ModelViewMatrix * vec4(gl_Normal, 0.0));
VEyeVertex = vec3(gl_ModelViewMatrix * gl_Vertex);
}
[/c]
ほとんどジオメトリシェーダのための下準備しかしてないですね。

Geometry Shader

[c]
#version 120
#extension GL_EXT_geometry_shader4 : enable

uniform float size;

//VARYING
varying in vec2 VTexCoord0[3]; //[3] because this makes a triangle
varying in vec3 VEyeNormal[3];
varying in vec3 VEyeVertex[3];

//GLOBALFUNCTION
void main()
{
int i;
vec3 newVertex;

/*
//Pass through the original vertex
for(i=0; i<gl_VerticesIn; i++)
{
gl_Position = gl_PositionIn[i];
gl_TexCoord[0].st = VTexCoord0[i];
EmitVertex();
}
EndPrimitive();
*/

//Push the vertex out a little using the normal
for(i=0; i<gl_VerticesIn; i++)
{
newVertex = VEyeNormal[i] * size + VEyeVertex[i];
gl_Position = gl_ProjectionMatrix * vec4(newVertex, 1.0);
gl_TexCoord[0].st = VTexCoord0[i];
EmitVertex();
}
EndPrimitive();
}
[/c]
三角形プリミティブを受け取って、各頂点を法線方向にずらしてプリミティブを描画しています。なのでメッシュに分割するエフェクトじゃなかった(笑)ネギミクさんは法線ベクトルの値がプリミティブに垂直だから分解されているようです。コメントアウトされている所では、元のプリミティブを描画できます。

Fragment Shader

[c]
#version 120

uniform sampler2DRect tex0;

void main()
{
vec4 texel = texture2DRect(tex0, gl_TexCoord[0].st);
gl_FragColor = texel;
}
[/c]
テクスチャを引っ張ってきて色を付けているだけです。

おまけ ジオメトリシェーダ エフェクト その2

スクリーンショット 2014-08-07 17.20.20
ジオメトリシェーダにtriangleで入ってきたプリミティブをline_stripに変換するとこんな表現も簡単にできます。

jit.gl.shader / GLSL でポイントスプライト #maxmsp #jitter #glsl

ジオメトリシェーダ

ジオメトリシェーダは、レンダリングパイプラインにおいて、バーテックスシェーダの後に配置されており、GPU上でプリミティブの変換や増減が可能なシェーダだそうです。

スクリーンショット 2014-08-05 19.29.40

ポイントスプライト

ポイントスプライトという手法は、CPUからpointsで与えた点群をジオメトリシェーダを用いてtriangleなどに変換しテクスチャを貼ることで、GPUの計算能力を活かしてテクスチャ付きのパーティクルをつくる方法です。

スクリーンショット 2014-08-05 18.46.52

実装してみた

今回はGLSL1.2で使えるジオメトリシェーダ(Geometry Shader)を用いて、ポイントスプライトを実装してみました。OpenGL 4.0 シェーディング言語 -実例で覚えるGLSLプログラミング-に掲載されているコードを参考にしています。ジオメトリシェーダは、Maxのjit.gl.shaderでもちゃんと使えるのですが、ググっても解説がないためつまずくポイントも多かったため、少し詳しく紹介します。

スクリーンショット 2014-08-05 18.46.57

パラメーターの宣言

[html]
<param name="size" type="vec2" default="1. 1." />
[/html]
ジオメトリシェーダに限らない事ですが、Maxから指定するパラメーターは、param要素として指定します。初期値も与えられますがコンマで区切っては駄目みたいなので注意です。

パラメーターのバインド

[html]
<language name="glsl" version="1.2">
<bind param="size" program="gp" />
<bind param="tex0" program="fp" />
<program>
</program>
</language>
[/html]

宣言したパラメーターが、どのプログラマブルシェーダで利用されるかをprogram属性にbindします。例:sizeをgeometry shaderで利用する。例外ですがテクスチャをシェーダで扱いたい場合には、Maxの場合tex0というパラメーターが予約されているようで、宣言していなくても使えました。マルチテクスチャをやる場合については未調査です。

サンプルコードで使われているプログラム名称

  • “gp” : geometry shader
  • “fp” : fragment shader
  • “vp” : vertex shader

ジオメトリシェーダの宣言

[html]
<program name="gp" vertices_out="4" input_type="points" output_type="triangle_strip" type="geometry">
<![CDATA[
/*glslのプログラムを記述*/
]]>
</program>
[/html]
program要素を宣言します。vertices_out / input_type / output_typeが合ってないと実行できません。今回は、”points”を”triangle_strip”に変換し”4”つのvertexが出力されるという設定になっています。

ジオメトリシェーダのコード

[c]
#version 120
#extension GL_EXT_geometry_shader4 : enable

uniform vec2 size;
varying in vec2 texdim0[1];

void main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_PositionIn[0];
gl_Position.x -= size.x/2;
gl_Position.y += size.y/2;
gl_TexCoord[0].st = vec2(0,0) * texdim0[0];
EmitVertex();
gl_Position.y -= size.y;
gl_TexCoord[0].st = vec2(0,1) * texdim0[0];
EmitVertex();
gl_Position.x += size.x;
gl_Position.y += size.y;
gl_TexCoord[0].st = vec2(1,0) * texdim0[0];
EmitVertex();
gl_Position.y -= size.y;
gl_TexCoord[0].st = vec2(1,1) * texdim0[0];
EmitVertex();
}
[/c]
#extension GL_EXT_geometry_shader4 : enable でジオメトリシェーダを有効化しています。main関数では、Maxから受け取ったsizeを使ってポイント(頂点)を、四つ頂点へと増加させています。特殊な書き方ですがEmitVertex()を呼ぶことでgl_texCoord[0]に設定されている頂点を出力しています。gl_TexCoordには、テクスチャの参照位置を入れていますね。Maxから受け取るテクスチャの座標が正規化いないので、texdim0[0]を掛けています。

JXS TIPS

  • 日本語でコメントが書かれているとMaxが即落ちる
  • ポイントスプライトで使うテクスチャのバインド先は、jit.gl.shaerではなく、jit.gl のオブジェクト
  • jit.gl.mesh: unbinding vertex array: GL Error: Invalid operation

    というエラーが出た場合は、jit.gl.meshのdraw_modeがpointsになっていない可能性があります。

続きを読む jit.gl.shader / GLSL でポイントスプライト #maxmsp #jitter #glsl