SDL2 平滑时间点之间的纹理(精灵)动画功能
SDL2 Smooth texture(sprite) animation between points in time function
目前我正在尝试通过硬件加速技术(DirectX 或 OpenGL)开发流畅的动画效果,
我目前的目标很简单,我想在给定的时间内将纹理从 A 点移动到 B 点,
这是动画 objects,
的经典方式
我读了很多关于 Robert Penner 插值的文章,为此我想用最简单的线性插值来制作纹理动画,如下所述:
http://upshots.org/actionscript/jsas-understanding-easing
一切正常,除了我的动画不流畅,有点生涩。
不是掉帧的原因,是 double to int rounding 方面的问题,
我用 C++ 和 SDL2 库准备了非常短的示例来展示该行为:
#include "SDL.h"
//my animation linear interpol function
double GetPos(double started, double begin, double end, double duration)
{
return (end - begin) * (double)(SDL_GetTicks() - started) / duration + begin;
}
int main(int argc, char* argv[])
{
//init SDL system
SDL_Init(SDL_INIT_EVERYTHING);
//create windows
SDL_Window* wnd = SDL_CreateWindow("My Window", 0, 0, 1920, 1080, SDL_WINDOW_SHOWN | SDL_WINDOW_BORDERLESS);
//create renderer in my case this is D3D9 renderer, but this behavior is the same with D3D11 and OPENGL
SDL_Renderer* renderer = SDL_CreateRenderer(wnd, 0, SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE | SDL_RENDERER_PRESENTVSYNC);
//load image and create texture
SDL_Surface* surf = SDL_LoadBMP("sample_path_to_bmp_file");
SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, surf);
//get rid of surface we dont need surface anymore
SDL_FreeSurface(surf);
SDL_Event event;
int action = 0;
bool done = false;
//animation time start and duration
double time_start = (double) SDL_GetTicks();
double duration = 15000;
//loop render
while (!done)
{
action = 0;
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_QUIT:
done = 1;
break;
case SDL_KEYDOWN:
action = event.key.keysym.sym;
break;
}
}
switch (action)
{
case SDLK_q:
done = 1;
default:
break;
}
//clear screen
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
//calculate new position
double myX = GetPos(time_start, 10, 1000, duration);
SDL_Rect r;
//assaign position
r.x = (int) round(myX);
r.y = 10;
r.w = 600;
r.h = 400;
//render to rendertarget
SDL_RenderCopy(renderer, tex, 0, &r);
//present
SDL_RenderPresent(renderer);
}
//cleanup
SDL_DestroyTexture(tex);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(wnd);
SDL_Quit();
return 0;
}
我想这个不稳定的动画效果与我的 GetPos(...) 函数有关,该函数使用双精度值,并且我通过 int 值渲染。但是我不能双倍渲染到屏幕,因为我显然不能以 1.2px 绘制,
我的问题是:
你知道任何技术或者你有一些建议如何使这种动画(从,到,持续时间)平滑而没有生涩的效果?
我确信这绝对是可能的,因为 WPF、WIN_RT、Cocos2DX、AndroidJava 等框架都支持这种动画,而且 texture/object 动画很流畅,
提前致谢
编辑
根据评论中的@genpfault 请求,我逐帧添加 x 位置值,如 int 和 double:
rx: 12 myX: 11.782
rx: 13 myX: 13.036
rx: 13 myX: 13.366
rx: 14 myX: 14.422
rx: 16 myX: 15.544
rx: 17 myX: 16.666
rx: 18 myX: 17.722
rx: 19 myX: 18.91
rx: 20 myX: 19.966
rx: 21 myX: 21.154
rx: 22 myX: 22.21
rx: 23 myX: 23.266
rx: 24 myX: 24.388
rx: 25 myX: 25.444
rx: 27 myX: 26.632
rx: 28 myX: 27.754
rx: 29 myX: 28.81
rx: 30 myX: 29.866
rx: 31 myX: 30.988
rx: 32 myX: 32.044
rx: 33 myX: 33.166
rx: 34 myX: 34.288
rx: 35 myX: 35.344
rx: 36 myX: 36.466
rx: 38 myX: 37.588
rx: 39 myX: 38.644
最终 update/solve:
- 我将问题标题从 DirectX/OpenGL 更改为 SDL2,因为问题本身与 SDL2 有关,
- 我将 Rafael Bastos 的回答标记为正确,因为他将我推向了正确的方向,问题是由基于 int 精度值的 SDL 渲染管道引起的
- 正如我们在上面的日志中看到的那样 - 卡顿是由不规则的 X 值引起的,这些值从浮点数四舍五入。为了解决这个问题,我不得不更改 SDL2 渲染管道以使用浮点数而不是整数
- 有趣的是,Opengl、opengles2、d3d9 和 d3d11 渲染器的 SDL2 内部使用浮点数,但是 public SDL_RenderCopy/SDL_RenderCopyEx api 基于 SDL_Rect 和 int 值,当动画基于插值函数时,这会导致动画效果不稳定,
我在 SDL2 中所做的更改远远超出了 Whosebug 的范围,但在接下来的步骤中,我写了一些要点,应该如何避免动画卡顿:
- 我将 SDL_FRect 和 SDL_FPoint 结构从内部 sys_render api 移动到 render.h api 使它们成为 public
- 我在 rect.h/rect.c 中扩展了当前的 SDL 方法以支持 SDL_FRect 和 SDL_FPoint,例如 SDL_HasIntersectionF(...),SDL_IsEmptyF(...) SDL_IntersectRectF(...)
- 我添加了基于 GetRenderViewPort 的新方法 GerRenderViewPortF 以支持浮点精度
- 我添加了 2 个新方法 SDL_RenderCopyF 和 SDL_RenderCopyFEx 以避免任何数字舍入并将实际浮点值传递给内部渲染器,
- 所有 public 功能都必须反映在 dyn_proc SDL api 中,这需要一些 SDL 架构知识才能做到,
为了避免 SDL_GetTick() 和任何其他计时精度问题,我决定将我的插值步骤从时间更改为帧依赖。例如计算动画持续时间我不使用:
float start = SDL_GetTicks();
float duration = some_float_value_in_milliseconds;
我将其替换为:
float step = 0;
float duration = some_float_value_in_milliseconds / MonitorRefreshRate
现在我在每帧渲染后递增 step++
当然它有一些副作用,如果我的引擎会丢掉一些帧,那么我的动画时间不等于持续时间,因为它更依赖于帧,
当然这个duration计算只有在VSYNC ON时有效,vblank off时无用,
现在我有了非常流畅和不抖动的免费动画,带有时间轴功能,
@genpfault 和@RafaelBastos 感谢您的宝贵时间和建议,
看来你需要减去 started from SDL_GetTicks()
像这样:
(end - begin) * ((double)SDL_GetTicks() - started) / duration + begin
(end - begin) gives you the total movement
(SDL_GetTicks() - started) / duration
给你插值比例,乘以总移动量得到插值量,需要加到开始部分,所以你可以得到绝对插值位置
如果不是这样,那么它可能是一个舍入问题,但如果你只能以 int 精度进行渲染,那么我认为你需要绕过 sdl 并使用普通的 opengl 或 directx 调用来渲染它,这允许浮动精度.
目前我正在尝试通过硬件加速技术(DirectX 或 OpenGL)开发流畅的动画效果, 我目前的目标很简单,我想在给定的时间内将纹理从 A 点移动到 B 点, 这是动画 objects,
的经典方式我读了很多关于 Robert Penner 插值的文章,为此我想用最简单的线性插值来制作纹理动画,如下所述: http://upshots.org/actionscript/jsas-understanding-easing
一切正常,除了我的动画不流畅,有点生涩。 不是掉帧的原因,是 double to int rounding 方面的问题,
我用 C++ 和 SDL2 库准备了非常短的示例来展示该行为:
#include "SDL.h"
//my animation linear interpol function
double GetPos(double started, double begin, double end, double duration)
{
return (end - begin) * (double)(SDL_GetTicks() - started) / duration + begin;
}
int main(int argc, char* argv[])
{
//init SDL system
SDL_Init(SDL_INIT_EVERYTHING);
//create windows
SDL_Window* wnd = SDL_CreateWindow("My Window", 0, 0, 1920, 1080, SDL_WINDOW_SHOWN | SDL_WINDOW_BORDERLESS);
//create renderer in my case this is D3D9 renderer, but this behavior is the same with D3D11 and OPENGL
SDL_Renderer* renderer = SDL_CreateRenderer(wnd, 0, SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE | SDL_RENDERER_PRESENTVSYNC);
//load image and create texture
SDL_Surface* surf = SDL_LoadBMP("sample_path_to_bmp_file");
SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, surf);
//get rid of surface we dont need surface anymore
SDL_FreeSurface(surf);
SDL_Event event;
int action = 0;
bool done = false;
//animation time start and duration
double time_start = (double) SDL_GetTicks();
double duration = 15000;
//loop render
while (!done)
{
action = 0;
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_QUIT:
done = 1;
break;
case SDL_KEYDOWN:
action = event.key.keysym.sym;
break;
}
}
switch (action)
{
case SDLK_q:
done = 1;
default:
break;
}
//clear screen
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
//calculate new position
double myX = GetPos(time_start, 10, 1000, duration);
SDL_Rect r;
//assaign position
r.x = (int) round(myX);
r.y = 10;
r.w = 600;
r.h = 400;
//render to rendertarget
SDL_RenderCopy(renderer, tex, 0, &r);
//present
SDL_RenderPresent(renderer);
}
//cleanup
SDL_DestroyTexture(tex);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(wnd);
SDL_Quit();
return 0;
}
我想这个不稳定的动画效果与我的 GetPos(...) 函数有关,该函数使用双精度值,并且我通过 int 值渲染。但是我不能双倍渲染到屏幕,因为我显然不能以 1.2px 绘制, 我的问题是: 你知道任何技术或者你有一些建议如何使这种动画(从,到,持续时间)平滑而没有生涩的效果? 我确信这绝对是可能的,因为 WPF、WIN_RT、Cocos2DX、AndroidJava 等框架都支持这种动画,而且 texture/object 动画很流畅, 提前致谢
编辑 根据评论中的@genpfault 请求,我逐帧添加 x 位置值,如 int 和 double:
rx: 12 myX: 11.782
rx: 13 myX: 13.036
rx: 13 myX: 13.366
rx: 14 myX: 14.422
rx: 16 myX: 15.544
rx: 17 myX: 16.666
rx: 18 myX: 17.722
rx: 19 myX: 18.91
rx: 20 myX: 19.966
rx: 21 myX: 21.154
rx: 22 myX: 22.21
rx: 23 myX: 23.266
rx: 24 myX: 24.388
rx: 25 myX: 25.444
rx: 27 myX: 26.632
rx: 28 myX: 27.754
rx: 29 myX: 28.81
rx: 30 myX: 29.866
rx: 31 myX: 30.988
rx: 32 myX: 32.044
rx: 33 myX: 33.166
rx: 34 myX: 34.288
rx: 35 myX: 35.344
rx: 36 myX: 36.466
rx: 38 myX: 37.588
rx: 39 myX: 38.644
最终 update/solve:
- 我将问题标题从 DirectX/OpenGL 更改为 SDL2,因为问题本身与 SDL2 有关,
- 我将 Rafael Bastos 的回答标记为正确,因为他将我推向了正确的方向,问题是由基于 int 精度值的 SDL 渲染管道引起的
- 正如我们在上面的日志中看到的那样 - 卡顿是由不规则的 X 值引起的,这些值从浮点数四舍五入。为了解决这个问题,我不得不更改 SDL2 渲染管道以使用浮点数而不是整数
- 有趣的是,Opengl、opengles2、d3d9 和 d3d11 渲染器的 SDL2 内部使用浮点数,但是 public SDL_RenderCopy/SDL_RenderCopyEx api 基于 SDL_Rect 和 int 值,当动画基于插值函数时,这会导致动画效果不稳定,
我在 SDL2 中所做的更改远远超出了 Whosebug 的范围,但在接下来的步骤中,我写了一些要点,应该如何避免动画卡顿:
- 我将 SDL_FRect 和 SDL_FPoint 结构从内部 sys_render api 移动到 render.h api 使它们成为 public
- 我在 rect.h/rect.c 中扩展了当前的 SDL 方法以支持 SDL_FRect 和 SDL_FPoint,例如 SDL_HasIntersectionF(...),SDL_IsEmptyF(...) SDL_IntersectRectF(...)
- 我添加了基于 GetRenderViewPort 的新方法 GerRenderViewPortF 以支持浮点精度
- 我添加了 2 个新方法 SDL_RenderCopyF 和 SDL_RenderCopyFEx 以避免任何数字舍入并将实际浮点值传递给内部渲染器,
- 所有 public 功能都必须反映在 dyn_proc SDL api 中,这需要一些 SDL 架构知识才能做到,
为了避免 SDL_GetTick() 和任何其他计时精度问题,我决定将我的插值步骤从时间更改为帧依赖。例如计算动画持续时间我不使用:
float start = SDL_GetTicks(); float duration = some_float_value_in_milliseconds;
我将其替换为:
float step = 0; float duration = some_float_value_in_milliseconds / MonitorRefreshRate
现在我在每帧渲染后递增 step++
当然它有一些副作用,如果我的引擎会丢掉一些帧,那么我的动画时间不等于持续时间,因为它更依赖于帧, 当然这个duration计算只有在VSYNC ON时有效,vblank off时无用,
现在我有了非常流畅和不抖动的免费动画,带有时间轴功能,
@genpfault 和@RafaelBastos 感谢您的宝贵时间和建议,
看来你需要减去 started from SDL_GetTicks()
像这样:
(end - begin) * ((double)SDL_GetTicks() - started) / duration + begin
(end - begin) gives you the total movement
(SDL_GetTicks() - started) / duration
给你插值比例,乘以总移动量得到插值量,需要加到开始部分,所以你可以得到绝对插值位置
如果不是这样,那么它可能是一个舍入问题,但如果你只能以 int 精度进行渲染,那么我认为你需要绕过 sdl 并使用普通的 opengl 或 directx 调用来渲染它,这允许浮动精度.