运动与矩阵容器------完成一个小型行星运动系统

运动与矩阵容器------完成一个小型日-地-月系统

运动

在使用OpenGL制作动画中, 我们通常会希望组装较小的简单模型来组成复杂模型, 例如: 可以分别绘制头部, 身体, 腿部和手臂来绘制. 如果以身体为中心, 那么头, 腿部, 手臂的运动都可以看作是依附于身体的运动.如果身体躯干的运动可以用一系列已知矩阵来描述的话, 那么对于头, 腿部, 手臂等依附于身体所进行的复合运动想要直接描述和跟踪则相当困难. 所以就有了矩阵堆栈这样的概念, 如<<计算机图形学编程>>这本书中所述:
在这里插入图片描述
然而我并不会就此满足, 之后会介绍到矩阵栈的局限性, 并且引入一个新的结构–矩阵树结构. 接下来请容我介绍我自己的矩阵堆栈的实现.

矩阵堆栈

首先是基本的矩阵-模型类

typedef struct MM{
	float scale = 1.f;
	glm::mat4 trans;
	Model* pModel;
	MM() : trans(glm::mat4(1.0)), pModel(nullptr), scale(1.f) {};
	MM(glm::mat4 const& mat, Model* p, float scale = 1.f) : trans(mat), pModel(p), scale(scale) {};
} MAM;

class TransformComb_Base {
public:
	virtual void display(My_Shader& shader) const = 0;
	virtual bool updateMat(const Model*, glm::mat4 const&) = 0;
	virtual bool updateMat(const Model*, glm::mat4&&) = 0;
};

简单地把模型和矩阵变换以及缩放比例打包到一个类中, 不需要设计其他的成员函数, 类名MAM意为(Model & Matrix).
TransformComb_Base即接下来我们要引入的两种结构的基类.
首先是矩阵栈

class TfComb_Stack: public TransformComb_Base {
public:
	TfComb_Stack() = default;
	template<template <class> class Container>
	TfComb_Stack(Container<MAM> const&);
	template<typename T>
	void push(T&&);
	void push(std::initializer_list<MAM> const& list);
	bool updateMat(const Model*, glm::mat4 const&) override;
	bool updateMat(const Model*, glm::mat4&&) override;
	bool getMat(const Model*, glm::mat4&);
	void display(My_Shader& shader) const override;
private:
	std::vector<MM> matrix_stack;
	MAM* get(const Model*) ;
};

我们通过维护一个向量作为保存矩阵的栈, 向量的元素的前驱可以天然地作为该元素物体运动所依赖的对象. 我们在插入(push)进对象时不需要指定对象运动的依赖者. updateMat()函数是提供给用户来更新某个物体的运动行为矩阵, getMat用于得到某个物体的实际运动矩阵. display用于渲染.

template< template<class> class Container>
TfComb_Stack::TfComb_Stack(Container<MAM> const& list) { // 接受任何容器
	this->matrix_stack = std::vector<MAM>(list.begin(),list.end());
}

void TfComb_Stack::display(My_Shader& shader) const{
	// 分为两阶段: 1. 前向计算矩阵变换的叠加 2. 逐个渲染
	// Step 1:
	// 拷贝容器
	auto copy_stack = this->matrix_stack;
	size_t size = copy_stack.size();
	for (int i = 1; i < size; ++i) {
		auto& curr = copy_stack[i], & prev = copy_stack[i - 1];
		// 注意运算顺序, 应当先施加当前的缩放变换后施加当前的位置变换再施加前驱物体的变换
		curr.trans = prev.trans
			* curr.trans
			* glm::scale(glm::mat4(1.f), glm::vec3(curr.scale));
	}
	// Step 2: 渲染
	// 使用同一个着色器进行渲染, 对于每个物体渲染时应当更新着色器的model_matrix
	shader.use();
	auto f = [&shader](MAM const& mam) {
		shader.setMat4("model", mam.trans);
		mam.pModel->Draw(shader);
	};
	std::for_each(copy_stack.begin(), copy_stack.end(),	f);
}

