贝塞尔曲线和粒子描边
本文最后更新于 60 天前。

基本概念

本段内容节选自 怎么理解贝塞尔曲线? – FrancisZhao的回答 – 知乎

贝塞尔曲线于 1962 年,由法国工程师皮埃尔·贝济埃(Pierre Bézier)所广泛发表,他运用贝塞尔曲线来为汽车的主体进行设计,贝塞尔曲线最初由保尔·德·卡斯特里奥于1959年运用德卡斯特里奥算法开发,以稳定数值的方法求出贝塞尔曲线.

贝塞尔曲线有着很多特殊的性质, 在图形设计和路径规划中应用都非常广泛, 我就是想在路径规划中贝塞尔曲线完全由其控制点决定其形状, n个控制点对应着n-1阶的贝塞尔曲线,并且可以通过递归的方式来绘制.

一阶曲线

一阶曲线是一条直线。
$ B_1(t)=P_0+(P_1-P_0)t $
$ B_1(t)=(1-t)P_0+tP_1,t ,t\in[0,1] $

一阶曲线就是很好理解, 就是根据$t$来的线性插值. $P_0$表示的是一个向量$[x,y]$, 其中$x$和$y$是分别按照这个公式来计算的.

image.png

二阶曲线

二阶曲线共三个控制点。

image.png

image.png

在平面内任选 3 个不共线的点,依次用线段连接。在第一条线段上任选一个点 D。计算该点到线段起点的距离 AD,与该线段总长 AB 的比例。
image.png

根据上一步得到的比例,从第二条线段上找出对应的点 E,使得 AD:AB = BE:BC。
image.png

image.png

这时候DE又是一条直线了, 就可以按照一阶的贝塞尔方程来进行线性插值了, t= AD:AE
这时候就可以推出公式了.
image.png

image.png

$P_0’=(1-t)P_0+tP_1$
对应着上图绿色线段的左端点
$P_1’=(1-t)P_1+tP_2$
对应着上图绿色线段的右端点
$B_2(t)=(1-t)P_0’+tP_1’$
$=(1-t)((1-t)P_0+tP_1)+t((1-t)P_1+tP_2)$
$=(1-t)^2P_0+2t(1-t)P_1+t^2P_2$
对应着绿色线段的一阶贝塞尔曲线(线性插值)
$B_2(t)=(1-t)^2P_0+2t(1-t)P_1+t^2P_2, t\in[0,1]$
整理一下公式, 得到二阶贝塞尔公式.

三次曲线

image.png

二阶的贝塞尔通过在控制点之间再采点的方式实现降阶, 每一次选点都是一次的降阶.
四个点对应是三次的贝塞尔曲线. 分别在 AB BC CD 之间采EFG点, EFG三个点对应着二阶贝塞尔, 在EF FG之间采集HI点来降阶为一阶贝塞尔曲线.
image.png

image.png

Aegisub中的贝塞尔

Aegisub中的贝塞尔曲线用四个控制点,生成一条三阶贝塞尔曲线。

image.png

用粒子生成贝塞尔曲线

可以用多个粒子来“合成”一条贝塞尔曲线。

获取一帧的时间

Comment: 0,0:00:00.00,0:00:00.00,Default,Get Frame Dur,0,0,0,code once,msa = _G.aegisub.ms_from_frame(1) msb = _G.aegisub.ms_from_frame(100) if msa == nil and msb == nil then frame_dur = 33 else frame_dur = (msb-msa)/100 end

Bezier_move函数

