LearnGL - 08 - Camera - linmath.h 版


LearnGL - 學習筆記目錄

本人才疏學淺,如有什麼錯誤,望不吝指出。

上一篇:LearnGL - 07 - DrawCube - 旋轉Cube、深度緩存/測試,瞭解如何繪製一個Cube立方體、旋轉3D 幾何體、深度緩存、深度測試都了一些基礎的認識。

這一篇搬磚:矩陣來實現 攝像機/鏡頭 類,便於測試項目中對鏡頭的:位移,和部分軸向的旋轉。


看了一下 攝像機 這篇實現的夠詳細的,再搬磚一下。


幾何變換

之前的文章有將到,我們傳入的頂點數據通常是在 對象空間 的,就是在 3D 建模軟件是的座標。

這些頂點座標,最終會顯示在屏幕上,中間會經歷好幾個座標空間(座標系)的變換:


總覽變換

也可以參考我之前的:OpenGL Transformation 幾何變換的順序概要(MVP,NDC,Window座標變換過程),這裏再次簡單描述一下

Model Matrix
Word2View Matrix
View2Projtion Matrix
Perspective Division
Viewport Mapping
Object/Local Space
Model/World Space
View/Eye/Camera Space
Projection/Clip Space
NDC Space
Screen/View Space
Object/Local Space
Model/World Space
View/Eye/Camera Space
Projection/Clip Space
NDC Space
Screen/View Space

Local to World

Model Matrix
Object/Local Space
Model/World Space

對象空間 轉到 世界空間 一般是每個繪製對象都可能不一樣的變換數據,也就是中間那個:Model Matrix 相對於每一個繪製對象來說,都可能是不一樣的,這其中包含了:縮放、旋轉、平移。


World to View

View Matrix
Model/World Space
View/Eye/Camera Space

這個一般是 攝像機 控制的變換,這裏頭的變換一般只有:旋轉、平移。注意我們的World To View的座標系是 右手座標系的,即:Z軸正方向是從屏幕指向你的眼睛的方向。


View to Projection

Projection Matrix
View/Eye/Camera Space
Projection/Clip Space

這個一般也是由 攝像機 控制的變換,這裏頭的變換一般只有:是隻有軸的縮放。它是負責將3D空間的座標變換到 4D 的齊次座標,並調整4D 座標的第四個分量以便後續做透視除法後投影到 NDC 而是用的。


Projection to NDC

Perspective Division
Projection/Clip Space
NDC Space

這個除法處理,一般來說不需要我們處理,我們只要在 VS(Vertex Shader,頂點着色器)將座標從 Object Space 通過 MVP(Model, View, Projection)變換後調整到用於投影用的空間:Clip Space(這裏一般被稱作:裁剪空間)。

OpenGL 的底層實現 會將 PositionNDC=PositionClip/Positionclip.w;Position_{NDC}=Position_{Clip} / Position_{clip}.w;

的到的 NDC 座標後,就會下一個階段,直到傳到 光柵階段 後,光柵器 會將圖元生成插值片元。

便於後續的 片元着色器 用。


NDC to Screen

Viewport Mapping
NDC Space
Screen/View Space

上面得到的 NDC 座標後,光柵器生成片元后,在傳給片元着色器之前,它會先將片元的座標轉換到屏幕座標。

NDC的x,y範圍是-1~1的,OpenGL 中的NDC.z也是-1~1的,DX的NDC.z是 0~1。

OpenGL 會將 NDC 座標通過 glViewport 設置的x,y,w,h來定位於屏幕的座標。

Positionscreen.x=(PositionNDC.x+1)0.5Wviewport;Positionscreen.y=(PositionNDC.y+1)0.5Hviewport; Position_{screen}.x = (Position_{NDC}.x + 1) * 0.5 * W_{viewport};\\ Position_{screen}.y = (Position_{NDC}.y + 1) * 0.5 * H_{viewport};

NDC to Screen 也是 OpenGL 底層實現的。不需要我們處理。


相機類 - my_camera.h

這篇我們主要是實現 攝像機 的功能,也就是 :WorldToView、ViewToProjection 的變換矩陣我們要生成用於實現攝像機的定位,與投影。

首先是一個純數據:Camera 類,然後是 CameraController 攝像機控制類:

CameraController 提供簡單的自由視角操作:

  • 鏡頭視角的左右上下旋轉
  • 鏡頭根據視角方向左右上下的平移

