바이브코딩으로 만드는 웹아트 #2 – 일반영상을 ASCII 영상으로..

일반영상을 ASCII 영상으로 바꾸어 보았어요.
기존 영상과는 전혀 다른 느낌이네요. 나름 느낌이 있네요.


아래 코드를 참고하셔서 기존 영상을 나만의 ASCII 영상으로 변환해 보는 건 어떨까요?

< 소스코드 >

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Video ASCII Art Effect</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        html, body {
            height: 100%;
            overflow: hidden;
        }

        body {
            background: linear-gradient(45deg, #1a1a1a 25%, transparent 25%),
                        linear-gradient(-45deg, #1a1a1a 25%, transparent 25%),
                        linear-gradient(45deg, transparent 75%, #1a1a1a 75%),
                        linear-gradient(-45deg, transparent 75%, #1a1a1a 75%);
            background-size: 20px 20px;
            background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
            background-color: #2a2a2a;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        #canvas {
            width: 85vmin;
            height: 85vmin;
            border: 2px solid rgba(255, 255, 255, 0.15);
            box-shadow: 0 0 50px rgba(255, 255, 255, 0.1),
                        inset 0 0 30px rgba(0, 0, 0, 0.5);
            border-radius: 8px;
        }

        .overlay {
            position: fixed;
            top: 20px;
            left: 20px;
            color: rgba(255, 255, 255, 0.3);
            font-family: 'Courier New', monospace;
            font-size: 12px;
            pointer-events: none;
            animation: pulse 3s ease-in-out infinite;
        }

        @keyframes pulse {
            0%, 100% { opacity: 0.3; }
            50% { opacity: 0.6; }
        }
    </style>
    <script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
</head>
<body>
    <canvas id="canvas"></canvas>
    <div class="overlay">ASCII VIDEO RENDERER</div>

    <!-- Vertex Shader -->
    <script id="vertexShader" type="x-shader/x-vertex">#version 300 es
        precision highp float;
        in vec4 position;
        void main() {
            gl_Position = vec4(position);
        }
    </script>

    <!-- Fragment Shader -->
    <script id="fragmentShader" type="x-shader/x-fragment">#version 300 es
        precision highp float;
        out vec4 fragColor;
        uniform vec2 u_resolution;
        uniform float u_time;
        uniform sampler2D u_video;
        
        #define R u_resolution
        #define T u_time

        float character(int n, vec2 p) {
            p = floor(p * vec2(-4., 4.) + 2.5);
            if (clamp(p.x, 0., 4.) == p.x && clamp(p.y, 0., 4.) == p.y) {
                int a = int(round(p.x) + 5. * round(p.y));
                if (((n >> a) & 1) == 1) return 1.;
            }
            return 0.;
        }

        vec3 adjustColor(vec3 col) {
            // Enhance contrast and maintain original color feel
            col = pow(col, vec3(0.95));
            col = mix(col, col * 1.2, 0.3);
            return clamp(col, 0., 1.);
        }

        void main() {
            vec2 F = gl_FragCoord.xy;
            vec2 uv = (2. * F.xy - R.xy) / max(R.x, R.y);
            
            // Dynamic size based on distance from center
            float dist = length(uv * 0.5);
            float size = 6. + sin(T * 0.5) * 0.5 - dist * 2.;
            size = max(4., size);
            
            float srez = size * 2.;
            vec3 col = texture(u_video, floor(F.xy / srez) * srez / R.xy).rgb;
            col = adjustColor(col);
            
            float gray = 0.299 * col.r + 0.587 * col.g + 0.114 * col.b;
            
            // Add slight animation to threshold
            float threshold_offset = sin(T * 2. + dist * 5.) * 0.02;
            gray = clamp(gray + threshold_offset, 0., 1.);
            
            int n = 0;
            
            // Character mapping with different symbols
            if (gray > 0.025) n = 4096;      // .
            if (gray > 0.050) n = 131200;    // :
            if (gray > 0.075) n = 4329476;   // !
            if (gray > 0.100) n = 459200;    // =
            if (gray > 0.125) n = 4591748;   // 1
            if (gray > 0.150) n = 12652620;  // 3
            if (gray > 0.175) n = 14749828;  // 7
            if (gray > 0.200) n = 18393220;  // y
            if (gray > 0.225) n = 15239300;  // ?
            if (gray > 0.250) n = 17318431;  // l
            if (gray > 0.275) n = 32641156;  // t
            if (gray > 0.300) n = 18393412;  // v
            if (gray > 0.325) n = 18157905;  // x
            if (gray > 0.350) n = 17463428;  // 4
            if (gray > 0.375) n = 14954572;  // 5
            if (gray > 0.400) n = 13177118;  // 2
            if (gray > 0.425) n = 18405034;  // w
            if (gray > 0.450) n = 16269839;  // c
            if (gray > 0.475) n = 15018318;  // 0
            if (gray > 0.500) n = 18400814;  // u
            if (gray > 0.525) n = 33081316;  // q
            if (gray > 0.550) n = 15255086;  // o
            if (gray > 0.575) n = 32045584;  // p
            if (gray > 0.600) n = 6566222;   // 6
            if (gray > 0.625) n = 15022158;  // 9
            if (gray > 0.650) n = 18444881;  // k
            if (gray > 0.675) n = 16272942;  // g
            if (gray > 0.700) n = 18415153;  // h
            if (gray > 0.725) n = 32641183;  // i
            if (gray > 0.750) n = 32540207;  // j
            if (gray > 0.775) n = 18732593;  // m
            if (gray > 0.800) n = 18667121;  // n
            if (gray > 0.825) n = 16267326;  // s
            if (gray > 0.850) n = 32575775;  // z
            if (gray > 0.875) n = 15022414;  // 8
            if (gray > 0.900) n = 15255537;  // a
            if (gray > 0.925) n = 32032318;  // d
            if (gray > 0.950) n = 33061407;  // e
            if (gray > 0.975) n = 11512810;  // #

            vec2 p = mod(F.xy / size, 2.) - vec2(1);
            
            // Apply character with slight glow effect
            float charValue = character(n, p);
            col = col * charValue;
            
            // Add subtle color shift
            col.r *= 1.05;
            col.b *= 0.95;
            
            fragColor = vec4(col, 1.0);
        }
    </script>

    <script>
        // Video setup
        const video = document.createElement("video");
        video.src = "https://cdn.midjourney.com/video/5476d672-13ea-4453-b108-54a48813f908/1.mp4";
        video.crossOrigin = "anonymous";
        video.loop = true;
        video.muted = true;
        video.autoplay = true;
        video.play().catch(e => console.log("Video play error:", e));

        // WebGL setup
        const glcanvas = document.getElementById("canvas");
        const gl = glcanvas.getContext("webgl2", {
            antialias: false,
            preserveDrawingBuffer: false
        });

        if (!gl) {
            alert("WebGL2 is not supported in your browser");
        }

        const programInfo = twgl.createProgramInfo(gl, [
            "vertexShader",
            "fragmentShader"
        ]);

        const arrays = {
            position: [-1, -1, 0, 1, -1, 0, -1, 1, 0, -1, 1, 0, 1, -1, 0, 1, 1, 0],
            texcoord: { 
                numComponents: 2, 
                data: [0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1] 
            }
        };

        const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
        const textures = twgl.createTextures(gl, {
            video: { 
                src: video, 
                min: gl.LINEAR, 
                mag: gl.LINEAR,
                wrap: [gl.CLAMP_TO_EDGE, gl.CLAMP_TO_EDGE] 
            }
        });

        // Render loop
        function render(time) {
            twgl.resizeCanvasToDisplaySize(gl.canvas, 1.0);

            if (video.readyState >= video.HAVE_CURRENT_DATA) {
                gl.bindTexture(gl.TEXTURE_2D, textures.video);
                gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
                gl.texImage2D(
                    gl.TEXTURE_2D, 
                    0, 
                    gl.RGBA, 
                    gl.RGBA, 
                    gl.UNSIGNED_BYTE, 
                    video
                );
            }

            gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
            gl.clearColor(0.1, 0.1, 0.1, 1.0);
            gl.clear(gl.COLOR_BUFFER_BIT);

            gl.useProgram(programInfo.program);
            twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
            twgl.setUniforms(programInfo, {
                u_time: time * 0.001,
                u_video: textures.video,
                u_resolution: [gl.canvas.width, gl.canvas.height]
            });
            twgl.drawBufferInfo(gl, bufferInfo);

            requestAnimationFrame(render);
        }

        // Start rendering when DOM is ready
        window.addEventListener("DOMContentLoaded", () => {
            requestAnimationFrame(render);
        });

        // Handle window resize
        window.addEventListener("resize", () => {
            twgl.resizeCanvasToDisplaySize(gl.canvas, 1.0);
        });
    </script>
</body>
</html>

댓글 남기기

AI, 코딩, 일상 및 다양한 정보 공유에서 더 알아보기

지금 구독하여 계속 읽고 전체 아카이브에 액세스하세요.

계속 읽기