Home Map Index Search News Archives Links About LF
[Top Bar]
[Bottom Bar]
 
Antonio Castro 
 

Acerca del Autor: Soy informático de profesión y llevo ya varios años ganándome la vida con esto. Lo que más tiempo llevo manejando es el lenguaje C. Tengo experiencia también como administrador de Unix aunque ahora estoy desarrollando con Visual C++ en WindowsNT (Puaffff lo que hay que hacer para ganarse el pan) Desde que tuve conocimiento de la existencia de Linux y del modelo de organización empleado en su desarrollo me di cuenta que el futuro tenía que ir por aquí. Tengo muchas esperanzas puestas en Linux y no me está defraudando. Justo lo contrario me pasa con Microsoft.

Escribe al autor

Index
El arte y la técnica en la infografía
Objetos geométricos simples
Objetos geométricos compuestos
Utilización de bucles y condiciones en Povray
Prueba y error, frente al cálculo
Erizos marinos
Generación mediante programas externos
Optimización del uso de la CPU
Lo que se hecha de menos
Ejercicios
Aportación de los lectores

Diseñando estructuras iterativas en Povray

Resumen: En este artículo veremos la forma de usar Povray para el diseño de estructuras iterativas y como pueden lograrse con ellas bellas imagenes.


El arte y la técnica en la infografía.

Estos artículos sobre Povray tratan tanto de los aspectos técnicos como de aquellos que no lo son. La técnica ya lo hemos dicho es importante pero también hay que aprender a sacar el máximo provecho de ella. La infografía es tan distinta a otras disciplinas que quizás para algunos sea un medio donde puedan descubrir sus capacidades artísticas. No abandonaremos la técnica porque también es necesaria, pero la técnica es solo un instrumento, para dominar la forma de expresión. Por ello continuaremos mezclando la fría exposición de los conocimientos técnicos con aspectos más creativos.Se trata de dar ideas. Puntos desde los cuales se pueden explorar muchos caminos. Ocurre que muchas veces empiezo a diseñar ejemplos y me divierto tanto que las cosas que quería tratar en ese momento pasan a un segundo plano. Una exposición demasiado sistemática resultaría demasiado aburrida y por ello tampoco resultaría muy didáctica. El manual de Povray sí que está desarrollado siguiendo una secuencia sistemática de los diferentes temas. Como instrumento de consulta está muy bien. Yo lo uso mucho. A diferencia del sistema metódico y secuencial que se utiliza en el manual yo utilizo un método que podría decirse que sigue un recorrido en espiral volviendo a temas tratados para tratarlos con más amplitud. 

Las escenas que utilizaré como ejemplo para ilustrar algunos aspectos técnicos no serán meros ejercicios técnicos sino que intentaré buscar la belleza estética y un acabado más completo de la escena aunque eso suponga introducir elementos extraños no explicados aun en el ejemplo.

Poco a poco se irán revisando todos los conceptos para que lo que no se pudo comprender en una primera lectura se pueda asimilar en futuros artículos. Por otra parte muchos conceptos son tan visuales que apenas necesitan excesiva explicación porque el verlos en un ejemplo resulta suficiente para poder aplicarlo a la perfección. Quizas mejor incluso que si lo leemos en el manual sin una buena imagen como ejemplo. Tampoco hay que olvidar que para los que deseen avanzar más deprisa siempre pueden recurrir a este manual de Povray.

Objetos geométricos simples

La mayoría de estos objetos ya han sido utilizados en algún ejemplo. Los comentamos ahora a modo de repaso.

El número de objetos que se pueden diseñar usando estas formas básicas es enorme, y además hay que tener en cuenta que el uso de formas sencillas representa un ahorro considerable en el tiempo de proceso de la escena.

  • esfera

  • sphere { < x, y, z>, radio ..... } / 
    x, y, z son las coordenadas del centro de la esfera. 
    En el lugar de los puntos suspensivos se pueden añadir sentencias para 
    el escalado, rotación, traslación, pigmentación, textura, etc... 
  • cono

  • Realmente la sintaxis permite definir un tronco de cono. 
    cone { <x1, y1, z1>, rad1 <x2, y2, z2>, rad2 [open] ..... } 
    x1, y1, z1 son las coordenadas del centro en el un extremo del tronco 
    de cono, y rad1 el radio en este extremo. 
    x2, y2, z2 son las coordenadas del centro en el otro extremo del tronco de cono, y rad2 el radio en este extremo. Para que el tronco de cono se convierta en un cono basta con que uno de los radios sea cero. Si deseamos que sea un cono abierto añadiremos la palabra 'open' en caso contrario la omitimos y obtendremos un cono macizo. 
  • cilindro

  • cylinder { <x1, y1, z1>, <x2, y2, z2>, rad [open] ...... } 
    x1, y1, z1 son las coordenadas del centro en el un extremo del cilindro x2, y2, z2 son las coordenadas del centro en el otro extremo del cilindro como, y rad el radio del cilindro. Para que el cilindro se sea un cilindro abierto añadiremos la palabra 'open' en caso contrario la omitimos y obtendremos un cilindro macizo. 
    Recordemos que en caso de las esferas, conos y cilindros tenemos el recurso de deformar estas figuras usando unos factores de escala adecuados. También puede intentar deformar otros objetos pero en estos casos resulta especialmente interesante porque aparentemente se obtiene una figura distinta. 
  • plano

  • plane { <x, y, z>, dist ..... } 
    En esta ocasión x, y, z no representan una posición sino un vector cuya dirección es perpendicular a la superficie del plano. El valor dist será la distancia al centro de coordenadas. Recordemos que en Povray se puede abreviar la expresión de un vector. x = <0, 1 , 0> -z = <0, 0, -1> 
    Se puede usar en lugar de un plano una esfera de gran tamaño. Esto no causa ningún problema. Nosotros ya lo hemos utilizado alguna vez. 
  • caja

  • box { <x1, y1, z1>, <x2, y2, z2>...... } 
    x1, y1, z1 son las coordenadas de una de las esquinas de la caja x2, y2, z2 son las coordenadas de la esquina opuesta.

