Chapter 18. Camera view

In our previous chapters we used straight projection of our subject to the "screen surface". For more realistic picture we need Camera. OpenGL ES doesn't offer an embedded camera object, so we'll need to create one.

Windows

1. Start VS. Open C:\CPP\a997modeler\p_windows\p_windows.sln.


To see the difference with Camera view let's first simplify our box movement.

2. Open TheGame.cpp and replace rotations speed setting (line 26) by

pGS->ownSpeed.setDegrees(0,1,0);

Build and run. Now box is spinning simply around Y axis. Looks a bit weird, isn't it?


3. Under xEngine add new header file Camera.h

Location: C:\CPP\engine

Code:

#pragma once
#include "Coords.h"
#include "linmath.h"

class Camera
{
public:
	Coords ownCoords;
	mat4x4 lookAtMatrix;
};

We will need to set up camera position and orientation. Eventually we will need to calculate a lookAtMatrix in order to build MVP matrix (Model-View-Projection) for rendering.

Our box size is 100x200x400 and location - 0x0x0. Let's place camera at a some distance from the box closer to us at a some height in order to look at the box slightly from above. Position 0x200x1000 will do.

View direction will be direction from camera position to the box position, in our case 0x-200x-1000.

To define view matrix we'll need camera position, view direction, which we just calculated, and camera UP vector, which we don't have yet, but we can calculate it from direction vector. From this vector we can extract camera's yaw and pitch, which in this case are 180 and 11.3 degrees. Then we can set up camera orientation and rotationMatrix, and then apply this matrix to vertical vector 0x1x0. Then we can call mat4x4_look_at() function to calculate camera's lookAtMatrix.

Yaw and pitch calculations we will place in utils.


4. Open utils.h and replace code by

#pragma once
#include <string>
#include "linmath.h"

int checkGLerrors(std::string ref);
void mat4x4_mul_vec4plus(vec4 vOut, mat4x4 M, vec4 vIn, int v3);

void v3set(float* vOut, float x, float y, float z);
void v3copy(float* vOut, float* vIn);
float v3pitchRd(float* vIn);
float v3yawRd(float* vIn);
float v3pitchDg(float* vIn);
float v3yawDg(float* vIn);


5. Open utils.cpp and replace code by

#include "utils.h"
#include "platform.h"

extern float radians2degrees;

int checkGLerrors(std::string ref) {
    //can be used after any GL call
    int res = glGetError();
    if (res == 0)
        return 0;
    std::string errCode;
    switch (res) {
        //case GL_NO_ERROR: errCode = "GL_NO_ERROR"; break;
        case GL_INVALID_ENUM: errCode = "GL_INVALID_ENUM"; break;
        case GL_INVALID_VALUE: errCode = "GL_INVALID_VALUE"; break;
        case GL_INVALID_OPERATION: errCode = "GL_INVALID_OPERATION"; break;
        case GL_INVALID_FRAMEBUFFER_OPERATION: errCode = "GL_INVALID_FRAMEBUFFER_OPERATION"; break;
        case GL_OUT_OF_MEMORY: errCode = "GL_OUT_OF_MEMORY"; break;
        default: errCode = "??"; break;
    }
    mylog("GL ERROR %d-%s in %s\n", res, errCode.c_str(), ref.c_str());
    return -1;
}
void mat4x4_mul_vec4plus(vec4 vOut, mat4x4 M, vec4 vIn, int v3) {
    vec4 v2;
    if (vOut == vIn) {
        memcpy(&v2, vIn, sizeof(vec4));
        vIn = v2;
    }
    vIn[3] = (float)v3;
    mat4x4_mul_vec4(vOut, M, vIn);
}
void v3set(float* vOut, float x, float y, float z) {
    vOut[0] = x;
    vOut[1] = y;
    vOut[2] = z;
}
void v3copy(float* vOut, float* vIn) {
    for (int i = 0; i < 3; i++)
        vOut[i] = vIn[i];
}
float v3yawRd(float* vIn) {
    return atan2f(vIn[0], vIn[2]);
}
float v3pitchRd(float* vIn) {
    return -atan2f(vIn[1], sqrtf(vIn[0] * vIn[0] + vIn[2] * vIn[2]));
}
float v3pitchDg(float* vIn) { 
    return v3pitchRd(vIn) * radians2degrees; 
}
float v3yawDg(float* vIn) { 
    return v3yawRd(vIn) * radians2degrees; 
}


