テラByteの時代にキロByte

shader又はdemosceneに関係する事

つぶやきGLSLで使ってるレイベクトル

つぶやきGLSLでraymarchingをする時に使ってるレイベクトルについて書いてみます。twiglのgeekest(300es)のフォーマットで話は進めます。
通常のレイベクトルは

normalize(vec3(((FC.xy*2.-r)/r.y),2))

と、だいたい2.0くらいを焦点距離にして作る。それをつぶやきGLSLでは手を抜いて

vec3(((FC.xy-.5*r)/r.y),1)

として運用している。正規化をしてないのだから画面中央から離れれば離れる程ベクトルのスカラーが大きくなるのは想像出来る。raymarchigのロジックからしても破綻してる。なのに解っていても、これを使う理由は一つ。短く書けるからだ。これをそのまま使うと画面の端にはアーティファクトが発生する。さて、どうするか?対処法としてSDFに重みを付ける。

sdf(p)*0.5

みたいな感じ。
一度、絵を出してみる。
通常の場合

normalize(vec3(((FC.xy*2.-.r)/r.y),2))

f:id:gaziya:20210722134551p:plain 正規化を外した場合

vec3((FC.xy*2.-r)/r.y,2)

f:id:gaziya:20210722135115p:plain 当然ヒドイ事になる。
次に焦点距離とuvの範囲を変えてみる。

vec3((FC.xy-.5*r)/r.y,1)

f:id:gaziya:20210722135503p:plain だいぶマシになった。ここでSDFに重みを付けてみる。この場合のSDFは

(length(p)-.3)*.7

f:id:gaziya:20210722140018p:plain これで使えるレベルまで来た。ちょっとアーティファクトがあるけど気になるなら重みを変えるだけ。
これをすると画面が暗くなるけど、違うところで調整すれば良い。
こんなトリックで成立している。フラクタルをするなら、必ず重みの調整がついてまわるので*.7は文字数の内に入らない。フラクタルの初期倍率は、1.0なのだが、それを2.0にするだけ。
更に美味しい話がある。
通常のraymarchingの進行は。(進んだ距離をgとした場合)

