THE WATCHER
no te muevas · te está mirando
wogl.io
#version 300 es
in vec2 p; void main(){ gl_Position=vec4(p,0.,1.); }
#version 300 es
precision highp float;
out vec4 frag;
uniform vec2 uRes;
uniform float uTime, uAwaken, uBlink, uDilate, uLock, uShake;
uniform vec2 uGaze;
uniform int uTentN, uFbmOct;
uniform vec4 uTent[8]; // xy=objetivo · z=nacimiento · w=semilla
float hash(vec2 p){ p=fract(p*vec2(123.34,456.21)); p+=dot(p,p+45.32); return fract(p.x*p.y); }
float noise(vec2 p){ vec2 i=floor(p),f=fract(p);
float a=hash(i),b=hash(i+vec2(1,0)),c=hash(i+vec2(0,1)),d=hash(i+vec2(1,1));
vec2 u=f*f*(3.0-2.0*f); return mix(mix(a,b,u.x),mix(c,d,u.x),u.y); }
float fbm(vec2 p,int oct){ float v=0.0,a=0.55; for(int i=0;i<4;i++){ if(i>=oct) break; v+=a*noise(p); p=p*2.03+vec2(1.7,9.2); a*=0.5; } return v; }
mat2 rot(float a){ float c=cos(a),s=sin(a); return mat2(c,-s,s,c); }
vec2 asp(vec2 uv){ return (uv*uRes-0.5*uRes)/uRes.y; }
const float YB=-0.62;
// ── EL OJO (enfermizo, inyectado, se fija en ti) ──
vec3 drawEye(vec2 pp, vec3 bg, float aw){
float aspx=uRes.x/uRes.y;
float fit=min(1.0,(aspx*0.5*0.94)/0.52);
pp/=fit;
float W=0.52, A=0.36;
float xr=pp.x/W;
float almond=1.0-xr*xr;
if(almond<=0.001) return bg;
float lidH=A*almond;
float dy=abs(pp.y);
if(dy>lidH+0.03) return bg;
vec2 ic=clamp(uGaze,-0.25,0.25)*vec2(0.30,0.20);
vec2 ip=pp-ic; float ir=length(ip);
float ri=0.255;
// esclerótica enferma (amarillenta) + venas de sangre SIEMPRE presentes
vec3 sclera=mix(vec3(0.16,0.15,0.09), vec3(0.09,0.09,0.06), smoothstep(0.0,W,abs(pp.x)));
float rn=fbm(pp*10.0+vec2(2.0), uFbmOct);
float vein=smoothstep(0.06,0.0,abs(rn-0.5))*smoothstep(ri-0.02,ri+0.20,ir)*(0.45+0.7*aw);
float rn2=fbm(pp*18.0-vec2(5.0,1.0), uFbmOct);
vein+=smoothstep(0.035,0.0,abs(rn2-0.5))*smoothstep(ri,ri+0.28,ir)*(0.3+0.9*aw);
sclera=mix(sclera, vec3(0.62,0.05,0.04), clamp(vein,0.0,1.0)*0.85);
vec3 eyeCol=sclera;
// iris bilioso
float ang=atan(ip.y,ip.x);
float fib=fbm(vec2(ang*6.0, ir*11.0)+uTime*0.1, uFbmOct);
vec3 irisIn=mix(vec3(0.10,0.20,0.04), vec3(0.55,0.12,0.04), aw); // verde bilis → sangre al despertar
vec3 iris=mix(vec3(0.02,0.06,0.02), irisIn, smoothstep(ri,0.05,ir));
iris*=0.55+0.7*fib;
eyeCol=mix(eyeCol, iris, smoothstep(ri, ri-0.012, ir));
// anillo límbico negro
eyeCol=mix(eyeCol, vec3(0.0), (smoothstep(ri,ri-0.03,ir)-smoothstep(ri-0.03,ri-0.06,ir))*0.95);
// resplandor interno rojo al despertar
eyeCol+=vec3(0.5,0.03,0.0)*smoothstep(0.14,0.0,ir)*aw*0.7;
// PUPILA · rendija que se DILATA al picar y se CONTRAE a un punto cuando te fija
float pw=mix(0.085,0.22, clamp(uDilate,0.0,1.0));
pw=mix(pw, 0.026, clamp(uLock,0.0,1.0));
eyeCol=mix(eyeCol, vec3(0.0), smoothstep(1.0,0.86,length(ip/vec2(pw,0.30))));
// brillo húmedo frío
eyeCol+=smoothstep(0.04,0.0,length(ip-vec2(-0.06,0.09)))*vec3(0.7,0.75,0.7);
// párpado (carne oscura) + parpadeo
float ap=mix(1.0,0.02, clamp(uBlink,0.0,1.0));
float openH=lidH*ap;
float openMask=smoothstep(openH+0.014, openH-0.014, dy);
vec3 lid=mix(vec3(0.05,0.03,0.03), vec3(0.10,0.05,0.05), 0.5+0.5*pp.y);
lid=mix(lid, vec3(0.0), (smoothstep(lidH,lidH-0.02,dy)-smoothstep(lidH-0.02,lidH-0.05,dy))*0.6);
vec3 interior=mix(lid, eyeCol, openMask);
interior=mix(interior, vec3(0.0), (smoothstep(lidH+0.01,lidH,dy)-smoothstep(lidH,lidH-0.012,dy))*0.85);
float aMask=smoothstep(lidH+0.014, lidH-0.006, dy);
return mix(bg, interior, aMask);
}
// ── TENTÁCULOS (se retuercen, buscan tu dedo, más al despertar) ──
void tentacle(vec2 p, vec4 T, float t, float aw, inout vec3 col){
if(T.z < -100.0) return;
float age=t-T.z;
float grow=smoothstep(0.0,0.5,age);
float ret =1.0-smoothstep(4.4,5.2,age);
float alive=grow*ret; if(alive<=0.001) return;
float sd=T.w; vec2 tgt=T.xy;
float tipY=mix(YB,tgt.y,grow);
float base_x=tgt.x;
float spd=mix(2.2,5.0,aw);
float hh=(p.y-YB)/max(tipY-YB,1e-3);
float hc=clamp(hh,0.0,1.0);
float cx=base_x
+ sin(hc*7.0 + t*spd + sd*6.28)*0.13*(1.0-0.3*hc)*(1.0+aw)
+ sin(hc*3.5 - t*1.7 + sd*3.0)*0.06
+ (tgt.x-base_x)*hc*0.20;
float w=mix(0.062,0.005,hc)*alive;
float dx=p.x-cx;
float dseg=length(vec2(dx,(hh-hc)*(tipY-YB)))-w;
float m=smoothstep(0.006,0.0,dseg);
if(m<=0.001) return;
float nx=clamp(dx/max(w,1e-3),-1.0,1.0);
float nz=sqrt(max(1.0-nx*nx,0.0));
vec3 N=normalize(vec3(nx,0.25,nz));
vec3 L=normalize(vec3(-0.5,0.7,0.6));
float dif=clamp(dot(N,L),0.0,1.0);
float spec=pow(clamp(dot(reflect(-L,N),vec3(0,0,1)),0.0,1.0),30.0);
float rim=pow(1.0-nz,2.0);
vec3 base=mix(vec3(0.01,0.07,0.04), vec3(0.06,0.22,0.10), hc); // verde oscuro carne
base=mix(base, vec3(0.35,0.03,0.03), aw*0.6); // se ensangrienta
vec3 tc=base*(0.20+0.9*dif) + spec*vec3(0.8,0.9,0.8) + rim*vec3(0.6,0.15,0.1)*aw;
float suck=smoothstep(0.5,0.85,sin(hc*52.0))*step(0.2,nx);
tc=mix(tc, vec3(0.0), suck*0.7*m);
col=mix(col, tc, m);
}
vec3 wShoulder(vec3 c){
c = max(c, 0.0);
float p = max(c.r, max(c.g, c.b));
if (p <= 0.70) return c;
float s = 0.70 + 0.30 * (1.0 - exp(-(p - 0.70) / 0.30));
return c * (s / p);
}
void main(){
vec2 uv=gl_FragCoord.xy/uRes;
vec2 p=asp(uv);
float t=uTime, aw=clamp(uAwaken,0.0,1.0);
// temblor al picar
p+=uShake*vec2(sin(t*92.0),cos(t*99.0));
// latido de corazón (lub-dub) · marca el pulso del terror
float bt=t*mix(0.9,1.7,aw);
float heart=exp(-fract(bt)*7.0)+0.6*exp(-fract(bt+0.16)*7.0);
// fondo: casi negro, con bruma roja/verde muy tenue que respira con el latido
vec3 col=vec3(0.012,0.016,0.012);
float fog=fbm(p*1.8+vec2(t*0.03,-t*0.05), uFbmOct);
col+=mix(vec3(0.02,0.05,0.03), vec3(0.10,0.01,0.01), aw)*pow(fog,2.5)*(0.4+0.8*aw)*(0.7+0.5*heart);
col+=vec3(0.14,0.02,0.0)*exp(-dot(p,p)*2.4)*aw*(0.4+0.6*heart); // aura roja tras el ojo
// EL OJO
col=drawEye(p, col, aw);
// lágrimas de sangre (al despertar)
if(aw>0.32){
float aspx=uRes.x/uRes.y; float fit=min(1.0,(aspx*0.5*0.94)/0.52);
vec2 pe=p/fit;
float tear=0.0;
for(float sxx=-0.17; sxx<0.18; sxx+=0.34){
float dxx=pe.x-(sxx+0.02*sin(pe.y*22.0+t*2.0));
float streak=smoothstep(0.014,0.0,abs(dxx))*smoothstep(-0.30,-0.36,pe.y)*smoothstep(-0.85,-0.36,pe.y);
float drip=smoothstep(0.02,0.0,length(pe-vec2(sxx, -0.34-0.18*fract(t*0.3+sxx))));
tear=max(tear, max(streak, drip));
}
col=mix(col, vec3(0.30,0.0,0.0), tear*smoothstep(0.32,0.6,aw));
}
// TENTÁCULOS por encima
for(int i=0;i<8;i++){ if(i>=uTentN) break; tentacle(p, uTent[i], t, aw, col); }
// ── post: viñeta con latido + grano fuerte + fundido a negro ──
float r=length(p);
col*=mix(0.10,1.0, smoothstep(1.35,0.15,r)); // negrura densa
col*=1.0-0.28*heart*smoothstep(0.2,1.1,r); // el latido oscurece los bordes
col=mix(col, col*vec3(1.2,0.6,0.6), aw*0.4); // deriva a rojo enfermo
col+=(hash(gl_FragCoord.xy+fract(t))-0.5)*mix(0.03,0.10,aw); // grano que crece con el terror
col=max(col,0.0);{float L=dot(col,vec3(0.2126,0.7152,0.0722));float o=max(L-0.70,0.0);float Lt=L-o+o/(1.0+o);col*= L>0.0 ? Lt/L : 0.0;}col=pow(clamp(col,0.0,1.0),vec3(0.4545));{float L=dot(col,vec3(0.2126,0.7152,0.0722));col=clamp(mix(vec3(L),col,1.3),0.0,1.0);}
frag=vec4(wShoulder(col),1.0);
}