Comment: 0,0:00:00.00,0:00:00.00,Default,Beizer Curve Move,0,0,0,code once all,function bezier_move(x1,y1,x2,y2,x3,y3,x4,y4,t1,t2,accel,afterimage_blur,afterimage_dur,density) if density == 0 or density == nil then density = 1 end if afterimage_blur == 0 or afterimage_blur == nil then x_blur = 0 y_blur = 0 else x_blur = math.random(-afterimage_blur,afterimage_blur) y_blur = math.random(-afterimage_blur,afterimage_blur) end if afterimage_dur == nil or afterimage_dur < frame_dur then afterimage_dur = frame_dur end  if accel == nil then accel = 1 end x_pos = {} y_pos = {} dur = t2 - t1 frames = math.floor(dur/(frame_dur/density)) x_pos[j] =  (1-(j/frames)^accel)^3 * x1 + 3*(1-(j/frames)^accel)^2 * ((j/frames)^accel) * x2 + 3*(1-(j/frames)^accel) * ((j/frames)^accel)^2 * x3 + ((j/frames)^accel)^3 * x4  y_pos[j] = (1-(j/frames)^accel)^3 * y1 + 3*(1-(j/frames)^accel)^2 * ((j/frames)^accel) * y2 + 3*(1-(j/frames)^accel) * ((j/frames)^accel)^2 * y3 + ((j/frames)^accel)^3 * y4  if j == 1 then maxloop(frames) end retime("presyl",t1+(j-1)*(frame_dur/density),t1+j*(frame_dur/density)+afterimage_dur) return string.format("\\move(%d,%d,%d,%d)",x_pos[j],y_pos[j],x_pos[j] + x_blur ,y_pos[j] + y_blur) end
function bezier_move(x1,y1,x2,y2,x3,y3,x4,y4,t1,t2,accel,afterimage_blur,afterimage_dur,density) 
    if density == 0 or density == nil 
        then density = 1 
    end 
    if afterimage_blur == 0 or afterimage_blur == nil then 
        x_blur = 0 
        y_blur = 0 
    else 
        x_blur = math.random(-afterimage_blur,afterimage_blur) y_blur = math.random(-afterimage_blur,afterimage_blur) 
    end 
    if afterimage_dur == nil or afterimage_dur < frame_dur then 
        afterimage_dur = frame_dur 
    end  
    if accel == nil then 
        accel = 1 
    end 
    x_pos = {} 
    y_pos = {} 
    dur = t2 - t1 
    frames = math.floor(dur/(frame_dur/density)) 
    x_pos[j] =  (1-(j/frames)^accel)^3 * x1 + 3*(1-(j/frames)^accel)^2 * ((j/frames)^accel) * x2 + 3*(1-(j/frames)^accel) * ((j/frames)^accel)^2 * x3 + ((j/frames)^accel)^3 * x4  y_pos[j] = (1-(j/frames)^accel)^3 * y1 + 3*(1-(j/frames)^accel)^2 * ((j/frames)^accel) * y2 + 3*(1-(j/frames)^accel) * ((j/frames)^accel)^2 * y3 + ((j/frames)^accel)^3 * y4  
    if j == 1 then 
        maxloop(frames) 
    end 
    retime("presyl",t1+(j-1)*(frame_dur/density),t1+j*(frame_dur/density)+afterimage_dur) 
    return string.format("\\move(%d,%d,%d,%d)",x_pos[j],y_pos[j],x_pos[j] + x_blur ,y_pos[j] + y_blur) 
end

传入值分别为:四个控制点;
t1提前第一个粒子的开始、结束时间,必须填负数或0;
t2延后最后一个粒子的开始、结束时间,必须填正数或0;
t1t2决定了整个曲线的持续时间)
accel为加速度;
afterimage_blur为这一个粒子的位移参数;(随机向外扩散)
afterimage_dur决定单个粒子的持续时间;
density为总的粒子密度、浓度。
frames=math.floor(dur/(frame_dur/density))一句中,density作为自己设定的变量,决定了frames的值,而由maxloop(frames)可知循环次数为frmaes次,所以density越大,frames越大,粒子看起来越密集。[1]

生成一个半圆

{\an5\bord0!bezier_move($center-$width*1.3/2,$middle,$center-$width*1.3/2,$middle-$width*1.3/2*1.414,$center+$width*1.3/2,$middle-$width*1.3/2*1.414,$center+$width*1.3/2,$middle,-500,0,1,10,2000,7)!\p1\c&H11ECA2&}m 0 0 l 2 0 l 2 2 l 0 2

image.png

樱花飘落

樱花绘图代码:[2]

m 9 23 b 8 22 7 21 6 20 b 4 18 3 16 2 12 b 2 9 3 5 4 3 b 5 2 6 1 7 1 b 8 3 8 4 9 5 b 10 4 10 3 11 1 b 15 5 16 8 16 12 b 15 15 14 18 12 20 b 11 21 10 22 9 9 b 8 22 7 21 5 19 b 4 18 2 16 1 11 b 1 8 2 5 4 3 b 5 2 6 1 7 1 b 8 3 8 4 9 5 b 10 4 10 3 11 1 b 13 2 14 3 14 3 b 16 5 17 8 17 11 b 16 16 14 18 13 19 b 11 21 10 22 9 23 

