0
\$\begingroup\$

The tools I am using are: gcc, opengl es 2 and glm.

I am trying to align a weapon model with the first-person camera. The main issue is that I can not seem to figure out how to "anchor the weapon to the camera" by utilizing the camera space and offsetting and rotating the object so it fits like in an FPS-game. Instead I use the member variables of the Camera class to align and rotate the weapon accordingly.

With the help of GDN, I created this camera class:

#pragma once

#include <GLES2/gl2.h>

#include "deps/glm/glm.hpp"
#include "deps/glm/gtc/matrix_transform.hpp"

#define DEFAULT_YAW -90.f
#define DEFAULT_PITCH 0.f
#define DEFAULT_SPEED 15.f
#define DEFAULT_SCROLL_SPEED 0.04f
#define DEFAULT_SENSE .24f
#define DEFAULT_FOV_Y 45.f
#define DEFAULT_RATIO 16.f/9.f
#define DEFAULT_NEAR .1f
#define DEFAULT_FAR 1024.f

#define MIN_FOV 44.3f
#define MAX_FOV DEFAULT_FOV_Y
#define MIN_PITCH -89.f
#define MAX_PITCH 89.f

class Camera
{
public:
    Camera(glm::vec3 pos = default_pos, glm::vec3 up = default_up) :
        m_Position(pos),
        m_WorldUp(up)
    {
        UpdateVectors(); // Otherwise glm::LookAt will complain.
    }
protected:
    glm::vec3 m_Position; // Set by ctor.
    glm::vec3 m_WorldUp; // Set by ctor.

    glm::vec3 m_Target; // Set by UpdateVectors().
    glm::vec3 m_Right; // Set by UpdateVectors().
    glm::vec3 m_Up; // Set by UpdateVectors().

    GLfloat m_Ratio = DEFAULT_RATIO;
    GLfloat m_FOV = DEFAULT_FOV_Y;
    GLfloat m_FOVmin = MIN_FOV;
    GLfloat m_FOVmax = MAX_FOV;

    GLfloat m_Yaw = DEFAULT_YAW;
    GLfloat m_Pitch = DEFAULT_PITCH;
    GLfloat m_PitchMin = MIN_PITCH;
    GLfloat m_PitchMax = MAX_PITCH;

    GLfloat m_MovementSpeed = DEFAULT_SPEED;
    GLfloat m_ScrollSpeed = DEFAULT_SCROLL_SPEED;
    GLfloat m_MouseSenseX = DEFAULT_SENSE;
    GLfloat m_MouseSenseY = DEFAULT_SENSE;
public:
    glm::mat4 View() const { return glm::lookAt(m_Position, m_Target + m_Position, m_Up); }
    glm::mat4 Projection() const { return glm::perspective(m_FOV, m_Ratio, DEFAULT_NEAR, DEFAULT_FAR); }

    glm::mat4 ViewProjection() const { return Projection() * View(); }

    glm::vec3 const& Position() const { return m_Position; }
    void Position(glm::vec3 pos) { m_Position = pos; }

    glm::vec3 Target() const { return m_Target; }
    void Target(GLfloat x, GLfloat y, GLfloat z) { m_Target = glm::vec3(x, y, z); }

    glm::vec3 Right() const { return m_Right; }
    glm::vec3 WorldUp() const { return m_WorldUp; }
    glm::vec3 Up() const { return m_Up; }

    GLfloat const& GetRatio() const { return m_Ratio; }
    GLfloat const& GetFOV() const { return m_FOV; }
    GLfloat const& GetYaw() const { return m_Yaw; }
    GLfloat const& GetPitch() const { return m_Pitch; }

    void SetYaw(GLfloat yaw) { m_Yaw = yaw; }
    void SetPitch(GLfloat pitch) { m_Pitch = pitch; }

    void MoveForward(GLfloat time_delta) { m_Position += m_Target*m_MovementSpeed*time_delta; }

    void MoveBack(GLfloat time_delta) { m_Position -= m_Target*m_MovementSpeed*time_delta; }

    void MoveLeft(GLfloat time_delta){ m_Position -= m_Right*m_MovementSpeed*time_delta; }

