auto
  • 自动类型推导
1
2
3
std::vector<std::string> vec;
for(auto i = vec.begin(), i != vec.end(), ++i)
{……}
  • 定义模板函数时,推到依赖模板函数的变量类型
1
2
3
4
5
template<_typename _Tx, _typename _Ty>
{
auto v = x * y;
std::cout<<v<<endl;
}
  • 返回值
1
2
3
4
5
auto multipy(_Tx x, _Ty y)->decltype(x * y)
return x * y;

auto func1(int a)->bool
{}
  • 其他
1
auto a=10,b=11.0;//报错,初始化必须统一类型
1
2
3
4
5
6
7
int a=1;
int &b =a;
auto c=b;//c为int,去除&
auto &d=b;//d为引用
const int e=10;
auto f = e;//去除const
auto &g = e;//用&,不去除const
1
2
3
int arr[3] = {1, 2, 3};
auto brr = arr; //brr 类型为int*
auto &crr = arr; //crr为数组,即crr=arr[3]
decltype

从变量或者表达式获取类型

1
2
3
4
int var;
const int&& fx();
struct A { double x; }
const A* a = new A();
1
2
3
4
decltype(var);//int
decltype(fx());//const int&&
decltype(a->x);//double
decltype((a->x));//const double&,内部括号导致语句作为表达式而不是成员访问计算。由于a声明为 const指针,因此类型是对const double的引用。

对于decltype所用的引用来说,如果变量名加上了一对括号,则得到的类型与不加括号时会有所不同。如果decltype使用的是一个不加括号的变量,则得到的结果就是该变量的类型;如果给变量加上了一层或多层括号,编译器就会把它当成是一个表达式。

C++14可以使用不带尾随返回类型的 decltype(auto) 来声明其返回类型取决于其模板参数类型的模板函数。

1
2
3
template<typename T, typename U>
decltype(auto) myFunc(T&& t, U&& u)
{ return forward<T>(t) + forward<U>(u); };
using
  • 命名空间
1
using namespace std;
  • 定义别名,类似typedef
1
using itType=std::vector<std::string>::iterator;

using与typedef的差别是 using可以用于模板部分具体化,但是typedef不能

1
2
template<class T>
using arr12=std::array<T,12>;
  • 当一个派生类私有继承基类时,基类的public和protected数据成员在派生类中是private的形式,如果想让这些继承而来的数据成员作为public或者protected成员,可以用using来重新声明。using声明语句中名字的访问权限由该using声明语句之前的访问说明符决定。
1
2
3
4
5
6
7
8
9
10
11
class Basic{
public:
int a;
int b;
};
class Bulk : private Basic{
public:
using Basic::a;
protected:
using Basic::b;

  • 关于重写重载函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A1
{
public:
void test(){cout<<"A1"; };
void test(int a){cout<<a;};
};

class A2 :public A1
{
public:
void test(){cout<<"A2";};
using A1::test;//如果不加此行,A2将只保留test()部分,导致主函数内报错
};

int main()
{
A2 a2;
a2.test(11);//如果不用using,此行报错
system("pause");
return 0;
}
nullptr

nullptr比0更安全

枚举作用域
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum EnumA
{
P1=0,
P2=1
};
enum EnumB
{
P1,//歧义,编译失败
P2//歧义,编译失败
};

int main()
{
EnumA e1=EnumA::P1;
return 0;
}

所以我们需要设置枚举的作用域,这样才可以编译的过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum class EnumA
{
P1=0,
P2=1
};
enum struct EnumB
{
P1,
P2
};

int main()
{
EnumA e1=EnumA::P1;
EnumB e2=EnumB::P2;
}
Lambda

其实lambda实现的方法是创建一个简略的类。**这个类重载了operator()**,所以表现的像个普通函数。一个lambda函数是这个类的实例。当这个类构造的时候,所有捕获的变量被传送到类中并保存为成员变量。

表达式

1
[capture](parameters)->return-type{body}

几个例子

1
2
3
4
5
[](int x, int y) { return x + y; } // 隐式返回类型
[](int& x) { ++x; } // 没有return语句 -> lambda 函数的返回类型是'void'
[]() { ++global_x; } // 没有参数,仅访问某个全局变量
[]{ ++global_x; } // 与上一个相同,省略了()
[](int x, int y) -> int { int z = x + y; return z; }//指示了返回值int
  • 关于[]的捕获信息
1
2
3
4
5
6
7
[]        //未定义变量.试图在Lambda内使用任何外部变量都是错误的.
[x, &y] //x 按值捕获, y 按引用捕获.
[&] //用到的任何外部变量都隐式按引用捕获
[=] //用到的任何外部变量都隐式按值捕获
[&, x] //x显式地按值捕获. 其它变量按引用捕获
[=, &z] //z按引用捕获. 其它变量按值捕获
[this] //截取当前类中的this指针。如果已经使用了&或者=就默认添加此选项。

示例

1
2
3
4
A1* a1=new A1();
int a2=2;
auto lambda1=[&a1,a2](int v){a1->num=v;a2++;};//错误,a2按值传递,不能修改
auto lambda1=[&a1,a2](int v)mutable {a2++;a1->num=v+a2;;};//增加mutable后可以修改,但是对原数据无影响,输出结果a1->num : 15
explicit

显式转换,禁止单参数构造函数导致的自动转换

1
2
3
4
5
6
7
8
9
10
class plebe
{
plebe(int);
explicit plebe(double);
}

plebe a,b;
a=1;//隐式转换
b=0.5;//错误
b=plebe(0.5);//显式转换
default,delete
  • default

4类特殊函数可以用default,即默认构造函数、析构函数、拷贝构造函数以及拷贝赋值运算符

特殊情况,如果类内有指针成员,特殊函数都用default,以下情况会导致错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class testA
{
public:
testA() =default;
testA(const testA& t)=default;
~testA()=default;
testA& operator=(const testA& t)=default;

int num=1;
int *p=&num;
};

testA *a1= new testA(1);
testA *a2 =a1;
delete a1;//a2的p也被释放了
cout<<*a2->p<<endl;
  • delete
类内成员初始化
1
2
3
4
5
class se
{
int mem1=10;//类内初始化
double mem2{123.65};//同上
}
右值引用
  • 左值、右值

在C++11中可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值(将亡值或纯右值)

  • 右值、将亡值
  1. C++98中右值是纯右值,纯右值指的是临时变量值、不跟对象关联的字面量值。

  2. 将亡值则是C++11新增的跟右值引用相关的表达式,这样表达式通常是将要被移动的对象(移为他用),比如返回右值引用T&&的函数返回值、std::move的返回值,或者转换为T&&的类型转换函数的返回值。

  • 左值引用、右值引用
  1. 左值引用就是对一个左值进行引用的类型。右值引用就是对一个右值进行引用的类型,事实上,由于右值通常不具有名字,我们也只能通过引用的方式找到它的存在
  2. 右值引用和左值引用都是属于引用类型。无论是声明一个左值引用还是右值引用,都必须立即进行初始化
  3. 引用类型本身自己并不拥有所绑定对象的内存,只是该对象的一个别名。左值引用是具名变量值的别名,而右值引用则是不具名(匿名)变量的别名。
1
2
3
4
5
6
7
8
9
//int &a =2;//左值引用绑定右值,fail
int b=1;//非常量左值绑定右值,ok
const int &c=b;//常量左值引用绑定非常量左值,ok
const int &d=2;//常量左值引用绑定常量左值,ok
int && f=10;//右值引用绑定右值,ok
const int &g=f;//常量左值引用绑定右值引用,ok
//int &&h=b;//右值引用绑定非常量左值,fail
int &&h=std::move(b);//move把左值转换为右值,ok
//int &&i=h;//变量是左值,h是左值,fail

image-20200707151813950

智能指针

智能指针是用对象去管理一个资源指针,同时用一个计数器计算引用当前指针对象的个数,当管理指针的对象增加或减少时,计数器也相应加1或减1,当最后一个指针管理对象销毁时,计数器为1,此时在销毁指针管理对象的同时,也对指针管理对象所管理的指针进行delete操作。

  • shared_ptr

std::shared_ptr包装了new操作符动态分配的内存,可以自由拷贝复制,基本上是使用最多的一个智能指针类型。

注意事项

我们尽量使用shared_ptr构造函数或者make_shared的方式创建shared_ptr,禁止使用裸指针赋值的方式,这样会shared_ptr难于管理指针的生命周期。

1
2
3
4
5
6
7
8
// 使用裸指针赋值构造,不推荐,裸指针被释放后,shared_ptr就野了,不能完全控制裸指针的生命周期,失去了智能指针价值
int *p = new int(10);
shared_ptr<int>sp = p;
delete p; // sp将成为野指针,使用sp将crash
// 将裸指针作为匿名指针传入构造函数,一般做法,让shared_ptr接管裸指针的生命周期,更安全
shared_ptr<int>sp1(new int(10));
// 使用make_shared,推荐做法,更符合工厂模式,可以连代码中的所有new,更高效;方法的参数是用来初始化模板类
shared_ptr<int>sp2 = make_shared<int>(10);

禁止使用指向shared_ptr的裸指针,也就是智能指针的指针,使用shared_ptr的指针指向一个shared_ptr时,引用计数并不会加一,操作shared_ptr的指针很容易就发生野指针异常。

1
2
3
4
5
6
shared_ptr<int>sp = make_shared<int>(10);
cout << sp.use_count() << endl; //输出1
shared_ptr<int> *sp1 = &sp;
cout << (*sp1).use_count() << endl; //输出依然是1
(*sp1).reset(); //sp成为野指针
cout << *sp << endl; //crash
  • weak_ptr

与std::shared_ptr最大的差别是在赋值的时候,不会引起智能指针计数增加。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class A {
public:
shared_ptr<B> b;
};
class B {
public:
weak_ptr<A> a;
};

int main()
{

shared_ptr<A> spa = make_shared<A>();
shared_ptr<B> spb = make_shared<B>();
spa->b = spb; //spb强引用计数为2,弱引用计数为1
spb->a = spa; //spa强引用计数为1,弱引用计数为2

system("pause");
return 0;
}

weak_ptr的一些用法

1
2
3
4
5
6
7
weak_ptr<T> w;  //空weak_ptr可以指向类型为T的对象
weak_ptr<T> w(shared_ptr sp); //与sp指向相同对象的weak_ptr, T必须能转换为sp指向的类型
w = p; //p可以是shared_ptr或者weak_ptr,赋值后w和p共享对象
w.reset(); //weak_ptr置为空
w.use_count(); //与w共享对象的shared_ptr的计数
w.expired(); //w.use_count()为0则返回true,否则返回false
w.lock(); //w.expired()为true,返回空的shared_ptr;否则返回指向w的shared_ptr
  • unique_ptr

unique_ptr是auto_ptr的继承者,对于同一块内存只能有一个持有者,而unique_ptr和auto_ptr唯一区别就是unique_ptr不允许赋值操作,也就是不能放在等号的右边(函数的参数和返回值例外),这一定程度避免了一些误操作导致指针所有权转移,然而,unique_str依然有提供所有权转移的方法move,调用move后,原unique_ptr就会失效

1
2
3
4
unique_ptr<int> p1=make_unique<int>(11);
cout<<*p1<<endl;//11
auto p2=move(p1);
cout<<*p1<<endl;//访问空指针,崩溃
模板和STL方面的修改
基于范围的for循环
1
2
3
double price[5]={1.1,2.2,3.3,4.4,5.5};
for(double x:prices)//也可以使用auto来申明x,编译器将自动推断
std::cout<<x<<std::endl;
新的STL容器

C++11新增的容器:forward_list,unordered_map,unordered_multimap,unordered_set,unordered_multiset

  • 关于unordered_map与map

两者的接口差不多,基本可以互换。

一般来说unordered_map的综合性能比map要好,因此,通常我们可以使用unorderd_map代替map。

以下情况推荐使用map:

关键字类型的hash函数设计的很差, 或者==运算符的性能极差, 导致hash过程太耗时;
对内存使用有严格要求, 不能接受存储hash table的额外内存开销;
元素要求按顺序存储, 或者常常需要关联访问一个元素的上一个/下一个元素, 或者需要遍历整个map。

新的STL方法

新增了STL方法cbegin()cend(),这些方法也返回一个迭代器,指向容器的第一个元素和最后一个元素的后面,因此可以用于指定包含全部元素的区间;这些新方法将元素视为const。类似的,crbegin()和crend()是rbegin()和rend()的const版本

valarray升级

C++11添加了两个函数begin()和end(),都接受valarray作为参数并且返回迭代器

摒弃export

C++98增加了关键字export,C++11摒弃了这个特性但是保留了关键字export

尖括号

为了避免与运算符>>混淆,C++要求在申明嵌套模板时使用空格将尖括号分开

1
vector<list<int>> vl;//C++11开始不再强求空格

用DirectX绘制几何体

流程简述

顶点与输入布局
  1. 创建顶点结构体
  2. 设置输入布局描述D3D12_INPUT_ELEMENT_DESC
顶点/索引缓冲区
  1. 创建顶点数据/索引数据
  2. 创建缓冲区/索引缓冲区
顶点着色/像素着色
  1. 编写HLSL着色器文件
  2. 编译着色器
常量缓冲区(上传堆,CPU可写,GPU可读)
  1. 填充常量缓冲区所需的描述符(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV)
  2. 构造缓冲区对象(单独的类),该类创建常量缓冲区
  3. 设置缓冲区大小为256B的整数倍
  4. 创建常量缓冲区视图
  5. 创建根签名

后续在提交命令列表的时候讲根签名和常量缓冲区绑定到流水线

流水线状态对象 PSO
  1. 创建PSO对象 D3D12_GRAPHICS_PIPELINE_STATE_DESC
  2. 设置PSO对象的各类参数,包括输入布局描述符、根签名、PS、VS、光栅器对象等
  3. 通过md3dDevice->CreateGraphicsPipelineState(&psoDesc,IID_PPV_ARGS(&mPSO))创建PSO
构建几何体
  1. 顶点/索引数据,布局描述
  2. 创建Geo类,并且设置类内参数,这些参数在填充视口描述的时候需要
  3. 创建顶点/索引缓冲区
  4. 在绘制阶段通过命令列表将顶点和索引视图绑定到流水线插槽
  5. 通过DrawIndexedInstanced绘制几何体
刷新(update)
  1. 根据鼠标信息重新构建观察矩阵(相对坐标的变换)
  2. 刷新常量缓冲区
Resize
  1. 重新构建透视投影矩阵
  2. 然后反馈到Update的常量缓冲区上

世界矩阵随移动/旋转/缩放而改变

观察矩阵随虚拟摄像机的移动/旋转/缩放而改变

投影矩阵随窗口大小调整而改变

刷新的数据通过根签名设置到着色器

顶点与输入布局

  • 顶点结构体
1
2
3
4
5
struct Vertex
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
};
1
2
3
4
5
6
7
struct Vertex2
{
XMFLOAT3 Pos;
XMFLOAT3 Normal;
XMFLOAT2 Tex0;
XMFLOAT2 Tex1;
}
  • 输入布局描述
1
2
3
4
5
typedef struct D3D12_INPUT_LAYOUT_DESC
{
_Field_size_full_(NumElements) const D3D12_INPUT_ELEMENT_DESC *pInputElementDescs;
UINT NumElements;//数量
} D3D12_INPUT_LAYOUT_DESC;
1
2
3
4
5
6
7
8
9
10
typedef struct D3D12_INPUT_ELEMENT_DESC
{
LPCSTR SemanticName;
UINT SemanticIndex;
DXGI_FORMAT Format;
UINT InputSlot;
UINT AlignedByteOffset;
D3D12_INPUT_CLASSIFICATION InputSlotClass;
UINT InstanceDataStepRate;
} D3D12_INPUT_ELEMENT_DESC;
  1. SemanticName:语义,用于将顶点结构体中的元素与顶点着色器输入签名(参数列表)中的元素映射起来
  2. SemanticIndex:附加到语义的索引。
  3. Format:顶点元素格式,如DXGI_FORMAT_R32_FLOAT1D32位浮点标量
  4. InputSlot:传递元素所用的输入槽,DirectX12一共16个,索引0-15,一般用同一个输入槽即输入槽0
  5. AlignedByteOffset:顶点结构体的首地址到某点元素起始地址的偏移量
1
2
3
4
5
6
7
struct Vertex2
{
XMFLOAT3 Pos;//偏移0字节
XMFLOAT3 Normal;//偏移12字节
XMFLOAT2 Tex0;//偏移24字节
XMFLOAT2 Tex1;//偏移32字节
}
  1. InputSlotClass:一般指定为D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,另外一种D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA用于实现实例化的高级技术
  2. InstanceDataStepRate:一般设置0,如果采用实例化就用1
  • 示例
1
2
3
4
5
D3D12_INPUT_ELEMENT_DESC desc1 =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};
1
2
3
4
5
6
7
D3D12_INPUT_ELEMENT_DESC desc2 =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 24, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 32, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};

顶点缓冲区

为了使GPU可以访问顶点数组,就需要把它们放置在缓冲区的GPU资源(ID2D12Resource)

  • 顶点缓冲区:存储顶点的缓冲区

  • 缓冲区描述:CD3D12_RESOURCE_DESC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct CD3DX12_RESOURCE_DESC : public D3D12_RESOURCE_DESC
{
CD3DX12_RESOURCE_DESC()
{}
explicit CD3DX12_RESOURCE_DESC( const D3D12_RESOURCE_DESC& o ) :
D3D12_RESOURCE_DESC( o )
{}
CD3DX12_RESOURCE_DESC(
D3D12_RESOURCE_DIMENSION dimension,
UINT64 alignment,
UINT64 width,
UINT height,
UINT16 depthOrArraySize,
UINT16 mipLevels,
DXGI_FORMAT format,
UINT sampleCount,
UINT sampleQuality,
D3D12_TEXTURE_LAYOUT layout,
D3D12_RESOURCE_FLAGS flags )
/**********/
}

此描述继承自D3D_RESOURCE_DESC,重点实现了几个比较便捷的方法如

1
2
3
4
5
6
7
8
static inline CD3DX12_RESOURCE_DESC Buffer( 
UINT64 width,
D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE,
UINT64 alignment = 0 )
{
return CD3DX12_RESOURCE_DESC( D3D12_RESOURCE_DIMENSION_BUFFER, alignment, width, 1, 1, 1,
DXGI_FORMAT_UNKNOWN, 1, 0, D3D12_TEXTURE_LAYOUT_ROW_MAJOR, flags );
}

其他还有比如CD3DX12_RESOURCE_DESC::Tex1D,CD3DX12_RESOURCE_DESC::2D等便捷方法

然后通过ID3D12Device::CreateCommittedResource方法创建ID3D12Resource对象

  • 示例:创建默认缓冲区函数申明
1
2
3
4
5
6
7
8
// Create the actual default buffer resource.
ThrowIfFailed(device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(byteSize),
D3D12_RESOURCE_STATE_COMMON,
nullptr,
IID_PPV_ARGS(defaultBuffer.GetAddressOf())));

DX12中所有资源都用ID3D12Resource接口表示,DX11用各种比如ID3D11Buffers等表示

为了优化性能,静态几何体的顶点缓冲区置于默认堆中

因为CPU不能向默认堆(D3D12_HEAP_TYPE_DEFAULT)中的顶点缓冲区写入数据,所以就需要用到一个中介

  • 上传缓冲区
    为了将数据从CPU复制到GPU显存中,即将顶点数据从系统内存复制到上传缓冲区,然后再复制到真正的顶点缓冲区中

  • 示例:创建默认缓冲区函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
Microsoft::WRL::ComPtr<ID3D12Resource> d3dUtil::CreateDefaultBuffer(
ID3D12Device* device,
ID3D12GraphicsCommandList* cmdList,
const void* initData,
UINT64 byteSize,
Microsoft::WRL::ComPtr<ID3D12Resource>& uploadBuffer)
{
ComPtr<ID3D12Resource> defaultBuffer;

//创建实际的默认缓冲区资源
ThrowIfFailed(device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(byteSize),
D3D12_RESOURCE_STATE_COMMON,
nullptr,
IID_PPV_ARGS(defaultBuffer.GetAddressOf())));

//为了将CPU端内存中的数据复制到缓冲区,我们就创建处于中介位置的上传堆
ThrowIfFailed(device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(byteSize),
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(uploadBuffer.GetAddressOf())));


// 描述我们希望复制到默认缓冲区的数据
D3D12_SUBRESOURCE_DATA subResourceData = {};
subResourceData.pData = initData;
subResourceData.RowPitch = byteSize;
subResourceData.SlicePitch = subResourceData.RowPitch;

//将数据复制到默认缓冲区的流程
//UpdateSubresources辅助函数将数据从CPU复制到上传堆
//再通过ID3D12CommandList::CopySubresourceRegion函数复制到mBuff中
cmdList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(defaultBuffer.Get(),
D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST));
UpdateSubresources<1>(cmdList, defaultBuffer.Get(), uploadBuffer.Get(), 0, 0, 1, &subResourceData);
cmdList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(defaultBuffer.Get(),
D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_GENERIC_READ));

// uploadBuffer必须在调用方法以后依然存在,因为命令列表的复制操作可能未执行,等调用者复制完消息以后才可以释放


return defaultBuffer;
}
  • D3D12_SUBRESOURCE_DATA
1
2
3
4
5
6
typedef struct D3D12_SUBRESOURCE_DATA
{
const void *pData;//初始化缓冲区所用的数据指针
LONG_PTR RowPitch;//对于缓冲区,此参数为欲复制数据的字节数
LONG_PTR SlicePitch;//同上
} D3D12_SUBRESOURCE_DATA;
  • 示例:创建有立方体8个顶点的默认缓冲区
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 std::array<Vertex, 8> vertices =
{
Vertex({ XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT4(Colors::White) }),
Vertex({ XMFLOAT3(-1.0f, +1.0f, -1.0f), XMFLOAT4(Colors::Black) }),
Vertex({ XMFLOAT3(+1.0f, +1.0f, -1.0f), XMFLOAT4(Colors::Red) }),
Vertex({ XMFLOAT3(+1.0f, -1.0f, -1.0f), XMFLOAT4(Colors::Green) }),
Vertex({ XMFLOAT3(-1.0f, -1.0f, +1.0f), XMFLOAT4(Colors::Blue) }),
Vertex({ XMFLOAT3(-1.0f, +1.0f, +1.0f), XMFLOAT4(Colors::Yellow) }),
Vertex({ XMFLOAT3(+1.0f, +1.0f, +1.0f), XMFLOAT4(Colors::Cyan) }),
Vertex({ XMFLOAT3(+1.0f, -1.0f, +1.0f), XMFLOAT4(Colors::Magenta) })
};

const UINT vbByteSize = (UINT)vertices.size() * sizeof(Vertex);
Microsoft::WRL::ComPtr<ID3D12Resource> VertexBufferGPU = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> VertexBufferUploader = nullptr;

mBoxGeo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), vertices.data(), vbByteSize, mBoxGeo->VertexBufferUploader);

image-20200630173313574

  • 顶点缓冲区视图:为了将顶点缓冲区绑定到渲染流水线
1
2
3
4
5
6
typedef struct D3D12_VERTEX_BUFFER_VIEW
{
D3D12_GPU_VIRTUAL_ADDRESS BufferLocation;
UINT SizeInBytes;
UINT StrideInBytes;
}
  1. BufferLocation:顶点缓冲区的虚拟地址,通过ID3D12Resource::GetGPUVirtualAddress方法得到此地址
  2. SizeInBytes:顶点缓冲区大小(字节)
  3. StrideInBytes:每个顶点元素占用的字节

然后将顶点缓冲区视图和渲染流水线上的一个输入槽绑定

1
2
3
4
//第一个参数:所用的输入槽,一般用0
//第二个参数:与输入槽绑定的缓冲区数量,即pView数组中视图的数量
//视图数组的第一个元素的地址
mCommandList->IASetVertexBuffers(0, 1, &mBoxGeo->VertexBufferView());

