KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программы » Michel Anders - Написание скриптов для Blender 2.49

Michel Anders - Написание скриптов для Blender 2.49

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн "Michel Anders - Написание скриптов для Blender 2.49". Жанр: Программы издательство неизвестно, год неизвестен.
Перейти на страницу:

scn               = Scene.GetCurrent()

context           = scn.getRenderingContext()

current_frame     = context.currentFrame() #Текущий кадр

start_frame       = context.startFrame()   #Начальный кадр

end_frame         = context.endFrame()     #Конечный кадр

frames_per_second = context.fps            #Частота

                                           #кадров, fps

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

absolute_time = current_frame/float(frames_per_second)

relative_time = (current_frame-start_frame)/

                 float(frames_per_second)

Заметьте преобразование во float (число с плавающей точкой) в знаменателе (выделено). Этим способом мы гарантируем, чтобы деление рассматривалось как операция с плавающей точкой. Не строго необходимо, поскольку fps возвращается с типом плавающей точки, но множество людей считают частоту кадров как некоторую целую величину, например, 25 или 30. Тем не менее, так бывает не всегда (например, кодировка NTSC использует дробную частоту кадров), так что мы лучше сделаем это явно. Также заметьте, что мы не можем покончить с этим делением, в противном случае, когда люди захотят изменить своё решение о выбранной частоте кадров, скорость анимации должна измениться.


Всё, что выглядит хорошо — это хорошо

Точно имитировать то, как выглядят пульсации, вызванные падением капелек, может показаться трудным, но это просто, хотя и немного запутано. Читатели, интересующиеся базовой математикой, могут   проверить   какие-нибудь   ссылки   (например, http://en.wikipedia.org/wiki/Wave). Нашей целью, тем не менее, не является моделирование реального мира с максимально возможной точностью, а обеспечение художника текстурой, которая выглядит хорошо и управляется так, чтобы текстуру можно было применить даже в нереалистичных ситуациях.

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



Для того, чтобы вычислить высоту в любой позиции x, y на нашей текстуре, вышеуказанное можно осуществить следующим образом:

position_of_maximum=speed*time

damping = 1.0/(1.0+dampf*position_of_maximum)

distance = sqrt((x-dropx)**2+(y-dropy)**2)

height = damping*a*exp(-(distance-

position_of_maximum)**2/c)*

         cos(freq*(distance-position_of_maximum))

Здесь, dropx и dropy - позиция ударившей капли, a - наш регулируемый параметр высоты.

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


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

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

Мы должны были бы делать это каждый раз при вызове метода __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.

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

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

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

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*