Всё, заработало. Разобрался с тем, что делают в Quake-1. Фишка вот какая - GU нужно чтобы вершины после проекции попадали в видимое окно. Иными словами, нужно отсечь вершины полигонов по плоскостям, ограничивающим вид игрока слева, справа, снизу и сверху. А передняя плоскость отсечётся автоматом.
Как это сделать? Считать три матрицы - GU_PROJECTION, GU_MODEL, GU_VIEW. Перемножить их и получить итоговую матрицу преобразования координат. Из этой матрицы можно вытащить все нужные ограничивающие вид плоскости (4 компоненты полученного вектора задают плоскость с уравнением ax+by+cz+w=0). (a,b,c) - вектор нормали, а w=a*x0+b*y0+c*z0 - характеризует некую точку (x0,y0,z0) плоскости. Сами координаты точки нам не нужны - достаточно знать w.
Вычисляется это всё так:
Функции для перевода вектора в нормализованное представление (x,y,z,w) и умножения матрицы на матрицу:
//----------------------------------------------------------------------------------------------------
//умножение двух матриц типа ScePspFMatrix4
//----------------------------------------------------------------------------------------------------
void CMain::MultiplyScePspFMatrix4(const ScePspFMatrix4& a,const ScePspFMatrix4& b,ScePspFMatrix4& out)
{
out.x.x=a.x.x*b.x.x+a.x.y*b.y.x+a.x.z*b.z.x+a.x.w*b.w.x;
out.x.y=a.x.x*b.x.y+a.x.y*b.y.y+a.x.z*b.z.y+a.x.w*b.w.y;
out.x.z=a.x.x*b.x.z+a.x.y*b.y.z+a.x.z*b.z.z+a.x.w*b.w.z;
out.x.w=a.x.x*b.x.w+a.x.y*b.y.w+a.x.z*b.z.w+a.x.w*b.w.w;
out.y.x=a.y.x*b.x.x+a.y.y*b.y.x+a.y.z*b.z.x+a.y.w*b.w.x;
out.y.y=a.y.x*b.x.y+a.y.y*b.y.y+a.y.z*b.z.y+a.y.w*b.w.y;
out.y.z=a.y.x*b.x.z+a.y.y*b.y.z+a.y.z*b.z.z+a.y.w*b.w.z;
out.y.w=a.y.x*b.x.w+a.y.y*b.y.w+a.y.z*b.z.w+a.y.w*b.w.w;
out.z.x=a.z.x*b.x.x+a.z.y*b.y.x+a.z.z*b.z.x+a.z.w*b.w.x;
out.z.y=a.z.x*b.x.y+a.z.y*b.y.y+a.z.z*b.z.y+a.z.w*b.w.y;
out.z.z=a.z.x*b.x.z+a.z.y*b.y.z+a.z.z*b.z.z+a.z.w*b.w.z;
out.z.w=a.z.x*b.x.w+a.z.y*b.y.w+a.z.z*b.z.w+a.z.w*b.w.w;
out.w.x=a.w.x*b.x.x+a.w.y*b.y.x+a.w.z*b.z.x+a.w.w*b.w.x;
out.w.y=a.w.x*b.x.y+a.w.y*b.y.y+a.w.z*b.z.y+a.w.w*b.w.y;
out.w.z=a.w.x*b.x.z+a.w.y*b.y.z+a.w.z*b.z.z+a.w.w*b.w.z;
out.w.w=a.w.x*b.x.w+a.w.y*b.y.w+a.w.z*b.z.w+a.w.w*b.w.w;
}
//----------------------------------------------------------------------------------------------------
//нормирование вектора типа ScePspFMatrix4
//----------------------------------------------------------------------------------------------------
void CMain::NormaliseScePspFVector4(ScePspFVector4& v)
{
const float norma=sqrtf(v.x*v.x+v.y*v.y+v.z*v.z);
const float scale=1.0f/norma;
v.x*=scale;
v.y*=scale;
v.z*=scale;
v.w*=scale;
}
//получаем матрицу проецирования
sceGumMatrixMode(GU_PROJECTION);
ScePspFMatrix4 projection_matrix;
sceGumStoreMatrix(&projection_matrix);
//получаем матрицу видового преобразования
sceGumMatrixMode(GU_VIEW);
ScePspFMatrix4 view_matrix;
sceGumStoreMatrix(&view_matrix);
//получаем матрицу моделирования
sceGumMatrixMode(GU_MODEL);
ScePspFMatrix4 model_matrix;
sceGumStoreMatrix(&model_matrix);
sceGuFinish();
//вычисляем общую матрицу view-projection
ScePspFMatrix4 projection_view_matrix;
MultiplyScePspFMatrix4(view_matrix,projection_matrix,projection_view_matrix);
//вычисляем общую матрицу view-projection-model
ScePspFMatrix4 projection_view_model_matrix;
MultiplyScePspFMatrix4(model_matrix,projection_view_matrix,projection_view_model_matrix);
//вычисляем матрицу view-model
ScePspFMatrix4 view_model_matrix;
MultiplyScePspFMatrix4(model_matrix,view_matrix,view_model_matrix);
//вычисляем четыре плоскости отсечения по проекции (верх, низ, лево, право)
ScePspFVector4 frustum[4];//четверка чисел описывает плоскость: ax+by+cz+d=0
//левая
frustum[0].x=projection_view_model_matrix.x.w+projection_view_model_matrix.x.x;
frustum[0].y=projection_view_model_matrix.y.w+projection_view_model_matrix.y.x;
frustum[0].z=projection_view_model_matrix.z.w+projection_view_model_matrix.z.x;
frustum[0].w=projection_view_model_matrix.w.w+projection_view_model_matrix.w.x;
NormaliseScePspFVector4(frustum[0]);
//правая
frustum[1].x=projection_view_model_matrix.x.w-projection_view_model_matrix.x.x;
frustum[1].y=projection_view_model_matrix.y.w-projection_view_model_matrix.y.x;
frustum[1].z=projection_view_model_matrix.z.w-projection_view_model_matrix.z.x;
frustum[1].w=projection_view_model_matrix.w.w-projection_view_model_matrix.w.x;
NormaliseScePspFVector4(frustum[1]);
//верхняя
frustum[2].x=projection_view_model_matrix.x.w-projection_view_model_matrix.x.y;
frustum[2].y=projection_view_model_matrix.y.w-projection_view_model_matrix.y.y;
frustum[2].z=projection_view_model_matrix.z.w-projection_view_model_matrix.z.y;
frustum[2].w=projection_view_model_matrix.w.w-projection_view_model_matrix.w.y;
NormaliseScePspFVector4(frustum[2]);
//нижняя
frustum[3].x=projection_view_model_matrix.x.w+projection_view_model_matrix.x.y;
frustum[3].y=projection_view_model_matrix.y.w+projection_view_model_matrix.y.y;
frustum[3].z=projection_view_model_matrix.z.w+projection_view_model_matrix.z.y;
frustum[3].w=projection_view_model_matrix.w.w+projection_view_model_matrix.w.y;
NormaliseScePspFVector4(frustum[3]);
Теперь у нас в массиве векторов frustum заданы плоскости отсечения.
А дальше надо просто найти пересечения сторон полигона с плоскостями и посчитать новые точки, а старые (которые вне плоскостей) выкинуть. Это делается так:
//----------------------------------------------------------------------------------------------------
//получить точку пересечения прямой и плоскости
//----------------------------------------------------------------------------------------------------
void CMain::GetIntersectionPlaneAndLine(const SGuNVCTPoint& A,const SGuNVCTPoint& B,SGuNVCTPoint& new_point,float nx,float ny,float nz,float w)
{
new_point=A;
float ax=A.sGuVertex.X;
float ay=A.sGuVertex.Y;
float az=A.sGuVertex.Z;
float au=A.sGuTexture.U;
float av=A.sGuTexture.V;
float bx=B.sGuVertex.X;
float by=B.sGuVertex.Y;
float bz=B.sGuVertex.Z;
float bu=B.sGuTexture.U;
float bv=B.sGuTexture.V;
float dx=bx-ax;
float dy=by-ay;
float dz=bz-az;
float du=bu-au;
float dv=bv-av;
float top=(nx*ax)+(ny*ay)+(nz*az)+w;
float bottom=(nx*dx)+(ny*dy)+(nz*dz);
float time=-top/bottom;
float vx=ax+time*dx;
float vy=ay+time*dy;
float vz=az+time*dz;
float vu=au+time*du;
float vv=av+time*dv;
//добавляем новую точку
SetVertexCoord(new_point.sGuVertex,vx,vy,vz);
SetTextureCoord(new_point.sGuTexture,vu,vv);
}
//----------------------------------------------------------------------------------------------------
//выполнить коррекцию координат
//----------------------------------------------------------------------------------------------------
void CMain::Clip(const vector<SGuNVCTPoint>& vector_point_input,vector<SGuNVCTPoint>& vector_point_output,float nx,float ny,float nz,float w)
{
vector_point_output.clear();
long point=vector_point_input.size();
for(long n=0;n<point;n++)
{
long next_p=n+1;
if (next_p>=point) next_p-=point;
const SGuNVCTPoint *sGuNVCTPoint_Current_Ptr=&(vector_point_input[n]);
float current_vx=sGuNVCTPoint_Current_Ptr->sGuVertex.X;
float current_vy=sGuNVCTPoint_Current_Ptr->sGuVertex.Y;
float current_vz=sGuNVCTPoint_Current_Ptr->sGuVertex.Z;
//определяем положение относительно плоскости отсечения
float current_ret=current_vx*nx+current_vy*ny+current_vz*nz+w;
const SGuNVCTPoint *sGuNVCTPoint_Next_Ptr=&(vector_point_input[next_p]);
float next_vx=sGuNVCTPoint_Next_Ptr->sGuVertex.X;
float next_vy=sGuNVCTPoint_Next_Ptr->sGuVertex.Y;
float next_vz=sGuNVCTPoint_Next_Ptr->sGuVertex.Z;
//определяем положение относительно плоскости отсечения
float next_ret=next_vx*nx+next_vy*ny+next_vz*nz+w;
if (current_ret>0)//текущая точка видима
{
if (next_ret>0)//следующая точка видима
{
vector_point_output.push_back(*sGuNVCTPoint_Next_Ptr);
}
else
{
//добавляем новую точку пересечения
SGuNVCTPoint sGuNVCTPoint_New;
GetIntersectionPlaneAndLine(*sGuNVCTPoint_Current_Ptr,*sGuNVCTPoint_Next_Ptr,sGuNVCTPoint_New,nx,ny,nz,w);
vector_point_output.push_back(sGuNVCTPoint_New);
}
}
else//текущая точка не видна
{
if (next_ret>0)//следующая точка видна
{
//добавляем новую точку пересечения
SGuNVCTPoint sGuNVCTPoint_New;
GetIntersectionPlaneAndLine(*sGuNVCTPoint_Current_Ptr,*sGuNVCTPoint_Next_Ptr,sGuNVCTPoint_New,nx,ny,nz,w);
vector_point_output.push_back(sGuNVCTPoint_New);
//добавляем сдудующую точку
vector_point_output.push_back(*sGuNVCTPoint_Next_Ptr);
}
}
}
}
Тогда рисовать можно вот так:
//#pragma pack(1)
//[for vertices(1-8)] [weights (0-8)] [texture uv] [color] [normal] [vertex] [/for]
#pragma pack(1)
struct SGuVertex
{
float X;
float Y;
float Z;
};
struct SGuNormal
{
float Nx;
float Ny;
float Nz;
};
struct SGuTexture
{
float U;
float V;
};
struct SGuColor
{
unsigned long Color;
};
#pragma pack()
#pragma pack(32)
struct SGuNVCTPoint
{
SGuTexture sGuTexture;
SGuColor sGuColor;
SGuNormal sGuNormal;
SGuVertex sGuVertex;
};
#pragma pack()
//задаём геометрию
SGuNVCTPoint sGuNVCTPoint;
vector<SGuNVCTPoint> vector_point;
SetVertexCoord(sGuNVCTPoint.sGuVertex,-100,100,0);
SetTextureCoord(sGuNVCTPoint.sGuTexture,0,0);
SetNormalCoord(sGuNVCTPoint.sGuNormal,0,0,1);
SetColorValue(sGuNVCTPoint.sGuColor,0xFFFFFFFF);
vector_point.push_back(sGuNVCTPoint);
SetVertexCoord(sGuNVCTPoint.sGuVertex,100,100,0);
SetTextureCoord(sGuNVCTPoint.sGuTexture,1,0);
SetNormalCoord(sGuNVCTPoint.sGuNormal,0,0,1);
SetColorValue(sGuNVCTPoint.sGuColor,0xFFFFFFFF);
vector_point.push_back(sGuNVCTPoint);
SetVertexCoord(sGuNVCTPoint.sGuVertex,100,-100,0);
SetTextureCoord(sGuNVCTPoint.sGuTexture,1,1);
SetNormalCoord(sGuNVCTPoint.sGuNormal,0,0,1);
SetColorValue(sGuNVCTPoint.sGuColor,0xFFFFFFFF);
vector_point.push_back(sGuNVCTPoint);
SetVertexCoord(sGuNVCTPoint.sGuVertex,-100,-100,0);
SetTextureCoord(sGuNVCTPoint.sGuTexture,0,1);
SetNormalCoord(sGuNVCTPoint.sGuNormal,0,0,1);
SetColorValue(sGuNVCTPoint.sGuColor,0xFFFFFFFF);
vector_point.push_back(sGuNVCTPoint);
//выполняем отсечение
vector<SGuNVCTPoint> vector_clip_point;
for(long n=0;n<4;n++)
{
float nx=frustum[n].x;
float ny=frustum[n].y;
float nz=frustum[n].z;
float w=frustum[n].w;
Clip(vector_point,vector_clip_point,nx,ny,nz,w);
vector_point=vector_clip_point;
}
//теперь рисуем
sceGuStart(GU_DIRECT,DisplayList);
//выводим прямоугольник
sceGuColor(0xffffffff);
//вершина с нормалью, текстурой, цветом и координатами
sceGuEnable(GU_TEXTURE_2D);
sceGuTexMode(GU_PSM_8888,0,0,0);
sceGuTexImage(0,WidthImage,HeightImage,WidthImage,Data);
sceGuTexFunc(GU_TFX_MODULATE,GU_TCC_RGBA);
sceGuTexFilter(GU_NEAREST,GU_NEAREST);
sceGuTexWrap(GU_REPEAT,GU_REPEAT);
sceGuTexScale(1,1);
sceGuTexOffset(0,0);
long vertex_amount=vector_point.size();
SGuNVCTPoint *sGuNVCTPoint_Ptr=(SGuNVCTPoint*)sceGuGetMemory(vertex_amount*sizeof(SGuNVCTPoint));
if (sGuNVCTPoint_Ptr!=NULL)
{
for(long n=0;n<vertex_amount;n++) sGuNVCTPoint_Ptr[n]=vector_point[n];
sceGumDrawArray(GU_TRIANGLE_FAN,GU_COLOR_8888|GU_VERTEX_32BITF|GU_TRANSFORM_3D|GU_NORMAL_32BITF|GU_TEXTURE_32BITF,vertex_amount,0,sGuNVCTPoint_Ptr);
}
sceGuDisable(GU_TEXTURE_2D);
//запускаем список на выполнение
sceGuFinish();
sceGuSync(GU_SYNC_WAIT,GU_SYNC_FINISH);
Можно, кстати, векторный процессор PSP использовать для скалярного умножения векторов:
//выполняем отсечение
vector<SGuNVCTPoint> vector_clip_point;
//используем векторный процессор PSP
__asm__ volatile
(
"ulv.q C700, %0\n" //загружаем вектор в регистр
"ulv.q C710, %1\n" //загружаем вектор в регистр
"ulv.q C720, %2\n" //загружаем вектор в регистр
"ulv.q C730, %3\n" //загружаем вектор в регистр
:: "m"(FrustumPlane[0]),"m"(FrustumPlane[1]),"m"(FrustumPlane[2]),"m"(FrustumPlane[3])
);
//проверим необходимость отсечения
long vertex=vector_point.size();
bool clipping=false;
for(long n=0;n<vertex;n++)
{
ScePspFVector4 current_vertex;
current_vertex.x=vector_point[n].sGuVertex.X;
current_vertex.y=vector_point[n].sGuVertex.Y;
current_vertex.z=vector_point[n].sGuVertex.Z;
current_vertex.w=1;
float ret1,ret2,ret3,ret4;
__asm__ volatile
(
"ulv.q C610, %4\n" // загружаем вектор вершины в регистр
"vone.s S613\n" // Now set the 4th entry to be 1 as that is just random
"vdot.q S620, C700, C610\n" // s620 = вычисляем скалярное произведение
"vdot.q S621, C710, C610\n" // s621 = вычисляем скалярное произведение
"vdot.q S622, C720, C610\n" // s622 = вычисляем скалярное произведение
"vdot.q S623, C730, C610\n" // s623 = вычисляем скалярное произведение
"mfv %0, S620\n" // out1 = s620
"mfv %1, S621\n" // out2 = s621
"mfv %2, S622\n" // out3 = s622
"mfv %3, S623\n" // out4 = s623
: "=r"(ret1), "=r"(ret2), "=r"(ret3), "=r"(ret4) : "m"(current_vertex)
);
if (ret1<0 || ret2<0 || ret3<0 || ret4<0)//требуется отсечение
{
clipping=true;
break;
}
}
Но эта функция из движка, а не из приводимой ниже программы. Взял я этот код процессора из того же Quake-1.
Движок под GU тоже заработал. Только нифига ускорения не вышло - тормоза одни. Причём, при отключённом текстурировании скорость резко возрастает. Может, нужно как-то текстуру в видеопамять перебросить.
Вот сама демонстрационная программа (не забудьте скопировать текстуру в каталог с eboot.pbp):