Files
aibis-dream/Assets/Render/OceanAsciiDoor.shader
T
2026-03-06 16:45:02 +08:00

342 lines
13 KiB
GLSL

Shader "Custom/OceanAsciiDoor"
{
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
[Header(Grid)]
_GridCols ("Grid Columns", Range(20, 200)) = 80
_GridRows ("Grid Rows", Range(15, 150)) = 55
[Header(Wave)]
_WaveSpeed ("Wave Speed", Range(0, 1.0)) = 0.15
_WaveCount ("Wave Count", Range(2, 10)) = 5
_ShoreRows ("Shore Rows", Range(1, 8)) = 3
[Header(Intensity)]
_CrestBright ("Crest Brightness", Range(0.3, 1)) = 0.85
_BodyBright ("Body Brightness", Range(0.1, 1)) = 0.65
[Header(Background)]
_BgColor ("Background Color", Color) = (0,0,0,1)
}
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}
Cull Off
Lighting Off
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 uv : TEXCOORD0;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
fixed4 _BgColor;
float _GridCols;
float _GridRows;
float _WaveSpeed;
float _WaveCount;
float _ShoreRows;
float _CrestBright;
float _BodyBright;
// ─── Bitmap font: 5 wide x 7 tall ───────────────────────────
// charId mapping (matches HTML CREST_CHARS order):
// 0 = ~ (tilde)
// 1 = ≈ (approx equal)
// 2 = ∿ (sine wave)
// 3 = 0 (zero digit)
// 4 = 1 (one digit)
//
// HTML: CREST_CHARS = ['~','≈','~','≈','∿','~','~','≈']
// indices: 0 1 0 1 2 0 0 1
// So crestCharMap[8] = {0,1,0,1,2,0,0,1}
float glyphPixel(int charId, int px, int py)
{
// ~ (tilde) — classic wave shape
if (charId == 0)
{
if (py == 2) return (px == 0 || px == 1) ? 1.0 : 0.0;
if (py == 3) return (px == 0 || px == 2 || px == 4) ? 1.0 : 0.0;
if (py == 4) return (px == 3 || px == 4) ? 1.0 : 0.0;
return 0.0;
}
// ≈ (approx equal — stacked double tilde)
if (charId == 1)
{
if (py == 1) return (px == 0 || px == 1) ? 1.0 : 0.0;
if (py == 2) return (px == 0 || px == 2 || px == 4) ? 1.0 : 0.0;
if (py == 3) return (px == 3 || px == 4) ? 1.0 : 0.0;
if (py == 4) return (px == 0 || px == 1) ? 1.0 : 0.0;
if (py == 5) return (px == 0 || px == 2 || px == 4) ? 1.0 : 0.0;
if (py == 6) return (px == 3 || px == 4) ? 1.0 : 0.0;
return 0.0;
}
// ∿ (sine wave — S-curve)
if (charId == 2)
{
if (py == 0) return (px == 1 || px == 2) ? 1.0 : 0.0;
if (py == 1) return (px == 0 || px == 3) ? 1.0 : 0.0;
if (py == 2) return (px == 0) ? 1.0 : 0.0;
if (py == 3) return (px == 0 || px == 4) ? 1.0 : 0.0;
if (py == 4) return (px == 4) ? 1.0 : 0.0;
if (py == 5) return (px == 1 || px == 4) ? 1.0 : 0.0;
if (py == 6) return (px == 2 || px == 3) ? 1.0 : 0.0;
return 0.0;
}
// 0 (zero digit)
if (charId == 3)
{
if (py == 0) return (px >= 1 && px <= 3) ? 1.0 : 0.0;
if (py == 1) return (px == 0 || px == 4) ? 1.0 : 0.0;
if (py == 2) return (px == 0 || px == 3 || px == 4) ? 1.0 : 0.0;
if (py == 3) return (px == 0 || px == 2 || px == 4) ? 1.0 : 0.0;
if (py == 4) return (px == 0 || px == 1 || px == 4) ? 1.0 : 0.0;
if (py == 5) return (px == 0 || px == 4) ? 1.0 : 0.0;
if (py == 6) return (px >= 1 && px <= 3) ? 1.0 : 0.0;
return 0.0;
}
// 1 (one digit)
if (charId == 4)
{
if (py == 0) return (px == 2) ? 1.0 : 0.0;
if (py == 1) return (px == 1 || px == 2) ? 1.0 : 0.0;
if (py >= 2 && py <= 5) return (px == 2) ? 1.0 : 0.0;
if (py == 6) return (px >= 1 && px <= 3) ? 1.0 : 0.0;
return 0.0;
}
return 0.0;
}
// HTML: CREST_CHARS = ['~','≈','~','≈','∿','~','~','≈']
// Map index 0..7 to charId
int crestCharId(int idx)
{
// 0:~ 1:≈ 2:~ 3:≈ 4:∿ 5:~ 6:~ 7:≈
if (idx == 1 || idx == 3 || idx == 7) return 1; // ≈
if (idx == 4) return 2; // ∿
return 0; // ~
}
// ─── Wave math (matches HTML prototype) ──────────────────────
float bodyNoise(float c, float r, float t)
{
return (
sin(c * 1.3 + r * 0.7 + t * 0.11) * 0.35 +
sin(c * 0.37 + r * 1.17 + t * 0.07) * 0.30 +
sin(c * 2.1 + r * 0.31 + t * 0.19) * 0.20 +
sin(c * 0.71 + r * 2.03 + t * 0.05) * 0.15
) * 0.5 + 0.5;
}
float hash11(float p)
{
p = frac(p * 0.1031);
p *= p + 33.33;
p *= p + p;
return frac(p);
}
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.color = v.color * _Color;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float2 uv = i.uv;
float r = (1.0 - uv.y) * _GridRows;
float c = uv.x * _GridCols;
float shoreY = _GridRows - _ShoreRows;
float sp = shoreY / max(1.0, _WaveCount);
float cellFracX = frac(c);
float cellFracY = frac(r);
int glyphX = (int)(cellFracX * 5.0);
int glyphY = (int)(cellFracY * 7.0);
glyphX = clamp(glyphX, 0, 4);
glyphY = clamp(glyphY, 0, 6);
float cellC = floor(c);
float cellR = floor(r);
// HTML's T increments by 1 per frame (~30fps)
float T = _Time.y * _WaveSpeed * 30.0;
float time = _Time.y;
// ── Wave layer computation ───────────────────────────────
float bestDepth = 999.0;
float bestWaveY = -999.0;
for (float wi = 0; wi < _WaveCount + 2; wi++)
{
float wSeed = hash11(wi * 7.31);
float wSpeed = 0.12 + wSeed * 0.06;
float baseY = -sp + wi * sp;
baseY += T * wSpeed * (1.0 + saturate(baseY / max(1.0, shoreY)) * 0.4);
baseY = baseY - floor(baseY / (_GridRows + sp)) * (_GridRows + sp) - sp;
float p1 = hash11(wi * 3.17) * 6.2832;
float p2 = hash11(wi * 5.23) * 6.2832;
float p3 = hash11(wi * 9.41) * 6.2832;
p1 += time * 0.008;
p2 -= time * 0.005;
p3 += time * 0.011;
float a1 = 1.5 + hash11(wi * 2.71) * 1.0;
float a2 = 0.45 + hash11(wi * 4.13) * 0.3;
float a3 = 0.18 + hash11(wi * 6.67) * 0.12;
float f1 = 0.045 + hash11(wi * 1.23) * 0.025;
float f2 = 0.10 + hash11(wi * 8.37) * 0.05;
float f3 = 0.19 + hash11(wi * 11.03) * 0.07;
float crest = baseY
+ sin(cellC * f1 + p1) * a1
+ sin(cellC * f2 + p2) * a2
+ sin(cellC * f3 + p3) * a3;
float d = cellR - crest;
if (d >= -0.5 && d < bestDepth)
{
bestDepth = d;
bestWaveY = crest;
}
}
// ── Determine brightness and character ───────────────────
float bright = 0.0;
int charId = 0;
if (bestWaveY > -900.0 && cellR < shoreY)
{
float depthVal = bestDepth;
float depthRatio = saturate(depthVal / max(0.001, sp));
float proximity = saturate(bestWaveY / max(0.001, shoreY));
if (depthVal < 1.0)
{
// ── CREST ────────────────────────────────────
bright = (0.55 + proximity * 0.45) * _CrestBright;
// HTML: idx = floor((c * 0.35 + T * 0.07) % CREST_CHARS.length)
float rawIdx = cellC * 0.35 + T * 0.07;
int idx = ((int)floor(rawIdx)) % 8;
if (idx < 0) idx += 8;
charId = crestCharId(idx);
}
else
{
// ── WAVE BODY ────────────────────────────────
bright = pow(1.0 - depthRatio, 1.5) * (0.18 + proximity * 0.62) * _BodyBright;
if (bright >= 0.03)
{
float n = bodyNoise(cellC, cellR, T * 0.06);
float threshold = depthRatio * depthRatio * 0.65;
if (n < threshold)
{
bright = 0.0;
}
else
{
// HTML: charNoise → sin > 0 ? '1' : '0'
charId = sin(cellC * 0.97 + cellR * 1.53 + T * 0.13) > 0 ? 4 : 3;
}
}
else
{
bright = 0.0;
}
}
}
else if (cellR >= shoreY)
{
// ── Shore foam ───────────────────────────────────
float dInShore = (cellR - shoreY) / max(1.0, _ShoreRows);
float n = bodyNoise(cellC, cellR, T * 0.2);
bright = (1.0 - dInShore) * (0.3 + n * 0.6) * _BodyBright;
if (bright >= 0.06)
{
if (cellR < shoreY + 1.0)
{
// HTML: CREST_CHARS[floor((c*0.4+T*0.1) % 8)]
float rawCi = cellC * 0.4 + T * 0.1;
int ci = ((int)floor(rawCi)) % 8;
if (ci < 0) ci += 8;
charId = crestCharId(ci);
}
else
{
// HTML: n > 0.5 ? '1' : '0'
charId = n > 0.5 ? 4 : 3;
}
}
else
{
bright = 0.0;
}
}
// ── Render glyph on black background ─────────────────────
bright = saturate(bright);
float pixel = 0.0;
if (bright > 0.01)
pixel = glyphPixel(charId, glyphX, glyphY);
float charBright = pixel * bright;
float3 col = float3(charBright, charBright, charBright) * i.color.rgb;
// Black opaque background — character brightness on top
float3 finalCol = lerp(_BgColor.rgb, col, charBright);
float finalAlpha = _BgColor.a * i.color.a;
return fixed4(finalCol, finalAlpha);
}
ENDCG
}
}
Fallback "Sprites/Default"
}