从零开始物理引擎(一)-简单界面

界面

界面部分主要可视化物理引擎运行情况

创建窗口

首先需要创建出窗口,这里采用winapi,创建出窗口,没什么好说的,官方文档中给的也比较详细。这里给一个窗口消息的处理方式:

处理窗口消息

将窗口的行为封装成类,窗口消息要准确的传递给特定的对象,但api中给出的回调方式无法回调类成员方法,这里要倒腾一下:

LRESULT ZDSJ::MyWindowClass::handelMessageSetUp(HWND handle, UINT msg, WPARAM wParam, LPARAM lParam)
{
	if (msg == WM_NCCREATE) {
		// 创建窗口时触发
		// lParam 指向 CREATESTRUCT 结构的指针,其中包含有关正在创建的窗口的信息。
		// CREATESTRUCT.lpCreateParams 也就是CreateWindowExW的LPVOID指向的指针
		const CREATESTRUCTW* const pCreat = reinterpret_cast<CREATESTRUCTW*>(lParam);
		ZDSJ::MyWindowInterface* const pwnd = static_cast<ZDSJ::MyWindowInterface*>(pCreat->lpCreateParams);
		// 将执行CreateWindowExW的this指针存入窗口
		SetWindowLongPtrW(handle, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pwnd));
		// 不能通过WINAPI调用成员函数, 在将此函数设置为消息处理函数后,被当作WINAPI了
		// 将消息转发到静态方法
		// GWLP_WNDPROC设置窗口过程的新地址
		SetWindowLongPtrW(handle, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&ZDSJ::MyWindowClass::handelMessageForward));
		return pwnd->handelMessage(handle, msg, wParam, lParam);
	}
	return DefWindowProc(handle, msg, wParam, lParam);
}

LRESULT ZDSJ::MyWindowClass::handelMessageForward(HWND handle, UINT msg, WPARAM wParam, LPARAM lParam)
{
	ZDSJ::MyWindowInterface* const pwnd = reinterpret_cast<ZDSJ::MyWindowInterface*>(GetWindowLongPtrW(handle, GWLP_USERDATA));
	return pwnd->handelMessage(handle, msg, wParam, lParam);
}

这里借助的是CreateWindowExW时传入lparam参数,将自身指针作为参数传入,在回调时会带上lparam,可以理解为一种扩展载荷或者上下文信息之类的。

外部捕获消息采用的是PeekMessage,他在没有消息的时候不会阻塞。采用chrono库进行帧数控制,最终调用doFram方法运行帧,也就是交给dx11去渲染。

dx11

dx11的创建就不在赘述了,因为要做一个物理引擎,首先先把简单的形状能够绘制出来,至于之后是否需要加载模型之后再说。

在不考虑纹理的情况下,vertexbuffer用来定义形状,为了节省顶点所占资源,引入indexbuffer,两个结合定义形状,vertexshader用来将图形变换到屏幕空间,再经过pixelshader上色就可以在屏幕中看到一个带颜色的图形了。这其中不同形状所需的设置不同,相同的形状所需的设置相似,因此将vertexbuffer这些需要再渲染时设置的抽取出接口。

class BindAbleInterface {
	public:
		virtual void bind(ID3D11DeviceContext* _context) = 0;
	};

void ZDSJ::VertexBufferBindAble::bind(ID3D11DeviceContext* _context)
{
	_context->IASetVertexBuffers(0, 1, this->m_buffer.GetAddressOf(), &this->m_stride, &this->m_offset);
}

之后需要设置到渲染管线中的只需继承自该接口,在渲染时可以统一绑定。

创建绘制形状接口,在draw时调用bind,绑定管线渲染需要的资源。

void ZDSJ::DrawAbleAdapter::draw(ID3D11DeviceContext* _context, short _fps)
{
	//if (this->m_index_size == 0) {
	//	return;
	//}
	this->bind(_context);
	this->update(_context, _fps);
	_context->DrawIndexed(this->m_index_size, 0u, 0u);
}

void ZDSJ::DrawAbleAdapter::bind(ID3D11DeviceContext* _context)
{
	for (auto item : *this->m_bind_able) {
		item->bind(_context);
	}
}

类图如下:

 

769dc43849a644e9a5b702cba2b1af50.png

 8d01917aab504146808ae60961a5ef98.png

