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

GLSLのminifierを比較してみた

これはKLab Engineer Advent Calendar 2019の17日目の記事です。

背景

OpenGLやWebGLでは、Direct3Dのようにシェーダーのプリコンパイルをサポートしておらず1、GLSLをランタイム時にコンパイルするしかありません。
そのため、GLSLのファイルサイズがそのままダウンロードサイズに影響してしまいます。

Unity等のゲームエンジンでは、HLSL(ShaderLab)からGLSL等にトランスパイルする際にコードを圧縮2できますが、
ゲームエンジンを使わない場合でもminifier(圧縮ツール)を利用することで同様に圧縮することでダウンロードサイズを削減できます。

今回、以下3つのminifierに対して圧縮率を比較検証しました。

比較結果

ツール名 GLSL ES 1.0のコードの圧縮率 GLSL ES 3.0のコードの圧縮率
Shader Minifier 41.6 % 41.7 %
glsl-optimizer 118.9 % 118.9 %
glsl-minifier 53.7 % 未対応

最も圧縮率が高かったのはShader Minifierでした。

なお、glsl-optimizerでは適用後の方がファイルサイズが大きくなってしまいました。
これは、glsl-optimizerが圧縮というよりも実行速度の向上を目的とした最適化ツールだったようで、
比較対象として不適切だったと思われます。

比較方法

筆者制作のレイマーチング作品『Shinto Shrine Archway』のフラグメントシェーダーを各minifierで圧縮し、圧縮率(圧縮後のファイルサイズ / 圧縮前のファイルサイズ)を比較しました。

2つバージョンのGLSLで検証しました。

  1. GLSL ES 1.0(OpenGL ES 2.0 / WebGL 1.0)
  2. GLSL ES 3.0(OpenGL ES 3.0 / WebGL 2.0)

なお、圧縮対象のフラグメントシェーダーのファイルサイズは、GLSL ES 1.0の場合は7471 byte、GLSL ES 3.0の場合は7502byteです。

圧縮前のコード(GLSL ES 1.0)

archway_es1.glsl(7471 byte)
archway_es1.glsl
// Ported from my shadertoy work: https://www.shadertoy.com/view/XttSWN

// BEGIN: shadertoy porting template
// https://gam0022.net/blog/2019/03/04/porting-from-shadertoy-to-glslsandbox/
// precision highp float;// shader_minifier のエラー回避のためにコメントアウト

uniform vec2 resolution;
uniform float time;
uniform vec2 mouse;

#define iResolution resolution
#define iTime time
#define iMouse mouse

void mainImage(out vec4 fragColor, in vec2 fragCoord);

void main(void) {
    vec4 col;
    mainImage(col, gl_FragCoord.xy);
    gl_FragColor = col;
}
// END: shadertoy porting template


// consts
const float EPS = 1e-4;
const float OFFSET = EPS * 10.0;
const float PI = 3.14159;
const float INF = 1e+10;

const vec3 lightDir = vec3( -0.48666426339228763, 0.8111071056538127, -0.3244428422615251 );
const vec3 backgroundColor = vec3( 0.0 );
const vec3 gateColor = vec3( 1.0, 0.1, 0.1 );

const float totalTime = 75.0;

// globals
vec3 cPos, cDir;
float normalizedGlobalTime = 0.0;
//vec3 illuminationColor;

struct Intersect {
    bool isHit;

    vec3 position;
    float distance;
    vec3 normal;

    int material;
    vec3 color;
};

const int BASIC_MATERIAL = 0;
const int MIRROR_MATERIAL = 1;


// distance functions
vec3 opRep( vec3 p, float interval ) {
    return mod( p, interval ) - 0.5 * interval;
}

vec2 opRep( vec2 p, float interval ) {
    return mod( p, interval ) - 0.5 * interval;
}

float opRep( float x, float interval ) {
    return mod( x, interval ) - 0.5 * interval;
}

float sphereDist( vec3 p, vec3 c, float r ) {
    return length( p - c ) - r;
}

float sdCappedCylinder( vec3 p, vec2 h ) {
    vec2 d = abs(vec2(length(p.xz),p.y)) - h;
    return min(max(d.x,d.y),0.0) + length(max(d,0.0));
}

float udBox( vec3 p, vec3 b )
{
    return length(max(abs(p)-b,0.0));
}

float udFloor( vec3 p ){
    float t1 = 1.0;
    float t2 = 3.0;
    float d = -0.5;
    for( float i = 0.0; i < 3.0; i++ ) {
        float f = pow( 2.0, i );
        d += 0.1 / f * ( sin( f * t1 * p.x + t2 * iTime ) + sin( f * t1 * p.z + t2 * iTime ) );
    }
    return dot(p,vec3(0.0,1.0,0.0)) - d;
}

float dGate( vec3 p ) {
    p.y -= 1.3 * 0.5;

    float r = 0.05;
    float left  = sdCappedCylinder( p - vec3( -1.0, 0.0, 0.0 ),  vec2(r, 1.3));
    float right = sdCappedCylinder( p - vec3( 1.0,  0.0, 0.0 ),  vec2(r, 1.3));

    float ty = 0.02 * p.x * p.x;
    float tx = 0.5 * ( p.y - 1.3 );
    float katsura  = udBox( p - vec3( 0.0, 1.3 + ty, 0.0 ), vec3( 1.7 + tx, r * 2.0 + ty, r ) );

    float kan = udBox( p - vec3( 0.0, 0.7, 0.0 ), vec3( 1.3, r, r ) );
    float gakuduka = udBox( p - vec3( 0.0, 1.0, 0.0), vec3( r, 0.3, r ) );

    return min( min( left, right ), min( gakuduka, min( katsura, kan ) ) );
}

float dRepGate( vec3 p ) {
    if ( normalizedGlobalTime <= 0.5 ) {
        p.z = opRep( p.z, 1.0 + 20.0 * cos( PI * normalizedGlobalTime ) );
    } else {
        p.xz = opRep( p.xz, 10.0  );
    }
    return dGate( p );
}

float sceneDistance( vec3 p ) {
    return udFloor( p );
}


// color functions
vec3 hsv2rgb( vec3 c ) {

    vec4 K = vec4( 1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0 );
    vec3 p = abs( fract( c.xxx + K.xyz ) * 6.0 - K.www );
    return c.z * mix( K.xxx, clamp( p - K.xxx, 0.0, 1.0 ), c.y );

}

Intersect minIntersect( Intersect a, Intersect b ) {
    if ( a.distance < b.distance ) {
        return a;
    } else {
        return b;
    }
}

Intersect sceneIntersect( vec3 p ) {

    Intersect a;
    a.distance = udFloor( p );
    a.material = MIRROR_MATERIAL;
    // return minIntersect( a, b );
    return a;
}

vec3 getNormal( vec3 p ) {
    vec2 e = vec2( 1.0, -1.0 ) * 0.001;
    return normalize(
        e.xyy * sceneDistance( p + e.xyy ) + e.yyx * sceneDistance( p + e.yyx ) +
        e.yxy * sceneDistance( p + e.yxy ) + e.xxx * sceneDistance( p + e.xxx ) );
}

float getShadow( vec3 ro, vec3 rd ) {

    float h = 0.0;
    float c = 0.0;
    float r = 1.0;
    float shadowCoef = 0.5;

    for ( float t = 0.0; t < 50.0; t++ ) {

        h = sceneDistance( ro + rd * c );

        if ( h < EPS ) return shadowCoef;

        r = min( r, h * 16.0 / c );
        c += h;

    }

    return 1.0 - shadowCoef + r * shadowCoef;

}

Intersect getRayColor( vec3 origin, vec3 ray ) {

    // marching loop
    float dist, minDist, trueDepth;
    float depth = 0.0;
    vec3 p = origin;
    int count = 0;
    Intersect nearest;

    // first pass (water)
    for ( int i = 0; i < 120; i++ ){

        dist = sceneDistance( p );
        depth += dist;
        p = origin + depth * ray;

        count = i;
        if ( abs(dist) < EPS ) break;

    }

    if ( abs(dist) < EPS ) {

        nearest = sceneIntersect( p );
        nearest.position = p;
        nearest.normal = getNormal(p);
        nearest.distance = depth;
        float diffuse = clamp( dot( lightDir, nearest.normal ), 0.1, 1.0 );
        float specular = pow( clamp( dot( reflect( lightDir, nearest.normal ), ray ), 0.0, 1.0 ), 6.0 );
        //float shadow = getShadow( p + nearest.normal * OFFSET, lightDir );

        if ( nearest.material == BASIC_MATERIAL ) {
        } else if ( nearest.material == MIRROR_MATERIAL ) {
            nearest.color = vec3( 0.5, 0.7, 0.8 ) * diffuse + vec3( 1.0 ) * specular;
        }

        nearest.isHit = true;

    } else {

        nearest.color = backgroundColor;
        nearest.isHit = false;

    }
    nearest.color = clamp( nearest.color - 0.1 * nearest.distance, 0.0, 1.0 );

    // second pass (gates)
    p = origin;
    depth = 0.0;
    minDist = INF;
    for ( int i = 0; i < 20; i++ ){
        dist = dRepGate( p );
        minDist = min(dist, minDist);
        /*if ( dist < minDist ) {
            minDist = dist;
            trueDepth = depth;
        }*/
        depth += dist;
        p = origin + depth * ray;
        if ( i == 9 && normalizedGlobalTime <= 0.5 ) {
            break;
        }
    }

    if ( abs(dist) < EPS ) {
        nearest.color += gateColor;
    } else {
        nearest.color += gateColor * clamp( 0.05 / minDist, 0.0, 1.0 );
    }

    return nearest;

}

