Книга: Написание скриптов для Blender 2.49

Собираем всё это вместе

Собираем всё это вместе

Взяв все эти идеи из предыдущих параграфов, мы можем приготовить следующую программу для нашего Pynode Raindrops (с опущенными операторами import):

class Raindrops(Node.Scripted):
   def __init__(self, sockets):
      sockets.input = [
         Node.Socket('Drops_per_second'  ,
                     val = 5.0, min = 0.01, max = 100.0),
         Node.Socket('a',val=5.0,min=0.01,max=100.0),
         Node.Socket('c',val=0.04,min=0.001,max=10.0),
         Node.Socket('speed',val=1.0,min=0.001, max=10.0),
         Node.Socket('freq',val=25.0,min=0.1, max=100.0),
         Node.Socket('dampf',val=1.0,min=0.01, max=100.0),
         Node.Socket('Coords', val = 3*[1.0])]
      sockets.output = [
              Node.Socket('Height', val = 1.0),
              Node.Socket('Normal', val = 3 *[0.0])]
      self.drops_per_second = None
      self.ndrops = None

Код инициализации определяет множество входных сокетов помимо координатного. Drops_per_second (капель в секунду) должен быть самочитаемым. a и c - общая высота и ширина пульсаций, двигающихся наружу из точки удара. speed и freq определяют, как быстро наши пульсации двигаются и насколько близко волны друг к другу. То, как быстро высота волн уменьшается во время пути наружу, определяет dampf.

Мы также определяем два выходных сокета: Height будет содержать рассчитанную высоту и Normal будет содержать соответствующую нормаль в этой же точке. Normal - это то, что Вы должны обычно использовать для получения поверхностного эффекта распространения, но рассчитанная высота может быть полезной, например, чтобы смягчить величину отражательной способности поверхности.

Инициализация заканчивается с определением некоторых переменных экземпляра, которые будут использованы, чтобы определить, нужно ли нам вычислять позицию падения капли заново, как мы увидим в определении функции __call__().

Определение функции __call__() начинается с инициализации множества локальных переменных. Одно примечательное место - то, где мы установили произвольное семя, используемое функциями модуля Noise (выделено в следующем коде). Таким образом, мы убеждаемся, что всякий раз, когда мы пересчитываем точки удара, мы получаем повторяемые результаты, что если мы установили бы количество капель в секунду сначала на десять, а позже на двадцать, и, затем ввернулись к десяти, сгенерированный узор будет тем же. Если Вы хотели бы изменить это, Вы могли бы добавить дополнительный входной сокет, который нужно использовать как вход для функции setRandomSeed():

   def __call__(self):
      twopi = 2*pi
      col = [0,0,0,1]
      nor = [0,0,1]
      tex_coord = self.input.Coords
      x = tex_coord[0]
      y = tex_coord[1]
      a = self.input.a
      c = self.input.c
      Noise.setRandomSeed(42)
      scn               = Scene.GetCurrent()
      context           = scn.getRenderingContext()
      current_frame     = context.currentFrame()
      start_frame       = context.startFrame()
      end_frame         = context.endFrame()
      frames_per_second = context.fps
      time = current_frame/float(frames_per_second)

Следующим шагом нужно определить, должны ли мы вычислять позиции точек удара капель заново. Это необходимо, только если величина входного сокета  drops_per_second была изменена пользователем (Вы могли бы соединить этот вход с некоторым другим нодом, который будет изменять эту величину на каждом пикселе, но это плохая идея), или когда стартовый или конечный кадр анимации изменились, как эти влияния количества капель мы должны вычислять. Этот тест выполняется на выделенной строке следующего кода сравнением вновь полученных величин с сохранёнными в переменных экземпляра:

      drops_per_second = self.input.Drops_per_second
      # вычисление числа капель для генерации
      # в период анимации
      ndrops = 1 + int(drops_per_second *
               (float(end_frame) – start_frame+ 1)/
               frames_per_second )
      if self.drops_per_second != drops_per_second
      or self.ndrops != ndrops:
         self.drop = [ (Noise.random(), Noise.random(),
            Noise.random() + 0.5) for i in range(ndrops)]
         self.drops_per_second = drops_per_second
         self.ndrops = ndrops

Если мы должны вычислить позиции капель заново, мы назначаем список кортежей в переменную экземпляра self.drop, каждый из которых состоит из координат x и y позиции капли и случайного размера капли, от которой будет зависеть высота волн.

Строк оставшейся части полностью выполняются всякий раз при вызове __call__(), но выделенная строка показывает значимую оптимизацию. Поскольку капли, которые еще не упали в текущем кадре, не привносят изменений высоты, мы исключаем их из вычисления:

      speed=self.input.speed
      freq=self.input.freq
      dampf=self.input.dampf
      height = 0.0
      height_dx = 0.0
      height_dy = 0.0
      nabla = 0.01
      for i in range(1+int(drops_per_second*time)):
         dropx,dropy,dropsize = self.drop[i]
         position_of_maximum=speed*time-
            i/float(drops_per_second)
         damping = 1.0/(1.0+dampf*position_of_maximum)
         distance = sqrt((x-dropx)**2+(y-dropy)**2)
         height += damping*a*dropsize*
            exp(-(distance-position_of_maximum)**2/c)*
            cos(freq*(distance-position_of_maximum))
         distance_dx = sqrt((x+nabla-dropx)**2+
                            (y-dropy)**2)
         height_dx += damping*a*dropsize*
            exp(-(distance_dx-position_of_maximum)**2/c)
            * cos(freq*(distance_dx-position_of_maximum))
         distance_dy = sqrt((x-dropx)**2+
                            (y+nabla-dropy)**2)
         height_dy += damping*a*dropsize*
            exp(-(distance_dy-position_of_maximum)**2/c)
            *cos(freq*(distance_dy-position_of_maximum))

В предыдущем коде мы действительно вычисляем высоту в трех различных позициях, чтобы получить возможность аппроксимировать нормаль (как объяснено раньше). Эти величины используются в следующих строках, чтобы определить x и y компоненты нормали (z компонента установлена в единицу). Сама рассчитанная высота делится на количество капель (таким образом, средняя высота не изменится при изменении количества капель) и на общий коэффициент масштабирования a, который может быть задан пользователем прежде, чем будет подсоединён выходной сокет (выделено):

      nor[0]=height-height_dx
      nor[1]=height-height_dy
      height /= ndrops * a
      self.output.Height = height
      N = (vec(self.shi.surfaceNormal)+0.2 *
           vec(nor)).normalize()
      self.output.Normal= N
__node__ = Raindrops

Рассчитанная нормаль затем добавляется к поверхностной нормали того пикселя, который мы вычисляем, таким образом, волны будут все еще хорошо выглядеть на искривленной поверхности, и нормируется перед назначением её в выходной сокет. Последняя строка как обычно определяет значимое имя для этого Pynode. Полный код и пример настройки нодов доступны как raindrops.py в файле raindrops.blend. Пример кадра из анимации показан на следующем скриншоте:


Пример нодовой сети показан на следующем скриншоте:


Оглавление книги


Генерация: 1.197. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз