缓冲对象与顶点
缓冲对象 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];...
着色器
需要预先学习: 着色与渲染管线 缓冲对象与顶点 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 向量) ...
纹理
需要预先学习: 纹理映射 缓冲对象与顶点 着色器 纹理坐标 在 纹理映射 中,你已经知道纹理坐标(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] 内。这也是纹理坐标的好处——换一张分辨率更高的纹理,模型上的贴图位置不用改。 ...
帧缓冲
需要预先学习: 缓冲对象与顶点 深度缓冲 帧缓冲(Framebuffer) 着色与渲染管线 的片元处理结束时,通过所有测试的片元会被写入帧缓冲(framebuffer)——渲染管线的最终目的地。默认情况下这个目的地是屏幕:窗口系统在创建窗口时自动配置好一块帧缓冲,渲染结果直接显示。帧缓冲对象(FBO,Framebuffer Object)允许你把渲染目标从屏幕换成自定义的纹理或缓冲,这是后处理、阴影贴图、环境光预计算等一切"渲染到纹理"技术的基础。这篇笔记介绍 FBO 的三种附件(颜色、深度、模板),并重点讲清片元何时写入附件。 默认帧缓冲 深度缓冲 中讲过,帧缓冲不是一个单独的缓冲,而是多块逐像素并列的存储的集合: 缓冲 存储内容 每个像素存什么 颜色缓冲(color buffer) 片元最终颜色 一个 RGBA 颜色值 深度缓冲(depth buffer) 当前最近的深度 一个深度值 模板缓冲(stencil buffer) 模板测试用的标记 一个整数 默认帧缓冲由窗口系统(Win32、X...
网格、材质与模型
需要预先学习: 几何体 网格(Mesh) 在 几何体 中你已经见过网格的定义:一组顶点,加上描述顶点如何连成三角形的索引。缓冲对象与顶点 里又把这些数据装进了 VertexBuffer 和 IndexBuffer,并用 VertexBufferLayout 描述顶点格式。这一篇把三样东西组合成 Mesh 类——一个构造即上传、析构即释放的几何体对象,并顺着它引出材质与模型两个更高层的封装。 网格类的设计 Mesh 不引入新的 OpenGL 概念,它是一层纯粹的组织:把"绘制一个几何体所需的三样数据"收进同一个类。 成员 存放的内容 对应的概念 VertexBuffer 一串 float 顶点数据(位置、UV、法线……) IndexBuffer 一串 uint32_t 顶点如何组成三角形 VertexBufferLayout 属性列表 每个顶点的数据怎么切分、解读 三者在绘制时总是同时出现:单有 VertexBuffer,GPU 眼里只是一串没有含义的数字,要靠 layout 才知道怎么切成一个个顶点;...
GLSL 语法
需要预先了解: 着色器 程序结构 在着色器中你已经见过一段完整的 GLSL 代码。这篇笔记把它的语法规则系统梳理一遍。一个 GLSL 程序由版本声明、全局声明(变量、结构体、函数)和入口函数 main 组成: #version 330 core layout (location = 0) in vec3 aPos; // 全局变量声明 out vec4 FragColor; // 输出变量 void main() // 入口函数 { gl_Position = vec4(aPos, 1.0); } 几个语法层面的硬性规则: #version 声明必须出现在文件第一行(前面只允许空白和注释),且要先于任何其他声明 入口函数是 main,返回 void,不接受参数 每条语句以分号结尾,注释风格与 C 相同(// 与 /* */) 注:#version 330 core 中的 core 表示核心模式(core profile)。...
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 用反...
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); ...
CSM级联阴影
需要预先学习: 阴影 ShadowMap与PCF、PCSS CSM 级联阴影 ShadowMap与PCF、PCSS 中实现了单张阴影贴图:一张正交投影的深度图罩住整个场景。它有一个根本性的问题——分辨率分配与视觉需求不匹配。CSM(Cascaded Shadow Maps,级联阴影贴图)把视锥按深度切成多段,每段单独渲染一张阴影贴图,让近处获得高分辨率、远处用低分辨率,是实时渲染中大范围场景阴影的标准方案。 分辨率困境 透视投影下,屏幕空间里"近大远小":近处的物体占满屏幕的大片像素,远处的物体挤在屏幕的一角。而阴影贴图是均匀的——正交投影把整个场景等分进固定数量的纹素。于是: 近处:屏幕上一大片像素对应的场景区域,在阴影贴图里只占几个纹素,阴影边缘糊成一片块状锯齿; 远处:阴影贴图的大半纹素被远方的场景占用,但它们在屏幕上只占几行像素,分辨率被浪费。 提高贴图分辨率只能缓解,不能解决:要让近处阴影清晰,需要的分辨率高到远处完全用不完;照顾远处,近处又回到锯齿。正确思路是按需分配——把阴影贴图拆成多张,近处的贴图只覆盖视锥近...
法线贴图与TBN矩阵
需要预先学习: 纹理 纹理映射 Phong模型与Blinn-Phong模型 凹凸感的来源 Phong模型与Blinn-Phong模型 的光照计算只依赖一个输入描述表面形状:法线(normal)。一个表面看起来是平的还是坑坑洼洼的,本质上取决于法线在表面上如何变化——砖墙缝隙处法线突然翻转,凹陷处法线朝向内倾,人眼把这些明暗变化解读为"凹凸"。 要让一面砖墙真的有凹凸,最直接的办法是建出缝隙的几何:每个砖块、每条沟壑都用三角形描述。代价是三角形数量爆炸——一面墙几十万三角形,剔除总结 与 前向渲染与延迟渲染 里讨论的所有开销跟着翻倍。而仔细想想,这些三角形只是提供了"法线在像素间变化"这一件事。 法线贴图(normal mapping)的思路:几何保持平滑(低多边形),把逐像素的法线存进一张纹理,着色时采样它代替插值法线。凹凸感来自法线变化,而不是几何变化——一张图就能骗过光照计算。 方案 三角形数量 细节层级 代价 几何建模凹凸 极多 顶点级 顶点处理、带宽、内存 顶点...