文章標題爲何叫 linmath.h 版,因爲我們的線性代數(矩陣、向量、四元數)使用的是 GLFW的 linmath.h 頭文件中帶有的功能:github glfw/deps/linmath.h、後續我會再使用一個 GLM 的版本,因爲 linmath.h 的太簡潔,vec2,3,4, matrix沒有封裝都只是float數組,並提供vec, matrix的方法而已。所以再一些使用方法中,如果需要對參數爲:vec,matrix的賦值會相當的麻煩,特別是默認值,只能是 NULL,所以後續還是使用 GLM

// my_camera.h
/* author : jave.lin
鏡頭/攝像機類
Reference/參考:https://learnopengl.com/code_viewer_gh.php?code=includes/learnopengl/camera.h
*/
#ifndef _MY_CAMERA__H_
#define _MY_CAMERA__H_

#include"linmath.h"
#include"my_math_util.h"

namespace my_util {

    // 定義一些鏡頭可能移動選項。
    enum Camera_Movement {
        FORWARD,
        BACKWARD,
        LEFT,
        RIGHT
    };

    // 默認的鏡頭參數值
    const float C_YAW = -90.0f;
    const float C_PITCH = 0.0f;
    const float C_SPEED = 2.5f;
    const float C_SENSITIVITY = 0.1f;
    const float C_FOV = 45.0f;
    const float C_ASPECT = 1.0f;
    const float C_NEAR = 0.1f;
    const float C_FAR = 10.0f;
    const float C_SIZE = 500;               // near/far plane 的高的一半,例如,1000 的高,1000 / 2 = 500

    // 相機/攝像機/鏡頭數據類
    class Camera {
    public:
        // 鏡頭屬性
        vec3 mPosition;         // 鏡頭的位置
        vec3 mFront;            // 鏡頭前方向(透鏡正方向)
        vec3 mUp;               // 鏡頭上方向
        vec3 mRight;            // 鏡頭右方向
        vec3 mWorldUp;          // 參考的世界 “上/頂” 的方向
        // 鏡頭選項
        float mFov;           // field of view
        float mAspect;        // the ratio of width / height to viewport
        float mNear;          // distance of near plane
        float mFar;           // distance of far palne
        float mSize;          // 正交用,

        // 使用向量的構造函數,mFront 默認爲:{ 0.0f, 0.0f, -1.0f }
        // @mPosition 如果 == NULL,則默認爲:{ 0.0f, 0.0f, 0.0f }
        // @up 如果 == NULL,則默認爲:{ 0.0f, 1.0f, 0.0f }
        Camera(
            vec3 pos = NULL, vec3 up = NULL, float fov = C_FOV, float aspect = C_ASPECT,
            float n = C_NEAR, float f = C_FAR, // 這裏不能用near,far於window sdk的minwindef的宏定義有重名
            float size = C_SIZE);
        // 使用標量的構造函數
        Camera(
            float posX = 0.0f, float posY = 0.0f, float posZ = 0.0f, float upX = 0.0f, float upY = 1.0f, float upZ = 0.0f,
            float fov = C_FOV, float aspect = C_ASPECT,
            float n = C_NEAR, float f = C_FAR, // 這裏不能用near,far於window sdk的minwindef的宏定義有重名
            float size = C_SIZE);
    };

    Camera::Camera(
        vec3 pos, vec3 up, float fov, float aspect,
        float n, float f, // 這裏不能用near,far於window sdk的minwindef的宏定義有重名
        float size)
        :
        mFov(fov),
        mAspect(aspect),
        mNear(n),
        mFar(f),
        mSize(size)
    {
        if (mPosition == NULL) {
            set_vec3_by_scalar(mPosition, 0.0f, 0.0f, 0.0f);
        }
        else {
            set_vec3_by_vec(mPosition, mPosition);
        }

        if (up == NULL) {
            set_vec3_by_scalar(mWorldUp, 0.0f, 1.0f, 0.0f);
        }
        else {
            set_vec3_by_vec(mWorldUp, up);
        }
        set_vec3_by_scalar(mFront, 0.0f, 0.0f, -1.0f);
    }
    Camera::Camera(
        float posX, float posY, float posZ, float upX, float upY, float upZ,
        float fov, float aspect,
        float n, float f, // 這裏不能用near,far於window sdk的minwindef的宏定義有重名
        float size)
        :
        mFov(fov),
        mAspect(aspect),
        mNear(n),
        mFar(f),
        mSize(size)
    {
        set_vec3_by_scalar(mPosition, posX, posY, posZ);
        set_vec3_by_scalar(mWorldUp, upX, upY, upZ);
        set_vec3_by_scalar(mFront, 0.0f, 0.0f, -1.0f);
    }

