tutorial:camera

Result

This tutorial will create an application that uses a camera to move around the scene. 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;
}

To understand the way that matrices work in 3D Graphics, we need to talk about the three fundamental matrices required for any z-buffer rendered application. These three matrices are the projection, view, and model matrices.

The projection matrix manages the actual translation from a 2D space to a 3D space. This can be perspective, orthographic, or other formats that translate 3D points to a 2D screen.

The model and view matrix function similarly and store affine transformations to arrive at a certain end result. The view matrix typically is used to represent the camera transformations to get to the point of view that you wish to be at. The model matrix typically represents the global position and orientation of an object.

There are three main types of affine transformations that can be applied to the matrices, which is translate, rotate, and scale. These need to be used in combination. Each transformation can be represented by a matrix which is then multiplied against the existing model or view matrices. This makes order relevant. For example, if you scale then transform a point, for example a point at (1,1) scaled by 2 and translated right by 3, you would get (2 + 3, 2) = (5, 2). However if you did the reverse order on the same point, you would get (1+3, 1) = (4, 1) * 2 = (8, 1).

When you apply these transformations it matters which side the matrix is on, which gives you the order you need to put them in. For example on the PSP, if we perform a translate followed by a scale in our code, it actually has the result of scale then translate due the matrix multiplication order. We'll keep this in mind when setting up our camera.

typedef struct{
    float x, y;
    float rot;
} Camera2D;

Defining a camera is relatively easy, the camera has an X, Y (, Z) position and has a rotation. In this context, we're making a 2D camera so this is all the information we need. Similarly, we can set up a 3D camera by simply adding a few elements. This tutorial will focus on a 2D camera, but adding a 3D camera is as simple as extending the dimensions. It's important to remember that the rotation variables = Number of Dimensions - 1.

typedef struct {
    float x, y, z;
    float yaw, pitch;
} Camera3D;

In order to actually have the camera work, we need to apply the camera to the view matrix. To do so, we will use the following code.

void apply_camera(const Camera2D* cam){
    glMatrixMode(GL_VIEW);
    glLoadIdentity();
 
    ScePspFVector3 v = {cam->x, cam->y, 0};
    gluTranslate(&v);
    gluRotateZ(cam->rot / 180.0f * 3.14159f);
 
    glMatrixMode(GL_MODEL);
    glLoadIdentity();
}

Our function here first starts by setting our matrix to the view matrix, which we will be using for transforming the camera. We then load the default matrix here to make sure our data is valid before applying transformations. Transformations should be always applied as scale, rotate, translate – which due to the order of multiplication ends up being translate, rotate, scale in our code.

We start by defining a vector v which contains the position to translate to and performing gluTranslate (sceGumTranslate). We then perform a rotation on the Z axis as the Z axis originates from the center of our camera, and produces the correct rotation in 2D. In 3D, you will use the X and Y axes with the yaw being Y and pitch being X. It's important to note that rotations are measured using angles, and these angles are in radians. If you want to use degrees for common use, you will have to perform the conversion as shown in the code to radians.

Then we set back to the model matrix just in case your code doesn't check / set the matrix referred to. We then reset the model matrix.

int main() {
    //...
    Camera camera = {
        .x = 0,
        .y = 0,
        .rot = 45.0f
    };
 
    //...
    while(running) {
         // After clear...
         apply_camera(&camera);
         // Translate + Draw...
    }
}

Using the code we created earlier, this should result in 45 degree rotated sprites. Let's take this one step further.

In a real game, we'll probably be modifying the camera almost every frame. To demonstrate this, we'll set up a fixed motion. Append the following code to the end of the while loop.

        camera.rot += 1.0f;
        camera.y = sinf(camera.rot / 180.0f) / 2.0f;

This code increases the angle of the camera by 1 degree per frame. It then bobs the object up and down based upon the rotation using sine. The code should result in a spinning and bobbing set of sprites.

Congratulations! You now have a camera that you can control to scroll around your screen. As an exercise, try combining the camera with button inputs.

  • tutorial/camera.txt
  • Last modified: 2022/09/23 01:37
  • by iridescence