Edited at
WebGLDay 20

glslifyでGLSLをモジュール化しよう

More than 3 years have passed since last update.

いやぁ、今年のWebGL Advent Calendarはレベルが高いですね・・・

全然ついていけないので、ちょっと箸休めにシェーダーのビルド周りのお話をしてみます。

最近、Deja vu | KAMRAGitレポジトリが全面公開されてたので、お宝探しに中を覗いていたのですが、その中でglslifyが使われていまして、コレは気になってたヤツ!だったのでちょっと色々試してみました。

一言で言えば、glslにrequireの仕組みを導入して、主に関数をモジュール化して別ファイルに分ける事が出来るやつですね。

node.jsで動きます。


とりあえずコマンドラインで使ってみる


glslifyのインストール

とりあえず、グローバルインストールで説明します。

npm install -g glslify


glslify コマンドを実行

glslify input.glsl -o output.glsl

これで、カレントディレクトリのinput.glslからoutput.glslが書き出されます。

これだけだとサッパリなので、実際にモジュールを使ったinput.glslを書いて説明していきます。


npmで公開されているモジュールを使ってみる

まずは、npmで公開されているモジュールを使ってみます。

サンプルとして擬似乱数を生成する関数のglsl-randomを用います。

ちなみに、npmで公開されているモジュールを探すにはstackgl/packagesの「Shader Components」を参照すると良いと思います。

割とよく使いそうなヤツが揃っていますね。


モジュールのインストール

どこかのプロジェクトフォルダ内にglsl-randomをローカルインストールします。

npm install glsl-random


モジュールのrequire

フラグメントシェーダーで擬似乱数によるモノクロのノイズ模様を描画するサンプルです。


input.glsl

#pragma glslify: random = require(glsl-random)


uniform vec2 resolution; // 描画サイズ

void main(){
gl_FragColor = vec4(vec3(random(gl_FragCoord.xy / resolution.xy)), 1.0 );
}


#pragma glslify: がglslifyに対する処理を記述する部分です。

ここでは random という名前でglsl-randomをrequireしています。

そして、そのmain関数内でそのrandomを使って擬似乱数を生成して gl_FragColor に色として渡します。


glslifyした出力結果

先ほどのコマンドでinput.glslからoutput.glslを書き出すとこんな結果になります。


output.glsl

#define GLSLIFY 1

highp float random(vec2 co)
{
highp float a = 12.9898;
highp float b = 78.233;
highp float c = 43758.5453;
highp float dt= dot(co.xy ,vec2(a,b));
highp float sn= mod(dt,3.14);
return fract(sin(sn) * c);
}

uniform vec2 resolution; // 描画サイズ

void main(){
gl_FragColor = vec4(vec3(random(gl_FragCoord.xy / resolution.xy)), 1.0 );
}


見事にrequireした部分が実際の関数に置き換わっていますね!

#define GLSLFY 1

が先頭に自動追加されてますが、恐らくglslifyが何かに使っているのだと思います(適当)。


自前のモジュールを使ってみる

先ほどは公開されているモジュールを使いましたが、今度は自分でモジュールを作ってみます。


モジュールの作成

以下は、思いつきで作ったサンプルです。

与えられた-1.0から1.0の値を0.0から1.0に変換します(念のためclampで切り詰める処理も)。


zero-one.glsl

float zero_one(float t) {

return clamp(t, -1.0, 1.0) * 0.5 + 0.5;
}

#pragma glslify: export(zero_one)


関数を作って、 #pragma glslify: でexportをしておけば大丈夫です。

これを、zero-one.glslという名前でinput.glslと同じフォルダに置いておきます。


モジュールのrequire

続いて、このzero-one.glslをinput.glslにてrequireしてみます。


input.glsl

#pragma glslify: zero_one = require("./test.glsl")


uniform vec2 resolution; // 描画サイズ

void main(){
gl_FragColor = vec4(vec3(zero_one(sin(gl_FragCoord.x / resolution.x))), 1.0 );
}


先ほどはnpmにて公開されているモジュールを使ったので、require(モジュール名)だけでいけましたが、

今回はnpmにない手元のファイルを使うので相対パスで指定します。

クォートで囲まなかったり、./がないとエラーになったので注意が必要です。


glslifyした出力結果

これをコマンドラインでglslifyすると、こんな結果になりました。


output.glsl

#define GLSLIFY 1

float zero_one_1540259130(float t) {
return clamp(t, -1.0, 1.0) * 0.5 + 0.5;
}

uniform vec2 resolution; // 描画サイズ

void main(){
gl_FragColor = vec4(vec3(zero_one_1540259130(sin(gl_FragCoord.x / resolution.x))), 1.0 );
}