Objetos geométricos compuestos. (CSG)

Las primitivas CSG permiten combinar varias formas simples en una sola más compleja. Después de esto tenemos un objeto que puede ser manejado cómodamente en escalado, translaciones, giros, texturas, etc.. sin necesidad de tener que aplicar estas transformaciones por separado a cada uno de los componentes. 
Las formas en que se pueden combinar los elementos sencillos entre si son cuatro y existe una quinta complemento que se puede aplicar a un elemento solo. 
  • Unión

  • 'union { object {A} object{B} }' 
    Todos sus puntos dentro de A or B 
  • Intersección

  • 'intersection { object {A} object {B} }' 
    Todos sus puntos dentro de A y B 
  • Complemento

  • object {A inverse} 
  • Diferencia

  • 'difference { object {A} object {B} }' 
    Todos sus puntos dentro de A que no estén dentro B 
    Equivale a 'union { object {A} object {B inverse }' 
  • Mezcla

  • 'merge { object {A} object {B} }' 
    Como la union pero desaparecen las superficies internas 
    de los componentes A y B. Si los objetos son todos opacos 
    este detalle no ofrece ningún interés.
En los ejemplos siguientes se utilizan algunas de estas funciones. 
Como sistema para diseñar sin equivocarse se puede utilizar lo siguiente. Junte todas las primitivas que añaden volumen en un único objeto. Luego haga la intersección de este objeto tal cual con el complemento las primitivas que eliminan volumen 
#define ManzanaMordida = intersection {
 object { ManzanaEntera }
 object { Mordisco1 inverse }
 object { Mordisco2 inverse }
}
Procure que las figuras compuestas no tengan superficies en común, porque puede producir una indeterminación para establecer a que objeto pertenece esa superficie en común. Para solucionar esto basta desplazar en una cantidad muy pequeña uno de los objetos.

Utilización de bucles y condiciones en Povray.

Los elementos de una escena se describen generalmente en un orden arbitrario. A pesar de ello hay circunstancias en que puede ser necesario implementar bucles descriptivos para crear estructuras iterativas por ejemplo. En Povray existen varias formas de hacerlo. Una mediante las estructuras de control de flujo que proporciona el propio lenguaje de Povray. En su momento ya mencionamos las directivas de lenguaje #declare e #include y calificamos muchas otras como de menor importancia. Ahora las usaremos pero veremos que tampoco son imprescindibles.

En el siguiente ejemplo usamos un programa escrito directamente en lenguaje Povray. Tengo que decir que personalmente es la primera vez que uso esto. El motivo es que no lo consideré muy necesario. Se puede conseguir lo mismo con programación externa que produzca el fuente necesario, y veremos más adelante un ejemplo de esta segunda posibilidad, que yo uso bastante a menudo. Se pueden hacer cosas mixtas también. Veremos que el estilo de programación con Povray no queda tan elegante como con un lenguaje de propósito general. La razón es que Povray se pensó como un lenguaje de descripción de escenas, y estas cosas son añadidos recientes. Vemos que con Povray se pueden programar toda clase de bucles y expresiones matemáticas complejas. En mi opinión está muy bien que Povray incorpore estas cosas pero no son imprescindibles. Todo ello se puede compensar utilizando programación externa, para generar un fuente más o menos complejo o iterativo. Creo que el hacerlo de una manera u otra nada tiene que ver con la parte artística que es la importante, por lo tanto no tiene especial merito hacerlo de una manera u otra. Lo más importante es la imagen final. En menor medida también puede ser importante el esfuerzo de desarrollo y la cantidad de proceso empleado. Si conoce un lenguaje de propósito general con el que se sienta más cómodo utilice a Povray como un paso final. Si quiere implementar algo sencillo puede usar Povray directamente para ello. Veremos ambas formas de hacerlo, pero antes comentaremos un par de cosas más.

Prueba y error, frente al cálculo.

Tenga en cuenta que muchas de las escenas pueden diseñarse partiendo de una idea. Luego se prueba con valores estimados por aproximación, y por último se va corrigiendo mediante prueba y error hasta obtener el resultado deseado. Para ello ya diseñamos una herramienta que nos permite usar Povray cómodamente (POV). Si piensa que todo puede hacerse con este sistema está equivocado. En ocasiones no queda más remedio que coger el lápiz, el papel y la calculadora. Además los problemas que tenemos que resolver son problemas geométricos en 3D. Es necesario un mínimo de conocimientos de geometría espacial y de trigonometría si quiere conseguir ciertos efectos. Generalmente basta con saber aplicar unas pocas fórmulas. Recordamos aquí unas pocas.

