模式 7 仿射变换中不受控制的摄像机移动的原因是啥?

Posted

技术标签:

【中文标题】模式 7 仿射变换中不受控制的摄像机移动的原因是啥?【英文标题】:The cause of uncontrolled camera movement in a mode 7 affine transformation?模式 7 仿射变换中不受控制的摄像机移动的原因是什么? 【发布时间】:2021-09-09 17:10:12 【问题描述】:

我正在尝试使用 C 和 SDL2 实现模式 7 效果。最终,我希望能够做出很酷的效果,比如改变高度和视野。但是现在,我只想让一些简单的工作。这是我目前所拥有的:

我的问题是,在转弯时,我似乎朝着同一个方向前进,即使我试图转向不同的方向。很难看清这一点,但在运行我的代码后,它应该会变得清晰。

我很困惑为什么会这样。我的代码主要基于this 教程。我在我的 C 代码中留下了 cmets 来描述每个部分的意图。如果您知道为什么我尝试模拟 SNES 模式 7 的尝试不起作用,请告诉我。注意:要转动,按左右箭头键,要前进和后退,按前进和后退。此代码可能无法在非英特尔系统上运行,因为我依赖于英特尔 SIMD 内在函数。如果有帮助,我正在使用 clang 12.0.5。

这是我的 mode_7 函数,它驱动效果:

void mode_7(const Camera camera, const Screen screen, const Sprite* sprite, const Uint8* keys) 
    static Uint32 local_buffer[height][width];

    const int height_center = height / 2;
    const Vector dimensions = vec_set(sprite -> w);

    for (int z = -height_center; z < height_center; z++) 
        const int y = z + height_center;

        for (int x = 0; x < width; x++) 
            const int reverse_x = width - x;

            const Vector rot_pos_3D = 
                reverse_x * camera.dir[1] + x * camera.dir[0],
                reverse_x * camera.dir[0] - x * camera.dir[1]
            ;

            const Vector pos_2D = vec_add(camera.pos, vec_div(rot_pos_3D, vec_set(z)));
            const Vector floor_pos_2D = floor(pos_2D[0]), floor(pos_2D[1]);
            const Vector tex_pos = vec_mul(vec_sub(pos_2D, floor_pos_2D), dimensions);

            local_buffer[y][x] = read_sprite_pixel(sprite, (long) tex_pos[0], (long) tex_pos[1]);
        
    
    memcpy(screen.pixels, local_buffer, width * height * sizeof(Uint32));

这就是我计算相机位置、方向和角度的方式:

void update_camera(Camera* camera, const Uint8* keys) 
    if (keys[SDL_SCANCODE_LEFT]) 
        if ((camera -> angle -= camera -> v_turn) < 0.0) camera -> angle = two_pi;
    
    if (keys[SDL_SCANCODE_RIGHT]) 
        if ((camera -> angle += camera -> v_turn) > two_pi) camera -> angle = 0.0;
    

    camera -> dir = (Vector) cos(camera -> angle), sin(camera -> angle);
    const Vector forward_movement = vec_mul(camera -> dir, vec_set(camera -> v_move));
    
    Vector movement = 0.0, 0.0;
    if (keys[SDL_SCANCODE_UP])
        movement = vec_add(movement, forward_movement);
    if (keys[SDL_SCANCODE_DOWN])
        movement = vec_sub(movement, forward_movement);

    camera -> pos = vec_add(camera -> pos, movement);

所有的代码,包括上面的代码,都在下面,如果你想试试的话。

// SDL2 header, handy macros, constants, typedefs

#include <SDL2/SDL.h>

#define FAIL(...) fprintf(stderr, __VA_ARGS__); exit(1);

#define vec_set _mm_set1_pd
#define vec_add _mm_add_pd
#define vec_sub _mm_sub_pd
#define vec_mul _mm_mul_pd
#define vec_div _mm_div_pd

const double two_pi = M_PI * 2.0;

enum 
    fps = 60, width = 800, height = 600,
    pixel_format = SDL_PIXELFORMAT_ARGB8888, pixel_format_bpp = 4
;

typedef SDL_Surface Sprite;
typedef __m128d Vector;

typedef struct 
    SDL_Window* window;
    SDL_Renderer* renderer;
    SDL_Texture* buffer;
    SDL_PixelFormat* pixel_format;
    void* pixels;
    int pixel_pitch;
 Screen;

typedef struct 
    Vector pos, dir;
    double angle;
    const double v_move, v_turn;
 Camera;

// abstraction for the screen

