几何体
注:本文内容已全部移动到 顶点、图元与屏幕映射 的「模型的表示」与「网格几何体的操作」两节,此文件待删除。
context
需要预先了解: channel sync 包与原子操作 context 一个请求往往要派生多个 goroutine:查数据库、调下游服务、写日志。如果其中一步超时或客户端断开,其他步骤需要尽快停下。context 就是用来统一传递取消信号、截止时间和请求范围内的元数据的标准机制。 需求引入 假设一个函数要并发做三件事,任何一件失败或超时就整体放弃。如果每个 goroutine 各自用 done channel,代码会变成一堆手写的通知逻辑: done1 := make(chan struct{}) done2 := make(chan struct{}) // 谁来关闭?超时怎么办?取消要不要级联? context 把这些问题标准化:它是一棵可级联取消的树,父节点取消时所有子节点都会收到通知。 Context 接口 type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{...
Phong模型与Blinn-Phong模型
需要预先学习: 着色与渲染管线 着色器 Phong 反射模型 着色与渲染管线 中给出了 Blinn-Phong 反射模型的数学形式,这篇笔记把它的前身——Phong 反射模型(Phong reflection model)——与 Blinn-Phong 放在一起对比,并分别给出对应的 GLSL 实现。 两个模型都是局部光照模型(local illumination model):每个片元独立计算颜色,只依赖光源、表面材质与观察方向,不考虑阴影与光线的多次反弹。它们共享同一个框架,把片元的输出颜色分解为三个分量之和: L=La+Ld+LsL = L_a + L_d + L_s L=La+Ld+Ls 分量 名称 模拟的现象 是否依赖观察方向 LaL_aLa 环境光(ambient) 光线多次反弹形成的间接光照 否 LdL_dLd 漫反射(diffuse) 粗糙表面向各个方向均匀散射 否 LsL_sLs 高光(specular) 光滑表面向镜像方向集中反射 是 两者的区别只在第三项——高光的计算方式:Phong 用反...
指针常量与常量指针
需要预先了解: 指针 变量与常量 常量指针:指向常量的指针。 1. 该指针指向常量,被其指向的内容不可修改。 int a = 10; const int *ptr = &a; *ptr = 20; // 错误操作,不可以修改常量指针指向的内容 即使常量指针指向的是变量,也不可以通过常量指针对其进行修改。 2. 该指针本身为变量,可以修改指针的指向。 int a = 10, b = 20; const int *ptr = &a; ptr = &b; // 正确,允许修改指针指向 指针常量:指针本身是常量,指向不可改变。 1. 该指针指向的是变量,可以修改指针指向的内容。 int a = 10; int *const ptr = &a; *ptr = 20; // 正确,允许修改指针常量所指向的内容 2. 该指针本身为常量,不可以修改指针的指向。 int a = 10, b = 20; int * const ptr = &a; ptr =...
阴影
需要预先学习: 深度缓冲 ShadowMapping(阴影贴图) 着色与渲染管线 里提过,着色是局部的:每个点只根据光源与自身材质计算颜色,完全不知道其他物体的存在。所以 Blinn-Phong 管线本身画不出阴影——被遮挡的点和直接被照亮的点会得到相同的着色结果。ShadowMapping(阴影贴图)为光栅化管线补上这块能力,出发点是一个关键观察: 一个点不在阴影中,当且仅当光源能直接看到这个点。 "从某个视点看,谁能被看见"正是 深度缓冲 解决的问题。ShadowMapping 的思路,就是把深度测试搬到光源的位置再做一遍。 算法流程 ShadowMapping 分两遍(two-pass)渲染。 Pass 1:从光源渲染场景,只记深度。把光源当作一台摄像机——平行光配正交投影,点光源配透视投影(见 视图(摄像机)与投影)——渲染整个场景,但不存颜色,只把深度缓冲存下来。这张深度图就是阴影贴图(shadow map):它记录了从光源看过去,每个方向上最近的表面有多远。 **Pass 2:从摄像机正常渲染场景。**着...
并发设计模式与陷阱总结
需要预先了解: goroutine 与并发初探 channel sync 包与原子操作 context 并发设计模式与陷阱总结 前四篇讲了 goroutine、channel、同步原语和 context 的用法,这一篇把它们组织成可复用的模式,并把并发编程里最容易踩的坑集中列出来。 pipeline **pipeline(流水线)**把任务拆成若干阶段,每个阶段的输入输出都是 channel,数据像流水一样流过: // 生成器:产生数据 func gen(nums ...int) <-chan int { out := make(chan int) go func() { defer close(out) for _, n := range nums { out <- n } }() return out } // 阶段:处理数据 func square(in <-chan int) <-chan int { out ...
ShadowMap与PCF、PCSS
需要预先学习: 阴影 ShadowMap 阴影 中讲过 ShadowMapping 的原理:把深度测试搬到光源位置再做一遍,用"光源视角记深度 + 摄像机视角比深度"的两遍框架判断片元是否被遮挡。这篇笔记把它落地成 OpenGL 代码,并在这个基础上实现 PCF 与 PCSS。 基本原理回顾 ShadowMapping 分两遍渲染: Pass 1:从光源渲染场景,只记深度。 平行光配正交投影,把场景画进一张深度贴图(depth map)——只写深度缓冲,不写颜色。深度贴图记录了从光源看过去每个方向上最近的表面距离。 Pass 2:从摄像机正常渲染场景。 着色时把片元变换到光源的观察空间,投影得到它对应的贴图坐标与深度,与贴图中记录的最近深度比较:片元更远,说明它前面有别的表面挡光,在阴影中。 // 伪代码:Pass 2 中的阴影判断 float d_frag = to_light_view(fragment.pos).z; // 片元在光源视角下的深度 float d_map = shadow_map.sample(uv); ...
引用与左右值
引用 引用(Reference) 是一个已存在变量的别名,它提供了一种更方便的方式来间接访问变量,语法为类型 &别名 = 变量名;: int num = 10; int& ref = num; // ref 是变量 num 的别名 引用的注意事项 1. 引用必须在定义的时候初始化。 int& ref; // 错误操作 2. 引用不能再绑定其它变量。 int num1 = 10, num2 = 20; int& ref = num1; ref = num2; // 这个操作是把 num2 的值赋给 num1,而不是让ref成为num2的引用 常引用 常引用(Const Reference) 是指向常量的引用,使用const关键字修饰,语法为const 类型 &常引用名 = 变量名/常量名。 int num = 10; const int& const_ref = num; 即使常引用指向的是变量,也不允许通过引用对其进行修改。 左值与右值 左值 左值(lvalue):具有持久内存地...
光线追踪
需要预先学习: 光栅化 光线追踪 光栅化 处理的是"每个三角形覆盖哪些像素",从几何走向图像;着色与渲染管线 里的着色是局部的——每个点只依赖光源与自身材质,不知道其他物体的存在。于是阴影要靠 ShadowMapping 单独补救,镜面反射要靠额外的 pass 画,多次反弹的间接光只能用一个常数环境光糊弄过去。 光线追踪(ray tracing)换一个方向思考:既然光从光源出发,经过物体的反射、折射,最终进入人眼,那么从摄像机出发,沿像素方向把这条路径倒着走一遍,同样能算出像素的颜色。它模拟的是光的传播本身,全局光照的效果(阴影、反射、折射、多次反弹)自然地包含在流程里,代价是计算量大得多。本篇以经典的 Whitted 风格光线追踪(Whitted-style ray tracing)为主线,介绍光线与物体的求交、递归追踪与加速结构。 光线的定义 光线的数学表示 一条光线(ray)由一个起点和一个方向确定,写成参数形式: P(t)=O+tD,t≥0\mathbf{P}(t) = \mathbf{O} + t\mathbf{D}...
内存分配与逃逸分析
需要预先了解: 数组与切片 结构体与指针 方法 内存分配与逃逸分析 一个变量分配在栈上还是堆上,由编译器通过**逃逸分析(escape analysis)**决定。这个决定直接影响性能:栈分配只是移动一下栈指针,几乎零成本;堆分配要走分配器、还要被垃圾回收扫描。这一篇讲清楚逃逸分析怎么看、哪些写法会让变量逃逸,以及内存对齐和分配大小分级这两个容易忽略的细节。 栈与堆 维度 栈 堆 分配成本 极低(移动栈指针) 较高(分配器 + 可能的锁) 回收方式 函数返回时自动释放 由垃圾回收器回收 生命周期 与函数调用绑定 由可达性决定 访问速度 更快(缓存友好) 稍慢 Go 的 goroutine 栈是动态增长的:初始约 8KB,不够时自动扩容(最大到 1GB,64 位平台)。栈上变量的地址在扩容时会被修正,所以程序里取到的指针在逻辑上始终有效。 Go 的分配器 Go 运行时自带内存分配器,按对象大小走不同的路径: 小对象(≤ 32KB): mcache(每个 P 私有,无锁) | mcent...




