绘制 grid
之前一直想知道各种三维软件中的虚拟网格是怎么绘制的,于是上网搜了一下相关实现。没想到里面的小细节还挺多,就用一篇博客记录一下吧 ( •̀ ω •́ )。
下图展示了 Blender 中网格的效果:

从图中可以看到几个基本特征:
网格绘制在世界坐标系下的底面(在一般的右手系坐标系中,Z轴向上,X轴向前,Y轴向右),则对应 XY 平面;
![]()
x 轴标记为红色 \((1,0,0)\),y 轴标记为绿色 \((0,1,0)\)
按照固定间隔绘制网格
之前一直想知道各种三维软件中的虚拟网格是怎么绘制的,于是上网搜了一下相关实现。没想到里面的小细节还挺多,就用一篇博客记录一下吧 ( •̀ ω •́ )。
下图展示了 Blender 中网格的效果:

从图中可以看到几个基本特征:
网格绘制在世界坐标系下的底面(在一般的右手系坐标系中,Z轴向上,X轴向前,Y轴向右),则对应 XY 平面;

x 轴标记为红色 \((1,0,0)\),y 轴标记为绿色 \((0,1,0)\)
按照固定间隔绘制网格
最近需要在离线开发环境中配置 VS Code。本来想在 Visual Studio Marketplace 网页上手动下载,再拿到 VS Code 里安装,结果发现有些插件无法直接下载打包好的 .vsix 文件。查了一下后发现,可以直接在 VS Code 的扩展面板里下载,所以这里记录一下具体做法。
首先在外网机器上准备好 VS Code 和对应版本的安装包(一般直接使用最新版即可)。可以通过下列方式查看 VS Code 版本:


可以在 这里 下载到对应的安装包。
一定要确保安装包和外网机器安装的 VS Code 版本一致,否则下载的插件可能无法正常安装。
Shader(着色器)简单来说就是运行在 GPU 上的一小段程序,用于控制图形渲染的方式。
在传统渲染管线中,GPU 会自动执行一套固定流程,而 Shader 的出现让开发者能够自定义其中的一部分流程,从而实现更灵活、更复杂的图形效果。
Shader Toy 是一个在线的 Shader 编写和展示网站,可以实时展示 Shader 编程效果。

在图形 API(OpenGL)中,支持多种 Shader 类型:
而在 Shader Toy 中,所谓的 Shader 通常特指 Fragment Shader。它的输入可以理解为一个覆盖整个显示区域的平面,因此我们可以在 Fragment Shader 中决定显示区域内每一个像素的颜色(RGBA)。
??
所谓投影,就是 将高维空间中的对象通过某种方式映射到低维空间,或者映射到另一个空间中。如下图所示(图片来自网络)。

当我们在二维平面上展示三维物体时,看到的其实就是三维物体在二维空间中的一个投影,而这个从三维到二维的转换过程就是投影变换。
如果投影过程可以表示成线性变换,就可以用一个矩阵来描述它,如下式所示,其中 \(M_\text{projection}\) 就称为投影变换矩阵。 \[ P^{\prime} = M_{\text{projection}} \cdot P \]
最近写博客时,我发现 GitHub Markdown 中提供的 Alerts 很有用,可以作为提示信息来展示。
它本质上是在 引用文本(Quoting text) 的基础上增加了一个特殊标记,从而在渲染时呈现出特殊的显示效果,对应的 Markdown 代码如下:
1 | > [!NOTE] |
渲染结果如下:

本来我以为这是 gfm(GitHub Flavored Markdown Spec)规范的一部分,后来才发现这其实是 GitHub 单独实现的功能,Hexo 默认并不支持。不过搜了一下,发现实现起来并不复杂,于是就用这篇博客记录一下实现过程。
Conan 作为一个 C++
包管理工具,相对于之前介绍的 CMake FetchContent 或者 ExternalProject
方式添加第三方依赖会更加简单,但是凡是碰上了
C++,都简单不到哪去,学习成本还是有一点的,就用一篇笔记介绍一下
Conan 的基本使用。
由于我们后面会大量使用 conanfile.py,且也可以借助 Python 插件实现
conanfile.py 的代码补全,直接使用 Python 库的方式安装
Conan,安装命令如下:
1 | python -m pip install conan |
安装之后例行检查一下版本
1 | conan --version |

