Пару месяцев назад я вновь достал из ящика запылившуюся PSP и решил портировать туда мой ранее уже показанный движок. С программной отрисовкой проблем не возникло – всё и так работает. А вот с использованием GU всё оказалось не так просто. В данной статье я покажу на примере, как можно написать для PSP простое трёхмерное приложение, использующее GU.
Заранее предупреждаю, что руководств по программированию для 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, %0n" //загружаем вектор в регистр
"ulv.q C710, %1n" //загружаем вектор в регистр
"ulv.q C720, %2n" //загружаем вектор в регистр
"ulv.q C730, %3n" //загружаем вектор в регистр
:: "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, %4n" // загружаем вектор вершины в регистр
"vone.s S613n" // ставим единицу в четвёртой компоненте вектора
"vdot.q S620, C700, C610n" // s620 = вычисляем скалярное произведение
"vdot.q S621, C710, C610n" // s621 = вычисляем скалярное произведение
"vdot.q S622, C720, C610n" // s622 = вычисляем скалярное произведение
"vdot.q S623, C730, C610n" // s623 = вычисляем скалярное произведение
"mfv %0, S620n" // out1 = s620
"mfv %1, S621n" // out2 = s621
"mfv %2, S622n" // out3 = s622
"mfv %3, S623n" // 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 так устроен и как правильно работать с ним.
Автор:
font-face{font-family:'Fira Sans';font-style:normal;font-weight:500;src:url(/fonts/0/FiraSans/firaSans-medium.eot);src:local("Fira Sans Medium"),local("FiraSans-Medium"),url(/fonts/0/FiraSans/firaSans-medium.eot?#iefix) format("embedded-opentype"),url(/fonts/0/FiraSans/firaSans-medium.woff2) format("woff2"),url(/fonts/0/FiraSans/firaSans-medium.woff) format("woff"),url(/fonts/0/FiraSans/firaSans-medium.ttf) format("truetype")}
/* Font Face Observer v2.0.13 - © Bram Stein. License: BSD-3-Clause */(function(){'use strict';var f,g=[];function l(a){g.push(a);1==g.length&&f()}function m(){for(;g.length;)g[0](),g.shift()}f=function(){setTimeout(m)};function n(a){this.a=p;this.b=void 0;this.f=[];var b=this;try{a(function(a){q(b,a)},function(a){r(b,a)})}catch(c){r(b,c)}}var p=2;function t(a){return new n(function(b,c){c(a)})}function u(a){return new n(function(b){b(a)})}function q(a,b){if(a.a==p){if(b==a)throw new TypeError;var c=!1;try{var d=b&&b.then;if(null!=b&&"object"==typeof b&&"function"==typeof d){d.call(b,function(b){c||q(a,b);c=!0},function(b){c||r(a,b);c=!0});return}}catch(e){c||r(a,e);return}a.a=0;a.b=b;v(a)}}
function r(a,b){if(a.a==p){if(b==a)throw new TypeError;a.a=1;a.b=b;v(a)}}function v(a){l(function(){if(a.a!=p)for(;a.f.length;){var b=a.f.shift(),c=b[0],d=b[1],e=b[2],b=b[3];try{0==a.a?"function"==typeof c?e(c.call(void 0,a.b)):e(a.b):1==a.a&&("function"==typeof d?e(d.call(void 0,a.b)):b(a.b))}catch(h){b(h)}}})}n.prototype.g=function(a){return this.c(void 0,a)};n.prototype.c=function(a,b){var c=this;return new n(function(d,e){c.f.push([a,b,d,e]);v(c)})};
function w(a){return new n(function(b,c){function d(c){return function(d){h[c]=d;e+=1;e==a.length&&b(h)}}var e=0,h=[];0==a.length&&b(h);for(var k=0;k<a.length;k+=1)u(a[k]).c(d(k),c)})}function x(a){return new n(function(b,c){for(var d=0;dparseInt(a[1],10)}else C=!1;return C}function J(){null===F&&(F=!!document.fonts);return F}
function K(){if(null===E){var a=document.createElement("div");try{a.style.font="condensed 100px sans-serif"}catch(b){}E=""!==a.style.font}return E}function L(a,b){return[a.style,a.weight,K()?a.stretch:"","100px",b].join(" ")}
A.prototype.load=function(a,b){var c=this,k=a||"BESbswy",q=0,D=b||3E3,H=(new Date).getTime();return new Promise(function(a,b){if(J()&&!G()){var M=new Promise(function(a,b){function e(){(new Date).getTime()-H>=D?b():document.fonts.load(L(c,'"'+c.family+'"'),k).then(function(c){1parseInt(b[1],10)||536===parseInt(b[1],10)&&11>=parseInt(b[2],10))),b=B&&(f==v&&g==v&&h==v||f==w&&g==w&&h==w||f==x&&g==x&&h==x)),b=!b;b&&(d.parentNode&&d.parentNode.removeChild(d),clearTimeout(q),a(c))}function I(){if((new Date).getTime()-H>=D)d.parentNode&&d.parentNode.removeChild(d),b(c);else{var a=document.hidden;if(!0===a||void 0===a)f=e.a.offsetWidth,
g=n.a.offsetWidth,h=p.a.offsetWidth,u();q=setTimeout(I,50)}}var e=new r(k),n=new r(k),p=new r(k),f=-1,g=-1,h=-1,v=-1,w=-1,x=-1,d=document.createElement("div");d.dir="ltr";t(e,L(c,"sans-serif"));t(n,L(c,"serif"));t(p,L(c,"monospace"));d.appendChild(e.a);d.appendChild(n.a);d.appendChild(p.a);document.body.appendChild(d);v=e.a.offsetWidth;w=n.a.offsetWidth;x=p.a.offsetWidth;I();z(e,function(a){f=a;u()});t(e,L(c,'"'+c.family+'",sans-serif'));z(n,function(a){g=a;u()});t(n,L(c,'"'+c.family+'",serif'));
z(p,function(a){h=a;u()});t(p,L(c,'"'+c.family+'",monospace'))})})};"object"===typeof module?module.exports=A:(window.FontFaceObserver=A,window.FontFaceObserver.prototype.load=A.prototype.load);}());
(function( w ){
if( w.document.documentElement.className.indexOf( "fonts-loaded" ) > -1 ){ return; }
var html = document.documentElement;
var FS500 = new w.FontFaceObserver("Fira Sans", { weight: 500 });
FS500.load().then(function() {
html.classList.add('fonts-loaded');
sessionStorage.fontsLoaded = true;
console.log('FS500-loaded');
}).catch(function () {
sessionStorage.fontsLoaded = false;
console.log('FS500-unloaded');
});
if (sessionStorage.fontsLoaded) {
html.classList.add('fonts-loaded');
}
}(this));
var N = 5; var ar_duo1 = Math.floor(Math.random()*N+1);
if (typeof adbl == 'undefined'){ var adbl = 'yes';}
var user_type = "guest";
var page_type = "publish_ugc";
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-726094-21', 'auto');
ga('create', 'UA-726094-24', 'auto', {'name': 'HGM'});
ga('require', 'displayfeatures');
ga('set', 'dimension1', user_type); // user type - guest/readonly/habrauser
ga('set', 'dimension2', adbl);
ga('set', 'dimension3', page_type);
(function() {
var win = window;
var removeUtms = function(){
var location = win.location;
if (location.search.indexOf('utm_') != -1 && history.replaceState) {
history.replaceState({}, '', window.location.toString().replace(/(&|?)utm([_a-z0-9=-]+)/g, ""));
}
};
ga('send', 'pageview', { 'hitCallback': removeUtms });
})();
ga('HGM.set', 'dimension1', user_type);
ga('HGM.set', 'dimension2', "geektimes");
ga('HGM.send', 'pageview');
var adcm_config ={
id:1034,
platformId: 34,
tags: ['hub_antikvariat', 'hub_programming', 'g_computer_hardware', 'g_programming'],
init: function () {
window.adcm.call();
}
};
вчера в 13:58
Отображение трёхмерной графики на PSP
Пару месяцев назад я вновь достал из ящика запылившуюся PSP и решил портировать туда мой ранее уже показанный движок. С программной отрисовкой проблем не возникло – всё и так работает. А вот с использованием GU всё оказалось не так просто. В данной статье я покажу на примере, как можно написать для PSP простое трёхмерное приложение, использующее GU.
Заранее предупреждаю, что руководств по программированию для 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, %0n" //загружаем вектор в регистр
"ulv.q C710, %1n" //загружаем вектор в регистр
"ulv.q C720, %2n" //загружаем вектор в регистр
"ulv.q C730, %3n" //загружаем вектор в регистр
:: "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, %4n" // загружаем вектор вершины в регистр
"vone.s S613n" // ставим единицу в четвёртой компоненте вектора
"vdot.q S620, C700, C610n" // s620 = вычисляем скалярное произведение
"vdot.q S621, C710, C610n" // s621 = вычисляем скалярное произведение
"vdot.q S622, C720, C610n" // s622 = вычисляем скалярное произведение
"vdot.q S623, C730, C610n" // s623 = вычисляем скалярное произведение
"mfv %0, S620n" // out1 = s620
"mfv %1, S621n" // out2 = s621
"mfv %2, S622n" // out3 = s622
"mfv %3, S623n" // 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 так устроен и как правильно работать с ним.
!function(e){function t(t,n){if(!(n in e)){for(var r,a=e.document,i=a.scripts,o=i.length;o--;)if(-1!==i[o].src.indexOf(t)){r=i[o];break}if(!r){r=a.createElement("script"),r.type="text/javascript",r.async=!0,r.defer=!0,r.src=t,r.charset="UTF-8";;var d=function(){var e=a.getElementsByTagName("script")[0];e.parentNode.insertBefore(r,e)};"[object Opera]"==e.opera?a.addEventListener?a.addEventListener("DOMContentLoaded",d,!1):e.attachEvent("onload",d):d()} } }t("//top-fwz1.mail.ru/js/code.js","_tmr"),t("//mediator.imgsmail.ru/2/mpf-mediator.min.js","_mediator")}(window);
Заранее предупреждаю, что руководств по программированию для 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, %0n" //загружаем вектор в регистр
"ulv.q C710, %1n" //загружаем вектор в регистр
"ulv.q C720, %2n" //загружаем вектор в регистр
"ulv.q C730, %3n" //загружаем вектор в регистр
:: "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, %4n" // загружаем вектор вершины в регистр
"vone.s S613n" // ставим единицу в четвёртой компоненте вектора
"vdot.q S620, C700, C610n" // s620 = вычисляем скалярное произведение
"vdot.q S621, C710, C610n" // s621 = вычисляем скалярное произведение
"vdot.q S622, C720, C610n" // s622 = вычисляем скалярное произведение
"vdot.q S623, C730, C610n" // s623 = вычисляем скалярное произведение
"mfv %0, S620n" // out1 = s620
"mfv %1, S621n" // out2 = s621
"mfv %2, S622n" // out3 = s622
"mfv %3, S623n" // 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 так устроен и как правильно работать с ним.
!function(e){function t(t,n){if(!(n in e)){for(var r,a=e.document,i=a.scripts,o=i.length;o--;)if(-1!==i[o].src.indexOf(t)){r=i[o];break}if(!r){r=a.createElement("script"),r.type="text/javascript",r.async=!0,r.defer=!0,r.src=t,r.charset="UTF-8";;var d=function(){var e=a.getElementsByTagName("script")[0];e.parentNode.insertBefore(r,e)};"[object Opera]"==e.opera?a.addEventListener?a.addEventListener("DOMContentLoaded",d,!1):e.attachEvent("onload",d):d()} } }t("//top-fwz1.mail.ru/js/code.js","_tmr"),t("//mediator.imgsmail.ru/2/mpf-mediator.min.js","_mediator")}(window);
-
+12
-
4,2k -
4
-
17 октября 2014 в 16:20
Интервью с создателем 3d мышки Леонардо. Часть 1 -
13 октября 2014 в 14:45
Профессиональные ручные 3d-сканеры -
16 февраля 2008 в 20:09
Apple может выпустить новый игровой iPod с непревзойденной 3D-графикой от nVidia
// global vars
var g_base_url = 'geektimes.ru';
var g_show_xpanel = false;
var g_base_fullurl = 'https://geektimes.ru/';
var g_is_guest = false;
MathJax.Hub.Config({
showProcessingMessages: false,
showMathMenu: true,
tex2jax: {
inlineMath: [['$inline$','$inline$']],
displayMath: [['$$display$$','$$display$$']],
processEscapes: true
},
MathMenu: {
showRenderer: true,
showContext: true
}
});
MathJax.Extension.Img2jax = {
PreProcess: function (element) {
var hasMath = false;
var images = element.querySelectorAll('[data-tex]');
for (var i = images.length - 1; i >= 0; i--) {
var img = images[i];
var tex = img.alt.replace(/(rn|n|r)/gm, " ");
if (tex && tex[0] === '$'){
var script = document.createElement("script"); script.type = "math/tex";
hasMath = true;
if (img.getAttribute('data-tex') == "display"){script.type += ";mode=display"}
MathJax.HTML.setScript(script, tex.substring(1,tex.length-1));
img.parentNode.replaceChild(script,img);
}
}
}
};
MathJax.Hub.Register.PreProcessor(["PreProcess", MathJax.Extension.Img2jax]);
$(document).ready( function(){
window.tmidLogin = function(){ return false; };
if( $.cookie('tmid_no_check') == undefined ) {
var expire = new Date();
expire.setMinutes(expire.getMinutes() + 10 );
$.cookie('tmid_no_check', 1, { expires: expire } );
$.getScript("https://id.tmtm.ru/checklogin/", function(){
if( window.tmidLogin() ) {
var href = $('#login').attr('href')+'?checklogin=true';
if( href !== undefined ) { window.location.href = href; }
}
});
}
});
(function (d, w, c) {
(w[c] = w[c] || []).push(function() {
try {
if (typeof (_yaparams) != 'undefined') {
w.yaCounter26722401 = new Ya.Metrika({
id: 26722401,
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true,
params: _yaparams
});
} else {
w.yaCounter26722401 = new Ya.Metrika({
id: 26722401,
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
}
} catch(e) { }
});
var n = d.getElementsByTagName("script")[0],
s = d.createElement("script"),
f = function () { n.parentNode.insertBefore(s, n); };
s.type = "text/javascript";
s.async = true;
s.src = "https://mc.yandex.ru/metrika/watch.js";
if (w.opera == "[object Opera]") {
d.addEventListener("DOMContentLoaded", f, false);
} else { f(); }
})(document, window, "yandex_metrika_callbacks");
uterr
17 августа 2017 в 17:30
0
san-x
17 августа 2017 в 18:37
+3
da-nie
17 августа 2017 в 19:32
0
iSage
17 августа 2017 в 23:47
0
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.