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

  • 38
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

いやぁ、今年の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 さんです!