将顶点缓冲区置入输入槽并不会进行绘制操作,仅仅是为顶点数据送至渲染流水线做好准备

真正绘制顶点的方法

1
2
3
4
5
6
7
//参数1:每个实例绘制的顶点数
//参数2:实例化高级技术,目前给1
//参数3:第一个被绘制顶点的索引
//参数4:实例化技术,目前给0
mCommandList->DrawIndexedInstanced(
mBoxGeo->DrawArgs["box"].IndexCount,
1, 0, 0, 0);

索引和索引缓冲区

  • 索引缓冲区

与顶点类似,为了使GPU可以访问索引数组,需要将它们放置于GPU的缓冲区资源内,此缓冲区即索引缓冲区

同样用CreateDefaultBuffer方法创建索引缓冲区

索引缓冲区用到结构体D3D12_INDEX_BUFFER_VIEW描述

1
2
3
4
5
6
typedef struct D3D12_INDEX_BUFFER_VIEW
{
D3D12_GPU_VIRTUAL_ADDRESS BufferLocation;//带创建索引缓冲区虚拟地址,用ID3D12Resource::GetGUPVirtualAddress()得到
UINT SizeInBytes;//索引缓冲区大小
DXGI_FORMAT Format;//必须为DXGI_FORMAT_R16_UINT类型或者DXGI_FORMAT_R32_UINT
}

同顶点缓冲区,也需要绑定到渲染流水线,通过ID3D12GraphicsCommandList::IASetIndexBuffer方法将索引缓冲区绑定到输入装配器阶段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
	std::array<std::uint16_t, 36> indices =
{
// front face
0, 1, 2,
0, 2, 3,

// back face
4, 6, 5,
4, 7, 6,

// left face
4, 5, 1,
4, 1, 0,

// right face
3, 2, 6,
3, 6, 7,

// top face
1, 5, 6,
1, 6, 2,

// bottom face
4, 0, 3,
4, 3, 7
};

const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t);

Microsoft::WRL::ComPtr<ID3D12Resource> IndexBufferGPU = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> IndexBufferUploader = nullptr;

mBoxGeo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), indices.data(), ibByteSize, mBoxGeo->IndexBufferUploader);

使用索引时,要用ID3D12GraphicsCommandList::DrawIndexedInstanced代替DrawInstanced方法

1
2
3
4
5
//第4个参数:读取顶点之前,要为每个索引都加上的整数值
//其他参考顶点绘制
mCommandList->DrawIndexedInstanced(
mBoxGeo->DrawArgs["box"].IndexCount,
1, 0, 0, 0);

将多个顶点缓冲区和索引缓冲区合并以后会需要用到第4个参数

1
2
3
mCommandList->DrawIndexedInstanced(numOfBox, 1, 0, 0, 0);
mCommandList->DrawIndexedInstanced(numOfSph, 1, firstSphIdx, firstSphIdx, 0);
mCommandList->DrawIndexedInstanced(numOfCyl, 1, firstCylIdx, firstCylIdx, 0);

image-20200624172043571

顶点着色器示例

  • 高级着色器语言:HLSL

示例

1
2
3
4
5
6
7
8
9
10
11
cbuffer cbPerObject:register(b0)
{
float4x4 gWorldViewProj;
}
void VS(float3 iPosL:POSITION,float4 iColor:COLOR,out float4 oPosH:SV_POSTION,out float4 oColor:COLOR)
{
//把顶点变换到齐次裁剪空间
oPosH = mul(float4(iPosL,1.0f),gWolrdViewProj);
//将顶点颜色信息传至像素着色器
oColor=iColor;
}

HLSL没有指针和引用,用out表示输出参数

HLSL所有函数都是lnline函数

参数POSITIONCOLOR语义将元素映射到顶点着色器对应的输入参数;同理输出参数的语义将参数映射到下一个阶段(几何着色器或者像素着色器)对应的输入参数;

SV_POSTIONSV代表系统值,此数据存有齐次裁剪空间顶点信息,指定了此语义以后使GPU进行如裁剪、深度测试等处理时,实现其他属性无法介入的有关运算

任何没有系统修饰的参数,都可以根据需求以合法的语义修饰

可以把函数的参数封装成结构体,下面是另一种实现方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct VertexIn
{
float3 PosL:POSTION;
float4 Color:COLOR;
};
struct VertexOut
{
float3 PosH:SV_POSTION;
float4 Color:COLOR;
};
VextexOut VS(VertexIn vin)
{
VertexOut vout;
vout.PosH=mul(float4(vin.PosL,1.0f),gWolrdViewProj);
vout.Color=vin.Color;
return vout;
}

如果没有使用几何着色器,那么顶点着色器必须用SV_POSITION来输出顶点在齐次裁剪空间的位置

如果使用了,可以把得到齐次裁剪空间位置的工作交给它来处理

顶点着色器/几何着色器无法完成透视除法,此阶段只能完成投影矩阵运算,透视除法由后续硬件执行

布局描述符与签名匹配问题

ID3D12Device::CreateGraphicsPipelineState函数时,需要指定布局描述符和顶点着色器,这就涉及到参数匹配问题

  • 不匹配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct Vertex
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
//XMFLOAT3 Normal;//缺少此行输入
}

D3D12_INPUT_ELEMENT_DESC[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 24, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};

struct VertexIn
{
float3 PosL:POSTION;
float4 Color:COLOR;
float3 Normal:NORMAL;
};
struct VertexOut
{
float3 PosH:SV_POSTION;
float4 Color:COLOR;
};

VertexOut VS(VertexIn vin){...};
  • 匹配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct Vertex
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
XMFLOAT3 Normal;
}

D3D12_INPUT_ELEMENT_DESC[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 24, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};

struct VertexIn
{
float3 PosL:POSTION;
float4 Color:COLOR;
//float3 Normal:NORMAL;//输入有多余的参数可以匹配
};
struct VertexOut
{
float3 PosH:SV_POSTION;
float4 Color:COLOR;
};

VertexOut VS(VertexIn vin){...};
  • 匹配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct Vertex
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
XMFLOAT3 Normal;
}

D3D12_INPUT_ELEMENT_DESC[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 24, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};

struct VertexIn
{
float3 PosL:POSTION;
float4 Color:COLOR;
int3 Normal:NORMAL;//Direct3D会对数据类型重新解释,但会报警告
};
struct VertexOut
{
float3 PosH:SV_POSTION;
float4 Color:COLOR;
};

VertexOut VS(VertexIn vin){...};

像素着色器示例

光栅化阶段先对顶点着色器进行插值计算,然后把数据传递至像素着色器作为输出

像素着色器针对每一个像素片段而运行的函数,但是部分像素片段不会传入或者留存在后台缓冲区,会在深度测试等情况被丢弃掉;即像素是最终写入后台缓冲区的数据,像素片段是候选像素

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cbuffer cbPerObject:register(b0)
{
float4x4 gWorldViewProj;
}
void VS(float3 iPosL:POSITION,float4 iColor:COLOR,out float4 oPosH:SV_POSTION,out float4 oColor:COLOR)
{
//把顶点变换到齐次裁剪空间
oPosH = mul(float4(iPosL,1.0f),gWolrdViewProj);
//将顶点颜色信息传至像素着色器
oColor=iColor;
}

float4 PS(float4 posH:SV_POSITION,float4 color:COLOR):SV_TARGET
{
return color;
}

参数列表后的SV_TARGET表示该返回值的类型樱岛与渲染目标格式相匹配(render target format )1

同样可以利用输入输出结构体来重写上述代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct VertexIn
{
float3 PosL:POSTION;
float4 Color:COLOR;

};
struct VertexOut
{
float3 PosH:SV_POSTION;
float4 Color:COLOR;
};
VextexOut VS(VertexIn vin)
{
VertexOut vout;
vout.PosH=mul(float4(vin.PosL,1.0f),gWolrdViewProj);
vout.Color=vin.Color;
return vout;
}
float4 PS(VertexOut pin):VS_TARGET
{
return pin.Color;
}

常量缓冲区

创建常量缓冲区

常量缓冲区也是一种GPU资源ID3D12Resource

1
2
3
4
cbuffer cbPerObject:register(b0)
{
float4x4 gWorldViewProj;
}

此代码中的cbuffer对象(常量缓冲区)的名称就是cbPerObject,存储一个4x4矩阵gWorldViewProj

此矩阵表示把一个点从局部空间变换到齐次裁剪空间所用到的由世界、视图和投影3种变换组合而成的矩阵

与顶点和索引缓冲区不同,从常量缓冲区由CPU每帧更新顶一次

常量缓冲区创建到上传堆而非默认堆,使得我们从CPU端更新常量

常量缓冲区的硬件有特别要求,大小必须为256B的整数倍

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct ObjectConstants
{
XMFLOAT4X4 WorldViewProj = MathHelper::Identity4x4();
};

UINT mElementByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(T));//换算使得缓冲区大小保持在256B的倍数

Microsoft::WRL::ComPtr<ID3D12Resource> mUploadBuffer;
ThrowIfFailed(device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(mElementByteSize*elementCount),
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&mUploadBuffer)));

mUploadCBuffer存储了一个ObjectConstants类型的常量缓冲区数组

绘制物体时,只要将常量缓冲区视图CBV绑定到那个存有物体相应常量缓冲区的子区域即可

  • 着色器模型5.1
1
2
3
4
5
6
struct ObjectConstants
{
float4x4 gWorldViewProj;
uint matIndex;
};
ConstantBuffer<ObjectConstants> gObjConstants : register(b0);

常量缓冲区的数据元素被定义在一个单独的结构体中,随后用这个结构来创建一个常量缓冲区,然后就可以在着色器里访问常量缓冲区中的各个字段

1
uint index = gObjConstants.matIndex;

着色器模型定义了HLSL的编写规范,确定了其内置函数、着色器属性等一切语言元素

更新常量缓冲区

常量缓冲区是用D3D12_HEAP_TYPE_UPLOAD类型创建的,所以可以用CPU为常量缓冲区更新数据

1
2
3
4
5
6
Microsoft::WRL::ComPtr<ID3D12Resource> mUploadBuffer;
BYTE* mMappedData = nullptr;
//参数1:预映射的子资源,对于缓冲区来说设置为0
//参数2:指向D3D12_RANGE结构体的可选项,描述了映射范围,如果是空就对整个资源映射
//参数3:双重指针返回待映射资源书的目标内存块
ThrowIfFailed(mUploadBuffer->Map(0, nullptr, reinterpret_cast<void**>(&mMappedData)));
1
memcpy(mMappedData,&data,dataSizeInBytes);//用此函数将数据从系统内存复制到常量缓冲区

等更新完成,然后在释放映射内存之前对其进行Unmap(取消映射)操作

1
2
3
if(mUploadBuffer != nullptr)
mUploadBuffer->Unmap(0,nullptr);
mMappedData = nullptr;

Unmap的第一个参数是子资源索引,第二个是指向D3D12_RANGE结构体的指针,描述取消映射的内存范围,如空则整个资源映射。

上传缓冲区辅助函数
  • UploadBuffer类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
template<typename T>
class UploadBuffer
{
public:
UploadBuffer(ID3D12Device* device, UINT elementCount, bool isConstantBuffer) :
mIsConstantBuffer(isConstantBuffer)
{
mElementByteSize = sizeof(T);

//常量缓冲区的大小为256B的整数倍
//长度者两种规格来查看常量数据
//UINT64 SizeInBytes;//256的整数倍
//UINT OffsetInBytes;//256的整数倍
if(isConstantBuffer)
mElementByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(T));

ThrowIfFailed(device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(mElementByteSize*elementCount),
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&mUploadBuffer)));

ThrowIfFailed(mUploadBuffer->Map(0, nullptr, reinterpret_cast<void**>(&mMappedData)));

//只要还会需改当前的资源,我们就无需取消映射
// 但是在资源被GPU使用期间,我们千万不可向该资源进行写操作(所以需要借助同步技术)
}

UploadBuffer(const UploadBuffer& rhs) = delete;
UploadBuffer& operator=(const UploadBuffer& rhs) = delete;
~UploadBuffer()
{
if(mUploadBuffer != nullptr)
mUploadBuffer->Unmap(0, nullptr);

mMappedData = nullptr;
}

ID3D12Resource* Resource()const
{
return mUploadBuffer.Get();
}

void CopyData(int elementIndex, const T& data)
{
memcpy(&mMappedData[elementIndex*mElementByteSize], &data, sizeof(T));
}

private:
Microsoft::WRL::ComPtr<ID3D12Resource> mUploadBuffer;
BYTE* mMappedData = nullptr;

UINT mElementByteSize = 0;
bool mIsConstantBuffer = false;
};

世界矩阵随移动/旋转/缩放而改变

观察矩阵随虚拟摄像机的移动/旋转/缩放而改变

投影矩阵随窗口大小调整而改变

  • OnMouseMove
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void BoxApp::OnMouseMove(WPARAM btnState, int x, int y)
{
if((btnState & MK_LBUTTON) != 0)
{
// 根据鼠标的移动距离计算旋转角度,令每个像素按此角度的1/4旋转
float dx = XMConvertToRadians(0.25f*static_cast<float>(x - mLastMousePos.x));
float dy = XMConvertToRadians(0.25f*static_cast<float>(y - mLastMousePos.y));

// 根据鼠标输入来更新摄像机绕立方体旋转的角度
mTheta += dx;
mPhi += dy;

// 限制角度mPhi的范围
mPhi = MathHelper::Clamp(mPhi, 0.1f, MathHelper::Pi - 0.1f);
}
else if((btnState & MK_RBUTTON) != 0)
{
// 使场景中的每个像素按鼠标移动距离的0.005倍进行缩放
float dx = 0.005f*static_cast<float>(x - mLastMousePos.x);
float dy = 0.005f*static_cast<float>(y - mLastMousePos.y);

// 根据鼠标的输入更新摄像机的可视范围半径
mRadius += dx - dy;

// 限制可视半径范围
mRadius = MathHelper::Clamp(mRadius, 3.0f, 15.0f);
}

mLastMousePos.x = x;
mLastMousePos.y = y;
}
  • Update
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void BoxApp::Update(const GameTimer& gt)
{
// 将球坐标(球面坐标)转换为笛卡尔坐标系
float x = mRadius*sinf(mPhi)*cosf(mTheta);
float z = mRadius*sinf(mPhi)*sinf(mTheta);
float y = mRadius*cosf(mPhi);

// 构建观察矩阵
XMVECTOR pos = XMVectorSet(x, y, z, 1.0f);
XMVECTOR target = XMVectorZero();
XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);

XMMATRIX view = XMMatrixLookAtLH(pos, target, up);
XMStoreFloat4x4(&mView, view);

XMMATRIX world = XMLoadFloat4x4(&mWorld);
XMMATRIX proj = XMLoadFloat4x4(&mProj);
XMMATRIX worldViewProj = world*view*proj;

// 用最新的 worldViewProj 矩阵来更新常量缓冲区
ObjectConstants objConstants;
XMStoreFloat4x4(&objConstants.WorldViewProj, XMMatrixTranspose(worldViewProj));
mObjectCB->CopyData(0, objConstants);
}
常量缓冲区描述符

常量缓冲区描述符都存放在以D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV类型的描述符堆里,这个堆混合存储了常量缓冲区、着色器资源和无序访问描述符

1
2
3
4
5
6
7
D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc;
cbvHeapDesc.NumDescriptors = 1;
cbvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
cbvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
cbvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&cbvHeapDesc,
IID_PPV_ARGS(&mCbvHeap)));

在创建着色器程序访问的描述符时,需要把Flags指定为DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE

然后通过如下代码创建常量缓冲区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void BoxApp::BuildConstantBuffers()
{
//此常量缓冲区存储了绘制n个物体所需的常量数据
std::unique_ptr<UploadBuffer<ObjectConstants>> mObjectCB = std::make_unique<UploadBuffer<ObjectConstants>>(md3dDevice.Get(), 1, true);

UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
//缓冲区的起始地址
D3D12_GPU_VIRTUAL_ADDRESS cbAddress = mObjectCB->Resource()->GetGPUVirtualAddress();
//偏移到常量缓冲区中绘制第i个物体所需的常量数据
int boxCBufIndex = 0;
cbAddress += boxCBufIndex*objCBByteSize;

D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;
cbvDesc.BufferLocation = cbAddress;
cbvDesc.SizeInBytes = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));

md3dDevice->CreateConstantBufferView(
&cbvDesc,
mCbvHeap->GetCPUDescriptorHandleForHeapStart());
}
根签名和描述符表
  • 根签名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//将纹理资源绑定到纹理寄存器槽0
Texture2D gDiffuseMap : register(t0);
//把下列采样器资源一次绑定到采样器槽0-5
SamplerState gsamPointWrap : register(s0);
SamplerState gsamPointClamp : register(s1);
//将常量缓冲区资源绑定到常量缓冲区寄存器槽0
cbuffer cbPerObject : register(b0)
{
float4x4 gWorld;
float4x4 gTexTransform;
}
//绘制每种材质所需的各种不同的常量数据
cbuffer cbMaterial : register(b2)
{
float4 gDiffuseAlbedo;
float3 gFresnelR0;
float gRoughness;
float4x4 gMatTransform;
}

register(*#)中的*表示寄存器传递的资源类型。

t:着色器资源视图;

s:采样器

u:无序访问视图

b:常量缓冲区视图

在执行绘制命令之前,那些应用程序将绑定到渲染流水线上的资源,它们会被映射到着色器的对应输入寄存器。

根签名一定要与使用它的着色器相兼容,在创建流水线状态对象时会对此进行验证

不同的绘制调用可能会用到一组不同的着色器程序,意味着要用到不同的根签名

在Direct3D中,根签名由ID3D12RootSignature接口表示,并用一组描述绘制调用过程中着色器所需资源的根参数定义而成。

根参数可以是根常量根描述符或者描述符表

如果把着色器程序看成是一个大函数,那顶点数据和常量数据就是从CPU传入着色器函数的参数,而根签名就好比这些参数的函数签名。所以根签名其实就是将着色器需要用到的数据绑定到对应的寄存器槽上,供着色器访问。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
oid BoxApp::BuildRootSignature()
{
// Shader programs typically require resources as input (constant buffers,
// textures, samplers). The root signature defines the resources the shader
// programs expect. If we think of the shader programs as a function, and
// the input resources as function parameters, then the root signature can be
// thought of as defining the function signature.

// 根参数可以是根常量、根描述符或者描述符表
CD3DX12_ROOT_PARAMETER slotRootParameter[1];

// 创建一个只存有一个CBV的描述符表
CD3DX12_DESCRIPTOR_RANGE cbvTable;
cbvTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);//1:表中的描述符数量,0:将此段描述符区域绑定至此基准着色器寄存器
slotRootParameter[0].InitAsDescriptorTable(1, &cbvTable);//1:描述符区域的数量;table:指向描述符区域数组的指针

// 根签名由一组根参数构成
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(1, slotRootParameter, 0, nullptr,
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

//创建仅含一个槽位的根签名
ComPtr<ID3DBlob> serializedRootSig = nullptr;
ComPtr<ID3DBlob> errorBlob = nullptr;
HRESULT hr = D3D12SerializeRootSignature(&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1,
serializedRootSig.GetAddressOf(), errorBlob.GetAddressOf());

if(errorBlob != nullptr)
{
::OutputDebugStringA((char*)errorBlob->GetBufferPointer());
}
ThrowIfFailed(hr);

ThrowIfFailed(md3dDevice->CreateRootSignature(
0,
serializedRootSig->GetBufferPointer(),
serializedRootSig->GetBufferSize(),
IID_PPV_ARGS(&mRootSignature)));
}

1
2
3
4
5
6
7
// Root parameter can be a table, root descriptor or root constants.
CD3DX12_ROOT_PARAMETER slotRootParameter[1];
cbvTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV,//描述符表的类型
1, //表中描述符的数量
0);//将这段描述符区域绑定至此基础着色器寄存器
slotRootParameter[0].InitAsDescriptorTable(1,//描述符区域的数量
&cbvTable);//指向描述符区域数组的指针

上述代码创建了一个根参数,目的是将含有一个CBV的描述符表绑定到常量缓冲区寄存器0,即HLSL代码中的register(b0)


根签名至定义了应用程序要绑定到渲染流水线的资源,没有真正的执行任何资源绑定操作

只有通过命令列表设置根签名,才可以用ID3D12GraphicsCommandList::SetGraphicsRootDescriptorTable 方法令描述符表与渲染流水线绑定

1
2
mCommandList->SetGraphicsRootDescriptorTable(0, //将根参数按此索引进行设置
mCbvHeap->GetGPUDescriptorHandleForHeapStart());//此参数指定的是将要想着色器绑定的描述符表中的第一个描述符位于描述符堆中的句柄

下列代码先将根签名和CBV堆设置到命令列表上,并随后通过设置描述符表来指定我们希望绑定到渲染流水线的资源

1
2
3
4
5
6
7
8
9
mCommandList->SetGraphicsRootSignature(mRootSignature.Get());

ID3D12DescriptorHeap* descriptorHeaps[] = { mCbvHeap.Get() };
mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);
//偏移到此次绘制调用所需的CBV处
CD3DX12_GPU_DESCRIPTOR_HANDLE cbv(mCbvHeap->GetGPUDescriptorHandleForHeapStart());
cbv.Offset(cbvIndex,mCbvSrvUavDescriptorSize);

mCommandList->SetGraphicsRootDescriptorTable(0,cbv);

·

编译着色器

着色器程序必须先被编译为一种可移植的字节码,接下来图形驱动程序将获取这些字节码,并将其重新便以为针对当前系统GPU所优化的本地指令[ATI1]

可以用如下函数在运行期间对着色器进行编译

1
2
3
4
5
6
7
8
9
10
11
HRESULT D3DCompileFromFile(
LPCWSTR pFileName,//希望编译的以.hlsl作为扩展名的源代码文件
const D3D_SHADER_MACRO *pDefines,//本书中用nullptr
ID3DInclude *pInclude,//同上
LPOCSTR pEntrypoint,//着色器入口点函数,一个.hlsl文件可能存有多个着色器程序
LPCSTR pTarget,//指定所用着色器类型和版本的字符串,本书用的是5.0和5.1
UINT Flags1,//一般用2种:D3DCOMPLIE_DEBUG/D3DCOMPILE_SKIP_OPTIMIZATIOIN,详见SDK
UINT Flags2,//本书不用
ID3DBlob **ppCode,//返回一个数据结构指针,存储着编译好的着色器对象字节码
ID3DBlob **ppErrorMsgs//返回报错信息
);
  • ID3DBlob

一段普通的内存块,有2个接口方法

  1. LPVOID GetBufferPointer:返回对象中数据的void*类型的指针,使用之前需要转换为适当类型
  2. SIZE_T GetBufferSize:返回缓冲区字节大小
  • 运行时编译着色器函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
ComPtr<ID3DBlob>  CompileShader(
const std::wstring& fileName,
const D3D_SHADER_MACRO* defines,
const std::string& enteryPoint,
const std::string& target)
{
//若处于调试模式,则使用调试标志
UINT compileFlags = 0;
#if defined(DEBUG) || defined(_DEBUG)
//用调试模式来编译着色器 | 指示编译器跳过优化阶段
compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#endif // defined(DEBUG) || defined(_DEBUG)

HRESULT hr = S_OK;

ComPtr<ID3DBlob> byteCode = nullptr;
ComPtr<ID3DBlob> errors;
hr = D3DCompileFromFile(fileName.c_str(), //hlsl源文件名
defines, //高级选项,指定为空指针
D3D_COMPILE_STANDARD_FILE_INCLUDE, //高级选项,可以指定为空指针
enteryPoint.c_str(), //着色器的入口点函数名
target.c_str(), //指定所用着色器类型和版本的字符串
compileFlags, //指示对着色器断代码应当如何编译的标志
0, //高级选项
&byteCode, //编译好的字节码
&errors); //错误信息

if (errors != nullptr)
{
OutputDebugStringA((char*)errors->GetBufferPointer());
}
ThrowIfFailed(hr);

return byteCode;
}
离线编译

