tag dispatch

对于short类型来说,会优先匹配通用引用版本的重载,导致无法构造string

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
namespace TagDispatch {

template <typename T>
void fun(T&& params) {
std::vector<std::string> temp;
temp.emplace_back(std::forward<T>(params));
std::cout << "called fun(T&& params)" << std::endl;
}

void fun(int a) {
std::cout << "called fun(int a)" << std::endl;
}

template <typename T>
void fwd(T&& params) {
fun(std::forward<T>(params));
}

void test()
{
fwd("abc"); //suc
int a = 0;
fwd(a); //suc

short b = 10;
//fwd(b); //failed, 匹配到了通用引用函数

}

int main(int argc, const char * argv[]) {
TagDispatch::test();

return 0;
}

}

使用TagDispath,来解决匹配的问题

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
namespace TagDispatch {

template <typename T>
void fun2(T&& params, std::false_type) {
std::vector<std::string> temp;
temp.emplace_back(std::forward<T>(params));
std::cout << "called fun(T&& params)" << std::endl;
}

void fun2(int a, std::true_type) {
std::cout << "called fun(int a)" << std::endl;
}

template <typename T>
void fwd(T&& params) {
fun2(std::forward<T>(params), std::is_integral<typename std::remove_reference<T>::type>());
}

void test()
{
fwd("abc"); //suc
int a = 0;
fwd(a); //suc

short b = 10;
fwd(b); //succeed, 匹配到了通用引用函数
}

}

针对函数的构造函数,使用通用引用重载,主要解决思路是通过enable_if来限制模板的匹配

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
class Person
{
public:
template <
typename T,
typename = std::enable_if_t<
!std::is_base_of<Person, std::decay_t<T>>::value // 防止调用拷贝和移动构造函数,并且考虑了子类
&& // decay,移除指针引用和cv修饰符
!std::is_integral<std::remove_reference_t<T>>::value
>
>
explicit Person(T&& params) {
std::vector<std::string> temp;
temp.emplace_back(std::forward<T>(params));
std::cout << "Person(T&& params)" << std::endl;
}

explicit Person(int a) {
std::cout << "Person(int a)" << std::endl;
}
};

void testCtrOverload() {

short b = 10;
Person person(b);

}

}

模板规则推导

模板推导规则

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
template<typename T>
void passByRefFun(T& val) { }

template<typename T>
void passByUniRefFun(T&& val) { }

template<typename T>
void passByValueFun(T val) { }

void fun3(int& a) { }
void fun3(int&& a) { }

void test()
{
int a = 0; //a int
const int b = a; //b const int
const int& c = a; //c const int&

passByRefFun(a); // int&
passByRefFun(b); // const int&
passByRefFun(c); // const int&
//passByRefFun(27); // 报错

passByUniRefFun(a); // int&
passByUniRefFun(b); // const int&
passByUniRefFun(c); // const int&
passByUniRefFun(27); // int&&

passByValueFun(a); // int
passByValueFun(b); // int
passByValueFun(c); // int
passByValueFun(27); // int

{
int&& a = 10;
fun3(a); // called void fun3(int& a)
fun3(std::move(a)); // called void fun3(int&& a)

}
}

电子书目录

渲染整理

开放世界的场景管理

1. 切成tile
2. 数据分级LOD
3. 根据天顶角,调整远平面,裁剪数据

游戏引擎分层架构

1. EditorLayer
2. FuntionLayer
    1. Rendering
    2. Animation
    3. Camera
    4. Physics
    5. Script
3. ResourceLayer
4. CoreLayer
    1. threadPoolManagement
    2. memoryPool
    3. mathLibrary
5. PlatformLayer
    1. RHI

Renderable

1. shader
    1. vs,fs
    2. macro
2. RenderState
3. MVP 
4. Texture
5. DrawImp

渲染效果

1. 前向渲染
      1. PBR
      2. 布林冯模型
2. 阴影shadowMap:
    1. 根据相机的位置,对整个场景,绘制出深度图,表示光的可见性
    2. 渲染时,将相机位置变化到光源位置,计算深度值与shadowmap进行比较,从而决定绘制的亮度
3. 楼的倒影
    1. 矩阵楼块插入地面
    2. 模板测试,只有地面和水才绘制
4. AO
5. 聚光灯效果
6. UV动画

技术点提炼

1. 楼块
    1. 切成小块
    2. 柔化圆角
    3. 贴UV
2. 3d瓦片绘制
    1. 瓦片的加载,与cache
    2. 非实施例渲染,按材质进行分类渲染
    3. 实例化渲染,动态计算lod进行渲染
3. 模型渲染
    1. PBR渲染
    2. 非PBR渲染

c++ 可变参数模板

可变模版参数(variadic templates)

可以对参数进行高度泛化,标识0到任意个数参数

两种展开形式

  1. 使用特化的终止函数结合递归
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;

//终止函数
int multiply()
{
return 1;
}

//递归展开
template <typename T, typename ...Args>
int multiply(T&& t, Args&& ...arg)
{
return t * multiply(arg...);
}