    void MoveRight(GLfloat time_delta) { m_Position += m_Right*m_MovementSpeed*time_delta; }

    void MoveUp(GLfloat time_delta) { m_Position += m_WorldUp*m_MovementSpeed*time_delta; }

    void MoveDown(GLfloat time_delta) { m_Position -= m_WorldUp*m_MovementSpeed*time_delta; }

    void Yaw(GLfloat offset) { m_Yaw += offset*m_MouseSenseX; }

    void Pitch(GLfloat offset)
    {
        m_Pitch += offset*m_MouseSenseY;

        if (m_Pitch > m_PitchMax)
        {
            m_Pitch = m_PitchMax;
        }
        else if (m_Pitch < m_PitchMin)
        {
            m_Pitch = m_PitchMin;
        }
    }

    void Zoom(GLfloat y)
    {
        if (m_FOV >= m_FOVmin && m_FOV <= m_FOVmax)
        {
            m_FOV -= y*m_ScrollSpeed;
        }
        if (m_FOV > m_FOVmax)
        {
            m_FOV = m_FOVmax;
        }
        if (m_FOV < m_FOVmin)
        {
            m_FOV = m_FOVmin;
        }
    }

    void UpdateVectors()
    {
        glm::vec3 front;
        front.x = glm::cos(glm::radians(m_Yaw)) * glm::cos(glm::radians(m_Pitch));
        front.y = glm::sin(glm::radians(m_Pitch));
        front.z = glm::sin(glm::radians(m_Yaw)) * glm::cos(glm::radians(m_Pitch));

        m_Target = glm::normalize(front);
        m_Right = glm::normalize(glm::cross(m_Target, m_WorldUp));
        m_Up = glm::normalize(glm::cross(m_Right, m_Target));
    }
};

All my models inherit from the Transformable class, which I use to create the model matrix with Transform(), take a look:

#pragma once

#include <GLES2/gl2.h>

#include "deps/glm/gtc/matrix_transform.hpp"

class Transformable
{
public:
    Transformable() {}

    Transformable(glm::vec3 const& pos, glm::vec3 const& size)
    {
        Translate(pos.x, pos.y + size.y/2.f, pos.z); // Place on the y-coordinate.
        Scale(size); // Set initial size.
    }
protected:
    glm::mat4 m_TransformTranslation;
    glm::mat4 m_TransformScaling;

    glm::mat4 m_TransformRotationX;
    glm::mat4 m_TransformRotationY;
    glm::mat4 m_TransformRotationZ;

    glm::vec3 m_Orientation;
public:
    void Position(glm::vec3 const& pos) { m_TransformTranslation = glm::translate(glm::mat4(1.f), pos); }

    glm::vec4 const& Position() const { return m_TransformTranslation[3]; }
    glm::mat4 Rotation() const { return m_TransformRotationX * m_TransformRotationY * m_TransformRotationZ; }

    GLfloat X() const { return Position().x; }
    GLfloat Y() const { return Position().y; }
    GLfloat Z() const { return Position().z; }

    GLfloat Width() const { return m_TransformScaling[0].x; }
    GLfloat Height() const { return m_TransformScaling[1].y; }
    GLfloat Length() const { return m_TransformScaling[2].z; }

    void OrientX(GLfloat deg) { m_Orientation.x = deg; m_TransformRotationX = glm::rotate(glm::mat4(1), m_Orientation.x, glm::vec3(1, 0, 0)); }
    void OrientY(GLfloat deg) { m_Orientation.y = -deg; m_TransformRotationY = glm::rotate(glm::mat4(1), m_Orientation.y, glm::vec3(0, 1, 0)); }
    void OrientZ(GLfloat deg) { m_Orientation.z = deg; m_TransformRotationZ = glm::rotate(glm::mat4(1), m_Orientation.z, glm::vec3(0, 0, 1)); }

    void Translate(glm::vec3 const& vec) { m_TransformTranslation = glm::translate(m_TransformTranslation, vec); }
    void Scale(glm::vec3 const& vec) { m_TransformScaling = glm::scale(glm::mat4(1), vec); }

    void Translate(GLfloat x, GLfloat y, GLfloat z) { Translate(glm::vec3(x, y, z)); }
    void Scale(GLfloat x, GLfloat y, GLfloat z) { Scale(glm::vec3(x, y, z)); }

