缓冲对象与顶点
缓冲对象 OpenGL 运行在 GPU 上,而 C++ 代码运行在 CPU 上。要让 GPU 处理图形数据,需要把数据从 CPU 的内存复制到 GPU 的显存中。缓冲对象(Buffer Object)就是 OpenGL 用来管理 GPU 端内存的机制——它本质上是在 GPU 上分配的一块存储区域,可以把数据复制进去,之后 GPU 就直接从中读取。 操作缓冲对象的一般流程是三步:生成(glGenBuffers)→ 绑定(glBindBuffer)→ 填充数据(glBufferData)。 生成缓冲对象(glGenBuffers) glGenBuffers 函数用于生成一个或多个缓冲对象的名称(ID),并将其存储在指定的变量中。 函数原型:glGenBuffers(GLsizei n, GLuint *buffers); n:要生成的缓冲对象的数量 buffers:指向一个无符号整数数组的指针,用于存储生成的缓冲对象名称 GLuint VBO; glGenBuffers(1, &VBO); // 生成一个顶点缓冲对象 GLuint VBOs[2];...
变量与常量
需要预先了解: 内存基础 变量 变量就是编程里用来存放数据的盒子。 ——豆包 变量有名字(变量名,是一个标识符),可以用于找到它 变量里能存储数据(数字、文字、真假值等) 变量里的数据可以修改 常见的变量类型 整型变量 修饰符signed和unsigend 默认为有符号(signed),表面变量可以表示正数、负数和0。 用unsigned修饰后,变量只能表示非负整数,即0和正数 常见整型变量空间占用: 数据类型 32位系统(x86) 64位系统(x64) int 4字节(32位) 8字节(64位) short/short int 2字节(16位) 2字节(16位) long/long int 4字节(32位) 8字节(64位) long long/long long int 8字节(64位) 8字节(64位) unsigned 类型 和对应的有符号类型相同 和对应的有符号类型相同 浮点型变量 float:单精度浮点型,占用4个字节(32位),大约能提供7位有效数字。 double:双精度浮点型,占用8个字节...
包与模块:代码组织
需要预先了解: 错误处理 包与模块:代码组织 Go 用**包(package)组织代码,用模块(module)**管理依赖。包对应目录,目录内的所有文件共享同一个包名;模块由 go.mod 定义,记录依赖的精确版本。这一篇把可见性规则、导入方式、初始化顺序和模块机制讲清楚。 包的概念 一个目录就是一个包,目录下所有 .go 文件的 package 声明必须一致: mathutil/ ├── add.go // package mathutil └── mul.go // package mathutil 包名不必与目录名相同,但惯例上保持一致。包名应该短小、全小写、不用下划线(mathutil 而不是 math_util)。 main 包是特殊的:它可以被编译成可执行文件,且必须包含 main 函数。其他包只能被导入。 同一个目录下不能同时存在两个包(_test.go 文件的 package foo_test 是例外,见 测试)。 可见性 标识符的可见性由首字母大小写决定,没有 pub...
着色器
需要预先学习: 着色与渲染管线 缓冲对象与顶点 GLSL 语言与着色器 着色器(shader)是运行在 GPU 上的小程序,它接管渲染管线中某些阶段的处理工作。在前一篇笔记中,你把顶点数据放进了 VBO,但 GPU 还不知道怎么处理这些数据——着色器就是告诉 GPU “该做什么” 的程序。 OpenGL 的着色器使用 GLSL(OpenGL Shading Language)编写,它是一种类 C 语言的着色器语言。一个着色器程序的入口点是 main 函数,和 C++ 一样。 数据传递 着色器之间通过特定的变量关键字来传递数据: in:从上一个阶段接收数据 out:向下一个阶段发送数据 uniform:从 CPU 端传入的全局变量,对所有顶点或片元都相同 数据类型 GLSL 有 C 语言风格的类型系统,并提供了面向图形的内置类型: 类别 类型 基础类型 int、float、bool、double 向量 vec2、vec3、vec4(float 向量)、ivec2~ivec4(int 向量)、bvec2~bvec4(bool 向量) ...
表达式与基础语句
需要预先学习: 变量与常量 基础表达式 表达式是由运算符和操作数组成的计算单元。最简单的表达式可以是一个变量或一个字面量(如 a、10),复杂的表达式则通过运算符将多个操作数组合起来(如 a + b * 2)。每个表达式在求值后都会产生一个结果值。 在 C++ 中,只要是能产生值的代码片段,都可以看作表达式。 算数表达式 算术表达式使用算术运算符对数值类型(如 int、float、double)的操作数进行计算。C++ 提供五种基本算术运算符: 运算符 含义 示例 + 加法 a + b - 减法 a - b * 乘法 a * b / 除法 a / b % 取模(取余) a % b int a = 10; int b = 3; int sum = a + b; // 13 int diff = a - b; // 7 int prod = a * b; // 30 int quot = a / b; // 3 int rem = a % b; // 1 溢出的问题 +、-、* 运算可能导致溢出—...
泛型
需要预先了解: 接口 包与模块:代码组织 泛型 Go 1.18 引入了泛型(generics):函数和类型可以带类型参数,在调用或使用时才确定具体类型。它解决的是"同一套逻辑要写很多遍,只有类型不同"的问题——比复制粘贴好维护,比 any + 类型断言更安全,也比接口更直接(不需要为每种类型实现方法)。 从重复代码出发 先看一个没有泛型的场景:为 int 和 float64 各写一遍求最小值的函数。 func MinInt(a, b int) int { if a < b { return a } return b } func MinFloat(a, b float64) float64 { if a < b { return a } return b } 两个函数除了类型以外完全相同。用泛型可以合成一个: func Min[T Ordered](a, b T) T { if a < b { ...
纹理
需要预先学习: 纹理映射 缓冲对象与顶点 着色器 纹理坐标 在 纹理映射 中,你已经知道纹理坐标(UV)是什么:每个顶点指定一个 [0, 1] 范围内的坐标,告诉 GPU 这个顶点对应纹理上的哪个位置。这一篇笔记讲 OpenGL 中从数据准备到最终显示的完整流程:在顶点数据里加上纹理坐标 → 加载图片生成纹理对象 → 配置采样方式 → 在着色器中采样。 纹理坐标系 OpenGL 的 2D 纹理坐标系有两条轴:水平轴 S、垂直轴 T(也叫 U、V)。坐标范围约定为 [0, 1],(0, 0) 对应纹理的左下角,(1, 1) 对应右上角: t ↑ (0,1) ──────── (1,1) | | | 纹理图片 | | | (0,0) ──────── (1,0) → s 坐标与纹理的分辨率无关:不管纹理是 512×512 还是 4096×4096,坐标都落在 [0, 1] 内。这也是纹理坐标的好处——换一张分辨率更高的纹理,模型上的贴图位置不用改。 ...
if 语句
需要预先学习: 表达式与基础语句 能作为条件的表达式 在 C++ 中,条件就是放在 if、while 等控制结构里用来决定程序走向的表达式。条件表达式的类型是 bool 类型,值只能是 true(真)或 false(假)。 bool flag = true; // flag 的值为 true if (flag) // flag 是 bool 类型,直接作为条件 { // 条件成立时执行 } 关系表达式 关系表达式使用关系运算符比较两个值的大小或相等关系,结果是 bool 类型。 运算符 含义 示例 结果 > 大于 a > b a 大于 b 则为 true < 小于 a < b a 小于 b 则为 true >= 大于等于 a >= b a 大于等于 b 则为 true <= 小于等于 a <= b a 小于等于 b 则为 true == 等于 a == b a 等于 b 则为 true != 不等于 ...
深度缓冲
需要预先学习: 光栅化 深度缓冲(Depth Buffer 或 Z-Buffer) 光栅化之后,屏幕上每个像素可能对应多个片元——多个三角形在投影后会互相重叠,同一个像素可能被多个三角形覆盖。最终该显示哪个片元,需要判断谁离摄像机最近。深度缓冲(depth buffer)就是为此设计的存储结构,也是深度测试(depth test)的基础。 帧缓冲区与深度缓冲 先分清两个容易混淆的缓冲。 帧缓冲区(frame buffer,这里特指颜色缓冲 color buffer)保存每个像素最终显示的颜色;深度缓冲(depth buffer,也称 Z-Buffer,因为存的是 z 坐标)保存每个像素当前最近的深度值。 缓冲 存储内容 每个像素存什么 用途 帧缓冲区(frame buffer) 颜色 一个颜色值(RGB) 最终显示 深度缓冲(depth buffer / z-buffer) 深度 一个深度值(z) 可见性判断 二者有两个重要的关系: 并列结构:帧缓冲与深度缓冲是两块并列的存储,尺寸与屏幕分辨率相同、逐像素一一对应——第 (x,y)(x, ...
goroutine 与并发初探
需要预先了解: 函数 包与模块:代码组织 goroutine 与并发初探 Go 的并发能力从语言层面就开始了:一个 go 关键字就能启动一个 goroutine。这篇先讲清楚"并发"与"并行"的区别、goroutine 的启动与开销,再引出 sync.WaitGroup、sync.Once 这两个基础同步工具,最后给出 GMP 调度模型的概览,为后面几篇铺路。 并发与并行 先区分两个概念: 并发(concurrency):多个任务在重叠的时间段内推进,宏观上"同时"发生,微观上可能是交替执行; 并行(parallelism):多个任务在同一个时刻真正同时执行,需要多核支持。 并发是一种程序结构,并行是一种执行状态。一个程序可以并发但不并行(比如只给一个 CPU 核心)。 几个系统层面的执行单元: 概念 说明 进程 拥有独立地址空间的执行单元,进程之间默认不共享内存 线程 进程内的执行单元,共享进程的地址空间,由操作系统调度 协程 用户态的轻量执行单元,由语言运行时或库调度...