int main(int argc, const char * argv[]) {

cout << multiply(3, 4, 5) << endl;

cout << multiply() << endl;

return 0;
}
  1. 使用逗号表达式
    初始化列表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)…}将会展开成((printarg(arg1),0), (printarg(arg2),0), (printarg(arg3),0), etc… ),最终会创建一个元素值都为0的数组int arr[sizeof…(Args)]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
template<class F, class... Args>void expand(const F& f, Args&&...args)
{
//这里用到了完美转发,关于完美转发,读者可以参考笔者在上一期程序员中的文章《通过4行代码看右值引用》
initializer_list<int>{(f(std::forward< Args>(args)),0)...};
}

int main(int argc, const char * argv[]) {

expand([](int i) {
cout<<i<<endl;
}, 1,2,3);
return 0;
}

initializer_list

为了编写处理不同数量实参的函数,如果参数类型相同,可以使用initializer_list, 如果实参类型不同,使用可变参数模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct myclass {
myclass(int,int)
{
cout << "myclass constructor" << endl;
}
myclass(initializer_list<int>)
{
cout << "myclass initializer_list constructor" << endl;
}
};


int main(int argc, const char * argv[]) {

auto il = {10, 20, 30};

myclass foo {10,20}; // calls initializer_list constructor
myclass bar (10,20); // calls first constructo

return 0;
}

c++继承权限

  1. 继承有三种权限,public,proteced,private,默认不写是private

  2. 权限的最低是public,其次是protected,最高private

  3. 继承方式代表是父类属性在当前类中的最低呈现

  4. 父类中的privated属性在子类中不可访问

OpenGL模板测试流程

模板测试流程

不考虑earlyZ的情况下,fragment执行后,进行模板测试,通过后,进入深度测试

模板测试一般使用流程:

1. 启用模板缓冲写入
2. 渲染物体,更新模板缓冲
3. 禁用模板缓冲写入
4. 渲染其他物体,根据模板缓冲内容决定是否丢弃片段
使用模板测试绘制物体轮廓的例子
1
2
3
glStencilMask();
glStencilFunc(GLenum func, GLint ref, GLuint mask);
glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass);
1. 开启模板测试和深度测试 2. 第一次render pass,主要是绘制,并写入模板 1. 开启模板测试和深度测试 2. glStencilMask(0xFF); 3. glStencilOp(keep, keep, replace); 4. glStencilFunc(always, 1, 0xFF); 5. 绘制物体 3. 第二次render pass, 放大物体,通过模板测试剔除非边缘像素 1. 将物体缩放变大 2. 关闭深度测试 //因为这里的边缘不需要有拓扑关系 3. 关闭模板写入glStencilMask(0x00); 4. glStencilFunc(not_equal, 1, 0xFF); 5. 绘制物体

关于OpenGL里面的Mask

1. 写入颜色是,r,g,b,a 分别与对应的mask,进行&运算后写入
2. depth也是同样道理,如果设置成true,就是允许写入,设置成false,不允许写入
3. stencil的Mask,是0xFF~0x00,之间的256个数,一般设置是0xFF,允许任意值写入,0x00是不允许写入

实现完美转发

什么是完美转发?

在理解什么是完美转发之前,需要知道什么是万能引用?

在模板推导过程中,使用T&& a,这时候,并不是类型T的右值引用,而是万能引用,如果a是左值,这时候,就是一个左值引用,如果a是右值,这时候就是一个右值引用,具体原理是发生引用折叠。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename T>
void Add(T&& a, T&& b) {
cout << a << endl;
cout << b << endl;
}

int main() {

Add(4, 5); // a,b的类型会被推导成int&&
int a = 0;
int b = 0;
Add(a, b); // a,b的类型会被推导成int&

return 0;
}

根据参数的具体类型,来实例化模板,准确的生成左值引用和右值引用的实例,这就是万能引用

万能引用遇到的问题?

上面的例子中,Add函数参数虽然是类型是右值引用,但是值确实左值,导致函数内继续使用调用其他函数时,参数类型由右值变成左值,也就是无法将右值引用这个类型继续转发.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template <typename T>
void AddImp(T&& a, T&& b) {
cout << a << endl;
cout << b << endl;
}

template <typename T>
void Add(T&& a, T&& b) {
AddImp(a, b);
}

int main() {

Add(4, 5);
int a = 0;
int b = 0;
Add(a, b);

return 0;
}

解决方案: std::forward

1
2
3
4
5
template <typename T>
void Add(T&& a, T&& b) {
AddImp(std::forward<T>(a), std::forward<T>(b));
}

std::forward的具体实现

1
2
3
4
5
template <class _Tp>
_Tp&& forward(typename remove_reference<_Tp>::type& __t)
{
return static_cast<_Tp&&>(__t);
}

具体分析一下,也是通过引用折叠来实现

  1. 如果_Tp的类型是int&, 通过引用折叠 int& && 折叠后是左值引用int&
  2. 如果_Tp的类型是int&&, 通过引用折叠 int&& && 折叠后是int&&