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
| Palabra | Qué significa | Ejemplo |
|---|---|---|
| Normal | la flecha perpendicular a la superficie en un punto | |
| Vector unitario | un vector de largo 1 | (0, 0, 1) |
| Producto escalar | multiplicar coordenada a coordenada y sumar | |
| Luz difusa | la que una superficie mate reparte en todas direcciones | |
| Brillo | el 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 luz | Producto escalar | Brillo |
|---|---|---|
| (0, 0, 1), de frente | 1 | 200 |
| (3, 0, 4), ya con largo 5 | 0,8 | 160 |
| (1, 0, 1), a 45 grados | 0,71 | 141 |
| (0, 1, 0), de lado | 0 | 0 |
| (0, 0, -4), por detrás | Negativo | 0 |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Calcula tres brillos con la base 200
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
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
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.