Чтение онлайн

ЖАНРЫ

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

Anders Michel

Шрифт:

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:

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:

Поделиться с друзьями: