Пару месяцев назад я вновь достал из ящика запылившуюся PSP и решил портировать туда мой ранее уже показанный движок. С программной отрисовкой проблем не возникло – всё и так работает. А вот с использованием GU всё оказалось не так просто. В данной статье я покажу на примере, как можно написать для PSP простое трёхмерное приложение, использующее GU.
Заранее предупреждаю, что руководств по программированию для PSP довольно мало, и поэтому какие-то мои выводы могут оказаться неверными. Но, к делу.
Главная функция программы для PSP, если кто не знает, оформляется примерно вот так:
Инициализация GU выполняется следующим образом:
Сначала мы запрашиваем указатели на три буфера – экранный, внеэкранный и буфер глубины (Z-буфер). Буферы выравниваются по 512 пикселей в строке (хотя у PSP строка 480 пикселей). Также требуется учесть формат цвета пикселя. В данном примере использован формат GU_PSM_8888 — по 8 бит на R,G,B и Alpha-компоненты цвета пикселя. Для Z-буффера использован формат GU_PSM_4444 просто потому что это 16 бит — Z-буфер у PSP 16 битный.
Функция запроса указателей на буферы определяется как
Это не мои функции – я их взял из какой-то программы давным-давно и лишь слегка изменил. Память распределяется в области видеопамяти. Текстуры также следует по возможности размещать там же, запрашивая указатель через getStaticVramTexture, иначе быстродействие резко упадёт. Разумеется, никакой динамической памяти при таких запросах не выделяется, а просто распределяется часть заданного адресного пространства PSP под экран и текстуры. Насколько я помню, видеопамяти у PSP всего 2 мегабайта — это очень мало для хранения множества текстур.
Программирование GU у PSP похоже на программирование для OpenGL с одним отличием — выполнение команд требует их размещения в дисплейном списке, причём память для этого списка должна быть заранее выделена и при этом выровнена:
static unsigned char __attribute__((aligned(16))) DisplayList[262144];
Команды, относящиеся к преобразованию координат не требуют дисплейного списка и могут выполняться в любом месте программы.
Инициализировать GU можно, например, так:
После завершения работы с GU следует вызвать sceGuTerm().
После загрузки текстуры размера (WidthImage; HeightImage) любым удобным способом (указатель Data на данные текстур�� – и лучше его получать в области видеопамяти), мы можем её вывести на экран.
Как вывести полигон? Для рисования геометрии GU у PSP просит поместить все точки в массив, указатель на который нужно предварительно получить командой sceGuGetMemory, передав ей размер запрашиваемого блока памяти в байтах. Дальше по этому указателю вы должны записать массив точек и попросить PSP их вывести, например, командой sceGumDrawArray с нужными параметрами. Но каков формат этих точек? У PSP данные точек располагаются в определённом порядке и размер массива, описывающего одну точку должен быть кратен 32 байтам: Вес вершины, текстурные координаты, цвет точки, нормаль к точке, координата точки. Именно в таком порядке. Чтобы не заморачиваться с форматом, я определил набор ��труктур и функций для работы с ними:
Тогда задать геометрию (в данном случае — квадрат) можно, например, так:
А вывести его, например, так:
Для вывода я указал функции sceGumDrawArray, что именно я рисую и каков формат точки ( GU_COLOR_8888|GU_VERTEX_32BITF|GU_TRANSFORM_3D|GU_NORMAL_32BITF|GU_TEXTURE_32BITF — точка состоит из цвета, координат, нормали, текстурных координат и требует перемножения координат на соответствующие матрицы перед рисованием). Рисование возможно только треугольниками. Но это ещё не всё…
Вроде бы всё работает, но работает только, если все точки находятся перед глазами и видимы. Стоит хотя бы одной точке уйти в какую-то туманную даль, как GU отказывается рисовать весь многоугольник. Как я понимаю, GU у PSP требует, чтобы относительно четырёх плоскостей отсечения (левая, правая, верхняя и нижняя (а передняя получится автоматически)) точка лежала внутри этого объёма, иначе GU не согласен её выводить. Проблема. Но в играх-то 3D-графика присутствует и таких артефактов не наблюдается! Давайте посмотрим, как решили эту проблему в PSP Quake 1, благо исходники доступны для анализа.
Что же мы видим из анализа исходников? А по сути вот что:
То есть, в Quake 1 перед выводом просто переносят все точки внутрь объёма, ограничивающего взгляд, либо выбрасывают их вовсе (если вся фигура не видна). Как же это сделать? Нужно просто считать три матрицы — 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.
Отсечение выполняется следующим образом (для четырёх вышеуказанных плоскостей по-очереди в цикле):
Но для такого фокуса нам потребуются следующие функции (списанные из Quake 1):
И вот только после выполнения такого отсечения у вас, наконец-таки, корректно заработает вывод трёхмерной графики на PSP с помощью GU. Можете создавать игру! :)

