变换
前言 在计算机图形学中,变换(transformation)是对点、向量的坐标进行修改的操作。渲染管线的各个环节(比如摆放物体、设置摄像机、把三维场景投影到屏幕上等)本质上都离不开变换。本节从缩放、旋转、平移这三种基础变换入手,介绍它们对应的矩阵,最后把二维的结论推广到三维空间。 在此先行强调一下:我们的变换通过矩阵乘法实现,即让在一个 矩阵/向量 上左乘一个变换矩阵,实现坐标的修改。 一些事实 在推导变换矩阵之前,先列出几个关于向量与矩阵的基本事实,后面的内容会反复用到: 一个 nnn 维向量左乘 nnn 阶单位矩阵(identity matrix)InI_nIn,得到它本身: (1001)(xy)=(xy)\begin{pmatrix} 1 & 0 \\ 0 & 1 \end{pmatrix} \begin{pmatrix} x \\ y \end{pmatrix} = \begin{pmatrix} x \\ y \end{pmatrix} (1001)(xy)=(xy) 若矩阵 AAA 可逆,则它与逆矩阵(inverse matrix...
CMake基础
CMake CMake 是一个跨平台的构建系统生成工具。它不直接编译代码,而是根据开发者编写的 CMakeLists.txt 配置文件,生成对应平台的构建文件(如 Unix 下的 Makefile、Windows 下的 Visual Studio 解决方案、Ninja 构建文件等),再由这些构建文件驱动编译器完成编译。 简单来说,CMake 解决的是"同一个项目如何在不同的平台和编译器上编译"的问题。 CMakeLists.txt 文件 CMakeLists.txt 是 CMake 的配置文件,所有构建规则都写在它里面。CMake 对它有严格的命名和位置要求: 1. 文件名严格区分大小写,必须精确地命名为 CMakeLists.txt,不能写成 cmakelists.txt、Cmakelists.txt 或其他变体。这是因为 CMake 在搜索配置文件时只会匹配这个准确的文件名。 2. 文件必须放在项目的根目录下(也可以放在子目录中以构建多级项目,但是也属于子项目的根目录)。 3. 每一个包含 CMakeLists.txt 的目录都是一个独立的构建单元,C...
初识 Go 与开发环境搭建
初识 Go 与开发环境搭建 Go 是 Google 在 2007 年立项、2009 年 11 月开源、2012 年 3 月发布 1.0 版本的编程语言。它由 Robert Griesemer、Rob Pike、Ken Thompson 三人设计,目标是解决大规模工程中"编译慢、语言特性多、并发难写"这三类问题。 这篇笔记先建立对 Go 的整体认识,然后把开发环境搭好,最后跑通第一个程序,并说清楚源文件的一些基本约定。 Go 的设计目标 Go 的核心设计目标可以归纳为下面几条,后面的所有语言特性几乎都是围绕它们展开的: 编译型语言:源码编译为机器码后直接执行,没有虚拟机或解释器参与运行。 静态类型:所有变量的类型在编译期确定,类型不匹配在编译期就报错。 原生并发:goroutine(由运行时管理的轻量执行单元)和 channel(用于在 goroutine 之间传递数据的管道)是语言级特性,不是库。 语法简洁:关键字只有 25 个,没有类继承、没有异常、没有隐式类型转换。 编译速度快:大型项目也能在几秒内完成构建,这是明确的设计指标。 内置工具链:格式化、...
SVN操作
SVN 介绍 SVN 全称 Subversion,是一个版本控制系统(Version Control System,VCS),主要用于管理文件和目录的变更。它允许多个用户协作开发项目,跟踪文件的历史记录,并支持分支和合并操作。 SVN 属于集中式(centralized)版本控制:整个项目只有一份权威仓库放在服务器上,所有人的工作副本都从它检出,也都向它提交。这一点和 Git 那种每个克隆都是完整仓库的分布式(distributed)模型不同——离了服务器,SVN 的工作副本既看不了历史,也提交不了东西。 主要作用 记录历史:每一次提交都会在仓库里留下一个不可变的版本,之后可以随时查看、对比,或者把某个文件回退到任意历史版本 多人协作:所有人共用一个仓库,通过提交和更新交换代码,服务器负责协调谁改动了哪些文件 分支与标签:可以用很低的成本把一份代码复制成分支(branch)或标签(tag),支撑并行开发和版本归档 权限控制:可以按用户、按目录设置只读或可写的访问权限 统一入口:无论团队里有多少人,仓库地址只有一个,新成员检出之后拿到的就是完整代码 基本概念 Repos...
模型、视图、投影(MVP)变换
需要预先了解: 变换 模型变换(Model Transformation) 模型变换(model transformation)把顶点从模型空间(model space,也称局部空间 local space)变换到世界空间(world space),对应的矩阵称为模型矩阵(model matrix),一般用符号 MMM 来表示。 计算机图形学中,我们经常会提到“空间”的概念,这里可以把空间理解为一个坐标系,用于描述物体的位置和方向。 具体地讲,通常一个模型在建模软件里被创造出来,里面需要存储顶点之类的模型的数据,建模的过程其实就是在模型空间中,原点通常落在模型的中心或脚底,轴向由建模习惯决定(Blender 默认 zzz 轴向上,glTF 和多数引擎默认 yyy 轴向上)。这些数字只有在"这个模型自己"的语境下才有意义。世界空间则是整个场景(scene)共享的坐标系,物体的摆放、光源与摄像机的位置都在这里描述,后续会讲到的视图变换与光照计算也都在这个空间里进行。 模型矩阵的组成 模型矩阵一般由平移(translation)、旋转(rotat...
变量、常量与基本类型
需要预先了解: 初识 Go 与开发环境搭建 变量、常量与基本类型 上一篇把环境搭好并跑通了第一个程序,这篇开始进入语法。Go 的变量声明有几种固定写法,const 的规则比大多数语言严格,而基本类型里 rune、byte 和 int 的宽度是最容易搞混的几个点。 变量声明:var var 是最基础的声明关键字,有下面几种形式: var a int = 10 // 完整写法:类型 + 初值 var b = 20 // 省略类型,由右边的值推断 var c, d = 1, 2 // 一次声明多个,类型分别推断 var e int // 只声明不赋值,取零值 类型推断只在有初值的时候生效,var e 这种没有初值的写法必须写类型,否则编译器无法知道 e 是什么。 变量也可以成组声明,适合把相关的变量放在一起: var ( name string = "一撮灰" age int = 3 ) 零值 **零值(zero value)**是变量声明后未显式赋值时自动获得的值。Go 没有"未初始化变量...
顶点、图元与屏幕映射
需要预先了解: 模型、视图、投影(MVP)变换 模型的表示 我们想要在电脑里创建一个几何体,就是要想办法去存储一个模型。首先,不免要考虑一个模型应当用什么数据表示。表示方式决定了存储、处理和绘制的方方面面。 几何表示分两大类: 隐式表示(implicit representation):不直接给出表面上的点,而是给出点满足的关系。比如单位球面 x2+y2+z2=1x^2 + y^2 + z^2 = 1x2+y2+z2=1,以及距离场、水平集、CSG 等 显式表示(explicit representation):直接给出点,或者给出从参数到三维点的映射。比如点云、贝塞尔曲面、多边形网格。 两类表示正好互补: 表示方式 典型例子 判断点在内外 找到表面上的点 隐式 代数曲面、距离场 容易,代入函数看符号 困难,需求解方程 显式 点云、贝塞尔曲面、网格 困难 容易,直接给出或按参数计算 我们在实时渲染中更多地会使用显式表示,尤其是网格。因为显式表示可以直接送进 GPU 渲染管线,而隐式表示通常需要先离散化成显式表示才能渲染。 下面介绍几种常见...
运算符与流程控制
需要预先了解: 变量、常量与基本类型 运算符与流程控制 Go 的流程控制关键字只有 if、for、switch、select(并发场景用),没有 while、没有 do...while。这套精简的设计带来了一些与直觉不同的细节:if 可以带初始化语句、switch 默认不穿透、++ 是语句而不是表达式。 运算符 算术运算符 运算符 含义 示例 结果 + 加法 1 + 2 3 - 减法/取负 5 - 3 2 * 乘法 2 * 3 6 / 除法 7 / 2 3 % 取余 7 % 2 1 整数除法会截断小数部分,取余的结果符号与被除数一致: fmt.Println(7/2, 7%2, -7/2, -7%2) 3 1 -3 -1 除数为常量 0 是编译错误,除数为变量 0 会在运行时 panic(integer divide by zero)。 位运算符 运算符 含义 示例 结果 & 按位与 5 & 3 1 | 按位或 5 | 3 7 ^ 按位异或 5 ^ 3 6 &^ 位清除 5 &a...
光栅化
需要预先学习: 顶点、图元与屏幕映射 光栅化 光栅化(rasterization)是把 图元 转换为 片元(fragment)的过程。 光栅化是渲染管线中从"几何"跨越到"图像"的一步:之前的模型变换、视图变换、投影变换处理的都是连续的几何数据,而屏幕是离散的像素网格,光栅化负责决定每个像素显示成什么颜色。 本笔记的光栅化只考虑三角形图元,不对其他图元(线段、点、曲面)做讨论。 那么,光栅化一个三角形的核心问题只有一个:哪些像素被这个三角形覆盖? 三角形设置 三角形设置(triangle setup)在硬件里由光栅器的固定功能单元完成,对每个三角形执行一次。我们会在此阶段提前算出后面三角形遍历阶段要反复用的全部常量,给后面遍历做预处理,避免每个像素重复算一遍。 输入是三个屏幕空间的顶点(已经过了 视口变换),尽量做到把能提前算的都提前算好,追求在逐像素阶段就只剩加法与比较。 有向面积的计算 我们将三角形顶点记为 P0=(x0,y0)P_0 = (x_0, y_0)P0...