sin(a) = A / C = (Cateto opuesto / Hipotenusa) 
cos(a) = B / C = (Cateto adyacente / Hipotenusa) 
tan(a) = A / B = (Cateto opuesto / Cateto adyacente) 

Por tanto:
A = sin(a)/C 
B = cos(a)/C 
C = sqrt(A^2 + B^2)
Como 2* Pi radianes = 360º

1 Radian = 180 / Pi = 57,29577951308232
1º = Pi / 180 = 0,174532925199432957

Vamos a recordar los rangos de las principales funciones trigonométricas, en función del cuadrante:

Quadrante  Seno  Coseno  Tangente 
0 .. 90  0 .. +1  +1 .. 0  0 .. +Infinito 
90 .. 180  +1 .. 0  0 .. -1  -Infinito .. 0 
180 .. 270  0 .. -1  -1 .. 0  0 .. +Infinito 
270 .. 360  -1 .. 0  0 .. +1  -Infinito .. 0 
 

a = atan(A/B)

Esta última función es un poco ambigua. Realmente no podemos conocer el valor de Alfa si no conocemos el cuadrante, por ello es preferible usar:
a = atan2(A,B)

Distancia entre P1(x1,y1,z1) y P2(x2,y2,z2) es 

D = sqrt( (x2-x1)^2 + (y2-y1)^2 + (z2-z1)^2 ) 