bool TfComb_Stack::getMat(const Model* model, glm::mat4& res) {
	MAM* res_mam = nullptr; size_t index = -1;
	for (int i = 0; i < matrix_stack.size(); ++i) {
		if (matrix_stack[i].pModel == model) {
			res_mam = &matrix_stack[i];
			index = i;
			break;
		}
	}
	if (res_mam == nullptr) return false;
	res = glm::mat4(1.f);
	for (int i = index; i >= 0; --i) {
		res = matrix_stack[i].trans * res;
	}
	return true;
}

bool TfComb_Stack::updateMat(const Model* model, glm::mat4 const& newMat) {
	auto res_mam = get(model);
	if (res_mam == nullptr) return false;
	res_mam->trans = newMat;
	return true;
}

bool TfComb_Stack::updateMat(const Model* model, glm::mat4&& newMat) {
	auto res_mam = get(model);
	if (res_mam == nullptr) return false;
	res_mam->trans = std::move(newMat);
	return true;
}

MAM* TfComb_Stack::get(const Model* model){
	if (model == nullptr) return nullptr;
	MAM* res{ nullptr };
	std::for_each(matrix_stack.begin(), matrix_stack.end(),
		[&res, model](MM& mam) { if (mam.pModel == model) { res = &mam; } });
	return res;
}

template<typename T>
auto TfComb_Stack::push(T&& M)->void {
	this->matrix_stack.push_back(M);
}
auto TfComb_Stack::push(std::initializer_list<MAM> const& list)->void {
	for (auto& v : list) {
		this->matrix_stack.push_back(std::move(v));
	}
}

代码部分结合注释来看应该不会特别难. 部分使用了模板是为了快速构造和万能引用. 其次是在渲染的过程的第一步值得好好推敲. 矩阵的结合顺序应当服从元素在向量中的先后顺序的逆序(由于矩阵乘法从右向左, 故实际上的书写顺序与元素先后顺序相同).

// 注意运算顺序, 应当先施加当前的缩放变换后施加当前的位置变换再施加前驱物体的变换
		curr.trans = prev.trans
			* curr.trans
			* glm::scale(glm::mat4(1.f), glm::vec3(curr.scale));

这样一来二去我们就可以通过矩阵栈来构建一个运动的系统了, 我们以一个行星系统为例:
在这里插入图片描述
主程序核心代码如下:

...
// Models
TfComb_Stack TS;
Model Solar(resource + "planet/planet.obj");
MAM MAM_Solar(glm::mat4(1.0), &Solar);
Model Earth(resource + "planet/planet.obj");
MAM MAM_Earth(glm::mat4(1.0), &Earth, 0.75f);
Model Moon(resource + "planet/planet.obj");
MAM MAM_Moon(glm::mat4(1.0), &Moon, 0.5f);
glm::mat4 SolarModel(1.0f);
glm::mat4 EarthModel = glm::translate(glm::mat4(1.0f), glm::vec3(20.f, 0, 0));
glm::mat4 MoonModel = glm::translate(glm::mat4(1.0f), glm::vec3(7.5f, 0, 0));
TS.push({ MAM_Solar, MAM_Earth, MAM_Moon });
TS.updateMat(&Solar, SolarModel);
TS.updateMat(&Earth, EarthModel);
TS.updateMat(&Moon, MoonModel);
My_Shader shader(shaderProgram + "planet.vs", shaderProgram + "planet.fs");
...
while(...){
	...
 	glm::mat4 model = glm::mat4(1.0f);
 	glm::mat4 view = camera.GetViewMatrix();
 	shader.use();
 	shader.setMat4("view", view);
 	shader.setMat4("model", model);
 	TS.updateMat(&Earth,
    	glm::rotate(glm::mat4(1.f), glm::radians(currentFrame * 10),
		glm::vec3(0, 1, 0)) * EarthModel);
 	TS.updateMat(&Moon,
    	glm::rotate(glm::mat4(1.f), glm::radians(currentFrame * 120),
     	glm::vec3(0, 1, 0))* MoonModel);
 	TS.display(shader);
 	...
}

我们利用了顺序结构每个元素天然的具有前驱和后继来构建被渲染物体之间运动的相互关系, 那么如果有多个物体依附于同一个运动物体呢?
在这里插入图片描述
很容易想到树结构, 我们只需要把将物体运动的依赖关系用树构建起来即可
首先定义树节点:

typedef struct MMNode : public MAM {
	MMNode* parent;
	std::vector<MMNode*> children;
	MMNode(glm::mat4 const& mat, Model* p, float scale = 1.f, MMNode* parent = nullptr) :
		MM(mat, p, scale), parent(parent) {
		children.clear();
	}
	MMNode(MM const& mm, MMNode* parent = nullptr) : MM(mm), parent(parent) {
		children.clear();
	}
} MAMNode, * pMAMNode;

还有我们的用于管理模型矩阵的树类:

class TfComb_Tree : public TransformComb_Base {
public:
	TfComb_Tree() :root(nullptr) {};
	TfComb_Tree(MAMNode& MAM) : root(&MAM) {};
	~TfComb_Tree();
	void insert(MAM& M);
	void insert(MAM& M, const Model* parent);
	bool updateMat(const Model*, glm::mat4 const&)override;
	bool updateMat(const Model*, glm::mat4&&)override;
	void display(My_Shader& shader) const override;
	auto getMat(const Model*, glm::mat4&) -> bool;
private:
	pMAMNode root;
	pMAMNode get(const Model*);
	TfComb_Tree* getCopy() const;
	template<typename Func>
	friend void traverse(pMAMNode root, Func);
};
// template <typename T>
void TfComb_Tree::insert(MAM& M) {
	root = new MAMNode(M);
}

// template <typename T>
void TfComb_Tree::insert(MAM& M, const Model* Father) {
	auto Node = new MAMNode(M);
	auto fth = this->get(Father);
	Node->parent = fth;
	fth->children.push_back(Node);
}

bool TfComb_Tree::updateMat(const Model* model, glm::mat4 const& newMat) {
	auto Node = get(model);
	if (Node == nullptr) return false;
	Node->trans = newMat;
	return true;
}

bool TfComb_Tree::updateMat(const Model* model, glm::mat4&& newMat) {
	auto Node = get(model);
	if (Node == nullptr) return false;
	Node->trans = std::move(newMat);
	return true;
}

void TfComb_Tree::display(My_Shader& shader) const {
	auto copy_tree = this->getCopy();
	auto new_root = copy_tree->root;
	// 同样地分两步进行, 第一步预先计算每个节点的最终模型矩阵
	traverse(new_root, [](pMAMNode curr) {
			auto prev = curr->parent;
			if (prev == nullptr) return;
			curr->trans = prev->trans
				* curr->trans
				* glm::scale(glm::mat4(1.f), glm::vec3(curr->scale));
		});
	//第二步进行渲染
	traverse(new_root, [&shader](pMAMNode M) { 
		shader.use();
		shader.setMat4("model", M->trans);
		M->pModel->Draw(shader); 
	});
	delete copy_tree;
}

bool TfComb_Tree::getMat(const Model* model, glm::mat4& res) {
	auto Node = this->get(model);
	if (Node == nullptr) return false;
	res = Node->trans;
	return true;
}

pMAMNode TfComb_Tree::get(const Model* model) {
	if (model == nullptr) return nullptr;
	pMAMNode res{nullptr};
	traverse(this->root,
		[&res, model](pMAMNode M) {
			if (M->pModel == model) res = M;
		});
	return res;
}

pMAMNode clone(pMAMNode root) {
	if (root == nullptr) return nullptr;
	pMAMNode new_Node = new MAMNode(*root);
	size_t n = 0;
	for (auto child : root->children) {
		pMAMNode new_child = clone(child);
		new_child->parent = new_Node;
		new_Node->children[n++] = new_child;
	}
	return new_Node;
}

TfComb_Tree* TfComb_Tree::getCopy() const {
	return new TfComb_Tree(*clone(this->root));
}

template <typename Visiter>
void traverse(pMAMNode root, Visiter visits) {
	if (root == nullptr)return;
	std::queue<pMAMNode> q;
	q.push(root);
	while (!q.empty()) {
		auto front = q.front(); q.pop();
		size_t size = front->children.size();
		for (int i = 0; i < size; ++i) {
			if ( front->children[i] != nullptr )
				q.push(front->children[i]);
		}
		visits(front);
	}
}

