¿Te resulta más fácil leer con fondo crema, un tono de color, más contraste o un texto más grande? Elige tus colores y tu texto una sola vez, para todas las páginas.

Lección gratuita

Iluminación difusa y sombreado

Después de esta lección: Puedo iluminar una superficie con sombreado difuso.

Lección de 75 min2 comandosLaboratorio de visión por computador: píxeles, umbrales y recuento de objetos en JupyterLab

El brillo depende del ángulo con la luz

El sombreado difuso calcula cuánta luz recibe una superficie según el ángulo entre su normal y la dirección de la luz. Por eso una bola iluminada desde arriba a la izquierda tiene una cara clara y otra oscura.

Con un producto escalar y un máximo puedes dar volumen a cualquier forma.

Al final de esta lección

Nota

Puedo iluminar una superficie con sombreado difuso.

Palabras clave

PalabraQué significaEjemplo
Normalla flecha perpendicular a la superficie en un punto
Vector unitarioun vector de largo 1(0, 0, 1)
Producto escalarmultiplicar coordenada a coordenada y sumar
Luz difusala que una superficie mate reparte en todas direcciones
Brilloel nivel de gris que resulta, de 0 a 255

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

La normal y la luz dan el brillo

La normal es la flecha que sale de la superficie en ángulo recto. La luz se da como una dirección que va del punto hacia la luz.

Si los dos vectores miden 1, su producto escalar es el coseno del ángulo entre ellos. Vale 1 de frente, 0 de lado y es negativo por detrás. El brillo es base * max(0, producto).

Ejemplo resuelto: brillos con normal fija

Dirección a la luzProducto escalarBrillo
(0, 0, 1), de frente1200
(3, 0, 4), ya con largo 50,8160
(1, 0, 1), a 45 grados0,71141
(0, 1, 0), de lado00
(0, 0, -4), por detrásNegativo0

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

Calcula tres brillos con la base 200

Comando
docker exec jupyter python3 -c "print(round(200 * max(0, (0 * 3 + 0 * 0 + 1 * 4) / 5)), round(200 * max(0, (0 * 3 + 1 * 0 + 0 * 4) / 5)), round(200 * max(0, (0 * -3 + 0 * 0 + 1 * -4) / 5)))"

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Qué imprimen los tres brillos

Imprime 160 0 0. Cada producto se divide entre 5, el largo de la luz (3, 0, 4) o de (0, 0, 4).

La luz (3, 0, 4) da 0,8 y un brillo de 160. La luz de lado da 0. La luz de (0, 0, -4) daría -0,8, y max(0, ...) lo deja en 0: una superficie no puede tener un brillo negativo.

Antes de multiplicar hay que normalizar

Si un vector no mide 1, el producto escalar no es el coseno. Normalizar es dividir cada coordenada entre el largo del vector, que es math.sqrt(x * x + y * y + z * z).

El vector (3, 0, 4) mide 5, y su versión unitaria es (0,6, 0, 0,8).

Normaliza la luz y calcula el brillo

Código: python
import math
# Brillo difuso de una superficie con normal (0, 0, 1) y luz desde (3, 0, 4).
normal = (0, 0, 1)
luz = (3, 0, 4)
largo = math.sqrt(luz[0] * luz[0] + luz[1] * luz[1] + luz[2] * luz[2])
unitario = (luz[0] / largo, luz[1] / largo, luz[2] / largo)
coseno = normal[0] * unitario[0] + normal[1] * unitario[1] + normal[2] * unitario[2]
print("largo:", largo, "coseno:", coseno)
print("brillo:", int(200 * max(0, coseno) + 0.5))
# Cambia luz a (0, 0, -4): la luz viene por detrás y el brillo es 0.

Un error muy común

Advertencia

Mucha gente cree que un producto escalar negativo da un brillo negativo. Un brillo no puede ser menor que 0: la cara que mira al lado contrario de la luz queda a oscuras. Por eso se usa max(0, producto).

En una esfera la normal sale del centro

En una esfera, la normal de un punto va del centro a ese punto. Dividida entre el radio, mide 1.

Con la luz desde arriba a la izquierda y un poco hacia nosotros, el brillo es mayor arriba a la izquierda. Baja hasta 0 en la parte de abajo a la derecha, que la luz no alcanza.

Dibuja una esfera iluminada

Comando
docker exec jupyter python3 -c "import numpy as np; from PIL import Image; y, x = np.mgrid[0:60, 0:60] - 30; d = x ** 2 + y ** 2; z = np.sqrt(np.maximum(625 - d, 0)); b = np.clip((-x - y + z) / (25 * 3 ** 0.5), 0, 1); im = np.where(d <= 625, np.round(255 * b), 0).astype('uint8'); Image.fromarray(im).save('esfera.png'); print(im[30, 30], im[15, 15], im[45, 45], im[0, 0])"

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Qué imprime la esfera y cómo se ve

Imprime 147 255 0 0. El centro de la bola vale 147 y el punto de arriba a la izquierda, 255. El de abajo a la derecha vale 0, igual que la esquina de la imagen, que es fondo.

El archivo esfera.png mide 60 por 60. Verás una bola de 50 píxeles de diámetro. Es clara arriba a la izquierda y se oscurece hacia abajo a la derecha, donde se funde con el fondo negro.

Para ir más lejos: cambia la luz

Abre esfera.png en JupyterLab desde la carpeta de trabajo, después de ejecutar el comando de la esfera. Cambia -x - y + z por x - y + z en el comando y ejecútalo otra vez.

¿Por dónde llega la luz ahora? Mira qué lado de la bola queda clara antes de abrir la imagen.

Sobre este laboratorio y su código: Laboratorio de visión por computador: píxeles, umbrales y recuento de objetos en JupyterLab

¿Enseñas a una clase? Asigna este módulo a tus alumnos con guía del profesor, tareas con corrección automática y un laboratorio para cada alumno.