void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
    normalizedGlobalTime = mod( iTime / totalTime, 1.0 );

    // fragment position
    vec2 p = ( fragCoord.xy * 2.0 - iResolution.xy ) / min(  iResolution.x,  iResolution.y );

    // camera and ray
    if ( normalizedGlobalTime < 0.7 ) {
        cPos = vec3( 0.0, 0.6 + 0.4 * cos( iTime ), 3.0 * iTime );
        cDir = normalize( vec3( 0.0, -0.1, 1.0 ) );
    } else {
        cPos = vec3( 0.0, 0.6 + 0.4 * cos( iTime ) + 50.0 * ( normalizedGlobalTime - 0.7 ), 3.0 * iTime );
        cDir = normalize( vec3( 0.0, -0.1 - ( normalizedGlobalTime - 0.7 ), 1.0 ) );
    }
    vec3 cSide = normalize( cross( cDir, vec3( 0.0, 1.0 ,0.0 ) ) );
    vec3 cUp   = normalize( cross( cSide, cDir ) );
    float targetDepth = 1.3;
    vec3 ray = normalize( cSide * p.x + cUp * p.y + cDir * targetDepth );

    // Illumination Color
    // illuminationColor = hsv2rgb( vec3( iTime * 0.02 + 0.6, 1.0, 1.0 ) );

    vec3 color = vec3( 0.0 );
    float alpha = 1.0;
    Intersect nearest;

    for ( int i = 0; i < 3; i++ ) {

        nearest = getRayColor( cPos, ray );

        color += alpha * nearest.color;
        alpha *= 0.5;
        ray = normalize( reflect( ray, nearest.normal ) );
        cPos = nearest.position + nearest.normal * OFFSET;

        if ( !nearest.isHit || nearest.material != MIRROR_MATERIAL ) break;

    }

    fragColor = vec4(color, 1.0);

}

圧縮前のコード(GLSL ES 3.0)

archway_es3.glsl(7502 byte)
archway_es3.glsl
#version 300 es
// Ported from my shadertoy work: https://www.shadertoy.com/view/XttSWN

// BEGIN: shadertoy porting template
// https://gam0022.net/blog/2019/03/04/porting-from-shadertoy-to-glslsandbox/
// precision highp float;// shader_minifier のエラー回避のためにコメントアウト

uniform vec2 resolution;
uniform float time;
uniform vec2 mouse;

#define iResolution resolution
#define iTime time
#define iMouse mouse

void mainImage(out vec4 fragColor, in vec2 fragCoord);

out vec4 outColor;
void main(void) {
    vec4 col;
    mainImage(col, gl_FragCoord.xy);
    outColor = col;
}
// END: shadertoy porting template


// consts
const float EPS = 1e-4;
const float OFFSET = EPS * 10.0;
const float PI = 3.14159;
const float INF = 1e+10;

const vec3 lightDir = vec3( -0.48666426339228763, 0.8111071056538127, -0.3244428422615251 );
const vec3 backgroundColor = vec3( 0.0 );
const vec3 gateColor = vec3( 1.0, 0.1, 0.1 );

const float totalTime = 75.0;

// globals
vec3 cPos, cDir;
float normalizedGlobalTime = 0.0;
//vec3 illuminationColor;

struct Intersect {
    bool isHit;

    vec3 position;
    float distance;
    vec3 normal;

    int material;
    vec3 color;
};

const int BASIC_MATERIAL = 0;
const int MIRROR_MATERIAL = 1;


// distance functions
vec3 opRep( vec3 p, float interval ) {
    return mod( p, interval ) - 0.5 * interval;
}

vec2 opRep( vec2 p, float interval ) {
    return mod( p, interval ) - 0.5 * interval;
}

float opRep( float x, float interval ) {
    return mod( x, interval ) - 0.5 * interval;
}

float sphereDist( vec3 p, vec3 c, float r ) {
    return length( p - c ) - r;
}

float sdCappedCylinder( vec3 p, vec2 h ) {
    vec2 d = abs(vec2(length(p.xz),p.y)) - h;
    return min(max(d.x,d.y),0.0) + length(max(d,0.0));
}

float udBox( vec3 p, vec3 b )
{
    return length(max(abs(p)-b,0.0));
}

float udFloor( vec3 p ){
    float t1 = 1.0;
    float t2 = 3.0;
    float d = -0.5;
    for( float i = 0.0; i < 3.0; i++ ) {
        float f = pow( 2.0, i );
        d += 0.1 / f * ( sin( f * t1 * p.x + t2 * iTime ) + sin( f * t1 * p.z + t2 * iTime ) );
    }
    return dot(p,vec3(0.0,1.0,0.0)) - d;
}

float dGate( vec3 p ) {
    p.y -= 1.3 * 0.5;

    float r = 0.05;
    float left  = sdCappedCylinder( p - vec3( -1.0, 0.0, 0.0 ),  vec2(r, 1.3));
    float right = sdCappedCylinder( p - vec3( 1.0,  0.0, 0.0 ),  vec2(r, 1.3));

    float ty = 0.02 * p.x * p.x;
    float tx = 0.5 * ( p.y - 1.3 );
    float katsura  = udBox( p - vec3( 0.0, 1.3 + ty, 0.0 ), vec3( 1.7 + tx, r * 2.0 + ty, r ) );

    float kan = udBox( p - vec3( 0.0, 0.7, 0.0 ), vec3( 1.3, r, r ) );
    float gakuduka = udBox( p - vec3( 0.0, 1.0, 0.0), vec3( r, 0.3, r ) );

    return min( min( left, right ), min( gakuduka, min( katsura, kan ) ) );
}

float dRepGate( vec3 p ) {
    if ( normalizedGlobalTime <= 0.5 ) {
        p.z = opRep( p.z, 1.0 + 20.0 * cos( PI * normalizedGlobalTime ) );
    } else {
        p.xz = opRep( p.xz, 10.0  );
    }
    return dGate( p );
}

float sceneDistance( vec3 p ) {
    return udFloor( p );
}


// color functions
vec3 hsv2rgb( vec3 c ) {

    vec4 K = vec4( 1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0 );
    vec3 p = abs( fract( c.xxx + K.xyz ) * 6.0 - K.www );
    return c.z * mix( K.xxx, clamp( p - K.xxx, 0.0, 1.0 ), c.y );

}

Intersect minIntersect( Intersect a, Intersect b ) {
    if ( a.distance < b.distance ) {
        return a;
    } else {
        return b;
    }
}

Intersect sceneIntersect( vec3 p ) {

    Intersect a;
    a.distance = udFloor( p );
    a.material = MIRROR_MATERIAL;
    // return minIntersect( a, b );
    return a;
}

vec3 getNormal( vec3 p ) {
    vec2 e = vec2( 1.0, -1.0 ) * 0.001;
    return normalize(
        e.xyy * sceneDistance( p + e.xyy ) + e.yyx * sceneDistance( p + e.yyx ) +
        e.yxy * sceneDistance( p + e.yxy ) + e.xxx * sceneDistance( p + e.xxx ) );
}

float getShadow( vec3 ro, vec3 rd ) {

    float h = 0.0;
    float c = 0.0;
    float r = 1.0;
    float shadowCoef = 0.5;

    for ( float t = 0.0; t < 50.0; t++ ) {

        h = sceneDistance( ro + rd * c );

        if ( h < EPS ) return shadowCoef;

        r = min( r, h * 16.0 / c );
        c += h;

    }

    return 1.0 - shadowCoef + r * shadowCoef;

}

Intersect getRayColor( vec3 origin, vec3 ray ) {

    // marching loop
    float dist, minDist, trueDepth;
    float depth = 0.0;
    vec3 p = origin;
    int count = 0;
    Intersect nearest;

    // first pass (water)
    for ( int i = 0; i < 120; i++ ){

        dist = sceneDistance( p );
        depth += dist;
        p = origin + depth * ray;

        count = i;
        if ( abs(dist) < EPS ) break;

    }

    if ( abs(dist) < EPS ) {

        nearest = sceneIntersect( p );
        nearest.position = p;
        nearest.normal = getNormal(p);
        nearest.distance = depth;
        float diffuse = clamp( dot( lightDir, nearest.normal ), 0.1, 1.0 );
        float specular = pow( clamp( dot( reflect( lightDir, nearest.normal ), ray ), 0.0, 1.0 ), 6.0 );
        //float shadow = getShadow( p + nearest.normal * OFFSET, lightDir );

        if ( nearest.material == BASIC_MATERIAL ) {
        } else if ( nearest.material == MIRROR_MATERIAL ) {
            nearest.color = vec3( 0.5, 0.7, 0.8 ) * diffuse + vec3( 1.0 ) * specular;
        }

        nearest.isHit = true;

    } else {

        nearest.color = backgroundColor;
        nearest.isHit = false;

    }
    nearest.color = clamp( nearest.color - 0.1 * nearest.distance, 0.0, 1.0 );

    // second pass (gates)
    p = origin;
    depth = 0.0;
    minDist = INF;
    for ( int i = 0; i < 20; i++ ){
        dist = dRepGate( p );
        minDist = min(dist, minDist);
        /*if ( dist < minDist ) {
            minDist = dist;
            trueDepth = depth;
        }*/
        depth += dist;
        p = origin + depth * ray;
        if ( i == 9 && normalizedGlobalTime <= 0.5 ) {
            break;
        }
    }

    if ( abs(dist) < EPS ) {
        nearest.color += gateColor;
    } else {
        nearest.color += gateColor * clamp( 0.05 / minDist, 0.0, 1.0 );
    }

    return nearest;

}