image.png

Comment: 0,0:00:00.00,0:00:00.00,Default,Get Frame Dur,0,0,0,code once,msa = _G.aegisub.ms_from_frame(1) msb = _G.aegisub.ms_from_frame(100) if msa == nil and msb == nil then frame_dur = 33 else frame_dur = (msb-msa)/100 end
Comment: 0,0:00:00.00,0:00:00.00,Default,Beizer Curve Move,0,0,0,code once all,function bezier_move(x1,y1,x2,y2,x3,y3,x4,y4,t1,t2,accel,afterimage_blur,afterimage_dur,density) if density == 0 or density == nil then density = 1 end if afterimage_blur == 0 or afterimage_blur == nil then x_blur = 0 y_blur = 0 else x_blur = math.random(-afterimage_blur,afterimage_blur) y_blur = math.random(-afterimage_blur,afterimage_blur) end if afterimage_dur == nil or afterimage_dur < frame_dur then afterimage_dur = frame_dur end  if accel == nil then accel = 1 end x_pos = {} y_pos = {} dur = t2 - t1 frames = math.floor(dur/(frame_dur/density)) x_pos[j] =  (1-(j/frames)^accel)^3 * x1 + 3*(1-(j/frames)^accel)^2 * ((j/frames)^accel) * x2 + 3*(1-(j/frames)^accel) * ((j/frames)^accel)^2 * x3 + ((j/frames)^accel)^3 * x4  y_pos[j] = (1-(j/frames)^accel)^3 * y1 + 3*(1-(j/frames)^accel)^2 * ((j/frames)^accel) * y2 + 3*(1-(j/frames)^accel) * ((j/frames)^accel)^2 * y3 + ((j/frames)^accel)^3 * y4  if j == 1 then maxloop(frames) end retime("presyl",t1+(j-1)*(frame_dur/density),t1+j*(frame_dur/density)+afterimage_dur) return string.format("\\move(%d,%d,%d,%d)",x_pos[j],y_pos[j],x_pos[j] + x_blur ,y_pos[j] + y_blur) end
Comment: 0,0:00:00.00,0:00:00.00,Default,Beizer Curve Move,0,0,0,code once,shape="m 9 23 b 8 22 7 21 6 20 b 4 18 3 16 2 12 b 2 9 3 5 4 3 b 5 2 6 1 7 1 b 8 3 8 4 9 5 b 10 4 10 3 11 1 b 13 2 14 3  b 15 5 16 8 16 12 b 15 15 14 18 12 20 b 11 21 10 22 9 9 b 8 22 7 21 5 19 b 4 18 2 16 1 11 b 1 8 2 5 4 3 b 5 2 6 1 7 1 b 8 3 8 4 9 5 b 10 4 10 3 11 1 b 13 2 14 3 14 3 b 16 5 17 8 17 11 b 16 16 14 18 13 19 b 11 21 10 22 9 23"
Comment: 0,0:00:00.00,0:00:00.00,Default,,0,0,0,template notext noblank,{\an5\bord0!bezier_move(math.random($center-60,$center+60),$top,math.random($center-60,$center+60),math.random($top-15,$top+75),math.random($center-60,$center+60),math.random($top-15,$top+75),math.random($center-60,$center+60),math.random($top-15,$top+75),0,500,1,10,2000,1)!\t(0.8,\frx!math.random(-540,540)!\fry!math.random(-540,540)!\frz!math.random(-540,540)!)\p2\c!math.random(1,2)==1 and "&HF486FF&" or "&HF8C7FD&"!}!shape!
Comment: 0,0:00:00.00,0:00:00.00,Default,,0,0,0,template noblank,{\an5\pos($center,$middle)}
Comment: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,karaoke,{\k63}这{\k63}是{\k63}一{\k63}句{\k63}测{\k63}试{\k63}字{\k63}幕

image.png

可以把function中的y_blur = math.random(-afterimage_blur,afterimage_blur)改为y_blur = math.random(-afterimage_blur,afterimage_blur*2),使y方向上偏移更多

N次贝塞尔粒子曲线

可以调整原先的Bezier函数,使其支持N个控制点。

数学原理

高阶的贝塞尔可以通过不停的递归直到一阶;

image.png

image.png