离线编译的原因

  1. 对于复杂的着色器,编译耗时太长,因此借助离线编译即可缩短应用程序的加载时间
  2. 以便在早于运行时的构建处理期间提早发现编译错误
  3. 对于Windows9应用商店的应用而言,必须采取离线编译这种方式

一般用.cso为已编译的着色器对象的扩展名

使用DirectX自带的FXC命令行编译工具编译

  • 如用CMD输入代码编译
1
C:\Program Files (x86)\Windows Kits\10\bin\x86>fxc.exe C:\Users\Administrator\Desktop\color.hlsl /T vs_5_0 /Fo "color.cso" /E "VS"

成功输出color.cso文件

compilation object save succeeded; see C:\Program Files (x86)\Windows Kits\10\bin\x86\color.cso

  • 常用的编译参数
参数 描述
/Od 禁用优化(利于调试)
/Zi 开启调试信息
/T “string” 着色器模型版本,如输入vs_5_0
/E “string” 着色器入口,如案例中的VS,PS
/Fo “string” 经过编译的着色器对象字节码
/Fc “string” 输出一个着色器汇编语言清单(调试、查阅细节)

其他清单可以参考微软SDK文档

光栅器状态

光栅器状态只接受配置,非可编程,由结构体D3D12_RASTERIZER_DESC表示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct D3D12_RASTERIZER_DESC
{
D3D12_FILL_MODE FillMode;//默认:D3D12_FILL_MODE_SOLID
D3D12_CULL_MODE CullMode;//默认: D3D12_CULL_MODE_BACK
BOOL FrontCounterClockwise;//默认:false
INT DepthBias;
FLOAT DepthBiasClamp;
FLOAT SlopeScaledDepthBias;
BOOL DepthClipEnable;
BOOL MultisampleEnable;
BOOL AntialiasedLineEnable;
UINT ForcedSampleCount;
D3D12_CONSERVATIVE_RASTERIZATION_MODE ConservativeRaster;
} D3D12_RASTERIZER_DESC;

FillMode:默认是实体渲染,如果用D3D12_FILL_MODE_WIREFRAME则是线框渲染

CullMode:默认剔除背面,D3D12_CULL_MODE_NONE不剔除,D3D12_CULL_MODE_FRONT剔除正面

FrontCounterClockwise:默认false,根据观察视角,将定点顺序为顺时针方向的三角形看作正面;如true则相反

示例

1
2
3
CD3DX12_RASTERIZER_DESC rsDesc(D3D12_DEFAULT);
reDesc.FillMode = D3D12_FILL_MODE_WIREFRAME;
reDesc.CullMode = D3D12_CULL_MODE_NONE;

CD3DX12_RASTERIZER_DESC是扩展自D3DX12_RASTERIZER_DESC的结构,添加了一些辅助构造函数的工具类,如接受CD3DX12_DEFAULT参数的构造函数。

CD3DX12_DEFAULT是一个哑类型(dummy),将需要被初始化的成员重载为默认值

1
2
struct CD3DX12_DEFAULT {};
extern const DECLSPEC_SELECTANY CD3DX12_DEFAULT D3D12_DEFAULT;

流水线状态对象

ID3D12PipelineState表示流水线状态对象(PSO)

结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
typedef struct D3D12_GRAPHICS_PIPELINE_STATE_DESC
{
ID3D12RootSignature *pRootSignature;//指向一个于此PSO绑定的根签名,该根签名必须与PSO指定的着色器兼容
D3D12_SHADER_BYTECODE VS;//待绑定的顶点着色器,用结构体 D3D12_SHADER_BYTECODE表示
D3D12_SHADER_BYTECODE PS;//待绑定的像素着色器
D3D12_SHADER_BYTECODE DS;//域着色器
D3D12_SHADER_BYTECODE HS;//外壳着色器
D3D12_SHADER_BYTECODE GS;//几何着色器
D3D12_STREAM_OUTPUT_DESC StreamOutput;//流输出高级技术
D3D12_BLEND_DESC BlendState;//混合,默认指定CD3DX12_BLEND_DESC(D3D12_DEFAULT)
UINT SampleMask;//禁止的采样,默认0xffffffff都进行采样
D3D12_RASTERIZER_DESC RasterizerState;//指定用来配置光栅器的光栅化状态
D3D12_DEPTH_STENCIL_DESC DepthStencilState;//用于配置深度/模板测试的深度/模板状态,默认用D3D12_DEPTH_STENCIL_DESC(D3D12_DEFAULT)
D3D12_INPUT_LAYOUT_DESC InputLayout;//输入布局描述,有两个成员,D3D12_INPUT_ELEMENT_DESC数组和一个表示元素个数的无符号整数
D3D12_INDEX_BUFFER_STRIP_CUT_VALUE IBStripCutValue;
D3D12_PRIMITIVE_TOPOLOGY_TYPE PrimitiveTopologyType;//图元的拓扑类型,D3D12_PRIMITIVE_TOPOLOGY_TYPE
UINT NumRenderTargets;//同时所用的渲染目标数量
DXGI_FORMAT RTVFormats[ 8 ];//渲染目标格式
DXGI_FORMAT DSVFormat;//深度/模板缓冲的格式
DXGI_SAMPLE_DESC SampleDesc;//多重采样对每个像素采样的数量和质量级别
UINT NodeMask;
D3D12_CACHED_PIPELINE_STATE CachedPSO;
D3D12_PIPELINE_STATE_FLAGS Flags;
} D3D12_GRAPHICS_PIPELINE_STATE_DESC;

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc;
ZeroMemory(&psoDesc, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
psoDesc.InputLayout = { mInputLayout.data(), (UINT)mInputLayout.size() };
psoDesc.pRootSignature = mRootSignature.Get();
psoDesc.VS =
{
reinterpret_cast<BYTE*>(mvsByteCode->GetBufferPointer()),
mvsByteCode->GetBufferSize()
};
psoDesc.PS =
{
reinterpret_cast<BYTE*>(mpsByteCode->GetBufferPointer()),
mpsByteCode->GetBufferSize()
};
psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
psoDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
psoDesc.SampleMask = UINT_MAX;
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.NumRenderTargets = 1;
psoDesc.RTVFormats[0] = mBackBufferFormat;
psoDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
psoDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
psoDesc.DSVFormat = mDepthStencilFormat;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&mPSO)));

PSO的验证和创建过于耗时,所以一般在初始化期间就生成PSO

视口和裁剪矩形等属性独立于PSO

用不同的PSO绘制不同的物体

1
2
3
4
5
mCommandList->Reset(mDirectCmdListAlloc.Get(),mPSO1.Get());

mCommandList->Reset(mDirectCmdListAlloc.Get(),mPSO2.Get());

mCommandList->Reset(mDirectCmdListAlloc.Get(),mPSO3.Get());

几何图形辅助结构体

此结构体定义了MeshGeometry中存储的单个几何体

1
2
3
4
5
6
7
8
9
struct SubmeshGeometry
{
UINT IndexCount = 0;
UINT StartIndexLocation = 0;
INT BaseVertexLocation = 0;

// 通过此子网格来定义当前结构体中所存几何体的包围盒 bounding box ,后续章节会讲述
DirectX::BoundingBox Bounds;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
struct MeshGeometry
{
// 几何体网格集的名称
std::string Name;

// 系统内存中的副本,由于顶点/索引可以是泛型格式,所以用Blob类型
Microsoft::WRL::ComPtr<ID3DBlob> VertexBufferCPU = nullptr;
Microsoft::WRL::ComPtr<ID3DBlob> IndexBufferCPU = nullptr;

Microsoft::WRL::ComPtr<ID3D12Resource> VertexBufferGPU = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> IndexBufferGPU = nullptr;

Microsoft::WRL::ComPtr<ID3D12Resource> VertexBufferUploader = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> IndexBufferUploader = nullptr;

// 缓冲区数据
UINT VertexByteStride = 0;
UINT VertexBufferByteSize = 0;
DXGI_FORMAT IndexFormat = DXGI_FORMAT_R16_UINT;
UINT IndexBufferByteSize = 0;

// 一个结构体能够存储一组顶点/索引缓冲区中的多个几何体
//用下列容器来定义子网格几何体,就能单独绘制其中的子网格
std::unordered_map<std::string, SubmeshGeometry> DrawArgs;

D3D12_VERTEX_BUFFER_VIEW VertexBufferView()const
{
D3D12_VERTEX_BUFFER_VIEW vbv;
vbv.BufferLocation = VertexBufferGPU->GetGPUVirtualAddress();
vbv.StrideInBytes = VertexByteStride;
vbv.SizeInBytes = VertexBufferByteSize;

return vbv;
}

D3D12_INDEX_BUFFER_VIEW IndexBufferView()const
{
D3D12_INDEX_BUFFER_VIEW ibv;
ibv.BufferLocation = IndexBufferGPU->GetGPUVirtualAddress();
ibv.Format = IndexFormat;
ibv.SizeInBytes = IndexBufferByteSize;

return ibv;
}

// 等数据上传GPU后,我们就能释放内存了
void DisposeUploaders()
{
VertexBufferUploader = nullptr;
IndexBufferUploader = nullptr;
}
};

绘制Box

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
#include <Windows.h>
#include "../Common/d3dUtil.h"
#include "DXApp.h"
#include "../Common/UploadBuffer.h"
#include "../Common/MathHelper.h"


using namespace DirectX;
using namespace DirectX::PackedVector;
using Microsoft::WRL::ComPtr;

#define _DEBUG 1
struct Vertex
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
};

struct ObjectConstants
{
XMFLOAT4X4 WorldViewProj=MathHelper::Identity4x4();
};

class MyBox:public DXApp
{
public:
MyBox(HINSTANCE instance);
MyBox(const MyBox& rhs)=delete;
MyBox& operator=(const MyBox& rhs)=delete;
~MyBox();
virtual bool Initialize()override;




private:
virtual void OnMouseDown(WPARAM btnState, int x, int y)override;
virtual void OnMouseUp(WPARAM btnState, int x, int y)override;
virtual void OnMouseMove(WPARAM btnState, int x, int y)override;

void BuildDescriptorHeaps();//常量缓冲区描述符堆
void BuildConstantBuffers();
void BuildRootSignature();
void BuildShadersAndInputLayout();
void BuildBoxGeometry();
void BuildPSO();

virtual void Update(const GameTimer& gt)override;
virtual void Draw(const GameTimer& gt)override;
virtual void OnResize()override;

ComPtr<ID3D12DescriptorHeap> mCbvHeap = nullptr;
ComPtr<ID3D12RootSignature> mRootSignature = nullptr;
std::unique_ptr<UploadBuffer<ObjectConstants>> mObjectCB = nullptr;
//封装的几何体结构体的指针
std::unique_ptr<MeshGeometry> mBoxGeo = nullptr;

//编译材质参数
ComPtr<ID3DBlob> mvsByteCode = nullptr;
ComPtr<ID3DBlob> mpsByteCode = nullptr;
//输入布局描述
std::vector<D3D12_INPUT_ELEMENT_DESC> mInputLayout;

ComPtr<ID3D12PipelineState> mPSO = nullptr;


XMFLOAT4X4 mWorld = MathHelper::Identity4x4();
XMFLOAT4X4 mView = MathHelper::Identity4x4();
XMFLOAT4X4 mProj = MathHelper::Identity4x4();

float mTheta = 1.5f*XM_PI;
float mPhi = XM_PIDIV2;
float mRadius = 5.0f;
POINT mLastMousePos;
};

MyBox::MyBox(HINSTANCE instance):DXApp(instance)
{

}



MyBox::~MyBox()
{

}

void MyBox::Update(const GameTimer& gt)
{
// 将球坐标(球面坐标)转换为笛卡尔坐标系
float x = mRadius * sinf(mPhi)*cosf(mTheta);
float z = mRadius * sinf(mPhi)*sinf(mTheta);
float y = mRadius * cosf(mPhi);

// 构建观察矩阵
XMVECTOR pos = XMVectorSet(x, y, z, 1.0f);
XMVECTOR target = XMVectorZero();
XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);

XMMATRIX view = XMMatrixLookAtLH(pos, target, up);
XMStoreFloat4x4(&mView, view);//观察矩阵

XMMATRIX world = XMLoadFloat4x4(&mWorld);
XMMATRIX proj = XMLoadFloat4x4(&mProj);
XMMATRIX worldViewProj = world * view*proj;

// 用最新的 worldViewProj 矩阵来更新常量缓冲区
ObjectConstants objConstants;
XMStoreFloat4x4(&objConstants.WorldViewProj, XMMatrixTranspose(worldViewProj));
mObjectCB->CopyData(0, objConstants);

}

void MyBox::Draw(const GameTimer& gt)
{
ThrowIfFailed(mDirectCmdListAlloc->Reset());
ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), mPSO.Get()));

mCommandList->RSSetViewports(1,&mScreenViewport);
mCommandList->RSSetScissorRects(1,&mScissorRect);
//指示资源使用情况的状态转换。
mCommandList->ResourceBarrier(1,&CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),D3D12_RESOURCE_STATE_PRESENT,D3D12_RESOURCE_STATE_RENDER_TARGET));
//清理后台缓冲区和深度缓冲区
mCommandList->ClearRenderTargetView(CurrentBackBufferView(),Colors::LightSteelBlue,0,nullptr);
mCommandList->ClearDepthStencilView(DepthStencilView(),D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL,1.0f,0,0,nullptr);
//指定要渲染的缓冲区
mCommandList->OMSetRenderTargets(1 , &CurrentBackBufferView(),true, &DepthStencilView());

//指定cbv描述符堆成员为一个
ID3D12DescriptorHeap* descriptorHeaps[]={mCbvHeap.Get()};
mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps),descriptorHeaps);
//设置根签名
mCommandList->SetGraphicsRootSignature(mRootSignature.Get());

//顶点缓冲区视图和渲染流水线上的一个输入槽绑定
mCommandList->IASetVertexBuffers(0,1,&mBoxGeo->VertexBufferView());
mCommandList->IASetIndexBuffer(&mBoxGeo->IndexBufferView());
mCommandList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);//这里是DX11版本,没有12版本

//描述符表与渲染流水线绑定
mCommandList->SetGraphicsRootDescriptorTable(0,mCbvHeap->GetGPUDescriptorHandleForHeapStart());

mCommandList->DrawIndexedInstanced(mBoxGeo->DrawArgs["box"].IndexCount,1,0,0,0);

mCommandList->ResourceBarrier(1,&CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));


ThrowIfFailed(mCommandList->Close());

//提交命令列表,交换前后台缓冲区
ID3D12CommandList* cmdLists[]={mCommandList.Get()};
mCommandQueue->ExecuteCommandLists(_countof(cmdLists),cmdLists);


ThrowIfFailed(mSwapChain->Present(0,0));
mCurrBackBuffer=(mCurrBackBuffer+1)%SwapChainBufferCount;

FlushCommandQueue();

}

void MyBox::OnResize()
{
DXApp::OnResize();

// The window resized, so update the aspect ratio and recompute the projection matrix.
XMMATRIX P = XMMatrixPerspectiveFovLH(0.25f*MathHelper::Pi, AspectRatio(), 1.0f, 1000.0f);
XMStoreFloat4x4(&mProj, P);
}

void MyBox::OnMouseDown(WPARAM btnState, int x, int y)
{
mLastMousePos.x = x;
mLastMousePos.y = y;

SetCapture(mhMainWnd);
}

void MyBox::OnMouseUp(WPARAM btnState, int x, int y)
{
ReleaseCapture();
}

void MyBox::OnMouseMove(WPARAM btnState, int x, int y)
{
if ((btnState & MK_LBUTTON) != 0)
{
// 根据鼠标的移动距离计算旋转角度,令每个像素按此角度的1/4旋转
float dx = XMConvertToRadians(0.25f*static_cast<float>(x - mLastMousePos.x));
float dy = XMConvertToRadians(0.25f*static_cast<float>(y - mLastMousePos.y));

// 根据鼠标输入来更新摄像机绕立方体旋转的角度
mTheta -= dx;
mPhi -= dy;

//限制角度mPhi的范围
mPhi = MathHelper::Clamp(mPhi, 0.1f, MathHelper::Pi - 0.1f);
}
else if ((btnState & MK_RBUTTON) != 0)
{
// 使场景中的每个像素按鼠标移动距离的0.005倍进行缩放
float dx = 0.005f*static_cast<float>(x - mLastMousePos.x);
float dy = 0.005f*static_cast<float>(y - mLastMousePos.y);

// 根据鼠标的输入更新摄像机的可视范围半径
mRadius += dx - dy;

// 限制可视半径范围
mRadius = MathHelper::Clamp(mRadius, 3.0f, 15.0f);
}

mLastMousePos.x = x;
mLastMousePos.y = y;
}

void MyBox::BuildDescriptorHeaps()
{
D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc;
cbvHeapDesc.Flags=D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;//创建着色器程序访问的描述符时需要指定visible
cbvHeapDesc.NodeMask=0;
cbvHeapDesc.NumDescriptors=1;
cbvHeapDesc.Type=D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&cbvHeapDesc,IID_PPV_ARGS(&mCbvHeap)));

}

void MyBox::BuildConstantBuffers()
{
//此常量缓冲区存储了绘制n个物体所需的常量数据
mObjectCB=std::make_unique<UploadBuffer<ObjectConstants>>(md3dDevice.Get(),1,true);

UINT objCBByteSize=d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
//缓冲区的起始地址
D3D12_GPU_VIRTUAL_ADDRESS cbAddress=mObjectCB->Resource()->GetGPUVirtualAddress();

int boxCBufIndex=0;
cbAddress += boxCBufIndex*objCBByteSize;

D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;
cbvDesc.BufferLocation = cbAddress;
cbvDesc.SizeInBytes = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));

md3dDevice->CreateConstantBufferView(&cbvDesc,mCbvHeap->GetCPUDescriptorHandleForHeapStart());
}

void MyBox::BuildRootSignature()
{
CD3DX12_ROOT_PARAMETER slotRootParameter[1];//根签名表

CD3DX12_DESCRIPTOR_RANGE cbvTable;
//1:表中的描述符数量,0:将此段描述符区域绑定至此基准着色器寄存器
cbvTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV,1,0);
//1:描述符区域的数量;table:指向描述符区域数组的指针
slotRootParameter[0].InitAsDescriptorTable(1,&cbvTable);
// 根签名由一组根参数构成
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(1,slotRootParameter,0,nullptr,
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

ComPtr<ID3DBlob> serializedRootSig = nullptr;
ComPtr<ID3DBlob> errorBlob=nullptr;
HRESULT hr=D3D12SerializeRootSignature(&rootSigDesc,D3D_ROOT_SIGNATURE_VERSION_1,
serializedRootSig.GetAddressOf(),errorBlob.GetAddressOf());

if (errorBlob!=nullptr)
{
::OutputDebugStringA((char*)errorBlob->GetBufferPointer());
}
ThrowIfFailed(hr);

ThrowIfFailed(md3dDevice->CreateRootSignature(
0,serializedRootSig->GetBufferPointer(),
serializedRootSig->GetBufferSize(),
IID_PPV_ARGS(&mRootSignature)
));
}

void MyBox::BuildShadersAndInputLayout()
{
HRESULT hr = S_OK;
mvsByteCode = d3dUtil::CompileShader(L"Shaders\\color.hlsl", nullptr, "VS", "vs_5_0");
mpsByteCode = d3dUtil::CompileShader(L"Shaders\\color.hlsl", nullptr, "PS", "ps_5_0");

mInputLayout=
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}
};
}

void MyBox::BuildBoxGeometry()
{
std::array<Vertex,8> verteices=
{
Vertex({ XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT4(Colors::Red) }),
Vertex({ XMFLOAT3(-1.0f, +1.0f, -1.0f), XMFLOAT4(Colors::Green) }),
Vertex({ XMFLOAT3(+1.0f, +1.0f, -1.0f), XMFLOAT4(Colors::Blue) }),
Vertex({ XMFLOAT3(+1.0f, -1.0f, -1.0f), XMFLOAT4(Colors::White) }),
Vertex({ XMFLOAT3(-1.0f, -1.0f, +1.0f), XMFLOAT4(Colors::Blue) }),
Vertex({ XMFLOAT3(-1.0f, +1.0f, +1.0f), XMFLOAT4(Colors::White) }),
Vertex({ XMFLOAT3(+1.0f, +1.0f, +1.0f), XMFLOAT4(Colors::Red) }),
Vertex({ XMFLOAT3(+1.0f, -1.0f, +1.0f), XMFLOAT4(Colors::Green) })
};
std::array<std::uint16_t, 36> indices =

{
// front face
0, 1, 2,
0, 2, 3,

// back face
4, 6, 5,
4, 7, 6,

// left face
4, 5, 1,
4, 1, 0,

// right face
3, 2, 6,
3, 6, 7,

// top face
1, 5, 6,
1, 6, 2,

// bottom face
4, 0, 3,
4, 3, 7
};

const UINT vbByteSize = (UINT)verteices.size() * sizeof(Vertex);
const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t);

mBoxGeo=std::make_unique<MeshGeometry>();
mBoxGeo->Name = "boxGeo";

ThrowIfFailed(D3DCreateBlob(vbByteSize,&mBoxGeo->VertexBufferCPU));
CopyMemory(mBoxGeo->VertexBufferCPU->GetBufferPointer(),verteices.data(),vbByteSize);

ThrowIfFailed(D3DCreateBlob(ibByteSize, &mBoxGeo->IndexBufferCPU));
CopyMemory(mBoxGeo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);

//创建顶点/索引默认缓冲区
mBoxGeo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(
md3dDevice.Get(),mCommandList.Get(),verteices.data(),vbByteSize,mBoxGeo->VertexBufferUploader);
mBoxGeo->IndexBufferGPU= d3dUtil::CreateDefaultBuffer(
md3dDevice.Get(),mCommandList.Get(),indices.data(),ibByteSize,mBoxGeo->IndexBufferUploader);
//缓冲区数据
//字节跨度
mBoxGeo->VertexByteStride=sizeof(Vertex);
mBoxGeo->VertexBufferByteSize=vbByteSize;
mBoxGeo->IndexFormat=DXGI_FORMAT_R16_UINT;
mBoxGeo->IndexBufferByteSize=ibByteSize;

SubmeshGeometry subMesh;
subMesh.IndexCount=(UINT) indices.size();
subMesh.StartIndexLocation=0;
subMesh.BaseVertexLocation=0;


// 一个结构体能够存储一组顶点/索引缓冲区中的多个几何体
//用下列容器来定义子网格几何体,就能单独绘制其中的子网格
mBoxGeo->DrawArgs["box"]=subMesh;
}

void MyBox::BuildPSO()
{
//流水线状态描述
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc;
//将数据的缓冲区用0来填充,使用一块内存区域前对其进行清空处理可以避免内存数据的不确定性。
ZeroMemory(&psoDesc,sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
psoDesc.InputLayout={mInputLayout.data(),(UINT)mInputLayout.size()};
psoDesc.pRootSignature=mRootSignature.Get();
psoDesc.VS=
{
reinterpret_cast<BYTE*>(mvsByteCode->GetBufferPointer()),
mvsByteCode->GetBufferSize()
};
psoDesc.PS=
{
reinterpret_cast<BYTE*>(mpsByteCode->GetBufferPointer()),
mpsByteCode->GetBufferSize()
};
psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT); //光栅化状态
psoDesc.BlendState= CD3DX12_BLEND_DESC(D3D12_DEFAULT);
psoDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
psoDesc.SampleMask=UINT_MAX;
psoDesc.PrimitiveTopologyType=D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.NumRenderTargets=1;
psoDesc.RTVFormats[0]=mBackBufferFormat;
psoDesc.SampleDesc.Count= m4xMsaaState?4:1;
psoDesc.SampleDesc.Quality=m4xMsaaState?(m4xMsaaState-1):0;
psoDesc.DSVFormat=mDepthStencilFormat;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&psoDesc,IID_PPV_ARGS(&mPSO)));


}

