dx12 龙书第十五章学习笔记 -- 构建第一人称视角的摄像机与动态索引

24 篇文章 10 订阅

1.重温取景变换

从世界空间到观察空间(摄像机空间)的坐标变换称为取景变换(view transform),对应的矩阵叫做观察矩阵(view matrix)。

如果Q_w=(Q_x,Q_y,Q_z,1)u_w=(u_x,u_y,u_z,0)v_w=(v_x,v_y,v_z,0)w_w=(w_x,w_y,w_z,0)分别表示观察空间中的原点、x轴、y轴、z轴相对于世界空间的齐次坐标,那么从观察空间至世界空间的坐标变换矩阵为:

W=\begin{bmatrix} u_x & u_y & u_z & 0 \\ v_x & v_y & v_z & 0 \\ w_x & w_y & w_z & 0 \\ Q_x & Q_y & Q_z & 1 \end{bmatrix}

利用线性变换将👇拆开:

ux+vy+wz+Q

因为坐标都是相对于世界空间的,所以构造的矩阵是观察空间向世界空间转换的矩阵。我们可以通过逆变换得到我们想要的矩阵(世界空间->观察空间):

V=W^{-1}=(RT)^{-1}=T^{-1}R^{T}= {\color{Red} \begin{bmatrix} u_x & v_x & w_x & 0 \\ u_y & v_y & w_y & 0 \\ u_z & v_z & w_z & 0 \\ -Q_x &- Q_y & -Q_z & 1 \end{bmatrix}}

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)与观测点已知,首先将他们正交规范化,然后根据👇构造观察矩阵:

V=W^{-1}=(RT)^{-1}=T^{-1}R^{T}= {\color{Red} \begin{bmatrix} u_x & v_x & w_x & 0 \\ u_y & v_y & w_y & 0 \\ u_z & v_z & w_z & 0 \\ -Q_x &- Q_y & -Q_z & 1 \end{bmatrix}}

其中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不同的是,此数组中所存的纹理的尺寸和格式可各不相同,这使得它更灵活

对于演示程序来讲,我们希望把每个渲染项所需配置的描述符数量降到最低。我们可以通过动态索引与实例化(下一章)两项技术搭配使用,效果显著,我们采取的策略如下:

  1. 创建一个存有所有材质数据的结构化缓冲区 -- 用结构化缓冲区代替常量缓冲区来存储其材质数据,我们可以在着色器程序中对结构化缓冲区进行索引,在绘制每一帧画面时,我们都将这个结构化缓冲区与渲染流水线绑定一次,使所有材质都能被着色器程序所用
  2. 通过为物体常量缓冲区添加MaterialIndex字段来指定本次绘制调用所用的材质索引 -- 我们利用此字段来在着色器程序中索引结构化缓冲区
  3. 在绘制每一帧画面时,我们直接将场景中全部纹理SRV(以描述符表的形式)与渲染流水线一次性绑定,而不是像以前一样为每个渲染项绑定SRV
  4. 向材质数据结构体中添加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

动态索引的其他三种用法:

  1. 将有着不同纹理的邻近网格合并为一个单独的渲染项,可以把这些网格的纹理与材质数据保存为顶点结构体中的一个属性 -- 可以有效的将具有不同位置大小纹理的物体合并到一个渲染项中,减少draw call
  2. 在含有不同大小与不同格式纹理的单次渲染过程中,使用多纹理贴图技术
  3. 使用SV_InstancedID作为索引来实例化具有不同纹理的不同材质的渲染项(下一章)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值