Screen init_screen(void) 
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0)
        FAIL("Could not initialize SDL\n");

    SDL_SetHintWithPriority(SDL_HINT_RENDER_VSYNC, "1", SDL_HINT_OVERRIDE);

    Screen screen;
    SDL_CreateWindowAndRenderer(width, height, SDL_RENDERER_ACCELERATED, &screen.window, &screen.renderer);
    screen.buffer = SDL_CreateTexture(screen.renderer, pixel_format, SDL_TEXTUREACCESS_STREAMING, width, height);
    screen.pixel_format = SDL_AllocFormat(pixel_format);

    SDL_SetWindowTitle(screen.window, "Mode 7");
    SDL_SetRenderTarget(screen.renderer, NULL);
    SDL_SetRenderDrawColor(screen.renderer, 0, 0, 0, 0);

    return screen;


void deinit_screen(const Screen screen) 
    SDL_DestroyWindow(screen.window);
    SDL_DestroyRenderer(screen.renderer);
    SDL_DestroyTexture(screen.buffer);
    SDL_FreeFormat(screen.pixel_format);
    SDL_Quit();


void clear_screen(Screen* const screen) 
    SDL_LockTexture(screen -> buffer, NULL, &screen -> pixels, &screen -> pixel_pitch);


void refresh_screen(const Screen screen, const Uint32 before) 
    SDL_UnlockTexture(screen.buffer);
    SDL_RenderCopy(screen.renderer, screen.buffer, NULL, NULL);
    SDL_RenderPresent(screen.renderer);

    const int wait = fps / 1000 - (SDL_GetTicks() - before);
    if (wait > 0) SDL_Delay(wait);


// abstraction for sprites

Sprite* init_sprite(const char* const path, const SDL_PixelFormat* pixel_format) 
    SDL_Surface* const unconverted_surface = SDL_LoadBMP(path);
    if (unconverted_surface == NULL) FAIL("Could not load a sprite of path %s\n", path);

    SDL_Surface* const converted_surface = SDL_ConvertSurface(unconverted_surface, pixel_format, 0);
    if (converted_surface == NULL) FAIL("Could not convert a sprite's surface type: %s\n", path);
    SDL_FreeSurface(unconverted_surface);
    SDL_LockSurface(converted_surface);

    return converted_surface;


void deinit_sprite(Sprite* sprite) 
    SDL_UnlockSurface(sprite);
    SDL_FreeSurface(sprite);


Uint32 read_sprite_pixel(const Sprite* sprite, const int x, const int y) 
    return *(Uint32*) ((Uint8*) sprite -> pixels + y * sprite -> pitch + x * pixel_format_bpp);


// the core of my code

void mode_7(const Camera camera, const Screen screen, const Sprite* sprite, const Uint8* keys) 
    static Uint32 local_buffer[height][width];

    const int height_center = height / 2;
    const Vector dimensions = vec_set(sprite -> w);

    for (int z = -height_center; z < height_center; z++) 
        const int y = z + height_center;

        for (int x = 0; x < width; x++) 
            const int reverse_x = width - x;

            const Vector rot_pos_3D = 
                reverse_x * camera.dir[1] + x * camera.dir[0],
                reverse_x * camera.dir[0] - x * camera.dir[1]
            ;

            const Vector pos_2D = vec_add(camera.pos, vec_div(rot_pos_3D, vec_set(z)));
            const Vector floor_pos_2D = floor(pos_2D[0]), floor(pos_2D[1]);
            const Vector tex_pos = vec_mul(vec_sub(pos_2D, floor_pos_2D), dimensions);

            local_buffer[y][x] = read_sprite_pixel(sprite, (long) tex_pos[0], (long) tex_pos[1]);
        
    
    memcpy(screen.pixels, local_buffer, width * height * sizeof(Uint32));


// input reading + main

void update_camera(Camera* camera, const Uint8* keys) 
    if (keys[SDL_SCANCODE_LEFT]) 
        if ((camera -> angle -= camera -> v_turn) < 0.0) camera -> angle = two_pi;
    
    if (keys[SDL_SCANCODE_RIGHT]) 
        if ((camera -> angle += camera -> v_turn) > two_pi) camera -> angle = 0.0;
    

    camera -> dir = (Vector) cos(camera -> angle), sin(camera -> angle);
    const Vector forward_movement = vec_mul(camera -> dir, vec_set(camera -> v_move));
    
    Vector movement = 0.0, 0.0;
    if (keys[SDL_SCANCODE_UP])
        movement = vec_add(movement, forward_movement);
    if (keys[SDL_SCANCODE_DOWN])
        movement = vec_sub(movement, forward_movement);

    camera -> pos = vec_add(camera -> pos, movement);


