tutorial:sprites

Result

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;
}

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);
}

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.

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!

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!

  • tutorial/sprites.txt
  • Last modified: 2022/09/28 03:07
  • by iridescence