Hay muchas fórmulas útiles en trigonometría pero creo que con estas es suficiente para la mayoría de los casos. En Povray se asume que para las funciones trigonométricas trabajamos con radianes por lo que usaremos la función de conversión radians(). radians(Alfa) Por el contrario para los giros se asume que trabajamos con grados. :-(

Erizos marinos

El ejemplo siguiente es un buen ejercicio para repasar la trigonometría básica. Se puede apreciar que en el erizo las púas tienen una colocación bastante buena. Esto no se puede improvisar, hay que analizarlo, elegir un algoritmo e implementarlo con la idea clara de lo que se quiere obtener. El fuente está ampliamente comentado para resultar lo más claro posible. Es un caso claro en el que se especifica una estructura altamente iterativa. En este caso elegimos Povray como lenguaje para implementar esta estructura iterativa pero no existe ninguna razón para no hacerlo en la otra forma que veremos más tarde mediante un programa externo.
erizo 

Empezmos mostrando el fuente de los peces. Se trata de una figura compuesta en la que usamos las primitivas CGS que ya hemos comentado.
 

// (Copyright) (No modificar estas 11 primeras Lineas) 
// Autor: Antonio Castro Snurmacher  
// (E-mail acastro@ctv.es ) 
// 
// Este fuente puede ser utilizado, distribuido, y modificado
// libremente pero siempre se deberá respetar la propiedad
// intelectual de su autor.  El autor renuncia a todo tipo de
// beneficio económico y no se hace responsable de los
// posibles perjuicios derivados del uso del mismo.  Toda
// modificación queda sujeta a las mismas condiciones de uso
// que el original.  En caso de traducción deberá conservarse
// el texto original de esta cabecera y añadirse la traducción
// a continuación de ella.
///////////////////////////////////////////////////// 

//------------------------------------------------------------ 
// balistap.inc (1-Mayo-1998) 
//------------------------------------------------------------ 
// Esta versión está dedicada a su inclusión en la revista 
//                     LinuxFocus 
//------------------------------------------------------------ 
  

#declare Color1 = color red 0.5 green 0.2 blue 0.4 
#declare ColorEye = color red 0.5 green 0.5 blue 0.5 

// Aleta inferior
#declare AletDown =
intersection {
   sphere { <-10,-7,0> 10
      scale <2, 1, 0.1>
   }
   sphere { <-15,-17,0> 10
      scale <2, 1, 1>
      inverse<
   }
   cone { <-10, 0, 0>, 0 <-45, 0, 0>, 20 inverse }
   pigment { color Color1 }
}

// Aleta superior
#declare AletUp =
intersection {
   sphere { <-10,7,0>, 10 
         scale <2, 1, 0.1>
   }
   sphere { <-15, 17,0>, 10 
         scale <2, 1, 1>
         inverse
   }
   cone { <-10, 0, 0>, 0 <-45, 0, 0>, 20 inverse }
   pigment { color Color1 }
}

// Aleta caudal (cola)
#declare Tail =
intersection {
            sphere { <-19,0,0>, 10
                    scale <2, 1, 0.1>
             }
             sphere { <-25,0,0>, 10
             scale <2, 1.3, 1>
             inverse
             }
             pigment { color Color1 }
}

// Pez completo
#declare Balistap =
union{
   sphere { <10, 4,0>,4 
      pigment { color ColorEye }
         scale <1,1, 0.6>
   }
   sphere { <10.2, 4.35,0>,3.43
      pigment { color Gray20 }
         scale <1,1, 0.7>
   }
   sphere { <0,0,0> 10 
         scale <2, 1, 0.3>
        pigment { 
           gradient y 
           colour_map {
                [0.0  colour White]
                [0.1  colour Grey]
                [0.99  colour Black]
           }
          scale <1, 17, 1>
          translate <0, -7, 0>
        }
   }

   cone {<19, 0, 0>, 1.5 <-2, 0, 0> 8
        scale <1,1, 0.5>
        pigment { color Color1 }
   }
   cone {<21, 0, 0>, 1 <0, 0, 0> 13
        scale <1,1, 0.1>
        pigment { color Color1 }
   }
  object {AletUp}
  object {AletDown}
  object {Tail}
}

Ahora el fuente principal donde definimos el resto de la escena. Lo más destacable es la definición de los erizos que sin duda son los protagonistas de la escena.
/////////(Copyright) (No modificar estas 11 primeras Lineas)  
// Autor: Antonio Castro Snurmacher (E-mail acastro@ctv.es ) 
// 
// Este fuente puede ser utilizado, distribuido, y modificado
// libremente pero siempre se deberá respetar la propiedad
// intelectual de su autor.  El autor renuncia a todo tipo de
// beneficio económico y no se hace responsable de los
// posibles perjuicios derivados del uso del mismo.  Toda
// modificación queda sujeta a las mismas condiciones de uso
// que el original. En caso de traducción deberá conservarse
// el texto original de esta cabecera y añadirse la traducción
// a continuación de ella.
/////////////////////////////////////////////////// 
  

//-----------------------------------------------------------
//erizo.pov (1-Mayo-1998)
//-----------------------------------------------------------
//Esta versión esta dedicada a su inclusión en la revista //
//LinuxFocus
//-----------------------------------------------------------

#include "colors.inc"
#include "textures.inc"
#include "balistap.inc"

#declare RadioCuerpo = 5
#declare NumEspinasMeridiano = 40

// Definimos el tamaño de las puas en función del tamaño del
// cuerpo Los erizos de mar en la naturaleza presentan puas
// largas en la parte superior y cortas en la parte inferior.

#declare LongitudMaximaPua  = RadioCuerpo * 2
#declare LongitudMinimaPua  = RadioCuerpo / 4

// Color del erizo
#declare TexturePua = texture { pigment {VeryDarkBrown} }
#declare TextureCuerpo = texture { pigment {DarkBrown} }

// Todas las definiciones que siguen a continuación están
// calculadas a partir de las anteriores.  Suponemos
// inicialmente un erizo con cuerpo esférico.  pi está
// predefinido como

#declare pi = 3.1415926535897932384626
#declare LongitudMeridiano  = 2 * pi * RadioCuerpo

// Suponemos que está totalmente recubierto de púas cónicas. 
// El radio de una púa en su base será 'RadioPua'  

#declare MeridianoPua =  LongitudMeridiano / NumEspinasMeridiano
#declare RadioPua     =  MeridianoPua / 2

// Usaremos la notación de eje, meridiano, y paralelo en el
// erizo tal como lo haríamos con el eje, los meridianos, y
// paralelos terrestres.  Para recubrir el erizo totalmente de
// púas trazaremos varios circulos 'paralelos del erizo', y
// para ello tomaremos un 'meridiano del erizo' como punto de
// partida de todos ellos. Llamaremos angulo vertical al
// angulo formado con el 'eje del erizo' y el punto de
// comienzo de un paralelo. En los polos este angulo vertical
// valdrá 0 y 180, y en el ecuador valdrá 90. Necesitamos ir
// incrementado este angulo para procesar en cada uno de ellos
// las puas de un paralelo.  Para calcular el incremento del
// angulo vertical hacemos una regla de tres LongitudMeridiano
// ---> 360 MeridianoPua ---> IncAngVert

#declare IncAngVert   = 360 * MeridianoPua / LongitudMeridiano

// Para que el erizo no sen unda en la arena ni flote en el
// agua calculamos la distancia del centro al extremo de las
// puas pequeñas situadas en la parte inferior del erizo.

#declare CorreccionY = RadioCuerpo + LongitudMinimaPua
camera {
        location < -40, 40, -40>
        look_at < 25, CorreccionY , 25>
}

// En el fondo del mar la luz llega de varios puntos debido al
// oleaje en la superficie. Para simular esto usaremos varias
// fuentes de luz.

light_source { <-200, 300, -200> color White}
light_source { <-300, 300, -100> color White}
light_source { <-100, 300, -300> color White}
light_source { <0, 1200, 0> color White}

// Para conseguir la coloracion del agua utilizamos un efecto
// atmosférico.

fog { distance 250 color SeaGreen   }

// La arena la definimos con un color Sienna, y con profundas
// ondulaciones de gran tamaño.

plane { y, 0
  pigment { Sienna }
  normal {
  ripples 1.0
  frequency 300.0
  }
  finish {
  ambient 0.1
  diffuse 0.9
  }
  scale <3000, 3000, 3000>
}

// ******************* Declaracion del erizo ****************

#declare erizo = object {
union {

// Calcularemos un paralelo de puas para cada valor de AngVert
// El primer valor será 0. (0 puas en la misma dirección del
// eje vertical.  El segundo valor serán una pocas puas
// situadas en el primero paralelo El máximo valor se
// conseguirá para AngVert == a 90 porque es la zona del
// ecuador donde cabe el máximo de puas.  Las puas en un mismo
// meridiano se calculan variando el ángulo horizontal
// 'AngHoriz'

#declare AngVert=0
#while (AngVert < 180 )
#declare RadParalelo = abs ( RadioCuerpo * sin(radians(AngVert)))
#declare LongitudParalelo = 2 * pi * RadParalelo
#declare NumEspinasParalelo = LongitudParalelo / MeridianoPua
#declare LongitudPua = LongitudMinimaPua + (   \
     (LongitudMaximaPua-LongitudMinimaPua) * ((180-AngVert)/180) )
// #declare LongitudPua = LongitudMaximaPua
#declare IncAngHoriz = 360 / NumEspinasParalelo
#declare Ybase = RadioCuerpo * cos (radians(AngVert))
#debug concat("\nAngVert=", str(AngVert,5,0), \
   " LongitudPua=", str(LongitudPua,5,0),     \
   "Ybase=", str(Ybase,5,0), "  ");