bool MyBox::Initialize()
{
if (!DXApp::Initialize())
{
return false;
}
//准备初始化命令之前线重置命令列表
ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(),nullptr));

BuildDescriptorHeaps();
BuildConstantBuffers();
BuildRootSignature();
BuildShadersAndInputLayout();
BuildBoxGeometry();
BuildPSO();

ThrowIfFailed(mCommandList->Close());
ID3D12CommandList* cmdLists[]={mCommandList.Get()};
mCommandQueue->ExecuteCommandLists(_countof(cmdLists),cmdLists);

FlushCommandQueue();

return true;
}

int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd)
{

// Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
try
{
MyBox theApp(hInstance);
if (!theApp.Initialize())
return 0;

return theApp.Run();
}
catch (DxException& e)
{

MessageBox(nullptr, e.ToString().c_str(), L"HR Failed", MB_OK);

return 0;
}



}




渲染流水线

平行线最终会相交于消失点,又称为灭点

物体重叠:即不同命的物体能够遮挡住其后侧物体的局部

3D实体对象是通过三角形网格来近似表示的

颜色

分量式乘法:(r,g,b)*(a,b,c)=(ra,gb,bc)

128位颜色

每个分量用浮点表示,即4D向量 (r,g,b,a)

0<=r,g,b,a<=1

DirectXMath库对分量式乘法的支持

1
XMVECTOR XM_CAKKCIBV XMColorModulate (FXMVECTOR C1,FXMVECTOR C2);//返回C1*C2
32位颜色

每个分量仅分配1个字节,因此每个占用8字节的颜色分量就分别描述256种不同的颜色强度

即0代表无强度,256代表最大强度

DirectXMath(include<DirectXPackedVector.h>)DirectX::PackedVector命名空间提供了相应函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
namespace DirectX
{
namespace PackedVector
{
struct XMCOLOR
{
union
{
struct
{
uint8_t b; // Blue: 0/255 to 255/255
uint8_t g; // Green: 0/255 to 255/255
uint8_t r; // Red: 0/255 to 255/255
uint8_t a; // Alpha: 0/255 to 255/255

};uint32_t c;
XMCOLOR() = default;

XMCOLOR(const XMCOLOR&) = default;
XMCOLOR& operator=(const XMCOLOR&) = default;

XMCOLOR(XMCOLOR&&) = default;
XMCOLOR& operator=(XMCOLOR&&) = default;

XM_CONSTEXPR XMCOLOR(uint32_t Color) : c(Color) {}
XMCOLOR(float _r, float _g, float _b, float _a);
explicit XMCOLOR(_In_reads_(4) const float *pArray);

operator uint32_t () const { return c; }

XMCOLOR& operator= (const uint32_t Color) { c = Color; return *this; }
}
}
}
}

通过将[0,255]映射到[0,1]即可完成32位到128位的转换

转换函数

1
XMVECTOR XM_CALLCONV XMLoadColor( const XMCOLOR* pSource);
1
void XM_CALLCONV XMStoreColor( XMCOLOR* pDestination, FXMVECTOR V );

渲染流水线概述

渲染流水线(rendering pipeline)是以摄像机位观察视角而生成的2D图像的一系列完整步骤

输入装配阶段

输入装配阶段:从显存中读取集合数据(顶点和索引,vertex and index)

再装配为几何图元(几何基元),如三角形和线条

  • 顶点缓冲区:将顶点与渲染流水线绑定的特殊结构体
图元拓扑(primitive topology)
1
2
virtual void STDMETHODCALLTYPE IASetPrimitiveTopology( 
_In_ D3D12_PRIMITIVE_TOPOLOGY PrimitiveTopology) = 0;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
typedef 
enum D3D_PRIMITIVE_TOPOLOGY
{
D3D_PRIMITIVE_TOPOLOGY_UNDEFINED = 0,
D3D_PRIMITIVE_TOPOLOGY_POINTLIST = 1,
D3D_PRIMITIVE_TOPOLOGY_LINELIST = 2,
D3D_PRIMITIVE_TOPOLOGY_LINESTRIP = 3,
D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST = 4,
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP = 5,
D3D_PRIMITIVE_TOPOLOGY_LINELIST_ADJ = 10,
D3D_PRIMITIVE_TOPOLOGY_LINESTRIP_ADJ = 11,
D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ = 12,
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP_ADJ = 13,
D3D_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST = 33,
D3D_PRIMITIVE_TOPOLOGY_2_CONTROL_POINT_PATCHLIST = 34,
D3D_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST = 35,
D3D_PRIMITIVE_TOPOLOGY_4_CONTROL_POINT_PATCHLIST = 36,
D3D_PRIMITIVE_TOPOLOGY_5_CONTROL_POINT_PATCHLIST = 37,
D3D_PRIMITIVE_TOPOLOGY_6_CONTROL_POINT_PATCHLIST = 38,
D3D_PRIMITIVE_TOPOLOGY_7_CONTROL_POINT_PATCHLIST = 39,
D3D_PRIMITIVE_TOPOLOGY_8_CONTROL_POINT_PATCHLIST = 40,
D3D_PRIMITIVE_TOPOLOGY_9_CONTROL_POINT_PATCHLIST = 41,
D3D_PRIMITIVE_TOPOLOGY_10_CONTROL_POINT_PATCHLIST = 42,
D3D_PRIMITIVE_TOPOLOGY_11_CONTROL_POINT_PATCHLIST = 43,
D3D_PRIMITIVE_TOPOLOGY_12_CONTROL_POINT_PATCHLIST = 44,
D3D_PRIMITIVE_TOPOLOGY_13_CONTROL_POINT_PATCHLIST = 45,
D3D_PRIMITIVE_TOPOLOGY_14_CONTROL_POINT_PATCHLIST = 46,
D3D_PRIMITIVE_TOPOLOGY_15_CONTROL_POINT_PATCHLIST = 47,
D3D_PRIMITIVE_TOPOLOGY_16_CONTROL_POINT_PATCHLIST = 48,
D3D_PRIMITIVE_TOPOLOGY_17_CONTROL_POINT_PATCHLIST = 49,
D3D_PRIMITIVE_TOPOLOGY_18_CONTROL_POINT_PATCHLIST = 50,
D3D_PRIMITIVE_TOPOLOGY_19_CONTROL_POINT_PATCHLIST = 51,
D3D_PRIMITIVE_TOPOLOGY_20_CONTROL_POINT_PATCHLIST = 52,
D3D_PRIMITIVE_TOPOLOGY_21_CONTROL_POINT_PATCHLIST = 53,
D3D_PRIMITIVE_TOPOLOGY_22_CONTROL_POINT_PATCHLIST = 54,
D3D_PRIMITIVE_TOPOLOGY_23_CONTROL_POINT_PATCHLIST = 55,
D3D_PRIMITIVE_TOPOLOGY_24_CONTROL_POINT_PATCHLIST = 56,
D3D_PRIMITIVE_TOPOLOGY_25_CONTROL_POINT_PATCHLIST = 57,
D3D_PRIMITIVE_TOPOLOGY_26_CONTROL_POINT_PATCHLIST = 58,
D3D_PRIMITIVE_TOPOLOGY_27_CONTROL_POINT_PATCHLIST = 59,
D3D_PRIMITIVE_TOPOLOGY_28_CONTROL_POINT_PATCHLIST = 60,
D3D_PRIMITIVE_TOPOLOGY_29_CONTROL_POINT_PATCHLIST = 61,
D3D_PRIMITIVE_TOPOLOGY_30_CONTROL_POINT_PATCHLIST = 62,
D3D_PRIMITIVE_TOPOLOGY_31_CONTROL_POINT_PATCHLIST = 63,
D3D_PRIMITIVE_TOPOLOGY_32_CONTROL_POINT_PATCHLIST = 64,
D3D10_PRIMITIVE_TOPOLOGY_UNDEFINED = D3D_PRIMITIVE_TOPOLOGY_UNDEFINED,
D3D10_PRIMITIVE_TOPOLOGY_POINTLIST = D3D_PRIMITIVE_TOPOLOGY_POINTLIST,
D3D10_PRIMITIVE_TOPOLOGY_LINELIST = D3D_PRIMITIVE_TOPOLOGY_LINELIST,
D3D10_PRIMITIVE_TOPOLOGY_LINESTRIP = D3D_PRIMITIVE_TOPOLOGY_LINESTRIP,
D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP = D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
D3D10_PRIMITIVE_TOPOLOGY_LINELIST_ADJ = D3D_PRIMITIVE_TOPOLOGY_LINELIST_ADJ,
D3D10_PRIMITIVE_TOPOLOGY_LINESTRIP_ADJ = D3D_PRIMITIVE_TOPOLOGY_LINESTRIP_ADJ,
D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ,
D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP_ADJ = D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP_ADJ,
D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED = D3D_PRIMITIVE_TOPOLOGY_UNDEFINED,
D3D11_PRIMITIVE_TOPOLOGY_POINTLIST = D3D_PRIMITIVE_TOPOLOGY_POINTLIST,
D3D11_PRIMITIVE_TOPOLOGY_LINELIST = D3D_PRIMITIVE_TOPOLOGY_LINELIST,
D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP = D3D_PRIMITIVE_TOPOLOGY_LINESTRIP,
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP = D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
D3D11_PRIMITIVE_TOPOLOGY_LINELIST_ADJ = D3D_PRIMITIVE_TOPOLOGY_LINELIST_ADJ,
D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP_ADJ = D3D_PRIMITIVE_TOPOLOGY_LINESTRIP_ADJ,
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ,
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP_ADJ = D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP_ADJ,
D3D11_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_2_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_2_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_4_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_4_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_5_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_5_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_6_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_6_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_7_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_7_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_8_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_8_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_9_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_9_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_10_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_10_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_11_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_11_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_12_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_12_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_13_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_13_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_14_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_14_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_15_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_15_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_16_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_16_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_17_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_17_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_18_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_18_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_19_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_19_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_20_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_20_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_21_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_21_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_22_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_22_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_23_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_23_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_24_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_24_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_25_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_25_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_26_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_26_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_27_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_27_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_28_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_28_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_29_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_29_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_30_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_30_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_31_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_31_CONTROL_POINT_PATCHLIST,
D3D11_PRIMITIVE_TOPOLOGY_32_CONTROL_POINT_PATCHLIST = D3D_PRIMITIVE_TOPOLOGY_32_CONTROL_POINT_PATCHLIST
} D3D_PRIMITIVE_TOPOLOGY;

示例

1
2
3
4
5
6
//通过线列表来绘制对象
mCommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_LINELIST);
//通过三角形列表来绘制对象
mCommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
//通过三角形带来绘制对象
mCommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
  • 点列表 D3D_PRIMITIVE_TOPOLOGY_POINTLIST
  • 线条带 D3D_PRIMITIVE_TOPOLOGY_LINESTRIP
  • 线列表 D3D_PRIMITIVE_TOPOLOGY_LINELIST
  • 三角形带 D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP

  • 剔除(culling)问题:奇数与偶数三角形的绕序(环绕顺序)即装配图元的顶点顺序是不同的

CPU会对偶数三角形的前两个顶点顺序进行调换已达到有奇数三角形绕序相同

正确的应该是后两个顶点顺序调换,而OpenGL中才是前两个顶点调换

  • 三角形列表

D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST

三角形列表与三角形带的区别是,三角形列表的三角形可以彼此分离,所以每n*3个顶点组成n个三角形

  • 具有邻接数据的图元拓扑

对于有邻接数据的三角形列表,每个三角形都有3个与之相邻的邻接三角形

借助顶点缓冲区索引缓冲区将它们随主三角形一并提交至渲染流水线

并且要把拓扑类型指定为D3D12_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ让渲染流水线知道如何以顶点缓冲区的顶点来构建主三角形和其邻接三角形;

邻接图元的顶点只作为集合着色器的输入数据,并不会被绘制

索引
1
2
3
4
Vertex quad[6]={
v0,v1,v2,//三角形0
v0,v2,v3 //三角形1
};
  • 绕序:为三角形指定顶点顺序

顶点列表索引列表组合起来构成三角形

1
2
3
4
5
6
7
8
9
10
11
Vertex v[9]={v0,v1,v2,v3,v4,v5,v6,v7,v8};
UINT indexList[24]={
0,1,2,
0,2,3,
0,3,4,
0,4,5,
0,5,6,
0,6,7,
0,7,8,
0,8,1
}

顶点着色阶段

  • 世界变换:将局部坐标系内的坐标转换到世界坐标系
  • 世界矩阵:上述变换的矩阵

$$
W=SRT
$$

  • 观察空间:相机的局部坐标系,亦称视觉空间、摄像机空间等
  • 取景变换:世界空间至观察空间的变换;此变换的矩阵叫做观察矩阵

观察矩阵函数

1
2
3
4
5
6
inline XMMATRIX XM_CALLCONV XMMatrixLookAtLH
(
FXMVECTOR EyePosition,
FXMVECTOR FocusPosition,
FXMVECTOR UpDirection
)

示例

1
2
3
4
5
6
XMVECTOR eyePos=XMVectorSet(100,100,100,1);
XMVECTOR target=XMVectorZero();
XMVECTOR up=XMVectorSet(0,1,0,0);
XMMATRIX V=XMMatrixLookAtLH(eyePos,target,up);//返回观察矩阵
XMVECTOR v2=XMVector3TransformCoord(target,V);
cout<<v2;//输出得到(0,0,173.2),即在观察空间的坐标
  • 顶点的投影线:顶点到观察点的连线
  • 透视投影变换:3D顶点v变换至其投影线与3D投影平面的交点v

近平面(近裁剪面)n,远平面f,垂直视场角α,纵横比r,4个参数定义了以原点作为投影的中心,以Z轴进行观察的平截头体

纵横比
$$
r=w/h
$$
w:投影窗口的宽度

h:投影窗口的高度

如果后台缓冲区与投影窗口纵横比不一致,在映射的过程中会产生非均匀缩放(不等比例缩放),导致图像拉伸

  • NDC:规格化设备坐标(Normalized Device Coordinates)

  • 透视除法(齐次除法):顶点与投影矩阵相乘以后,对每个坐标除以w=z

  • 归一化深度值:通过函数 g(z)z坐标从[n,f]映射到区间[0,1]

$$
g(z)=A+ {B \over z}
$$

  • 透视投影矩阵

$$
P=\begin{bmatrix}1\over rtan({\alpha \over 2}) & 0 & 0 & 0\
0 & 1\over{tan({\alpha\over 2})} & 0 & 0 \
0 & 0 & f\over {f-n} & 1 \
0 & & 0 & -nf\over{f-n} & 0
\end{bmatrix}
$$

在顶点乘以投影矩阵后,集合体会处于所谓的齐次裁剪空间或者投影空间

完成透视除法以后,便是**规格化设备坐标(NDC)**了

  • XMMatrixPerspectiveFovLH
1
2
3
4
5
6
7
inline XMMATRIX XM_CALLCONV XMMatrixPerspectiveFovLH
(
float FovAngleY, //用弧度值表示的垂直视场角
float AspectRatio, //纵横比:宽度/高度
float NearZ, //近平面距离
float FarZ//到远平面距离
)

示例

1
2
XMMATRIX M2=XMMatrixPerspectiveFovLH(0.25f*XM_PI,1092/1080,1.0,1000);
cout<<M2;

2.41421 0 0 0
0 2.41421 0 0
0 0 1.001 1
0 0 -1.001 0

曲面细分阶段

利用镶嵌化处理技术对网格中的三角形进行细分,以此来增加物体表面上的三角形数量。

再将这些新增的三角形偏移到适当的位置,使网格表现出更加细腻的细节

  • 曲面细分的优点
  1. 实现LOD
  2. 在内存中只维护低模,在有需求时动态增添三角形,从而节省资源
  3. 处理动画和物理时使用低模,渲染时使用处理过的高模

几何着色器阶段

几何着色器阶段接受输入是完整的图元

与顶点着色器相比,几何着色器可以创建或者修改几何体

比如,将一个点或者一条线扩展为一个四边形

裁剪

对视锥体之外的物体进行裁剪

苏泽兰-霍奇曼裁剪算法

光栅化阶段

光栅化阶段(RS,栅格化):投影主屏幕上的3D三角形计算出对应的像素颜色

  • 视口变换:裁剪完成后,硬件通过透视除法将物体从齐次空间变换为规格化设备坐标(NDC)。此后顶点x、y坐标会以像素单位表示
  • 背面剔除:将背面朝向的三角形从渲染流水线中除去
  • 透视矫正插值:为得到2D空间的顶点的插值属性,对3D空间的三角形属性进行线性插值,即利用三角形属性计算出内部像素的属性
  • 像素着色器阶段(pixel shader):针对每个像素片段进行处理,根据顶点的差值属性作为输入来计算像素颜色,亦可实现如逐像素光照(per-pixel lighting)、反射以及阴影等复杂效果
  • 输入合并阶段:上述阶段生成的像素片段送至渲染流水线的输出合并阶段(OM)。丢弃部分像素(如未通过深度测试等),剩下的写入后台缓冲区,执行混合(blend)操作(透明效果也混合实现的)

向量代数

向量

DirectX3D采用的左手坐标系

  • 左手坐标系:伸出左手,手指方向对准X轴正方向,弯曲手指对象Y轴正方向,大拇指指的就是Z轴正方向
向量的基本运算
  • 两个向量相等。即u=v。当且仅当u和v的每个分量相等,即ux=vx,uy=vy, uz=vz

  • 向量的加法即两个向量对应分量都相加

  • 向量与标量相乘即每一个分量与标量相乘

  • 向量减法与加法类似

向量加法的几何意义u+v,即u的尾部与v的头部重合

向量的长度和单位向量
  • 3D向量的模可以用2次毕达哥拉斯定理(勾股定理)计算得到

image-20200118213350051

  • 一个向量的长度变为单位长度称为向量的规范化(normalizing),具体实现方法是

image-20200118213401165

点积

  • 点积(dot product)是一种计算结果为标量值的向量乘法

image-20200118213420038

  • 如果u·v=0,那么u和v正交(垂直)
  • 如果u·v>0,那么夹角为锐角,即小于90°
  • 如果>0,那么夹角为钝角
正交投影

image-20200118213446595

即向量p是v在n向量上的投影

一般的投影公式如下

image-20200118213508242

正交化

如果向量集里面所有向量都相互正交,那么此向量集是规范正交

格拉姆-施密特正交化

image-20200118213545042

从文字上描述即,将向量v添加到规范正交集中时,需要用v减去这个规范正交集中的所有其他向量{w1,w2…}方向上的分量投影,这样确保新加入的v与该集合中的其他放量相互正交

  • 假设有向量集{v0,v1},将其规范正交至集{w0,w1}

则需要进行如下操作

image-20200118213557536

  • 如果是三维向量集{v0,v1,v2},至规范正交集{w0,w1,w2}

则进行如下操作

image-20200118213605437

image-20200118213614501

叉积

叉积公式

image-20200118213635523

两个向量叉积的意义即得到正交于两个向量的向量,采用的左手坐标系,即手指指向一个向量,通过向内弯曲小于等于180°的角度后到达另外一个向量,则大拇指方向是叉积所得向量的方向

用叉积类规范正交化

image-20200118213701332

点(x,y,z)即从原点至该点的向量

用DirectXMath库

DirectXMath变量的使用规范
  • 局部变量或全局变量用XMVECTOR类型
  • 对于类中的数据成员,用XMFLOAT2\XMFLOAT3和XMFLOAT4类型
  • 在运算之前,通过加载函数将XMFLOATn类型转换成XMVECTOR类型
  • 用XMVECTOR实例来进行运算
  • 通过存储函数将XMVECTOR类型转换为XMFLOATn类型
加载方法和存储方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//将数据从XMFLOAT2类型加载到XMVECTOR类型
XMVECTOR XM_CALLCONV XMLoadFloat2(const XMFLOAT2* pSource);
//将数据从XMFLOAT3类型加载到XMVECTOR类型,XMFLOAT4类似
XMVECTOR XM_CALLCONV XMLoadFloat3(const XMFLOAT3* pSource);
//将数据从XMVECTOR类型存储到XMFLOAT2类型,XMFLOAT3以及XMFLOAT4方法类似
void XM_CALLCONV XMStoreFloat2(XMFLOAT2* pDestination,FXMVECTOR V);
//********************//
//得到XMVECTOR实例中的一个分量或者将一个方脸转换为XMVECTOR类型
float XM_CALLCONV XMVectorGetX(FXMVECTOR V);
float XM_CALLCONV XMVectorGetY(FXMVECTOR V);
float XM_CALLCONV XMVectorGetZ(FXMVECTOR V);
float XM_CALLCONV XMVectorGetX(FXMVECTOR V);

XMVECTOR XM_CALLCONV XMVectorSetX(FXMVECTOR V,float x);
XMVECTOR XM_CALLCONV XMVectorSetY(FXMVECTOR V,float y);
XMVECTOR XM_CALLCONV XMVectorSetZ(FXMVECTOR V,float z);.
XMVECTOR XM_CALLCONV XMVectorSetW(FXMVECTOR V,float w);
参数传递
传递规则
  • 前三个 XMVECTOR 参数用类型 FXMVECTOR
  • 第四个 XMVECTOR 参数用 GXMVECTOR
  • 第5、6个 XMVECTOR 参数用 HXMVECTOR
  • 其余的 XMVECTOR 参数用 CXMVECTOR

在32位windows系统,编译器将根据**_fastcall**调用约定将前3个XMVECTOR参数传递到寄存器,其余放到栈

在32位windows系统,编译器将根据**_vectorcall**调用约定将前6个XMVECTOR参数传递到寄存器,其余放到栈上

其余平台上的定义,可以参见 DirectXMath库文档中的 Library Internals下的 Calling Converntions 部分的[DirectXMath]

常向量

XMVECTOR类型的常量实例用 XMVECTORF32 表示

1
static const XMVECTORF32 v1 = { 0.5f,1.0f,1.0f,1.0f };

XMVECTORF32是一种16字节对齐的结构体,数学库中有将他转换至XMVECTOR类型的运算符

另外也可以用XMVECTORU32类型来创建由证书数据构成的XMVECTOR常向量

1
static const XMVECTORU32 v2 = { 1,2,3,4 };
重载运算符

XMVECTOR类型针对向量的加法、减法和标量乘法都重载了运算符,如下

image-20200118215001131

其他

DirectXMath库定义了一组于PI有关的常用数学常量近似值

image-20200118215207615

另外还有角度和弧度之间的转化以及比较大小的函数

image-20200118215348688

image-20200118215354016

Setter函数

DirectXMath库提供了下列函数,用来设置XMVECTOR类型中的数据

1
2
3
4
5
6
7
8
9
10
//返回零向量
XMVECTOR XM_CALLCONV XMVectorZero();
//返回{1,1,1,1}
XMVECTOR XM_CALLCONV XMVectorSplatOne();
//返回{x,y,z,w}
XMVECTOR XM_CALLCONV XMVectorSet(float x,float y,float z,float w);
//返回{value),value),value),value)}
XMVECTOR XM_CALLCONV XMVectorReplicate(float value);
//返回{Vx,Vx,Vx,Vx},同理;用SplateY,SplayteZ可以返回P{Vy...},或者{Vz..}
XMVECTOR XM_CALLCONV XMVectorSplateX(FXMVECTOR V);
使用案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
#include <stdlib.h>
#include <iostream>

using namespace DirectX;
using namespace DirectX::PackedVector;
using namespace std;


