1.重温取景变换
从世界空间到观察空间(摄像机空间)的坐标变换称为取景变换(view transform),对应的矩阵叫做观察矩阵(view matrix)。
如果、、、分别表示观察空间中的原点、x轴、y轴、z轴相对于世界空间的齐次坐标,那么从观察空间至世界空间的坐标变换矩阵为:
利用线性变换将👇拆开:
因为坐标都是相对于世界空间的,所以构造的矩阵是观察空间向世界空间转换的矩阵。我们可以通过逆变换得到我们想要的矩阵(世界空间->观察空间):
2.摄像机类
#ifndef CAMERA_H
#define CAMERA_H
#include "d3dUtil.h"
class Camera
{
public:
Camera();
~Camera();
// Get/Set world camera position.
DirectX::XMVECTOR GetPosition()const;
DirectX::XMFLOAT3 GetPosition3f()const;
void SetPosition(float x, float y, float z);
void SetPosition(const DirectX::XMFLOAT3& v);
// Get camera basis vectors.
DirectX::XMVECTOR GetRight()const;
DirectX::XMFLOAT3 GetRight3f()const;
DirectX::XMVECTOR GetUp()const;
DirectX::XMFLOAT3 GetUp3f()const;
DirectX::XMVECTOR GetLook()const;
DirectX::XMFLOAT3 GetLook3f()const;
// Get frustum properties.
float GetNearZ()const;
float GetFarZ()const;
float GetAspect()const;
float GetFovY()const;
float GetFovX()const;
// Get near and far plane dimensions in view space coordinates.
float GetNearWindowWidth()const;
float GetNearWindowHeight()const;
float GetFarWindowWidth()const;
float GetFarWindowHeight()const;
// Set frustum.
void SetLens(float fovY, float aspect, float zn, float zf); // 设置视锥体相关属性
// Define camera space via LookAt parameters.
void LookAt(DirectX::FXMVECTOR pos, DirectX::FXMVECTOR target, DirectX::FXMVECTOR worldUp);
void LookAt(const DirectX::XMFLOAT3& pos, const DirectX::XMFLOAT3& target, const DirectX::XMFLOAT3& up);
// Get View/Proj matrices.
DirectX::XMMATRIX GetView()const;
DirectX::XMMATRIX GetProj()const;
DirectX::XMFLOAT4X4 GetView4x4f()const;
DirectX::XMFLOAT4X4 GetProj4x4f()const;
// Strafe/Walk the camera a distance d.
void Strafe(float d);
void Walk(float d);
// Rotate the camera.
void Pitch(float angle);
void RotateY(float angle);
// After modifying camera position/orientation, call to rebuild the view matrix.
void UpdateViewMatrix();
private:
// Camera coordinate system with coordinates relative to world space.
DirectX::XMFLOAT3 mPosition = { 0.0f, 0.0f, 0.0f };
DirectX::XMFLOAT3 mRight = { 1.0f, 0.0f, 0.0f };
DirectX::XMFLOAT3 mUp = { 0.0f, 1.0f, 0.0f };
DirectX::XMFLOAT3 mLook = { 0.0f, 0.0f, 1.0f };
// Cache frustum properties.
float mNearZ = 0.0f;
float mFarZ = 0.0f;
float mAspect = 0.0f;
float mFovY = 0.0f;
float mNearWindowHeight = 0.0f;
float mFarWindowHeight = 0.0f;
bool mViewDirty = true;
// Cache View/Proj matrices.
DirectX::XMFLOAT4X4 mView = MathHelper::Identity4x4();
DirectX::XMFLOAT4X4 mProj = MathHelper::Identity4x4();
};
#endif // CAMERA_H
相关属性成员:
- 垂直视场角(vertical field of view angle) α :mFovY
- 纵横比(aspect ratio, 宽高比) r :mAspect [比如16:9,4:3]
与摄像机变换相关的函数:
- Walk:前后移动 -- z轴方向移动
- Strafe:左右移动 -- x轴方向移动
- Pitch:抬头低头 -- 绕x轴旋转y轴
- RotateY:左右摇头 -- 绕y轴旋转z轴
- ※roll:横滚 -- 飞行游戏,绕观察向量进行旋转
[按照绕一个轴旋转另一个轴的情况,第三个轴也会发生变换]
// 移动变换的示例: 移动距离*方向向量
XMVECTOR s = XMVectorReplicate(d); // d:位移距离 此函数意思是将p复制到XMVECTOR的四个变量中
XMVECTOR l = XMLoadFloat3(&mLook);
XMVECTOR p = XMLoadFloat3(&mPosition);
XMStoreFloat3(&mPosition, XMVectorMultiplyAdd(s,l,p)); // p'=s*l+p
// 旋转变换的示例: 一个向量绕一个轴旋转多少角度 -- 内置函数得到旋转矩阵
XMMATRIX R = XMMatrixRotationAxis(XMLoadFloat(&mRight), angle);
XMStoreFloat3(&mUp, XMVector3TransfromNormal(XMLoadFloat3(&mUp), R)); // 完成矩阵乘积运算
XMStoreFloat3(&mLook, XMVector3TransfromNormal(XMLoadFloat3(&mLook), R));
构造观察矩阵:(两种方法)
方法①:右向量(right),上向量(up),观察向量(look)与观测点已知,首先将他们正交规范化,然后根据👇构造观察矩阵:
其中mRight:u;mUp: v;mLook:w;mPosition:Q;
void Camera::UpdateViewMatrix()
{
if(mViewDirty)
{
XMVECTOR R = XMLoadFloat3(&mRight);
XMVECTOR U = XMLoadFloat3(&mUp);
XMVECTOR L = XMLoadFloat3(&mLook);
XMVECTOR P = XMLoadFloat3(&mPosition);
// Keep camera's axes orthogonal to each other and of unit length.
L = XMVector3Normalize(L);
U = XMVector3Normalize(XMVector3Cross(L, R));
// U, L already ortho-normal, so no need to normalize cross product.
R = XMVector3Cross(U, L);
// Fill in the view matrix entries.
float x = -XMVectorGetX(XMVector3Dot(P, R));
float y = -XMVectorGetX(XMVector3Dot(P, U));
float z = -XMVectorGetX(XMVector3Dot(P, L));
XMStoreFloat3(&mRight, R);
XMStoreFloat3(&mUp, U);
XMStoreFloat3(&mLook, L);
mView(0, 0) = mRight.x;
mView(1, 0) = mRight.y;
mView(2, 0) = mRight.z;
mView(3, 0) = x;
mView(0, 1) = mUp.x;
mView(1, 1) = mUp.y;
mView(2, 1) = mUp.z;
mView(3, 1) = y;
mView(0, 2) = mLook.x;
mView(1, 2) = mLook.y;
mView(2, 2) = mLook.z;
mView(3, 2) = z;
mView(0, 3) = 0.0f;
mView(1, 3) = 0.0f;
mView(2, 3) = 0.0f;
mView(3, 3) = 1.0f;
mViewDirty = false;
}
}
方法②:使用DirectXMath库函数(龙书P157):
XMMATRIX XM_CALLCONV XMMatrixLookAtLH( // 返回view matrix
FXMVECTOR EyePosition, // 摄像机位置
FXMVECTOR FocusPosition, // 观察点位置
FXMVECTOR UpDirection // 世界空间中向上方向的向量 -- 一般是(0,1,0)
);
3.动态索引
动态索引的概念是:在着色器程序中对资源数组进行动态地索引。本章演示程序中,所用的资源是纹理数组。
指定索引的方法各式各样:
- 索引可以是常量缓冲区中的某个元素
- 索引可以是如SV_PrimitiveID、SV_VertexID、SV_DispatchThreadID或SV_InstanceID等类似的系统ID
- 索引可以通过计算求取
- 索引可来自纹理所存的数据
- 索引也可以出自顶点结构体中的分量
示例:用动态索引对具有4个元素的纹理数组进行索引
cbuffer cbPerDrawIndex : register(b0)
{
int gDiffuseTexIndex;
};
Texture2D gDiffuseMap[4] : register(t0);
float4 texValue = gDiffuseMap[gDuffuseTexIndex].Sample(gsamLinearWrap, gin.TexC);
龙书P417纹理数组Texture2DArray,与运用动态索引的纹理数组Texture []
// 着色器模型5.1+才支持的纹理数组 Texture2D gDiffuseMap[4] : register(t0);
它与Texture2DArray不同的是,此数组中所存的纹理的尺寸和格式可各不相同,这使得它更灵活
对于演示程序来讲,我们希望把每个渲染项所需配置的描述符数量降到最低。我们可以通过动态索引与实例化(下一章)两项技术搭配使用,效果显著,我们采取的策略如下:
- 创建一个存有所有材质数据的结构化缓冲区 -- 用结构化缓冲区代替常量缓冲区来存储其材质数据,我们可以在着色器程序中对结构化缓冲区进行索引,在绘制每一帧画面时,我们都将这个结构化缓冲区与渲染流水线绑定一次,使所有材质都能被着色器程序所用
- 通过为物体常量缓冲区添加MaterialIndex字段来指定本次绘制调用所用的材质索引 -- 我们利用此字段来在着色器程序中索引结构化缓冲区
- 在绘制每一帧画面时,我们直接将场景中全部纹理SRV(以描述符表的形式)与渲染流水线一次性绑定,而不是像以前一样为每个渲染项绑定SRV
- 向材质数据结构体中添加DiffuseMapIndex字段,以指定与材质所关联的纹理图
通过这一系列配置,我们仅需要为每个渲染项都设置一个物体常量缓冲区,通过MaterialIndex字段索引对应的材质,通过DiffuseMapIndex字段索引对应的纹理
我们是将材质常量缓冲区替换成材质结构化缓冲区,因为结构化缓冲区位于GPU显存中,所以对数据的更新需要通过上传缓冲区实现
这样我们就不需要在每一帧中对每个渲染项依次绑定材质和纹理
struct MaterialData
{
float4 DiffuseAlbedo;
float3 FresnelR0;
float Roughness;
float4x4 MatTransform;
uint DiffuseMapIndex;
uint MatPad0;
uint MatPad1;
uint MatPad2;
};
Texture2D gDiffuseMap[4] : register(t0);
StructuredBuffer<MaterialData> gMaterialData : register(t0, space1); // 将材质结构化缓冲区置于space1空间,因为纹理数组会占用t0、t1、t2、t3的space0空间,防止重叠
注意:Texture2D gDiffuseMap[4] : register(t0); -- 其实占用了寄存器t0~t3
动态索引的其他三种用法:
- 将有着不同纹理的邻近网格合并为一个单独的渲染项,可以把这些网格的纹理与材质数据保存为顶点结构体中的一个属性 -- 可以有效的将具有不同位置大小纹理的物体合并到一个渲染项中,减少draw call
- 在含有不同大小与不同格式纹理的单次渲染过程中,使用多纹理贴图技术
- 使用SV_InstancedID作为索引来实例化具有不同纹理的不同材质的渲染项(下一章)