#declare Ypunta = (RadioCuerpo + LongitudPua)* \
                      cos (radians(AngVert))
#declare AngHoriz=0
#while (AngHoriz < 360)
#declare Xbase = RadParalelo * cos (radians(AngHoriz))
#declare Xpunta = (RadParalelo + LongitudPua) * \ 
                      cos (radians(AngHoriz))
#declare Zbase = RadParalelo * sin (radians(AngHoriz))
#declare Zpunta = (RadParalelo + LongitudPua) * \
                      sin (radians(AngHoriz))  
//#debug concat( "Vert=", str(AngVert,5,0), \
//               "  Horiz=", str(AngHoriz,5,0), \
//               "\n")     
cone { , RadioPua, , 0 
texture { TexturePua }
}
#declare AngHoriz =AngHoriz + IncAngHoriz
#end
#declare AngVert=AngVert+IncAngVert
#end


// El cuerpo es una esfera.
sphere { <0,0,0> RadioCuerpo
texture { TextureCuerpo }
}

} // end union
// Colocamos el erizo a la altura correcta.
translate y*CorreccionY

// Pero los erizos no son esféricos sino que tanto el cuerpo
// como el resto están achatados. Lo que hacemos es conservar
// la proporción en el eje Y aumentando las proporciones en X,
// y Z.  Multiplicándolas por 1.5

scale <1.5, 1, 1.5>
} // end object erizo

// Ya tenemos un erizo perfecto. Vamos a colocar unos cuantos
// En primero lugar establecemos una distancia mínima entre
// ellos.

#declare DistanciaMinima = 3 * (RadioCuerpo+LongitudMaximaPua)

// Vamos a disponerlos en un cuadrado de 5 * 5.  Para evitar
// que se vean como una formación excesivamente geométrica Los
// desplazamos ligeramente en horizontal con valores
// aleatorios.

#declare Xi=0
#declare R1 = seed(0);
#while (Xi < 5)
#declare Yi=0
#while (Yi<5)
#declare Xpos= Xi * DistanciaMinima + \
           ( rand(R1) * DistanciaMinima * 0.5 )

#declare Ypos= Yi * DistanciaMinima + \
           ( rand(R1) * DistanciaMinima * 0.5 )

#debug concat ("\nXpos=", str(Xpos, 5, 0), \
               "  Ypos=", str(Ypos, 5,0))

object {erizo
translate
}
#declare Yi= Yi+1
#end
#declare Xi= Xi+1
#end

// Vamos ahora a colocar los peces. Usaremos un procedimiento
// muy parecido al utilizado para colocar los erizos. En este
// caso en lugar de disponerlos en un único cuadrado de 5 * 5
// los colocaremos en tres grupos de 4 * 4 En primero lugar
// establecemos una distancia mínima entre ellos.

#declare DistanciaMinima = 90
#declare Xi=0
#declare R1 = seed(0);
#while (Xi < 4)
#declare Yi=0
#while (Yi<4)
#declare Xpos= Xi * DistanciaMinima + \
       ( rand(R1) * DistanciaMinima * 0.5 )

#declare Ypos= Yi * DistanciaMinima + \
      ( rand(R1) * DistanciaMinima * 0.5 )

#debug concat ("\nXpos=", str(Xpos, 5, 0), \ 
      "  Ypos=", str(Ypos, 5,0))

object { Balistap 
         scale  1.2
         rotate y*50*rand(R1)
         translate
        }

object { Balistap 
         scale  1.2
         rotate y*50*rand(R1)
         translate
        }

object { Balistap 
         scale  1.2
         rotate y*50*rand(R1)
         translate
        }
#declare Yi= Yi+1
#end
#declare Xi= Xi+1
#end

// Vamos a colocar a uno de ellos como si estuviera comiendo
// algo en el fondo.

object { Balistap
         scale 1.1
         rotate z* -45
         rotate y*200
         translate<80, 19, 360>
        }