// 重写了<<运算符,使得cout函数可以输出XMVECTOR
ostream& XM_CALLCONV operator<<(ostream& os, FXMVECTOR v)
{
XMFLOAT4 dest;
XMStoreFloat4(&dest, v);
os << "(" << dest.x << "," << dest.y << "," << dest.z <<","<<dest.w<< ")" << endl;
return os;
}

int main()
{

if (!XMVerifyCPUSupport())
{
cout << "directx math not supported" << endl;
return 0;
}

static const XMVECTORF32 v1 = { 0.5f,1.0f,1.0f,1.0f };
XMVECTOR v2 = XMVectorReplicate(0.99f);
XMVECTOR v3 = { .5f,.5f,.5f,.5f };
XMVECTOR v4 = XMVectorSet(1, 2, 3, 4);
XMVECTOR v5 = XMVectorSplatOne();
XMVECTOR v6 = XMVectorZero();
XMVECTOR v7 = XMVectorSplatZ(v4);
cout << v1 << v2 << v3 << v4 << v5 << v6 << v7;



system("pause");
return 0;
}

image-20200118221313633

向量函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
XMVECTOR v1 = XMVectorSet(1, 1, 1, 4);
XMVECTOR v2 = XMVectorSet(-1, -1, -1, 5);
XMVECTOR v3 = XMVectorSet(1, 0, 0 ,0);
XMVECTOR a = XMVector4Length(v1);//||v1||2,2_范数,即平方和的更号
XMVECTOR b = XMVector4LengthSq(v1);//||v1||1,1_范数,即平方和
XMVECTOR c = v1 - v2;//减法
XMVECTOR d = 10 * v1;//乘法
XMVECTOR e = XMVector4Normalize(v1);//标准化,即v1/|v1|
XMVECTOR f = XMVector3Dot(v1,v2);//v1·v1
XMVECTOR g = XMVector3Cross(v1, v2);//v1*v1
XMVECTOR projw, perpw;
XMVector3ComponentsFromNormal(&projw, &perpw, v1, v3);//求v1在v3的proj和perp
XMVECTOR angle = XMVector4AngleBetweenVectors(v1, v2);//2个向量的夹角

cout << a<<b<<c<<d<<e<<f<<g<<projw<<perpw<<angle;

image-20200119135645869

浮点数的误差
1
2
XMVector Epsilon={0.10.10.10.1};
bool IsEqual = XMVectorNearEqual(v1, v2,Epsilon);//约等于来解决

矩阵代数

定义

m*n的矩阵M,即由m行,n列构成的矩形阵列

  • A1.*
  • A*.1

image-20200120070458975

image-20200120070505831

乘法

  • 矩阵AxB乘法的前提条件:A的列数必须于B的行数相同

乘法公式

image-20200120070807173

  • 即矩阵A的第i个行向量于B的第j个列向量点积

image-20200120070906956

  • 行列数不相等的矩阵乘法不满足交换律即 AB≠BA
向量于矩阵乘法

image-20200120071122512

image-20200120071219317

转置矩阵

转置矩阵(transpose matrix)是将原矩阵阵列的 行与列进行互换 即mn的矩阵变成nm的矩阵

**M **的转置矩阵记作 Mt

  • 转置矩阵具有下列性质

image-20200120071623379

单位矩阵

单位矩阵(identity matrix)是一种主对角线上的元素都是1,其他元素都是0的方阵,如

image-20200120071758210

  • 任何单位与单位矩阵相乘,得到的依然是原来的矩阵,而且满足交换律

MI=IM=M

矩阵的行列式

行列式记作: det A

  • 当且仅当det A≠0时,方阵A是可逆的
余子阵

n x n的矩阵A,余子阵(minor matrix) image-20200120191929162即从A中去除第 i 行和第 j 列的**(n-1)*(n-1)**矩阵

image-20200120200716480

image-20200120200727115

image-20200120200736692

行列式

image-20200120200844209

  • 对于2 x 2 的矩阵来说,行列式公式为:

image-20200120200914468

  • 3 x 3的矩阵,行列式公式为:

image-20200120201007378

  • 4 x 4的矩阵,行列式公式为:

image-20200120201040699

  • 案例:

image-20200120201108843

一个矩阵的行列式就是一个平行多面体的(定向的)体积,这个多面体的每条边对应着对应矩阵的列。如果学生得知了这个秘密(在纯粹的代数式的教育中,这个秘密被仔细的隐藏了起来),那么行列式的整个理论都将成为多重线性形式理论的一部分。倘若用别的方式来定义行列式,任何敏感的人都将会永远痛恨诸如行列式,Jacobian式,以及隐函数定理这些东西。

——俄国数学家阿诺尔德(Vladimir Arnold)《论数学教育》

伴随矩阵

image-20200120201159666

如果A中的每个元素分别计算出Cij,并将它至于矩阵CA中的第 i 行,第 j 列,那么将获得矩阵A的 代数余子式矩阵(cofactor matrix of A)

image-20200120201605278

取矩阵CA的转置矩阵,得到矩阵A的伴随矩阵(adjoint matrix of A),记作

image-20200120204905556

逆矩阵

  • 只有方阵才有逆矩阵
  • n x n矩阵M的逆也是一个n x n的矩阵,记作 M-1
  • 不是每个方阵都有逆矩阵,存在逆矩阵的方阵称为可逆矩阵(invertible matrix),不存在逆矩阵的叫做奇异矩阵(singular matrix)
  • 可逆矩阵的逆矩阵是唯一的
  • 可逆矩阵有逆矩阵相乘等到单位方阵: MM-1=M-1M=I,矩阵与其逆矩阵妈祖交换律

逆矩阵的推导公式

image-20200120205253567

  • 案例,image-20200120205340831

image-20200120205418259

  • image-20200120205505699

image-20200120205519977

  • image-20200120211359396

DirectXMath库处理矩阵阵列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
ostream& XM_CALLCONV operator<<(ostream& os, FXMVECTOR v)
{
XMFLOAT4 dest;
XMStoreFloat4(&dest, v);
os << "(" << dest.x << "," << dest.y << "," << dest.z <<","<<dest.w<< ")" ;
return os;
}

ostream& XM_CALLCONV operator<<(ostream& os, FXMMATRIX v)
{

for (int i=0;i<4;i++)
{
os << XMVectorGetX(v.r[i]) << "\t";
os << XMVectorGetY(v.r[i]) << "\t";
os << XMVectorGetZ(v.r[i]) << "\t";
os << XMVectorGetW(v.r[i]) << "\t";
os << endl;
}
return os;

}

int main()
{

if (!XMVerifyCPUSupport())
{
cout << "directx math not supported" << endl;
return 0;
}
XMMATRIX A
(
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 2.0f, 0.0f, 0.0f,
0.0f, 0.0f, 4.0f, 0.0f,
1.0f, 2.0f, 3.0f, 1.0f
);

XMMATRIX B = XMMatrixIdentity();//单位矩阵,斜线都是1
XMMATRIX C = A * B;//矩阵乘法,AI=A;
XMMATRIX D = XMMatrixTranspose(A);//转置矩阵,行列反转
XMVECTOR det = XMMatrixDeterminant(A);//得到(det A,det A,det A,det A)
XMMATRIX E = XMMatrixInverse(&det, A);//返回M逆矩阵
XMMATRIX F = A * E;//矩阵与逆矩阵乘积等于单位矩阵





cout << A << endl << B << endl << C << endl << D << endl << E << endl << det << endl << F;

system("pause");
return 0;
}

image-20200120211048937

变换

线性变换

线性变换函数满足

线性变换函数公式

image-20200121070851768

矩阵表示法

**u=(x,y,z)**也可以写作

矩阵表示法公式
image-20200121071106597
R3,标准基向量

i=(1,0,0), j=(0,1,0), k=(0,0,1)

矩阵表示法公式2

image-20200121071255330

根据线性组合,上述公式可以改写成

线性变换的矩阵表示法

image-20200121071642398

我们称矩阵 A是线性变换的矩阵表示法

缩放
定义

image-20200121071743966

  • S就是一种线性变换

image-20200121071817696

缩放变换的矩阵表达式

image-20200121071907341

  • 缩放矩阵的逆矩阵

image-20200623102531554

  • 案例

image-20200623102549550

旋转
旋转矩阵基础公式

image-20200121153741133

image-20200623102608770

  • 上述公式里,c=cosθ 而且 s=sinθ

  • 旋转矩阵有个特性:每个行向量都为单位长度且两两正交,即这些行向量都是规范正交(orthonormal)

  • 若一个矩阵的行向量都是规范正交的,则此矩阵为正交矩阵(orthogonal matrix)

正交矩阵的逆矩阵

image-20200121154123112

通俗公式

如果选择绕x、y、z轴旋转,即分别取 n =(1,0,0)、n =(0,1,0)、n =(0,0,1),那么对应的旋转矩阵是

image-20200121154343881

案例

image-20200121154450682

仿射变换

齐次坐标

  • (x,y,z,0)表示向量
  • (x,y,z,1)表示点
仿射变换定义
  • 仿射变换:一个线性变换加上一个平移变量 b,即

α(u)=τ(u)+b

  • 或者用矩阵表示法,A是一个线性变换的矩阵表示

image-20200121160932351

  • 如果w=1把坐标扩充为齐次坐标,那么把上述公式简化成

image-20200121161200597

平移
恒等变换

恒等变换是一种直接返回其输入参数的线性变换,如 I(u)=u

如果将平移变换定义为仿射变换,贼其中的线性变换就是一种恒等变换,即

image-20200121161843458

平移矩阵

image-20200121161704819

平移矩阵的逆矩阵

image-20200121161727755

案例

image-20200121161922233

缩放和旋转的放射矩阵
  • 如果放射变换的 b=0 ,则放射变换就是线性变换
  • 意味着,可以通过一个 4 x 4 的放射矩阵表达任意线性变换
4x4的缩放矩阵

image-20200121163133298

4x4的旋转矩阵

image-20200121163151822

仿射变换矩阵的几何意义

image-20200121163854031

变换的复合

假设S为缩放矩阵,T为平移矩阵,R为旋转矩阵

因为矩阵乘法(不满足交换律),所以我么可以定义C=SRT,即把3个矩阵变成1个,方便计算

坐标变换

坐标变换矩阵/标架变换矩阵公式
image-20200121170431813
结合律
  • 3个标架F,G,H
  • AF转换到G的变换矩阵
  • BG转换到H的变换矩阵
  • pF:F中的一个向量
  • 求:此向量在标架H中的坐标PH

***(pF***A)B=PH

(pG)B=PH

  • 由于矩阵乘法满足结合律,所以

pF(AB)=PH

逆矩阵

pFA=PH

PF=pHA-1

变换矩阵与坐标变换矩阵

在数学上,可以将改变几何体的变换解释为坐标变换,反之亦然

DirectXMath库的变换函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#pragma 
#include <windows.h>
#include <iostream>
#include <DirectXMath.h>
#include <DirectXPackedVector.h>

using namespace std;
using namespace DirectX;
using namespace DirectX::PackedVector;



ostream& operator<<( ostream& os, FXMVECTOR v)
{

XMFLOAT4 value;
XMStoreFloat4(&value, v);
os << "(" << value.x << "," << value.y << "," << value.z << "," << value.w << ")" << endl;
return os;
}

ostream& operator<<(ostream& os, FXMMATRIX M)
{
for (int i=0;i<4;i++)
{
os << XMVectorGetX(M.r[i]) << "\t" ;
os << XMVectorGetY(M.r[i]) << "\t" ;
os << XMVectorGetZ(M.r[i]) << "\t" ;
os << XMVectorGetW(M.r[i]) << "\t" ;
os << endl;
}
return os;
}


int main()
{

XMMATRIX m1 =
{
1.0f, 1.0f, 1.0f, 1.0f,
2.0f, 2.0f, 2.0f, 2.0f,
3.0f, 3.0f, 3.0f, 3.0f,
4.0f, 4.0f, 4.0f, 4.0f
};
//缩放系数
XMMATRIX Scale1 = XMMatrixScaling(1.0f, 2.0f, 3.0f);//缩放矩阵1
XMVECTOR v1 = XMVectorSet(0.1, 0.1, 0.1, 0.1);
XMMATRIX Scale2 = XMMatrixScalingFromVector(v1);//缩放矩阵2,根据Vector

XMMATRIX RotX = XMMatrixRotationX(90.f);//X轴旋转矩阵
XMMATRIX RotY = XMMatrixRotationY(45.f);
XMMATRIX RotZ = XMMatrixRotationZ(-180.f);
XMVECTOR Axis= XMVectorSet(1, 0, 0, 0);
XMMATRIX RotAxis = XMMatrixRotationAxis(Axis, 45.f);//根据自定义轴旋转
XMMATRIX TransT = XMMatrixTranslation(100.f, 10.f, -20.f);//位移矩阵
XMVECTOR vTransCoord = XMVector3TransformCoord(v1, TransT);//移动向量
XMVECTOR vTransNormal = XMVector3TransformNormal(v1, TransT);

cout << "m=" << endl << m1 << endl;
cout << "m*scale1=" << endl<< m1* Scale1 << endl;
cout << "m*scale2==" << endl << m1*Scale2 << endl;
cout << "RotX==" << endl << RotX << endl;
cout << "m*RotX==" << endl << m1 * RotX << endl;
cout << "v1:TransCoord==" << endl << vTransCoord << endl;
cout << "v1:TransNormal==" << endl << vTransNormal << endl;
system("pause");
return 0;
}

image-20200121182846768

Ctrl+Shift+M激活公式

上下标

a^2+\ b_{y_3}

$$
a^2+\ b_{y_3}
$$

括号

  • 中小括号

(a+b)*[c+2]
$$
(a+b)*[c+2]
$$

  • 大括号需要加\

\{1,2,3 \}
$$
{1,2,3 }
$$

  • 尖括号

\langle x \rangle
$$
\langle x \rangle
$$

  • 上下取整

\lceil x \rceil+\lfloor y\rfloor
$$
\lceil x \rceil+\lfloor y\rfloor
$$

  • 特殊括号

\begin{pmatrix} 1\ 2\\ 3\ 4 \end{pmatrix}\\ \begin{bmatrix} 1\ 2\\ 3\ 4 \end{bmatrix}\\ \begin{Bmatrix} 1\ 2\\ 3\ 4 \end{Bmatrix}\\ \begin{vmatrix} 1& 2\\ 3& 4 \end{vmatrix}\\ \begin{Vmatrix} 1& 2\\ 3& 4 \end{Vmatrix}\\
$$
\begin{pmatrix} 1\ 2\ 3\ 4 \end{pmatrix}\
\begin{bmatrix} 1\ 2\ 3\ 4 \end{bmatrix}\
\begin{Bmatrix} 1\ 2\ 3\ 4 \end{Bmatrix}\
\begin{vmatrix} 1& 2\ 3& 4 \end{vmatrix}\
\begin{Vmatrix} 1 & 2\ 3 & 4 \end{Vmatrix}\
$$

希腊字母

1.\alpha\ A\\ 2.\beta\ B\\ 3.\gamma\ \Gamma\\ 4.\delta\ \Delta\\ 5.\epsilon\ E\\ 6.\zeta\ Z\\ 7.\eta\ H\\ 8.\theta\ \Theta\\ 9.\iota\ I\\ 10.\kappa\ K\\ 11.\lambda\ \Lambda\\ 12.\mu\ M\\ 13.\nu\ N\\ 14.\xi\ \Xi\\ 15.\omicron\ O\\ 16.\pi\ \Pi\\ 17.\rho\ P\\ 18.\sigma\ \Sigma\\ 19.\tau\ T\\ 20.\upsilon\ \Upsilon\\ 21.\phi\ \Phi\\ 22.\chi\ X\\ 23.\psi\ \Psi\\ 24.\omega\ \Omega\\
$$
1.\alpha\ A\
2.\beta\ B\
3.\gamma\ \Gamma\
4.\delta\ \Delta\
5.\epsilon\ E\
6.\zeta\ Z\
7.\eta\ H\
8.\theta\ \Theta\
9.\iota\ I\
10.\kappa\ K\
11.\lambda\ \Lambda\
12.\mu\ M\
13.\nu\ N\
14.\xi\ \Xi\
15.\omicron\ O\
16.\pi\ \Pi\
17.\rho\ P\
18.\sigma\ \Sigma\
19.\tau\ T\
20.\upsilon\ \Upsilon\
21.\phi\ \Phi\
22.\chi\ X\
23.\psi\ \Psi\
24.\omega\ \Omega\
$$

根式分式

\sqrt[x+y]{\frac ab}+\sqrt{c+2\over 50+x}
$$
\sqrt[x+y]{\frac ab}+\sqrt{c+2\over 50+x}
$$

字体

ABCabc+\ \mathbb{ ABCabc}+\ \Bbb{ ABCabc黑板粗体}\\ ABCabc+\mathbf{ABCabc黑体}\\ ABCabc+\mathtt{ABCabc打印字体} \\ ABCabc+\mathrm{ABCabc罗马字体} \\ ABCabc+\mathscr{ABCabc手写字体} \\ ABCabc+\mathfrak{ABCabc德国字体Fraktur}
$$
ABCabc+\ \mathbb{ ABCabc}+\ \Bbb{ ABCabc黑板粗体}\
ABCabc+\mathbf{ABCabc黑体}\
ABCabc+\mathtt{ABCabc打印字体} \
ABCabc+\mathrm{ABCabc罗马字体} \
ABCabc+\mathscr{ABCabc手写字体} \
ABCabc+\mathfrak{ABCabc德国字体Fraktur}
$$

表格

\begin{array}{c|lcr} n & \text{Left} & \text{Center} & \text{Right} \\ \hline 1 & 0.24 & 1 & 125 \\ 2 & -1 & 189 & -8 \\ 3 & -20 & 2000 & 1+10i \\ \end{array}
$$
\begin{array}{c|lcr} n & \text{Left} & \text{Center} & \text{Right} \ \hline 1 & 0.24 & 1 & 125 \ 2 & -1 & 189 & -8 \ 3 & -20 & 2000 & 1+10i \ \end{array}
$$

矩阵

\begin{matrix} 1 & x & x^2 \\ 1 & y & y^2 \\ 1 & z & z^2 \end{matrix}
$$
\begin{matrix} 1 & x & x^2 \ 1 & y & y^2 \ 1 & z & z^2 \end{matrix}
$$

向量等顶部符号

\vec{abc} \ ,\overline b\ ,\overrightarrow{cde} \ ,\dot c\ , \dot {adb}\ ,\ddot{acd}\ ,\dddot{adfe}
$$
\vec{abc} \ ,\overline b\ ,\overrightarrow{cde} \ ,\dot c\ , \dot {adb}\ ,\ddot{acd}\ ,\dddot{adfe}
$$

对其

需要使用&来指示需要对齐的位置

\begin{align} \sqrt{37} & = \sqrt{\frac{73^2-1}{12^2}} \\ & = \sqrt{\frac{73^2}{12^2} \cdot \frac{73^2-1}{73^2}} \\ & = \frac{73}{12} \sqrt{1 - \frac{1}{73^2}} \\ & \approx \frac{73}{12} \left( 1 - \frac{1}{2 \cdot 73^2} \right) \end{align}
$$
\begin{align} \sqrt{37} & = \sqrt{\frac{73^2-1}{12^2}} \ & = \sqrt{\frac{73^2}{12^2} \cdot \frac{73^2-1}{73^2}} \ & = \frac{73}{12} \sqrt{1 - \frac{1}{73^2}} \ & \approx \frac{73}{12} \left( 1 - \frac{1}{2 \cdot 73^2} \right) \end{align}
$$

分类表达式

f(n) = \begin{cases} n/2, & \text{if $n$ is even} \\ 3n+1, & \text{if $n$ is odd} \end{cases}
$$
f(n) = \begin{cases} n/2, & \text{if $n$ is even} \ 3n+1, & \text{if $n$ is odd} \end{cases}
$$
\left. \begin{array}{l} \text{if $n$ is even:} & n/2 \\ \text{if $n$ is odd:} & 3n+1 \end{array} \right\} = f(n)
$$
\left. \begin{array}{l} \text{if $n$ is even:} & n/2 \ \text{if $n$ is odd:} & 3n+1 \end{array} \right} = f(n)
$$

公式标记与引用

a:= x^2-y^3 \tag{公式1}\label{公式1}
$$
a:= x^2-y^3 \tag{公式1}\label{公式1}
$$
a+y^3 \stackrel{\eqref{公式1}}=x^2

$$
a+y^3 \stackrel{\eqref{公式1}}=x^2
$$

其他

\to \rightarrow \leftarrow \Rightarrow \Leftarrow \mapsto
$$
\to \rightarrow \leftarrow \Rightarrow \Leftarrow \mapsto
$$
\lt \gt \le \ge \neq
$$
\lt \gt \le \ge \neq
$$

\sin x\\ \arctan_y\\ \lim_{1\to\infty}\\
$$
\sin x\
\arctan_y\
\lim_{1\to\infty}\
$$
\sum_1^n\ ,\int_1^{x+y}\
$$
\sum_1^n\ ,\int_1^{x+y}
$$

合集图片

20190703164359871

Direct3D初始化

预备知识

组件对象模型

组件对象模型(Component Object Model,COM):不受DirectX语言束缚,并且向后兼容的技术

  • 获得COM接口需要借助特定函数,而不是C++的new
  • 删除COM有Release方法,而不是delete
  • Mirrosoft::WRL::ComPtr类是Window是下的COM对象的智能指针
  • ComPtr出作用域时,它会自动调用Release方法
1
2
3
4
5
//Get: 返回一个指向此底层COM接口的指针,此方法常用于把原始COM接口指针作为参数传给函数
ComPtr<ID3D12RootSignature> mRootSignature;
...
//SetGraphicsRootSignature需要获取ID3D12RootSignature*类型的参数
mCommandList->SetGraphicsRootSignature(mRootSignature.Get());
1
2
3
4
5
6
7
//GetAddressOf:返回指向此底层COM接口指针的地址,此函数可以利用函数参数返回COM接口的指针
ComPtr<ID3D12CommandAlllocator> mDirectCmdListAlloc;
...
ThrowIfFailed(md3dDevice->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT,
mDirectCmdListAlloc.GetAddressOf());

1
//Reset:将此ComPtr实例设置为nullptr释放与之相关的所有引用,同时减少COM接口引用次数,此方法功能与将ComPtr目标实例赋值nullptr效果相同
纹理格式

2D纹理是一种由数据元素构成的矩阵,每个元素存储的都是一个像素的颜色

  • DXGI_FORMAT_R32G32B32_FLOAT:每个元素由2个32位无符号整数分量构成,存储的不一定是颜色信息

  • DXGI_FORMAT_R16G16B16A16_UNORM:每个元素由4个8位无符号分量构成,每个分量都被映射到**[0,1]**的区间

  • DXGI_FORMAT_R32G32_UINT:每个元素由2个32位无符号整数分量构成

  • DXGI_FORMAT_R8G8B8A8_UNORM:每个元素由4个8位无符号分量构成,每个分量都被映射到**[0,1]**的区间

  • DXGI_FORMAT_R8G8B8A8_SNORM:每个元素由4个8位无符号分量构成,每个分量都被映射到**[-1,1]**的区间

  • DXGI_FORMAT_R9G9B9A9_SINT:每个元素由4个8位无符号分量构成,每个分量都被映射到**[-128,127]**的区间

  • DXGI_FORMAT_R8G8B8A8_UINT:每个元素由4个8位无符号分量构成,每个分量都被映射到**[0,255]**的区间

其他格式

  • DXGI_FORMAT_R16G16B16A16_TYPELESS:每个元素由4个16位无符号分量构成,但是没有指出数据类型
