3.6.3  El Objeto Isosuperficie

Sabes que has estado trazando demasiado tiempo cuando ...
... Te descubres a ti mismo deseando haber prestado más atención en las clases de matemáticas a todas esas fórmulas que pensaste que no tendrían ninguna utilidad para ti en la vida real.
-- Jeff Lee

Las Isosuperficies son formas descritas por funciones matemáticas.

En contraste con otras formas con base matemática en POV-Ray, las isosuperficies son aproximadas durante el dibujado y por ello a veces son más difíciles de manejar. De todas formas, ofrecen interesantes posibilidades.

Tener algún conocimiento sobre funciones matemáticas y geometría es útil, pero no es requisito indispensable para trabajar con isosuperficies.

3.6.3.1  Funciones simples

Para empezar, elegiremos la función más simple: x. El valor de esta función es exactamente la coordenada x actual.

El objeto isosuperficie toma esa función como una función definida por el usuario:

  isosurface {
    function { x }
    contained_by { box { -2, 2 } }
  }
Ejemplo de Isosuperficie (function { x })
Ejemplo de Isosuperficie (función { x })

La forma resultante es realmente simple: un cubo.

El hecho de que sea un cubo está causado por el objeto contenedor, el cual es necesario para una isosuperficie. Podéis utilizar un cubo o una esfera para este propósito.

Por ello, de hecho sólo se construye una cara del cubo por la función. Esta superficie nacerá donde la coordenada x es 0, ya que 0 es el umbral (threshold) por defecto. Normalmente no hay razón para cambiarlo, ya que es el valor más común y sugestivo, pero puedes especificar algo diferente añadiendo

threshold 1

a la definición de isosuperficie.

Ejemplo de isosuperficie (function { x }, threshold 1)
Ejemplo de isosuperficie (función { x }, threshold 1)

Como podéis ver, la superficie está ahora en la coordenada x 1.

También podemos eliminar las superficies visibles del objeto contenedor añadiendo la palabra 'open' a la definición de la isosuperficie.

Ejemplo de isosuperficie (function { x }, open)
Ejemplo de isosuperficie (función { x }, open)

Para aclarar qué superficies son la isosuperficie real y cuáles son causadas por el objeto contenedor, el color será diferente en las imágenes siguientes.

Ahora reemplazamos la función utilizada con algo diferente:

function { x+y }

Ejemplo de isosuperficie (plane function)
Ejemplo de isosuperficie (función plana)

function { x+y+z }

Ejemplo de isosuperficie (plane function)
Ejemplo de isosuperficie (función plana)

Nota: aquí se añade 'max_gradient 4' a la definición de isosuperficie. Esto se explicará más adelante.

Todas estas funciones describen planos que pasan a través del origen. La función simplemente describe el vector de la normal de este plano.

3.6.3.2  Algunas superficies

Las dos funciones siguientes dan los mismos resultados:

function { abs(x)-1 }

function { sqrt(x*x)-1 }

Ejemplo de isosuperficie (function { abs(x)-1 }, open)
Ejemplo de isosuperficie (función { abs(x)-1 }, open)

Podéis ver que ahora hay dos planos. La razón es que ambas fórmulas tienen las mismas dos soluciones (cuando el valor de la función es 0), a saber x=-1 y x=1.

Por mucho que mezclemos estos elementos en combinaciones diferentes, los resultados siempre consistirán en superficies planas:

function { abs(x)-1+y }

Ejemplo de isosuperficie (funciones lineales combinadas)
Ejemplo de isosuperficie (funciones lineales combinadas)

function { abs(x)+abs(y)+abs(z)-2 }

Ejemplo de isosuperficie (funciones lineales combinadas)
Ejemplo de isosuperficie (funciones lineales combinadas)

3.6.3.3  Funciones no lineales

Se pueden conseguir muchos tipos diferentes de superficies curvadas con funciones no lineales.

function { pow(x,2) + y }

Ejemplo de isosuperficie (funciones no lineales)
Ejemplo de isosuperficie (funciones no lineales)

Podéis ver la forma parabólica causada por la función "elevado al cuadrado".

Para obtener una superficie cilíndrica podemos utilizar la siguiente función.

function { sqrt(pow(x,2) + pow(z,2)) - 1 }

Esto describe un círculo en 2 dimensiones, como la 3ª dimensión es constante, obtenemos un cilindro:

Ejemplo de Isosuperficie (cylinder function)
Ejemplo de Isosuperficie (función cilindro)

No es difícil transformarlo en un cono, simplemente necesitamos añadir un componente lineal en el la dirección y:

function { sqrt(pow(x,2) + pow(z,2)) + y }

Ejemplo de isosuperficie (cone function)
Ejemplo de isosuperficie (función cono)

Y por supuesto, también podemos hacer una esfera:

function { sqrt(pow(x,2) + pow(y,2) + pow(z,2)) - 2 }

Ejemplo de isosuperficie (sphere function)
Ejemplo de isosuperficie (función esfera)

Aquí el 2 especifica el radio.

3.6.3.4  Funciones internas

Hay un montón de funciones internas disponibles en POV-Ray. Por ejemplo, una esfera puede ser generada también con function { f_sphere(x, y, z, 2) } Estas funciones están declaradas en el archivo de inclusión functions.inc. La mayoría de ellas son más complicadas y normalmente es más rápido utilizarlas en lugar de su equivalente de código a mano. Para ver más detalles, mira la lista completa.

Lo siguiente hace un torus igual al objeto torus de POV-Ray:

  #include "functions.inc"

  isosurface {
    function { f_torus(x, y, z, 1.6, 0.4) }
    contained_by { box { -2, 2 } }
  }
Ejemplo de isosuperficie (torus function)
Ejemplo de isosuperficie (función toro)

Los parámetros 4º y 5º son los radios mayor y menor, igual que los valores correspondientes en el objeto torus{}.

Los parámetros X, Y y Z son necesarios, porque es una función declarada. También podéis declarar funciones vosotros mismos como se explica en la sección de referencia.

3.6.3.5  Combinando funciones de isosuperficie

También podemos simular en parte la CSG (Geometría Constructiva de Sólidos) con funciones de isosuperficies. Si no sabes nada de CSG te sugerimos que le eches un vistazo primero a "¿Qué es CSG?" o al apartado correspondiente de la sección de referencia.

Vamos a utilizar dos funciones: un cilindro y un cubo rotado:

  #declare fn_A = function { sqrt(pow(y,2) + pow(z,2)) - 0.8 }
  #declare fn_B = function { abs(x)+abs(y)-1 }

Si los combinamos de esta manera, obtenemos una "fusión":

function { min(fn_A(x, y, z), fn_B(x, y, z)) }

Ejemplo de isosuperficie (merge)
Ejemplo de isosuperficie (merge)

Se puede obtener una "intersección" utilizando max() en lugar de min():

function { max(fn_A(x, y, z), fn_B(x, y, z)) }

Ejemplo de isosuperficie (intersection)
Ejemplo de isosuperficie (intersection)

Por supuesto, también es posible una "diferencia", sólo tenemos que añadir un signo de menos (-) antes de la segunda función:

function { max(fn_A(x, y, z), -fn_B(x, y, z)) }

Ejemplo de isosuperficie (difference)
Ejemplo de isosuperficie (difference)

Aparte del CSG básico, podéis obtener transiciones suaves entre las diferentes superficies (como con el objeto blob)

  #declare Blob_threshold=0.01;

  isosurface {
    function {
      (1+Blob_threshold)
      -pow(Blob_threshold, fn_A(x,y,z))
      -pow(Blob_threshold, fn_B(x,y,z))
    }
    max_gradient 4
    contained_by { box { -2, 2 } }
  }
Ejemplo de isosuperficie (blob)
Ejemplo de isosuperficie (blob)

El valor Blob_threshold influye en la suavidad de las transición entre formas. Un valor bajo conlleva bordes más afilados.

3.6.3.6  Funciones de ruido y pigmento

Algunas de las funciones internas tienen una estructura aleatoria o parecida al ruido

Junto con las funciones de pigmento, son una de las herramientas más poderosas para el diseño de isosuperficies. Podemos añadir desplazamiento real de la superficie a los objetos en lugar de la usual perturbación ya conocida de la sentencia normal{}.

Las funciones internas relevantes son:

Utilizar ruido3d puro como una función da como resultado la siguiente imagen:

function { f_noise3d(x, y, z)-0.5 }

Ejemplo de isosuperficie (noise3d)
Ejemplo de isosuperficie (noise3d)

Nota: -0.5 está apuntado aquí sólo para hacerlo casar con el valor de umbral 0, la función f_noise3d retorna valores entre 0 y 1.