/** Este nos puede servir para visualizar el pez completo *****

object { Balistap 
         scale  1.1
         rotate y*225
         translate<25, 40, 25>
        }

**********************
Hay elementos en esta escena que aún no hemos comentado. Son los efectos de superficie (arena ondulada). Tampoco hemos tratado los efectos atmosféricos (En este caso nos referimos a una niebla de color verde mar muy espesa y muy pero que muy húmeda.) :-), también venimos usando las figuras compuestas y aún no las hemos explicado. Se trata de que se puede definir una figura como unión, intersección, diferencia o complemento de otras. En suma se trata de una forma de combinar elementos sencillos para lograr otros más complejos. Esto último lo veremos más adelante en este mismo artículo.

La luz de esta escena se consigue usando muchos puntos de luz para simular la iluminación subacuática caracterizada por el desorden introducido por el oleaje en superficie. Un solo foco de luz nos delataría en seguida porque la sombra del erizo en el fondo sería demasiado nítida.


Generación mediante programas externos.

Un trazador de rayos es solo la herramienta para generar toda una escena que ha sido especificada con ayuda de un lenguaje formal que es el trazador de rayos. Éste es capaz de entender las especificaciones de formas colores, luces , etc... pero muchas veces existe un trabajo previo para obtener lo que deseamos. Escaneres tridimensionales, programas de modelado, traductores de formatos, programación. etc... En este sentido un trazador de rayos puede utilizarse como un eslabón más en la cadena de herramientas, de todo tipo que nos permiten obtener un resultado final. En otras palabras no sólo es posible diseñar tecleando directamente el código fuente en el lenguaje del trazador de rayos para plasmar nuestra idea. Podemos ayudarnos de herramientas y también de la programación para generar toda clase de figuras complejas altamente repetitivas o recursivas.

Vamos a poner un ejemplo de lo estamos diciendo. Este programa está escrito en C y trata de lo siguiente. Define un espacio con las proporciones de la resolución empleada (1000*750). Una vez definida esta área, el programa trata de situar esferas en dicha área de forma aleatoria. Habrá que detectar que la posición generada no tiene cerca ninguna otra de las esferas ya generadas para evitar que se ínter-penetren. Para controlar esto vamos guardando la posición de cada esfera y su tamaño. Si la posición obtenida cae encima de una esfera ya existente probaremos en otro lugar. En caso contrario comprobamos cual es la distancia mínima al borde más cercano del resto de las esferas. Si es necesario reducimos el tamaño de la esfera para que pueda entrar. En esta ocasión la nueva esfera será menor que el tamaño inicial establecido y quedará en contacto con otra. Después de un elevado número de iteraciones obtendremos una superficie llena de esferas de distintos tamaños sin apenas espacios libres entre ellas. Estamos usando mucha fuerza bruta de cálculo porque cada vez el programa necesita más intentos para colocar una nueva esfera.

/////////(Copyright) (No modificar estas 11 primeras Lineas)
// Autor: Antonio Castro Snurmacher (E-mail acastro@ctv.es ) 
//
// Este fuente puede ser utilizado, distribuido, y modificado
// libremente pero siempre se deberá respetar la propiedad
// intelectual de su autor.  El autor renuncia a todo tipo de
// beneficio económico y no se hace responsable de los
// posibles perjuicios derivados del uso del mismo.  Toda
// modificación queda sujeta a las mismas condiciones de uso
// que el original. En caso de traducción deberá conservarse
// el texto original de esta cabecera y añadirse la traducción
// a continuación de ella.  

/////////////////////////////////////////////////////////////

//-----------------------------------------------------------
//burbujas.c (1-Mayo-1998) */
//-----------------------------------------------------------
//Esta versión esta dedicada a su inclusión en la revista
//LinuxFocus
//-----------------------------------------------------------

  

/*************************************************************/ 
/** Compilar mediante cc balls.c -lm -o balls **/
/*************************************************************/ 

#include 
#include 
#include 

#define MAX_X       1000.0
#define MAX_Z        750.0
#define MAX_BALLS   10000
#define MIN_RAD     3.0
#define MAX_RAD     55.0

double PosX[MAX_BALLS];
double PosZ[MAX_BALLS];
double Rad[MAX_BALLS];
int contBall=0;
int contIter=0;
int i;

/*****************************************/
void intento(double x, double z, int cont){
   double distX2;
   double distZ2;
   double rad, cur_rad;

   contIter++;
   rad=MAX_RAD;
   for (i=0; i MAX_RAD)
         rad = MAX_RAD;
   if (rad >= MIN_RAD ){
       Rad[contBall]=rad;
       PosX[contBall]=x;
       PosZ[contBall]=z;
       printf("sphere { <%4.0f, 0, %4.0f> %3.7f
               texture { textureBall } } //(%d/%d)\n",
               x, z, rad, contBall,contIter);
       contBall++;
       if ( (contBall >= MAX_BALLS) )  ){
              exit(0);
       }
          else{
              return;   /** Siguiente bola **/
          }
   }
   return; /** fallo nuevo intento **/
}

/****************************************/
int r_rand(int m){
   return (int) ( (double)m * rand()/(RAND_MAX+1.0));
}