void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
    normalizedGlobalTime = mod( iTime / totalTime, 1.0 );

    // fragment position
    vec2 p = ( fragCoord.xy * 2.0 - iResolution.xy ) / min(  iResolution.x,  iResolution.y );

    // camera and ray
    if ( normalizedGlobalTime < 0.7 ) {
        cPos = vec3( 0.0, 0.6 + 0.4 * cos( iTime ), 3.0 * iTime );
        cDir = normalize( vec3( 0.0, -0.1, 1.0 ) );
    } else {
        cPos = vec3( 0.0, 0.6 + 0.4 * cos( iTime ) + 50.0 * ( normalizedGlobalTime - 0.7 ), 3.0 * iTime );
        cDir = normalize( vec3( 0.0, -0.1 - ( normalizedGlobalTime - 0.7 ), 1.0 ) );
    }
    vec3 cSide = normalize( cross( cDir, vec3( 0.0, 1.0 ,0.0 ) ) );
    vec3 cUp   = normalize( cross( cSide, cDir ) );
    float targetDepth = 1.3;
    vec3 ray = normalize( cSide * p.x + cUp * p.y + cDir * targetDepth );

    // Illumination Color
    // illuminationColor = hsv2rgb( vec3( iTime * 0.02 + 0.6, 1.0, 1.0 ) );

    vec3 color = vec3( 0.0 );
    float alpha = 1.0;
    Intersect nearest;

    for ( int i = 0; i < 3; i++ ) {

        nearest = getRayColor( cPos, ray );

        color += alpha * nearest.color;
        alpha *= 0.5;
        ray = normalize( reflect( ray, nearest.normal ) );
        cPos = nearest.position + nearest.normal * OFFSET;

        if ( !nearest.isHit || nearest.material != MIRROR_MATERIAL ) break;

    }

    fragColor = vec4(color, 1.0);

}

検証環境

  • Mac Book Air Early 2015
  • macOS Catalina

検証の詳細

以下、各minifierの実行方法と結果を紹介します。

Shader Minifier

有名なデモ制作チーム、Ctrl-Alt-Testによるツールです。

F#で実装されており、同じバイナリが Windows OS でも macOS でも動作します。

ビルド済みのバイナリはGitHubのreleasesからダウンロードできます。

使い方

mono shader_minifier.exe --help
    -o <string>: Set the output filename (default is shader_code.h)
    -v: Verbose, display additional information
    --hlsl: Use HLSL (default is GLSL)
    --format <string>: Can be: c-variables (default), c-array, js, nasm, or none
    --field-names <string>: Choose the field names for vectors: 'rgba', 'xyzw', or 'stpq'
    --preserve-externals: Do not rename external values (e.g. uniform)
    --preserve-all-globals: Do not rename functions and global variables
    --no-renaming: Do not rename anything
    --no-renaming-list <string>: Comma-separated list of functions to preserve
    --no-sequence: Do not use the comma operator trick
    --smoothstep: Use IQ's smoothstep trick
    -- ...: Stop parsing command line
    --help: display this list of options
    -help: display this list of options

minifyの実行

GLSL ES 1.0

mono shader_minifier.exe archway_es1.glsl -o archway_es1.shader_minifier.min.glsl --format none
archway_es1.shader_minifier.min.glsl
uniform vec2 m;uniform float i;uniform vec2 v;
#define iResolution resolution
#define iTime time
#define iMouse mouse
const float y=.0001,r=y*10.,f=3.14159,n=1e+10;const vec3 l=vec3(-.486664,.811107,-.324443),x=vec3(0.),e=vec3(1.,.1,.1);const float z=75.;vec3 c,a;float d=0.;struct Intersect{bool isHit;vec3 position;float distance;vec3 normal;int material;vec3 color;};const int t=0,k=1;vec3 s(vec3 v,float i){return mod(v,i)-.5*i;}vec2 p(vec2 v,float i){return mod(v,i)-.5*i;}float h(float v,float i){return mod(v,i)-.5*i;}float h(vec3 v,vec3 i,float y){return length(v-i)-y;}float w(vec3 v,vec2 i){vec2 m=abs(vec2(length(v.xz),v.y))-i;return min(max(m.x,m.y),0.)+length(max(m,0.));}float I(vec3 v,vec3 i){return length(max(abs(v)-i,0.));}float I(vec3 v){float i=1.,a=3.,r=-.5;for(float f=0.;f<3.;f++){float m=pow(2.,f);r+=.1/m*(sin(m*i*v.x+a*iTime)+sin(m*i*v.z+a*iTime));}return dot(v,vec3(0.,1.,0.))-r;}float h(vec3 v){v.y-=.65;float i=.05,m=w(v-vec3(-1.,0.,0.),vec2(i,1.3)),y=w(v-vec3(1.,0.,0.),vec2(i,1.3)),a=.02*v.x*v.x,r=.5*(v.y-1.3),f=I(v-vec3(0.,1.3+a,0.),vec3(1.7+r,i*2.+a,i)),e=I(v-vec3(0.,.7,0.),vec3(1.3,i,i)),l=I(v-vec3(0.,1.,0.),vec3(i,.3,i));return min(min(m,y),min(l,min(f,e)));}float p(vec3 v){if(d<=.5)v.z=h(v.z,1.+20.*cos(f*d));else v.xz=h(v.xz,10.);return h(v);}float s(vec3 v){return I(v);}vec3 w(vec3 v){vec4 i=vec4(1.,2./3.,1./3.,3.);vec3 m=abs(fract(v.xxx+i.xyz)*6.-i.www);return v.z*mix(i.xxx,clamp(m-i.xxx,0.,1.),v.y);}Intersect o(Intersect v,Intersect i){if(v.distance<i.distance)return v;else return i;}Intersect o(vec3 v){Intersect i;i.distance=I(v);i.material=k;return i;}vec3 u(vec3 v){vec2 i=vec2(1.,-1.)*.001;return normalize(i.xyy*s(v+i.xyy)+i.yyx*s(v+i.yyx)+i.yxy*s(v+i.yxy)+i.xxx*s(v+i.xxx));}float u(vec3 v,vec3 i){float m=0.,f=0.,a=1.,r=.5;for(float e=0.;e<50.;e++){m=s(v+i*f);if(m<y)return r;a=min(a,m*16./f);f+=m;}return 1.-r+a*r;}Intersect b(vec3 v,vec3 i){float m,f,a,r=0.;vec3 c=v;int z=0;Intersect I;for(int h=0;h<120;h++){m=s(c);r+=m;c=v+r*i;z=h;if(abs(m)<y)break;}if(abs(m)<y){I=o(c);I.position=c;I.normal=u(c);I.distance=r;float h=clamp(dot(l,I.normal),.1,1.),g=pow(clamp(dot(reflect(l,I.normal),i),0.,1.),6.);if(I.material==t);else if(I.material==k)I.color=vec3(.5,.7,.8)*h+vec3(1.)*g;I.isHit=true;}else I.color=x,I.isHit=false;I.color=clamp(I.color-.1*I.distance,0.,1.);c=v;r=0.;f=n;for(int h=0;h<20;h++){m=p(c);f=min(m,f);r+=m;c=v+r*i;if(h==9&&d<=.5){break;}}if(abs(m)<y)I.color+=e;else I.color+=e*clamp(.05/f,0.,1.);return I;}void g(out vec4 v,in vec2 i){d=mod(iTime/z,1.);vec2 m=(i.xy*2.-iResolution.xy)/min(iResolution.x,iResolution.y);if(d<.7)c=vec3(0.,.6+.4*cos(iTime),3.*iTime),a=normalize(vec3(0.,-.1,1.));else c=vec3(0.,.6+.4*cos(iTime)+50.*(d-.7),3.*iTime),a=normalize(vec3(0.,-.1-(d-.7),1.));vec3 f=normalize(cross(a,vec3(0.,1.,0.))),l=normalize(cross(f,a));float y=1.3;vec3 e=normalize(f*m.x+l*m.y+a*y),I=vec3(0.);float h=1.;Intersect n;for(int t=0;t<3;t++){n=b(c,e);I+=h*n.color;h*=.5;e=normalize(reflect(e,n.normal));c=n.position+n.normal*r;if(!n.isHit||n.material!=k)break;}v=vec4(I,1.);}void main(){vec4 v;g(v,gl_FragCoord.xy);gl_FragColor=v;}

7471 byte -> 3110 byte (41.6 %)

GLSL ES 3.0