通过 $PROFILE 变量,可以查询到 PowerShell
默认加载的配置文件路径,如下所示

如果文件不存在,直接创建一个即可,然后填入下面两个预定义的函数(默认代理的地址为
127.0.0.1:7890,纪念天国的CFW🙏)
1 | function proxy_on { |
当更改完 $PROFILE 文件后,使用 . $PROFILE
加载更新后的配置文件,并通过简单的 curl
命令判断是否可用
1 | git clone <repo-url> |
示例命令如下
1 | git clone https://github.com/boostorg/boost.git |
一些常用的参数(全部参数选项可以通过 git clone --help
查看)
--recursive 或
--recurse-submodules(在较新版本的 git
中使用,命令语义更加清晰):当要拉取的仓库中包含 submodule
时,默认不会拉取子仓库,而指定这个参数即可拉取所有的子仓库。
如果我们在下载的时候忘记指定
--recursive,也可以通过下面命令重新拉取子仓库
1 | git submodule update --init --recursive |
--depth:当我们只想下载指定数量的提交历史时,这个参数可以减少下载体积;如果只需要源码,也可以直接下载
zip 或 tar.gz 等压缩文件。
-b 或者
--branch:指定要拉取的分支名称,例如下载 boost-1.86.0
(这个实际上是一个 tag,但也可以通过这种方式下载)。
1 | git clone https://github.com/boostorg/boost.git --branch boost-1.86.0 |
对于一个\(2^n\times2^n\)的二维网格由于地址空间都是一维的(例如0x0000到0xffff),其实际在计算机中会将多维压缩成一维,可以形式化表示为
\[ \begin{align} M_{\text{2D}}&:(x,y)\to v \\ M_{\text{2D}}^{-1}&:v\to (x,y) \\ \end{align} \]
对于\(M_{\text{2D}}(x,y)\)也可记作\(\text{encode}(x,y)\),\(M^{-1}_{\text{2D}}(v)\)为\(decode(v)\)
一种最为简单的存储方式就是逐行地或逐列地存储二维数据,对于一个指定大小的二维方块(\(w\times h\)),从0开始的坐标\((x,y)\) 对应的一维地址为(一行包含\(w\)个元素) \[ v = x \times w + y \] 同理,将一维坐标\((v)\)转换二维方块坐标也很简单,只需要对行/列进行整除和取余操作即可 \[ \begin{align} x &= \lfloor v / w \rfloor \\ y &= v - x \times w \end{align} \] 上面这种方式在一般情况下足够使用,但是对于纹理/体素等数据的读取时,通常会利用到二维/三维坐标的局部性(即当我们访问坐标\((x,y)\)时,我们有很大概率将访问该坐标周围的坐标),而使用线性存储方式则会丢失这种局部性。
例如对于一个\(16 \times 16\)的方块中坐标\((4,4)\)和坐标\((6,6)\),其二维曼哈顿距离为 \[ (6-4)+(6-4) = 4 \] 而经过线性存储映射后距离为 \[ (6*16+6)-(4*16+4)=38 \] ,可以观察到经过一维映射后两个数据距离相差较远。
下图展示了 linear curve 的示意图

直接说距离似乎难以理解这种差距,考虑一个实际读取场景,假设CPU的cache一次可以存放16个数据,当我们读取纹理中\((4,4)\)位置的数据后,下一个准备读取其周围\((6,6)\)的数据,由于\((6,6)\)的数据在内存中地址距离\((4,4)\)数据地址较远,无法一次加载到cache中,即出现cache miss,这样我们就只能从内存中重新加载数据,无法利用高速缓存,影响数据读取性能。
针对这个问题,可以使用Hilbert曲线或者Morton曲线这类特殊的映射方式进行坐标映射