/****************************************/
main (){
  int X, Z;

  for(;;){
        X=r_rand(MAX_X);
        Z=r_rand(MAX_Z);
        X -= MAX_X/2;
        Z -= MAX_Z/2;
        intento(X, Z, contBall);
   }
}
Una vez compilado lo ejecutaremos con la salida entandar redirigida a un fichero 'burbujas.inc' donde se grabaran los datos. burbujas > burbujas.inc La salida obtenida será de este tipo:
sphere{<-375, 0,   33> 55.0000000 texture{Gold_Metal}} //(0/1)
sphere{< -86, 0,   62> 55.0000000 texture{Gold_Metal}} //(1/2)
sphere{<-326, 0,  346> 55.0000000 texture{Gold_Metal}} //(2/3)
sphere{< 190, 0, -156> 55.0000000 texture{Gold_Metal}} //(3/4)
sphere{<  62, 0, -293> 55.0000000 texture{Gold_Metal}} //(4/5)
sphere{< 323, 0,  161> 55.0000000 texture{Gold_Metal}} //(5/6)
sphere{< 341, 0,  -15> 55.0000000 texture{Gold_Metal}} //(6/7)
...................

Tenemos que tener paciencia, pero si vemos que pasa el tiempo y no hay avances podemos interrumpir el proceso. Bastará editar el fichero y comprobar que la última linea está entera y sino quitarla. El fuente pov será el siguiente:

/////////(Copyright) (No modificar estas 11 primeras Lineas)
//Autor: Antonio Castro Snurmacher (E-mail acastro@ctv.es )
// 
// Este fuente puede ser utilizado, distribuido, y modificado
// libremente pero siempre se deberá respetar la propiedad
// intelectual de su autor.  El autor renuncia a todo tipo de
// beneficio económico y no se hace responsable de los
// posibles perjuicios derivados del uso del mismo.  Toda
// modificación queda sujeta a las mismas condiciones de uso
// que el original. En caso de traducción deberá conservarse
// el texto original de esta cabecera y añadirse la traducción
// a continuación de ella.
//////////////////////////////////////////////////////////////
  

//------------------------------------------------------------

// burbujas.pov (1-Mayo-1998) 
//------------------------------------------------------------

// Esta versión esta dedicada a su inclusión en la revista Lin
// uxFocus
//------------------------------------------------------------

// Se recomienda usar el siguiente comando para la utilidad 'pov' 
// pov burbujas 6 9 1 4  
//              ^ ^ ^ ^  
//              | | | |  
// Resolucion __/ | | |  
// Calidad ____/  | | | 
// Desde Clock = 1 _/ |  
// Hasta Clock = 4 ___/  

#include "colors.inc"
#include "textures.inc"

// Por defecto max_trace_level vale 5 . Aumentado a 15
// conseguimos mayor nivel detalle en las imágenes recursivas
// generadas por reflejos

global_settings {
#max_trace_level 15
}

// Declaramos distintos puntos de vista y solo elegiremos
// finalmente una en función del valor de Clock

#declare img1 =
camera {
  location <0, 10000 ,0 >
  look_at <0, 0, 0>
  angle 7
}

#declare img2 =
camera {
  location <0, 250 ,0 >
  look_at <0, 0, 0>
  angle 130
}

#declare img3 =
camera {
  location <12, 3 ,12 >
  look_at <200, 3, 50>
  angle 30
}

#declare img4 =
camera {
  location <12, 3 ,12 >
  look_at <200, 3, 50>
  angle 120
}

  #switch (clock)
    #case (1)
      // This section is parsed if clock=1
       camera {img1}
    #break  
    #case (2)
      // This section is parsed if clock=2
       camera {img2}
    #break  
    #case (3)
      // This section is parsed if clock=4
       camera {img3}
    #break 
    #case (4)
      // This section is parsed if clock=5
       camera {img4}
    #break 
    #else
      #warning "Clock outside expected range\n"
  #end // End of conditional part

// Fuente de luz blanca
object {
  light_source {
  <100, 1000, 100>
  color White
  }
}

#declare textureBall = texture { Gold_Metal }

// El fichero include 'burbujas.inc' es un fichero obtenido
// mediante la salida de un programa escrito en lenguaje C.

#include "burbujas.inc"
Volvemos a usar el valor clock pero en este caso con un propósito muy diferente. En este caso no se producirá una secuencia de imágenes correspondientes a una animación sino cuatro imágenes muy distintas, como consecuencia de variar la posición y el ángulo de apertura del objetivo de la cámara.

Ya mencionamos en el artículo anterior que veríamos algunos ejemplos para comprobar las posibilidades de la cámara. Pues bien nos referíamos a este ejemplo. Podemos ver los distintos efectos de perspectiva conseguidos dependiendo de usar un ángulo muy pequeño desde muy lejos, o usar un ángulo muy abierto desde muy cerca. El máximo ángulo posible para la apertura del objetivo es 180. Es un caso extremo en el que no se distinguen las formas. Como puede verse un simple juego con la cámara puede producir efectos interesantes.

Los renders se han realizado en un Pentium 200 MMX 48 MB Ram (398 bogomips). Lo ejecutamos según se sugiere usando nuestra herramienta 'pov' con el comando siguiente:


pov burbujas 6 9 1 5 1 5
Tiempo total = 

Los parametros y sus significados son: 

  • Usaremos el fuente $HOME/dat/pov/LF2-Burb1/LF2-Burb1.pov 
  • Tamaño 6 = (400x300) 
  • Calidad 9 
  • fotograma inicial = 1 
  • fotograma final = 5 
  • clock inicial = 1 
  • clock final = 5 