仔细看可以发现, 贝塞尔的参数B是二项式(t+(1-t))^n = (1)^n的展开公式.

代码code行[3]

存储单帧时间

msa = _G.aegisub.ms_from_frame(1) 
msb = _G.aegisub.ms_from_frame(100) 
if msa == nil and msb == nil then 
    frame_dur = 33 
else 
    frame_dur = (msb-msa)/100 
end

存储临时数据

temp = {} 
function set_temp(ref,val) 
    temp[4]= val; 
    return val; 
end

定义n次贝赛尔曲线

function Bezier(n,x,y,t) 
    p_x = 0 p_y = 0 
    for i = 1, n, 1 do 
        p_y = p_y + y[i] * set_temp("bern",bernstein(i-1,n-1,t)) p_x = p_x + x[i] * temp.bern  
    end 
    return p_x, p_y 
end

这是定义n次贝赛尔曲线的函数,它是为了进一步定义一个个粒子都在该曲线上

伯恩施坦(Bernstein)多项式

function bernstein(i,n,t) 
    return (factk(n) / (factk(i)*factk(n-i))) * (t^i) * ((1-t)^(n-i)) 
end

此函数定义了三个参数,最后返回的是几个阶乘和三个参数的计算结果。

阶乘

function factk(n) 
    k = 1 
    if (n>1) then 
        for i = 2, n, 1 do 
            k = k * i 
        end 
    end 
    return k 
end

贝塞尔曲线

function Bezier_Move(s_time,e_time,aftimg_dur,x_blur,y_blur,accel,max_space,...)
    a = {...} 
    if j == 1 then 
        t1 = s_time 
        t2 = e_time 
        dur = t2-t1 
        n = #a/2 
        for i = 1,n*2-1,2 do 
            x[(i+1)/2] = a[i] 
        end 
        for i = 2, n*2,2 do 
            y[i/2] = a[i] 
        end 
        c_t = 0 
        maxloop(2) 
        f_t = 1/(dur/frame_dur) 
    end 
        afterimage_dur = aftimg_dur 
    if (afterimage_dur<0 ) then 
        afterimage_dur = 0 
    end 
    pos_x, pos_y = Bezier(n,x,y,c_t) 
    n_t = c_t + f_t 
    n_x, n_y = Bezier(n,x,y,n_t) 
    dist = math.sqrt(math.abs(n_x-pos_x)^2+math.abs(n_y-pos_y)^2) 
    a_t =f_t*(max_space/dist) 
    if (a_t>f_t) then 
        a_t = f_t 
    end
    n_t = c_t + a_t 
    if (n_t < 1) then 
        maxj = j + 1 
    end 
    retime("presyl",t1+dur*(c_t^accel),t1+dur*(n_t^accel)+afterimage_dur) 
    t = c_t 
    c_t = n_t 
    return string.format("\\move(%f,%f,%f,%f,%f,%f)",pos_x,pos_y,pos_x+x_blur,pos_y+y_blur,0,dur-dur*t+afterimage_dur) 
end;  -- 此行可以改成%.2,即保留两位小数
x = {} 
y = {}

Bezier_Move(s_time,e_time,aftimg_dur,x_blur,y_blur,accel,max_space,...)
s_time决定第一个粒子的开始、结束时间(其他粒子也会相应地变化)
e_time决定第一个粒子的开始、结束时间(其他粒子也会相应地变化)
s_timee_time不限正负,但要求e_times_time>0
max_space是生成两个粒子之间的最大距离,和密度成反比
...表示n个控制点的坐标

实际应用效果

此函数对整句话生效;如果有音节分隔,则需要使用Firstsyl

