LoginSignup
23
7

More than 3 years have passed since last update.

フラグメントシェーダーでルーン文字のようなものをプロシージャルに生成する

Last updated at Posted at 2019-12-13

この記事はシェーダーアドベントカレンダー Advent Calendar 2019 14日目です!
https://qiita.com/advent-calendar/2019/shader-advent-calender-2019

13日目は@MachiaWorxさんのシェーダで音楽を作るmusic shaderの紹介
https://qiita.com/MachiaWorx/items/fe586e7ceefbf107254f
でした。

概要

魔法陣の飾りに、ルーン文字っぽいものをプロシージャルに生成したい
さやちゃんぐbotからルーン文字っぽくないという指摘を受けて調べたところ
https://coliss.com/articles/freebies/free-font-which-cannot-read.html
どちらかというと、アトランティック言語に似ていますね

■ 使用例
summon.png

完成図

image.png

precision mediump float;
uniform vec2  m;       // mouse
uniform float t;       // time
uniform vec2  r;       // resolution
uniform sampler2D smp; // prev scene

#define saturate(x) clamp(x,0.,1.)
#define _tail2x(p,n) (mod(p,2.)-1.)

float Hash( vec2 p, in float s ){
    return fract(sin(dot(vec3(p.xy,10.0 * abs(sin(s))),vec3(27.1,61.7, 12.4)))*273758.5453123);
}

float noise(in vec2 p, in float s){
  vec2 i = floor(p);
  vec2 f = fract(p);
  return mix(
    mix(Hash(i + vec2(0.,0.), s), Hash(i + vec2(1.,0.), s),f.x),
    mix(Hash(i + vec2(0.,1.), s), Hash(i + vec2(1.,1.), s),f.x),f.y) * s;
}

float fbm(vec2 p){
  float v = 0.0;
  v += noise(p*34., .1);
  v += noise(p*20., .04);
  return v;
}

vec2 tailY2x(vec2 p,float n){p*=n;return vec2(p.x,_tail2x(p.y,n));}

// signed distance
float sd(float d,float r){return r-d;} 
float dot2(vec2 p){return dot(p,p);}

void main(void){
  vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
  float l = saturate(abs(1./((p.x + fbm(p)) * 80.0))*sd(dot2(tailY2x(p+vec2(.1,0.),12.)),.9));
  gl_FragColor = vec4(l*vec3( 0.75, 0.5, .05 )*2., 1.0);
}

解説

実は単なるfbmです。
一般的なfbmの解説はしませんので、以下The Book of Shaderのfbmの解説の方を参考にしてください。
https://thebookofshaders.com/13/?lan=jp

ノイズ生成で補間しない

重要なのはノイズ発生部分

float noise(in vec2 p, in float s){
  vec2 i = floor(p);
  vec2 f = fract(p);
  f *= f * (3.0-2.0*f);//ノイズの頂点を滑らかに補間してつなぐ
  return mix(
    mix(Hash(i + vec2(0.,0.), s), Hash(i + vec2(1.,0.), s),f.x),
    mix(Hash(i + vec2(0.,1.), s), Hash(i + vec2(1.,1.), s),f.x),f.y) * s;
}

通常ノイズ生成時に補間関数を使って各ノイズの頂点を滑らかに補間してつなぎますが、文字のカクカクした感じを出したかったので、補間関数を取り除きます。何もしなければ線形補間になるのでカクカクした感じになります。fbmで良く雷のサンプルが上がってますが、イマイチなのはこのせいです。ノイズ生成時に滑らかに補間していては、どう頑張ってもイナズマのカクカクした感じは出せません。この補間関数部分に、イージング関数を使って変化を強調しても面白い効果が出せます。

image.png

float noise(in vec2 p, in float s){
  vec2 i = floor(p);
  vec2 f = fract(p);
  //f *= f * (3.0-2.0*f);//補間しない
  return mix(
    mix(Hash(i + vec2(0.,0.), s), Hash(i + vec2(1.,0.), s),f.x),
    mix(Hash(i + vec2(0.,1.), s), Hash(i + vec2(1.,1.), s),f.x),f.y) * s;
}

image.png

fbmの回数は2回まで

次にfbmの回数を2回にします。
fbmの回数が増えるとドンドン角が取れてしまいます。

float fbm(vec2 p){
  float v = 0.0;
  v += noise(p*34., .1);
  v += noise(p*20., .04);
  return v;
}

マスクを掛ける

void main(void){
  vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
  float l = saturate(abs(1./((p.x + fbm(p)) * 80.0)));
  gl_FragColor = vec4(l*vec3( 0.75, 0.5, .05 )*2., 1.0);
}

void main(void){
  vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
  float l = saturate(abs(1./((p.x + fbm(p)) * 80.0))*sd(dot2(tailY2x(p+vec2(.1,0.),12.)),.9));
  gl_FragColor = vec4(l*vec3( 0.75, 0.5, .05 )*2., 1.0);
}

image.png

sd(dot2(tailY2x(p+vec2(.1,0.),12.)),.9)

最後に、文字の形に見えるように円のグラデーションをタイリングしたマスクを掛けて完成です!

image.png

明日は、さやちゃんぐbotさんの80年代ゲーセン筐体っぽいレトロ調の画面をUnityシェーダーで作る。です。

23
7
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
7