基本的には公開モジュールと同じような結果になりましたが、1点だけ異なるのが関数名に謎の接尾語が付いている点です。

名前の重複を防ぐための措置でしょうか・・・?

何はともあれ、こんな感じで自分が作った関数を別ファイルに分割出来るので、再利用が楽になりますね。


glslify-import

glslifyは基本的に関数をexportしてrequireするものですが、glslify-importを使うと単に別ファイルのGLSL構文をそのまま持ってこれます。

要はテンプレート機能ですね。


glslify-importのインストール

プロジェクトフォルダ内にglslify-importをローカルインストールします。

npm install glslify-import


importさせるファイル

試しによく使うattribute変数とvarying変数の宣言をまとめてみました。

template.glslというファイル名でinput.glslと同じフォルダにおいておきます。


template.glsl

// よく使うattribute変数

attribute vec3 position;
attribute vec4 color;
attribute vec2 uv;
attribute vec3 normal;

// よく使うvarying変数
varying vec3 vPosition;
varying vec4 vColor;
varying vec2 vUv;
varying vec3 vNormal;



importするファイル

後はコレを #pragma glslify: を使ってimportさせるだけです。


input.glsl

#pragma glslify: import("./template.glsl")


uniform mat4 mvpMatrix;

void main(){
vColor = color;
vNormal = normal;
vUv = uv;
gl_Position = mvpMatrix * vec4(position, 1.0);
}



importした出力結果

ちゃんとテンプレートしてますね。


output.glsl

#define GLSLIFY 1

attribute vec3 position;
attribute vec4 color;
attribute vec2 uv;
attribute vec3 normal;

varying vec4 vColor;
varying vec2 vUv;
varying vec3 vNormal;

uniform mat4 mvpMatrix;

void main(){
vColor = color;
vNormal = normal;
vUv = uv;
gl_Position = mvpMatrix * vec4(position, 1.0);
}



【おまけ】gulpプラグインもどきを作ってみた

glslifyはBrowserifyとwebpackをサポートしているみたいですが、残念ながら自分はこれらを使っていません。

で、gulpで使えたら良いなぁと思って探してみたのですが、どうやら無さそうでした・・・

というわけで、見よう見まねでgulpプラグインもどきを作ってみたので、おまけで紹介しておきます。


gulp-glslify.js

// gulpプラグインで必要なモジュール

var through2 = require('through2');
var gulpUtil = require('gulp-util');
var PluginError = gulpUtil.PluginError;

// glslifyとそのプラグイン的なの
var glslify = require('glslify');
var glslifyImport = require('glslify-import');
var glslifyHex = require('glslify-hex');

var PLUGIN_NAME = 'gulp-glslify';

function gulpGlslify(options) {

if (!options) options = {};
// 文字列を扱う前提で inline オプションは true
options.inline = true;
if (!options.transform) options.transform = [];
// glslify-import と glslify-hex を追加
options.transform.unshift(glslifyImport, glslifyHex);

var stream = through2.obj(function (file, enc, cb) {

// 対象ファイルから相対パスにするため
options.basedir = file.history[0].split("/").slice(0, -1).join("/");

if (file.isNull()) {
cb(null, file);
return;
}

if (file.isStream()) {
cb(new PluginError(PLUGIN_NAME, 'Streaming not supported'));
return;
}

var _this = this;
if (file.isBuffer()) {
glslify.bundle(file.contents.toString(), options, function(error, glsl, files) {
if (error) {
cb(new PluginError(PLUGIN_NAME, error));
return;
} else {
file.contents = new Buffer(glsl);
_this.push(file);
return cb();
}
});
}

});

return stream;
}

module.exports = gulpGlslify;


先ほど説明した glslify-import に加えて glslify-hexを自動的に使うようにしてみました。

glslify-hex はGLSL中に #336699 があると vec3(0.2, 0.4, 0.6) みたいに自動変換してくれます。

とりあえず適当な場所に置いて、相対パスでrequireすれば使えるようになっています。

npmで公開するかどうかはちょっと考え中・・・(やったことないので不安)


gulpfile.js

var gulp = require("gulp");

var glslify = require("./gulp-glslify");

gulp.task("gulp-glslify", null, function() {
gulp.src("./src/glsl/**/*.{vert,flag}")
.pipe(glslify())
.pipe(gulp.dest("./dest/"));
});


こんな感じでいつも通り使えます。

事前に glslify, glslify-import, glslify-hexは必ずインストールしておいてください。

また、through2 と gulp-util は他のgulpプラグインが使っていれば既に入ってる可能性がありますが、無ければこれらもインストールする必要があります。

また、through2 と gulp-utilも必ず先にインストールしておいてください。


それでは、良きGLSLライフを!

明日はWebGL総本山@doxas さんです!