Con ésta y las otras funciones puedes generar objetos similares a campos de elevación (height_fields), teniendo la ventaja de que se puede conseguir una alta resolución sin grandes requisitos de memoria.

function { x+f_noise3d(0, y, z) }

Ejemplo de isosuperficie (noise3d 'heightfield')
Ejemplo de isosuperficie (noise3d 'height_field')

Por supuesto, La función de ruido se puede restar, lo que da como resultado una versión 'invertida':

function { x-f_noise3d(0, y, z) }

Ejemplo de isosuperficie (noise3d 'heightfield' invertida)
Ejemplo de isosuperficie (noise3d 'height_field' invertida)

En las últimas dos imágenes, añadimos la función de ruido a una función de plano. El parámetro x estaba fijado a 0 por lo que la función de ruido es constante en la dirección x. De esta forma conseguimos la estructura típica de un campo de elevación.

Por supuesto, podemos añadir ruido a cualquier otra función. Si la función de ruido es muy fuerte, ello puede dar como resultado varias superficies separadas.

function { f_sphere(x, y, z, 1.2)-f_noise3d(x, y, z) }

Ejemplo de isosuperficie (noise3d on sphere)
Ejemplo de isosuperficie (noise3d sobre una esfera)

Ésta es una función de ruido aplicada a la superficie de una esfera, podemos influir en la intensidad del ruido multiplicándolo por un factor y cambiar el escalado multiplicando los parámetros de las coordenadas:

function { f_sphere(x, y, z, 1.6)-f_noise3d(x*5, y*5, z*5)*0.5 }

Ejemplo de isosuperficie (noise3d on sphere scaled)
Ejemplo de isosuperficie (noise3d sobre una esfera escalada)

Como alternativa a las funciones de ruido, también podemos utilizar cualquier pigmento en una función:

  #declare fn_Pigm=function {
    pigment {
      agate
      color_map {
        [0 color rgb 0]
        [1 color rgb 1]
      }
    }
  }

Esta función es una función de vector que retorna un vector (color). Para utilizarlo en una isosuperficie, tenemos que especificar el componente a utilizar (para tener más detalles ver la sección de referencia).

function { f_sphere(x, y, z, 1.6)-fn_Pigm(x/2, y/2, z/2).gray*0.5 }

Ejemplo de isosuperficie (pigment function)
Ejemplo de isosuperficie (función pigmento)

Hay un montón de posibilidades con las funciones de pigmento, pero probablemente os habréis dado cuenta de que esto se dibuja realmente despacio.

3.6.3.7  Precisión (accuracy), max_gradient, etc.

Para optimizar la aproximación de la isosuperficie y conseguir la máxima velocidad de dibujado, es importante adaptar ciertos valores (ver también "Mejorando la velocidad de las isosuperficies" en la sección de referencia).

precisión (accuracy)

El valor de precisión influye en cuan precisa es calculada la geometría de la superficie. Valores bajos dan resultados más precisos, pero también más lentos. El valor por defecto de 0.001 es realmente bajo. Hemos utilizado este valor en todos los ejemplos anteriores, pero normalmente podéis elevarlo bastante más y por tanto obtener mayor velocidad.

max_gradient

Para que POV-Ray pueda encontrar la superficie real es importante que conozca el gradiente máximo de la función, que significa cómo de rápido cambia el valor de la función. Podemos especificar un valor con el identificador max_gradient. Valores bajos de max_gradient producen un dibujado más rápido, pero si el valor especificado está por debajo del gradiente máximo real de la función, pueden aparecer agujeros u otros defectos en la superficie.

Por la misma razón, no deben usarse funciones con un gradiente infinito. Esto es de aplicación a las funciones de pigmento con un patrón de ladrillo (brick) o ajedrezado (checker) por ejemplo. Por ello también debéis tener cuidado cuando utilicéis select() en la funciones de isosuperficie.

Si el gradiente máximo real difiere mucho del valor especificado, POV-Ray emite una advertencia junto con el valor hallado para el gradiente máximo. Habitualmente es suficiente con usar este número para el parámetro max_gradient para obtener resultados rápidos y correctos.

POV-Ray también puede cambiar dinámicamente el max_gradient cuando especifiquéis evaluate con 3 parámetros en la definición de la isosuperficie. Respecto a los detalles sobre esto y otras cosas, ver la sección de referencia.