    // 相機/鏡頭控制類
    class CameraController {
    public:
        CameraController(Camera* cam, float yaw = C_YAW, float pitch = C_PITCH);
        ~CameraController();
        // 獲取視圖矩陣
        void getViewMatrix(mat4x4 result);
        // 獲取投影矩陣
        void getProjectionMatrix(mat4x4 result);
        // 處理鍵盤輸入。
        void processKeyboard(Camera_Movement direction, float deltaTime);
        // 處理鼠標輸入:鼠標移動的 x 和 y 方向。
        void processMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true);
        // 處理鼠標滾輪輸入。僅處理垂直滾輪的輸入。
        void processMouseScroll(float yoffset);
        // 歐拉角
        float mYaw;
        float mPitch;
        // 鏡頭選項
        float mMovementSpeed;
        float mMouseSensitivity;
        // 控制的鏡頭對象
        Camera* mCam;
        // 是否正交相機
        bool isOrthorgrahic = false;
    private:
        // 根據鏡頭的歐拉角計算出來前、右、上向量
        void updateCameraVectors();
        // 獲取透視矩陣
        void getPerspectiveMatrix(mat4x4 result);
        // 獲取正交矩陣
        void getOrthographicMatrix(mat4x4 result);
    };

    CameraController::CameraController(Camera* cam, float yaw, float pitch)
        :
        mCam(cam),
        mYaw(yaw),
        mPitch(pitch),
        mMovementSpeed(C_SPEED),
        mMouseSensitivity(C_SENSITIVITY)
    {
        updateCameraVectors();
    }

    CameraController::~CameraController() {
        this->mCam = NULL;
    }

    void CameraController::getViewMatrix(mat4x4 result) {
        vec3 target;
        vec3_add(target, mCam->mPosition, mCam->mFront);
        mat4x4_look_at(result, mCam->mPosition, target, mCam->mUp);
    }

    void CameraController::getProjectionMatrix(mat4x4 result) {
        if (isOrthorgrahic) getOrthographicMatrix(result);
        else getPerspectiveMatrix(result);
    }

    void CameraController::processKeyboard(Camera_Movement direction, float deltaTime) {
        float velocity = mMovementSpeed * deltaTime;
        vec3 value;
        if (direction == FORWARD) {
            vec3_scale(value, mCam->mFront, velocity);
            vec3_add(mCam->mPosition, mCam->mPosition, value);
        }
        if (direction == BACKWARD) {
            vec3_scale(value, mCam->mFront, velocity);
            vec3_sub(mCam->mPosition, mCam->mPosition, value);
        }
        if (direction == LEFT) {
            vec3_scale(value, mCam->mRight, velocity);
            vec3_sub(mCam->mPosition, mCam->mPosition, value);
        }
        if (direction == RIGHT) {
            vec3_scale(value, mCam->mRight, velocity);
            vec3_add(mCam->mPosition, mCam->mPosition, value);
        }
    }

    void CameraController::processMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch) {
        xoffset *= mMouseSensitivity;
        yoffset *= mMouseSensitivity;

        mYaw += xoffset;
        mPitch += yoffset;

        // 確保pitch(沿着x軸旋轉)超出了邊界,因爲屏幕不能翻轉
        if (constrainPitch) {
            if (mPitch > 89.0f) mPitch = 89.0f;
            if (mPitch < -89.0f) mPitch = -89.0f;
        }
        // 使用歐拉角更新 前、右 和 上 向量
        updateCameraVectors();
    }

    void CameraController::processMouseScroll(float yoffset) {
        float fov = mCam->mFov - yoffset;
        if (fov < 1.0f)
            fov = 1.0f;
        if (fov > 60.0f)
            fov = 60.0f;
        mCam->mFov = fov;
    }

    void CameraController::updateCameraVectors() {
        // 計算前向量
        // method 1:
        vec4 front;
        front[0] = cos(D2R(mYaw));
        front[1] = sin(D2R(mPitch));
        front[2] = sin(D2R(mYaw));
        vec3_norm(mCam->mFront, front);

        // method 2:
        //vec3 front;
        //front[0] = cos(D2R(mYaw)) * cos(D2R(mPitch));
        //front[1] = sin(D2R(mPitch));
        //front[2] = sin(D2R(mYaw)) * cos(D2R(mPitch));
        //vec3_norm(mCam->mFront, front);

        // 也需要重新計算 右 和 上 向量
        vec3_mul_cross(mCam->mRight, mCam->mFront, mCam->mWorldUp);
        vec3_norm(mCam->mRight, mCam->mRight); // 需要單位化(歸一化),因爲cross叉乘的參數都是歸一化,但計算出來的不一定是單位向量

        vec3_mul_cross(mCam->mUp, mCam->mRight, mCam->mFront);
        vec3_norm(mCam->mUp, mCam->mUp);
    }

    void CameraController::getPerspectiveMatrix(mat4x4 result) {

        // 有BUG,暫時不用
        //mat4x4_identity(result);
        //float top = mCam->mSize;
        //float bottom = -top;
        //float right = -(top * mCam->mAspect);
        //float left = -right;
        //mat4x4_frustum(result, left, right, bottom, top, mCam->mNear, mCam->mFar);

        mat4x4_perspective(result, D2R(mCam->mFov), mCam->mAspect, mCam->mNear, mCam->mFar);
    }

    void CameraController::getOrthographicMatrix(mat4x4 result) {
        // 有BUG,暫時不用
        mat4x4_identity(result);
        float top = mCam->mSize;
        float bottom = -top;
        float right = -(top * mCam->mAspect);
        float left = -right;
        mat4x4_ortho(result, left, right, bottom, top, mCam->mNear, mCam->mFar);
    }
}
#endif