In TheGame.cpp instead of just straight mProjection matrix we will use perspective mProjection and mViewProjection, which is a multiplication of mProjection and camera's lookAtMatrix.

Perspective mProjection matrix call is:

mat4x4_perspective(mProjection, 3.14f / 6.0f, (float)screenSize[0]/screenSize[1], 700.f, 1300.f);

Parameters:

  • mProjection - where to save resulting matrix
  • 3.14f / 6.0f - view angle, in this case - 30 degrees in radians
  • (float)screenSize[0]/screenSize[1] - screen size ratio
  • 700.f, 1300.f - near clip and far clip

6. Open TheGame.cpp and replace code by:

#include "TheGame.h"
#include "platform.h"
#include "utils.h"
#include "linmath.h"
#include "Texture.h"
#include "Shader.h"
#include "DrawJob.h"
#include "ModelBuilder.h"

extern std::string filesRoot;

std::vector<GameSubj*> TheGame::gameSubjs;

int TheGame::getReady() {
    bExitGame = false;
    Shader::loadShaders();
    glEnable(GL_CULL_FACE);

    //=== create box ========================
    GameSubj* pGS = new GameSubj();
    gameSubjs.push_back(pGS);

    pGS->name.assign("box1");
    pGS->ownCoords.setPosition(0, 0, 0);
    pGS->ownCoords.setDegrees(0, 0, 0);
    pGS->ownSpeed.setDegrees(0,1,0);

    ModelBuilder* pMB = new ModelBuilder();
    pMB->useSubjN(gameSubjs.size() - 1);

    //define VirtualShape
    VirtualShape vs;
    vs.type.assign("box");
    vs.whl[0] = 100;
    vs.whl[1] = 200;
    vs.whl[2] = 400;

    Material mt;
    //define material - flat red
    mt.shaderN = Shader::spN_flat_ucolor;
    mt.primitiveType = GL_TRIANGLES;
    mt.uColor.setRGBA(255, 0, 0,255); //red
    pMB->useMaterial(&mt);

    pMB->buildBoxFace("front", &vs);
    pMB->buildBoxFace("back", &vs);
    pMB->buildBoxFace("top", &vs);
    pMB->buildBoxFace("bottom", &vs);
    pMB->buildBoxFace("left", &vs);

    mt.uColor.setRGBA(0, 0, 255,255); pMB->useMaterial(&mt); //blue
    pMB->buildBoxFace("right", &vs);

    pMB->buildDrawJobs(gameSubjs);

    delete pMB;

    //===== set up camera
    v3set(mainCamera.ownCoords.pos, 0, 200, 1000); //set position
    float cameraDir[3];
    v3set(cameraDir, 0, -200, -1000); //set direction vector
    float cameraYawDg = v3yawDg(cameraDir);
    float cameraPitchDg = v3pitchDg(cameraDir);
    mylog("cameraYaw=%f, cameraPitch=%f\n", cameraYawDg, cameraPitchDg);

    mainCamera.ownCoords.setDegrees(cameraPitchDg, cameraYawDg, 0);
    float cameraUp[4] = { 0,1,0,0 }; //y - up
    mat4x4_mul_vec4plus(cameraUp, *mainCamera.ownCoords.getRotationMatrix(), cameraUp, 0);

    mat4x4_look_at(mainCamera.lookAtMatrix, mainCamera.ownCoords.pos, pGS->ownCoords.pos, cameraUp);

    return 1;
}
int TheGame::drawFrame() {
    myPollEvents();

    //glClearColor(0.0, 0.0, 0.5, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    mat4x4 mProjection, mViewProjection, mMVP;
    //mat4x4_ortho(mProjection, -(float)screenSize[0] / 2, (float)screenSize[0] / 2, -(float)screenSize[1] / 2, (float)screenSize[1] / 2, 100.f, 500.f);
    mat4x4_perspective(mProjection, 3.14f / 6.0f, (float)screenSize[0] / screenSize[1], 700.f, 1300.f);
    mat4x4_mul(mViewProjection, mProjection, mainCamera.lookAtMatrix);
    //mViewProjection[1][3] = 0; //keystone effect

    //scan subjects
    int subjsN = gameSubjs.size();
    for (int subjN = 0; subjN < subjsN; subjN++) {
        GameSubj* pGS = gameSubjs.at(subjN);
        //behavior - apply rotation speed
        pGS->moveSubj();
        //prepare subject for rendering
        pGS->buildModelMatrix(pGS);
        //build MVP matrix for given subject
        mat4x4_mul(mMVP, mViewProjection, pGS->ownModelMatrix);
        //render subject
        for (int i = 0; i < pGS->djTotalN; i++) {
            DrawJob* pDJ = DrawJob::drawJobs.at(pGS->djStartN + i);
            pDJ->execute((float*)mMVP, NULL);
        }
    }
    mySwapBuffers();
    return 1;
}
int TheGame::cleanUp() {
    int itemsN = gameSubjs.size();
    //delete all UISubjs
    for (int i = 0; i < itemsN; i++) {
        GameSubj* pGS = gameSubjs.at(i);
        delete pGS;
    }
    gameSubjs.clear();
    //clear all other classes
    Texture::cleanUp();
    Shader::cleanUp();
    DrawJob::cleanUp();
    return 1;
}
int TheGame::onScreenResize(int width, int height) {
    if (screenSize[0] == width && screenSize[1] == height)
        return 0;
    screenSize[0] = width;
    screenSize[1] = height;
    screenRatio = (float)width / (float)height;
    glViewport(0, 0, width, height);
    mylog(" screen size %d x %d\n", width, height);
    return 1;
}
int TheGame::run() {
    getReady();
    while (!bExitGame) {
        drawFrame();
    }
    cleanUp();
    return 1;
}


