新缓动系统

很久前提到过一个“如何绘制跨屏长条”的问题,当时的思路是:
A. 分析长条的开头节点/结尾节点是否在屏幕上;
B. 开头节点不在屏幕上 -> 使用屏幕底部对应DTime(变速时间)的插值节点替换“开头节点”;
C. 结尾节点不在屏幕上 -> 使用屏幕顶部对应DTime的插值节点替换“结尾节点”;
D. 绘制。
当需要绘制的跨屏长条是曲线长条时,上述的插值操作便会涉及到个人称之为“曲线拆分”的问题。
一、 曲线拆分问题
以下是个人对曲线拆分问题能想到的最简单的描述。
现有一条曲线A:(x1,y1)--<type1>--(x2,y2),即用类型为type1的曲线连接(x1,y1)和(x2,y2)两个点。在这条曲线途经的点中取一点(x3,y3),绘制曲线B:(x1,y1)--<type2>--(x3,y3)--<type3>--(x2,y2)。试问如何规定type2和type3,才能使曲线B与曲线A重合?
二、常规思路
如果type1、type2、type3代表的都是“直线线段”,曲线A和曲线B就一定重合。
因此,在谱面加载阶段通过插值法将曲线A转换为如下形式的折线段:
(x1,y1)--<Linear>--(x3,y3)--<Linear>--(x4,y4)······(xn,yn)--<Linear>--(x2,y2)
即可实现(对该折线段的)任意次无损拆分。
三、新缓动系统
目前,我们正在尝试利用GPU的并行计算能力来优化物件渲染,对于长条而言优势就是可以以极低的成本换取极高的缓动精度,但问题就是将物件信息上传到VRAM需要一定的成本。
进行这种非必需的优化讲求的就是一个极致。“转换为折线”这一过程存在着一定的精度损失,也会造成长条节点数量的暴增,相对应地GPU处理单根长条需要绘制多个梯形也存在着一定的额外开支()
为此,以下介绍一种不转换为折线也能完成曲线拆分的方案。
A. 根据缓动API的封装习惯,缓动曲线使用ratio表示:ratio∈[0,1],f(ratio)∈[0,1]
B. 初始状态提供如下几种缓动曲线:
Linear -> f(x) = x
InSine -> f(x) = sin( x*π/2 )
OutSine -> f(x) = 1 - sin( x*π/2 )
C. 取ratio=m的一点插值:
InSine type2 -> f(x) = sin( x*π/2m )
InSine type3 -> f(x) = sin( x*π/2(1-m) + m/(1-m) )
OutSine type2 -> f(x) = 1 - sin( x*π/2m )
OutSine type3 -> f(x) = 1 - sin( x*π/2(1-m) + m/(1-m) )
D. 传入的数据可以简化为x1、y1、x2、y2初始类型、头部ratio、结尾ratio七个。
设头部ratio=m,尾部ratio=n,曲线类型为InSine f(x)=sin(x*π/2),则先拉伸为原始曲线的1/(n-m)倍,再向左平移m/(n-m)单位长度,使新曲线g(x)满足g(0)=f(m)、g(1)=f(n),那么
InSine g(x) = sin( x*π/2(n-m) + m/(n-m) )
OutSine g(x) = 1 - sin( x*π/2(n-m) + m/(n-m) )
E. 验证:
头部ratio=0、尾部ratio=1 -> InSine g(x) = sin( x*π/2 ) -> 原始曲线
头部ratio≠0、尾部ratio=1 -> InSine g(x) = sin( x*π/2(1-m) + m/(1-m) ) -> type3
头部ratio=0、尾部ratio≠1 -> InSine g(x) = sin( x*π/2n ) -> type2
渲染曲线长条时,每根长条需传入左easetype、左x1、左x2、右easetype、右x1、右x2、y1、y2、ratio(y1)、ratio(y2)十个参数。
参数个数最好能控制在8个以内,那么x方向处理成类似Project SEKAI或者Chunithm的“定轨变宽”风格,就可以用位段将(左x1、左x2、左easetype)合并到一个float,(右x1、右x2、右easetype)同理。
通过在逻辑中固定屏幕底部y1和屏幕顶部y2,可以将y1和ratio(y1)合并为yr1:
yr1>1 : 该值表示y1、ratio(y1)=0
yr1≦1:该值表示ratio(y1),y1为逻辑中固定的屏幕底部y1
同理,y2和ratio(y2)也可以合并为yr2:
yr2>1 : 该值表示y2、ratio(y2)=1
yr2≦1:该值表示ratio(y2),y2为逻辑中固定的屏幕顶部y2
通过这样一轮合并,每根曲线长条的传参量便控制在了4个。