Ayy3

时间停止吧,你是多么的美丽

本来打算今天拆笔记本的,结果没动力,啥都不想干。

前几天被舍友叫去打CS2,然后正在等人的时候,突然steam上一个不认识但是是好友的人问我能不能一起打。

我说可以,但我都忘记我什么时候加的这个人的了。后面他开语音,我才记得是高一的舍友。

他问我是不是保去复旦了,我说我爱南航。他是厦大的,我自然不如他,当然打游戏也不如他。他5e1900分,我1000分。

没什么脸,哪怕是去回忆高中的那些同学。除了我全是985本科,我还搁哪苦恼能不能保研成功呢,他们已经有工作了。

小学同学肯定是都忘了,估计也死了几个。初中同学当然也忘的差不多了。

大学,呵呵。

回望大一的时候,特别喜欢玩Unity,特别喜欢算法竞赛。

现在已经没什么欲望了。

要我说,在南航的四年,不至于生不如死,因为我还不希望我出生呢。

这就是个垃圾学校,经费全喂狗的学校,或者拿去充榜单排名,然后天天碰瓷985的出生学校。

以前的我满怀期待地去做大创,满怀期待地加入ACM校队,满怀期待地上机器学习课。

我不知道我在期待什么,明知道自己的水平肯定不只有南航的水平,但是就是想要得到指导,想要从学校学点知识。神经病啊。想在南航学习?

自从搬到天目湖后,南航的ACM就是个狗屎。但我还比较贱,即便身边根本没人打CF,我还搁哪训。

结果训emo了。不是因为我水平低,而是因为我发现自己复健后的水平其实比拿银牌的时候还要高。

发现很有潜力?呵呵,南航直接毁了你的潜力。

我的水平在这里一直都是独一档,断档压制一般的oier和其他人,但是比不过中学学过很多年的有NOI牌子的人。

他们固然是成功的,其他人固然是失败的,我是莫名其妙的死的。

我没有报过任何网课,我全靠看博客起来的。不过管你什么样,只要你初始没有省队的水平,再怎么努力都是失败,因为这里是南航。

这里是能偷改你学分,让你大四下还得上课的学校。

这里是能毁掉你的技术热情,让你被病态的内卷环境压力到变形的学校。

南航的同学,大部分也是傻逼。天天搁哪看AI论文,搁哪训神经网络,甚至在ACM实验室都没人刷题,一个个在哪看paper。懂点技术的又菜又爱装,眼高手低。

哦,我才是傻逼。我是AI的专业第一,但是天天逃课,骂学校骂老师骂同学,现在天天在那整游戏开发,大作业都让别人做。

我才是有错的人,身边的同学都太厉害拉,我是害虫。

南航太好了,是个好学校,如果你想死,建议你报考。


好了,正常点。我最近情绪又变得比较低迷,虽然从南航回到家里很开心,但是我发现我找不到动力了。明明大一的时候我能摸早起来启动Unity或者启动洛谷直接撸码,而现在做什么都没精神。原因只能是,我的本科经历,真的痛不欲生。

我懒吗?我学习能力差吗?

还是那句话,我痛苦的原因,不是我做错了什么,而是我什么都没做错,但是下场跟什么都做错了一样。

毕业了就和所有南航的人永别吧,这会让我更快乐,不要再想和南航有关的事了,肯定和解不了。好在现在认识的朋友都是校外的网友,互联网当e人准没错。

离南航和南航的人远点。

离南航和南航的人远点。

离南航和南航的人远点。

每周总结#3

游戏引擎

跟了个油管的博主,动手实现自己的game engine。

premake.lua

一个构建系统,相对cmake简单许多。

links :这个命令是我一开始不太清楚的。因为在我们的解决方案中,我们实际上将游戏引擎和Sandbox作为两个项目,让游戏引擎engine这个项目的最终文件是个动态链接库(windows是.dll),运行时和sandbox生成的.exe文件放在一块。

事实上如果用premake构建vs项目,links其实是对于一个项目创建了另一个项目的引用,而不是链接了一些库。引用是告诉链接器,某些符号是可以使用的,但是它的具体实现在dll文件里(因此有必要运行一些复制dll到指定路径的命令)。

defines:定义一些宏,这些宏在对应的项目中可以使用。

例如针对dll的导出导入符号:

1
2
3
4
5
6
7
8
9
#ifdef E_PLATFORM_WINDOWS
#ifdef E_BUILD_DLL
#define E_API __declspec(dllexport)
#else
#define E_API __declspec(dllimport)
#endif
#else
#error only windows!
#endif

我们可以在构建文件中,针对设备的系统,以及项目,定义一些特定的宏。

事件系统

目前的Event System都是Block的,就是对于事件的执行都是立刻的,会阻塞当前线程。

考虑如何设计,首先我们很容易想到用enum或enum class来设置事件的种类,类型。

