How to convert hydra-synth to MSL

hydra-synthというビデオシンセは独自の記法を使ってGLSLを生成することできます。

osc().out(o0)

というコードは以下のようにGLSLに展開されます。

precision highp float;
	
uniform float frequency1; 	// 60
uniform float sync2; 		// 0.1
uniform float offset3; 		// 0
uniform float time;
uniform vec2 resolution;
varying vec2 uv;
				
vec4 osc(vec2 _st, float freq, float sync, float offset){
    vec2 st = _st;
    float r = sin((st.x-offset/freq+time*sync)*freq)*0.5  + 0.5;
    float g = sin((st.x+time*sync)*freq)*0.5 + 0.5;
    float b = sin((st.x+offset/freq+time*sync)*freq)*0.5  + 0.5;
    return vec4(r, g, b, 1.0);
}		

void main () {
    vec4 c = vec4(1, 0, 0, 1);
    vec2 st = gl_FragCoord.xy/resolution.xy;
    gl_FragColor = osc(st, frequency1, sync2, offset3);
}

特徴としてはフィードバック処理を簡単に記述することができます。

osc(10).diff(o0).modulatePixelate(o0,0.2).rotate(0.002).out(o0)

GLSLを書くよりも簡易な記述によって、GLSLによる映像をつくることができます。GLSLに変換している部分を少しいじることにより、MSLとしても書き出すことが可能です。今回はその話となります。

https://github.com/ojack/hydra-synth/blob/master/src/GeneratorFactory.js
にGLSLに変換している処理が書かれています。(変換自体も興味深いですが、今回は割愛します)

osc().out(o0)

out()を呼ぶタイミングで

Generator.prototype.out = function (_output) {
    //  console.log('UNIFORMS', this.uniforms, output)
    var output = _output || this.defaultOutput
    output.renderPasses(this.glsl(output))
}

GLSLに変換しています。
この引数_outputは
https://github.com/ojack/hydra-synth/blob/master/src/output.js
に定義してありますが、browserで動くようにつくられており、MSLに変換する場合には都合が悪く、以下のようにNode.jsで動くように変更します。

var Output = function(uid) {
    global["o"+uid] = {
        index:uid,
	uniforms:{},
	getTexture:function() {},
	renderPasses:function(glsl) {			
    	    require("fs").writeFileSync("./assets/s"+this.index+".metal",glsl[0].frag);
	    require("fs").writeFileSync("./assets/u"+this.index+".json",stringifyWithFunctions(glsl[0].uniforms));
	}
    };
} 

Generator.prototype.outでrenderPassesが呼ばれたときに.metalとuniformをJSONとしてファイルに書き出します。
空のgetTexture関数がいるのはGeneratorFactory.jsのformatArguments()で
typedArg.value.getTextureによる分岐があり、その対策のためです。

hydra-synthの関数は、GLSLに書かれている関数に対応しており
https://github.com/ojack/hydra-synth/blob/master/src/composable-glsl-functions.js
に定義してあります。
GLSLではグローバルにuniformの値がありますが

uniform float time;
uniform vec2 resolution;

MSLの場合は関数内で初期化するため

fragment float4 fragmentShader(VertInOut inFrag[[stage_in]],constant FragmentShaderArguments &args[[buffer(0)]]) {
		
    float time = args.time[0];
    float2 resolution = args.resolution[0];

timeとresolutionを使っている関数は引数を追加し

vec4 osc(vec2 _st, float freq, float sync, float offset, float time) {

inputも引数の値の設定をする必要があります。

{
    name: 'time',
    type: 'float',
    default: 'time'
}

次にGeneratorFactory.jsの不要な部分を削っていき、GLSLのテンプレートの部分をMSLに向けに書き換えていきます。
https://github.com/mizt/MSL-Hydra-Synth/blob/master/src/MSLGeneratorFactory.js
GLSLとMSLで組み込み関数が違っている部分があり、 modとatanを置き換えています。

#define mod fmod
#define atan 6.28318530718-atan2

composable-glsl-functions.jsの_rgbToHsvの部分で
http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
のhsvへの変換処理が使われていますが、ゼロ除算対策の以下の部分がMSLだとおかしな結果となります。

float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);

画像1

正しい表示となるようにコードを修正します。

return vec3(abs(q.z+((d==0.0)?1.0:((q.w-q.y)/(6.0*d)))), (q.x==0.0)?3.402823466e+38:(d/q.x), q.x);

画像2

https://hydra-editor-v1.glitch.me/?sketch_id=example_6&code=JTJGJTJGJTIwYnklMjBEJUMzJUE5Ym9yYSUyMEZhbGxlaXJvcyUyMEdvbnphbGVzJTBBJTJGJTJGJTIwaHR0cHMlM0ElMkYlMkZ3d3cuZ29uemFsZXNkZWJvcmEuY29tJTJGJTBBJTBBb3NjKDUpLmFkZChub2lzZSg1JTJDJTIwMikpLmNvbG9yKDAlMkMlMjAwJTJDJTIwMykuY29sb3JhbWEoMC40KS5vdXQoKSUwQSUwQSUwQSUwQQ==

またhydra-synthの記述では変数に関数を渡すことができます。

.modulate(o0, () => mouse.x * 0.0003)

Node.jsで書き出したJSONのuniformの値は文字列化された関数になっているので

"amount_17": "(() => mouse.x * 0.0003)",

JavaScriptCore.frameworkを使いシェーダの値に関数の結果を渡せるようにします。

JSContext *context = [JSContext new];
[context evaluateScript:[NSString stringWithFormat:@"mouse={x:%f,y:%f};",mouseX,mouseY]]; 
[[context evaluateScript:[NSString stringWithFormat:@"(%@)();",@"(() => mouse.x * 0.0003)"] toDouble];

https://github.com/mizt/MSL-Hydra-Synth-Player/blob/master/libs/HydraMetalLayer.h

とりあえずこれで書き出した.metalを.metallibにビルドして読み込めばMetalでhydra-synthが動かせます。


この記事が気に入ったらサポートをしてみませんか?