If you want to go with such approach then circular trajectories does not feel right ... I would try something like rounded diamond shape:

You can use cubics for this (the blue points are the control points of interpolation cubic) that you init at start when new star is born. And then just iterate its parameter t
from -1 to +2 ... or use 3 consequent cubics (piecewise interpolation)
However to be much closer to real 3D simply move your stars in straight lines anti parallel to ship movement and simply project their position based on distance to ship 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;
Where yaw
is turn angle , (x,y,z)
is star coordinate and c0
is min distance constant to avoid division by zero, c1
is scaling constant like zoom
... You just generate radom star positions around your ship and iterate their z
coordinate once it crosses some threshold remove that star and generate another one ...
Here small C++/VCL example of this:
//$$---- Form CPP ----
//---------------------------------------------------------------------------
#include <vcl.h>
#include "GLSL_math.h"
#pragma hdrstop
#include "win_main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TMain *Main;
//---------------------------------------------------------------------------
// https://stackoverflow.com/a/71950012/2521214
//---------------------------------------------------------------------------
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;
}
//---------------------------------------------------------------------------
Where Main->xs,Main->ys
is the resolution of window Main->xs2,Main->ys2
is half of it (center of screen) and Main->pyx[y][x]
is direct pixel access to my backbuffer bitmap... The only important stuff are star_init,star_update,star_draw
functions just change the VCL rendering to your kind ... The camera turning is controled by dyaw
variable set by keyboard events ... The vecto rmath is GLSL like syntax but you can use any(like GLM) even hardcode as I do not use any wild stuff ...
Here preview for 90 deg rotations:

just play with the constants to match your needs and screen resolution ...
This approach can use any rotation not just yaw
... Now you can play with star colors You can use this:
And add red/blue shift based on z = dot(star[i],Z);
speed for thet you can use this:
You can also render Lines instead of dots to get the feeling of different speeds