    glm::mat4 Transform() const { return m_TransformTranslation * Rotation() * m_TransformScaling; }
};

I do all the offsetting, yawing and pitching in the player class, which holds the model for the weapon.

#pragma once

#include "camera.h"
#include "model.h"

class Player
{
public:
    Player(Model *weapon) :
        m_Camera(new Camera),
        m_Weapon(weapon)
    {}

    ~Player()
    {
        delete m_Camera;
    }
private:
    Camera *m_Camera;
    Model *m_Weapon;

    glm::vec3 m_Position;
    glm::vec3 m_Rotation;

    GLfloat m_MovementSpeed = 100.f;
    GLfloat m_MouseSenseX = 1;
    GLfloat m_MouseSenseY = 1;
public:
    Camera* PlayerCamera() const { return m_Camera; }
    Model* GetWeaponModel() const { return m_Weapon; }

    void Update()
    {
        m_Camera->Position(m_Position);
        m_Camera->Update();

        glm::vec3 weapon_offset_y = glm::vec3(0, 10, 0);

        m_Weapon->Position(m_Position + weapon_offset_y);
        m_Weapon->OrientY(m_Camera->GetYaw() + 30);
        m_Weapon->OrientZ(m_Camera->GetPitch() + 30);
        m_Weapon->OrientX(20);
    }

    void Position(glm::vec3 pos) { m_Position = pos; Update(); }
    void SetYaw(GLfloat deg) { m_Camera->SetYaw(deg); Update(); }

    void MoveForward(GLfloat delta) { m_Position += m_Camera->Target() * m_MovementSpeed * delta; Update(); }
    void MoveBack(GLfloat delta) { m_Position -= m_Camera->Target() * m_MovementSpeed * delta; Update(); }
    void MoveLeft(GLfloat delta) { m_Position -= m_Camera->Right() * m_MovementSpeed * delta; Update(); }
    void MoveRight(GLfloat delta) { m_Position += m_Camera->Right() * m_MovementSpeed * delta; Update(); }

    void Yaw(GLfloat delta) { m_Camera->Yaw(m_MouseSenseX * delta); Update(); }
    void Pitch(GLfloat delta) { m_Camera->Pitch(m_MouseSenseX * delta); Update(); }
};

As you can see, I am also struggling with the model matrix, which is currently an RTS-matrix and the camera provides a projection*view matrix with ViewProjection(). I multiply both and pass it as a uniform to a shader.

Although It seems that I am doing the offsets and rotations wrong, because when I move the camera, the weapon model moves around (up and down), instead of being glued to the view.

Any suggestions are much appreciated!

\$\endgroup\$
1
\$\begingroup\$

One old trick for FPS games is that the player's weapon is rendered separately. What that means in practice is that you render your scene in (at least) two parts: first the world, then the player weapon. The player weapon is matched exactly to the camera so that it always appears in the same position.

What about the weapons you see other players holding? It's a fake! If you think about it, to get the same look, the players would have to be holding their gun right under their eyes, about where their noses are.

It's a similar technique to skyboxes, which are also fixed (position, not rotation) to the camera but are rendered behind everything else, and makes them appear infinitely distant. This also means they don't have to be really large at all; I usually use something like a 1x1x1 cube.

I think changing your code to move the weapon to the camera's position will be enough to fix it. Just make sure that you don't completely divorce the weapon rendering to the world - you still want the lighting to make sense.

\$\endgroup\$
  • \$\begingroup\$ The alternative seems to be quite annoying, you have to do animations like this. The Firewatch main character animation had to be distorted to look nice in front of the FPS camera. \$\endgroup\$ – Alexandre Vaillancourt Apr 5 '17 at 1:10
  • \$\begingroup\$ I hadn't even thought of drawing the weapon model like I would a skybox, that is a rather clever solution. And thanks for showing the Firewatch model, it's hilarious. \$\endgroup\$ – Byrk Apr 5 '17 at 3:35
  • \$\begingroup\$ Oh, and a quick question, is the method for rotation that I am using viable? \$\endgroup\$ – Byrk Apr 5 '17 at 3:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.