Primeira vez aqui? Seja bem vindo e cheque o FAQ!
x

Como usar trigonometria e ruídos de Perlin para criar padrões de ondulação suaves?

+4 votos
28 visitas
perguntada Set 2 em Design por Renata Oliveira (46 pontos)  

Como reproduzir, usando python, os padrões da figura 4.15 do livro “Generative Art: A Practical Guide Using Processing” por Matt Pearson?
A imagem será apresentada aqui.

As figuras foram geradas com o código abaixo:
A imagem será apresentada aqui.

Compartilhe

1 Resposta

+1 voto
respondida Set 2 por Renata Oliveira (46 pontos)  
editado Set 3 por Renata Oliveira

Para “traduzir” o código da pergunta para python, os passos apresentados no livro foram seguidos. Antes de qualquer coisa, os módulos a serem utilizados são importados:

import numpy as np
from turtle import *
import noise as noise

Primeiramente, uma circunferência simples é desenhada usando trigonometria: os pontos (x,y) que formam uma circunferência de raio r centrada no ponto (centerX, centerY) são obtidos ao incrementar o ângulo \(\theta\) e calcular:

\[x=centerX + r*cos(\theta)\\ y=centerY + r*sen(\theta)\]

Usando o módulo turtle do python é possível desenhar a circunferência com um loop que, para cada ângulo, calcula os valores de x e y dado o raio e as coordenadas do centro da circunferência:

def trig_circle(radius, centerX=0, centerY=0):
    for theta in range(360):
        rad=np.radians(theta) #converts degrees to radians
        x=centerX+radius*np.cos(rad)
        y=centerY+radius*np.sin(rad)
        goto(x,y)
        pendown()

Para desenhar uma circunferência de raio 100:

if __name__=='__main__':
    begin_fill()
    penup()
    trig_circle(100)
    done()

O resultado é
A imagem será apresentada aqui.

Para criar uma espiral, o raio da circunferência é incrementado por uma constante a cada novo ponto (x,y). Na função abaixo, que difere da primeira apenas pela última linha que amplia o raio, a variável “size” indica quantas voltas terá a espiral:

def trig_spiral(size, radius, centerX=0, centerY=0, r_increment=0.1):
    for theta in range(size*360):
        rad=np.radians(theta)
        x=centerX+radius*np.cos(rad)
        y=centerY+radius*np.sin(rad)
        goto(x,y)
        pendown()
        radius=radius+r_increment

Para desenhar a espiral:

if __name__=='__main__':
    begin_fill()
    penup()
    trig_spiral(4, 10)
    done()

A imagem será apresentada aqui.

Adiciona-se então um ruído à espiral usando o módulo “noise”, que permite criar ruídos de Perlin. Este tipo de ruído é utilizado em computação gráfica para criar texturas e padrões de ondulação mais “naturais” ou “suaves”, uma vez que a sequência de números é pseudoaleatória. Uma função que gera um ruído contínuo inicialmente gera pontos aleatórios e depois suavemente interpola estes pontos. O ruído de Perlin resulta da soma de funções de ruídos como estas com diferentes frequências e amplitudes.

Uma função foi definida para criar uma lista de ruídos de Perlin unidimensionais através da função “pnoise1”. Os argumentos desta função determinam a suavidade do ruído. O argumento “octaves” indica quantos ruídos serão sucessivamente adicionados para criar o ruído de Perlin (o padrão para um ruído simples é octaves=1). O argumento “persistence” especifica a amplitude de cada ruído sucessivo em relação ao anterior (default: persistence=0.5, de modo que a amplitude do ruído adicional é a metade da amplitude do ruído anterior). Por fim, “lacunarity” indica a frequência de cada ruído sucessivo relativamente ao anterior, sendo 2 o valor padrão. Na função abaixo, a lista de ruídos admite uma escala que em geral será igual a 100, de modo que os valores da lista variam entre -1 e 1.

def noise_generator(shape, scale=100, octaves=6, persistence=0.5, lacunarity=2):
    noise_list=np.zeros(shape)
    for i in range(shape):
        noise_list[i] = noise.pnoise1(i/scale, octaves=octaves)
    return noise_list

def noisy_spiral(size, radius, centerX=0, centerY=0, scale=100, r_increment=0.1):
    noise_list=noise_generator(shape=size*360, scale=scale)
    for theta in range(size*360):
        rad=np.radians(theta)
        radius_noise=noise_list[theta]*100
        thisradius=radius+radius_noise
        x=centerX+thisradius*np.cos(rad)
        y=centerY+thisradius*np.sin(rad)
        goto(x,y)
        pendown()
        radius=radius+r_increment*2

Desenhando a espiral com ruído:

if __name__=='__main__':
    begin_fill()
    penup()
    noisy_spiral(4,10)
    done()

A imagem será apresentada aqui.

A partir destas formas iniciais é possível desenhar padrões ondulados. Começando pelo desenho de um círculo formado por linhas que conectam pontos opostos de uma circunferência. A função abaixo segue a mesma lógica da primeira, porém desta vez são calculados os pontos (x,y) da circunferência e os seus pontos opostos. Uma linha conecta os dois pontos.

