342 lines
13 KiB
GLSL
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"
|
|
}
|