树的操作会稍微复杂一些, 但也不是太难,值得一提的是为了避免书写重复代码我写了一个遍历函数, 为了保持析构函数正确的节点销毁顺序与渲染函数的正确模型计算顺序我们应当使用层序遍历的方式, 因此使用队列作为辅助容器.
接下来是主程序的核心代码, 绘制太阳, 地球, 火星, 月球4颗星星

// Models
TfComb_Stack TS;
Model Solar(resource + "planet/planet.obj");
MAM MAM_Solar(glm::mat4(1.0), &Solar);
Model Earth(resource + "planet/planet.obj");
MAM MAM_Earth(glm::mat4(1.0), &Earth, 0.75f);
Model Moon(resource + "planet/planet.obj");
MAM MAM_Moon(glm::mat4(1.0), &Moon, 0.5f);
Model Mars(resource + "planet/planet.obj");
MAM MAM_Mars(glm::mat4(1.0f), &Mars, 0.6f);
glm::mat4 SolarModel(1.0f);
glm::mat4 EarthModel = glm::translate(glm::mat4(1.0f), glm::vec3(20.f, 0, 0));
glm::mat4 MoonModel = glm::translate(glm::mat4(1.0f), glm::vec3(7.5f, 0, 0));
glm::mat4 MarsModel = glm::translate(glm::mat4(1.0f), glm::vec3(25.f, 0, 0));
TS.insert(MAM_Solar);
TS.insert(MAM_Earth, &Solar);
TS.insert(MAM_Moon, &Earth);
TS.insert(MAM_Mars, &Solar);
TS.updateMat(&Solar, SolarModel);
TS.updateMat(&Earth, EarthModel);
TS.updateMat(&Moon, MoonModel);
TS.updateMat(&Mars, MarsModel);
My_Shader shader(shaderProgram + "planet.vs", shaderProgram + "planet.fs");
...
while(...){
	...
 	 // draw scene as normal
 	glm::mat4 model = glm::mat4(1.0f);
 	glm::mat4 view = camera.GetViewMatrix();
 	shader.use();
 	shader.setMat4("view", view);
 	shader.setMat4("model", model);
 	TS.updateMat(&Earth,
    	glm::rotate(glm::mat4(1.f), glm::radians(currentFrame * 10),
		glm::vec3(0, 1, 0)) * EarthModel);
 	TS.updateMat(&Moon,
    	glm::rotate(glm::mat4(1.f), glm::radians(currentFrame * 120),
     	glm::vec3(0, 1, 0))* MoonModel);
 	TS.updateMat(&Mars,
     	glm::rotate(glm::mat4(1.f), glm::radians(currentFrame * 12.5f),
     	glm::vec3(0, 1, 0)) * MarsModel);
 	TS.display(shader);
 	...
}

LearnOpenGL在骨骼动画这一节中:https://learnopengl-cn.github.io/08%20Guest%20Articles/2020/01%20Skeletal%20Animation/ 提到了一种制作动画的方式, 这种方式本质上也是通过矩阵堆栈, 并且是在着色器中实现的. 这种方法依赖于我们所加载的动画文件, 如果你没用可以动的动画资源, 就请耐心使用以上工具搭建一个简单动画吧!
最后是完整的头文件和源文件:
TransformCombination.h:

#pragma once

#ifndef __TRAINSFORMCOMBINATION__
#define __TRAINSFORMCOMBINATION__

#include "Model.h"
#include <stack>
#include <vector>
#include <initializer_list>

typedef struct MM{
	float scale = 1.f;
	glm::mat4 trans;
	Model* pModel;
	MM() : trans(glm::mat4(1.0)), pModel(nullptr), scale(1.f) {};
	MM(glm::mat4 const& mat, Model* p, float scale = 1.f) : trans(mat), pModel(p), scale(scale) {};
} MAM;

class TransformComb_Base {
public:
	virtual void display(My_Shader& shader) const = 0;
	virtual bool updateMat(const Model*, glm::mat4 const&) = 0;
	virtual bool updateMat(const Model*, glm::mat4&&) = 0;
};