Кстати, можно также использовать для скалярного произведения векторов векторный процессор PSP. Например, вот функция, определяющая требуется ли вообще отсечение (выдранная по кусочкам из того же Quake 1 для PSP):
Тут всё просто — поместили вектора плоскостей и координаты точки в регистры и попросили VFPU выполнить скалярное произведение.
→ Ссылка на простейшее приложение, выводящее текстуру
→ Ссылка на движок для PSP с использованием GU
P.S. Я знаю, тут бывают профессионалы по программированию для PSP. Может быть они расскажут, почему GU у PSP так устроен и как правильно работать с ним.
Заранее предупреждаю, что руководств по программированию для PSP довольно мало, и поэтому какие-то мои выводы могут оказаться неверными. Но, к делу.
Главная функция программы для PSP, если кто не знает, оформляется примерно вот так:
#include <pspkernel.h> #include <pspdebug.h> #include <pspdisplay.h> //---------------------------------------------------------------------------------------- PSP_MODULE_INFO("GUTexture", 0, 1, 1); PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER|THREAD_ATTR_VFPU); void dump_threadstatus(void); bool done=false; int exit_callback(int arg1,int arg2,void *common) { done=true; return(0); } int CallbackThread(SceSize args, void *argp) { int cbid; cbid=sceKernelCreateCallback("Exit Callback",exit_callback,NULL); sceKernelRegisterExitCallback(cbid); sceKernelSleepThreadCB(); return(0); } int SetupCallbacks(void) { int thid = 0; thid=sceKernelCreateThread("update_thread",CallbackThread,0x11,0xFA0,0,0); if(thid>=0) sceKernelStartThread(thid, 0, 0); return(thid); } //---------------------------------------------------------------------------------------- //начинаем программу //---------------------------------------------------------------------------------------- int main(int argc, char **argv) { pspDebugScreenInit(); //устанавливаем обработчики SetupCallbacks(); //выполняем программу ………. //выходим из программы sceKernelExitGame(); return(0); }
Инициализация GU выполняется следующим образом:
Сначала мы запрашиваем указатели на три буфера – экранный, внеэкранный и буфер глубины (Z-буфер). Буферы выравниваются по 512 пикселей в строке (хотя у PSP строка 480 пикселей). Также требуется учесть формат цвета пикселя. В данном примере использован формат GU_PSM_8888 — по 8 бит на R,G,B и Alpha-компоненты цвета пикселя. Для Z-буффера использован формат GU_PSM_4444 просто потому что это 16 бит — Z-буфер у PSP 16 битный.
//размеры экрана #define SCREEN_WIDTH 480 #define SCREEN_HEIGHT 272 #define SCREEN_LINE_WIDTH 512 void* fbp0=getStaticVramBuffer(SCREEN_LINE_WIDTH, SCREEN_HEIGHT,GU_PSM_8888); void* fbp1=getStaticVramBuffer(SCREEN_LINE_WIDTH, SCREEN_HEIGHT,GU_PSM_8888); void* zbp=getStaticVramBuffer(SCREEN_LINE_WIDTH, SCREEN_HEIGHT,GU_PSM_4444);
Функция запроса указателей на буферы определяется как
#include <pspge.h> #include <pspgu.h> static unsigned int staticOffset=0; static unsigned int getMemorySize(unsigned int width,unsigned int height,unsigned int psm) { switch (psm) { case GU_PSM_T4: return((width*height)>>1); case GU_PSM_T8: return(width*height); case GU_PSM_5650: case GU_PSM_5551: case GU_PSM_4444: case GU_PSM_T16: return(2*width*height); case GU_PSM_8888: case GU_PSM_T32: return(4*width*height); default: return(0); } } void* getStaticVramBuffer(unsigned int width,unsigned int height,unsigned int psm) { unsigned int memSize=getMemorySize(width,height,psm); void* result=(void*)staticOffset; staticOffset+=memSize; return(result); } void* getStaticVramTexture(unsigned int width,unsigned int height,unsigned int psm) { void* result=getStaticVramBuffer(width,height,psm); return((void*)(((unsigned int)result) + ((unsigned int)sceGeEdramGetAddr()))); }
Это не мои функции – я их взял из какой-то программы давным-давно и лишь слегка изменил. Память распределяется в области видеопамяти. Текстуры также следует по возможности размещать там же, запрашивая указатель через getStaticVramTexture, иначе быстродействие резко упадёт. Разумеется, никакой динамической памяти при таких запросах не выделяется, а просто распределяется часть заданного адресного пространства PSP под экран и текстуры. Насколько я помню, видеопамяти у PSP всего 2 мегабайта — это очень мало для хранения множества текстур.
Программирование GU у PSP похоже на программирование для OpenGL с одним отличием — выполнение команд требует их размещения в дисплейном списке, причём память для этого списка должна быть заранее выделена и при этом выровнена:
static unsigned char __attribute__((aligned(16))) DisplayList[262144];
Команды, относящиеся к преобразованию координат не требуют дисплейного списка и могут выполняться в любом месте программы.
Инициализировать GU можно, например, так:
//размер сторон виртуального экрана PSP #define VIRTUAL_SCREEN_SIZE 2048 //соотношение сторон экрана #define SCREEN_ASPECT 16.0f/9.0f //передняя плоскость отсечения #define NEAR_PLANE_Z 5.0f //задняя плоскость отсечения #define FAR_PLANE_Z 4096.0f //угол зрения #define EYE_ANGLE 60.0f //инициализируем графику GU sceGuInit(); //создаём и запускаем на выполнение новый контекст дисплея - он должен выполниться сразу, т.к. GU_DIRECT sceGuStart(GU_DIRECT,DisplayList); //устанавливаем параметры буфера рисования- формат пикселя, указатель на область видеопамяти, длину строки (выровненную, а не физическую) sceGuDrawBuffer(GU_PSM_8888,fbp0,SCREEN_LINE_WIDTH); //устанавливаем параметры буфера экрана - размер экрана, указатель на видеопамять, длину строки sceGuDispBuffer(SCREEN_WIDTH,SCREEN_HEIGHT,fbp1,SCREEN_LINE_WIDTH); //устанавливаем параметры буфера глубины- указатель на начало буфера глубины в видеопамяти и длину строки sceGuDepthBuffer(zbp,SCREEN_LINE_WIDTH); //устанавливаем смещение экрана в общем пространстве 4096x4096 (в PSP такой размер виртуального экрана) sceGuOffset(VIRTUAL_SCREEN_SIZE-(SCREEN_WIDTH/2),VIRTUAL_SCREEN_SIZE-(SCREEN_HEIGHT/2));//ставим по центру //настраиваем видовой порт - порт просмотра- координаты центра и размеры сторон sceGuViewport(VIRTUAL_SCREEN_SIZE,VIRTUAL_SCREEN_SIZE,SCREEN_WIDTH,SCREEN_HEIGHT); //устанавливаем диапазон значений для буфера глубины - передняя и задняя плоскости отсечения (буфер инвертирован и значения от 0 до 65535 !) sceGuDepthRange(65535,0); //включаем обрезание области показа по размерам видового порта sceGuScissor(0,0,SCREEN_WIDTH,SCREEN_HEIGHT); sceGuEnable(GU_SCISSOR_TEST); sceGuEnable(GU_CLIP_PLANES); //настроим матрицу проецирования sceGumMatrixMode(GU_PROJECTION); sceGumLoadIdentity(); sceGumPerspective(EYE_ANGLE,SCREEN_ASPECT,NEAR_PLANE_Z,FAR_PLANE_Z); //включим режим гладкой интерполяции цвета граней sceGuShadeModel(GU_SMOOTH); //включим тест глубины sceGuDepthFunc(GU_GEQUAL); sceGuEnable(GU_DEPTH_TEST); sceGuDepthMask(GU_FALSE); //отключим режим отсечения граней, повёрнутых обратной стороной к наблюдателю sceGuFrontFace(GU_CCW); sceGuDisable(GU_CULL_FACE); //настраиваем прозрачность sceGuDisable(GU_BLEND); sceGuBlendFunc(GU_ADD,GU_SRC_ALPHA,GU_ONE_MINUS_SRC_ALPHA,0,0); //выполняем созданный список sceGuFinish(); sceGuSync(GU_SYNC_WAIT,GU_SYNC_FINISH); sceGuDisplay(GU_TRUE);
После завершения работы с GU следует вызвать sceGuTerm().
После загрузки текстуры размера (WidthImage; HeightImage) любым удобным способом (указатель Data на данные текстур�� – и лучше его получать в области видеопамяти), мы можем её вывести на экран.
//рисуем сцену sceGuStart(GU_DIRECT,DisplayList); //очистим экран и буфер глубины sceGuClearColor(0); sceGuClearDepth(0); sceGuClear(GU_COLOR_BUFFER_BIT|GU_DEPTH_BUFFER_BIT); //настроим матрицу проецирования sceGumMatrixMode(GU_PROJECTION); sceGumLoadIdentity(); sceGumPerspective(EYE_ANGLE,SCREEN_ASPECT,NEAR_PLANE_Z,FAR_PLANE_Z); sceGumUpdateMatrix();. //инициализируем матрицы sceGumMatrixMode(GU_TEXTURE); sceGumLoadIdentity(); sceGumMatrixMode(GU_VIEW); sceGumLoadIdentity(); sceGumMatrixMode(GU_MODEL); sceGumLoadIdentity(); //выводим прямоугольник с текстурой 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); //выводим полигон по точкам из массива … sceGuDisable(GU_TEXTURE_2D); //запускаем список на выполнение sceGuFinish(); sceGuSync(GU_SYNC_WAIT,GU_SYNC_FINISH); //делаем видимым буфер, в котором мы рисовали sceDisplayWaitVblankStart(); sceGuSwapBuffers();
Как вывести полигон? Для рисования геометрии GU у PSP просит поместить все точки в массив, указатель на который нужно предварительно получить командой sceGuGetMemory, передав ей размер запрашиваемого блока памяти в байтах. Дальше по этому указателю вы должны записать массив точек и попросить PSP их вывести, например, командой sceGumDrawArray с нужными параметрами. Но каков формат этих точек? У PSP данные точек располагаются в определённом порядке и размер массива, описывающего одну точку должен быть кратен 32 байтам: Вес вершины, текстурные координаты, цвет точки, нормаль к точке, координата точки. Именно в таком порядке. Чтобы не заморачиваться с форматом, я определил набор ��труктур и функций для работы с ними:
//#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() void SetVertexCoord(SGuVertex &sGuVertex,float x,float y,float z);//задать координаты вершины void SetNormalCoord(SGuNormal &sGuNormal,float nx,float ny,float nz);//задать координаты нормали void SetTextureCoord(SGuTexture &sGuTexture,float u,float v);//задать координаты текстуры void SetColorValue(SGuColor &sGuColor,unsigned long color);//задать цвет //---------------------------------------------------------------------------------------------------- //задать координаты вершины //---------------------------------------------------------------------------------------------------- void CMain::SetVertexCoord(SGuVertex &sGuVertex,float x,float y,float z) { sGuVertex.X=x; sGuVertex.Y=y; sGuVertex.Z=z; } //---------------------------------------------------------------------------------------------------- //задать координаты нормали //---------------------------------------------------------------------------------------------------- void CMain::SetNormalCoord(SGuNormal &sGuNormal,float nx,float ny,float nz) { sGuNormal.Nx=nx; sGuNormal.Ny=ny; sGuNormal.Nz=nz; } //---------------------------------------------------------------------------------------------------- //задать координаты текстуры //---------------------------------------------------------------------------------------------------- void CMain::SetTextureCoord(SGuTexture &sGuTexture,float u,float v) { sGuTexture.U=u; sGuTexture.V=v; } //---------------------------------------------------------------------------------------------------- //задать цвет //---------------------------------------------------------------------------------------------------- void CMain::SetColorValue(SGuColor &sGuColor,unsigned long color) { sGuColor.Color=color; }
Тогда задать геометрию (в данном случае — квадрат) можно, например, так:
//задаём геометрию 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);
А вывести его, например, так:
size_t vertex_amount=vector_point.size(); SGuNVCTPoint *sGuNVCTPoint_Ptr=(SGuNVCTPoint*)sceGuGetMemory(vertex_amount*sizeof(SGuNVCTPoint)); if (sGuNVCTPoint_Ptr!=NULL) { for(size_t 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); }
Для вывода я указал функции sceGumDrawArray, что именно я рисую и каков формат точки ( GU_COLOR_8888|GU_VERTEX_32BITF|GU_TRANSFORM_3D|GU_NORMAL_32BITF|GU_TEXTURE_32BITF — точка состоит из цвета, координат, нормали, текстурных координат и требует перемножения координат на соответствующие матрицы перед рисованием). Рисование возможно только треугольниками. Но это ещё не всё…
Вроде бы всё работает, но работает только, если все точки находятся перед глазами и видимы. Стоит хотя бы одной точке уйти в какую-то туманную даль, как GU отказывается рисовать весь многоугольник. Как я понимаю, GU у PSP требует, чтобы относительно четырёх плоскостей отсечения (левая, правая, верхняя и нижняя (а передняя получится автоматически)) точка лежала внутри этого объёма, иначе GU не согласен её выводить. Проблема. Но в играх-то 3D-графика присутствует и таких артефактов не наблюдается! Давайте посмотрим, как решили эту проблему в PSP Quake 1, благо исходники доступны для анализа.
Что же мы видим из анализа исходников? А по сути вот что:
//получаем матрицу проецирования 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]);
То есть, в Quake 1 перед выводом просто переносят все точки внутрь объёма, ограничивающего взгляд, либо выбрасывают их вовсе (если вся фигура не видна). Как же это сделать? Нужно просто считать три матрицы — 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.
Отсечение выполняется следующим образом (для четырёх вышеуказанных плоскостей по-очереди в цикле):
//выполняем отсечение 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; }
Но для такого фокуса нам потребуются следующие функции (списанные из Quake 1):
//---------------------------------------------------------------------------------------------------- //получить точку пересечения прямой и плоскости //---------------------------------------------------------------------------------------------------- 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); } } } }
И вот только после выполнения такого отсечения у вас, наконец-таки, корректно заработает вывод трёхмерной графики на PSP с помощью GU. Можете создавать игру! :)

Кстати, можно также использовать для скалярного произведения векторов векторный процессор PSP. Например, вот функция, определяющая требуется ли вообще отсечение (выдранная по кусочкам из того же Quake 1 для 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" // ставим единицу в четвёртой компоненте вектора "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; } }
Тут всё просто — поместили вектора плоскостей и координаты точки в регистры и попросили VFPU выполнить скалярное произведение.
→ Ссылка на простейшее приложение, выводящее текстуру
→ Ссылка на движок для PSP с использованием GU
P.S. Я знаю, тут бывают профессионалы по программированию для PSP. Может быть они расскажут, почему GU у PSP так устроен и как правильно работать с ним.
