界面
界面部分主要可视化物理引擎运行情况
创建窗口
首先需要创建出窗口,这里采用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);
}
}
类图如下:
其中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