mono shader_minifier.exe archway_es3.glsl -o archway_es3.shader_minifier.min.glsl --format none
archway_es3.shader_minifier.min.glsl
#version 300 es
uniform vec2 m;uniform float i;uniform vec2 v;
#define iResolution resolution
#define iTime time
#define iMouse mouse
out vec4 r;const float y=.0001,l=y*10.,f=3.14159,n=1e+10;const vec3 x=vec3(-.486664,.811107,-.324443),z=vec3(0.),e=vec3(1.,.1,.1);const float t=75.;vec3 c,a;float d=0.;struct Intersect{bool isHit;vec3 position;float distance;vec3 normal;int material;vec3 color;};const int p=0,k=1;vec3 s(vec3 v,float i){return mod(v,i)-.5*i;}vec2 h(vec2 v,float i){return mod(v,i)-.5*i;}float w(float v,float i){return mod(v,i)-.5*i;}float h(vec3 v,vec3 i,float y){return length(v-i)-y;}float I(vec3 v,vec2 i){vec2 m=abs(vec2(length(v.xz),v.y))-i;return min(max(m.x,m.y),0.)+length(max(m,0.));}float o(vec3 v,vec3 i){return length(max(abs(v)-i,0.));}float I(vec3 v){float i=1.,l=3.,r=-.5;for(float f=0.;f<3.;f++){float m=pow(2.,f);r+=.1/m*(sin(m*i*v.x+l*iTime)+sin(m*i*v.z+l*iTime));}return dot(v,vec3(0.,1.,0.))-r;}float h(vec3 v){v.y-=.65;float i=.05,m=I(v-vec3(-1.,0.,0.),vec2(i,1.3)),y=I(v-vec3(1.,0.,0.),vec2(i,1.3)),l=.02*v.x*v.x,f=.5*(v.y-1.3),a=o(v-vec3(0.,1.3+l,0.),vec3(1.7+f,i*2.+l,i)),e=o(v-vec3(0.,.7,0.),vec3(1.3,i,i)),x=o(v-vec3(0.,1.,0.),vec3(i,.3,i));return min(min(m,y),min(x,min(a,e)));}float o(vec3 v){if(d<=.5)v.z=w(v.z,1.+20.*cos(f*d));else v.xz=w(v.xz,10.);return h(v);}float s(vec3 v){return I(v);}vec3 w(vec3 v){vec4 i=vec4(1.,2./3.,1./3.,3.);vec3 m=abs(fract(v.xxx+i.xyz)*6.-i.www);return v.z*mix(i.xxx,clamp(m-i.xxx,0.,1.),v.y);}Intersect u(Intersect v,Intersect i){if(v.distance<i.distance)return v;else return i;}Intersect u(vec3 v){Intersect i;i.distance=I(v);i.material=k;return i;}vec3 b(vec3 v){vec2 i=vec2(1.,-1.)*.001;return normalize(i.xyy*s(v+i.xyy)+i.yyx*s(v+i.yyx)+i.yxy*s(v+i.yxy)+i.xxx*s(v+i.xxx));}float b(vec3 v,vec3 i){float m=0.,f=0.,l=1.,r=.5;for(float e=0.;e<50.;e++){m=s(v+i*f);if(m<y)return r;l=min(l,m*16./f);f+=m;}return 1.-r+l*r;}Intersect T(vec3 v,vec3 i){float m,f,l,r=0.;vec3 c=v;int t=0;Intersect a;for(int I=0;I<120;I++){m=s(c);r+=m;c=v+r*i;t=I;if(abs(m)<y)break;}if(abs(m)<y){a=u(c);a.position=c;a.normal=b(c);a.distance=r;float I=clamp(dot(x,a.normal),.1,1.),g=pow(clamp(dot(reflect(x,a.normal),i),0.,1.),6.);if(a.material==p);else if(a.material==k)a.color=vec3(.5,.7,.8)*I+vec3(1.)*g;a.isHit=true;}else a.color=z,a.isHit=false;a.color=clamp(a.color-.1*a.distance,0.,1.);c=v;r=0.;f=n;for(int I=0;I<20;I++){m=o(c);f=min(m,f);r+=m;c=v+r*i;if(I==9&&d<=.5){break;}}if(abs(m)<y)a.color+=e;else a.color+=e*clamp(.05/f,0.,1.);return a;}void g(out vec4 v,in vec2 i){d=mod(iTime/t,1.);vec2 m=(i.xy*2.-iResolution.xy)/min(iResolution.x,iResolution.y);if(d<.7)c=vec3(0.,.6+.4*cos(iTime),3.*iTime),a=normalize(vec3(0.,-.1,1.));else c=vec3(0.,.6+.4*cos(iTime)+50.*(d-.7),3.*iTime),a=normalize(vec3(0.,-.1-(d-.7),1.));vec3 f=normalize(cross(a,vec3(0.,1.,0.))),r=normalize(cross(f,a));float y=1.3;vec3 e=normalize(f*m.x+r*m.y+a*y),x=vec3(0.);float I=1.;Intersect n;for(int z=0;z<3;z++){n=T(c,e);x+=I*n.color;I*=.5;e=normalize(reflect(e,n.normal));c=n.position+n.normal*l;if(!n.isHit||n.material!=k)break;}v=vec4(x,1.);}void main(){vec4 v;g(v,gl_FragCoord.xy);r=v;}

7502 byte -> 3126 byte (41.7 %)

注意点

precision指定はエラーになりました。

Parse error: Error in archway.glsl: Ln: 5 Col: 17
precision highp float;
                ^
Expecting: '('

  at Parse+ParseImpl+runParser@347.Invoke (System.String message) [0x00001] in <5b13c9dcc40096cda7450383dcc9135b>:0
  at Microsoft.FSharp.Core.PrintfImpl.go@523-3[b,c,d] (System.String fmt, System.Int32 len, Microsoft.FSharp.Core.FSharpFunc`2[T,TResult] outputChar, Microsoft.FSharp.Core.FSharpFunc`2[T,TResult] outa, b os, Microsoft.FSharp.Core.FSharpFunc`2[T,TResult] finalize, Microsoft.FSharp.Collections.FSharpList`1[T] args, System.Int32 i) [0x00027] in <5b13c9dcc40096cda7450383dcc9135b>:0
  at Microsoft.FSharp.Core.PrintfImpl.run@521[b,c,d] (Microsoft.FSharp.Core.FSharpFunc`2[T,TResult] initialize, System.String fmt, System.Int32 len, Microsoft.FSharp.Collections.FSharpList`1[T] args) [0x00039] in <5b13c9dcc40096cda7450383dcc9135b>:0
  at Microsoft.FSharp.Core.PrintfImpl.capture@540[b,c,d] (Microsoft.FSharp.Core.FSharpFunc`2[T,TResult] initialize, System.String fmt, System.Int32 len, Microsoft.FSharp.Collections.FSharpList`1[T] args, System.Type ty, System.Int32 i) [0x00039] in <5b13c9dcc40096cda7450383dcc9135b>:0
  at <StartupCode$FSharp-Core>.$Reflect+Invoke@720-4[T1,T2].Invoke (T1 inp) [0x00001] in <5b13c9dcc40096cda7450383dcc9135b>:0
  at main.minify (System.String filename, System.String content) [0x00021] in <5b13c9dcc40096cda7450383dcc9135b>:0
  at main.minifyFile (System.String file) [0x00014] in <5b13c9dcc40096cda7450383dcc9135b>:0
  at main.run (System.String[] files) [0x00031] in <5b13c9dcc40096cda7450383dcc9135b>:0

今回はprecision指定をコメントアウトすることで回避しました。

-precision highp float;
+// precision highp float;// Shader Minifier のエラー回避のためにコメントアウト

glsl-optimizer

C++で実装されており、ソースコードを各環境でビルドすることで Windows OS でも macOS でも動作します。

macOSの場合は、リポジトリをclone してから cmake . && make で簡単にビルドができました。

使い方

./glsl-optimizer/glslopt
Usage: glslopt <-f|-v> <input shader> [<output shader>]
    -f : fragment shader (default)
    -v : vertex shader
    -1 : target OpenGL (default)
    -2 : target OpenGL ES 2.0
    -3 : target OpenGL ES 3.0

    If no output specified, output is to [input].out.

minifyの実行

GLSL ES 1.0

./glsl-optimizer/glslopt archway_es1.glsl archway_es1.glslopt.min.glsl -2