vec3 rd=normalize(vec3(((FC.xy*2.-r)/r.y),2);
vec3 ro=vec3(0,0,-5);
....
p=g*rd+ro;
...

つぶやきGLSLで書くなら

p=g*normalize(vec3(((FC.xy*2.-r)/r.y),2);
p.z-=5.;

これが、normalize()を使わないなら

p=vec3((FC.xy-.5*r)/r.y*g,g-5.)

これで済む。これで、かなりの文字数が削減された。つぶやきGLSLで、ここまでは読み切れないと思うので説明してみました。あくまでも、つぶやきGLSLの上のスキルと承知して使ってください。ループの中で毎回レイベクトルを生成してるし、画面中央部分のスッテプ回数は通常より多くなるので、コストパフォーマンスは悪過ぎです。文字数優先の為にこうなったけど本能的に受け付けないかも。自分も最初はGPU酷使には抵抗がありました。

shaderでmat4を使う為のあれこれ

最近はGPUが強くなってきたのでraymarchingで複数のオブジェクトも大丈夫かなと思えるようになってきた。だけど扱うオブジェクトの数が増えてくれば姿勢の管理の大変になる。そこでmat4を使ってみます。 普通raymarchingをするならmat4なんて面倒で使いたくない。最初はそうだったけどスキニングを始めたらmat4が便利でビックリでした。そこで得た方法なんかを書いていこうと思います。ただ、1個2個程度なら使わない方が良いかな。

使い方

mat4は数学どうのこうのというより乗算する順番が全てです。どちらかというと感覚で使った方が良いんじゃないって思えてきます。使っていて変なら掛ける順番を入れ替えるだけ。まあそんな感じで使ってます。使う行列は移動行列と回転行列だけです。skew行列とか拡大行列とかありますが距離関数の特性上アーティファクトが発生するので使えません。かえって分かりやすくて良いです。基本姿勢操作だけと思ってください。
説明が難しいので、雰囲気で書いて順番を入れ替えていけば、そのうちにツボが分かります。ここは確かめてはいませんが使っている感じだとvertex shaderの行列処理の逆行列を使っているみたいです。vertex shaderの行列処理とは反対だと思った方が良さそうです。
使える行列を書きだします。

mat4 move(vec3 p) 
{
  return mat4(1,0,0,0,0,1,0,0,0,0,1,0,-p,1);
}

mat4 rotX(float a) 
{
  float s=sin(a),c=cos(a);
  return mat4(1,0,0,0,0,c,s,0,0,-s,c,0,0,0,0,1);
}

mat4 rotY(float a) 
{
  float s=sin(a),c=cos(a);
  return mat4(c,0,s,0,0,1,0,0,-s,0,c,0,0,0,0,1);
}

mat4 rotZ(float a) 
{
  float s=sin(a),c=cos(a);
  return mat4(c,s,0,0,-s,c,0,0,0,0,1,0,0,0,0,1);
}

mat4 rot(vec3 axis, float t) 
{
    vec3 a = normalize(axis);
    float s=sin(t),c=cos(t),r=1.0-c;
    return mat4(
      a.x*a.x*r+c,a.y*a.x*r+a.z*s,a.z*a.x*r-a.y*s,0,
      a.x*a.y*r-a.z*s,a.y*a.y*r+c,a.z*a.y*r+a.x*s,0,
      a.x*a.z*r+a.y*s,a.y*a.z*r-a.x*s,a.z*a.z*r+c,0,
      0,0,0,1);
}

mat4 pointAtX(vec3 d) 
{
    vec3 s = normalize(cross(d,vec3(0,1,0)));
    return inverse(mat4(d,0,cross(s,d),0,s,0,0,0,0,1));
}

mat4 pointAtY(vec3 d) 
{
    vec3 s = normalize(cross(d,vec3(0,0,1)));
    return inverse(mat4(s,0,d,0,cross(s,d),0,0,0,0,1));
}

mat4 pointAtZ(vec3 d) 
{
    vec3 s = normalize(cross(d,vec3(0,1,0)));
    return inverse(mat4(s,0,cross(s,d),0,d,0,0,0,0,1));
}

moveは移動行列(引数は移動ベクトル)
rotX,rotY,rotZは各軸ごとの回転行列(引数は回転角)
rotは任意軸の回転行列(引数は回転軸、回転角)
pointAtX,pointAtY,pointAtZは各軸を任意方向と一致させる行列。これについては私がつくったので怪しいところがあったら適宜直してください。これには補助ベクトルが必要ですが関数の中に組み込んじゃってます。これにはinserveを使っているのでes300でしか使えません。inserveでなくてもtransposeでもいいみたいですがWebGL1.0では使えませんでした。WebGL1.0で使いたいならmat4の中身をtransposeして書き換えてください。これはジンバルロックを起こす可能性があります。補助ベクトルと任意ベクトルの一致は避けてください。
これらをこんな感じで使ってます。

      mat4 m=mat4(1);
      m*=rot(normalize(vec3(1,2,3)),time);
      m*=move(vec3(1,2,3));
      m*=rotZ(time*0.2);
      m*=rotX(time*0.3);
      
     p=(m*vec4(p,1)).xyz;

サンプル

f:id:gaziya:20200616172301g:plain bit.ly

最後に

色々なパターンの行列処理を考えましたが使って覚えるしかないかなって思ってしまいました。グループで行列を作って置いて、それに移動行列を乗算して使ったりとか色々できると思います。個々に回転させてベクトルを足して移動とかしなくていいので管理は楽になると思います。任意軸の回転行列も使えるのでクォターニオンじゃなくてもmat4だけで済んでしまうのもいいと思います。

ChucKにmusic shaderを移植

ChucKにmusic shaderを移植してみた。 まだ覚え始めなので、怪しいところもあると思います。
今回使ってるadsrの関数には、バグがあるので鵜呑みにしないでください。ここでは正常に動いているけど、違うパターンでダメな時がありました。そのうち直します。

元のmusic shaderはこれ。

www.shadertoy.com

#define BPM 140.
#define A (15./BPM)

float adsr(float t, vec4 e, float s)
{  
    return max(0.0,
        min(1.0, t/max(0.0001, e.x)) 
        - min((1.0 - s) ,max(0.0, t - e.x)*(1.0 - s)/max(0.0001, e.y))
        - max(0.0, t - e.z)*s/max(0.0001, e.w));
}

float square(float f)
{
    return sign(fract(f)-0.5);
}

float kick(float t){
    return cos(315.0 * t - 10. * exp( -50. * t )+0.3)*adsr(t,vec4(0.0, 0.3, 0.0, 0.0), 0.0);
            +0.2*square(50.*t)* adsr(t,vec4(0.0, 0.05, 0.0, 0.0), 0.0);
}

float snare(float t)
{
    return square(3063.0*t*sin(t*8000.0)) * adsr(t,vec4(0.005, 0.08, 0.0, 0.0), 0.0);
}

float closeHihat(float t)
{
    return square(2763.0*t*sin(t*8500.0)) * adsr(t,vec4(0.0, 0.03, 0.0, 0.0), 0.0);
}

float openHihat(float t)
{
    return square(2763.0*t*sin(t*8300.0)) * adsr(t,vec4(0.0, 0.05, 0.03, 0.03), 0.5);
}

float sequence(int s,float t)
{
  float n =mod(t,A);
  for(int i=0;i<16;i++){
    if((s>>(int(t/A)-i)%16&1)==1)break;
    n+=A;
  }
  return n;
}

vec2 mainSound( float time )
{
    int i = int(floor(time/(A*16.)))&3;
    int velocity = int[](0x3030,0x3030,0x3030,0x3030)[i]>>(int(floor(time/A))&15)&1;
    float vol = 0.2 *(1.0+1.5*float(velocity));
    return vec2(
        0.0
        + 0.3 * kick(sequence(      int[](0x0c05,0x0c05,0x0c05,0x0c0c)[i],time))
        + 0.3 * snare(sequence(     int[](0x9290,0x9290,0x4290,0x4292)[i],time))
        + vol * closeHihat(sequence(int[](0x5555,0x5555,0x5555,0x5155)[i],time))
        + vol * openHihat(sequence( int[](0x0000,0x0000,0x0000,0x0400)[i],time))
    );
}

Chuckでサンプリング音源を使った奴。

140 => float bpm;
1::second * 15 / bpm=> dur tick;

fun void kick(){
    SndBuf buf => Gain g => dac;
    .25 => g.gain;
    me.dir() + "data/kick.wav" => buf.read;
    [0x0c05,0x0c05,0x0c05,0x0c0c]@=> int seq[];
    0 => int bar;
    while( true ){
        for(int i; i < 16; i++)
        {
            if (seq[bar%4]>>i&1==1){0 => buf.pos;}
            1::tick => now;    
        }
        bar++;
    }
}

fun void snare(){
    SndBuf buf => Gain g => dac;
    .4 => g.gain;
    me.dir() + "data/snare.wav" => buf.read;
    [0x9290,0x9290,0x4290,0x4292]@=> int seq[];
    0 => int bar;
    while( true ){
       for(int i; i < 16; i++)
        {
            if (seq[bar%4]>>i&1==1){0 => buf.pos;}
            1::tick => now;    
        }
        bar++;
    }
}

fun void hihat(){
    SndBuf buf => Gain g => dac;
    .25 => g.gain;
    0 => int bar;
    [0x5555,0x5555,0x5555,0x5155]@=> int seq[];
    me.dir() + "data/hihat.wav" => buf.read;
    while( true ){
        for(int i; i < 16; i++)
        {
            if (seq[bar%4]>>i&1==1){0 => buf.pos;}
            1::tick => now;    
        }
        <<<"bar",bar>>>;
        bar++;
    }
}

fun void openHihat(){
    SndBuf buf => Gain g => dac;
    .25 => g.gain;
    0 => int bar;
    [0x0000,0x0000,0x0000,0x0400]@=> int seq[];
    me.dir() + "data/hihat-open.wav" => buf.read;
    while( true ){
        for(int i; i < 16; i++)
        {
            if (seq[bar%4]>>i&1==1){0 => buf.pos;}
            1::tick => now;    
        }
        bar++;
    }
}

spork ~ kick();
spork ~ snare();
spork ~ hihat();
spork ~ openHihat();
3::minute=> now;    

ChucKで音源をmusic shaderに合わせた奴。

音源のclassをつくるのに参考にしたページ

ChucK : [Extend]

140 => float bpm;
1::second * 15 / bpm=> dur tick;

fun float square(float f)
{
    return (Math.floor(Math.sin(f))+0.5)*2.0;
}

fun float adsr(float t, float a, float d, float s, float r, float gt)
{  
    return Math.max(0.0,
        Math.min(1.0, t/Math.max(0.0001, a)) 
        - Math.min((1.0 - gt) ,Math.max(0.0, t - a)*(1.0 - gt)/Math.max(0.0001, d))
        - Math.max(0.0, t - s)*gt/Math.max(0.0001,r));
}

class MyKick extends Chugen
{
    100 => float time;
    samp/second => float dt;
    fun float tick(float in)
    {
        Math.cos(315*time-10*Math.exp(-50*time)+0.3)*adsr(time,0,0.3,0,0,0) => float g;
        time+dt=>time;
        return g;
    }
}

class MySnare extends Chugen
{
    100 => float time;
    samp/second => float dt;
    fun float tick(float in)
    {
        square(3063.0*time*Math.sin(time*8000.0))*adsr(time,0.005,0.08,0,0,0) => float g;
        time+dt=>time;
        return g;
    }
}

class MyCloseHihat extends Chugen
{
    100 => float time;
    samp/second => float dt;
    fun float tick(float in)
    {
        square(3063.0*time*Math.sin(time*8000.0)) * adsr(time,0.005,0.08,0,0,0) => float g;
        time+dt=>time;
        return g;
    }
}

class MyOpenHihat extends Chugen
{
    100 => float time;
    samp/second => float dt;
    fun float tick(float in)
    {
        square(2763*time*Math.sin(time*8500)) * adsr(time,0.0,0.05,0.03,0.03,0.5) => float g;
        time+dt=>time;
        return g;
    }
}

fun void kick(){
    MyKick o => Gain g => dac;
    0.3 => g.gain;
    [0x0c05,0x0c05,0x0c05,0x0c0c]@=> int seq[];
    0 => int bar;
    while( true ){
        for(int i; i < 16; i++)
        {
            if (seq[bar%4]>>i&1==1){0=>o.time;}
            1::tick => now;    
        }
        bar++;
    }
}

fun void snare(){
    MySnare o => Gain g => dac;
    0.2 => g.gain;
    [0x9290,0x9290,0x4290,0x4292]@=> int seq[];
    0 => int bar;
    while( true ){
       for(int i; i < 16; i++)
        {
            if (seq[bar%4]>>i&1==1){0 => o.time;}
            1::tick => now;    
        }
        bar++;
    }
}

fun void closeHihat(){
    MyCloseHihat o => Gain g => dac;
    0.2 => g.gain;
    0 => int bar;
    [0x5555,0x5555,0x5555,0x5155]@=> int seq[];   
    [0x3030,0x3030,0x3030,0x3030]@=> int vel[];
    
    while( true ){
        for(int i; i < 16; i++)
        {
            if (seq[bar%4]>>i&1==1){0 => o.time;}
            if (vel[bar%4]>>i&1==1){0.3 => g.gain;}else{0.2 => g.gain;}
            1::tick => now;    
        }
        bar++;
    }
}

fun void openHihat(){
    MyOpenHihat o => Gain g => dac;
    0.2 => g.gain;
    0 => int bar;
    [0x0000,0x0000,0x0000,0x0400]@=> int seq[];
    [0x3030,0x3030,0x3030,0x3030]@=> int vel[];
    while( true ){
        for(int i; i < 16; i++)
        {
            if (seq[bar%4]>>i&1==1){0 => o.time;}
            if (vel[bar%4]>>i&1==1){0.3 => g.gain;}else{0.2 => g.gain;}
            1::tick => now;    
        }
        bar++;
    }
}

spork ~ kick();
spork ~ snare();
spork ~ closeHihat();
spork ~ openHihat();
3::minute=> now;    

ここで使っているシーケンスについての説明は

qiita.com

ここにあります。

メモ

--shader(GLSL)の小技 19-- 敷居の低いmusic shader

グラフィクを書いていると音楽をつけたくなります。だけど、shaderで音を出すのは敷居が高すぎる。
そこで、敷居だけは低いByteBeatを紹介します。敷居は低いけど奥は深すぎです。でも。お気楽に手を出すのはには、ちょうどいい。 説明は、難しいのと、忘れてる部分がいっぱいあるので、ひたすらリンクを張りまくります。
ポイントは

(t*9&t>>4|t*5&t>>7|t*3&t/1024)-1

ここを書き換えるだけ。

www.youtube.com

telegra.ph

battleofthebits.org

www.reddit.com

byte beatの作り方を解説しています。ただ、逆ポーランド記法なので、よくわからないけど、理屈はわかります。 0-0-0-0.blog.so-net.ne.jp

このページはオンラインでbytebeatが出来ます。

greggman.com

最近、ShaderBoyがmusic shaderに対応したので、そちらのリンクも張っておきます。  ShaderBoyはオンラインGLSL(shadertoy対応)エディターです。

今回のshader (shadertoy rule)

vec2 mainSound( float time )
{
    int t = int(time * 8000.0);
    t = (t*9&t>>4|t*5&t>>7|t*3&t/1024)-1;
    return vec2(float(t & 0xff - 128) / 128.);
} 

--shader(GLSL)の小技 18-- 距離関数専用エディターを移植

はてなブログjavascriptが使えるようなので、以前、jsdo.itに書いた距離関数専用エディターを移植した。 たぶん雰囲気で使えると思います。 サンプルを幾つか用意しました。リストで距離関数を選択、エディターの中を適当に編集して、runボタンを押して遊んで見てください。
サンプルは、当時書いたままで、手直ししていないけど、距離関数の作り方の雰囲気はわかると思います。
リストの中に幾つかのサンプルがあります。
このエディターには、保存機能が付いてません。良い距離関数が出来たら、コピペで、どこかに移してください。

距離関数専用エディター (javascript)

 

--shader(GLSL)の小技 17-- twitterに書いた小技のリンク集

小出しだけど、twitterに小技を書いてきた。バラバラになっていたので、まとめてみた。

--shader(GLSL)の小技 16-- 湾曲と補正

距離関数に入れる前の座標を湾曲させます。単純にすると歪がでるので、それを補正します。

www.iquilezles.org

ここにも載っていますが、違う方法でやってみます。
まず、プロットに使うような関数を用意します。

float func(float x)
{
    float t = iTime*3.0;
    return 0.2*sin(x*3.0+t)+0.2*sin(x*2.0+t);
}

これで空間を歪めます。

p.y -= func(p.x);

こうです。次に、この関数を微分します。微分は極小の勾配なので、

float e = 0.01;
float g = (func(p.x+e)-func(p.x-e))/(2.0*e);

こうなると思っていたら、添削を受けました。

GLSL Sandbox をsandboxにupしたらforkされて

GLSL Sandbox こうなりました。

float g = (func(p.x+e)-func(p.x-e))/(PI*e);

この方が良さそうです。まあ、見た目の話ですので、どちらでも良いとは思いますが、お好みでどうぞ。
このgを使って補正をかけます。

p.y *= cos(atan(g));

これについては、やはり適当にやって落ち着いた式です。数学的根拠はありません。良さげなので使ってます。 以前 Shader - Shadertoy BETA ここで、ツッコまれました。
これで、空間の湾曲と補正が終わりです。あとは普通に距離関数を使うだけです。
この応用で

vec2 bend(vec2 p,  float d)
{
    p.y -= d*p.x*p.x;
    p.y *= cos(atan(2.0*d*p.x));
    return p;   
}

こんなモノも作りました。ベジェ曲線はコストが高いので、2次関数で湾曲させます。ただし、両端は歪が取れないので誤魔化す様に使ってます。
また、2次元の湾曲もできます。

p.zy -= func(p.x);
vec2 g = (func(p.x+e)-func(p.x-e))/(PI*e);
p.zy *= cos(atan(g));

こんな感じで処理すれば、良いと思います。
このやり方でアーティファクトが出るので距離関数の出力に補正(*0.8)をかけてます。

return deSeg(p,2.0,0.2) *0.8;

アーティファクトの対処法として、1以下の数字を乗算する方法と、もう一つ

return min(0.4, deSeg(p,2.0,0.2));

なんてのもアリです。

今回のshader (shadertoy rule)

mat2 rotate(float a)
{
    return mat2(cos(a),sin(a),-sin(a),cos(a));
}

float func(float x)
{
    float t = iTime*3.0;
    return 0.2*sin(x*3.0+t)+0.2*sin(x*2.0+t);
}

float deSeg(vec3 p, float h, float r)
{
    p.x -= clamp( p.x, -h, h );
    return length( p ) - r;
}
 
#define PI radians(360.0)
float map(vec3 p)
{
    float e = 0.01;
    p.y -= func(p.x);
    // 微分する。
    float g = (func(p.x+e)-func(p.x-e))/(PI*e);
    // 補正
    p.y *= cos(atan(g));
    return deSeg(p,2.0,0.2) *0.8;
}

vec3 doColor(vec3 p)
{
    return vec3(0.3,0.5,0.8);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv=(fragCoord*2.0-iResolution.xy)/iResolution.y;
    vec3 ro=vec3(0,3,5);
    ro.zx*=rotate(iTime*0.1);
    vec3 rd=normalize(vec3(uv,2));
    vec3 w=normalize(-ro);
    vec3 u=normalize(cross(w,vec3(0,1,0)));
    rd=mat3(u,cross(u,w),w)*rd;
    float d,i,t=0.0;
    vec3 p=ro;
    vec3 col=vec3(0);
    for(i=1.0;i>0.0;i-=1./50.0)
    {
        t+=d=map(p);
        if(d<0.001)
        {
            col=doColor(p);
            col*=i*i;
            break;
        }
        p+=rd*d;
    }
   fragColor=vec4(col,1.0);
}