交换链和页面翻转

  • 前台缓冲区和后台缓冲区在绘制渲染过程中互换,这种操作称为:呈现(presenting,亦有译作提交、显示)
  • 前后台缓冲区构成了交换链,Direct3D中用==IDXGISwapChain==接口来表示
  • 这个接口不仅储存了前后台缓冲区的纹理,还提供了修改缓冲区大小(IDXGISwapChain::ResiezeBuffers)和呈现缓冲区内容(IDXGISwapChain::Present)的方法
  • 使用2个缓冲区的情况称为双缓冲(double buffering,亦有译作双重缓冲、双倍缓冲等)
  • 还可以用更多的缓冲区,使用3个缓冲区就叫作三重缓冲(triple buffering)
深度缓冲

深度缓冲区(depth buffer):存储的非图像数据,而是特定像素的深度信息

  • 深度值范围 0.0-1.0
  • 0.0代表观察者在视锥体(视域体、视景体、视截体、视体),即观察者能看到的空间范围
  • 1.0代表观察者在视锥体中嫩通过看到的离自己最远的像素
  • 如果后台缓冲区的分辨率位1280x1024,那么深度缓冲去也应当由1280x1024

深度缓冲区的原理:计算每个像素的深度值,并执行深度测试(depth test),具有最小深度值的像素会最终写入后台缓冲

深度缓冲区也是一种纹理,用如下格式来创建

  1. DXGI_FORMAT_D32_FLOAT_S8X24_UINT:占用64位,取其中的32位指定一个浮点型深度缓冲区,另有8位无符号整数分配给模板缓冲区(stencil buffer),并且将该元素映射到[0,255]
  2. DXGI_FORMAT_D32_FLOAT:指定一个32位浮点型深度缓冲区
  3. DXGI_FORMAT_D24_UNORM_指定一个无符号的24位深度缓冲区,并将该元素映射到[0,1]区间;另有8位无符号整数分配给模板缓冲区(stencil buffer),并且将该元素映射到[0,255]
  4. DXGI_FORMAT_D16_UNORM:指定一个16位浮点型深度缓冲区,并将该元素映射到[0,1]区间
资源与描述符

资源->中间层(即描述符)->GPU

描述符

  • 一种把送往GPU的资源进行描述的轻量级结构;
  • 绘制所需的资源通过描述符绑定到渲染流水线上;
  • 为GPU解释资源,如告知Direct3D某个资源如何使用(绑定到流水线的哪个阶段);
  • 指定欲绑定资源中的局部数据
常见描述符
  1. CBV/SRV/UAV :分表表示常量缓冲区(constant buffer view)、着色器资源视图(shader resource view)和无序访问试图(unordered access view)3种资源;
  2. ***采样器(sampler,亦有译作取样器)***:表示采样器资源(用于纹理)
  3. RTV:渲染目标视图资源(render target view)
  4. DSV:深度/模板视图资源(depth/stencil view)

描述符堆:存放某种特定类描述符的内存,可以看作是描述符数组

多重采样
超级采样(SSAA)

超级采样:反走样技术

  • 使用4倍于屏幕分辨率大小的后台缓冲区和深度缓冲去;
  • 3D场景以这种更大的分辨率渲染到后台缓冲区中;
  • 当数据要从后台缓冲区调往屏幕显示的时候,会将后台缓冲区按4个像素一组进行解析(降采样,downsample),把放大的采样点数降低回原来采样点数每组用求平均值的方法得到相对平滑的像素颜色
  • 实际上是通过软件的方式提升了画面分辨率
  • 超级采样是高开销的操作,因为限速处理数量和占用内存大小都增加到了4倍,因此Direct3D支持一种性能和效果折中的反走样技术:多重采样(multisampling),记作MSAA
多重采样(MSAA)
  • 多重采样不需要对每个子像素都进行计算
  • 而是仅计算一次像素中心的颜色,在基于可视性和覆盖性将得到的颜色信息分享给其子像素
区别
  • 超级采样:图像颜色要根据每一个像素来计算,因此每个子像素都可以各具不的颜色;开销更大但是更精确

  • 多重采样:每个像素只需要计算一次,最后假尼姑得到的颜色数据复制到多边形覆盖的所有可见子像素中

用Direct3D进行多重采样
1
2
3
4
5
typedef struct DXGI_SAMPLE_DESC
{
UINT count;//指定了每个像素的采样次数,采样次数越多,代价越高
UINT Quality;;//指示用户期望的图像质量级别,不同厂家而言,这个参数相差很多
}

根据给定的纹理格式和采样数量,用ID3D12Device::CheckFeatureSupport方法查询对应的质量级别

  • 考虑到多重采样会占用内存资源,又为了保证程序性能等原因,通常会把采样数量设定位 4 或 8
  • 如果不希望使用多重采样,可以设置采样数量位1,质量设置为0
功能级别

Direct3D 11开始引用了功能级别(feature level),代码里用 D3D_FEATURE_LEVEL表示

1
2
3
4
5
6
7
8
9
10
enum D3D_FEATURE_LEVEL
{
D3D_FEATURE_LEVEL_9_1 = 0x9100,
D3D_FEATURE_LEVEL_9_2 = 0x9200,
D3D_FEATURE_LEVEL_9_3 = 0x9300,
D3D_FEATURE_LEVEL_10_0 = 0xa000,
D3D_FEATURE_LEVEL_10_1 = 0xa100,
D3D_FEATURE_LEVEL_11_0 = 0xb000,
D3D_FEATURE_LEVEL_11_1 = 0xb100
}
  • 功能级别为不同级别所支持的功能进行严格界定
DirectX图形学基础结构

DirectX图形学基础结构(DXGI)是一种与Direct3D配合使用的API

如,IDXGIFactory是DXGI中的关键接口之一,用于创建IDXGISwapChain接口以及枚举显示适配器

一个系统可能由数个显示设备,我们称每一台显示设备都是一个显示输出,用IDXGIOutput接口来表示

功能支持的检测

ID3D12Device::CheckFeatureSupport方法是检测当前图形驱动对多重过采样的支持,圆形如下

1
HRESULT ID3D12Device::CheckFeatureSupport(D3D12_FEATURE Feature,void* pFeatureSuportData,UINT FeatureSupportDataSize);
  • ***Feature:***枚举类型D3D12_FEATURE中的成员之一,用于指定我们希望检测的功能支持类型,具体如下

    • D2D12_FEATURE_D3D12_OPTIONS:检测当前图形驱动对Direct3D 12各种功能的支持情况
    • D3D12_FEATURE_ARCHITECTURE:检测图形适配器中GPU的硬件体系架构特性
    • D3D12_FEATURE_FEATURE_LEVELS:检测对功能级别的支持情况
    • D3D12_FEATURE_FORMAT_SUPPORT:检测对给定纹理格式的支持情况
    • D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS:检测对多重采样功能的支持情况
  • pFeatureSuportData:指向某种数据结构的指针,该结构中存有检索到的特定功能支持的信息,此结构体的具体类型取决于Feature参数

    • D3D12_FEATURE_D3D12_OPTIONS:返回一个D3D12_FEATURE_DATA_D3D12_OPTIONS实例
    • D3D12_FEATURE_ARCHITECTURE:返回D3D12_FEATURE_ARCHITECTURE实例
    • D3D12_FEATURE_FEATURE_LEVELS:同上类推
    • D3D12_FEATURE_FORMAT_SUPPORT:同上类推
    • D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS:同上类推
  • FeatureSupportDataSize:传回pFeatureSuportData参数中的数据结构大小

资源驻留

Direct3D12中,应用程序通过控制资源在显存中的去留,主动管理资源的驻留情况(Direct3D11中则有系统自动管理

一般情况,资源创建时就会驻留在显存中,被销毁时则清出。但是通过下面方法我们可以自己管理资源的驻留

1
2
HRESULT ID3D12Device::MakeResident(UINT NumObjects,ID3D12Pageable* const *ppObjects);
HRESULT ID3D12Device::Evict(UINT NumObjects,ID3D12Pageable* const *ppObjects);

这两个方法的第二个参数都是ID3D12Pageable资源数组,第一个参数表示该数组资源的数量

CPU和GPU的交互

  • 每个GPU都至少维护这一个命令队列(command queue,本质上是环形缓冲区,即ring buffer)。
  • 借助Direct3D API,CPU可以用命令列表(command list)将命令提交到这个队列中去
  • 新加入的命令不会立即执行
  • 假如命令列表空空如也,那么GPU会闲置
  • 假如命令列表填满,那么CPU会在某个时刻保持空闲

在Direct3D 12中,命令队列被抽象为==ID3D12CommandQueue==接口来表示,通过填写D3D12_COMMAND_QUEUE_DESC结构体来表示队列,在通过调用ID3D12Device::CreateCommandQueue方法来创建。

命令队列
创建命令队列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//创建队列智能指针
Microsoft::WRL::ComPtr<ID3D12CommandQueue> mCommandQueue;
...;

D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
//创建队列方法
//IID_PPV_ARGS辅助宏本质是将ppType强制转换为void**类型
//Direct3D 12中创建接口实例的API时,大多数都有一个参数是类型void**的待创接口COM ID
ThrowIfFailed(md3dDevice>CreateCommandQueue(&queueDesc,IID_PPV_ARGS(&mCommandQueue)));

...;
//添加命令到命令列表里
//第一个参数是待执行的命令列表数组的数量
//第二个参数是待执行的命令列表数组
mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);
//下列两个方法不是执行命令,而是将命令添加到命令列表里,还是需要通过ExecuteCommandLists方法才将命令真正加入到命令列表
mCommandQueue->DrawIndexedInstanced(36,1,0,0,0);
mCommandQueue->RSSetViewports(1,&mScreenViewport);



//结束记录命令
//必须在调用ExecuteCommandLists方法之前先关闭
mCommandQueue->Close();
内存分配器

内存分配器:存储命令列表里的命令,执行ID3D12CommandQueue::ExecuteCommandLists方法时,命令队列就会引用分配器里的命令

1
2
3
4
virtual HRESULT STDMETHODCALLTYPE CreateCommandAllocator( 
_In_ D3D12_COMMAND_LIST_TYPE type,
REFIID riid,
_COM_Outptr_ void **ppCommandAllocator) = 0;
  • type:命令列表类型
    • D3D12_COMMAND_LIST_TYPE_DIRECT:GPU可直接执行的命令
    • D3D12_COMMAND_LIST_TYPE_BUNDLE:打包的命令列表,一般不用
  • riid:适配接口的COM ID
  • ppCommandAllocator:输出指向所建命令分配器的指针
1
2
3
4
5
6
7
8
9
//内存管理指针
Microsoft::WRL::ComPtr<ID3D12CommandAllocator> mDirectCmdListAlloc;
...;

//第一个参数:此命令分配器相关联的命令列表类型,具体见下图
//第二个参数:内存分配器地址
ThrowIfFailed(md3dDevice->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT,
IID_PPV_ARGS(mDirectCmdListAlloc.GetAddressOf())));
创建命令列表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//原型
virtual HRESULT STDMETHODCALLTYPE CreateCommandList(
_In_ UINT nodeMask,
_In_ D3D12_COMMAND_LIST_TYPE type,
_In_ ID3D12CommandAllocator *pCommandAllocator,
_In_opt_ ID3D12PipelineState *pInitialState,
REFIID riid,
_COM_Outptr_ void **ppCommandList) = 0;


//实例
ThrowIfFailed(md3dDevice->CreateCommandList(
0,
D3D12_COMMAND_LIST_TYPE_DIRECT,
mDirectCmdListAlloc.Get(), // Associated command allocator
nullptr, // Initial PipelineStateObject
IID_PPV_ARGS(mCommandList.GetAddressOf())));
  1. nodeMask:如果只有1个GPU,设置成0;多个GPU用于关联的物理GPU
  2. type:命令列表类型
  3. pCommandAllocator:所建命令列表关联的命令分配器,类型必须匹配
  4. pInitialState:指定命令列表的渲染流水线初始状态,一般可以设置为nullptr
  5. riid:待创建的ID3D12CommandList接口的COM ID
  6. ppCommandList:输出指向所建命令列表的指针
小结
  • 可以创建多个关联同一个命令分配器的命令列表
  • 但是不能同时用他们记录命令
  • 其中一个命令列表在记录命令时,必须关闭同一分配器的其他命令列表
  • 要保证命令列表中的所有命令都会按顺序连续的添加到命令分配器内
  • 当创建或重置一个命令列表时,它会处于“打开 ”状态,所以同时为同一命令列表分配器创建两个命令列表会报错
1
2
3
4
5
6
7
8
9
ID3D12CommandQueue::ExecuteCommandList(C);//把命令添加到命令列表
/*将命令列表恢复到初始状态,借此继续复用其底层内存;
重置列表不会影响命令队列的命令,内存分配器在当中维护
*/
ID3D12GraphicsCommandLIst::Reset();

//GPU提交了一整帧的渲染命令后,我们可能还要为了绘制下一帧服用命令分配器的内存
//注意:在没有确定GPU执行完命令分配器的所有命令之前,千万不要重置命令分配器
ID3D12CommandAllocator::Reset();
GPU与CPU的同步

刷新命令队列:CPU会等待GPU完成所有命令处理,直到到达指定的***围栏点(fence point)***为止。

创建围栏

1
2
3
4
5
6
7
8
HRESULT ID3D12Device::CreateFence(UINT64 InitialValue,
D3D12_FENSE_FLAGS Flags,
REFIID riid,
void** ppFence);
//示例
Microsoft::WRL::ComPtr<ID3D12Fence> mFence;
ThrowIfFailed(md3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE,
IID_PPV_ARGS(&mFence)));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void D3DApp::FlushCommandQueue()
{
// 增加围栏值,接下来将命令标记到此围栏点
mCurrentFence++;

// 向命令队列添加一条用来设置新围栏点的命令
//由于这条命令有交给GPU处理,所以在GPU处理完命令队列中此Signal()的所有命令之前
//它不会设置新的围栏点
ThrowIfFailed(mCommandQueue->Signal(mFence.Get(), mCurrentFence));

// 在CPU等待GPU,直到后者执行完这个围栏点之前的所有命令
if(mFence->GetCompletedValue() < mCurrentFence)
{
HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);

// 若GPU命令中当前的围栏点(即执行到Signal()指令,修改了围栏点),则激发预定事件
ThrowIfFailed(mFence->SetEventOnCompletion(mCurrentFence, eventHandle));

// 等待GPU命中围栏,激发事件
WaitForSingleObject(eventHandle, INFINITE);
CloseHandle(eventHandle);
}
}
资源转换

资源冒险:当GPU的写操作还没有完成或者还没有开始,却开始读取资源的情况

为了解决资源冒险问题,Direct3D设计了一组相关状态,如资源在创建的时候会初一默认状态,直到应用程序通过方法将其转换为另一种状态。

转换资源屏障(transition resource barrier):通过一个API调用来转换多个资源要用到的数组

初始化Direct3D

  • 初始化流程
  1. D3D12CreateDevice函数创建ID3D12Device接口实例
  2. 创建一个ID3D12Fence对象,并且查询描述符的大小
  3. 检测用户设备对4X MSAA质量级别的支持情况
  4. 一次创建命令队列、命令列表分配器和主命令列表
  5. 描述并创建交换链
  6. 创建应用程序所需的描述符堆
  7. 调整后台缓冲区的大小,并为它创建渲染图标视图
  8. 创建深度/模板缓冲区及与之关联的深度/模板视图
  9. 设置视口(viewport)和裁剪矩形(scissor rectangle)
创建设备
1
2
3
4
5
HRESULT WINAPI D3D12CreateDevice(
_In_opt_ IUnknown* pAdapter,
D3D_FEATURE_LEVEL MinimumFeatureLevel,
_In_ REFIID riid, // Expected: ID3D12Device
_COM_Outptr_opt_ void** ppDevice );

案例

1
2
3
4
5
// Try to create hardware device.
HRESULT hardwareResult = D3D12CreateDevice(
nullptr, // default adapter
D3D_FEATURE_LEVEL_11_0,
IID_PPV_ARGS(&md3dDevice));
  1. pAdapter:使用的显示适配器,如果空,则使用主显示适配器
  2. MinimumFeatureLevele:应用程序需要硬件所支持的最低功能级别,如果适配器不支持此功能级别,则设备创建失败
  3. riid:ID3D12Device接口的COM ID
  4. ppDevice:返回创建的Direct3D 12设备

创建失败的话会尝试创建WARP设备

创建围栏
1
2
3
4
5
6
ThrowIfFailed(md3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE,
IID_PPV_ARGS(&mFence)));
//查询并保存描述符信息,方便后面使用
mRtvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
mDsvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
mCbvSrvUavDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
检测4X MSAA支持
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 // Check 4X MSAA quality support for our back buffer format.
// All Direct3D 11 capable devices support 4X MSAA for all render
// target formats, so we only need to check quality support.

D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msQualityLevels;
msQualityLevels.Format = mBackBufferFormat;
msQualityLevels.SampleCount = 4;
msQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
msQualityLevels.NumQualityLevels = 0;
ThrowIfFailed(md3dDevice->CheckFeatureSupport(
D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
&msQualityLevels,
sizeof(msQualityLevels)));

m4xMsaaQuality = msQualityLevels.NumQualityLevels;
assert(m4xMsaaQuality > 0 && "Unexpected MSAA quality level.");
创建命令队列和命令列表
  • 命令队列:ID3D12CommandQueue

  • 命令分配器:ID3D12CommandAllocator

  • 命令列表:ID3D12GraphicsCommandList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//.h申明
Microsoft::WRL::ComPtr<ID3D12CommandQueue> mCommandQueue;
Microsoft::WRL::ComPtr<ID3D12CommandAllocator> mDirectCmdListAlloc;
Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList> mCommandList;
//.cpp
void D3DApp::CreateCommandObjects()
{
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
ThrowIfFailed(md3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)));

ThrowIfFailed(md3dDevice->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT,
IID_PPV_ARGS(mDirectCmdListAlloc.GetAddressOf())));

ThrowIfFailed(md3dDevice->CreateCommandList(
0,
D3D12_COMMAND_LIST_TYPE_DIRECT,
mDirectCmdListAlloc.Get(), // 关联的命令分配器
nullptr, //初始的PipelineStateObject
IID_PPV_ARGS(mCommandList.GetAddressOf())));

//从关闭状态开始。 这是因为我们第一次提到
//在命令列表中,我们将对其进行重置,并且需要先关闭它
//调用Reset。
mCommandList->Close();
}
创建交换链
  • DXGI_SWAP_CHAIN_DESC结构体定义
1
2
3
4
5
6
7
8
9
10
11
typedef struct DXGI_SWAP_CHAIN_DESC
{
DXGI_MODE_DESC BufferDesc;
DXGI_SAMPLE_DESC SampleDesc;
DXGI_USAGE BufferUsage;
UINT BufferCount;
HWND OutputWindow;
BOOL Windowed;
DXGI_SWAP_EFFECT SwapEffect;
UINT Flags;
} DXGI_SWAP_CHAIN_DESC;
  1. BufferDesc:后台缓冲区的属性,主要是高度、宽度和像素格式
  2. SampleDesc:多重采样的质量级别以及对每个像素的采样次数
  3. BufferUsage:如果要将数据渲染到后台缓冲区,则设置为DXGI_USAGE_RENDER_TARGET_OUTPUT
  4. BufferCount:缓冲区数量,指定为2即双缓冲
  5. OutputWindow:渲染窗口的句柄
  6. Windowed:true则窗口模式运行,否则全屏
  7. SwapEffect:指定为DXGI_SWAP_EFFECT_FLIP_DISCARD
  8. Flags:可选;如果指定为DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH则程序切换为全屏时,将选择适用于当前窗口尺寸的显示模式;否则就采用当前桌面的显示模式
  • DXGI_MODE_DESC
1
2
3
4
5
6
7
8
9
typedef struct DXGI_MODE_DESC
{
UINT Width;//缓冲区分辨率的宽度
UINT Height;//高度
DXGI_RATIONAL RefreshRate;
DXGI_FORMAT Format;//显示格式
DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;//逐行扫面vs.隔行扫描
DXGI_MODE_SCALING Scaling;//如何进行拉升
} DXGI_MODE_DESC;
执行创建交换链
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void D3DApp::CreateSwapChain()
{
// 释放我们将重新创建的先前的交换链。
mSwapChain.Reset();

DXGI_SWAP_CHAIN_DESC sd;
sd.BufferDesc.Width = mClientWidth;
sd.BufferDesc.Height = mClientHeight;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferDesc.Format = mBackBufferFormat;
sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
sd.SampleDesc.Count = m4xMsaaState ? 4 : 1;
sd.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.BufferCount = SwapChainBufferCount;
sd.OutputWindow = mhMainWnd;
sd.Windowed = true;
sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

// Note: 交换链使用队列执行刷新。
ThrowIfFailed(mdxgiFactory->CreateSwapChain(
mCommandQueue.Get(),
&sd,
mSwapChain.GetAddressOf()));
}
创建描述符堆

创建描述符堆来存储程序中要用到的描述符/视图,本例中需要创建两个描述符堆来存储SwapChainBufferCountRTV ,另外一个存储1个DSV

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//.h
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> mRtvHeap;
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> mDsvHeap;
//.cpp
void D3DApp::CreateRtvAndDsvDescriptorHeaps()
{
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc;
rtvHeapDesc.NumDescriptors = SwapChainBufferCount;//static value=2
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
rtvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
&rtvHeapDesc, IID_PPV_ARGS(mRtvHeap.GetAddressOf())));


D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc;
dsvHeapDesc.NumDescriptors = 1;
dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
dsvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
&dsvHeapDesc, IID_PPV_ARGS(mDsvHeap.GetAddressOf())));
}

创建以后需要通过方法来获得描述符的句柄

1
2
3
4
5
6
7
D3D12_CPU_DESCRIPTOR_HANDLE D3DApp::CurrentBackBufferView()const
{//构造函数根据给定的偏移量找到当前后台缓冲区的RTV
return CD3DX12_CPU_DESCRIPTOR_HANDLE(
mRtvHeap->GetCPUDescriptorHandleForHeapStart(),//堆中的首个句柄
mCurrBackBuffer,//偏移至后台缓冲区描述符句柄的索引
mRtvDescriptorSize);//描述符所占字节大小
}
1
2
3
4
D3D12_CPU_DESCRIPTOR_HANDLE D3DApp::DepthStencilView()const
{
return mDsvHeap->GetCPUDescriptorHandleForHeapStart();
}
创建渲染目标视图

资源不能与渲染流水线中的阶段直接绑定,所以必须先为资源创建视图(描述符),并将其绑定到流水线阶段

1
2
3
4
5
6
virtual HRESULT STDMETHODCALLTYPE GetBuffer( 
/* [in] */ UINT Buffer,
/* [annotation][in] */
_In_ REFIID riid,
/* [annotation][out][in] */
_COM_Outptr_ void **ppSurface) = 0;
  1. Buffer:后台缓冲区索引
  2. riid:COM ID
  3. ppSurface:返回ID3D12Resource接口的指针,即后台缓冲区

调用此方法后会增加计数,所以使用后需要释放,需通过ComPtr

然后获得后台缓冲区创建的渲染目标视图

1
2
3
4
virtual void STDMETHODCALLTYPE CreateRenderTargetView( 
_In_opt_ ID3D12Resource *pResource,
_In_opt_ const D3D12_RENDER_TARGET_VIEW_DESC *pDesc,
_In_ D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) = 0;
  1. pResource:指定用作渲染目标的资源
  2. pDesc:指向D3D12_RENDER_TARGET_VIEW_DESC数组结构体的指针
  3. DestDescriptor:引用所创建渲染目标视图的描述符句柄
1
2
3
4
5
6
7
8
9
10
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHeapHandle(mRtvHeap->GetCPUDescriptorHandleForHeapStart());
for (UINT i = 0; i < SwapChainBufferCount; i++)
{
//获得交换链中的第 i 个缓冲区
ThrowIfFailed(mSwapChain->GetBuffer(i, IID_PPV_ARGS(&mSwapChainBuffer[i])));
//为此缓冲区创建一个RTV
md3dDevice->CreateRenderTargetView(mSwapChainBuffer[i].Get(), nullptr, rtvHeapHandle);
//偏移到下一个缓冲区
rtvHeapHandle.Offset(1, mRtvDescriptorSize);
}
创建深度/模板缓冲区及其视图

