diff --git a/party-stage/src/scene/projection-screen.js b/party-stage/src/scene/projection-screen.js index 5818bf2..8127b2a 100644 --- a/party-stage/src/scene/projection-screen.js +++ b/party-stage/src/scene/projection-screen.js @@ -17,6 +17,7 @@ uniform sampler2D videoTexture; uniform float u_effect_type; uniform float u_effect_strength; uniform float u_time; +uniform float u_opacity; varying vec2 vUv; float random(vec2 st) { @@ -24,23 +25,36 @@ float random(vec2 st) { } void main() { - vec2 uv = vUv; - vec4 color = texture2D(videoTexture, uv); + // LED Grid Setup + float ledCountX = 128.0; + float ledCountY = 72.0; // 16:9 Aspect Ratio + + vec2 gridUV = vec2(vUv.x * ledCountX, vUv.y * ledCountY); + vec2 cell = fract(gridUV); + vec2 pixelatedUV = (floor(gridUV) + 0.5) / vec2(ledCountX, ledCountY); + + vec4 color = texture2D(videoTexture, pixelatedUV); // Effect 1: Static/Noise (Power On/Off) if (u_effect_type > 0.0) { - float noise = random(uv + u_time); + float noise = random(pixelatedUV + u_time); vec3 noiseColor = vec3(noise); color.rgb = mix(color.rgb, noiseColor, u_effect_strength); } - gl_FragColor = color; + float dist = distance(cell, vec2(0.5)); + float mask = 1.0 - smoothstep(0.35, 0.45, dist); + float brightness = max(color.r, max(color.g, color.b)); + float contentAlpha = smoothstep(0.05, 0.15, brightness); + + gl_FragColor = vec4(color.rgb, contentAlpha * mask * u_opacity); } `; const visualizerFragmentShader = ` uniform float u_time; uniform float u_beat; +uniform float u_opacity; varying vec2 vUv; vec3 hsv2rgb(vec3 c) { @@ -50,17 +64,27 @@ vec3 hsv2rgb(vec3 c) { } void main() { - vec2 uv = vUv; - float dist = length(uv - 0.5); + float ledCountX = 128.0; + float ledCountY = 72.0; + + vec2 gridUV = vec2(vUv.x * ledCountX, vUv.y * ledCountY); + vec2 cell = fract(gridUV); + vec2 uv = (floor(gridUV) + 0.5) / vec2(ledCountX, ledCountY); + + float dist = distance(cell, vec2(0.5)); + float mask = 1.0 - smoothstep(0.35, 0.45, dist); + + float d = length(uv - 0.5); float angle = atan(uv.y - 0.5, uv.x - 0.5); - float wave = sin(dist * 20.0 - u_time * 2.0); + float wave = sin(d * 20.0 - u_time * 2.0); float beatWave = sin(angle * 5.0 + u_time) * u_beat; - float hue = fract(u_time * 0.1 + dist * 0.2); + float hue = fract(u_time * 0.1 + d * 0.2); float val = 0.5 + 0.5 * sin(wave + beatWave); + float contentAlpha = smoothstep(0.1, 0.3, val); - gl_FragColor = vec4(hsv2rgb(vec3(hue, 0.8, val)), 1.0); + gl_FragColor = vec4(hsv2rgb(vec3(hue, 0.8, val)), contentAlpha * mask * u_opacity); } `; @@ -86,6 +110,7 @@ export class ProjectionScreen extends SceneFeature { onComplete: null }; state.originalScreenIntensity = 2.0; + state.screenOpacity = 1.0; // Ensure video element exists if (!state.videoElement) { @@ -161,11 +186,13 @@ export class ProjectionScreen extends SceneFeature { state.tvScreen.material = new THREE.ShaderMaterial({ uniforms: { u_time: { value: 0.0 }, - u_beat: { value: 0.0 } + u_beat: { value: 0.0 }, + u_opacity: { value: state.screenOpacity } }, vertexShader: screenVertexShader, fragmentShader: visualizerFragmentShader, - side: THREE.DoubleSide + side: THREE.DoubleSide, + transparent: true }); state.screenLight.intensity = state.originalScreenIntensity; } @@ -194,10 +221,12 @@ export function turnTvScreenOn() { u_effect_type: { value: 0.0 }, u_effect_strength: { value: 0.0 }, u_time: { value: 0.0 }, + u_opacity: { value: state.screenOpacity !== undefined ? state.screenOpacity : 0.7 } }, vertexShader: screenVertexShader, fragmentShader: screenFragmentShader, - side: THREE.DoubleSide + side: THREE.DoubleSide, + transparent: true }); state.tvScreen.material.needsUpdate = true;