時間類 - my_timer.h

後面的動畫內容需要用到統一的時間管理封裝,縮寫弄了:my_timer.h

// my_gltime.h
/* author : jave.lin
時間管理類
*/
#ifndef _MY_TIMER__H_
#define _MY_TIMER__H_

#include"glad/glad.h"
#include"GLFW/glfw3.h"

namespace my_util {

	class Timer;

	namespace internal_ns {				// 內部專用 namepsace
		Timer* g_Timer = NULL;
	}

	class Timer {
	public:
		static Timer* Get();			// 單例
	public:
		GLfloat scaled = 1;				// 縮放值
		GLfloat time = 0;				// 從 glfw 啓動到現在的時間(秒)
		GLfloat deltaTime = 0;			// 無縮放的每幀的間隔時間(秒)
		GLfloat scaledDeltaTime = 0;	// 有縮放的每幀的間隔時間(秒)
		GLfloat preseveTime = 0;		// 預留的時間值
		void update();
		~Timer();
	private:
		Timer();
		GLfloat lastTime = 0;			// 上一幀的 從 glfw 啓動到現在的時間(秒)
	};

	Timer* Timer::Get() {
		if (internal_ns::g_Timer == NULL) {
			internal_ns::g_Timer = new Timer();
		}
		return internal_ns::g_Timer;
	}

	Timer::Timer() {
		
	}

	Timer::~Timer() {
		internal_ns::g_Timer = NULL;
	}

	void Timer::update() {
		time = (float)glfwGetTime();

		deltaTime = time - lastTime;
		scaledDeltaTime = deltaTime * scaled;

		lastTime = time;
	}
}

#endif

然後在設置shader uniform時,只要這麼弄就可以了:

	vec4 time;															// 傳入shader的uniform vec4 _Time;

	time[0] = Timer::Get()->time;
	time[1] = Timer::Get()->deltaTime;
	time[2] = Timer::Get()->scaledDeltaTime;
	time[3] = Timer::Get()->preseveTime;

	// system uniform
	shaderProgram->setVec4("_Time", time[0], time[1], time[2], time[3]);	// time info

算術 - my_math.h

提供一些對於 常量定義、角度與弧度轉換、linmath.h 額外的操作:

// my_math_util.h
/* author : jave.lin
數學工具
*/
#ifndef _MY_MATH_UTIL__H_
#define _MY_MATH_UTIL__H_

#include<stdlib.h>
#include"linmath.h"

#define PI 3.14159265359

// 角度轉弧度
#define D2R(v) (v * (float) PI / 180.f)
// 弧度轉角度
#define R2D(v) (v * (float) 180.f / PI)