因为深度缓冲区就是一种2D纹理,所以我们通过填写D3D12_RESOURCE_DESC结构体来描述纹理资源

再用ID3D12Device::CreateCommittedResource方法来创建它

  • D3D12_RESOURCE_DESC
1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct D3D12_RESOURCE_DESC
{
D3D12_RESOURCE_DIMENSION Dimension;
UINT64 Alignment;
UINT64 Width;
UINT Height;
UINT16 DepthOrArraySize;
UINT16 MipLevels;
DXGI_FORMAT Format;
DXGI_SAMPLE_DESC SampleDesc;
D3D12_TEXTURE_LAYOUT Layout;
D3D12_RESOURCE_FLAGS Flags;
} D3D12_RESOURCE_DESC;
  1. D3D12_RESOURCE_DIMENSION Dimension:资源的维度

    1. ```cpp
      enum D3D12_RESOURCE_DIMENSION
      {
          D3D12_RESOURCE_DIMENSION_UNKNOWN    = 0,
          D3D12_RESOURCE_DIMENSION_BUFFER    = 1,
          D3D12_RESOURCE_DIMENSION_TEXTURE1D    = 2,
          D3D12_RESOURCE_DIMENSION_TEXTURE2D    = 3,
          D3D12_RESOURCE_DIMENSION_TEXTURE3D    = 4
      }     D3D12_RESOURCE_DIMENSION;
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36

      2. Width:像素单位的纹理宽度。对于缓冲区,此项是占用的字节数

      3. Height:同上

      4. DepthOrArraySize:纹素为单位的纹理深度,或者是纹理数组的大小

      5. MipLevels:mipmap层级的数量

      6. Format:DXGI_FORMAT枚举成员之一

      7. SampleDesc:多重采样级别和每个像素的采样次数

      8. Layout:D3D12_TEXTURE_LAYOUT枚举成员之一,用于指定纹理布局

      9. Flags:杂项标记,对于深度/模板缓冲区资源,设置为`D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL`



      > `GPU`资源都存储在`堆`中,本质是具有特定属性的`GPU`显存快
      >
      > `ID3D12Device::CreateCommitedResource`方法根据提供的属性创建一个资源和一个堆,把资源提交到这个堆



      ###### CreateCommittedResource

      ```cpp
      virtual HRESULT STDMETHODCALLTYPE CreateCommittedResource(
      _In_ const D3D12_HEAP_PROPERTIES *pHeapProperties,//资源提交到的堆的属性,见下
      D3D12_HEAP_FLAGS HeapFlags,//额外标记,一般是D3D12_HEAP_FLAG_NONE
      _In_ const D3D12_RESOURCE_DESC *pDesc,//描述待创建的资源
      D3D12_RESOURCE_STATES InitialResourceState,//此参数来设置资源的初始状态
      _In_opt_ const D3D12_CLEAR_VALUE *pOptimizedClearValue,//清楚资源的优化值,不需要就选择nullptr
      REFIID riidResource,//COM ID
      _COM_Outptr_opt_ void **ppvResource) = 0;//新创建的资源,指向ID3D12Resource的指针
D3D12_HEAP_PROPERTIES
1
2
3
4
5
6
7
8
typedef struct D3D12_HEAP_PROPERTIES
{
D3D12_HEAP_TYPE Type;
D3D12_CPU_PAGE_PROPERTY CPUPageProperty;
D3D12_MEMORY_POOL MemoryPoolPreference;
UINT CreationNodeMask;
UINT VisibleNodeMask;
} D3D12_HEAP_PROPERTIES;
D3D12_HEAP_TYPE
1
2
3
4
5
6
7
8
enum D3D12_HEAP_TYPE
{
D3D12_HEAP_TYPE_DEFAULT = 1,//默认堆
D3D12_HEAP_TYPE_UPLOAD = 2,//上传堆
D3D12_HEAP_TYPE_READBACK = 3,//回读堆
D3D12_HEAP_TYPE_CUSTOM = 4//高级场景使用
} D3D12_HEAP_TYPE;

示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 // Create the depth/stencil buffer and view.
D3D12_RESOURCE_DESC depthStencilDesc;
depthStencilDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
depthStencilDesc.Alignment = 0;
depthStencilDesc.Width = mClientWidth;
depthStencilDesc.Height = mClientHeight;
depthStencilDesc.DepthOrArraySize = 1;
depthStencilDesc.MipLevels = 1;
depthStencilDesc.Format = mDepthStencilFormat;
depthStencilDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
depthStencilDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
depthStencilDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
depthStencilDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;

D3D12_CLEAR_VALUE optClear;
optClear.Format = mDepthStencilFormat;
optClear.DepthStencil.Depth = 1.0f;
optClear.DepthStencil.Stencil = 0;
ThrowIfFailed(md3dDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&depthStencilDesc,
D3D12_RESOURCE_STATE_COMMON,
&optClear,
IID_PPV_ARGS(mDepthStencilBuffer.GetAddressOf())));
1
2
3
4
5
6
7
 // 使用资源格式将描述符创建为整个资源的MIP级别0。
md3dDevice->CreateDepthStencilView(mDepthStencilBuffer.Get(), nullptr, DepthStencilView());

// 将资源从其初始状态转换为深度缓冲区。
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(mDepthStencilBuffer.Get(),
D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_DEPTH_WRITE));

设置视口
1
D3D12_VIEWPORT mScreenViewport; 
1
2
3
4
5
6
7
8
9
typedef struct D3D12_VIEWPORT
{
FLOAT TopLeftX;
FLOAT TopLeftY;
FLOAT Width;
FLOAT Height;
FLOAT MinDepth;//负责从区间0-1转化为MinDepth-MaxDepth
FLOAT MaxDepth;
} D3D12_VIEWPORT;

填好结构体以后通过函数ID3D12GraphicsCommandList::RSSetViewports方法来设置视口

示例
1
2
3
4
5
6
7
8
9
mScreenViewport.TopLeftX = 0;
mScreenViewport.TopLeftY = 0;
mScreenViewport.Width = static_cast<float>(mClientWidth);
mScreenViewport.Height = static_cast<float>(mClientHeight);
mScreenViewport.MinDepth = 0.0f;
mScreenViewport.MaxDepth = 1.0f;


mCommandList->RSSetViewports(1, &mScreenViewport);

不能为同一个渲染目标指定多个视口

而多个视口则是一种用于对多个渲染目标同时进行渲染的高级技术

命令列表重置,视口也要重置

设置裁剪矩形
1
2
3
4
5
6
7
typedef struct tagRECT
{
LONG left;
LONG top;
LONG right;
LONG bottom;
} RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT;
1
2
3
mScissorRect = { 0, 0, mClientWidth, mClientHeight };

mCommandList->RSSetScissorRects(1, &mScissorRect);

不能为同一个渲染目标指定多个裁剪矩形。

多裁剪矩形是以各种用于同时对多个渲染目标进行渲染的高级技术

裁剪矩形需要随着命令列表重置而重置

计时与动画

性能计时器

QueryPerformanceCounter函数来活得性能计时器测量的当前时刻值

1
2
3
__int64 countsPerSec;
QueryPerformanceFrequency((LARGE_INTEGER*)&countsPerSec);
mSecondsPerCount = 1.0 / (double)countsPerSec;

通过如下方式转换为秒

1
valueInSecs=valueInCounts * mSecondsPercount;

调用2次QueryPerformanceCounter函数得到两个时间戳的相对插值

1
2
3
4
__int64 A;
QueryPerformanceFrequency((LARGE_INTEGER*)&A);
__int64 B;
QueryPerformanceFrequency((LARGE_INTEGER*)&B);

B-A即可获得执行期间的计数值,或者(B-1)*mSecondsPerCount获得代码运行期间所花费的秒数

游戏计时器类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class GameTimer
{
public:
GameTimer();

float TotalTime()const; //秒为单位
float DeltaTime()const; //秒为单位

void Reset(); // 开始循环之前调用
void Start(); // 接触计时器暂停时调用
void Stop(); // 暂停计时器调用
void Tick(); //每帧都要调用

private:
double mSecondsPerCount;
double mDeltaTime;

__int64 mBaseTime;
__int64 mPausedTime;
__int64 mStopTime;
__int64 mPrevTime;
__int64 mCurrTime;

bool mStopped;
};

帧与帧之间的时间间隔
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void GameTimer::Tick()
{
if( mStopped )
{
mDeltaTime = 0.0;
return;
}
// 获得本帧开始的时刻
__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
mCurrTime = currTime;

// 两帧的时间差
mDeltaTime = (mCurrTime - mPrevTime)*mSecondsPerCount;

// 把当前时间设置为下一次的开始时间
mPrevTime = mCurrTime;

//保证时间差为非负值;
//在处理器处于节能模式或者计算两次时间差的过程中切换到了另一个处理器可能会得到负值
if(mDeltaTime < 0.0)
{
mDeltaTime = 0.0;
}
}

Tick函数被调用在D3DApp::Run函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
int D3DApp::Run()
{
MSG msg = {0};

mTimer.Reset();

while(msg.message != WM_QUIT)
{
// If there are Window messages then process them.
if(PeekMessage( &msg, 0, 0, 0, PM_REMOVE ))
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
// Otherwise, do animation/game stuff.
else
{
mTimer.Tick();

if( !mAppPaused )
{
CalculateFrameStats();
Update(mTimer);
Draw(mTimer);
}
else
{
Sleep(100);
}
}
}

return (int)msg.wParam;
}

Reset方法初始化第一帧的数据,因为第一帧没有之前的帧

1
2
3
4
5
6
7
8
9
10
void GameTimer::Reset()
{
__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime);

mBaseTime = currTime;
mPrevTime = currTime;
mStopTime = 0;
mStopped = false;
}
总时间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
float GameTimer::TotalTime()const
{


//如果我们停止了,请勿计算自停止以来经过的时间。
//此外,如果我们之前已经停顿了一下
// mStopTime-mBaseTime包含暂停时间,我们不想计算。
//要纠正此问题,我们可以从mStopTime中减去暂停时间:
//
// |<--paused time-->|
// ----*---------------*-----------------*------------*------------*------> time
// mBaseTime mStopTime startTime mStopTime mCurrTime

if( mStopped )
{
return (float)(((mStopTime - mPausedTime)-mBaseTime)*mSecondsPerCount);
}

//距离mCurrTime-mBaseTime包括暂停时间,
//我们不想计算。 要纠正这一点,我们可以减去
//从mCurrTime暂停的时间:
//
// (mCurrTime - mPausedTime) - mBaseTime
//
// |<--paused time-->|
// ----*---------------*-----------------*------------*------> time
// mBaseTime mStopTime startTime mCurrTime

else
{
return (float)(((mCurrTime-mPausedTime)-mBaseTime)*mSecondsPerCount);
}
}

应用程序框架示例

自己模仿框架的代码

DXApp.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#pragma once
#include "../Common/d3dUtil.h"
#include "../Common/d3dx12.h"
#include "../Common/GameTimer.h"

#if defined(DEBUG) || defined(_DEBUG)
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#endif
#include <dxgiformat.h>

// Link necessary d3d12 libraries.
#pragma comment(lib,"d3dcompiler.lib")
#pragma comment(lib, "D3D12.lib")
#pragma comment(lib, "dxgi.lib")




class DXApp
{

public:
DXApp(HINSTANCE nInstance);
DXApp(const DXApp& rhs) = delete;
DXApp& operator=(const DXApp& rhs) = delete;
virtual ~DXApp();

protected:
virtual void OnResize();
virtual bool Initialize();
virtual void CreateRtvAndDsvDescriptorHeaps();//创建描述符堆
virtual void Update(const GameTimer& gt) = 0;
virtual void Draw(const GameTimer& gt) = 0;

bool InitMainWindow();//初始化窗口
bool InitDirect3D();//初始化DX


void FlushCommandQueue();//齐平命令队列
void CreateSwapChain();//创建交换链
void CreateCommandObjects();//创建命令队列、命令适配器、命令列表


ID3D12Resource* CurrentBackBuffer()const;
D3D12_CPU_DESCRIPTOR_HANDLE CurrentBackBufferView()const;
D3D12_CPU_DESCRIPTOR_HANDLE DepthStencilView()const;


void LogAdapters();
void LogAdapterOutputs(IDXGIAdapter* adapter);
void LogOutputDisplayModes(IDXGIOutput* output, DXGI_FORMAT format);
public:
static DXApp* GetApp();
HINSTANCE AppInst()const;//得到应用程序实例
HWND MainWnd()const;//得到窗口
float AspectRatio()const;//长宽比
bool Get4xMsaaState()const;//4XMSAA开启与否
void Set4xMsaaState(bool value);//设置4XMSAA

int Run();
void CalculateFrameStats();
/*
HWND hwnd; //窗口句柄
UINT message; //消息常量标识符
WPARAM wParam; //32位消息的特定附加信息,具体表示什么处决于message
LPARAM lParam; //32位消息的特定附加信息,具体表示什么处决于message
*/
virtual LRESULT MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

//鼠标输入
virtual void OnMouseDown(WPARAM btnState, int x, int y) { }
virtual void OnMouseUp(WPARAM btnState, int x, int y) { }
virtual void OnMouseMove(WPARAM btnState, int x, int y) { }



protected:

static DXApp* mApp;

HINSTANCE mhAppInst = nullptr; // 应用程序实例句柄
HWND mhMainWnd = nullptr; // 主窗口句柄
bool mAppPaused = false; // 应用程序是否已暂停?
bool mMinimized = false; // 将应用程序最小化?
bool mMaximized = false; // 应用程序是否已最大化?
bool mResizing = false; // 是否拖动了大小调整栏?
bool mFullscreenState = false;// 启用全屏

// 是否启用 4X MSAA (?.1.8). The default is false.
bool m4xMsaaState = false; // 启用 4X MSAA
UINT m4xMsaaQuality = 0; // 4倍MSAA的质量水平

GameTimer mTimer;

//COM
Microsoft::WRL::ComPtr<ID3D12Device> md3dDevice; //设备接口
Microsoft::WRL::ComPtr<IDXGIFactory4> mdxgiFactory;
Microsoft::WRL::ComPtr<IDXGISwapChain> mSwapChain;//交换链

Microsoft::WRL::ComPtr<ID3D12Fence> mFence;//围栏
UINT64 mCurrentFence = 0;

Microsoft::WRL::ComPtr<ID3D12CommandQueue> mCommandQueue;//命令队列
Microsoft::WRL::ComPtr<ID3D12CommandAllocator> mDirectCmdListAlloc;//适配器
Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList> mCommandList;//列表
//描述符
UINT mRtvDescriptorSize = 0;
UINT mDsvDescriptorSize = 0;
UINT mCbvSrvUavDescriptorSize = 0;

static const int SwapChainBufferCount = 2;//双重缓冲
int mCurrBackBuffer = 0;
Microsoft::WRL::ComPtr<ID3D12Resource> mSwapChainBuffer[SwapChainBufferCount];//交换链缓冲区数组
Microsoft::WRL::ComPtr<ID3D12Resource> mDepthStencilBuffer;//深度模板缓冲区

Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> mRtvHeap;//RTV描述符堆
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> mDsvHeap;//DSV描述符堆

std::wstring mMainWndCaption = L"DX App";
D3D_DRIVER_TYPE md3dDriverType = D3D_DRIVER_TYPE_HARDWARE;
DXGI_FORMAT mBackBufferFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
DXGI_FORMAT mDepthStencilFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;

D3D12_VIEWPORT mScreenViewport;//视口
D3D12_RECT mScissorRect;//裁剪

int mClientWidth = 800;
int mClientHeight = 600;
};
DXApp.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
#include "DXApp.h"
#include "WindowsX.h"
#include <winuser.h>
#include <dxgi.h>
#include <iostream>

using Microsoft::WRL::ComPtr;
using namespace std;
using namespace DirectX;



DXApp* DXApp::mApp = nullptr;

LRESULT CALLBACK
MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
//cout << "test";
return DXApp::GetApp()->MsgProc(hwnd, msg, wParam, lParam);
}



DXApp::DXApp(HINSTANCE nInstance):mhAppInst(nInstance)
{
assert(mApp == nullptr);
mApp = this;
}

DXApp::~DXApp()
{
if (mhAppInst != nullptr)
{
FlushCommandQueue();
}
}

void DXApp::OnResize()
{
assert(md3dDevice);
assert(mSwapChain);
assert(mDirectCmdListAlloc);

FlushCommandQueue();

ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(),nullptr));

//释放我们将重新创建的先前资源。
for (int i=0;i<SwapChainBufferCount;++i)
{
mSwapChainBuffer[i].Reset();
}
mDepthStencilBuffer.Reset();


//调整交换链的大小。
ThrowIfFailed(mSwapChain->ResizeBuffers(
SwapChainBufferCount,
mClientWidth,
mClientHeight,
mBackBufferFormat,
DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH));

mCurrBackBuffer=0;

CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHeapHandle(mRtvHeap->GetCPUDescriptorHandleForHeapStart());
for (UINT i = 0; i < SwapChainBufferCount; i++)
{
//构造函数根据给定的偏移量找到当前后台缓冲区的RTV
ThrowIfFailed(mSwapChain->GetBuffer(i, IID_PPV_ARGS(&mSwapChainBuffer[i])));
md3dDevice->CreateRenderTargetView(mSwapChainBuffer[i].Get(), nullptr, rtvHeapHandle);//堆中的首个句柄
rtvHeapHandle.Offset(1, mRtvDescriptorSize);
}

//创建深度/模板缓冲区和视口
D3D12_RESOURCE_DESC depthStencilDesc;
depthStencilDesc.Dimension= D3D12_RESOURCE_DIMENSION_TEXTURE2D;
depthStencilDesc.Alignment=0;
depthStencilDesc.Width=mClientWidth;
depthStencilDesc.Height=mClientHeight;
depthStencilDesc.DepthOrArraySize=1;
depthStencilDesc.MipLevels=1;
depthStencilDesc.Format=mDepthStencilFormat;
depthStencilDesc.SampleDesc.Count=m4xMsaaState?4:1;
depthStencilDesc.SampleDesc.Quality=m4xMsaaState?(m4xMsaaQuality-1):0;
depthStencilDesc.Layout=D3D12_TEXTURE_LAYOUT_UNKNOWN;
depthStencilDesc.Flags=D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;

D3D12_CLEAR_VALUE optClear;
optClear.Format=mDepthStencilFormat;
optClear.DepthStencil.Depth=1.0f;
optClear.DepthStencil.Stencil=0;
ThrowIfFailed(md3dDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&depthStencilDesc,
D3D12_RESOURCE_STATE_COMMON,
&optClear,
IID_PPV_ARGS(mDepthStencilBuffer.GetAddressOf())));

//使用资源的格式将描述符创建为整个资源的MIP级别0。
md3dDevice->CreateDepthStencilView(mDepthStencilBuffer.Get(),nullptr, DepthStencilView());
//将资源从其初始状态转换为深度缓冲区。
mCommandList->ResourceBarrier(1,&CD3DX12_RESOURCE_BARRIER::Transition(mDepthStencilBuffer.Get(),D3D12_RESOURCE_STATE_COMMON,D3D12_RESOURCE_STATE_DEPTH_WRITE));
//执行resize命令
ThrowIfFailed(mCommandList->Close());
ID3D12CommandList* cmdsLists[]={mCommandList.Get()};
mCommandQueue->ExecuteCommandLists(_countof(cmdsLists),cmdsLists);

//等待到resize完成
FlushCommandQueue();

//更新视口变换以覆盖客户区域。
mScreenViewport.TopLeftX = 0;
mScreenViewport.TopLeftY = 0;
mScreenViewport.Width = static_cast<float>(mClientWidth);
mScreenViewport.Height = static_cast<float>(mClientHeight);
mScreenViewport.MinDepth = 0.0f;
mScreenViewport.MaxDepth = 1.0f;

mScissorRect={0,0,mClientWidth,mClientHeight};
}

bool DXApp::Initialize()
{
if (!InitMainWindow())
{
return false;
}
if (!InitDirect3D())
{
return false;
}
OnResize();
return true;
}

void DXApp::CreateRtvAndDsvDescriptorHeaps()
{
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc;
rtvHeapDesc.NumDescriptors=SwapChainBufferCount;
rtvHeapDesc.Type=D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags=D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
rtvHeapDesc.NodeMask=0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&rtvHeapDesc,IID_PPV_ARGS(mRtvHeap.GetAddressOf())));

D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc;
dsvHeapDesc.NumDescriptors = SwapChainBufferCount;
dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
dsvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&dsvHeapDesc, IID_PPV_ARGS(mDsvHeap.GetAddressOf())));

}

bool DXApp::InitMainWindow()
{
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = mhAppInst;
wc.hIcon = LoadIcon(0, IDI_APPLICATION);
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);
wc.lpszMenuName = 0;
wc.lpszClassName = L"MainWnd";

if (!RegisterClass(&wc))
{
MessageBox(0, L"RegisterClass Failed.", 0, 0);
return false;
}

// Compute window rectangle dimensions based on requested client area dimensions.
RECT R = { 0, 0, mClientWidth, mClientHeight };
AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);
int width = R.right - R.left;
int height = R.bottom - R.top;

mhMainWnd = CreateWindow(L"MainWnd", mMainWndCaption.c_str(),
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, 0, 0, mhAppInst, 0);
if (!mhMainWnd)
{
MessageBox(0, L"CreateWindow Failed.", 0, 0);
return false;
}

ShowWindow(mhMainWnd, SW_SHOW);
UpdateWindow(mhMainWnd);

return true;
}



bool DXApp::InitDirect3D()
{
// {
//#if defined(DEBUG) || defined(_DEBUG)
// // Enable the D3D12 debug layer.
// {
// ComPtr<ID3D12Debug> debugController;
// ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)));
// debugController->EnableDebugLayer();
// }
//#endif
ThrowIfFailed(CreateDXGIFactory(IID_PPV_ARGS(&mdxgiFactory)));

//创建硬件设备
HRESULT hardwareResult = D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&md3dDevice));//第一个参数是空则使用默认显示器
//回退至WARP设备
if (FAILED(hardwareResult))
{
ComPtr<IDXGIAdapter> pWarpAdapter;
ThrowIfFailed(mdxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&pWarpAdapter)));
ThrowIfFailed(D3D12CreateDevice(pWarpAdapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&md3dDevice)));
}
ThrowIfFailed(md3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence)));

//描述符信息,方便以后使用
mRtvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
mDsvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
mCbvSrvUavDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

//检查4X MSAA质量对我们的后缓冲区格式的支持。
//所有支持Direct3D 11的设备都对所有渲染支持4倍MSAA
//目标格式,因此我们只需要检查质量支持。
D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msQualityLevels;
msQualityLevels.Format = mBackBufferFormat;
msQualityLevels.SampleCount = 4;
msQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
msQualityLevels.NumQualityLevels = 0;
ThrowIfFailed(md3dDevice->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, &msQualityLevels, sizeof(msQualityLevels)));
m4xMsaaQuality = msQualityLevels.NumQualityLevels;
assert(m4xMsaaQuality > 0 && "Unexpected MSAA quality level.");


CreateCommandObjects();
CreateSwapChain();
CreateRtvAndDsvDescriptorHeaps();

return true;
}

