模式 7 仿射变换中相机运动失控的原因?
The cause of uncontrolled camera movement in a mode 7 affine transformation?
我正在尝试使用 C 和 SDL2 实现模式 7 效果。最终,我希望能够做出很酷的效果,比如改变高度和视野。但是现在,我只想做一些简单的工作。这是我目前所拥有的:
我的问题是,在转弯时,即使我试图转向不同的方向,我似乎也在朝着同一个方向前进。很难得到这张照片,但根据 运行 我的代码,它应该变得清晰。
我很困惑为什么会这样。我的代码主要基于 this 教程。我在我的 C 代码中留下了注释,描述了每个部分的意图。如果您知道为什么我模拟 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);
}
}
您的代码存在两个主要问题。
一部分是:
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))));
现在应该可以正确渲染无限房间了。
我正在尝试使用 C 和 SDL2 实现模式 7 效果。最终,我希望能够做出很酷的效果,比如改变高度和视野。但是现在,我只想做一些简单的工作。这是我目前所拥有的:
我的问题是,在转弯时,即使我试图转向不同的方向,我似乎也在朝着同一个方向前进。很难得到这张照片,但根据 运行 我的代码,它应该变得清晰。
我很困惑为什么会这样。我的代码主要基于 this 教程。我在我的 C 代码中留下了注释,描述了每个部分的意图。如果您知道为什么我模拟 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);
}
}
您的代码存在两个主要问题。 一部分是:
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))));
现在应该可以正确渲染无限房间了。