Comment: 0,0:00:00.00,0:00:00.00,Default,Get Frame Dur,0,0,0,code once,msa = _G.aegisub.ms_from_frame(1) msb = _G.aegisub.ms_from_frame(100) if msa == nil and msb == nil then frame_dur = 33 else frame_dur = (msb-msa)/100 end
Comment: 0,0:00:00.00,0:00:00.00,Default,set_temp,0,0,0,code,function set_temp(ref,val) temp[4] = val; return val; end temp = {}
Comment: 0,0:00:00.00,0:00:00.00,Default,Bezier(n;x;y;t) ,0,0,0,code once,function Bezier(n,x,y,t) p_x = 0 p_y = 0 for i = 1, n, 1 do p_y = p_y + y[i] * set_temp("bern",bernstein(i-1,n-1,t)) p_x = p_x + x[i] * temp.bern end return p_x, p_y end
Comment: 0,0:00:00.00,0:00:00.00,Default,bernstein,0,0,0,code,function bernstein(i,n,t) return (factk(n) / (factk(i)*factk(n-i))) * (t^i) * ((1-t)^(n-i)) end
Comment: 0,0:00:00.00,0:00:00.00,Default,factk,0,0,0,code,function factk(n) k = 1 if (n > 1) then for i = 2, n, 1 do k = k * i end end return k end
Comment: 0,0:00:00.00,0:00:00.00,Default,Beizer_N,0,0,0,code once,function Bezier_Move(s_time,e_time,aftimg_dur,x_blur,y_blur,accel,max_space,...)                         a = {...}                       if j == 1 then                         t1 = s_time                         t2 = e_time                         dur = t2-t1                          n = #a/2                       for i = 1,n*2-1,2 do                         x[(i+1)/2] = a[i]                       end                      for i = 2, n*2,2 do                          y[i/2] = a[i]                           end                         c_t = 0                         maxloop(2)                         f_t = 1/(dur/frame_dur)                       end                         afterimage_dur = aftimg_dur                      if (afterimage_dur < 0 ) then                            afterimage_dur = 0                        end                           pos_x, pos_y = Bezier(n,x,y,c_t)                           n_t = c_t + f_t                           n_x, n_y = Bezier(n,x,y,n_t)                          dist = math.sqrt(math.abs(n_x-pos_x)^2+math.abs(n_y-pos_y)^2)                           a_t =f_t*(max_space/dist)                      if (a_t > f_t) then                           a_t = f_t                        end                          n_t = c_t + a_t                      if (n_t < 1) then                            maxj = j + 1                        end                    retime("presyl",t1+dur*(c_t^accel),t1+dur*(n_t^accel)+afterimage_dur)                          t = c_t                          c_t = n_t                        return string.format("\\move(%f,%f,%f,%f,%f,%f)",pos_x,pos_y,pos_x+x_blur,pos_y+y_blur,0,dur-dur*t+afterimage_dur)                end;                     x = {} y = {}        
Comment: 0,0:00:00.00,0:00:00.00,Default,,0,0,0,template line notext,{!Bezier_Move(0,1000,2000,math.random(0,0),math.random(0,0),1,1,$left-40,$top,$left+20,$top-50,$left+40,$top+60,$left+60,$top-70,$left+80,$top+280,$left+100,$top-90,$left+120,$top+100,$left+140,$top-500,$left+160,$top+140,$left+180,$top-300,$left+200,$top+100,$left+240,$top)!\bord0\p4}m 19 4 b 23 8 23 15 19 19 b 15 23 8 23 4 19 b 0 15 0 8 4 4 b 8 0 15 19 4 b 8 0 15 0 19 4
Comment: 0,0:00:00.00,0:00:00.00,Default,,0,0,0,template noblank,{\an5\pos($center,$middle)}
Comment: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,karaoke,测试字幕

image.png

描边函数Vector_Move

code行

Vector_Move