archway_es1.glslopt.min.glsl
aarchway_es1.glslopt.min.glsl
uniform highp vec2 resolution;
uniform highp float time;
highp vec3 cPos;
highp vec3 cDir;
highp float normalizedGlobalTime;
void main ()
{
  highp float alpha_2;
  highp vec3 color_3;
  highp vec3 ray_4;
  highp float tmpvar_5;
  tmpvar_5 = (float(mod ((time / 75.0), 1.0)));
  normalizedGlobalTime = tmpvar_5;
  highp vec2 tmpvar_6;
  tmpvar_6 = (((gl_FragCoord.xy * 2.0) - resolution) / min (resolution.x, resolution.y));
  if ((tmpvar_5 < 0.7)) {
    highp vec3 tmpvar_7;
    tmpvar_7.x = 0.0;
    tmpvar_7.y = (0.6 + (0.4 * cos(time)));
    tmpvar_7.z = (3.0 * time);
    cPos = tmpvar_7;
    cDir = vec3(0.0, -0.09950372, 0.9950371);
  } else {
    highp vec3 tmpvar_8;
    tmpvar_8.x = 0.0;
    tmpvar_8.y = ((0.6 + (0.4 * 
      cos(time)
    )) + (50.0 * (tmpvar_5 - 0.7)));
    tmpvar_8.z = (3.0 * time);
    cPos = tmpvar_8;
    highp vec3 tmpvar_9;
    tmpvar_9.xz = vec2(0.0, 1.0);
    tmpvar_9.y = (-0.1 - (tmpvar_5 - 0.7));
    cDir = normalize(tmpvar_9);
  };
  highp vec3 tmpvar_10;
  tmpvar_10 = normalize(((cDir.yzx * vec3(0.0, 0.0, 1.0)) - (cDir.zxy * vec3(1.0, 0.0, 0.0))));
  ray_4 = normalize(((
    (tmpvar_10 * tmpvar_6.x)
   + 
    (normalize(((tmpvar_10.yzx * cDir.zxy) - (tmpvar_10.zxy * cDir.yzx))) * tmpvar_6.y)
  ) + (cDir * 1.3)));
  color_3 = vec3(0.0, 0.0, 0.0);
  alpha_2 = 1.0;
  for (highp int i_1 = 0; i_1 < 3; i_1++) {
    highp vec3 origin_11;
    origin_11 = cPos;
    highp vec3 ray_12;
    ray_12 = ray_4;
    bool tmpvar_15;
    highp vec3 tmpvar_16;
    highp float tmpvar_17;
    highp vec3 tmpvar_18;
    highp vec3 tmpvar_19;
    highp vec3 p_20;
    highp float depth_21;
    highp float minDist_22;
    highp float dist_23;
    depth_21 = 0.0;
    p_20 = cPos;
    for (highp int i_14 = 0; i_14 < 120; i_14++) {
      highp float tmpvar_24;
      highp float d_25;
      highp float tmpvar_26;
      tmpvar_26 = (3.0 * time);
      d_25 = (-0.5 + (0.1 * (
        sin((p_20.x + tmpvar_26))
       + 
        sin((p_20.z + tmpvar_26))
      )));
      d_25 = (d_25 + (0.05 * (
        sin(((2.0 * p_20.x) + tmpvar_26))
       + 
        sin(((2.0 * p_20.z) + tmpvar_26))
      )));
      d_25 = (d_25 + (0.025 * (
        sin(((4.0 * p_20.x) + tmpvar_26))
       + 
        sin(((4.0 * p_20.z) + tmpvar_26))
      )));
      tmpvar_24 = (p_20.y - d_25);
      dist_23 = tmpvar_24;
      depth_21 = (depth_21 + tmpvar_24);
      p_20 = (origin_11 + (depth_21 * ray_12));
      highp float tmpvar_27;
      tmpvar_27 = abs(tmpvar_24);
      if ((tmpvar_27 < 0.0001)) {
        break;
      };
    };
    highp float tmpvar_28;
    tmpvar_28 = abs(dist_23);
    if ((tmpvar_28 < 0.0001)) {
      highp float tmpvar_29;
      tmpvar_29 = (3.0 * time);
      tmpvar_16 = p_20;
      highp vec3 p_30;
      p_30 = (p_20 + vec3(0.001, -0.001, -0.001));
      highp float d_31;
      d_31 = (-0.5 + (0.1 * (
        sin((p_30.x + tmpvar_29))
       + 
        sin((p_30.z + tmpvar_29))
      )));
      d_31 = (d_31 + (0.05 * (
        sin(((2.0 * p_30.x) + tmpvar_29))
       + 
        sin(((2.0 * p_30.z) + tmpvar_29))
      )));
      d_31 = (d_31 + (0.025 * (
        sin(((4.0 * p_30.x) + tmpvar_29))
       + 
        sin(((4.0 * p_30.z) + tmpvar_29))
      )));
      highp vec3 p_32;
      p_32 = (p_20 + vec3(-0.001, -0.001, 0.001));
      highp float d_33;
      d_33 = (-0.5 + (0.1 * (
        sin((p_32.x + tmpvar_29))
       + 
        sin((p_32.z + tmpvar_29))
      )));
      d_33 = (d_33 + (0.05 * (
        sin(((2.0 * p_32.x) + tmpvar_29))
       + 
        sin(((2.0 * p_32.z) + tmpvar_29))
      )));
      d_33 = (d_33 + (0.025 * (
        sin(((4.0 * p_32.x) + tmpvar_29))
       + 
        sin(((4.0 * p_32.z) + tmpvar_29))
      )));
      highp vec3 p_34;
      p_34 = (p_20 + vec3(-0.001, 0.001, -0.001));
      highp float d_35;
      d_35 = (-0.5 + (0.1 * (
        sin((p_34.x + tmpvar_29))
       + 
        sin((p_34.z + tmpvar_29))
      )));
      d_35 = (d_35 + (0.05 * (
        sin(((2.0 * p_34.x) + tmpvar_29))
       + 
        sin(((2.0 * p_34.z) + tmpvar_29))
      )));
      d_35 = (d_35 + (0.025 * (
        sin(((4.0 * p_34.x) + tmpvar_29))
       + 
        sin(((4.0 * p_34.z) + tmpvar_29))
      )));
      highp vec3 p_36;
      p_36 = (p_20 + vec3(0.001, 0.001, 0.001));
      highp float d_37;
      d_37 = (-0.5 + (0.1 * (
        sin((p_36.x + tmpvar_29))
       + 
        sin((p_36.z + tmpvar_29))
      )));
      d_37 = (d_37 + (0.05 * (
        sin(((2.0 * p_36.x) + tmpvar_29))
       + 
        sin(((2.0 * p_36.z) + tmpvar_29))
      )));
      d_37 = (d_37 + (0.025 * (
        sin(((4.0 * p_36.x) + tmpvar_29))
       + 
        sin(((4.0 * p_36.z) + tmpvar_29))
      )));
      highp vec3 tmpvar_38;
      tmpvar_38 = normalize(((
        ((vec3(0.001, -0.001, -0.001) * (p_30.y - d_31)) + (vec3(-0.001, -0.001, 0.001) * (p_32.y - d_33)))
       + 
        (vec3(-0.001, 0.001, -0.001) * (p_34.y - d_35))
      ) + (vec3(0.001, 0.001, 0.001) * 
        (p_36.y - d_37)
      )));
      tmpvar_18 = tmpvar_38;
      tmpvar_17 = depth_21;
      tmpvar_19 = ((vec3(0.5, 0.7, 0.8) * clamp (
        dot (vec3(-0.4866643, 0.8111071, -0.3244428), tmpvar_38)
      , 0.1, 1.0)) + vec3(pow (clamp (
        dot ((vec3(-0.4866643, 0.8111071, -0.3244428) - (2.0 * (
          dot (tmpvar_38, vec3(-0.4866643, 0.8111071, -0.3244428))
         * tmpvar_38))), ray_4)
      , 0.0, 1.0), 6.0)));
      tmpvar_15 = bool(1);
    } else {
      tmpvar_19 = vec3(0.0, 0.0, 0.0);
      tmpvar_15 = bool(0);
    };
    highp vec3 tmpvar_39;
    tmpvar_39 = clamp ((tmpvar_19 - (0.1 * tmpvar_17)), 0.0, 1.0);
    tmpvar_19 = tmpvar_39;
    p_20 = cPos;
    depth_21 = 0.0;
    minDist_22 = 1e+10;
    for (highp int i_13 = 0; i_13 < 20; i_13++) {
      highp vec3 p_40;
      p_40 = p_20;
      if ((normalizedGlobalTime <= 0.5)) {
        highp float interval_41;
        interval_41 = (1.0 + (20.0 * cos(
          (3.14159 * normalizedGlobalTime)
        )));
        p_40.z = ((float(mod (p_20.z, interval_41))) - (0.5 * interval_41));
      } else {
        p_40.xz = ((vec2(mod (p_40.xz, 10.0))) - 5.0);
      };
      highp vec3 p_42;
      p_42.xz = p_40.xz;
      p_42.y = (p_20.y - 0.65);
      highp vec3 p_43;
      p_43 = (p_42 - vec3(-1.0, 0.0, 0.0));
      highp vec2 tmpvar_44;
      tmpvar_44.x = sqrt(dot (p_43.xz, p_43.xz));
      tmpvar_44.y = p_43.y;
      highp vec2 tmpvar_45;
      tmpvar_45 = (abs(tmpvar_44) - vec2(0.05, 1.3));
      highp vec2 tmpvar_46;
      tmpvar_46 = max (tmpvar_45, 0.0);
      highp vec3 p_47;
      p_47 = (p_42 - vec3(1.0, 0.0, 0.0));
      highp vec2 tmpvar_48;
      tmpvar_48.x = sqrt(dot (p_47.xz, p_47.xz));
      tmpvar_48.y = p_47.y;
      highp vec2 tmpvar_49;
      tmpvar_49 = (abs(tmpvar_48) - vec2(0.05, 1.3));
      highp vec2 tmpvar_50;
      tmpvar_50 = max (tmpvar_49, 0.0);
      highp float tmpvar_51;
      tmpvar_51 = ((0.02 * p_40.x) * p_40.x);
      highp vec3 tmpvar_52;
      tmpvar_52.xz = vec2(0.0, 0.0);
      tmpvar_52.y = (1.3 + tmpvar_51);
      highp vec3 tmpvar_53;
      tmpvar_53.x = (1.7 + (0.5 * (p_42.y - 1.3)));
      tmpvar_53.y = (0.1 + tmpvar_51);
      tmpvar_53.z = 0.05;
      highp vec3 tmpvar_54;
      tmpvar_54 = max ((abs(
        (p_42 - tmpvar_52)
      ) - tmpvar_53), 0.0);
      highp vec3 tmpvar_55;
      tmpvar_55 = max ((abs(
        (p_42 - vec3(0.0, 0.7, 0.0))
      ) - vec3(1.3, 0.05, 0.05)), 0.0);
      highp vec3 tmpvar_56;
      tmpvar_56 = max ((abs(
        (p_42 - vec3(0.0, 1.0, 0.0))
      ) - vec3(0.05, 0.3, 0.05)), 0.0);
      highp float tmpvar_57;
      tmpvar_57 = min (min ((
        min (max (tmpvar_45.x, tmpvar_45.y), 0.0)
       + 
        sqrt(dot (tmpvar_46, tmpvar_46))
      ), (
        min (max (tmpvar_49.x, tmpvar_49.y), 0.0)
       + 
        sqrt(dot (tmpvar_50, tmpvar_50))
      )), min (sqrt(
        dot (tmpvar_56, tmpvar_56)
      ), min (
        sqrt(dot (tmpvar_54, tmpvar_54))
      , 
        sqrt(dot (tmpvar_55, tmpvar_55))
      )));
      dist_23 = tmpvar_57;
      minDist_22 = min (tmpvar_57, minDist_22);
      depth_21 = (depth_21 + tmpvar_57);
      p_20 = (origin_11 + (depth_21 * ray_12));
      if (((i_13 == 9) && (normalizedGlobalTime <= 0.5))) {
        break;
      };
    };
    highp float tmpvar_58;
    tmpvar_58 = abs(dist_23);
    if ((tmpvar_58 < 0.0001)) {
      tmpvar_19 = (tmpvar_39 + vec3(1.0, 0.1, 0.1));
    } else {
      tmpvar_19 = (tmpvar_19 + (vec3(1.0, 0.1, 0.1) * clamp (
        (0.05 / minDist_22)
      , 0.0, 1.0)));
    };
    color_3 = (color_3 + (alpha_2 * tmpvar_19));
    alpha_2 = (alpha_2 * 0.5);
    ray_4 = normalize((ray_4 - (2.0 * 
      (dot (tmpvar_18, ray_4) * tmpvar_18)
    )));
    cPos = (tmpvar_16 + (tmpvar_18 * 0.0009999999));
    if (!(tmpvar_15)) {
      break;
    };
  };
  mediump vec4 tmpvar_59;
  tmpvar_59.w = 1.0;
  tmpvar_59.xyz = color_3;
  gl_FragColor = tmpvar_59;
}

