일반영상을 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>