其中m_bind_able是BindAbleInterface的vector。m_animation是动画相关,之后会提取到父类中。

动画

将动画单独提取出类,需要考虑如何控制动画时间,这里采用时间基的方式。

首先指定动画执行时间,以毫秒为单位,假设设定为1000也就是1秒。首先会计算当前帧率作为初始时间基,假设为60,也就是在设置该动画时,帧率为60,该动画需要运行60帧达到1秒。随后一帧更新时,假设帧率为30,那么当前时间基为30,那么动画就需要跳过一帧,也就是当前帧需要运行两帧的结果,即初试时间基/当前时间基。

ZDSJ::DrawAbleAnimation::DrawAbleAnimation(float _change_value, long long _animation_time, short _fps, 
	std::function<void(float)> _update_function, std::function<float(float, float)> _exchange_function, bool _loop):
	m_change_value(_change_value), m_time_base(_fps), m_update_function(_update_function), m_exchange_function(_exchange_function), m_loop(_loop)
{
	// 计算出在当前时间基下(帧数)下需要多少帧
	this->m_animation_fps = round((_fps / 1000.0f) * _animation_time);
}

void ZDSJ::DrawAbleAnimation::update(short _fps, bool _continue)
{
	// continue 做动画中断
	
	// 换算时间基
	float temp = this->m_time_base / _fps;
	temp /= this->m_animation_fps;
	this->m_step += temp;
	// TODO变换
	this->m_update_function(this->m_exchange_function(this->m_step, this->m_change_value));
	if (!this->m_loop) {
		if (this->m_step >= this->m_animation_fps) {
			// TODO 结束回调
		}
	}
	else if (!_continue) {
		// TODO 结束回调
	}
	
}

 exchange_function用来指定动画更新方式,线性或者贝塞尔曲线等等,之后都方便实现。

遗留问题

ZDSJ::Triangle2DDrawAble::Triangle2DDrawAble(ID3D11Device* _device, ID3D11DeviceContext* _context, float _translation_x, bool _change) : m_change(_change), DrawAbleAdapter(_device) {
	// 顶点缓存
	std::vector<ZDSJ::Vertex2D> vertices = {
		{0.0f, 1.0f, 255, 0, 0, 128},
		{0.5f, 0.0f, 0, 255, 0, 255},
		{-0.5f, 0.0f, 0, 0, 255, 128},
	};
	// 顶点索引
	const UINT16 indices[] = {
		0,1,2
	};
	// 顶点缓存
	this->m_bind_able->push_back(new ZDSJ::VertexBufferBindAble(_device, vertices));
	// 顶点索引
	this->m_bind_able->push_back(new ZDSJ::IndexBufferBindAble(_device, indices, sizeof(indices)));
	this->m_index_size = sizeof(indices) / sizeof(UINT16);
	// 顶点着色器
	this->m_bind_able->push_back(new ZDSJ::VertexShaderBindAble(_device, g_main_vertex_shader, sizeof(g_main_vertex_shader)));
	// layout
	std::vector<D3D11_INPUT_ELEMENT_DESC> ied = {
		{"Position", 0, DXGI_FORMAT::DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
		{"Color", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 8u, D3D11_INPUT_PER_VERTEX_DATA, 0},
	};
	this->m_bind_able->push_back(new ZDSJ::InputLayoutBindAble(_device, ied, g_main_vertex_shader, sizeof(g_main_vertex_shader)));
	// 像素着色器
	this->m_bind_able->push_back(new ZDSJ::PixelShaderBindAble(_device, g_main_pixel_shader, sizeof(g_main_pixel_shader)));
	// 缩放旋转矩阵
	this->m_transform = new ZDSJ::VertexConstantBufferBindAble(_device);
	this->m_bind_able->push_back(m_transform);
	this->m_translation_x = _translation_x;
	// this->m_change = _change;
	// this->bind(_context);
}

在创建三角形时,每个三角形创建出一组新的绑定,但其中顶点缓存,顶点索引,等都是可以通用的,只需要针对不同的三角形应用不同的变换就行,之后会将其处理。同时,相同类型绑定到管线中的对象也是大致相同的,只有与单个对象相关的大小,旋转等信息不同,之后也会进行处理。

源码放在github

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值