Blender: Perlin Noise

Para trabajar con Perlin Noise existen diferentes programas y lenguajes y uno muy potente es Blender con Python. Tras haber trabajado y aprendido a manejar el Perlin Noise con Processing el paso a escribir en Python las funciones con las que trabajo fue complejo en el sentido de que existe poca información de cómo usar Perlin Noise en Blender. Intentaré exponer estas complejidades a continuación.

Blender utiliza Python para trabajar con sus scripts y también para hacer Addons personalizados. Python no es un lenguaje complicado y su sintaxis ya sabiendo programación se aprende en pocos minutos, la complejidad radica en la cantidad de librerías existentes y en la que concretamos aquí: bpy. Esta librería es la que une a Python con Blender y está diseñada para trabajar con todo lo que necesite el programa. Lo que hace esta librería es, a grandes rasgos, permitir escribir en Python los clicks que se hacen en el programa Blender.

Otra libreria que se une y se utiliza en Blender es mathutils. Es utilizada para cálculo con radianes, vectores, matrices… y noise, que es lo que nos interesa. Mahtutils contiene varias funciones de noise y permite diferentes variantes de este a gusto de todos.

Bien, los enlaces que hemos visto hasta ahora son la mayor ayuda que podemos encontrar (sin preguntar) por Internet, no solo porque son las APIs sino porque no existe mucha más información, excepto la que ha sido para mí la Piedra Rosetta: una pequeña duda en el Stack Exchange de Blender que tenía que ver con Perlin Noise / Simplex Noise y publicaba una respuesta con un código en Python de cómo usar las funciones de mathutils en Blender. La respuesta fue escrita por gandalf3 y publica un código repleto del Python de Blender por decirlo de alguna manera. Lo que quiero decir es que sino te sientes cómodo utilizando las funciones de bpy y solo has usado la superficie de Blender es difícil encontrar un sentido al principio de qué es lo que está pasando en este código. El código ya hace dos años que se publicó y el propio usuario ofrece una descarga de un archivo Blender donde se utiliza y se puede ver en directo. Esta es una imagen de ejemplo de lo que se puede encontrar:

 

TerrainWithVertexEste código itera a través de las diferentes funciones de noise de mathutils reflejando el resultado en un malla de vértices que se actualiza conforme avanzan los bucles for. Pero si lo que te interesa es coger una función de noise para reflejar sus resultados este código tiene muchas partes sobrantes (me parece un buen código para mostrar las funciones, dicho sea).

Para lo que nos acontece (conseguir valores de noise que dibujen un terreno) podemos quitar las iteraciones entre diferentes funciones de noise y usar cubos en lugar de vértices para que la simulación sea más parecida a otros ejemplos que podemos encontrar en este mismo blog.

En pro de hacer el código final lo más simple posible vamos a crear una matriz bidimensional (sí, en Python) y ahí vamos a guardar los valores de noise que vamos a generar. Después vamos a mostrarlos simplemente dibujando cubos en las posiciones que se pueden leer posteriormente de esta matriz bidimensional. Entonces, ¿cómo se crean cubos, se redimensionan y se cambian de sitio en Blender? Entra en juego bpy que permite crear y modificar no solo cubos, sino esferas, cilindros, conos… hasta un mono (un clásico de Blender). Bpy permite hacer mucho, mucho más, pero si nos ceñimos a solo esto por ahora las cosas van a ser mucho más sencillas. El siguiente código crea un cubo, lo escala y le asigna una localización.


bpy.ops.mesh.primitive_cube_add()
bpy.context.scene.objects.active.scale = (0.1, 0.1, 0.1)
bpy.context.scene.objects.active.location = (x, y, z)

cubeblender

Una vez que controlamos la parte de los cubos podemos pasar a ver cómo funciona la función más simple de noise: mathutils.noise.noise(). A esta función se le pasa tan solo un parámetro (de hecho pueden ser dos, pero podemos usar solo uno para simplificar las cosas), un vector. Con este vector la función devuelve un resultado de noise en esa posición (en la posición del vector). Un vector se crea de manera sencilla: tiene las tres componentes x, y, z del espacio, las cuales se asignan y ya está. Como queremos generar un terreno podemos usar la función de noise con la x y la y para cada posición y dejar la z para tener una seed. Una seed o semilla es un número que genera una serie de números de noise concretos, de esta manera una misma seed siempre generará el mismo terreno.


vec = mathutils.Vector((x,y,z))
noiseValue = noise.noise(vec)

Teniendo todos los ingredientes sobre la mesa solo falta mezclarlos. Con un par de bucles for podemos cargar las posiciones que nos dice la función de noise y podríamos poner los cubos al mismo tiempo que averiguamos los valores, o podríamos crear otro par de bucles for para poner los cubos en las posiciones de la matriz bidimensional anteriormente creada. De la segunda forma podemos crear una función para averiguar los valores y así poder utilizar ese mismo código más tarde sin alterar nada. Montándolo todo el resultado queda de la siguiente manera.


import bpy
import mathutils
from mathutils import noise

w, h = 30, 30;
terrain = [[0 for x in range(w)] for y in range(h)]

iOff = 0.0
for i in range(0,w):
      jOff = 0.0
      for j in range(0,h):
            vec = mathutils.Vector((iOff, jOff, 40))
            terrain[i][j] = noise.noise(vec) * 10
            print("Terrain [" + str(i) + "][" + str(j) + "] = " + str(terrain[i][j]))
            jOff = jOff + 0.05
      iOff = iOff + 0.05

for i in range(0,w):
      for j in range(0,h):
            print("Adding Cube in [" + str(i) + "][" + str(j) + "]")
            bpy.ops.mesh.primitive_cube_add()
            bpy.context.scene.objects.active.scale = (0.1, 0.1, 0.1)
            bpy.context.scene.objects.active.location = (i * 0.2, j * 0.2, terrain[i][j] * 0.2)

La generación de los números de noise es rápida, pero hay que tener en cuenta que cuantos más cubos pongamos más va a tardar en terminar. Es recomendable seguir la salida del programa desde un terminal para saber cuando ha acabado porque sino parecerá que el programa se ha colgado a pesar de que está añadiendo cada cubo poco a poco.

Tip

Para borrar todos los cubos de la pantalla y poder correr el script de nuevo utilizo las dos siguientes líneas en el interprete de Python que contiene el propio programa Blender.


for item in bpy.data.meshes:
      bpy.data.meshes.remove(item)

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s