void DXApp::FlushCommandQueue()
{
//提升围栏值以将命令标记到该围栏点。
mCurrentFence++;
//将指令添加到命令队列以设置新的防护点。 因为我们
//在GPU时间轴上,直到GPU完成后才会设置新的围栏点
//处理此Signal()之前的所有命令。
ThrowIfFailed(mCommandQueue->Signal(mFence.Get(),mCurrentFence));

//等待直到GPU完成命令为止。
if (mFence->GetCompletedValue() < mCurrentFence)
{
HANDLE eventHandle=CreateEventEx(nullptr,false,false,EVENT_ALL_ACCESS);
//GPU击中当前围墙时触发事件。
ThrowIfFailed(mFence->SetEventOnCompletion(mCurrentFence,eventHandle));

//等到GPU击中当前的fence事件后再触发。
WaitForSingleObject(eventHandle,INFINITE);
CloseHandle(eventHandle);
}
}

void DXApp::CreateSwapChain()
{
mSwapChain.Reset();
DXGI_SWAP_CHAIN_DESC sd;
sd.BufferDesc.Width = mClientWidth;
sd.BufferDesc.Height = mClientHeight;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferDesc.Format = mBackBufferFormat;
sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
sd.SampleDesc.Count = m4xMsaaState ? 4 : 1;
sd.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.BufferCount = SwapChainBufferCount;
sd.OutputWindow = mhMainWnd;
sd.Windowed = true;
sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

// Note: Swap chain uses queue to perform flush.
ThrowIfFailed(mdxgiFactory->CreateSwapChain(
mCommandQueue.Get(),
&sd,
mSwapChain.GetAddressOf()));
}

void DXApp::CreateCommandObjects()
{
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
ThrowIfFailed(md3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)));
ThrowIfFailed(md3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(mDirectCmdListAlloc.GetAddressOf())));
ThrowIfFailed(md3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, mDirectCmdListAlloc.Get(), nullptr, IID_PPV_ARGS(mCommandList.ReleaseAndGetAddressOf())));

//从关闭状态开始。 这是因为我们第一次提到
//在命令列表中,我们将对其进行重置,并且需要先关闭它
//调用Reset。
mCommandList->Close();
}

ID3D12Resource* DXApp::CurrentBackBuffer() const
{
return mSwapChainBuffer[mCurrBackBuffer].Get();
}

D3D12_CPU_DESCRIPTOR_HANDLE DXApp::CurrentBackBufferView() const
{
return CD3DX12_CPU_DESCRIPTOR_HANDLE(
mRtvHeap->GetCPUDescriptorHandleForHeapStart(),
mCurrBackBuffer,
mRtvDescriptorSize);
}

D3D12_CPU_DESCRIPTOR_HANDLE DXApp::DepthStencilView() const
{
return mDsvHeap->GetCPUDescriptorHandleForHeapStart();
}


void DXApp::LogAdapters()
{
UINT i = 0;
IDXGIAdapter* adapter = nullptr;
std::vector<IDXGIAdapter*> adapterList;
while (mdxgiFactory->EnumAdapters(i, &adapter) != DXGI_ERROR_NOT_FOUND)
{
DXGI_ADAPTER_DESC desc;
adapter->GetDesc(&desc);

std::wstring text = L"***Adapter: ";
text += desc.Description;
text += L"\n";

OutputDebugString(text.c_str());

adapterList.push_back(adapter);

++i;
}

for (size_t i = 0; i < adapterList.size(); ++i)
{
LogAdapterOutputs(adapterList[i]);
ReleaseCom(adapterList[i]);
}
}

void DXApp::LogAdapterOutputs(IDXGIAdapter* adapter)
{
UINT i = 0;
IDXGIOutput* output = nullptr;
while (adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND)
{
DXGI_OUTPUT_DESC desc;
output->GetDesc(&desc);

std::wstring text = L"***Output: ";
text += desc.DeviceName;
text += L"\n";
OutputDebugString(text.c_str());

LogOutputDisplayModes(output, mBackBufferFormat);

ReleaseCom(output);

++i;
}
}

void DXApp::LogOutputDisplayModes(IDXGIOutput* output, DXGI_FORMAT format)
{
UINT count = 0;
UINT flags = 0;

// Call with nullptr to get list count.
output->GetDisplayModeList(format, flags, &count, nullptr);

std::vector<DXGI_MODE_DESC> modeList(count);
output->GetDisplayModeList(format, flags, &count, &modeList[0]);

for (auto& x : modeList)
{
UINT n = x.RefreshRate.Numerator;
UINT d = x.RefreshRate.Denominator;
std::wstring text =
L"Width = " + std::to_wstring(x.Width) + L" " +
L"Height = " + std::to_wstring(x.Height) + L" " +
L"Refresh = " + std::to_wstring(n) + L"/" + std::to_wstring(d) +
L"\n";

::OutputDebugString(text.c_str());
}
}

DXApp* DXApp::GetApp()
{
return mApp;
}

HINSTANCE DXApp::AppInst() const
{
return mhAppInst;
}

HWND DXApp::MainWnd() const
{
return mhMainWnd;
}

float DXApp::AspectRatio() const
{
return static_cast<float>(mClientWidth) / mClientHeight;
}

bool DXApp::Get4xMsaaState() const
{
return m4xMsaaState;
}

void DXApp::Set4xMsaaState(bool value)
{
if (m4xMsaaState!=value)
{
m4xMsaaState = value;
//重置4xmasaa需要重新创建交换链和刷新尺寸
CreateSwapChain();
OnResize();

}
}

int DXApp::Run()
{
MSG msg = { 0 };

mTimer.Reset();

while (msg.message != WM_QUIT)
{
// If there are Window messages then process them.
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Otherwise, do animation/game stuff.
else
{
mTimer.Tick();

if (!mAppPaused)
{
CalculateFrameStats();
Update(mTimer);
Draw(mTimer);
}
else
{
Sleep(100);
}
}
}

return (int)msg.wParam;

}

void DXApp::CalculateFrameStats()
{
// Code computes the average frames per second, and also the
// average time it takes to render one frame. These stats
// are appended to the window caption bar.

static int frameCnt = 0;
static float timeElapsed = 0.0f;

frameCnt++;

// Compute averages over one second period.
if ((mTimer.TotalTime() - timeElapsed) >= 1.0f)
{
float fps = (float)frameCnt; // fps = frameCnt / 1
float mspf = 1000.0f / fps;

wstring fpsStr = to_wstring(fps);
wstring mspfStr = to_wstring(mspf);

wstring windowText = mMainWndCaption +
L" fps: " + fpsStr +
L" mspf: " + mspfStr;

SetWindowText(mhMainWnd, L"window text");

// Reset for next average.
frameCnt = 0;
timeElapsed += 1.0f;
}
}

LRESULT DXApp::MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
//激活或停用窗口时发送WM_ACTIVATE。
//当停用窗口时我们暂停游戏
//当它变为活动状态时取消暂停
case WM_ACTIVATE:
if (LOWORD(wParam)==WA_INACTIVE)
{
mAppPaused = true;
mTimer.Stop();
}
else
{
mAppPaused = false;
mTimer.Start();
}
return 0;
// 当用户调整窗口大小时,发送WM_SIZE。
case WM_SIZE:
// 保存新的客户区域尺寸。
mClientWidth = LOWORD(lParam);
mClientHeight = HIWORD(lParam);
if (md3dDevice)
{
if (wParam == SIZE_MINIMIZED)
{
mAppPaused = true;
mMinimized = true;
mMaximized = false;
}
else if (wParam == SIZE_MAXIMIZED)
{
mAppPaused = false;
mMinimized = false;
mMaximized = true;
OnResize();
}
else if (wParam == SIZE_RESTORED)
{

// Restoring from minimized state?
if (mMinimized)
{
mAppPaused = false;
mMinimized = false;
OnResize();
}

// Restoring from maximized state?
else if (mMaximized)
{
mAppPaused = false;
mMaximized = false;
OnResize();
}
else if (mResizing)
{
//如果用户拖动调整大小栏,我们不会调整大小
//这里的缓冲区,因为随着用户的不断前进
//拖动调整大小条,WM_SIZE消息流是
//发送到窗口,它将毫无意义(而且很慢)
//为从拖动中收到的每个WM_SIZE消息调整大小
//调整尺寸栏。 因此,我们在用户
//完成调整窗口大小并释放大小调整条
//发送WM_EXITSIZEMOVE消息。
}
else // API call such as SetWindowPos or mSwapChain->SetFullscreenState.
{
OnResize();
}
}
}
return 0;

// 当用户抓住调整大小条时,将发送WM_EXITSIZEMOVE。
case WM_ENTERSIZEMOVE:
mAppPaused = true;
mResizing = true;
mTimer.Stop();
return 0;

//当用户释放大小调整条时,发送WM_EXITSIZEMOVE。
//在这里,我们根据新窗口的尺寸重置所有内容。
case WM_EXITSIZEMOVE:
mAppPaused = false;
mResizing = false;
mTimer.Start();
OnResize();
return 0;
//销毁窗口时发送WM_DESTROY。
case WM_DESTROY:
PostQuitMessage(0);
return 0;
// 当菜单处于活动状态并且用户按下时,将发送WM_MENUCHAR消息与任何助记键或加速键都不对应的键。
case WM_MENUCHAR:
// 进入时不要发出哔声。
return MAKELRESULT(0, MNC_CLOSE);


//捕获此消息,以防止窗口变得太小。
case WM_GETMINMAXINFO:
((MINMAXINFO*)lParam)->ptMinTrackSize.x = 200;
((MINMAXINFO*)lParam)->ptMinTrackSize.y = 200;
return 0;

case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
OnMouseDown(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
return 0;
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
OnMouseUp(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
return 0;
case WM_MOUSEMOVE:
OnMouseMove(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
return 0;
case WM_KEYUP:
if (wParam == VK_ESCAPE)
{
PostQuitMessage(0);
}
else if ((int)wParam == VK_F2)
Set4xMsaaState(!m4xMsaaState);

return 0;
}


return DefWindowProc(hwnd, msg, wParam, lParam);
}


Instructions

  • It is very important that you should click the option button to set your keys ,and save it
  • Click the Multiplayer button to enter the character selection interface
    • V1.0 has no AI so we can not click SinglePlayer button
  • Use the LEFT and RIGHT buttons and A button in your button settings to select and confirm the role
    • ps. A Key is not the keyboard A, but the A in the game, generally the default is U key
  • Then player 1 can use the same operation to select the map
  • After that,let’s battle!

Charcater Moves

WuKong

NormalMoves
Keys Moves(temp name)
+A/C 1
+A/C 2
+B/D 3
+A/C 4
SpecialMoves
Keys Moves(temp name)
+A/C 1
+A/C 2
MaxSpecialMoves
Keys Moves(temp name)
(In Max Stat)+A/C 1

Aurora

NormalMoves
Keys Moves(temp name)
+A/C 1
+A/C 2
+B/D 3
+B/D 4
SpecialMoves
Keys Moves(temp name)
+A/C 1
+B/D 2
MaxSpecialMoves
Keys Moves(temp name)
(In Max Stat)+A/C 1

本文介绍路径点的优化方式,或者叫做多线段优化、轨迹点优化

根据设定阈值,去掉路径点中的部分多余点,以达到方便传输的目的

本文用UE4蓝图和C++蓝图函数库的2个方式解释

注:多种方法可以叠加使用

本算法提供了基于UE4的Demo,PC安卓

GitHub工程下载

Demo演示动图

阅读全文 »

角色位移本质上就是渲染问题,从这条思路我们去看看角色怎么实现这一部分渲染的

起点:SetActorLocation

蓝图调用的API实际调用的是K2_SetActorLocation,然后调用到此函数

1
return RootComponent->MoveComponent(Delta, GetActorQuat(), bSweep, OutSweepHitResult, MOVECOMP_NoFlags, Teleport);

只看这个关键代码,调用RootComponentMoveComponent

USceneComponent
MoveComponent

外部调用此函数后里面直接调用到带Impl后缀的虚函数

这个函数在UPrimitiveComponent内有重写

USceneComponent内主要调用了函数ConditionalUpdateComponentToWorld,而UPrimitiveComponent因为有碰撞等属性所以又多了很多逻辑,这里不多描述

UpdateComponentToWorldWithParent

此函数通过ConditionalUpdateComponentToWorld调用,后又调用到虚函数UpdateComponentToWorld,此虚函数在UActorComponent声明,在USceneComponent重写

然后再直接调用到此函数

1
2
3
4
5
6
7
8
9
if (Parent && !Parent->bComponentToWorldUpdated)
{
Parent->UpdateComponentToWorld();

if (bComponentToWorldUpdated)
{
return;
}
}

如果有父级组件,先去处理他

然后调用到函数PropagateTransformUpdate

PropagateTransformUpdate

该函数执行了

  • 更新体积(Bounds)
  • 更新变换(Transform)
    • 更新Attach的子组件的变换信息
  • 更新寻路信息

其中有个关键的函数UActorComponent::MarkRenderTransformDirty

然后经过简单判断以后调用UActorComponent::MarkForNeededEndOfFrameUpdate

然后调用了UWorldMarkActorComponentForNeededEndOfFrameUpdate

看主要代码

1
2
3
4
5
6
7
8
void UActorComponent::MarkRenderTransformDirty()
{
if (IsRegistered() && bRenderStateCreated)
{
bRenderTransformDirty = true;
MarkForNeededEndOfFrameUpdate();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void UActorComponent::MarkForNeededEndOfFrameUpdate()
{
if (bNeverNeedsRenderUpdate)
{
return;
}

UWorld* ComponentWorld = GetWorld();
if (ComponentWorld)
{
ComponentWorld->MarkActorComponentForNeededEndOfFrameUpdate(this, RequiresGameThreadEndOfFrameUpdates());
}
else if (!IsUnreachable())
{
// 如果没有世界,执行如下代码
DoDeferredRenderUpdates_Concurrent();
}
}
UWorld
MarkActorComponentForNeededEndOfFrameUpdate

代码在LevelTick.cpp L883

主要执行了把组件添加到了等待渲染的列表ComponentsThatNeedEndOfFrameUpdate_OnGameThread里,当然还有一个非游戏线程的ComponentsThatNeedEndOfFrameUpdate

1
2
3
4
5
6
7
8
9
10
if (bForceGameThread)
{
FMarkComponentEndOfFrameUpdateState::Set(Component, ComponentsThatNeedEndOfFrameUpdate_OnGameThread.Num(), EComponentMarkedForEndOfFrameUpdateState::MarkedForGameThread);
ComponentsThatNeedEndOfFrameUpdate_OnGameThread.Add(Component);
}
else
{
FMarkComponentEndOfFrameUpdateState::Set(Component, ComponentsThatNeedEndOfFrameUpdate.Num(), EComponentMarkedForEndOfFrameUpdateState::Marked);
ComponentsThatNeedEndOfFrameUpdate.Add(Component);
}

到此我们大概知道了这一条线的终点了,然后去看看哪里使用到了这个数组

SendAllEndOfFrameUpdates

确认使用上面数组是在此函数内,查找引用发现此函数在很多地方被调用,如SceneRendering.cpp,UnrealEngine.cpp大概就是要更新场景最新的渲染的时候就会调用,先不理会,看这个函数内实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for (UActorComponent* Component : ComponentsThatNeedEndOfFrameUpdate_OnGameThread)
{
if (Component)
{
if (Component->IsRegistered() && !Component->IsTemplate() && !Component->IsPendingKill())
{
FScopeCycleCounterUObject ComponentScope(Component);
FScopeCycleCounterUObject AdditionalScope(STATS ? Component->AdditionalStatObject() : nullptr);
Component->DoDeferredRenderUpdates_Concurrent();
}

check(Component->IsPendingKill() || Component->GetMarkedForEndOfFrameUpdateState() == EComponentMarkedForEndOfFrameUpdateState::MarkedForGameThread);
FMarkComponentEndOfFrameUpdateState::Set(Component, INDEX_NONE, EComponentMarkedForEndOfFrameUpdateState::Unmarked);
}
}
ComponentsThatNeedEndOfFrameUpdate_OnGameThread.Reset();
ComponentsThatNeedEndOfFrameUpdate.Reset();

遍历了所有数组成员,主要执行了DoDeferredRenderUpdates_Concurrent,回头看USceneComponent内的如果没有UWorld就直接调用了这个函数

UActorComponent
DoDeferredRenderUpdates_Concurrent

RecreateRenderState_Concurrent,SendRenderTransform_Concurrent,SendRenderDynamicData_Concurrent告诉引擎需要处理相应的渲染任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void UActorComponent::DoDeferredRenderUpdates_Concurrent()
{
checkf(!IsUnreachable(), TEXT("%s"), *GetFullName());
checkf(!IsTemplate(), TEXT("%s"), *GetFullName());
checkf(!IsPendingKill(), TEXT("%s"), *GetFullName());

FScopeCycleCounterUObject ContextScope(this);

if(!IsRegistered())
{
UE_LOG(LogActorComponent, Log, TEXT("UpdateComponent: (%s) Not registered, Aborting."), *GetPathName());
return;
}

if(bRenderStateDirty)
{
SCOPE_CYCLE_COUNTER(STAT_PostTickComponentRecreate);
RecreateRenderState_Concurrent();
checkf(!bRenderStateDirty, TEXT("Failed to route CreateRenderState_Concurrent (%s)"), *GetFullName());
}
else
{
SCOPE_CYCLE_COUNTER(STAT_PostTickComponentLW);
if(bRenderTransformDirty)
{
// Update the component's transform if the actor has been moved since it was last updated.
SendRenderTransform_Concurrent();
}

if(bRenderDynamicDataDirty)
{
SendRenderDynamicData_Concurrent();
}
}
}
SendRenderTransform_Concurrent

这个函数在基类只做了基础实现,主要是派生类重写实现主要逻辑,类似的也包括SendRenderDynamicData_Concurrent,CreateRenderState_Concurrent

1
2
3
4
5
6
7
8
{
check(bRenderStateCreated);
bRenderTransformDirty = false;

#if LOG_RENDER_STATE
UE_LOG(LogActorComponent, Log, TEXT("SendRenderTransform_Concurrent: %s"), *GetPathName());
#endif
}
UPrimitiveComponent

紧接上面,我们看看有形状体积等信息的UPrimitiveComponent

SendRenderTransform_Concurrent
1
2
3
4
5
6
7
8
9
10
11
void UPrimitiveComponent::SendRenderTransform_Concurrent()
{
UpdateBounds();
const bool bDetailModeAllowsRendering = DetailMode <= GetCachedScalabilityCVars().DetailMode;
if( bDetailModeAllowsRendering && (ShouldRender() || bCastHiddenShadow))
{
GetWorld()->Scene->UpdatePrimitiveTransform(this);
}

Super::SendRenderTransform_Concurrent();
}

看到在实现基类逻辑之前执行了FScene内的函数UpdatePrimitiveTransform

FScene

代码在 RendererScene.cpp中

UpdatePrimitiveTransform

UpdatePrimitiveTransform函数中会判断该Primitive有没有SceneProxy,如果有SceneProxy则判断该Primitive是否需要重新创建,如果需要重新创建则需要先删除,然后再添加该Primitive;如果没有SceneProxy则直接加入到场景中。接着我们具体看一下AddPrimitive函数的具体实现:

AddPrimitive

此函数主要做了如下几件事情

  • 创建SceneProxyPrimitiveSceneInfo
  • 利用FPrimitiveSceneInfo封装UPrimitiveComponent的信息,继续在render线程中进行操作
  • 给渲染线程发送2个命令,分别是设置PrimitiveTransform信息和将PrimitiveSceneInfo加入到渲染线程的数据集合中

代码如下

1
2
3
4
5
FPrimitiveSceneProxy* PrimitiveSceneProxy = Primitive->CreateSceneProxy();
Primitive->SceneProxy = PrimitiveSceneProxy;
// Create the primitive scene info.
FPrimitiveSceneInfo* PrimitiveSceneInfo = new FPrimitiveSceneInfo(Primitive, this);
PrimitiveSceneProxy->PrimitiveSceneInfo = PrimitiveSceneInfo;
1
2
3
4
5
6
7
8
9
10
11
// Create any RenderThreadResources required.
ENQUEUE_RENDER_COMMAND(CreateRenderThreadResourcesCommand)(
[Params](FRHICommandListImmediate& RHICmdList)
{
FPrimitiveSceneProxy* SceneProxy = Params.PrimitiveSceneProxy;
FScopeCycleCounter Context(SceneProxy->GetStatId());
SceneProxy->SetTransform(Params.RenderMatrix, Params.WorldBounds, Params.LocalBounds, Params.AttachmentRootPosition);

// Create any RenderThreadResources required.
SceneProxy->CreateRenderThreadResources();
});
1
2
3
4
5
6
ENQUEUE_RENDER_COMMAND(AddPrimitiveCommand)(
[Scene, PrimitiveSceneInfo](FRHICommandListImmediate& RHICmdList)
{
FScopeCycleCounter Context(PrimitiveSceneInfo->Proxy->GetStatId());
Scene->AddPrimitiveSceneInfo_RenderThread(RHICmdList, PrimitiveSceneInfo);
});
AddPrimitiveSceneInfo_RenderThread

AddPrimitiveSceneInfo_RenderThread函数是在渲染线程执行的,完成PrimitiveBoundsPrimitiveFlagsCompact等数据的初始化后,并调用LinkAttachmentGroup处理primitive的父子关系,调用LinkLODParentComponent处理LOD父子关系,然后调用FPrimitiveSceneInfo::AddToScene

AddToScene最终会把物体添加到列表DrawList 中去完成绘制,这里不继续深挖了,埋个扣子,以后来解开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Add the primitive to its shadow parent's linked list of children.
// Note: must happen before AddToScene because AddToScene depends on LightingAttachmentRoot
PrimitiveSceneInfo->LinkAttachmentGroup();

// Set lod Parent information if valid
PrimitiveSceneInfo->LinkLODParentComponent();

// Add the primitive to the scene.
const bool bAddToDrawLists = !(CVarDoLazyStaticMeshUpdate.GetValueOnRenderThread() && !WITH_EDITOR);
if (bAddToDrawLists)
{
PrimitiveSceneInfo->AddToScene(RHICmdList, true);
}
else
{
PrimitiveSceneInfo->AddToScene(RHICmdList, true, false);
PrimitiveSceneInfo->BeginDeferredUpdateStaticMeshes();
}
小结

由此我们就大致整理一下思路

设置Actor的位置,其实就是把新旧位置做一个插值,把这个插值传递给继承自USceneComponentMoveComponent方法,然后在下一帧渲染出新的位置

这里也接上了并解释了之前AI_MoveTo的内容了

上一张流程简图

补充
场景中静态物体的渲染顺序堆栈表参考
1
2
3
4
5
6
7
8
9
10
11
ActorComponet::ExecuteRegisterEvents
UPrimitiveComponent::CreateRenderState_Concurrent
FScene:: AddPrimitive
FScene:: AddPrimitiveSceneInfo_RenderThread
FScene:: AddStaticMeshes
FStaticMesh::AddToDrawLists
FMobileBasePassOpaqueDrawingPolicyFactory::AddStaticMesh
ProcessMobileBasePassMesh
FDrawMobileBasePassStaticMeshAction:: Process
AddMeshToStaticDrawList
//加入到scene GetMobileBasePassDrawList中

最终加入队列 以DrawingPolicykey map队列

1
TStaticMeshDrawList<TMobileBasePassDrawingPolicy<FUniformLightMapPolicy, 0> > MobileBasePassUniformLightMapPolicyDrawList[EBasePass_MAX];

前言

如图所示,我们经常在UE4内看到如此的异步节点,简单说此类节点的输出并非像函数一样瞬间完成,而是拥有自己的生命周期,此类节点一般在右上角有一个时钟标志

本文讲解如何制作类似AI_MoveTo的异步节点

另一篇AI_MoveTo简单分析和扩展介绍AI_MoveTo的简单运行机制和其他扩展方式


  • 2020.11.24更新:

更新各类移动扩展函数库以及K2Node

github

image-20201124134801666

  • 2021.2.25更新:

基于UBlueprintAsyncActionBase的异步节点 跳转

阅读全文 »