typedef struct MMNode : public MAM {
	MMNode* parent;
	std::vector<MMNode*> children;
	MMNode(glm::mat4 const& mat, Model* p, float scale = 1.f, MMNode* parent = nullptr) :
		MM(mat, p, scale), parent(parent) {
		children.clear();
	}
	MMNode(MM const& mm, MMNode* parent = nullptr) : MM(mm), parent(parent) {
		children.clear();
	}
} MAMNode, * pMAMNode;

class TfComb_Stack: public TransformComb_Base {
public:
	TfComb_Stack() = default;
	template<template <class> class Container>
	TfComb_Stack(Container<MAM> const&);
	template<typename T>
	void push(T&&);
	void push(std::initializer_list<MAM> const& list);
	bool updateMat(const Model*, glm::mat4 const&) override;
	bool updateMat(const Model*, glm::mat4&&) override;
	bool getMat(const Model*, glm::mat4&);
	void display(My_Shader& shader) const override;
private:
	std::vector<MM> matrix_stack;
	MAM* get(const Model*);
};

class TfComb_Tree : public TransformComb_Base {
public:
	TfComb_Tree() :root(nullptr) {};
	TfComb_Tree(MAMNode& MAM) : root(&MAM) {};
	~TfComb_Tree();
	void insert(MAM& M);
	void insert(MAM& M, const Model* parent);
	bool updateMat(const Model*, glm::mat4 const&)override;
	bool updateMat(const Model*, glm::mat4&&)override;
	void display(My_Shader& shader) const override;
	auto getMat(const Model*, glm::mat4&) -> bool;
private:
	pMAMNode root;
	pMAMNode get(const Model*);
	TfComb_Tree* getCopy() const;
	template<typename Func>
	friend void traverse(pMAMNode root, Func);
};


#endif // !__TRAINSFORMCOMBINATION__

TransformCombination.cpp:


#include "TransformCombination.h"
#include <algorithm>
#include <queue>

template< template<class> class Container>
TfComb_Stack::TfComb_Stack(Container<MAM> const& list) { // 接受任何容器
	this->matrix_stack = std::vector<MAM>(list.begin(),list.end());
}

void TfComb_Stack::display(My_Shader& shader) const{
	// 分为两阶段: 1. 前向计算矩阵变换的叠加 2. 逐个渲染
	// Step 1:
	// 拷贝容器
	auto copy_stack = this->matrix_stack;
	size_t size = copy_stack.size();
	for (int i = 1; i < size; ++i) {
		auto& curr = copy_stack[i], & prev = copy_stack[i - 1];
		// 注意运算顺序, 应当先施加当前的缩放变换后施加当前的位置变换再施加前驱物体的变换
		curr.trans = prev.trans
			* curr.trans
			* glm::scale(glm::mat4(1.f), glm::vec3(curr.scale));
	}
	// Step 2: 渲染
	// 使用同一个着色器进行渲染, 对于每个物体渲染时应当更新着色器的model_matrix
	shader.use();
	auto f = [&shader](MAM const& mam) {
		shader.setMat4("model", mam.trans);
		mam.pModel->Draw(shader);
	};
	std::for_each(copy_stack.begin(), copy_stack.end(),	f);
}

bool TfComb_Stack::getMat(const Model* model, glm::mat4& res) {
	MAM* res_mam = nullptr; size_t index = -1;
	for (int i = 0; i < matrix_stack.size(); ++i) {
		if (matrix_stack[i].pModel == model) {
			res_mam = &matrix_stack[i];
			index = i;
			break;
		}
	}
	if (res_mam == nullptr) return false;
	res = glm::mat4(1.f);
	for (int i = index; i >= 0; --i) {
		res = matrix_stack[i].trans * res;
	}
	return true;
}

bool TfComb_Stack::updateMat(const Model* model, glm::mat4 const& newMat) {
	auto res_mam = get(model);
	if (res_mam == nullptr) return false;
	res_mam->trans = newMat;
	return true;
}

