[連載 GLSL 物語] チカラが欲しいか……（第四話）

## 突然のココア

たけのこ隊長「覚悟しろ！ 権造さんの倅よ！」

左「うわあああ！」

恐ろしい闘気を身にまとったたけのこ隊長は、今も腰が抜けてしまって動けない左利きに向かって容赦なく突撃していく。

左利きは、あまりの恐怖に意識を失いかけていた。

左（親父を知っていると思しき、よくわからん人の話をまったく聞かない軍人に、俺は殺されるのか……）

消えてしまいそうになる意識の片隅に、まるで走馬灯のように幼い頃の思い出が蘇ってくる。

とてもつらいことだが、そのほとんどは、悲しいだけの思い出だ。

左（英雄の息子として生まれ、生きてきた……考えてみれば、親父が下手に有名人だったがために、ろくなきのこ生（人生）じゃなかったぜ……）

優秀な父親と比較される、二世きのこの悲しさ……

そして持って生まれた歪な左利きプレッツェル……

左利きは、話もよく飲み込めないまま、その短い生涯の終わりを感じていた。

しかしそのとき、左利きのこころのなかに、誰かの囁くような声が聞こえてきたのだ。

？？？「チカラが……欲しいか……」

左「ッッッッッ！？！？！？？！？」

```glsl
precision mediump float;
uniform vec2  resolution;
uniform vec2  mouse;
uniform float time;
uniform sampler2D backbuffer;

struct Intersect{
	float dist;
	vec3  color;
};
const float PI = 3.1415926;
const float PI2 = PI * 2.0;
const vec3 cColor = vec3(0.25, 0.1, 0.0);
const vec3 kColor = vec3(0.9, 0.8, 0.4);

float rnd(vec2 n) {
	return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453);
}
float noise(vec2 p){
	vec2 v = floor(p);
	vec2 u = fract(p);
	u = u * u * (3.0 - 2.0 * u);
	float r = mix(
		mix(rnd(v), rnd(v + vec2(1.0, 0.0)), u.x),
		mix(rnd(v + vec2(0.0, 1.0)), rnd(v + vec2(1.0, 1.0)), u.x),
		u.y
	);
	return r * r;
}
float snoise(vec2 p){
	float n = 0.0;
	for(float i = 0.0; i < 6.0; ++i){
		float v = pow(2.0, 2.0 + i);
		float w = pow(2.0, -1.0 - i);
		n += noise(p * v) * w;
	}
	return n;
}
float seamless(vec2 p, vec2 q, vec2 r){
	return snoise(vec2(p.x,	   p.y	  )) *		q.x  *		q.y  +
		   snoise(vec2(p.x,	   p.y + r.y)) *		q.x  * (1.0 - q.y) +
		   snoise(vec2(p.x + r.x, p.y	  )) * (1.0 - q.x) *		q.y  +
		   snoise(vec2(p.x + r.x, p.y + r.y)) * (1.0 - q.x) * (1.0 - q.y);
}
float smoothMin(float d1, float d2, float k){
	float h = exp(-k * d1) + exp(-k * d2);
	return -log(h) / k;
}
float dChoco(vec3 p){
	float rad = time * 0.2;
	mat3 m = mat3(cos(rad), 0.0, -sin(rad), 0.0, 1.0, 0.0, sin(rad), 0.0, cos(rad));
	vec3 q = m * (p + vec3(0.0, -0.55, 0.0)) * vec3(1.0, 1.2, 1.0);
	float len = sin(atan(q.z, q.x) * 7.0) * 0.01 * PI;
	return length(q) - 1.0 + len + step(p.y, -0.4) + pow(p.y, 1.0);
}
float dCylinder(vec3 p, vec2 r){
	float rad = time * 0.2;
	mat3 m = mat3(cos(rad), 0.0, -sin(rad), 0.0, 1.0, 0.0, sin(rad), 0.0, cos(rad));
	float s = -cos(p.y * 2.0) * 0.1 - 0.05;
	float l = length(p + vec3(0.0, 1.25, 0.0)) - 0.4;
	vec3 q = m * p + vec3(s, 0.5, 0.0);
	vec2 d = abs(vec2(length(q.xz), q.y)) - r;
	return smoothMin(min(max(d.x, d.y), 0.0), l, 32.0) + length(max(d, 0.0)) - 0.2;
}
Intersect distanceHub(vec3 p){
	float choco = dChoco(p);
	float cylin = dCylinder(p, vec2(0.1, 0.9));
	Intersect i;
	i.dist = min(choco, cylin);
	i.color = choco < cylin ? cColor : kColor;
	return i;
}
vec3 genNormal(vec3 p){
	float d = 0.001;
	return normalize(vec3(
		distanceHub(p + vec3(  d, 0.0, 0.0)).dist - distanceHub(p + vec3( -d, 0.0, 0.0)).dist,
		distanceHub(p + vec3(0.0,   d, 0.0)).dist - distanceHub(p + vec3(0.0,  -d, 0.0)).dist,
		distanceHub(p + vec3(0.0, 0.0,   d)).dist - distanceHub(p + vec3(0.0, 0.0,  -d)).dist
	));
}

void main(){
	vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y);
	vec3 cPos = vec3(0.0,  0.0,  3.0);
	vec3 cDir = vec3(0.0,  0.0, -1.0);
	vec3 cUp  = vec3(0.0,  1.0,  0.0);
	vec3 cSide = cross(cDir, cUp);
	float targetDepth = 1.0;
	vec3 ray = normalize(cSide * p.x + cUp * p.y + cDir * targetDepth);

	float dist = 0.0;
	float rLen = 0.0;
	vec3  rPos = cPos;
	Intersect intersect;
	for(int i = 0; i < 256; ++i){
		intersect = distanceHub(rPos);
		rLen += intersect.dist * 0.5;
		rPos = cPos + ray * rLen;
	}

	float tk = min(time * 0.2, 1.0);
	float tf = min((time - 4.0) * 0.2, 1.0);
	if(abs(intersect.dist) < 0.001){
		float fog = smoothstep(0.0, 15.0, length(rPos - cPos));
		vec3  normal = genNormal(rPos);
		vec3  light = normalize(vec3(mouse + 2.0, 3.0));
		float diff = max(dot(normal, light), 0.1);
		vec3  eye = reflect(normalize(rPos - cPos), normal);
		float speculer = clamp(dot(eye, light), 0.0, 1.0);
		vec3 specColor = pow(speculer, 20.0) + cColor;
		gl_FragColor = vec4(vec3(diff) * intersect.color * tk - fog + specColor, 1.0);
	}else{
		float f = pow((0.5 + tf * 0.5) / length(p), 3.0);
		float map = 2.0;
		vec2 c = vec2((atan(p.y, p.x) + PI) / PI2 * 2.0, mod(length(p) * 0.25 - time, map));
		float n = seamless(c, c / map, vec2(map));
		gl_FragColor = vec4(vec3(0.1, 0.3, 1.0) * n * f, 1.0);
	}
}
```

