本人才疏学浅,如有什么错误,望不吝指出。
上一篇:LearnGL - 07 - DrawCube - 旋转Cube、深度缓存/测试,了解如何绘制一个Cube立方体、旋转3D 几何体、深度缓存、深度测试都了一些基础的认识。
这一篇搬砖:矩阵来实现 摄像机/镜头 类,便于测试项目中对镜头的:位移,和部分轴向的旋转。
看了一下 摄像机 这篇实现的够详细的,再搬砖一下。
之前的文章有将到,我们传入的顶点数据通常是在 对象空间 的,就是在 3D 建模软件是的坐标。
这些顶点坐标,最终会显示在屏幕上,中间会经历好几个坐标空间(坐标系)的变换:
也可以参考我之前的:OpenGL Transformation 几何变换的顺序概要(MVP,NDC,Window坐标变换过程),这里再次简单描述一下
从 对象空间 转到 世界空间 一般是每个绘制对象都可能不一样的变换数据,也就是中间那个:Model Matrix 相对于每一个绘制对象来说,都可能是不一样的,这其中包含了:缩放、旋转、平移。
这个一般是 摄像机 控制的变换,这里头的变换一般只有:旋转、平移。注意我们的World To View的坐标系是 右手坐标系的,即:Z轴正方向是从屏幕指向你的眼睛的方向。
这个一般也是由 摄像机 控制的变换,这里头的变换一般只有:是只有轴的缩放。它是负责将3D空间的坐标变换到 4D 的齐次坐标,并调整4D 坐标的第四个分量以便后续做透视除法后投影到 NDC 而是用的。
这个除法处理,一般来说不需要我们处理,我们只要在 VS(Vertex Shader,顶点着色器)将坐标从 Object Space 通过 MVP(Model, View, Projection)变换后调整到用于投影用的空间:Clip Space(这里一般被称作:裁剪空间)。
OpenGL 的底层实现 会将 P o s i t i o n N D C = P o s i t i o n C l i p / P o s i t i o n c l i p . w ; Position_{NDC}=Position_{Clip} / Position_{clip}.w; PositionNDC=PositionClip/Positionclip.w;
的到的 NDC 坐标后,就会下一个阶段,直到传到 光栅阶段 后,光栅器 会将图元生成插值片元。
便于后续的 片元着色器 用。
上面得到的 NDC 坐标后,光栅器生成片元后,在传给片元着色器之前,它会先将片元的坐标转换到屏幕坐标。
NDC的x,y范围是-1~1的,OpenGL 中的NDC.z也是-1~1的,DX的NDC.z是 0~1。
OpenGL 会将 NDC 坐标通过 glViewport 设置的x,y,w,h来定位于屏幕的坐标。
P o s i t i o n s c r e e n . x = ( P o s i t i o n N D C . x + 1 ) ∗ 0.5 ∗ W v i e w p o r t ; P o s i t i o n s c r e e n . y = ( P o s i t i o n N D C . y + 1 ) ∗ 0.5 ∗ H v i e w p o r t ; Position_{screen}.x = (Position_{NDC}.x + 1) * 0.5 * W_{viewport};\\ Position_{screen}.y = (Position_{NDC}.y + 1) * 0.5 * H_{viewport}; Positionscreen.x=(PositionNDC.x+1)∗0.5∗Wviewport;Positionscreen.y=(PositionNDC.y+1)∗0.5∗Hviewport;
NDC to Screen 也是 OpenGL 底层实现的。不需要我们处理。
这篇我们主要是实现 摄像机 的功能,也就是 :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_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
提供一些对于 常量定义、角度与弧度转换、linmath.h 额外的操作:
// my_math_util.h
/* author : jave.lin
数学工具
*/
#ifndef _MY_MATH_UTIL__H_
#define _MY_MATH_UTIL__H_
#include
#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);
}