¿Cómo crear un punto luminoso con JavaScript y Canvas? | Tutorial

- Andrés Cruz

EN In english

¿Cómo crear un punto luminoso con JavaScript y Canvas? | Tutorial

Ver ejemplo en vivo Descargar código fuente

Continuando con nuestros experimentos visuales utilizando HTML5, Canvas y JavaScript, hoy vamos a explorar cómo generar efectos de iluminación desde cero. En este artículo, aprenderás a crear un punto luminoso manipulando directamente los píxeles del lienzo, una técnica fundamental para entender cómo funcionan los motores gráficos y el procesamiento de imágenes.

¿Cómo crear un punto luminoso con JavaScript y Canvas?

Aunque existen métodos automáticos en Canvas para crear sombras, el enfoque de este experimento es matemático. Vamos a calcular la iluminación de cada píxel basándonos en su distancia respecto a un punto central.

Aquí tienes el código modernizado utilizando estándares de ES6+:

const canvas = document.getElementById('canv');
const ctx = canvas.getContext('2d');
const width = 800;
const height = 400;

// Definimos nuestro "sol" o punto de luz
const lightSource = {
    x: 100,
    y: 100,
    intensity: 15
};

const drawLightPoint = () => {
    // Obtenemos el espacio para los píxeles
    const imageData = ctx.createImageData(width, height);
    const pixels = imageData.data;

    for (let x = 0; x < width; x++) {
        for (let y = 0; y < height; y++) {
            // Calculamos la distancia entre el píxel actual y la fuente de luz
            const d = Math.sqrt(Math.pow(x - lightSource.x, 2) + Math.pow(y - lightSource.y, 2));
            
            // Calculamos la densidad de luz (a menor distancia, mayor intensidad)
            const density = lightSource.intensity / d;
            
            // Índice en el arreglo unidimensional (RGBA)
            const index = (x + y * width) * 4;

            // Manipulamos los canales de color
            pixels[index]     = density * 70;       // Rojo
            pixels[index + 1] = density * 70;       // Verde
            pixels[index + 2] = density * 38;       // Azul (70 * 0.55)
            pixels[index + 3] = 255;                // Alpha (Opaco)
        }
    }
    // Volcamos los datos al canvas
    ctx.putImageData(imageData, 0, 0);
};

drawLightPoint();

Análisis del código y la lógica matemática

Para que este efecto funcione, el secreto reside en la fórmula de la distancia entre dos puntos.

La importancia de la distancia

En la línea const density = lightSource.intensity / d; ocurre la magia. Al dividir la intensidad por la distancia (d), logramos que los píxeles más cercanos al centro tengan valores de color muy altos (blancos/amarillos), mientras que los lejanos tienden a cero (negro).

Representación de la intensidad de luz según la distancia en Canvas
Los píxeles a la misma distancia del centro forman circunferencias de igual intensidad.

El arreglo de píxeles (ImageData)

Un error común al empezar con Canvas es pensar que los píxeles se manejan como una matriz bidimensional. En realidad, getImageData devuelve un TypedArray unidimensional donde cada píxel ocupa 4 posiciones consecutivas (R, G, B y A).

Si nuestro lienzo es de 800x400, el total de datos es: 
800 (ancho) * 400 (alto) * 4 (canales) = 1,280,000 elementos.

Variando el color del punto luminoso

Las multiplicaciones por valores constantes sirven para variar el color, pruebas distintos valores y verás cambios de colores.

Calculando la posición a pintar

El cálculo de la variable idx es para recorrer un arreglo de una dimensión a partir de los contadores (x yy) de una matriz, esto se debe a que la función getImageData devuelve un array de una dimensión y no una matriz como podríamos esperar.

Se multiplica por cuatro debido a que hay que agregar los canales RGBA los cuales son 4 y se suman al total de la matriz; si ejecutas el siguiente comando:

id.data.length

Veras que retorna -para nuestro experimento- 1280000; es decir la longitud de la data es calculada como:

AC * LC * TRGBA

En donde:

  • AC = Ancho del Canvas.
  • LC = Largo del Canvas.
  • TRGBA = Tamaño del canal RGBA (4).

Que en nuestro experimento viene siendo:

800 * 400 * 4 = 1280000

Pitando en el Canvas el punto luminoso

Finalmente pintamos el color calculado a través de la distancia entre dos puntos para cada canal RGBA:

    pxl[idx] = dens * 70;
    pxl[idx + 1] = (dens * 70);
    pxl[idx + 2] = (dens * 70) * 0.55;
    pxl[idx + 3] = 255;

Optimización: El método de Gradientes Radiales

El método anterior es excelente para aprender, pero procesar un millón de operaciones en el CPU cada vez que quieras mover la luz es ineficiente. Para aplicaciones en tiempo real o juegos, la API de Canvas ofrece createRadialGradient, que utiliza la aceleración por hardware (GPU).

// Alternativa eficiente para producción
const gradient = ctx.createRadialGradient(100, 100, 0, 100, 100, 50);
gradient.addColorStop(0, 'rgba(255, 255, 200, 1)');
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');

ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);

Conclusión y siguientes pasos

Entender cómo manipular píxeles individuales te abre las puertas a crear filtros de imagen, efectos de partículas y sombreadores (shaders) personalizados en el navegador. Aunque para efectos simples los gradientes son mejores, la manipulación directa de ImageData es la base de la computación gráfica avanzada.

Si te ha gustado este experimento, no te pierdas nuestro próximo artículo sobre cómo crear una onda sinusoidal con Canvas y JavaScript, donde aplicaremos funciones trigonométricas para dar movimiento a nuestros gráficos.

Aprende a crear efectos de iluminación en HTML5 Canvas manipulando píxeles con JavaScript (ImageData). Tutorial paso a paso con código modernizado en ES6.

Acepto recibir anuncios de interes sobre este Blog.

Andrés Cruz

EN In english