Выбрать главу

Эффекты от многих брошенных капель в разное время и в разных позициях можно просто вычислить суммированием результирующих высот.

Хранение   дорогостоящих   результатов   для многократного использования

Единственная капля - это, конечно, не дождь, так что мы хотели бы видеть сложенные эффекты от множества случайных капель. Следовательно, мы должны выбирать произвольные позиции и время ударов для стольких капелек, сколько мы хотели бы сымитировать.

Мы должны были бы делать это каждый раз при вызове метода __call__() (то есть, для каждого видимого пикселя в нашей текстуре). Тем не менее, это было бы огромными тратами процессорных сил, поскольку вычисление множества случайных чисел и получение и возврат памяти для, возможно, многих капель дорого.

К счастью, мы можем сохранить эти результаты в качестве экземпляров переменных нашего Pynode. Конечно, мы должны быть достаточно осторожными, чтобы проверять, что никакие входные параметры не были изменены между вызовами __call__ () и предпринять соответствующие меры, если они изменились. Общая картина будет выглядеть следующим образом:

class MyNode(Node.Scripted):

   def __init__(self, sockets):

      sockets.input   = [Node.Socket('InputParam',

                         val = 1.0)]

      sockets.output  = [Node.Socket('OutputVal' ,

                         val = 1.0)]

      self.InputParam = None

      self.Result     = None

   def __call__(self):

      if self.InputParam == None or \

         self.InputParam != self.input.InputParam :

         self.InputParam = self.input.InputParam

         self.Result     = интенсивные_вычисления ...

      self.output.OutputVal = другие_вычисления …

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

Вычисление нормалей

Нашей целью будет сгенерировать волновой узор, который можно использовать в качестве нормали. Так что нам нужен некий способ получить нормаль из рассчитанных высот. Блендер не предоставляет нам такого преобразующего нода для материалов, так что мы должны разработать схему самостоятельно.

В противовес нодам материалов, ноды текстур Блендера обеспечивают преобразующую функцию, называемую 'Value to Normal' (величина в нормаль), которая доступна в нодовом редакторе текстур из меню Add|Convertor|Value to Normal.

Теперь, как и в случае ряби, мы могли бы, в принципе, вычислить также точную нормаль для нашей капли дождя, но, вместо движения по математическому пути, мы снова применяем метод, используемый многими встроенными текстурами шума для вычисления нормалей, который работает независимо от основной функции.

Пока мы можем оценивать функцию в трех точках: f(x,y),f(x+nabla,y), и f(x,y+nabla), мы можем оценить направление нормали в x,y, изучая наклон нашей функции в направлениях x и y. Нормаль поверхности будет вектором,  перпендикулярным к плоскости, определенной этими двумя наклонами. Мы можем взять любую малую величину для nabla, чтобы попробовать с ней, и если это не будет выглядеть хорошо, мы можем её уменьшить.

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

Взяв все эти идеи из предыдущих параграфов, мы можем приготовить следующую программу для нашего 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.