Итак, мы хотим получить 4 точки пересечения между нашей плоскостью (Z=0.0
) и камерой Frustrum.Таким образом, идея состоит в том, чтобы отбросить 4 луча (по одному на каждый край борозды) из фокуса камеры и просто вычислить пересечение луча / плоскости.Так как плоскость равна Z=0.0
, точка пересечения тоже имеет Z=0.0
, поэтому пересечение довольно легко вычислить.
Приведение луча для каждого угла / ребра
от фокуса камеры до угла экрана (в пространстве экрана)
и преобразовать его в глобальные глобальные координаты (путем обращения перспективы ис использованием обратной матрицы моделей это описано позже).Луч должен быть в форме:
p(t) = p + dp*t
, где p
- фокус, а dp
- вектор направления (не нуждается в нормализации)
вычислить пересечение с плоскостью XY (Z=0.0
)
Как z=0.0
, тогда:
0 = p.z + dp.z*t
t = -p.z/dp.z
, чтобы мы могли вычислить точку пересечения напрямую.
преобразовать трехмерные точки пересечения в u,v
внутри карты
для этого простого точечного произведения достаточно.Так что, если p
является нашей точкой пересечения, то:
u = dot(p-p0,du)
v = dot(p-p0,dv)
, где u,v
- это координаты в нашем массиве 2D-карт или миникарте.Если ваши u,v
выровнены по оси, вы можете напрямую использовать (p.x-p0.x,p.y-p0.y)
без точечного произведения
Как преобразовать точку p
из координат камеры в глобальные мировые координаты:
вернуть перспективу
сначала получить параметры матрицы перспективы
double per[16],zNear,zFar,fx,fy;
glGetDoublev(GL_PROJECTION_MATRIX,per);
zFar =0.5*per[14]*(1.0-((per[10]-1.0)/(per[10]+1.0)));
zNear=zFar*(per[10]+1.0)/(per[10]-1.0);
fx=per[0];
fy=per[5];
Это даст вам проблемыближняя и дальняя плоскости и масштабирование по осям x, y.Теперь обратная перспектива - это просто инвертирование деления перспективы следующим образом:
p[1]*=(-p[2]/fy); // apply inverse of perspective
p[0]*=(-p[2]/fx);
znear
и zfar
необходимы для наведения лучей.Для получения дополнительной информации см .:
глобальные мировые координаты
просто используйте обратную матрицу ModelView
на нашем p
.Итак, сначала получите матрицу:
double cam[16];
glGetDoublev(GL_MODELVIEW_MATRIX,cam);
В качестве обратного вы можете использовать мой matrix_inv , поэтому теперь последний шаг:
p = Inverse(cam)*p;
, но не забывайте, что p
должно быть однородным, поэтому (x,y,z,1)
для точек и (x,y,z,0)
для векторов.
Посмотрите здесь, если вам не хватает базовых знаний или вам нужна математика вектор / матрица:
Здесь Small C ++ пример этого:
//---------------------------------------------------------------------------
void matrix_mul_vector(double *c,double *a,double *b)
{
double q[3];
q[0]=(a[ 0]*b[0])+(a[ 4]*b[1])+(a[ 8]*b[2])+(a[12]);
q[1]=(a[ 1]*b[0])+(a[ 5]*b[1])+(a[ 9]*b[2])+(a[13]);
q[2]=(a[ 2]*b[0])+(a[ 6]*b[1])+(a[10]*b[2])+(a[14]);
for(int i=0;i<3;i++) c[i]=q[i];
}
//---------------------------------------------------------------------------
void matrix_inv(double *a,double *b) // a[16] = Inverse(b[16])
{
double x,y,z;
// transpose of rotation matrix
a[ 0]=b[ 0];
a[ 5]=b[ 5];
a[10]=b[10];
x=b[1]; a[1]=b[4]; a[4]=x;
x=b[2]; a[2]=b[8]; a[8]=x;
x=b[6]; a[6]=b[9]; a[9]=x;
// copy projection part
a[ 3]=b[ 3];
a[ 7]=b[ 7];
a[11]=b[11];
a[15]=b[15];
// convert origin: new_pos = - new_rotation_matrix * old_pos
x=(a[ 0]*b[12])+(a[ 4]*b[13])+(a[ 8]*b[14]);
y=(a[ 1]*b[12])+(a[ 5]*b[13])+(a[ 9]*b[14]);
z=(a[ 2]*b[12])+(a[ 6]*b[13])+(a[10]*b[14]);
a[12]=-x;
a[13]=-y;
a[14]=-z;
}
//---------------------------------------------------------------------------
void draw_map()
{
int i,j;
double u,v,p[3],dp[3];
// here 3D view must be already set (modelview,projection)
glDisable(GL_CULL_FACE);
// [draw 3D map]
const int n=30; // map size
double p0[3]={0.0,0.0,0.0}; // map start point
double du[3]={1.0,0.0,0.0}; // map u step (size of grid = 1.0 )
double dv[3]={0.0,1.0,0.0}; // map v step (size of grid = 1.0 )
glColor3f(0.5,0.7,1.0);
glBegin(GL_LINES);
for (j=0;j<=n;j++)
{
for (i=0;i<3;i++) p[i]=p0[i]+(double(j)*du[i])+(double(0)*dv[i]); glVertex3dv(p);
for (i=0;i<3;i++) p[i]=p0[i]+(double(j)*du[i])+(double(n)*dv[i]); glVertex3dv(p);
for (i=0;i<3;i++) p[i]=p0[i]+(double(0)*du[i])+(double(j)*dv[i]); glVertex3dv(p);
for (i=0;i<3;i++) p[i]=p0[i]+(double(n)*du[i])+(double(j)*dv[i]); glVertex3dv(p);
}
glEnd();
// [compute trapeze points]
double cam[16],per[16],pt[4][3],zNear,zFar,fx,fy;
glGetDoublev(GL_PROJECTION_MATRIX,per); // obtain matrices
glGetDoublev(GL_MODELVIEW_MATRIX,cam);
matrix_inv(cam,cam);
zFar =0.5*per[14]*(1.0-((per[10]-1.0)/(per[10]+1.0)));
zNear=zFar*(per[10]+1.0)/(per[10]-1.0);
fx=per[0];
fy=per[5];
for (j=0;j<4;j++) // 4 corners
{
for (i=0;i<3;i++) dp[i]=0.0; // cast ray from camera focus dp
if (j==0) { p[0]=-1.0; p[1]=-1.0; } // to screen corner p
if (j==1) { p[0]=-1.0; p[1]=+1.0; }
if (j==2) { p[0]=+1.0; p[1]=+1.0; }
if (j==3) { p[0]=+1.0; p[1]=-1.0; }
p[2]=zNear; // start position at screen plane
p[1]*=(-p[2]/fy); // apply inverse of perspective
p[0]*=(-p[2]/fx);
// transform to worlds global coordinates
matrix_mul_vector( p,cam, p);
matrix_mul_vector(dp,cam,dp);
// compute intersection of ray and XY plane (z=0) as pt[j] (i exploited the fact that the intersection have z=0.0 for arbitrary plane it would be a bit more complicated)
for (i=0;i<3;i++) dp[i]=p[i]-dp[i];
u=p[2]/dp[2];
if (u<0.0) u=(p[2]-zFar)/dp[2]; // no intersection means "infinite" visibility
for (i=0;i<3;i++) pt[j][i]=p[i]-(u*dp[i]);
u=0.0;
}
// [draw 2D minimap]
GLint vp0[4];
GLint vp1[4]={10,10,150,150}; // minimap position and size ppixels[
double q0[2]={-1.0,-1.0 }; // minimap start point
double eu[2]={2.0/double(n),0.0}; // minimap u step
double ev[2]={0.0,2.0/double(n)}; // minimap v step
// set 2D view for minimap
glDisable(GL_DEPTH_TEST);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glGetIntegerv(GL_VIEWPORT,vp0);
glViewport(vp1[0],vp1[1],vp1[2],vp1[3]);
glColor3f(0.0,0.0,0.0); // clear background
glBegin(GL_QUADS);
for (i=0;i<2;i++) p[i]=q0[i]+(double(0)*eu[i])+(double(0)*ev[i]); glVertex2dv(p);
for (i=0;i<2;i++) p[i]=q0[i]+(double(n)*eu[i])+(double(0)*ev[i]); glVertex2dv(p);
for (i=0;i<2;i++) p[i]=q0[i]+(double(n)*eu[i])+(double(n)*ev[i]); glVertex2dv(p);
for (i=0;i<2;i++) p[i]=q0[i]+(double(0)*eu[i])+(double(n)*ev[i]); glVertex2dv(p);
glEnd();
glColor3f(0.15,0.15,0.15); // grid
glBegin(GL_LINES);
for (j=0;j<=n;j++)
{
for (i=0;i<2;i++) p[i]=q0[i]+(double(j)*eu[i])+(double(0)*ev[i]); glVertex2dv(p);
for (i=0;i<2;i++) p[i]=q0[i]+(double(j)*eu[i])+(double(n)*ev[i]); glVertex2dv(p);
for (i=0;i<2;i++) p[i]=q0[i]+(double(0)*eu[i])+(double(j)*ev[i]); glVertex2dv(p);
for (i=0;i<2;i++) p[i]=q0[i]+(double(n)*eu[i])+(double(j)*ev[i]); glVertex2dv(p);
}
glEnd();
glColor3f(0.5,0.5,0.5); // border of minimap
glLineWidth(2.0);
glBegin(GL_LINE_LOOP);
for (i=0;i<2;i++) p[i]=q0[i]+(double(0)*eu[i])+(double(0)*ev[i]); glVertex2dv(p);
for (i=0;i<2;i++) p[i]=q0[i]+(double(n)*eu[i])+(double(0)*ev[i]); glVertex2dv(p);
for (i=0;i<2;i++) p[i]=q0[i]+(double(n)*eu[i])+(double(n)*ev[i]); glVertex2dv(p);
for (i=0;i<2;i++) p[i]=q0[i]+(double(0)*eu[i])+(double(n)*ev[i]); glVertex2dv(p);
glEnd();
glLineWidth(1.0);
// 2D minimap render of the pt[]
glColor3f(0.7,0.1,0.1); // trapeze
glBegin(GL_LINE_LOOP);
for (j=0;j<4;j++)
{
// get u,v from pt[j]
for (i=0;i<3;i++) p[i]=pt[j][i]-p0[i];
for (u=0.0,i=0;i<3;i++) u+=p[i]*du[i];
for (v=0.0,i=0;i<3;i++) v+=p[i]*dv[i];
// convert to 2D position and render
for (i=0;i<2;i++) p[i]=q0[i]+(u*eu[i])+(v*ev[i]); glVertex2dv(p);
}
glEnd();
// restore 3D view
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glViewport(vp0[0],vp0[1],vp0[2],vp0[3]);
glEnable(GL_DEPTH_TEST);
}
//---------------------------------------------------------------------------
И предварительный просмотр:
Как видите, для этого нам нужны только матричные * векторные умножения и псевдообратные матричные функции (все остальные, такие как dot,+,-
, действительно просты и напрямуюзакодированы как встроенный код), и оба достаточно просты, чтобы напрямую реализовать его в коде, поэтому нет необходимости в GLM или аналогичной библиотеке.
Также мне было лень обрезать 4-точечный многоугольник на миникартеразмер, поэтому вместо этого я использовал glViewport
, который сделал это для меня.
Здесь Win32 BDS2006VCL / C ++ / OpenGL1.0 Демонстрация:
Просто выберите медленная загрузка и введите код подтверждения с картинки.Он не использует сторонних библиотек, кроме GL, GLU .Камера статична, поэтому просто добавьте события клавиатуры / мыши по своему вкусу.Если вы хотите перенести это в вашу среду, просто имитируйте поведение событий и игнорируйте материал VCL .
Инициализация OpenGL выполняется на основе этого:
Я только что удалил из него GLEW, GLSL и VAO .