7471 byte -> 8885 byte (118.9 %)

ファイルサイズは圧縮後のほうが増えてしまいました。

インライン展開などが行われ、変数名も置き換えられているのですが、置き換えられた変数名が長かったり、インデントの空白のためにファイルサイズが増えたようです。

GLSL ES 3.0

./glsl-optimizer/glslopt archway_es3.glsl archway_es3.glslopt.min.glsl -3

archway_es3.glslopt.min.glsl
archway_es3.glslopt.min.glsl
#version 300 es
uniform highp vec2 resolution;
uniform highp float time;
out highp vec4 outColor;
highp vec3 cPos;
highp vec3 cDir;
highp float normalizedGlobalTime;
void main ()
{
  highp float alpha_2;
  highp vec3 color_3;
  highp vec3 ray_4;
  highp float tmpvar_5;
  tmpvar_5 = (float(mod ((time / 75.0), 1.0)));
  normalizedGlobalTime = tmpvar_5;
  highp vec2 tmpvar_6;
  tmpvar_6 = (((gl_FragCoord.xy * 2.0) - resolution) / min (resolution.x, resolution.y));
  if ((tmpvar_5 < 0.7)) {
    highp vec3 tmpvar_7;
    tmpvar_7.x = 0.0;
    tmpvar_7.y = (0.6 + (0.4 * cos(time)));
    tmpvar_7.z = (3.0 * time);
    cPos = tmpvar_7;
    cDir = vec3(0.0, -0.09950372, 0.9950371);
  } else {
    highp vec3 tmpvar_8;
    tmpvar_8.x = 0.0;
    tmpvar_8.y = ((0.6 + (0.4 * 
      cos(time)
    )) + (50.0 * (tmpvar_5 - 0.7)));
    tmpvar_8.z = (3.0 * time);
    cPos = tmpvar_8;
    highp vec3 tmpvar_9;
    tmpvar_9.xz = vec2(0.0, 1.0);
    tmpvar_9.y = (-0.1 - (tmpvar_5 - 0.7));
    cDir = normalize(tmpvar_9);
  };
  highp vec3 tmpvar_10;
  tmpvar_10 = normalize(((cDir.yzx * vec3(0.0, 0.0, 1.0)) - (cDir.zxy * vec3(1.0, 0.0, 0.0))));
  ray_4 = normalize(((
    (tmpvar_10 * tmpvar_6.x)
   + 
    (normalize(((tmpvar_10.yzx * cDir.zxy) - (tmpvar_10.zxy * cDir.yzx))) * tmpvar_6.y)
  ) + (cDir * 1.3)));
  color_3 = vec3(0.0, 0.0, 0.0);
  alpha_2 = 1.0;
  for (highp int i_1 = 0; i_1 < 3; i_1++) {
    highp vec3 origin_11;
    origin_11 = cPos;
    highp vec3 ray_12;
    ray_12 = ray_4;
    bool tmpvar_15;
    highp vec3 tmpvar_16;
    highp float tmpvar_17;
    highp vec3 tmpvar_18;
    highp vec3 tmpvar_19;
    highp vec3 p_20;
    highp float depth_21;
    highp float minDist_22;
    highp float dist_23;
    depth_21 = 0.0;
    p_20 = cPos;
    for (highp int i_14 = 0; i_14 < 120; i_14++) {
      highp float tmpvar_24;
      highp float d_25;
      highp float tmpvar_26;
      tmpvar_26 = (3.0 * time);
      d_25 = (-0.5 + (0.1 * (
        sin((p_20.x + tmpvar_26))
       + 
        sin((p_20.z + tmpvar_26))
      )));
      d_25 = (d_25 + (0.05 * (
        sin(((2.0 * p_20.x) + tmpvar_26))
       + 
        sin(((2.0 * p_20.z) + tmpvar_26))
      )));
      d_25 = (d_25 + (0.025 * (
        sin(((4.0 * p_20.x) + tmpvar_26))
       + 
        sin(((4.0 * p_20.z) + tmpvar_26))
      )));
      tmpvar_24 = (p_20.y - d_25);
      dist_23 = tmpvar_24;
      depth_21 = (depth_21 + tmpvar_24);
      p_20 = (origin_11 + (depth_21 * ray_12));
      highp float tmpvar_27;
      tmpvar_27 = abs(tmpvar_24);
      if ((tmpvar_27 < 0.0001)) {
        break;
      };
    };
    highp float tmpvar_28;
    tmpvar_28 = abs(dist_23);
    if ((tmpvar_28 < 0.0001)) {
      highp float tmpvar_29;
      tmpvar_29 = (3.0 * time);
      tmpvar_16 = p_20;
      highp vec3 p_30;
      p_30 = (p_20 + vec3(0.001, -0.001, -0.001));
      highp float d_31;
      d_31 = (-0.5 + (0.1 * (
        sin((p_30.x + tmpvar_29))
       + 
        sin((p_30.z + tmpvar_29))
      )));
      d_31 = (d_31 + (0.05 * (
        sin(((2.0 * p_30.x) + tmpvar_29))
       + 
        sin(((2.0 * p_30.z) + tmpvar_29))
      )));
      d_31 = (d_31 + (0.025 * (
        sin(((4.0 * p_30.x) + tmpvar_29))
       + 
        sin(((4.0 * p_30.z) + tmpvar_29))
      )));
      highp vec3 p_32;
      p_32 = (p_20 + vec3(-0.001, -0.001, 0.001));
      highp float d_33;
      d_33 = (-0.5 + (0.1 * (
        sin((p_32.x + tmpvar_29))
       + 
        sin((p_32.z + tmpvar_29))
      )));
      d_33 = (d_33 + (0.05 * (
        sin(((2.0 * p_32.x) + tmpvar_29))
       + 
        sin(((2.0 * p_32.z) + tmpvar_29))
      )));
      d_33 = (d_33 + (0.025 * (
        sin(((4.0 * p_32.x) + tmpvar_29))
       + 
        sin(((4.0 * p_32.z) + tmpvar_29))
      )));
      highp vec3 p_34;
      p_34 = (p_20 + vec3(-0.001, 0.001, -0.001));
      highp float d_35;
      d_35 = (-0.5 + (0.1 * (
        sin((p_34.x + tmpvar_29))
       + 
        sin((p_34.z + tmpvar_29))
      )));
      d_35 = (d_35 + (0.05 * (
        sin(((2.0 * p_34.x) + tmpvar_29))
       + 
        sin(((2.0 * p_34.z) + tmpvar_29))
      )));
      d_35 = (d_35 + (0.025 * (
        sin(((4.0 * p_34.x) + tmpvar_29))
       + 
        sin(((4.0 * p_34.z) + tmpvar_29))
      )));
      highp vec3 p_36;
      p_36 = (p_20 + vec3(0.001, 0.001, 0.001));
      highp float d_37;
      d_37 = (-0.5 + (0.1 * (
        sin((p_36.x + tmpvar_29))
       + 
        sin((p_36.z + tmpvar_29))
      )));
      d_37 = (d_37 + (0.05 * (
        sin(((2.0 * p_36.x) + tmpvar_29))
       + 
        sin(((2.0 * p_36.z) + tmpvar_29))
      )));
      d_37 = (d_37 + (0.025 * (
        sin(((4.0 * p_36.x) + tmpvar_29))
       + 
        sin(((4.0 * p_36.z) + tmpvar_29))
      )));
      highp vec3 tmpvar_38;
      tmpvar_38 = normalize(((
        ((vec3(0.001, -0.001, -0.001) * (p_30.y - d_31)) + (vec3(-0.001, -0.001, 0.001) * (p_32.y - d_33)))
       + 
        (vec3(-0.001, 0.001, -0.001) * (p_34.y - d_35))
      ) + (vec3(0.001, 0.001, 0.001) * 
        (p_36.y - d_37)
      )));
      tmpvar_18 = tmpvar_38;
      tmpvar_17 = depth_21;
      tmpvar_19 = ((vec3(0.5, 0.7, 0.8) * clamp (
        dot (vec3(-0.4866643, 0.8111071, -0.3244428), tmpvar_38)
      , 0.1, 1.0)) + vec3(pow (clamp (
        dot ((vec3(-0.4866643, 0.8111071, -0.3244428) - (2.0 * (
          dot (tmpvar_38, vec3(-0.4866643, 0.8111071, -0.3244428))
         * tmpvar_38))), ray_4)
      , 0.0, 1.0), 6.0)));
      tmpvar_15 = bool(1);
    } else {
      tmpvar_19 = vec3(0.0, 0.0, 0.0);
      tmpvar_15 = bool(0);
    };
    highp vec3 tmpvar_39;
    tmpvar_39 = clamp ((tmpvar_19 - (0.1 * tmpvar_17)), 0.0, 1.0);
    tmpvar_19 = tmpvar_39;
    p_20 = cPos;
    depth_21 = 0.0;
    minDist_22 = 1e+10;
    for (highp int i_13 = 0; i_13 < 20; i_13++) {
      highp vec3 p_40;
      p_40 = p_20;
      if ((normalizedGlobalTime <= 0.5)) {
        highp float interval_41;
        interval_41 = (1.0 + (20.0 * cos(
          (3.14159 * normalizedGlobalTime)
        )));
        p_40.z = ((float(mod (p_20.z, interval_41))) - (0.5 * interval_41));
      } else {
        p_40.xz = ((vec2(mod (p_40.xz, 10.0))) - 5.0);
      };
      highp vec3 p_42;
      p_42.xz = p_40.xz;
      p_42.y = (p_20.y - 0.65);
      highp vec3 p_43;
      p_43 = (p_42 - vec3(-1.0, 0.0, 0.0));
      highp vec2 tmpvar_44;
      tmpvar_44.x = sqrt(dot (p_43.xz, p_43.xz));
      tmpvar_44.y = p_43.y;
      highp vec2 tmpvar_45;
      tmpvar_45 = (abs(tmpvar_44) - vec2(0.05, 1.3));
      highp vec2 tmpvar_46;
      tmpvar_46 = max (tmpvar_45, 0.0);
      highp vec3 p_47;
      p_47 = (p_42 - vec3(1.0, 0.0, 0.0));
      highp vec2 tmpvar_48;
      tmpvar_48.x = sqrt(dot (p_47.xz, p_47.xz));
      tmpvar_48.y = p_47.y;
      highp vec2 tmpvar_49;
      tmpvar_49 = (abs(tmpvar_48) - vec2(0.05, 1.3));
      highp vec2 tmpvar_50;
      tmpvar_50 = max (tmpvar_49, 0.0);
      highp float tmpvar_51;
      tmpvar_51 = ((0.02 * p_40.x) * p_40.x);
      highp vec3 tmpvar_52;
      tmpvar_52.xz = vec2(0.0, 0.0);
      tmpvar_52.y = (1.3 + tmpvar_51);
      highp vec3 tmpvar_53;
      tmpvar_53.x = (1.7 + (0.5 * (p_42.y - 1.3)));
      tmpvar_53.y = (0.1 + tmpvar_51);
      tmpvar_53.z = 0.05;
      highp vec3 tmpvar_54;
      tmpvar_54 = max ((abs(
        (p_42 - tmpvar_52)
      ) - tmpvar_53), 0.0);
      highp vec3 tmpvar_55;
      tmpvar_55 = max ((abs(
        (p_42 - vec3(0.0, 0.7, 0.0))
      ) - vec3(1.3, 0.05, 0.05)), 0.0);
      highp vec3 tmpvar_56;
      tmpvar_56 = max ((abs(
        (p_42 - vec3(0.0, 1.0, 0.0))
      ) - vec3(0.05, 0.3, 0.05)), 0.0);
      highp float tmpvar_57;
      tmpvar_57 = min (min ((
        min (max (tmpvar_45.x, tmpvar_45.y), 0.0)
       + 
        sqrt(dot (tmpvar_46, tmpvar_46))
      ), (
        min (max (tmpvar_49.x, tmpvar_49.y), 0.0)
       + 
        sqrt(dot (tmpvar_50, tmpvar_50))
      )), min (sqrt(
        dot (tmpvar_56, tmpvar_56)
      ), min (
        sqrt(dot (tmpvar_54, tmpvar_54))
      , 
        sqrt(dot (tmpvar_55, tmpvar_55))
      )));
      dist_23 = tmpvar_57;
      minDist_22 = min (tmpvar_57, minDist_22);
      depth_21 = (depth_21 + tmpvar_57);
      p_20 = (origin_11 + (depth_21 * ray_12));
      if (((i_13 == 9) && (normalizedGlobalTime <= 0.5))) {
        break;
      };
    };
    highp float tmpvar_58;
    tmpvar_58 = abs(dist_23);
    if ((tmpvar_58 < 0.0001)) {
      tmpvar_19 = (tmpvar_39 + vec3(1.0, 0.1, 0.1));
    } else {
      tmpvar_19 = (tmpvar_19 + (vec3(1.0, 0.1, 0.1) * clamp (
        (0.05 / minDist_22)
      , 0.0, 1.0)));
    };
    color_3 = (color_3 + (alpha_2 * tmpvar_19));
    alpha_2 = (alpha_2 * 0.5);
    ray_4 = normalize((ray_4 - (2.0 * 
      (dot (tmpvar_18, ray_4) * tmpvar_18)
    )));
    cPos = (tmpvar_16 + (tmpvar_18 * 0.0009999999));
    if (!(tmpvar_15)) {
      break;
    };
  };
  highp vec4 tmpvar_59;
  tmpvar_59.w = 1.0;
  tmpvar_59.xyz = color_3;
  outColor = tmpvar_59;
}