function Vector_Move(s_time,e_time,afterimage_dur,x_blur,y_blur,accel,base_pos_x,base_pos_y,max_space,scale_x,scale_y,s,x_add)  
    if (j == 1) then 
        t = 0 
        pos_x = {} 
        pos_y = {} 
        if (afterimage_dur < 0 ) then 
            afterimage_dur = 0 
        end 
        c_point_x = {} 
        c_point_y = {} 
        point = {} 
        i = 1 
        s:gsub("(%S+)", function(w) 
                point[i] = w i = i + 1 
            end
        ) 
        dur = e_time-s_time 
        i = 1 
        m = 0 
        max_x = -10000 
        min_x = 10000 
        max_y = -10000 
        min_y = 10000 
        scale_x = scale_x / 100 
        scale_y = scale_y / 100 
        while i <= #point do 
            c_point_x = {} 
            c_point_y = {} 
            if point[i] == "m" then 
                s_point_x = point[i+1] 
                s_point_y = point[i+2] 
                i = i + 3 
            elseif point[i] == "b" then 
                c_point_x[1] = s_point_x*scale_x 
                c_point_y[1] = s_point_y*scale_y 
                for k = 2, 4, 1 do 
                    c_point_x[k] = point[i+1+(k-2)*2]*scale_x 
                    c_point_y[k] = point[i+2+(k-2)*2]*scale_y 
                end 
                max_x = math.max(max_x,_G.unpack(c_point_x)) 
                max_y = math.max(max_y,_G.unpack(c_point_y)) 
                min_x = math.min(min_x,_G.unpack(c_point_x)) 
                min_y = math.min(min_y,_G.unpack(c_point_y)) 
                c_point_x[1] = s_point_x*scale_x+base_pos_x 
                c_point_y[1] = s_point_y*scale_y+base_pos_y 
                for k = 2, 4, 1 do 
                    c_point_x[k] = point[i+1+(k-2)*2]*scale_x+base_pos_x 
                    c_point_y[k] = point[i+2+(k-2)*2]*scale_y+base_pos_y 
                end 
                s_point_x = point[i+5] 
                s_point_y = point[i+6] 
                i = i + 7 
            elseif point[i] == "l" then 
                c_point_x[1] = s_point_x*scale_x 
                c_point_y[1] = s_point_y*scale_y 
                c_point_x[2] = point[i+1]*scale_x 
                c_point_y[2] = point[i+2]*scale_y 
                max_x = math.max(max_x,_G.unpack(c_point_x)) 
                max_y = math.max(max_y,_G.unpack(c_point_y)) 
                min_x = math.min(min_x,_G.unpack(c_point_x)) 
                min_y = math.min(min_y,_G.unpack(c_point_y)) 
                c_point_x[1] = s_point_x*scale_x+base_pos_x 
                c_point_y[1] = s_point_y*scale_y+base_pos_y 
                c_point_x[2] = point[i+1]*scale_x+base_pos_x 
                c_point_y[2] = point[i+2]*scale_y+base_pos_y 
                s_point_x = point[i+1] 
                s_point_y = point[i+2] i = i + 3 
            else 
                _G.aegisub.debug.out("Unknown drawing command. You can use only \"m\" , \"b\" , \"l\"^^;") 
                i = #point+1 
            end    
            c_t = 0 
            n = #c_point_x 
            if n ~= 0 then  
                while c_t <= 1 do 
                    m = m + 1  
                    pos_x[m], 
                    pos_y[m] = Bezier(n,c_point_x,c_point_y,c_t)   
                    n_x, n_y = Bezier(n,c_point_x,c_point_y,c_t+0.1) 
                    dist = math.sqrt(math.abs(n_x-pos_x[m])^2+math.abs(n_y-pos_y[m])^2) 
                    c_t = c_t + max_space/dist*0.1 
                end 
            end 
        end 
        maxloop(m) 
    end 
    retime("presyl",s_time+dur*(t^accel),s_time+dur*((t+1/m)^accel)+afterimage_dur) 
    t = t + 1/m 
    adjust_x = -(max_x - min_x) / 2 - min_x 
    adjust_y = -(max_y - min_y) / 2 - min_y  
    return  
    string.format("\\move(%f,%f,%f,%f,%f,%f)",pos_x[j]+x_add,pos_y[j],pos_x[j]+x_blur+x_add,pos_y[j]+y_blur,afterimage_dur/2,afterimage_dur) 
end

s_time决定第一个粒子的开始时间、结束时间(其他离子也会相应的变化)
e_time决定最后一个粒子的开始时间、结束时间(其他离子也会相应的变化)
e_times_time>0

举例:
s=500,e=0,则第一个粒子开始时间就延后500ms,最后一个粒子不变,剩下的其他粒子平分这中间的时间。由于每个粒子位置不一样,此时的移动效果看起来是反向
s=0,e=500,反之是正向
s=-500,e=-2000,与第一个同理,是反向
s=0,e=0,粒子不动,静态。

afterimage_dur是单个粒子的持续时间
x_blury_blur是x、y方向的随机移动
accel加速度参数
base_pos_xbase_pos_y 这两个参数将原始绘图的(0,0)点定位在(base_pos_x+x_add,bas_pos_y)所以这两个参数决定了粒子描边移动的起点
max_space决定生成的两个相邻粒子间的最大距离,与密度成反比
scale_xscale_y是对原始矢量绘图的缩放比例(基准100)
s是要描边的矢量绘图(字符串)
x_add表示在base_pos_x设定的粒子x坐标上,再在x坐标上加x_add,即在base_pos_x的基础上,粒子在x方向上平移
x_add一般填0,但如果要描移动图形的边,就要用x_add,且此时x_add是个变化的值,例如(j-1)/maxj*$lwidth

