General
This tutorial will create an application that can dynamically create sprites and meshes. The source can be found here. There is also a pure sceGu implementation there.
#include "../../common/callbacks.h" #include "../../common/common-gl.h" #include <gu2gl.h> #include <string.h> #include <malloc.h> #include <math.h> // PSP Module Info PSP_MODULE_INFO("Camera Sample", 0, 1, 1); PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER | THREAD_ATTR_VFPU); // Global variables int running = 1; static unsigned int __attribute__((aligned(16))) list[262144]; Vertex __attribute__((aligned(16))) square_indexed[4] = { {0.0f, 0.0f, 0xFF00FFFF, -0.25f, -0.25f, -1.0f}, {1.0f, 0.0f, 0xFFFF00FF, -0.25f, 0.25f, -1.0f}, {1.0f, 1.0f, 0xFFFFFF00, 0.25f, 0.25f, -1.0f}, {0.0f, 1.0f, 0xFF000000, 0.25f, -0.25f, -1.0f}, }; unsigned short __attribute__((aligned(16))) indices[6] = { 0, 1, 2, 2, 3, 0 }; int main() { // Boilerplate SetupCallbacks(); // Initialize Graphics guglInit(list); // Initialize Matrices glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(-16.0f / 9.0f, 16.0f / 9.0f, -1.0f, 1.0f, -10.0f, 10.0f); glMatrixMode(GL_VIEW); glLoadIdentity(); glMatrixMode(GL_MODEL); glLoadIdentity(); Texture* texture = load_texture("container.jpg", GL_TRUE, GL_TRUE); if(!texture) goto cleanup; Texture* texture2 = load_texture("circle.png", GL_TRUE, GL_TRUE); if(!texture) goto cleanup; //Main program loop while(running){ guglStartFrame(list, GL_FALSE); // We're doing a 2D, Textured render glDisable(GL_DEPTH_TEST); // Blending glBlendFunc(GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0); glEnable(GL_BLEND); //Clear background to Bjack glClearColor(0xFF000000); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); reset_transform(-0.5f, 0.0f, 0.0f); bind_texture(texture); // Draw Indexed Square glDrawElements(GL_TRIANGLES, GL_INDEX_16BIT | GL_TEXTURE_32BITF | GL_COLOR_8888 | GL_VERTEX_32BITF | GL_TRANSFORM_3D, 6, indices, square_indexed); reset_transform(0.5f, 0.0f, 0.0f); bind_texture(texture2); glDrawElements(GL_TRIANGLES, GL_INDEX_16BIT | GL_TEXTURE_32BITF | GL_COLOR_8888 | GL_VERTEX_32BITF | GL_TRANSFORM_3D, 6, indices, square_indexed); guglSwapBuffers(GL_TRUE, GL_FALSE); camera.rot += 1.0f; camera.y = sinf(camera.rot / 180.0f) / 2.0f; } cleanup: // Terminate Graphics guglTerm(); // Exit Game sceKernelExitGame(); return 0; }
Defining a Mesh
A mesh is a simple structure that holds all of the data required for rendering.
typedef struct { void* data; u16* indices; u32 index_count; } Mesh;
To create a mesh, we can write a function that takes in the number of vertices and indices we will need for that mesh.
Mesh* create_mesh(u32 vcount, u32 index_count) { Mesh* mesh = malloc(sizeof(Mesh)); if(mesh == NULL) return NULL; mesh->data = memalign(16, sizeof(Vertex) * vcount); if(mesh->data == NULL) { free(mesh); return NULL; } mesh->indices = memalign(16, sizeof(u16) * index_count); if(mesh->indices == NULL) { free(mesh->data); free(mesh); return NULL; } mesh->index_count = index_count; return mesh; }
If mesh fails to allocate, we must destroy the mesh and return NULL (failure). To ensure that our vertex data and indices are aligned, we use memalign instead of malloc. We can then write to this array to set up our vertices and indices. More on that to come. Next, we'll write a function to render using a mesh object.
void draw_mesh(Mesh* mesh) { glDrawElements(GL_TRIANGLES, GL_INDEX_16BIT | GL_TEXTURE_32BITF | GL_COLOR_8888 | GL_VERTEX_32BITF | GL_TRANSFORM_3D, mesh->index_count, mesh->indices, mesh->data); }
As you can see, the code is very simple. We'll also provide a function for destroying a mesh.
void destroy_mesh(Mesh* mesh) { free(mesh->data); free(mesh->indices); free(mesh); }
Defining a Sprite
A sprite is a 2D textured object to draw on the screen. It needs a position, size, and texture. For our sprites we can also define rotation, and Z-position (layer). The sprite will be built upon a mesh.
typedef struct { float x, y; float rot; float sx, sy; int layer; Mesh* mesh; Texture* tex; } Sprite;
The real struggle for this will be in creating the sprite. Firstly, let's define a function to produce a vertex from the given parameters.
Vertex create_vert(float u, float v, unsigned int color, float x, float y, float z) { Vertex vert = { .u = u, .v = v, .color = color, .x = x, .y = y, .z = z }; return vert; }
While simple, this function helps us set vertices in an efficient manner in our creation code.
Sprite* create_sprite(float x, float y, float sx, float sy, Texture* tex) { Sprite* sprite = malloc(sizeof(Sprite)); if(sprite == NULL) return NULL; sprite->mesh = create_mesh(4, 6); if(sprite->mesh == NULL){ free(sprite); return NULL; } sprite->x = x; sprite->y = y; sprite->sx = sx; sprite->sy = sy; sprite->mesh->index_count = 6; sprite->tex = tex; sprite->layer = 0; sprite->rot = 0; ((Vertex*)sprite->mesh->data)[0] = create_vert(0, 0, 0xFFFFFFFF, -0.25f, -0.25f, 0.0f); ((Vertex*)sprite->mesh->data)[1] = create_vert(0, 1, 0xFFFFFFFF, -0.25f, 0.25f, 0.0f); ((Vertex*)sprite->mesh->data)[2] = create_vert(1, 1, 0xFFFFFFFF, 0.25f, 0.25f, 0.0f); ((Vertex*)sprite->mesh->data)[3] = create_vert(1, 0, 0xFFFFFFFF, 0.25f, -0.25f, 0.0f); sprite->mesh->indices[0] = 0; sprite->mesh->indices[1] = 1; sprite->mesh->indices[2] = 2; sprite->mesh->indices[3] = 2; sprite->mesh->indices[4] = 3; sprite->mesh->indices[5] = 0; sceKernelDcacheWritebackInvalidateAll(); return sprite; }
First, we must allocate space for our sprite. If this fails, the function fails. We'll then create a mesh with 4 vertices and 6 indices. Likewise, this failing requires us to also accept failure. We then configure our sprite's X, Y, size X, size Y, layer, rotation, set the texture and index count. The next code takes advantage of our create_vert method to create a vertex. We assign this to the vertex array, and then set the indices individually. In the future you can change sprite to take in additional parameters like color and set the color via this create_vert method. We clear the Data Cache to make sure this is written and return the sprite. Using this sprite we can now draw it to the screen.
void draw_sprite(Sprite* sprite) { glMatrixMode(GL_MODEL); glLoadIdentity(); ScePspFVector3 v = { .x = sprite->x, .y = sprite->y, .z = sprite->layer, }; gluTranslate(&v); gluRotateZ(sprite->rot); ScePspFVector3 s = { .x = sprite->sx, .y = sprite->sy, .z = 1.0f }; gluScale(&s); bind_texture(sprite->tex); draw_mesh(sprite->mesh); }
Our draw function here takes a sprite and sets up the translation, rotation, and scale of the model matrix. We then bind the texture and use the draw_mesh to draw to the screen.
void destroy_sprite(Sprite* sprite) { destroy_mesh(sprite->mesh); free(sprite); }
Additionally we can delete our sprites.
Drawing Sprites
We can now use our newly created sprites to render to the screen!
// After texture load Sprite* sprite = create_sprite(-0.5f, 0.0f, 1.0f, 1.0f, texture); while(running) { //after apply camera draw_sprite(sprite) //before swap buffers } destroy_sprite(sprite);
You should hopefully see your sprite now set on the screen!
Conclusion
Congratulations! You can now create sprites on the fly in code without having to pre-write them. You can transform and modify them as you wish as well!