// vec3 賦值
void set_vec3_by_scalar(vec3 vector3, float v1, float v2, float v3) {
	vector3[0] = v1;
	vector3[1] = v2;
	vector3[2] = v3;
}
void set_vec3_by_vec(vec3 a, vec3 b) {
	a[0] = b[0];
	a[1] = b[1];
	a[2] = b[2];
}

#endif

繪製數據

主要我們現在設置的數據和之前的不太一樣了,之前我們的頂點,直接就是在 NDC 座標下的值。

現在這些座標是經過各個座標系(空間)變換的,所以我們的頂點座標是 Object Space的。

頂點的索引順序有一些調整:

在這裏插入圖片描述

GLfloat vertices[] = {
	// x,	y,	  z
	// 直接放 24 個頂點

	// back
	-0.5f,  0.5f, -0.5f,							// 第0 個頂點
	 0.5f,  0.5f, -0.5f,							// 第1 個頂點
	 0.5f, -0.5f, -0.5f,							// 第2 個頂點
	-0.5f, -0.5f, -0.5f,							// 第3 個頂點

	// front
	-0.5f, -0.5f,  0.5f,							// 第4 個頂點
	 0.5f, -0.5f,  0.5f,							// 第5 個頂點
	 0.5f,  0.5f,  0.5f,							// 第6 個頂點
	-0.5f,  0.5f,  0.5f,							// 第7 個頂點

	// left
	-0.5f, -0.5f, -0.5f,							// 第8 個頂點
	-0.5f, -0.5f,  0.5f,							// 第9 個頂點
	-0.5f,  0.5f,  0.5f,							// 第10個頂點
	-0.5f,  0.5f, -0.5f,							// 第11個頂點

	// right
	 0.5f, -0.5f,  0.5f,							// 第12個頂點
	 0.5f, -0.5f, -0.5f,							// 第13個頂點
	 0.5f,  0.5f, -0.5f,							// 第14個頂點
	 0.5f,  0.5f,  0.5f,							// 第15個頂點

	// top
	-0.5f,  0.5f,  0.5f,							// 第16個頂點
	 0.5f,  0.5f,  0.5f,							// 第17個頂點
	 0.5f,  0.5f, -0.5f,							// 第18個頂點
	-0.5f,  0.5f, -0.5f,							// 第19個頂點

	// bottom
	-0.5f, -0.5f, -0.5f,							// 第20個頂點
	 0.5f, -0.5f, -0.5f,							// 第21個頂點
	 0.5f, -0.5f,  0.5f,							// 第22個頂點
	-0.5f, -0.5f,  0.5f,							// 第23個頂點
};

GLfloat colors[] = {
	// back
	1.0, 0.0f, 0.0f, 1.0,
	1.0, 0.0f, 0.0f, 1.0,
	1.0, 0.0f, 0.0f, 1.0,
	1.0, 0.0f, 0.0f, 1.0,
	// front
	0.0, 1.0f, 0.0f, 1.0,
	0.0, 1.0f, 0.0f, 1.0,
	0.0, 1.0f, 0.0f, 1.0,
	0.0, 1.0f, 0.0f, 1.0,
	// left
	0.0, 0.0f, 1.0f, 1.0,
	0.0, 0.0f, 1.0f, 1.0,
	0.0, 0.0f, 1.0f, 1.0,
	0.0, 0.0f, 1.0f, 1.0,
	// right
	0.0, 1.0f, 1.0f, 1.0,
	0.0, 1.0f, 1.0f, 1.0,
	0.0, 1.0f, 1.0f, 1.0,
	0.0, 1.0f, 1.0f, 1.0,
	// top
	1.0, 1.0f, 0.0f, 1.0,
	1.0, 1.0f, 0.0f, 1.0,
	1.0, 1.0f, 0.0f, 1.0,
	1.0, 1.0f, 0.0f, 1.0,
	// bottom
	1.0, 1.0f, 1.0f, 1.0,
	1.0, 1.0f, 1.0f, 1.0,
	1.0, 1.0f, 1.0f, 1.0,
	1.0, 1.0f, 1.0f, 1.0,
};

