Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>WebGL Text Rendering</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| #glCanvas { | |
| display: block; | |
| background-color: #1a202c; | |
| border-radius: 0.5rem; | |
| box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); | |
| } | |
| .control-panel { | |
| background-color: #2d3748; | |
| border-radius: 0.5rem; | |
| } | |
| .text-input { | |
| background-color: #4a5568; | |
| color: white; | |
| border: none; | |
| border-radius: 0.25rem; | |
| padding: 0.5rem; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 text-gray-200 min-h-screen p-8"> | |
| <div class="max-w-6xl mx-auto"> | |
| <header class="mb-8"> | |
| <h1 class="text-4xl font-bold text-blue-400 mb-2">WebGL Text Rendering</h1> | |
| <p class="text-gray-400">Rendering text using pure WebGL</p> | |
| </header> | |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> | |
| <div class="lg:col-span-2"> | |
| <canvas id="glCanvas" width="800" height="500"></canvas> | |
| </div> | |
| <div class="control-panel p-6"> | |
| <h2 class="text-xl font-semibold mb-4 text-blue-300">Controls</h2> | |
| <div class="space-y-4"> | |
| <div> | |
| <label class="block text-sm font-medium mb-1" for="textInput">Text Content</label> | |
| <input type="text" id="textInput" value="WebGL" class="text-input w-full"> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium mb-1" for="fontSize">Font Size</label> | |
| <input type="range" id="fontSize" min="10" max="200" value="72" class="w-full"> | |
| <span id="fontSizeValue" class="text-sm">72px</span> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium mb-1" for="textColor">Text Color</label> | |
| <input type="color" id="textColor" value="#4299e1" class="w-full h-10"> | |
| </div> | |
| <button id="resetBtn" class="bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded transition-colors"> | |
| Reset Settings | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const canvas = document.getElementById('glCanvas'); | |
| const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); | |
| if (!gl) { | |
| alert('WebGL not supported in your browser'); | |
| return; | |
| } | |
| // Set up controls | |
| const textInput = document.getElementById('textInput'); | |
| const fontSize = document.getElementById('fontSize'); | |
| const fontSizeValue = document.getElementById('fontSizeValue'); | |
| const textColor = document.getElementById('textColor'); | |
| const resetBtn = document.getElementById('resetBtn'); | |
| fontSize.addEventListener('input', () => { | |
| fontSizeValue.textContent = `${fontSize.value}px`; | |
| updateText(); | |
| }); | |
| textInput.addEventListener('input', updateText); | |
| textColor.addEventListener('input', updateUniforms); | |
| resetBtn.addEventListener('click', () => { | |
| textInput.value = 'WebGL'; | |
| fontSize.value = 72; | |
| fontSizeValue.textContent = '72px'; | |
| textColor.value = '#4299e1'; | |
| updateText(); | |
| updateUniforms(); | |
| }); | |
| // Vertex shader | |
| const vsSource = ` | |
| attribute vec2 a_position; | |
| attribute vec2 a_texCoord; | |
| varying vec2 v_texCoord; | |
| void main() { | |
| gl_Position = vec4(a_position, 0, 1); | |
| v_texCoord = a_texCoord; | |
| } | |
| `; | |
| // Fragment shader (simplified for basic text rendering) | |
| const fsSource = ` | |
| precision mediump float; | |
| uniform sampler2D u_texture; | |
| uniform vec3 u_textColor; | |
| varying vec2 v_texCoord; | |
| void main() { | |
| vec4 texColor = texture2D(u_texture, v_texCoord); | |
| gl_FragColor = vec4(u_textColor, texColor.r); | |
| } | |
| `; | |
| // Compile shader | |
| function compileShader(gl, source, type) { | |
| const shader = gl.createShader(type); | |
| gl.shaderSource(shader, source); | |
| gl.compileShader(shader); | |
| if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { | |
| console.error('Shader compile error:', gl.getShaderInfoLog(shader)); | |
| gl.deleteShader(shader); | |
| return null; | |
| } | |
| return shader; | |
| } | |
| // Create shader program | |
| const vertexShader = compileShader(gl, vsSource, gl.VERTEX_SHADER); | |
| const fragmentShader = compileShader(gl, fsSource, gl.FRAGMENT_SHADER); | |
| const program = gl.createProgram(); | |
| gl.attachShader(program, vertexShader); | |
| gl.attachShader(program, fragmentShader); | |
| gl.linkProgram(program); | |
| if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { | |
| console.error('Program link error:', gl.getProgramInfoLog(program)); | |
| return; | |
| } | |
| gl.useProgram(program); | |
| // Get attribute and uniform locations | |
| const positionAttributeLocation = gl.getAttribLocation(program, 'a_position'); | |
| const texCoordAttributeLocation = gl.getAttribLocation(program, 'a_texCoord'); | |
| const textureUniformLocation = gl.getUniformLocation(program, 'u_texture'); | |
| const textColorUniformLocation = gl.getUniformLocation(program, 'u_textColor'); | |
| // Create a texture | |
| const texture = gl.createTexture(); | |
| gl.bindTexture(gl.TEXTURE_2D, texture); | |
| // Set texture parameters | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); | |
| // Create buffers for a fullscreen quad | |
| const positionBuffer = gl.createBuffer(); | |
| gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); | |
| // Positions for a fullscreen quad | |
| const positions = [ | |
| -1.0, -1.0, | |
| 1.0, -1.0, | |
| -1.0, 1.0, | |
| 1.0, 1.0, | |
| ]; | |
| gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); | |
| // Texture coordinates buffer | |
| const texCoordBuffer = gl.createBuffer(); | |
| gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer); | |
| const texCoords = [ | |
| 0.0, 0.0, | |
| 1.0, 0.0, | |
| 0.0, 1.0, | |
| 1.0, 1.0, | |
| ]; | |
| gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoords), gl.STATIC_DRAW); | |
| // Create a temporary canvas for text rendering | |
| const tempCanvas = document.createElement('canvas'); | |
| const tempCtx = tempCanvas.getContext('2d'); | |
| // Generate texture from text | |
| function generateTextTexture(text, size) { | |
| // Calculate canvas size based on text | |
| tempCtx.font = `${size}px Arial`; | |
| const metrics = tempCtx.measureText(text); | |
| const width = Math.max(128, metrics.width + 20); | |
| const height = size + 20; | |
| tempCanvas.width = width; | |
| tempCanvas.height = height; | |
| // Clear and draw text | |
| tempCtx.clearRect(0, 0, width, height); | |
| tempCtx.font = `${size}px Arial`; | |
| tempCtx.textAlign = 'center'; | |
| tempCtx.textBaseline = 'middle'; | |
| tempCtx.fillStyle = 'white'; | |
| tempCtx.fillText(text, width/2, height/2); | |
| // Upload to WebGL texture | |
| gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, tempCanvas); | |
| return { | |
| width: width, | |
| height: height | |
| }; | |
| } | |
| // Update uniforms | |
| function updateUniforms() { | |
| const color = hexToRgb(textColor.value); | |
| gl.uniform3f(textColorUniformLocation, color.r, color.g, color.b); | |
| render(); | |
| } | |
| // Hex to RGB conversion | |
| function hexToRgb(hex) { | |
| const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); | |
| return result ? { | |
| r: parseInt(result[1], 16) / 255, | |
| g: parseInt(result[2], 16) / 255, | |
| b: parseInt(result[3], 16) / 255 | |
| } : { r: 0, g: 0, b: 0 }; | |
| } | |
| // Update text | |
| function updateText() { | |
| const text = textInput.value || ' '; | |
| const size = parseInt(fontSize.value); | |
| generateTextTexture(text, size); | |
| updateUniforms(); | |
| } | |
| // Render function | |
| function render() { | |
| gl.clearColor(0.1, 0.1, 0.1, 1.0); | |
| gl.clear(gl.COLOR_BUFFER_BIT); | |
| // Enable position attribute | |
| gl.enableVertexAttribArray(positionAttributeLocation); | |
| gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); | |
| gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0); | |
| // Enable texture coordinate attribute | |
| gl.enableVertexAttribArray(texCoordAttributeLocation); | |
| gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer); | |
| gl.vertexAttribPointer(texCoordAttributeLocation, 2, gl.FLOAT, false, 0, 0); | |
| // Draw | |
| gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); | |
| } | |
| // Initial render | |
| updateText(); | |
| // Handle window resize | |
| window.addEventListener('resize', function() { | |
| const displayWidth = canvas.clientWidth; | |
| const displayHeight = canvas.clientHeight; | |
| if (canvas.width !== displayWidth || canvas.height !== displayHeight) { | |
| canvas.width = displayWidth; | |
| canvas.height = displayHeight; | |
| gl.viewport(0, 0, canvas.width, canvas.height); | |
| render(); | |
| } | |
| }); | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=victorqueiroz/webgl" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |