Help us understand the problem. What is going on with this article?

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

More than 5 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 さんです!

yuichiroharai
主にWebGL/Canvasとかでフロントエンドやってます。最近はWebStorm+gulp+TypeScript+SCSS+EJS+BrowserSyncの組み合わせがお気に入りです。他にもWebGL, Flash, Processing, Max, openFrameworksなどでクリエイティブコーディングも。http://www.yuichiroharai.com/wgl/
http://www.yuichiroharai.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away