GLfloat uvs[] = {									// 頂點的 uv 座標
	// back
	0.0f, 0.0f,										// 左下角
	1.0f, 0.0f,										// 右下角
	1.0f, 1.0f,										// 右上角
	0.0f, 1.0f,										// 左上角
	// front
	0.0f, 0.0f,										// 左下角
	1.0f, 0.0f,										// 右下角
	1.0f, 1.0f,										// 右上角
	0.0f, 1.0f,										// 左上角
	// left
	0.0f, 0.0f,										// 左下角
	1.0f, 0.0f,										// 右下角
	1.0f, 1.0f,										// 右上角
	0.0f, 1.0f,										// 左上角
	// right
	0.0f, 0.0f,										// 左下角
	1.0f, 0.0f,										// 右下角
	1.0f, 1.0f,										// 右上角
	0.0f, 1.0f,										// 左上角
	// top
	0.0f, 0.0f,										// 左下角
	1.0f, 0.0f,										// 右下角
	1.0f, 1.0f,										// 右上角
	0.0f, 1.0f,										// 左上角
	// bottom
	0.0f, 0.0f,										// 左下角
	1.0f, 0.0f,										// 右下角
	1.0f, 1.0f,										// 右上角
	0.0f, 1.0f,										// 左上角
};

GLuint indices[] = {							// 注意索引從0開始!通過索引緩存來指定 圖元 組成 用的 頂點有哪些
	// back
	0, 1, 2,									// 第 0 個三角面
	2, 3, 0,									// 第 1 個三角面
	// front
	4, 5, 6,									// 第 2 個三角面
	6, 7, 4,									// 第 3 個三角面
	// left
	8, 9, 10,									// 第 4 個三角面
	10, 11, 8,									// 第 5 個三角面
	// right
	12, 13, 14,									// 第 6 個三角面
	14, 15, 12,									// 第 7 個三角面
	// top
	16, 17, 18,									// 第 8 個三角面
	18, 19, 16,									// 第 9 個三角面
	// bottom
	20, 21, 22,									// 第 10個三角面
	22, 23, 20,									// 第 11個三角面
};

GLfloat scales[] = {							// 每個繪製對象的縮放
	1,
	0.9f,
	0.8f,
	0.7f,
	0.6f,
	0.5f,
	0.4f,
	0.3f,
	0.2f,
	0.1f,
};

GLfloat rots[] = {								// 每個繪製對象的旋轉
	1.0f,
	2.0f,
	0.5f,
	0.25f,
	3.0f,
	2.5f,
	4.0f,
	1.0f,
	2.0f,
	3.0f
};

GLfloat offsets[] = {							// 每個繪製對象的平移
	 0,  0,  0,
	 1,  0,  0,
	-1,  0,  0,
	 0,  1,  0,
	 0, -1,  0,
	 0,  0, -2,
	 1,  0, -2,
	-1,  0, -2,
	 0,  1, -2,
	 0, -1, -2,
};

使用

void _stdcall OnInitCallback() {
	...
	cam = new Camera(0.0f, 0.0f, 3.0f);
	camCtrl = new CameraController(cam);
	...
}

void _stdcall OnMouseMovelCallback(const float deltaX, const float deltaY) {
	camCtrl->processMouseMovement(deltaX, deltaY);
}

void _stdcall OnMouseScrollCallback(const float deltaY) {
	camCtrl->processMouseScroll(deltaY);
}

