第一人称摄像机
我们原来的摄像机主要是通过鼠标来旋转、放大图象。旋转用的是比较符合观察的那种。
本章呢要做这个第一人称视角的摄像机。实际上就是根据玩家的操作,来不断调整摄像机的位置以及朝向。
整体来说没什么难度。
在MiniEngine中有一个CameraController类,这个类本身实现的就是第一人称视角摄像机
来看下调用方式:
// 初始化
m_Camera.SetEyeAtUp({ 0.0f, 2.0f, -15.0f }, { 0.0f, 2.0f, 0.0f }, Math::Vector3(Math::kYUnitVector));
m_Camera.SetZRange(1.0f, 10000.0f);
m_CameraController.reset(new GameCore::CameraController(m_Camera, Math::Vector3(Math::kYUnitVector)));
// 在场景的update时
m_CameraController->Update(deltaT);
// 然后m_Camera中的参数就是最新的了
对于类:CameraController来说,本身实现的是右手坐标系的。我们camera已经改过了。这次把摄像机控制也一起改下。同时修改一下移动的速度
CameraController::CameraController( Camera& camera, Vector3 worldUp ) : m_TargetCamera( camera )
{
m_WorldUp = Normalize(worldUp);
m_WorldNorth = Normalize(Cross(Vector3(kXUnitVector), m_WorldUp)); // 修改为左手坐标系
m_WorldEast = Cross(m_WorldUp, m_WorldNorth); // 修改为左手坐标系
m_HorizontalLookSensitivity = 2.0f;
m_VerticalLookSensitivity = 2.0f;
m_MoveSpeed = 100.0f; // 移动速度
m_StrafeSpeed = 100.0f; // 转向速度
m_MouseSensitivityX = 1.0f;
m_MouseSensitivityY = 1.0f;
m_CurrentPitch = Sin(Dot(camera.GetForwardVec(), m_WorldUp));
Vector3 forward = Normalize(Cross(camera.GetRightVec(), m_WorldUp)); // 修改为左手坐标系
m_CurrentHeading = ATan2(-Dot(forward, m_WorldEast), Dot(forward, m_WorldNorth));
m_FineMovement = true;
m_FineRotation = true;
m_Momentum = true;
m_LastYaw = 0.0f;
m_LastPitch = 0.0f;
m_LastForward = 0.0f;
m_LastStrafe = 0.0f;
m_LastAscent = 0.0f;
}
屏蔽掉以前在update中做的摄像机代码就可以了。至于这个控制器中的其他参数,可以自行研究下。
动态索引
动态索引这个,主要就是提高效率。
当渲染目标很多时,按照我们现在做法,每一个渲染目标自身有3个特殊的缓冲区需要设置
- 转换矩阵
- 纹理SRV
- 材质
这个动态索引的方式是,在每一次渲染的时候,只设置一次纹理以及材质参数。然后通过渲染目标中设置一个索引来取出自身对应的纹理信息。
整体来说也比较简单。
- 先设置两个结构来存储所有的纹理SRV以及材质信息。
// 存储所有的纹理参数 StructuredBuffer m_mats; // 存储所有的纹理资源 D3D12_CPU_DESCRIPTOR_HANDLE m_srvs[4];
- 设置对应的根签名
// 创建根签名 m_RootSignature.Reset(4, 1); m_RootSignature.InitStaticSampler(0, Graphics::SamplerLinearWrapDesc); m_RootSignature[0].InitAsConstantBuffer(0); m_RootSignature[1].InitAsConstantBuffer(1); m_RootSignature[2].InitAsBufferSRV(0); // 存储材质信息 m_RootSignature[3].InitAsDescriptorRange(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 4); // 存储本项目中的4个纹理 m_RootSignature.Finalize(L"15 RS", D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
- shader的入参就不再细说了,shader直接看代码就好
- 在每一帧绘制时,设置所有材质和纹理信息
// 设置全部的纹理参数 gfxContext.SetBufferSRV(2, m_mats); // 设置全部的纹理资源 gfxContext.SetDynamicDescriptors(3, 0, 4, &m_srvs[0]); gfxContext.SetPipelineState(m_mapPSO[E_EPT_DEFAULT]); drawRenderItems(gfxContext, m_vecRenderItems[(int)RenderLayer::Opaque]);
MiniEngine中的InitAsDescriptorRange
这个设置根描述符参数的函数,第二个参数填入的是槽id,这里需要注意下
m_RootSignature[0].InitAsDescriptorRange(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 4)
此时占用的实际是t0-t3,所以如果想再设置一个最少要从4开始。否则会报错。例如:
m_RootSignature[0].InitAsDescriptorRange(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 4); // 占据t0-t3
m_RootSignature[1].InitAsDescriptorRange(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 4, 4); // 占据t4-t7
m_RootSignature[2].InitAsDescriptorRange(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 7, 1); // t7已经被占用,根签名创建会报错
简单对比下采用动态索引和非动态索引的帧数变化
实际变化很小,因为我们的渲染目标很少