La diferencia de tiempos entre los distintos fotogramas es muy considerable. Trazando con mayor calidad se obtienen los siguientes tiempos
pov burbujas 9 9 1 5 1 5

Tiempo total = 4 horas y media.
Primer fotograma 2 minutos
Segundo fotograma 5 minutos.
Tercer fotograma 10 minutos
Cuarto fotograma 13 minutos
Quinto fotograma 4 horas !!

Veamos ahora los resultados. En primer lugar en los cuatro primeros fotogramas solo varia la cámara. (posición, angulo, dirrección, etc) 

   
   

Falta un fotograma que lo mostraremos seguidamente pero antes vamos a tratar algunos temas en relación con este último fotograma.

Optimización del uso de la CPU

Como vemos, el último fotograma es el más complejo, y Povray no ha sabido optimizarlo. Cuando tenemos una figura compuesta, el trazador intenta determinar una figura más simple que lo envuelva.

Esto en versiones anteriores de Povray había que hacerlo siempre a mano. Se especificaba con la primitiva 'bounded_by' una forma que envolviera a uno o más objetos y de esta forma el trazador asumía que los rayos que no incidían en este en voltorio tampoco incidían en los objetos internos ahorrandose esa comprobación. Actualmente son poco frecuentes los casos donde una optimización manual resulte util, pero estamos precisamente ante uno de esos casos.

El último fotograma se corresponde con una figura compuesta excesivamente compleja. Por ello habría sido mejor o bien agrupar las burbujas por zonas bien delimitadas y combinarlas previamente en varias figuras compuestas para guiar así al optimizador o bien de combinarlas todas juntas en una como se hace en el ejemplo pero envolviendo grupos de esferas que esten en una zona bien delimitada usando 'bounded_by'. La sintaxisis sería:

    union {
    sphere { <x1, y1, z1>, r1 } 
    sphere { <x2, y2, z2>, r1 }
    sphere { <x3, y3, z3>, r1 }
    ..........................
    bounded_by { sphere { <xb, yb, zb>, rb } }
  }

Se puede aplicar no solo a uniones o intersecciones etc. sino a cualquier objeto, y se puede usar en 'bounded_by' una forma distinta para envolver aunque los mejores resultados los dan las esferas y las cajas.

Si la forma elegida deja fuera una parte del objeto el resultado puede ser defectuoso. Repito que la necesidad de hacer esto es muy rara. Este último ejemplo tenía muy mala idea y el optimizador de Povray no pudo hacer más. Se trata de un objeto compuesto con 2154 esferas. Puede contarlas en la imagen si no me cree. :-) La última imagen es el resultado de seguir jugando con las posibilidades del trazador de rayos.

Muchas veces a una misma idea se le pueden sacar muchas posibilidades y es aquí donde entra nuestra creatividad y nuestra imaginación. Es decir, que muchas veces no basta con partir de una buena idea sino que hay que seguir imaginando y jugando con distintas posibilidades hasta encontrar algo que nos guste.

 

Se pueden imaginar cantidad de escenarios en los que la programación puede servir de apoyo a la generación de imagenes sintéticas, estáticas o en movimiento.

Lo que se hecha de menos.

Povray proporciona muchas funciones matemáticas más o menos interesantes, pero hay una interesantísima que falta. Se trata de la una función spline. Nada hay más cómodo que trazar con lápiz y papel una serie de puntos y luego aplicar una función y obtener una curva que pase por esos puntos. La primitiva spline de Povray no puede usarse como función sino que genera una línea como base para construir formas, pero sería mucho mejor poder aplicar esa función a las variables que nosotros quisiéramos. De esta forma podríamos mover por ejemplo tanto objetos como la cámara siguiendo una trayectoria cualquiera. También sería bueno poder implementar funciones externamente.

Bueno de alguna manera todo eso se puede compensar usando includes y programación externa. Tenemos en Linux una utilidad llamada 'spline'. Se utiliza como un comando y genera la salida correspondiente a la curva deseada, a partir de una entrada que pueden ser parejas de puntos. Puede ser muy útil combinar este comando con el uso de la programación externa. Por ejemplo para obtener animaciones, o movimientos de cámara.

Ejercicios

  • Si alguien tiene curiosidad puede intentar como ejercicio la optimización de este último fotograma siguiendo las recomendaciones que acabamos de hacer.
  • Pasar el programa burbujas.c a lenguaje Povray.
  • Pasar el programa de erizos.c a un lenguaje de propósito general.

Aportación de los lectores.

Si algún lector implementa algún otro algoritmo para construcción de formas recursivas o iterativas. Puede enviarme sus experimentos, en forma de fuentes comprimidas con gzip. Las imágenes siempre comprimidas de alguna forma. Si hay material suficiente para ello se podría incluir en algún artículo. dedicado a Povray con una selección de las imágenes más curiosas, acompañadas de sus fuentes. Abstenerse de mandar nada que implique licencia de uso mediante pago en cualquiera de sus modalidades. Interesa sobre todo las fuentes.

pov3.tar.gz, Fuentes de los programas de este artículo (42652 bytes)


Texto original en Castellano


Páginas web mantenidas por Miguel Ángel Sepúlveda
© Antonio Castro 1998
LinuxFocus 1998