[連載 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 fColor = vec3(0.2, 0.7, 0.1);
const vec3 kColor = vec3(0.9, 0.8, 0.4);

float smoothMin(float d1, float d2, float k){
	float h = exp(-k * d1) + exp(-k * d2);
	return -log(h) / k;
}
float dChocokino(vec3 p){
	vec3 q = (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 dCylinderkino(vec3 p, vec2 r){
	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 = 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;
}
float waveline(vec2 p){
	float f = smoothstep(0.975, 1.0, cos(p.x * 2.5) - (p.y * 2.5) - 0.75);
	float g = smoothstep(0.975, 1.0, cos(p.x * PI + PI) - (p.y * 4.0) - 0.5);
	float h = smoothstep(0.975, 1.0, cos(p.x * 2.5) - (p.y * 2.5) + 0.5);
	return mix(mix(mix(0.0, 0.3, h), 0.4, g), 0.6, f);
}
float dChoco(vec3 p){
	float e = 1.0 - p.y * 0.7;
	float w = waveline(vec2(atan(p.z, p.x) / PI, p.y + 0.25)) * 0.15;
	return length((p + vec3(0.0, -0.5, 0.0)) * vec3(e, 0.5, e)) + p.y - 0.5 + step(p.y, -1.2) - w;
}
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.25 + step(-1.0, p.y);
}
float dFloor(vec3 p){
	return dot(p, vec3(0.0, 1.0, 0.0)) + 1.5;
}
Intersect distanceHub(vec3 p){
	Intersect i;
	float flor = dFloor(p);
	vec3 q = p + vec3(3.0, 0.0, 0.0);
	if(p.x < 0.0){
		float chocokino = dChocokino(q);
		float cylinkino = dCylinderkino(q, vec2(0.1, 0.9));
		i.dist = min(min(chocokino, flor), cylinkino);
		i.color = cylinkino < min(chocokino, flor) ? kColor : chocokino < flor ? cColor : fColor;
	}else{
		q = p;
		if(p.x > 6.0){
			q = vec3(mod(q, 3.0) - 1.5);
		}else{
			q = q - vec3(3.0, 0.0, 0.0);
		}
		q = vec3(q.x, p.y, q.z);
		float choco = dChoco(q);
		float cylin = dCylinder(q, vec2(0.5, 2.0));
		i.dist = min(cylin, min(choco, flor));
		i.color = cylin < min(choco, flor) ? kColor : choco < flor ? cColor : fColor;
	}
	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);
	float f = mod(time, 10.0);
	float t = exp(smoothstep(8.0, 9.0, f) * 4.0) - 1.0;
	float u = smoothstep(0.0, 1.0, f);
	vec3 cPos = vec3(0.0,  t,  5.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;
	}

	if(abs(intersect.dist) < 0.001){
		float fog = smoothstep(0.0, 30.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;
		float d = min(1.0, smoothstep(14.5, 15.0, length(rPos - vec3(25.0 - f * 2.0, 0.0, 0.0))) + 0.5);
		vec3 destColor = vec3(diff) * intersect.color + fog + specColor;
		gl_FragColor = vec4(destColor * d * u, 1.0);
	}else{
		gl_FragColor = vec4(vec3(u), 1.0);
	}
}
```

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

![kinocotakenoco.jpg](https://qiita-image-store.s3.amazonaws.com/0/39638/32b9dcc7-c700-55b5-7709-6699fe2cbfa7.jpeg)

左「な、なんだこの影は！」

隊長「ま、まさか！」

左「うっ……また、また……声が……ッ！」

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

あまりにも巨大な影に、大地がまるで飲み込まれていくようだ。

権造さんが戦争を終結させたほどの、超巨大なチカラ……太陽神が、今ふたたび左利きの激しい闘志によって召喚されてしまったのだ！

ズゴゴオゴゴオゴゴゴゴゴゴ……

隊長「まずい！ 総員退避だ！ いそげ！」

左「そんな馬鹿な！」

![aporo_01.jpg](https://qiita-image-store.s3.amazonaws.com/0/39638/a8df7fda-1ca8-7466-de28-930ced9ef05b.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 chocoColor = vec3(0.25, 0.1, 0.0);
const vec3 berryColor = vec3(0.75, 0.3, 0.6);
const vec3 floorColor = vec3(0.2, 0.7, 0.1);
const vec3 aporoColor = vec3(0.0, 0.9, 0.7);

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 < 4.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 circle(vec2 p, vec2 o, float r, vec2 offset){
	return step(length(p - o - offset) - r, 0.0);
}
float arc(vec2 p, vec2 o, float r, float h, vec2 offset){
	return step(length(p - o - offset) - r, 0.0) * step(0.0, length(p - o - offset) - h);
}
float rect(vec2 p, vec2 o, vec2 q, vec2 offset){
	vec2 r = p - o - offset;
	return step(abs(r.x) - abs(q.x), 0.0) * step(abs(r.y) - abs(q.y), 0.0);
}
float ellipse(vec2 p, vec2 o, vec2 q, vec2 offset){
	return rect(p, o, q, offset) + circle(p, o + vec2(0.0, q.y), q.x, offset) + circle(p, o - vec2(0.0, q.y), q.x, offset);
}
vec2 rotation(vec2 p, float r){
    float s = sin(r);
    float c = cos(r);
    return mat2(c, s, -s, c) * p;
}
vec4 aporo(vec2 p, vec2 offset){
    vec2 q = rotation(p - offset, radians(22.5)) + offset;
    float c = 0.0;
    // a
    c += rect(p, vec2(-0.65, 0.25), vec2(0.2, 0.05), offset);
    c += ellipse(p, vec2(-0.45, 0.125), vec2(0.1, 0.075), offset);
    c += rect(q, vec2(-0.6, -0.275), vec2(0.055, 0.125), offset);
    c += circle(p, vec2(-0.7625, -0.1), 0.12, offset);
    // po
    c += rect(p, vec2(-0.1, 0.235), vec2(0.2, 0.05), offset);
    c += rect(p, vec2(-0.05, 0.1), vec2(0.05, 0.3), offset);
    c += rect(p, vec2(-0.1775, 0.02), vec2(0.05, 0.115), offset);
    c += rect(p, vec2(0.0775, 0.02), vec2(0.05, 0.115), offset);
    c += arc(p, vec2(0.175, 0.245), 0.11, 0.05, offset);
    c += circle(p, vec2(-0.25, -0.1), 0.12, offset);
    c += circle(p, vec2(0.15, -0.1), 0.12, offset);
    // ro
    c += rect(p, vec2(0.565, 0.2475), vec2(0.15, 0.05), offset);
    c += rect(p, vec2(0.565, -0.17), vec2(0.15, 0.05), offset);
    c += ellipse(p, vec2(0.4125, 0.04), vec2(0.1, 0.1575), offset);
    c += ellipse(p, vec2(0.725, 0.04), vec2(0.1, 0.1575), offset);
    return vec4(aporoColor * min(c, 1.0), 0.0);
}
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.5, 0.0));
	float len = sin(atan(q.z, q.x) * 25.0) * 0.1 * PI - 0.2;
	float edge = 0.005 / (p.y + 1.25);
	return min(length(q) - 1.0 + p.y, max(length(q) - 1.05 + p.y, length(q) - 0.9 + len + p.y)) + edge + step(p.y, -1.25) * 10.0;
}
float dFloor(vec3 p){
	float n = snoise(p.xz);
	return dot(p, vec3(0.0, 1.0, 0.0)) + 1.0 - n * 0.15;
}
Intersect distanceHub(vec3 p){
	vec3 q = p - vec3(0.0, 2.5 - min(time * 0.25, 2.25), 0.0);
	float choco = dChoco(q);
	float flor = dFloor(p);
	Intersect i;
	i.dist = min(choco, flor);
	i.color = q.y > -0.85 ? berryColor : choco < flor ? chocoColor : floorColor;
	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);
	vec2 v = vec2(0.5 - snoise(vec2(time * 10.0, time)), 0.5 - snoise(vec2(time, time * time)));
	float b = smoothstep(9.0, 9.5, time) * (1.0 - smoothstep(9.25, 12.0, time));
	float c = smoothstep(12.0, 15.0, time);
	vec3 cPos = vec3(0.0,  0.5,  4.0) + vec3(v.xyx) * 0.5 * b;
	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 = 2.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 < 512; ++i){
		intersect = distanceHub(rPos);
		rLen += intersect.dist * 0.15;
		rPos = cPos + ray * rLen;
	}

	vec4 destColor = vec4(vec3(0.0), 1.0);
	if(abs(intersect.dist) < 0.001){
		float fog = smoothstep(0.0, 10.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) + chocoColor;
		float f = smoothstep(5.0, 9.0, time) * 3.0;
		float s = smoothstep(0.0, 1.5 + f, rPos.y);
		destColor = vec4(vec3(diff) * intersect.color + s + fog + specColor, 1.0);
	}else{
		destColor = vec4(1.0);
	}
	gl_FragColor = destColor - aporo(p.xy, vec2(0.0, 2.5 - c * 2.0));
}
```

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

![aporo_02.jpg](https://qiita-image-store.s3.amazonaws.com/0/39638/2131ea81-123a-38c3-263f-6e6bafc1e6bb.jpeg)

あああああああああああああああああああ！！！！！

このオチが言いたいためだけに！

たったそれだけのために！

なんで俺はこんな阿呆なことを思いついてしまったんだろうか……

どうか……どうかみなさん許してください……

あああああ……

疲れた……

> この連載で登場したシェーダに使われているいくつかのテクニックについては、いずれ日をあらためて解説記事を書こうと思います。
> 
> こんなクッソくだらない記事を最後まで読んでくださった方……マジでありがとうございます！ シェーダを使って表現するための、わずかばかりにでもヒントになったとしたらうれしいです。

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