void _stdcall OnUpdateCallback() {
	
	cam->mAspect = (GLfloat)win_width / (GLfloat)win_height;

	// check key status
	if (isPressKey(GLFW_KEY_W)) {
		camCtrl->processKeyboard(FORWARD, Timer::Get()->scaledDeltaTime);
	}
	if (isPressKey(GLFW_KEY_S)) {
		camCtrl->processKeyboard(BACKWARD, Timer::Get()->scaledDeltaTime);
	}
	if (isPressKey(GLFW_KEY_A)) {
		camCtrl->processKeyboard(LEFT, Timer::Get()->scaledDeltaTime);
	}
	if (isPressKey(GLFW_KEY_D)) {
		camCtrl->processKeyboard(RIGHT, Timer::Get()->scaledDeltaTime);
	}

	glClearColor(0.1f, 0.2f, 0.1f, 0.f);								// 設置清理顏色緩存時,填充的顏色值
	glClearDepthf(1.0f);												// 設置清理深度緩存時,填充的深度值
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);					// 清理顏色緩存 與 深度緩存

	mat4x4 tMat, rMat, sMat;											// model matrix == TRS matrix
	mat4x4 mvpMat, mMat, vMat, pMat;
	vec4 time;															// 傳入shader的uniform vec4 _Time;

	time[0] = Timer::Get()->time;
	time[1] = Timer::Get()->deltaTime;
	time[2] = Timer::Get()->scaledDeltaTime;
	time[3] = Timer::Get()->preseveTime;

	// system uniform
	shaderProgram->setVec4("_Time", time[0], time[1], time[2], time[3]);	// time info

	camCtrl->getViewMatrix(vMat);
	camCtrl->getProjectionMatrix(pMat);

	shaderProgram->use();												// 使用此着色器程序
	shaderProgram->setInt("main_tex", 0);								// 主			紋理設置採樣器採樣 0 索引紋理單元
	shaderProgram->setInt("mask_tex", 1);								// 遮罩			紋理設置採樣器採樣 1 索引紋理單元
	shaderProgram->setInt("flash_light_tex", 2);						// 閃光/流光	紋理設置採樣器採樣 2 索引紋理單元
	shaderProgram->setMatrix4x4("vMat", (const GLfloat*)vMat);			// view matrix
	shaderProgram->setMatrix4x4("pMat", (const GLfloat*)pMat);			// projection matrix

	glBindVertexArray(vertex_array_object);								// 先繪製 VAO[0] 的 VBO,EBO,VAF,ENABLED

	int objCount = sizeof(offsets) / (sizeof(offsets[0]) * 3);			// 每三個便宜點爲一個對象
	for (size_t i = 0; i < objCount; i++) {
		GLfloat scale = scales[i];
		GLfloat rot_scaled = rots[i];
		GLfloat offset_x = offsets[i * 3 + 0];
		GLfloat offset_y = offsets[i * 3 + 1];
		GLfloat offset_z = offsets[i * 3 + 2];

		mat4x4_identity(sMat);
		mat4x4_identity(rMat);

		mat4x4_scale(sMat, sMat, scale);
		sMat[3][3] = 1; // linmath.h 的mat4x4_scale有bug,這個應該是1,分則會影響透視效果

		mat4x4_rotate_Y(rMat, rMat, Timer::Get()->time * PI * 0.5f * rot_scaled);
		mat4x4_rotate_X(rMat, rMat, Timer::Get()->time * PI * 0.25f * rot_scaled);
		mat4x4_rotate_Z(rMat, rMat, Timer::Get()->time * PI * 0.125f * rot_scaled);

		mat4x4_translate(tMat, offset_x, offset_y, offset_z);

		mat4x4_mul(mMat, rMat, sMat);
		mat4x4_mul(mMat, tMat, mMat);

		shaderProgram->setMatrix4x4("mMat", (const GLfloat*)mMat);			// model matrix

		glDrawElements(GL_TRIANGLES, sizeof(vertices) / sizeof(vertices[0]), GL_UNSIGNED_INT, (GLvoid*)(0));		// 繪製 36 個索引對應的頂點
	}
}

着色器

着色器我們需要是對 VS 添加:M(Model)、V(View)、P(Projection) 三個矩陣(也可以使用一個MVP合併後的矩陣)

// jave.lin - testing_camera1.vert - 測試鏡頭1
#version 450 compatibility
uniform mat4 mMat; 
uniform mat4 vMat; 
uniform mat4 pMat; 

attribute vec3 vPos;
attribute vec2 vUV;
attribute vec4 vCol;

varying vec2 fUV;
varying vec4 fCol;
void main() {
	gl_Position = pMat * vMat * mMat * vec4(vPos, 1.0);
	fUV = vUV;
	fCol = vCol;
}

// jave.lin - testing_camera1.frag - 測試鏡頭1
#version 450 compatibility
varying vec2 fUV;					// uv 座標
varying vec3 fPos;
varying vec4 fCol;

uniform sampler2D main_tex;			// 主紋理
uniform sampler2D mask_tex;			// 遮罩紋理
uniform sampler2D flash_light_tex;	// 閃光/流光紋理

uniform vec4 _Time;					// 時間(秒)用於動畫

void main() {
	vec3 mainCol 	= texture(main_tex, fUV).rgb;
	float mask 		= texture(mask_tex, fUV).r;
	vec4 flashCol 	= texture(flash_light_tex, fUV + vec2(-_Time.x, 0));
	flashCol 		*= flashCol.a * mask;
	mainCol 		= mainCol + flashCol.rgb;
	gl_FragColor 	= mix(vec4(mainCol, 1.0), fCol, 0.5);
	// gl_FragColor 	= mix(vec4(mainCol, 1.0), fCol, 0.8);
}

運行效果

在這裏插入圖片描述


References

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章