TheGame.h is modified too, mainCamera was added.

7. Replace TheGame.h code by:

#pragma once
#include <vector>
#include "GameSubj.h"
#include "Camera.h"

class TheGame
{
public:
	int screenSize[2];
	float screenRatio;
	bool bExitGame;
	Camera mainCamera;

	//static arrays (vectors) of active GameSubjs
	static std::vector<GameSubj*> gameSubjs;
public:
	int run();
	int getReady();
	int drawFrame();
	int cleanUp();
	int onScreenResize(int width, int height);
};


8. Build and run. Result:

Much better, isn't it?


9. However, we have a

Keystone (trapezoid) effect

Everyone who took photos in the city faced this problem. This is an image perspective distortion. This is when the lines in the photo, which supposed to be vertical, are not:

A little trick can help fix this. In a mViewProjection matrix we can tell that we don't want screen coordinates to be affected by vertical (Y) component. It's a [1][3] position in the matrix. Just set it to zero.

It's line 83 in TheGame.cpp.

Before:

After:


Now - on

Android

10. Re-start VS. Open C:\CPP\a997modeler\p_android\p_android.sln.


11. Under xEngine add Existing Item

Go to C:\CPP\engine

Pick Camera.h

Add.


12. Switch on, unlock, plug in, allow.

Rebuild solution, run.

Doesn't fit the screen well, but works!


Leave a Reply

Your email address will not be published. Required fields are marked *