然后设置Event基类,后续具体的事件继承这个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class E_API Event
{
friend class EventDispatcher;
public:
virtual EventType GetEventType() const = 0;
virtual const char* GetName() const = 0;
virtual int GetCategoryFlags() const = 0;
virtual std::string ToString() const { return GetName(); }

inline bool IsInCategory(EventCategory category)
{
return (GetCategoryFlags() & category);
}
private:
bool m_Handled = false;
};

由于事件很多,对于每一个子类都要重写方法很累,因此有个比较骚的操作就是将重写的内容用宏替代:

1
2
3
4
5
#define EVENT_CLASS_CATEGORY(category) virtual int GetCategoryFlags() const override { return category; }

#define EVENT_CLASS_TYPE(type) static EventType GetStaticType() { return EventType::##type; } \
virtual EventType GetEventType() const override { return GetStaticType(); } \
virtual const char* GetName() const override { return #type; }

EventType::##type 中的 ## 是预处理操作符,用于将宏参数 type 与 EventType:: 连接起来。例如,如果 type 是 Mouse,那么 EventType::##type 就会被展开为 EventType::Mouse。 return #type; 中的 # 是字符串化操作符,将宏参数转换为字符串。如果 type 是 Mouse,那么 #type 就会被展开为 "Mouse"。

然后在类里面根据具体事件类型进行宏展开。

事件分发器,这个东西负责将其对应的事件传到某个函数进行执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class EventDispatcher
{
template<typename T>
using EventFn = std::function<bool(T&)>;
public:
EventDispatcher(Event& event)
: m_Event(event)
{
}

template<typename T>
bool Dispatch(EventFn<T> func)
{
if (m_Event.GetEventType() == T::GetStaticType())
{
m_Event.m_Handled = func(*(T*)&m_Event);
return true;
}
return false;
}
private:
Event& m_Event;
};

std::function 我并不是很熟悉(虽然见过好多次了),暂且将其理解为函数指针。然后对于这里强制转换:func(*(T*)&m_Event),个人认为没有什么必要?

由于还没使用到事件分发器,这里先略过。

软光栅

使用类似engine的设计思路,将渲染器的主体封装成dll,给具体的系统(前端233)作为运行时库调用。我目前希望能够跨平台(虽然现在只知道在windows下是用dll进行符号传递),所以采用了premake作为构建系统,然后不调win32。

目前完成了:

  • I/O
    • obj导入
    • 贴图导入
    • ppm形式输出
  • gl
    • 画线
    • 画三角形
    • Z-buffer

大部分时间都在调bug吧,今天调了一上午bug,发现是重心坐标公式抄错了。然后CursorAI把width和height弄反了,又搞了半天。

因为感觉渲染一张图有点慢了,因此尝试用了vs的性能分析,结果发现:

好家伙,时间全浪费在输出流上了。这个后续实现实时渲染的时候再考虑吧,看看能不能和QT对接(outputer使用特定平台开发,跨平台只针对我的AYR项目),然后去掉这个流。

UE

这个嘛,因为我更偏向做游戏客户端(引擎感觉太难了,可能要paper,然而我已经没时间走学术了),所以目前有空就熟悉一下UE的使用。后续找实习的时候肯定是需要一个具体游戏项目放在简历的。

毕设

懒得搞。

TODO

得处理一些学校的破事了。然后继续学习。

最近在考虑做游戏客户端是不是容易被AI替代,但是目前的我应该是没有能力去分析这个的。因为目前我还在图形学这里自娱自乐,整天盯着我那屎山项目。但如果以我几年前玩Unity的经验,我觉得短期容易被替代的是那种只会写简单逻辑的程序员。我相信gameplay会是一个很大的,充满想象力的学问,而AI是不会取代具有想象力的工作的。

所以该做什么就做什么,先把手上的软光栅做了,然后尝试构思自己的玩法,开发一个有意思的FPS游戏。

一个很不成熟的做题家思维:你一个南大的会被替代,那其他人不是更寄?。但是回过来看,光焦虑也没用,反而影响自己的进度。这种做题家思维反而会有效减少一些焦虑。

每周总结#2

1. 毕设

目前为止还是不知道自己要做什么。

不过就算知道了,我估计也做不出来。python代码有个弊端就是,对于一个变量它的类型需要你无数次Ctrl+F来自行推导。对于pytorch这里面无数的张量,你要一个一个推出它的shape,然后来确保自己在某个函数的代码能够理解了。

当然我要写的话应该是写cuda代码,然后在python这里看看怎么调用cuda。唉,真头疼,感觉选了个最难的毕设。

我要做的是研究3DGS与光照结合的方法。之前在games101学的无论是光栅化还是光线追踪,都是基于mesh的模型,而3DGS是点云模型,因此需要研究方法能让点云模型具有光照特性。老师给的论文介绍了,在3DGS的基础上,增加一些诸如法向量,以及一些PBR材质相关的可训练的参数。然而我不是做AI的,不知道在代码层面上是怎么在cuda里实现反向传播的(只知道pytorch里的optimizer.step),结果要我改这部分cuda的代码,。。。

希望能水过去,搞的我现在有点烦。

2. 学习

games104

开始了games104的课程,这是个讲游戏引擎怎么构建的课。

目前听了前三节,感觉讲的太笼统了,我还是比较喜欢看代码。

稍微总结一下吧:

首先是引擎架构,其根据功能进行分层,有:

  • 平台层:不同平台(比如操作系统,当然还有所谓的Graphics API,比如opengl,dx,vulkan,当然这些我也不是很懂)会提供不同的底层API,游戏引擎需要为开发者提供统一的API,这东西好像叫RHI。
  • 核心层:一些数学运算、内存管理、数据结构的实现,比较底层
  • 资源层:将不同类型的资源(resource)导入引擎成为对应的资产(asset),每个asset有对应的GUID方便管理,每个asset也有对应的生命周期,需要handle系统去管理
  • 功能层:每一帧会调用tick函数,它大致执行两个任务:tick logic和tick render。当然也不止这两个任务,功能层是最多的内容,它可能还涉及到并行化。
  • 工具层:编辑器那些,让开发者进行开发的空间。

然后是对于游戏世界的认识,它包括一些动态物、静态物、环境等等。对于基本的游戏世界的对象,用GO(Game Object)来表示。

每个GO有许多的组件(compoment,玩过Unity应该很熟悉这个),当然这样的组件化设计其实是有很多缺点的,但我没写过对应的代码,没什么认识。

其次就是tick,它是让游戏世界动起来的原因。一种tick方式是一个GO一个GO的tick,还有一种tick方式是按照组件类型进行tick,后者可能会更有效。(cache的空间局限性?)

其次就是交互,采用event事件机制,将不同对象的不同组件之间进行解耦合。

对于场景的管理,引擎的核心层(我猜的哈)可能会用一些数据结构进行高效管理,例如毕设接触到的bvh树来通过场景内的物体位置进行构建,加速查询流程。

对于tick,其实它的时序很有讲究。针对event的邮局机制则有效地解决了事件机制与并行化结合可能导致的时序问题。

总结完了,感觉啥都没总结到。慢慢学吧,我总是太,想看代码。

tinyrenderer

之前说过,结束了games101的学习,大部分同学可能都会尝试地去写一个自己的软光栅。我也不例外,因此我开始了这个项目的学习。

目前呢?刚学会画直线???2333

misc

好消息,离开NUAA了,真的非常开心。

下一次就可以永别这里了,希望再也不用看见这里的人,再也想不起这里的事。

3. TODO

白天搞学校的毕设和其它事情(读论文?做实验?反正都是浪费时间的事情)

晚上学自己喜欢的东西。

没时间打acm了,认命吧,拿不了金牌的fw。

每周总结#1

一类套路题

有两种购买物品的方式,花p元购买a个物品或者花q元购买b个物品。求至少买w个物品的情况下,花钱的最少数。

这种套路题用数学表示为,在满足 的情况下,最小化 的值。

解法

怎么做呢?首先你可能会想到用斜率+贪心的方法,但这肯定是错的。

事实上有这么一个结论:

不可能同时用花p元的方式购买b次且用花q元的方式购买a次。

如何证明呢?首先我们考虑购买ab个物品,如果用p元的方法会花pb元,而用另一种方法会花qa元。

如果我们同时购买很多次ab个物品,那么一定会只使用花费为pb或者qa中的一种的方法,看二者哪个更小。

因此的话,有种 的方法:从 枚举花p元的方式,再从 枚举花q元的方式。

ABC374E

在上面套路添加了一层二分,事实上二分很好想,但上面的贪心套路才是关键。

这里会爆 int ,因此我为了方便就 #define int long long 了。

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
#include<bits/stdc++.h>
typedef long long ll;
#define int long long
constexpr int _{110};

int a[_], b[_], p[_], q[_];
int n, x;
signed main()
{
std::cin >> n >> x;
for(int i = 0; i < n; ++i)
std::cin >> a[i] >> p[i] >> b[i] >> q[i];
int l{1}, r{1000000000};
int ans{};
while(l <= r)
{
int mid{l+r>>1};
ll sum{};
for(int i = 0; i < n; ++i)
{
int smn{x+1};
for(int j = 0; j < b[i]; ++j)
{
int s{};
if(j * a[i] >= mid)
{
s = j * p[i];
}
else
{
s = j * p[i] + (mid - j * a[i] + b[i] - 1) / b[i] * q[i];
}
smn = std::min(smn, s);
}
for(int j = 0; j < a[i]; ++j)
{
int s{};
if(j * b[i] >= mid)
{
s = j * q[i];
}
else
{
s = j * q[i] + (mid - j * b[i] + a[i] - 1) / a[i] * p[i];
}
smn = std::min(smn, s);
}

sum += smn;
}

if(sum <= x)
{
ans = mid;
l = mid + 1;
}
else
{
r = mid - 1;
}
}
std::cout << ans;
return 0;
}
0%