7502 byte -> 8920 byte (118.9 %)

GLSL ES 1.0と同様に圧縮後の方がファイルサイズが増えました。

glsl-minifier

node.jsで実装されており、 リポジトリをclone してから npm install することで、Windows OS でも macOS でも動作します。

使い方

node glsl-minifier/bin/glsl-minifier.js
usage: glsl-minifier.js [-h] [-v] -i INPUT -o OUTPUT [-sT {vertex,fragment}]
                        [-sV {2,3}]

glsl-minifier.js: error: Argument "-i/--input" is required

minifyの実行

GLSL ES 1.0

node glsl-minifier/bin/glsl-minifier.js -i archway_es1.glsl -o archway_es1.glsl-minifier.min.glsl
uniform highp vec2 resolution;uniform highp float time;highp vec3 a;highp vec3 b;highp float c;void main(){highp float d;highp vec3 e;highp vec3 f;highp float g;g=(float(mod((time/75.0),1.0)));c=g;highp vec2 h;h=(((gl_FragCoord.xy*2.0)-resolution)/min(resolution.x,resolution.y));if((g<0.7)){highp vec3 i;i.x=0.0;i.y=(0.6+(0.4*cos(time)));i.z=(3.0*time);a=i;b=vec3(0.0,-0.09950372,0.9950372);}else{highp vec3 j;j.x=0.0;j.y=((0.6+(0.4*cos(time)))+(50.0*(g-0.7)));j.z=(3.0*time);a=j;highp vec3 k;k.xz=vec2(0.0,1.0);k.y=(-0.1-(g-0.7));b=normalize(k);}highp vec3 l;l=normalize(((b.yzx*vec3(0.0,0.0,1.0))-(b.zxy*vec3(1.0,0.0,0.0))));f=normalize((((l*h.x)+(normalize(((l.yzx*b.zxy)-(l.zxy*b.yzx)))*h.y))+(b*1.3)));e=vec3(0.0,0.0,0.0);d=1.0;for(highp int m=0;m<3;m++){highp vec3 n;n=a;highp vec3 o;o=f;bool p;highp vec3 q;highp float r;highp vec3 s;highp vec3 t;highp vec3 u;highp float v;highp float w;highp float x;v=0.0;u=a;for(highp int y=0;y<120;y++){highp float z;highp float A;highp float B;B=(3.0*time);A=(-0.5+(0.1*(sin((u.x+B))+sin((u.z+B)))));A=(A+(0.05*(sin(((2.0*u.x)+B))+sin(((2.0*u.z)+B)))));A=(A+(0.025*(sin(((4.0*u.x)+B))+sin(((4.0*u.z)+B)))));z=(u.y-A);x=z;v=(v+z);u=(n+(v*o));highp float C;C=abs(z);if((C<0.0001)){break;}}highp float D;D=abs(x);if((D<0.0001)){highp float E;E=(3.0*time);q=u;highp vec3 F;F=(u+vec3(0.001,-0.001,-0.001));highp float G;G=(-0.5+(0.1*(sin((F.x+E))+sin((F.z+E)))));G=(G+(0.05*(sin(((2.0*F.x)+E))+sin(((2.0*F.z)+E)))));G=(G+(0.025*(sin(((4.0*F.x)+E))+sin(((4.0*F.z)+E)))));highp vec3 H;H=(u+vec3(-0.001,-0.001,0.001));highp float I;I=(-0.5+(0.1*(sin((H.x+E))+sin((H.z+E)))));I=(I+(0.05*(sin(((2.0*H.x)+E))+sin(((2.0*H.z)+E)))));I=(I+(0.025*(sin(((4.0*H.x)+E))+sin(((4.0*H.z)+E)))));highp vec3 J;J=(u+vec3(-0.001,0.001,-0.001));highp float K;K=(-0.5+(0.1*(sin((J.x+E))+sin((J.z+E)))));K=(K+(0.05*(sin(((2.0*J.x)+E))+sin(((2.0*J.z)+E)))));K=(K+(0.025*(sin(((4.0*J.x)+E))+sin(((4.0*J.z)+E)))));highp vec3 L;L=(u+vec3(0.001,0.001,0.001));highp float M;M=(-0.5+(0.1*(sin((L.x+E))+sin((L.z+E)))));M=(M+(0.05*(sin(((2.0*L.x)+E))+sin(((2.0*L.z)+E)))));M=(M+(0.025*(sin(((4.0*L.x)+E))+sin(((4.0*L.z)+E)))));highp vec3 N;N=normalize(((((vec3(0.001,-0.001,-0.001)*(F.y-G))+(vec3(-0.001,-0.001,0.001)*(H.y-I)))+(vec3(-0.001,0.001,-0.001)*(J.y-K)))+(vec3(0.001,0.001,0.001)*(L.y-M))));s=N;r=v;t=((vec3(0.5,0.7,0.8)*clamp(dot(vec3(-0.4866643,0.8111071,-0.3244428),N),0.1,1.0))+vec3(pow(clamp(dot((vec3(-0.4866643,0.8111071,-0.3244428)-(2.0*(dot(N,vec3(-0.4866643,0.8111071,-0.3244428))*N))),f),0.0,1.0),6.0)));p=bool(1);}else{t=vec3(0.0,0.0,0.0);p=bool(0);}highp vec3 O;O=clamp((t-(0.1*r)),0.0,1.0);t=O;u=a;v=0.0;w=1e+10;for(highp int P=0;P<20;P++){highp vec3 Q;Q=u;if((c<=0.5)){highp float R;R=(1.0+(20.0*cos((3.14159*c))));Q.z=((float(mod(u.z,R)))-(0.5*R));}else{Q.xz=((vec2(mod(Q.xz,10.0)))-5.0);}highp vec3 S;S.xz=Q.xz;S.y=(u.y-0.65);highp vec3 T;T=(S-vec3(-1.0,0.0,0.0));highp vec2 U;U.x=sqrt(dot(T.xz,T.xz));U.y=T.y;highp vec2 V;V=(abs(U)-vec2(0.05,1.3));highp vec2 W;W=max(V,0.0);highp vec3 X;X=(S-vec3(1.0,0.0,0.0));highp vec2 Y;Y.x=sqrt(dot(X.xz,X.xz));Y.y=X.y;highp vec2 Z;Z=(abs(Y)-vec2(0.05,1.3));highp vec2 ba;ba=max(Z,0.0);highp float bb;bb=((0.02*Q.x)*Q.x);highp vec3 bc;bc.xz=vec2(0.0,0.0);bc.y=(1.3+bb);highp vec3 bd;bd.x=(1.7+(0.5*(S.y-1.3)));bd.y=(0.1+bb);bd.z=0.05;highp vec3 be;be=max((abs((S-bc))-bd),0.0);highp vec3 bf;bf=max((abs((S-vec3(0.0,0.7,0.0)))-vec3(1.3,0.05,0.05)),0.0);highp vec3 bg;bg=max((abs((S-vec3(0.0,1.0,0.0)))-vec3(0.05,0.3,0.05)),0.0);highp float bh;bh=min(min((min(max(V.x,V.y),0.0)+sqrt(dot(W,W))),(min(max(Z.x,Z.y),0.0)+sqrt(dot(ba,ba)))),min(sqrt(dot(bg,bg)),min(sqrt(dot(be,be)),sqrt(dot(bf,bf)))));x=bh;w=min(bh,w);v=(v+bh);u=(n+(v*o));if(((P==9)&&(c<=0.5))){break;}}highp float bi;bi=abs(x);if((bi<0.0001)){t=(O+vec3(1.0,0.1,0.1));}else{t=(t+(vec3(1.0,0.1,0.1)*clamp((0.05/w),0.0,1.0)));}e=(e+(d*t));d=(d*0.5);f=normalize((f-(2.0*(dot(s,f)*s))));a=(q+(s*0.0009999999));if(!(p)){break;}}mediump vec4 bj;bj.w=1.0;bj.xyz=e;gl_FragColor=bj;}