def circle_lines(radius, size, centerX=0, centerY=0):
    for theta in range(size): 
        rad=np.radians(theta)
        opp_rad=rad+np.pi
        x=centerX+radius*np.cos(rad)
        y=centerY+radius*np.sin(rad)
        opp_x=centerX+radius*np.cos(opp_rad)
        opp_y=centerY+radius*np.sin(opp_rad)
        goto(x,y)
        goto(opp_x, opp_y)

Para desenhar este tipo de círculo:

if __name__=='__main__':
    begin_fill()
    circle_lines(100, 130)
    goto(0,0)
    clear()
    circle_lines(100, 180)
    done()

A imagem será apresentada aqui.
A imagem será apresentada aqui.

Adicionando ruído ao raio do círculo anterior, assim como foi feito na primeira parte ao desenhar apenas a circunferência:

def noisy_circle_lines(radius, size, centerX=0, centerY=0, scale=100):
noise_list=noise_generator(shape=size, scale=scale)
for theta in range(size): 
    rad=np.radians(theta)
    opp_rad=rad+np.pi
    radius_noise=noise_list[theta]*100
    thisradius=radius+radius_noise
    x=centerX+thisradius*np.cos(rad)
    y=centerY+thisradius*np.sin(rad)
    opp_x=centerX+thisradius*np.cos(opp_rad)
    opp_y=centerY+thisradius*np.sin(opp_rad)
    goto(x,y)
    goto(opp_x, opp_y)

O desenho é feito a partir do código:

if __name__=='__main__':
    begin_fill()
    noisy_circle_lines(100, 180)
    done()

A imagem será apresentada aqui.

Finalmente, é possível adicionar ruído também ao ponto que define o centro da forma circular de modo que a cada passo do loop o centro da figura é alterado. Esta última alteração permite a criação de padrões similares aos da figura 4.15. Para gerar estes padrões foi definida uma classe:

class Wave_clock:
def __init__ (self,
                size,              #number of "laps" for a spiral
                radius,            #radius
                detail=2,          #increase this parameter for more detail
                centerX=0,         #center of the circle (x coordinate)
                centerY=0,         #center of the circle (y coordinate)
                r_increment=2,     #increment to the radius
                scale=100,         #scale of the noise list (100 for a list between -1 and 1)
                octaves=6,         #number of noise functions added
                persistence=0.5,   #amplitude of each successive octave added relative to the one below it
                lacunarity=2,      #frequency of each successive octave added relative to the one below it
                xnoise=12,         #noise to x coordinate for the center of the spiral
                ynoise=20          #noise to y coordinate for the center of the spiral
                ):
    self.size=size
    self.radius=radius
    self.detail=detail
    self.centerX=centerX
    self.centerY=centerY
    self.r_increment=r_increment
    self.scale=scale
    self.octaves=octaves
    self.persistence=persistence
    self.lacunarity=lacunarity
    self.xnoise=xnoise
    self.ynoise=ynoise

def noise_generator(self): #creates a list of noises between 0 and 1
    noise_list=np.zeros(self.size*360)
    for i in range(self.size*360):
        noise_list[i] = noise.pnoise1(i/self.scale, octaves=self.octaves, persistence=self.persistence, lacunarity=self.lacunarity)
    return noise_list

def wave_clock(self): #adds noise to the center of the circumference
    noise_list=self.noise_generator()
    radius=self.radius
    centerX=self.centerX
    centerY=self.centerY
    for i in range(self.size*360):
        x_noise=noise_list[i]*self.xnoise
        y_noise=noise_list[i]*self.ynoise
        radius_noise=noise_list[i]*10
        angle=i*(1/self.detail)
        thisradius=radius*5+radius_noise
        rad=np.radians(angle)
        opp_rad=rad + np.pi
        x=centerX+thisradius*np.cos(rad)
        y=centerY+thisradius*np.sin(rad)
        opp_x=centerX+thisradius*np.cos(opp_rad)
        opp_y=centerY+thisradius*np.sin(opp_rad)
        goto(x,y)
        penup()
        goto(opp_x, opp_y)
        pendown()
        radius=radius+self.r_increment
        centerX=centerX+x_noise*2
        centerY=centerY+y_noise*2   

Usando os parâmetros padrão da classe, obtém-se a imagem:

if __name__=='__main__':
    my_waveclock=Wave_clock(2,100)
    begin_fill()
    my_waveclock.wave_clock()
    done()

A imagem será apresentada aqui.

Alterando alguns parâmetros é possível criar diferentes padrões. Os parâmetros a seguir dão origem às imagens abaixo, respectivamente:

if __name__=='__main__':
    my_wave_clock1 = Wave_clock(1, 100, detail = 2)
    my_wave_clock2 = Wave_clock(1, 100, detail = 2, xnoise=-14, ynoise=-10)
    my_wave_clock3 = Wave_clock(1, 100, detail = 2, xnoise=-12, ynoise=12)
    my_wave_clock4 = Wave_clock(1, 100, detail = 2, xnoise=0, ynoise=10)
    my_wave_clock5 = Wave_clock(2, 100, detail = 4)
    my_wave_clock6 = Wave_clock(2, 100, detail = 8)

A imagem será apresentada aqui.
A imagem será apresentada aqui.
A imagem será apresentada aqui.
A imagem será apresentada aqui.
A imagem será apresentada aqui.
A imagem será apresentada aqui.

...