（[GLSL Editor](https://doxas.org/work/glsl_editor/) に貼り付けて実行してね！）

![kinoco_02.jpg](https://qiita-image-store.s3.amazonaws.com/0/39638/a1a01115-ea9a-31e8-1756-d24ee94bcc02.jpeg)

左「うおおおおっ！！！」

左利きが死を覚悟したそのとき、彼のこころのなかで何かが弾けた！

？？？「チカラが欲しいか……！！」

隊長「な！ なんだと！！！」

突如、左利きの全身にチカラが漲り、青き炎が燃え上がった。

そのチカラがどこから湧いてきたのか、左利き自身にさえもわからない。だがこのチカラは……いや、このチカラがあれば、俺も親父のように戦える！　生まれてこの方、誰にも認められることなく生きてきた左利きの、蔑まれ続けてきたそのきのこ生の最後の輝きなのだろうか。

左利きは今、謎のチカラに目覚めたのだった。

左「隊長さんよ……親父とどんな縁があるんだか知らねえが、その息子をたけのこドリルブレイクで貫こうとしたこと、後悔させてやるぜ！」

左利きは、明らかに舞い上がっていた。

その臭すぎるセリフは、書いているこちらが恥ずかしくなるほどだ。

隊長「クソ！ なんだかよくわからんが、とにかくお前はやはり危険だ。この場で排除する」

左「謎のチカラを手にした俺に勝てると思うか！ あったかココアにしてやんよ！」

```glsl
precision mediump float;
uniform vec2  resolution;
uniform vec2  mouse;
uniform float time;
uniform sampler2D backbuffer;

struct Intersect{
	float dist;
	vec3  color;
};
const float PI = 3.1415926;
const float PI2 = PI * 2.0;
const vec3 cColor = vec3(0.7, 0.4, 0.2);
const vec3 kColor = vec3(0.9, 0.9, 0.9);

float rnd(vec2 n) {
	return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453);
}
float noise(vec2 p){
	vec2 v = floor(p);
	vec2 u = fract(p);
	u = u * u * (3.0 - 2.0 * u);
	float r = mix(
		mix(rnd(v), rnd(v + vec2(1.0, 0.0)), u.x),
		mix(rnd(v + vec2(0.0, 1.0)), rnd(v + vec2(1.0, 1.0)), u.x),
		u.y
	);
	return r * r;
}
float snoise(vec2 p){
	float n = 0.0;
	for(float i = 0.0; i < 6.0; ++i){
		float v = pow(2.0, 2.0 + i);
		float w = pow(2.0, -1.0 - i);
		n += noise(p * v) * w;
	}
	return n;
}
float seamless(vec2 p, vec2 q, vec2 r){
	return snoise(vec2(p.x,	   p.y	  )) *		q.x  *		q.y  +
		   snoise(vec2(p.x,	   p.y + r.y)) *		q.x  * (1.0 - q.y) +
		   snoise(vec2(p.x + r.x, p.y	  )) * (1.0 - q.x) *		q.y  +
		   snoise(vec2(p.x + r.x, p.y + r.y)) * (1.0 - q.x) * (1.0 - q.y);
}
float smoothMin(float d1, float d2, float k){
	float h = exp(-k * d1) + exp(-k * d2);
	return -log(h) / k;
}
float dChoco(vec3 p, vec2 r){
	float f = sin(length((p.xz + vec2(0.5, -0.5)) * 10.0) - time * 3.0) * 0.025;
	vec2 d = abs(vec2(length(p.xz), p.y - f)) - r;
	return min(max(d.x, d.y), 0.0) + length(max(d, 0.0));
}
float dCylinder(vec3 p, vec2 r){
	vec2 d = abs(vec2(length(p.xz), p.y)) - r;
	return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - 0.1;
}
float dTorus(vec3 p){
	vec3 q = p * vec3(1.75, 1.25, 1.0) + vec3(-1.45, 0.0, 0.0);
    vec2 t = vec2(0.5, 0.1);
    vec2 r = vec2(length(q.xy) - t.x, q.z);
    return length(r) - t.y;
}
Intersect distanceHub(vec3 p){
	float choco = dChoco(p, vec2(0.5, 0.4));
	float cylin = dCylinder(p, vec2(0.6, 0.5));
	float hole = dCylinder(p, vec2(0.5, 0.6));
	float torus = dTorus(p);
	Intersect i;
	i.dist = min(torus, min(choco, max(cylin, -hole)));
	i.color = choco < max(cylin, -hole) ? cColor : kColor;
	return i;
}
vec3 genNormal(vec3 p){
	float d = 0.001;
	return normalize(vec3(
		distanceHub(p + vec3(  d, 0.0, 0.0)).dist - distanceHub(p + vec3( -d, 0.0, 0.0)).dist,
		distanceHub(p + vec3(0.0,   d, 0.0)).dist - distanceHub(p + vec3(0.0,  -d, 0.0)).dist,
		distanceHub(p + vec3(0.0, 0.0,   d)).dist - distanceHub(p + vec3(0.0, 0.0,  -d)).dist
	));
}

void main(){
	vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y);
	vec3 cPos = vec3(0.0,  1.5,  2.0);
	vec3 cDir = vec3(0.0, -0.6, -0.8);
	vec3 cUp  = vec3(0.0,  0.8, -0.6);
	vec3 cSide = cross(cDir, cUp);
	float targetDepth = 1.0;
	vec3 ray = normalize(cSide * p.x + cUp * p.y + cDir * targetDepth);

	float dist = 0.0;
	float rLen = 0.0;
	vec3  rPos = cPos;
	Intersect intersect;
	for(int i = 0; i < 256; ++i){
		intersect = distanceHub(rPos);
		rLen += intersect.dist * 0.5;
		rPos = cPos + ray * rLen;
	}

	float n = clamp(seamless(p - vec2(sin(time * 0.05), time * 0.2), vec2(2.0), vec2(2.0)), 0.0, 1.0);
	float m = clamp(seamless(p - vec2(sin(time * 0.2), time * 0.1), vec2(2.0), vec2(2.0)), 0.0, 1.0);
	float l = clamp(seamless(p - vec2(cos(time * 0.15), time * 0.15), vec2(2.0), vec2(2.0)), 0.0, 1.0);
	float c = max(cos(p.x * 3.0), 0.0);
    float f = max(cos(p.x * 2.5) - (-(p.y - 0.5) * 1.5), 0.0);
	float smoke = n * m * l * f * c * 0.35;
	if(abs(intersect.dist) < 0.001){
		float fog = smoothstep(0.0, 55.0, length(rPos - cPos));
		vec3  normal = genNormal(rPos);
		vec3  light = normalize(vec3(0.5, 2.0, 1.5));
		float diff = max(dot(normal, light), 0.3);
		vec3  eye = reflect(normalize(rPos - cPos), normal);
		float speculer = clamp(dot(eye, light), 0.0, 1.0);
		speculer = pow(speculer, 20.0);
		gl_FragColor = vec4(vec3(diff) * intersect.color - fog + speculer + smoke, 1.0);
	}else{
		gl_FragColor = vec4(vec3(smoke), 1.0);
	}
}
```

（[GLSL Editor](https://doxas.org/work/glsl_editor/) に貼り付けて実行してね！）

![cocoa.jpg](https://qiita-image-store.s3.amazonaws.com/0/39638/7ab856bc-9333-706a-f68c-3f65e458e461.jpeg)

突如、謎のチカラに目覚めた左利き……

そして左利きの親父、もはやその存在感が霞のように薄くなってしまった権造さん……と、なんかしらんが因縁がありそうなたけのこ隊長。

果たしてふたりの戦いはどんな結末を迎えるのか！

次回、緊張の最終話。

to be continued!

※この物語はフィクションです。実在の人物や団体などとは関係ありません。