7471 -> 4012 byte (53.7 %)

GLSL ES 3.0

node glsl-minifier/bin/glsl-minifier.js -i archway_es3.glsl -o archway_es3.glsl-minifier.min.glsl -sV 3
/Users/gam0022/Dropbox/glsl-minifier-sandbox/glsl-minifier/lib/optimizer/glsl-optimizer-asm.js:10
var Module;if(!Module)Module=(typeof Module!=="undefined"?Module:null)||{};var moduleOverrides={};for(var key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}var ENVIRONMENT_IS_WEB=typeof window==="object";var ENVIRONMENT_IS_NODE=typeof process==="object"&&typeof require==="function"&&!ENVIRONMENT_IS_WEB;var ENVIRONMENT_IS_WORKER=typeof importScripts==="function";var ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;if(ENVIRONMENT_IS_NODE){if(!Module["print"])Module["print"]=function print(x){process["stdout"].write(x+"\n")};if(!Module["printErr"])Module["printErr"]=function printErr(x){process["stderr"].write(x+"\n")};var nodeFS=require("fs");var nodePath=require("path");Module["read"]=function read(filename,binary){filename=nodePath["normalize"](filename);var ret=nodeFS["readFileSync"](filename);if(!ret&&filename!=nodePath["resolve"](filename)){

Error: parameter is not allowed here at line 4
    at unexpected (/Users/gam0022/Dropbox/glsl-minifier-sandbox/glsl-minifier/node_modules/glsl-parser/lib/index.js:661:11)
    at parameter_or_not (/Users/gam0022/Dropbox/glsl-minifier-sandbox/glsl-minifier/node_modules/glsl-parser/lib/index.js:415:18)
    at /Users/gam0022/Dropbox/glsl-minifier-sandbox/glsl-minifier/node_modules/glsl-parser/lib/index.js:701:16
    at parse_decl (/Users/gam0022/Dropbox/glsl-minifier-sandbox/glsl-minifier/node_modules/glsl-parser/lib/index.js:379:6)
    at write (/Users/gam0022/Dropbox/glsl-minifier-sandbox/glsl-minifier/node_modules/glsl-parser/lib/index.js:190:18)
    at reader (/Users/gam0022/Dropbox/glsl-minifier-sandbox/glsl-minifier/node_modules/glsl-parser/lib/index.js:166:5)
    at DestroyableTransform.write [as _transform] (/Users/gam0022/Dropbox/glsl-minifier-sandbox/glsl-minifier/node_modules/glsl-parser/stream.js:16:17)
    at DestroyableTransform.Transform._read (/Users/gam0022/Dropbox/glsl-minifier-sandbox/glsl-minifier/node_modules/readable-stream/lib/_stream_transform.js:184:10)
    at DestroyableTransform.Transform._write (/Users/gam0022/Dropbox/glsl-minifier-sandbox/glsl-minifier/node_modules/readable-stream/lib/_stream_transform.js:172:12)
    at doWrite (/Users/gam0022/Dropbox/glsl-minifier-sandbox/glsl-minifier/node_modules/readable-stream/lib/_stream_writable.js:237:10)

エラーになってしまいました。

glsl-minifierの作者に問い合わせたところ、GLSL ES 3.0(WebGL 2.0)は未対応とのことでした。

おわりに

GLSLのminifierを探していたところ、いくつも候補が見つかり、どれを採用すべきか見極めるのが大変でした。

今回は圧縮率という観点で比較しましたが、実行速度なども比較するべきかと思います。

ちなみに、今回検証に使った3種以外にも、下記のような様々なGLSLのminifierが存在します。


  1. 2017年に登場したOpenGL 4.6からVulkan同様のシェーダー中間表現SPIR-Vが標準化されましたが、WebGLとOpenGL ESについては執筆時点では未対応です。 

  2. 圧縮といっても、コードサイズの最小化は目的としておらず、コメントと空行が除去され、インライン展開などが処理されるような感じです。シェーダーのインスペクタから「Compile and show code」ボタンをクリックすると、各プラットフォーム向けにトランスパイルされたシェーダーを確認できます。 

gam0022
CGとWebに興味があります。 最近はレイマーチングが熱いです。
https://gam0022.net/
klab
モバイルオンラインゲーム、その他スマートフォン関連サービス、及びサーバーインフラ開発・運用
http://www.klab.com/jp/
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
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account