旋转星域|在 2D 平面上模仿 3D Space

Rotating Star Field | Immitating 3D Space on a 2D Plane

我正在努力模拟 2 维表面上的 3D 星场,观看者只能在 x 方向转动。当然,对于普通的 2D 星场,您可以简单地修改星星的 X / Y 位置来模拟运动;但我想要找到的是一个明确定义的模型,其中恒星的速度给人一种围绕视图旋转的错觉。

下面是我的意思的一个例子。其中 r1r2r3 都是环绕观察者的恒星深度。包裹效果(和翘曲)是我卡住的地方。

我正在考虑两种方法,但目前正朝着第一种方法努力,因为它似乎是两种方法中更容易实现的一种。

  1. 随着恒星越来越靠近运动发生的边缘,它们的速度会加快。
  2. 随着星星越来越靠近屏幕的顶部和底部,星星向内弯曲。

  1. 使用某种“透镜效应”(矩阵投影魔法?),让星星看起来在屏幕周围弯曲。不知道该怎么做。

我的部分问题是不知道我在这里寻找的确切术语。我是一名程序员,而不是数学家,我相信这个问题可能更属于后者。有人问了这个问题 但我不是特别喜欢可视化结果方面的解决方案,所以我很好奇是否有人有其他解决方案。

如果您想采用这种方法,那么圆形轨迹感觉不对...我会尝试类似圆形菱形的东西:

你可以用cubics这个(蓝色点是插值立方体的控制点)你在新星诞生时开始初始化。然后只需将其参数 t 从 -1 迭代到 +2 ... 或使用 3 个后续三次方(分段插值)

但是要更接近真实的 3D 只需将您的星星移动到直线 反平行于船舶运动并简单地投影它们的位置基于到船的距离 d:

// camera basis vectors
X = vec3(-sin(yaw),0.0,+cos(yaw)); // view right direction (or left)
Y = vec3(        0,  1,        0); // view up direction (or left)
Z = vec3( cos(yaw),0.0, sin(yaw)); // view forward direction
// camera local coordinates assuming its placed at (0,0,0)
x' = dot(vec3(x,y,z),X);
y' = dot(vec3(x,y,z),Y);
z' = dot(vec3(x,y,z),Z);
// perspective projection
d=c1/max(c0,abs(z));
x' *= d;
y' *= d;

其中 yaw 是转角,(x,y,z) 是星坐标,c0 是最小距离常数以避免被零除,c1 是缩放常数,如 zoom ...你只需在你的船周围生成随机星位置并在它超过某个阈值后迭代它们的 z 坐标移除该星并生成另一颗 ...

这里是 C++/VCL 的小例子:

//$$---- Form CPP ----
//---------------------------------------------------------------------------
#include <vcl.h>
#include "GLSL_math.h"
#pragma hdrstop
#include "win_main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TMain *Main;
//---------------------------------------------------------------------------
// 
//---------------------------------------------------------------------------
const int stars=500;
vec3 star[stars];
float yaw=0.0;          // camera view angle
float forw=0.0;         // ship movement angle
float speed=0.02;
float dyaw=0.0;         // camera turning speed
//---------------------------------------------------------------------------
void stars_init()
    {
    int i;
    vec3 p;
    Randomize();
    for (i=0;i<stars;i++)
        {
        p=vec3(Random(),Random(),Random()); // range < 0, 1>
        p=p+p-vec3(1.0,1.0,1.0);                // range <-1,+1>
        star[i]=p;
        }
    }
//---------------------------------------------------------------------------
void stars_update()
    {
    int i;
    vec3 p;
    // update camera direction
    yaw=fmod(yaw+dyaw,2.0*M_PI);
    // ship speed vector
    vec3 dp=speed*vec3( cos(forw),0.0, sin(forw));
    // process stars
    for (i=0;i<stars;i++)
        {
        p=star[i]-dp;                           // apply speed
        while (length(p)>1.0)                   // if out of range
            {
            // create new star
            p=vec3(Random(),Random(),Random()); // range < 0, 1>
            p=p+p-vec3(1.0,1.0,1.0);            // range <-1,+1>
            }
        star[i]=p;                              // store new position
        }
    }
//---------------------------------------------------------------------------
void stars_draw()
    {
    int i,xx=0,yy=0;
    vec3 X,Y,Z;
    float x,y,z,d;
    // camera basis vectors (3x3 rotation matrix)
    X = vec3(-sin(yaw),0.0,+cos(yaw)); // view right direction (or left)
    Y = vec3(        0,  1,        0); // view up direction (or left)
    Z = vec3( cos(yaw),0.0, sin(yaw)); // view forward direction
    // stars
    for (i=0;i<stars;i++)
        {
        // camera local coordinates assuming its placed at (0,0,0)
        x = dot(star[i],X);
        y = dot(star[i],Y);
        z = dot(star[i],Z);
        // behind camera?
        if (z<0) continue;
        // perspective projection
        d=200.0/max(0.001,fabs(z));
        x*=d; xx=Main->xs2+x;
        y*=d; yy=Main->ys2-y;
        // screen cliping & render pixel
        if ((xx>=0)&&(xx<Main->xs)&&(yy>=0)&&(yy<Main->ys))
         Main->pyx[yy][xx]=0x00FFFFFF;
        }
    }
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
void TMain::draw()
    {
    double x,y,z,r=5,*p;
    // clear buffer
    bmp->Canvas->Brush->Color=clBlack;
    bmp->Canvas->FillRect(TRect(0,0,xs,ys));

    // stars
    stars_update();
    stars_draw();
    Caption=floor(yaw*180.0/M_PI);

    // render backbuffer
    Main->Canvas->Draw(0,0,bmp);
    }
//---------------------------------------------------------------------------
__fastcall TMain::TMain(TComponent* Owner) : TForm(Owner)
    {
    bmp=new Graphics::TBitmap;
    bmp->HandleType=bmDIB;
    bmp->PixelFormat=pf32bit;
    pyx=NULL;
    stars_init();
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormDestroy(TObject *Sender)
    {
    if (pyx) delete[] pyx;
    delete bmp;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormResize(TObject *Sender)
    {
    xs=ClientWidth;  xs2=xs>>1;
    ys=ClientHeight; ys2=ys>>1;
    bmp->Width=xs;
    bmp->Height=ys;
    if (pyx) delete[] pyx;
    pyx=new int*[ys];
    for (int y=0;y<ys;y++) pyx[y]=(int*) bmp->ScanLine[y];
    }
//---------------------------------------------------------------------------
void __fastcall TMain::tim_redrawTimer(TObject *Sender)
    {
    draw();
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift)
    {
    if (Key==37) dyaw=-2.0*M_PI/180.0;
    if (Key==39) dyaw=+2.0*M_PI/180.0;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormKeyUp(TObject *Sender, WORD &Key, TShiftState Shift)
    {
    if (Key==37) dyaw=0.0;
    if (Key==39) dyaw=0.0;
    }
//---------------------------------------------------------------------------

其中 Main->xs,Main->ys 是 window 的分辨率 Main->xs2,Main->ys2 是它的一半(屏幕中心),Main->pyx[y][x] 是对我的后台缓冲区位图的直接像素访问。 . 唯一重要的东西是 star_init,star_update,star_draw 函数,只需将 VCL 渲染更改为您的类型 ... 相机转动由键盘事件设置的 dyaw 变量控制 ... vecto rmath 是类似于 GLSL 的语法但是你可以使用任何(比如 GLM)甚至硬编码,因为我不使用任何疯狂的东西......

此处预览 90 度旋转:

只需使用常量来满足您的需要和屏幕分辨率...

这种方法可以使用任何旋转,而不仅仅是 yaw ...现在你可以玩星星的颜色你可以使用这个:

  • Star B-V color index to apparent RGB color

并根据 z = dot(star[i],Z); 速度添加 red/blue 换档,您可以使用此:

  • RGB values of visible spectrum

你也可以渲染线而不是点来获得不同速度的感觉