bool TfComb_Stack::updateMat(const Model* model, glm::mat4&& newMat) {
	auto res_mam = get(model);
	if (res_mam == nullptr) return false;
	res_mam->trans = std::move(newMat);
	return true;
}

MAM* TfComb_Stack::get(const Model* model){
	if (model == nullptr) return nullptr;
	MAM* res{ nullptr };
	std::for_each(matrix_stack.begin(), matrix_stack.end(),
		[&res, model](MM& mam) { if (mam.pModel == model) { res = &mam; } });
	return res;
}

TfComb_Tree::~TfComb_Tree() {
	traverse(this->root, [](pMAMNode Node) {if (Node) delete Node; });
}

template<typename T>
auto TfComb_Stack::push(T&& M)->void {
	this->matrix_stack.push_back(M);
}
auto TfComb_Stack::push(std::initializer_list<MAM> const& list)->void {
	for (auto& v : list) {
		this->matrix_stack.push_back(std::move(v));
	}
}

// template <typename T>
void TfComb_Tree::insert(MAM& M) {
	root = new MAMNode(M);
}

// template <typename T>
void TfComb_Tree::insert(MAM& M, const Model* Father) {
	auto Node = new MAMNode(M);
	auto fth = this->get(Father);
	Node->parent = fth;
	fth->children.push_back(Node);
}

bool TfComb_Tree::updateMat(const Model* model, glm::mat4 const& newMat) {
	auto Node = get(model);
	if (Node == nullptr) return false;
	Node->trans = newMat;
	return true;
}

bool TfComb_Tree::updateMat(const Model* model, glm::mat4&& newMat) {
	auto Node = get(model);
	if (Node == nullptr) return false;
	Node->trans = std::move(newMat);
	return true;
}

void TfComb_Tree::display(My_Shader& shader) const {
	auto copy_tree = this->getCopy();
	auto new_root = copy_tree->root;
	// 同样地分两步进行, 第一
	traverse(new_root, [](pMAMNode curr) {
			auto prev = curr->parent;
			if (prev == nullptr) return;
			curr->trans = prev->trans
				* curr->trans
				* glm::scale(glm::mat4(1.f), glm::vec3(curr->scale));
		});
	traverse(new_root, [&shader](pMAMNode M) { 
		shader.use();
		shader.setMat4("model", M->trans);
		M->pModel->Draw(shader); 
	});
	delete copy_tree;
}

bool TfComb_Tree::getMat(const Model* model, glm::mat4& res) {
	auto Node = this->get(model);
	if (Node == nullptr) return false;
	res = Node->trans;
	return true;
}

pMAMNode TfComb_Tree::get(const Model* model) {
	if (model == nullptr) return nullptr;
	pMAMNode res{nullptr};
	traverse(this->root,
		[&res, model](pMAMNode M) {
			if (M->pModel == model) res = M;
		});
	return res;
}

pMAMNode clone(pMAMNode root) {
	if (root == nullptr) return nullptr;
	pMAMNode new_Node = new MAMNode(*root);
	size_t n = 0;
	for (auto child : root->children) {
		pMAMNode new_child = clone(child);
		new_child->parent = new_Node;
		new_Node->children[n++] = new_child;
	}
	return new_Node;
}

TfComb_Tree* TfComb_Tree::getCopy() const {
	return new TfComb_Tree(*clone(this->root));
}

template <typename Visiter>
void traverse(pMAMNode root, Visiter visits) {
	if (root == nullptr)return;
	std::queue<pMAMNode> q;
	q.push(root);
	while (!q.empty()) {
		auto front = q.front(); q.pop();
		size_t size = front->children.size();
		for (int i = 0; i < size; ++i) {
			if ( front->children[i] != nullptr )
				q.push(front->children[i]);
		}
		visits(front);
	}
}

着色器 planet.vs:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 2) in vec2 aTexCoords;

out vec2 TexCoords;

uniform mat4 projection;
uniform mat4 view; 
uniform mat4 model;

void main()
{
    TexCoords = aTexCoords;
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}

planet.fs:

#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D texture_diffuse1;

void main()
{    
    FragColor = texture(texture_diffuse1, TexCoords);
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值