|
por Rodrigo Cabrera
- Pablo Castro - Rubén Falcón
- David Gonzalez
Rasterización: El término en sí se refiere
a las técnicas usadas para representar imágenes:
transformarlas en mosaicos de píxeles dibujados en diferentes
colores.
Puntos
Rasterizar un punto es muy fácil.
Todo lo que requiere es simplemente actualizar una posición
de memoria con un valor determinado para un color.
Hay que recordar que la primera entrada
en la video RAM se refiere al pixel superior izquierdo de la pantalla,
lo que determina que el sistema de coordenadas va en este caso
con X subiendo hacia la derecha, e Y subiendo hacia abajo :-)
Asumiendo que cada pixel está
representado por un byte, y que las dimensiones de la pantalla
activa son SCREEEN_X_SIZE por SCREEN Y_SIZE , para poner un punto
en (x,y) necesitamos acceder a la posición de memoria y*SCREEN_X_SIZE+x.
Aquí va un poco de código mostrando esto...
unsigned char *ScrBuffer //Buffer de pantalla
void Punto(int X,int Y, unsigned Color)
{
ScrBuffer[Y*SCREEN_X_SIZE+X]=Color;
}
Líneas
La rasterización de una línea
es una parte muy importante de código en cualquier librería
gráfica. También es usada para dibujo de polígonos,
y algunas otras rutinas.
Lo que hay que hacer es asignar un color
específico a todos los puntos en la trayectoria de la línea.
Fácilmente podemos calcular las
coordenadas de todos los puntos pertenecientes a la línea
(xstart,ystart) - (xend,yend).
Repasando un poco de geometría
básica, podemos ver que
dx=xend-xstart
dy=yend-ystart
entonces:
x
dx
x * dy
----- = ----- e y=----------
y
dy
dx
Dado que lo que queremos es asignar todos los puntos en una trayectoria, solamente tendríamos que tomar todos los X en el rango [xstart,xend] y calcular los respectivos valores de Y. Hay, sin embargo, un problema: solamente tenemos, como máximo, (xend-xstart) coordenadas calculadas. Si el rango de Y es mayor que el de X, la trayectoria que calculasemos no sería continua. En este caso, deberíamos calcular las coordenadas al revés, tomando el rango [ystart,yend] y calculando la respectiva X.
Lo que acabamos de explicar es un
algoritmo lógico y fácil de programar, pero no es
usado prácticamente nunca. La razón de esto es la
forma en que se hicieron los cálculos. Se usaron divisiones,
y la división es por lejos la operación más
lenta que realiza cualquier computadora, aún usando coprocesadores
matemáticos
Lo que haremos en cambio, sin usar ninguna
división, será utilizar el algoritmo de Bresenham.
De esta manera podemos encontrar todos los puntos usando algunas
operaciones lógicas y sumas de enteros.
La idea es encontrar el mayor rango entre
[xstart,xend] y [ystart,yend], ir punto por punto y tener una
variable que diga cuándo es tiempo de avanzar en el otro
rango.
Consideremos qué está pasando
en cierto momento durante la rasterización de la línea.
Asumamos que X es el rango mayor(dx>dy) y en esta línea
xstart<xend y ystart<yend:
Aquí, acabamos de dibujar un píxel P en las coordenadas (x,y) y ahora tenemos que decidir hacia dónde vamos, si al píxel L(x+1,y) o al H(x+1,y+1). Los puntos P,H,L en este caso representan centros de píxeles. Dado que la línea tiene que pasar por algún lado en el medio entre H y L, debemos dibujar el píxel cuyo punto está más cerca del punto de intersección I(x+1,real_y). Este píxel puede ser encontrado comparando los segmentos h y l resultados de la intersección. Recordando la dependencia usada en el método de línea anterior, tenemos que:
dy*(x+1)
real_y=-----------
dx
y
dy*(x+1)
h=y+1-real_y => h=y+1- -------------
dx
dy*(x+1)
l=real_y-y => l= ------------- - y
dx
Ahora bien, nosotros estamos interesados en comparar l y h:
dy*(x+1)
l - h = 2* --------------- - 2y-1
dx
Entonces, si l-h>0, significa que
l>h, el punto de intersección L está más
cerca del punto H, entonces H debería ser dibujado, si
l<h el dibujado sería L.
Multipliquemos ambos lados por dx:
dx(l-h)=2dyx+2dy-2dxy-dx
Ahora, como dx está asumido mayor que 0, entondes los signos de (l-h) y dx(l-h) deben ser iguales. Llamemos d a dx(l-h) y encontremos su signo y valor en un lugar Y y el siguiente Y+1:
d = 2dyx - 2dxy +2dy-dx
i
i-1 i-1
d = 2dyx - 2dxy +2dy-dx
i+1
i i
También es posible hallar el valor inicial de d en el primer punto, ya que x==0 e y==0
d =2dy-x
1
Como el signo de d determina hacia cual punto movernos (H o L), vamos a encontrar el valor de d en algún punto i, asumiendo que conocemos su valor en i-1 por cálculos previos:
d - d = 2dyx - 2dxy - 2dyx + 2dxy
i+1 i
i
i i-1
i-1
d - d = 2dy(x - x ) - 2dx(y - y)
i+1 i
i i-1
i i-1
Dependiendo de la decisión tomada en el punto anterior, el valor de d en el próximo punto podría ser:
Si H fue el elegido, porque x - x
=1 e y - y =1
i i-1 i
i-1
d - d = 2dy-2dx
i+1 i
d = d +2dy-2dx
i+1 i
Si, en cambio, el elegido fue L, porque
x - x =1 e y - y =0
i i-1 i
i-1
d - d = 2dy
i+1 i
d = d +2dy
i+1 i
Esto puede parecer un poco complicado,
pero el código para implementarlo ciertamente no lo es.
Lo que tenemos que hacer es encontrar el valor inicial de d y,
dentro del bucle y dependiendo de su signo, añadirle ya
sea (2dy-2dx) o solamente 2dy. Ya que estos valores son constantes,
pueden ser precalculados fuera del bucle.
En la teoría arriba, asumimos
que xstart<xend e ystart<yend. En la vida real, sin embargo,
no podemos garantizar que estas convenciones se cumplan. La manera
de resolver esto es simplemente cambiando los incrementos por
decrementos cuando las suposiciones de arriba no concuerdan con
la realidad.
Debemos también recordar que en
el bucle, cuando el punto elegido es L, el único valor
que se incrementa o decrementa es el asociado al eje con el rango
de línea más grande, mientras que si es elegido
H, tanto X comoY deben ser incrementados o decrementados, ya que
se avanza sobre los dos ejes.
Como puede verse, este algoritmo no involucra ninguna división. Si tiene multiplicación por 2, pero casi todos los compiladores optimizan esto a un SHL. De cualquier forma, si no confiamos en nuestro compilador, podemos hacerlo nosotros mismos...