int main(void) 
    Screen screen = init_screen();
    Sprite* sprite = init_sprite("../assets/dirt.bmp", screen.pixel_format);
    Camera camera = 0.0, 0.0, 0.0, 0.0, 0.0, 0.1, 0.05;

    const Uint8* keys = SDL_GetKeyboardState(NULL);
    SDL_Event event;
    while (1) 
        const Uint32 before = SDL_GetTicks();
        while (SDL_PollEvent(&event)) 
            if (event.type == SDL_QUIT) 
                deinit_sprite(sprite);
                deinit_screen(screen);
                return 0;
            
        
        update_camera(&camera, keys);
        clear_screen(&screen);
        mode_7(camera, screen, sprite, keys);
        refresh_screen(screen, before);
       

【问题讨论】:

@Yunnosch 我刚刚重组了我的问题,使其只关注转向问题。 你能在一个更简单的程序中按预期转动吗? IE。你需要多接近 SDL/OpenGL HelloWorld 才能让它正确?或者换一种说法ericlippert.com/2014/03/21/find-a-simpler-problem @Yunnosch 我只是把导致问题的 sn-p 放在第一位 - 这样,我的算法有什么问题,而不是与 SDL 和 C 相关的任何问题。如果你知道,请告诉我有更多反馈。 【参考方案1】:

您的代码存在两个主要问题。 一部分是:

const Vector rot_pos_3D = 
    reverse_x * camera.dir[1] + x * camera.dir[0],
    reverse_x * camera.dir[0] - x * camera.dir[1]
;

当 x 从0 变为width 时,这部分应该在两点之间进行或多或少的线性插值。一个位于camera.dir,指向前方。另一个是指向正交方向camera.dir[1], -camera.dir[0]

见下图:

但是,看起来坐标被交换了。应该是:

const Vector rot_pos_3D = 
    reverse_x * camera.dir[0] - x * camera.dir[1],
    reverse_x * camera.dir[1] + x * camera.dir[0],
;

现在导航变得不那么混乱了。但是,玩家的眼睛总是像上图一样看向左前方。

解决此问题的一个简单方法是将屏幕放置在播放器前方的点 (FORWARD+LEFT) 和 (FORWARD-LEFT) 点之间,如下图所示。

此修复适用于以下补丁:

@@ -107,6 +107,8 @@ void mode_7(const Camera camera, const Screen screen, const Sprite* sprite, cons
     const int height_center = height / 2;
     const Vector dimensions = vec_set(sprite -> w);
 
+    double d0[2] = camera.dir[0] + camera.dir[1], camera.dir[1] - camera.dir[0];
+    double d1[2] = camera.dir[0] - camera.dir[1], camera.dir[1] + camera.dir[0];
     for (int z = -height_center; z < height_center; z++) 
         const int y = z + height_center;
 
@@ -114,11 +116,11 @@ void mode_7(const Camera camera, const Screen screen, const Sprite* sprite, cons
             const int reverse_x = width - x;
 
             const Vector rot_pos_3D = 
-                reverse_x * camera.dir[1] + x * camera.dir[0],
-                reverse_x * camera.dir[0] - x * camera.dir[1]
+                reverse_x * d0[0] + x * d1[0],
+                reverse_x * d0[1] + x * d1[1],
             ;
 

另一个问题是“天花板”向相反方向移动。它是由屏幕上部的负“z”除引起的。只需申请abs()

-            const Vector pos_2D = vec_add(camera.pos, vec_div(rot_pos_3D, vec_set(z)));
+            const Vector pos_2D = vec_add(camera.pos, vec_div(rot_pos_3D, vec_set(abs(z))));

现在无限房间应该可以正确渲染了。

【讨论】:

完美,谢谢!这很棒。我还有一个问题:你知道我如何改变这里的视野吗?

以上是关于模式 7 仿射变换中不受控制的摄像机移动的原因是啥?的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL基础仿射变换原理解析

视觉高级篇22 # 如何用仿射变换来移动和旋转3D物体?

视觉高级篇22 # 如何用仿射变换来移动和旋转3D物体?

【转】仿射变换及其变换矩阵的理解

利用OpenCV的仿射变换函数warpAffine()实现图像的亚像素级平移

利用OpenCV的仿射变换函数warpAffine()实现图像的亚像素级平移