描边的绘图最好用圆形粒子,而不是方块

辅助函数和N次贝塞尔粒子曲线一致。

实际应用

Comment: 0,0:00:00.00,0:00:00.00,Default,Get Frame Dur,0,0,0,code once,msa = _G.aegisub.ms_from_frame(1) msb = _G.aegisub.ms_from_frame(100) if msa == nil and msb == nil then frame_dur = 33 else frame_dur = (msb-msa)/100 end
Comment: 0,0:00:00.00,0:00:00.00,Default,temp func,0,0,0,code once,temp = {} function set_temp(ref,val) temp[4] = val return val end
Comment: 0,0:00:00.00,0:00:00.00,Default,Bezier(n;x;y;t),0,0,0,code once,function Bezier(n,x,y,t) p_x = 0 p_y = 0 for i = 1, n, 1 do p_y = p_y + y[i] * set_temp("bern",bernstein(i-1,n-1,t)) p_x = p_x + x[i] * temp.bern end return p_x, p_y end
Comment: 0,0:00:00.00,0:00:00.00,Default,bernstein,0,0,0,code,function bernstein(i,n,t) return (factk(n) / (factk(i)*factk(n-i))) * (t^i) * ((1-t)^(n-i)) end
Comment: 0,0:00:00.00,0:00:00.00,Default,factk,0,0,0,code,function factk(n) k = 1 if (n > 1) then for i = 2, n, 1 do k = k * i end end return k end
Comment: 0,0:00:00.00,0:00:00.00,Default,Vector Move Function,0,0,0,code once,function Vector_Move(s_time,e_time,afterimage_dur,x_blur,y_blur,accel,base_pos_x,base_pos_y,max_space,scale_x,scale_y,s,x_add)  if (j == 1) then t = 0 pos_x = {} pos_y = {} if (afterimage_dur < 0 ) then afterimage_dur = 0 end c_point_x = {} c_point_y = {} point = {} i = 1 s:gsub("(%S+)", function(w) point[i] = w i = i + 1 end) dur = e_time-s_time i = 1 m = 0 max_x = -10000 min_x = 10000 max_y = -10000 min_y = 10000 scale_x = scale_x / 100 scale_y = scale_y / 100 while i <= #point do c_point_x = {} c_point_y = {} if point[i] == "m" then s_point_x = point[i+1] s_point_y = point[i+2] i = i + 3 elseif point[i] == "b" then c_point_x[1] = s_point_x*scale_x c_point_y[1] = s_point_y*scale_y for k = 2, 4, 1 do c_point_x[k] = point[i+1+(k-2)*2]*scale_x c_point_y[k] = point[i+2+(k-2)*2]*scale_y end max_x = math.max(max_x,_G.unpack(c_point_x)) max_y = math.max(max_y,_G.unpack(c_point_y)) min_x = math.min(min_x,_G.unpack(c_point_x)) min_y = math.min(min_y,_G.unpack(c_point_y)) c_point_x[1] = s_point_x*scale_x+base_pos_x c_point_y[1] = s_point_y*scale_y+base_pos_y for k = 2, 4, 1 do c_point_x[k] = point[i+1+(k-2)*2]*scale_x+base_pos_x c_point_y[k] = point[i+2+(k-2)*2]*scale_y+base_pos_y end s_point_x = point[i+5] s_point_y = point[i+6] i = i + 7 elseif point[i] == "l" then c_point_x[1] = s_point_x*scale_x c_point_y[1] = s_point_y*scale_y c_point_x[2] = point[i+1]*scale_x c_point_y[2] = point[i+2]*scale_y max_x = math.max(max_x,_G.unpack(c_point_x)) max_y = math.max(max_y,_G.unpack(c_point_y)) min_x = math.min(min_x,_G.unpack(c_point_x)) min_y = math.min(min_y,_G.unpack(c_point_y)) c_point_x[1] = s_point_x*scale_x+base_pos_x c_point_y[1] = s_point_y*scale_y+base_pos_y c_point_x[2] = point[i+1]*scale_x+base_pos_x c_point_y[2] = point[i+2]*scale_y+base_pos_y s_point_x = point[i+1] s_point_y = point[i+2] i = i + 3 else _G.aegisub.debug.out("Unknown drawing command. You can use only \"m\" , \"b\" , \"l\"^^;") i = #point+1 end    c_t = 0 n = #c_point_x if n ~= 0 then  while c_t <= 1 do m = m + 1  pos_x[m], pos_y[m] = Bezier(n,c_point_x,c_point_y,c_t)   n_x, n_y = Bezier(n,c_point_x,c_point_y,c_t+0.1) dist = math.sqrt(math.abs(n_x-pos_x[m])^2+math.abs(n_y-pos_y[m])^2) c_t = c_t + max_space/dist*0.1 end end end maxloop(m) end retime("presyl",s_time+dur*(t^accel),s_time+dur*((t+1/m)^accel)+afterimage_dur) t = t + 1/m adjust_x = -(max_x - min_x) / 2 - min_x adjust_y = -(max_y - min_y) / 2 - min_y  return  string.format("\\move(%f,%f,%f,%f,%f,%f)",pos_x[j]+x_add,pos_y[j],pos_x[j]+x_blur+x_add,pos_y[j]+y_blur,afterimage_dur/2,afterimage_dur) end
Comment: 0,0:00:00.00,0:00:00.00,Default,shape,0,0,0,code once,shape="m 16 21 b 14 19 10 17 7 16 l 4 20 b 7 22 11 24 13 26 l 16 21 m 14 34 b 12 32 8 30 5 29 l 2 33 b 5 35 9 37 11 39 l 14 34 m 12 41 b 9 46 6 52 3 56 l 8 60 b 10 55 13 50 16 44 l 12 41 m 39 24 b 37 31 35 37 32 42 b 28 37 26 31 24 24 l 39 24 m 41 18 l 40 18 l 18 18 l 18 24 l 19 24 l 19 24 b 21 33 24 41 28 47 b 24 51 19 54 14 55 b 15 56 17 59 18 60 b 23 58 28 55 31 51 b 35 55 39 58 43 60 b 44 59 46 56 47 55 b 42 53 38 50 35 47 b 40 40 43 31 45 19 l 41 18 m 57 25 l 87 25 l 87 31 l 93 31 l 93 20 l 77 20 b 76 18 75 16 74 15 l 68 17 b 69 17 69 19 70 20 l 52 20 l 52 31 l 57 31 l 57 25 m 94 41 l 76 41 l 76 40 b 80 38 83 35 86 32 l 82 29 l 81 29 l 60 29 l 60 34 l 75 34 b 73 36 71 37 69 38 l 69 41 l 52 41 l 52 46 l 69 46 l 69 54 b 69 54 69 54 68 54 b 67 54 64 54 61 54 b 62 56 63 58 63 60 b 67 60 70 60 72 59 b 75 58 76 57 76 54 l 76 46 l 94 46 l 94 41 "
Comment: 0,0:00:00.00,0:00:00.00,Default,,0,0,0,template line noblank,{!Vector_Move(0,2000,3000,0,math.random(0,0),1,400,300,1,500,500,shape,0)!\p2\bord0\an7}m 19 4 b 23 8 23 15 19 19 b 15 23 8 23 4 19 b 0 15 0 8 4 4 b 8 0 15 0 19 4 
Comment: 0,0:00:00.00,0:00:00.00,Default,,0,0,0,template line noblank,{\p1\pos(400,300)\an7\fscx500\fscy500\bord0\c&H7BFFEE&}!shape!
Comment: 0,0:00:00.00,0:00:07.23,Default,,0,0,0,karaoke,测试

678c8576-b03d-4a67-9b5f-7a32249434e8.gif

参考

  1. ^https://www.bilibili.com/video/BV1eE411t7rT
  2. ^https://www.bilibili.com/video/BV1eE411t72i
  3. ^https://www.bilibili.com/video/BV1SE411i7A3
标题:贝塞尔曲线和粒子描边
作者:IKK
除转载和特殊声明外,所有文章采用 CC BY-NC-SA 4.0协议

评论

  1. 2月前
    2021-7-31 22:15:43

    感谢博主的分享,支持了。
    技术文章,学习了。文章真长

    • K 博主
      2月前
      2021-7-31 22:24:52

      感谢支持!如果学习Aegisub的话,建议搭配参考链接的视频一起~

本文评论已关闭
上一篇
下一篇