🎯 一、创建型模式(Creational Patterns)核心目的:处理对象的创建过程,封装创建逻辑,解耦对象的使用与创建。
模式名称
作用简述
Singleton(单例模式)
保证一个类只有一个实例,并提供全局访问点。
Factory Method(工厂方法)
定义一个创建对象的接口,由子类决定实例化哪一个类。
Abstract Factory(抽象工厂)
提供一个创建一系列相关或相互依赖对象的接口。
Builder(建造者模式)
将复杂对象的构建与表示分离,使同样的构建过程可生成不同表示。
Prototype(原型模式)
通过复制已有对象(原型)来创建新对象。
⚙️ 二、结构型模式(Structural Patterns)核心目的:处理类或对象之间的组合结构,注重“组合”对象来获得更强功能。
模式名称
作用简述
Adapter(适配器)
将一个类的接口转换成客户端期望的接口。
Bridge(桥接)
将抽象部分与实现部分分离,使它们可以独立变化。
Composite(组合)
将对象组合成树形结构,统一处理个体和组合对象。
...
设计模式笔记:原型模式 (Prototype Pattern)一、一句话概括通过复制一个已经存在的实例(原型)来创建新的对象,而无需关心创建的细节。简单说,就是 “别用 new 了,用 clone() 吧”。
二、为什么需要它 (Why - The Pain Point)在软件开发中,我们常常会遇到以下痛点:
创建成本高昂:一个对象的初始化过程可能非常复杂和耗时。例如,需要查询数据库、进行RPC调用、读取大文件、进行复杂的计算等。如果需要频繁创建这类对象,系统的性能会受到严重影响。
避免与具体类耦合:假设你正在开发一个框架,它需要能创建和处理用户提供的各种对象。你的框架代码不应该硬编码具体的用户类名(例如 new UserDefinedClass()),否则框架的通用性就无从谈起。
简化对象状态的复制:当需要一个与现有对象状态完全相同或大部分相同的新对象时,从头开始设置所有属性会非常繁琐。直接复制一个现有对象的状态会便捷得多。
核心问题:如何高效、灵活地创建复杂对象,同时又不让创建过程与系统的其他部分紧密耦合?
三、它是什么 (What - The Solution)原型模式提供了 ...
设计模式笔记:工厂方法模式 (Factory Method Pattern)一、一句话概括定义一个用于创建对象的接口(工厂方法),但将 “究竟创建哪种具体对象” 的决定权推迟到子类去实现。
二、为什么需要它 (Why - The Pain Point)想象一下,你正在开发一个应用,需要根据不同的场景创建不同的对象。比如一个文档处理程序,它可以创建和操作不同类型的文档(Word, PDF, Txt)。
最初的简单实现(糟糕的设计):
123456789101112131415161718class DocumentProcessor { public Document createDocument(String type) { Document doc = null; // 痛点:大量的 if-else 或 switch-case 语句 if ("word".equalsIgnoreCase(type)) { doc = new WordDocument(); ...
设计模式笔记:建造者模式 (Builder Pattern)一、一句话概括将一个复杂对象的构建过程与其最终表示相分离,使得同样的构建过程可以创建出不同配置的对象。通俗地说,就是 “把组装电脑的活儿交给专业的装机师傅,你只需要告诉他要什么配置就行了”。
二、为什么需要它 (Why - The Pain Point)想象一下,当你要创建一个具有很多属性的对象时,会遇到哪些困境:
**伸缩构造函数 (Telescoping Constructor Problem)**:为了应对不同的属性组合,你可能会创建一堆重载的构造函数。
123456// 痛点:构造函数爆炸,参数列表又长又乱public Pizza(String dough, String sauce) { ... }public Pizza(String dough, String sauce, String cheese) { ... }public Pizza(String dough, String sauce, String cheese, String topping1) { ...
1. 核心概念:什么是纹理?
不仅仅是图片:在 OpenGL 中,“纹理 (Texture)”是一个广义的概念。它是一个数据容器,可以存储一维、二维或三维的数据阵列。最常见的用途是存储 2D 图像的颜色信息,但也可以用来存储法线(法线贴图)、高度(高度图)、光照查找表等。
**纹理单元 (Texture Unit)**:GPU 内部有多个可以放置纹理的“插槽”,这些插槽被称为纹理单元。通常至少有16或32个(GL_TEXTURE0, GL_TEXTURE1, …)。你可以将不同的纹理放入不同的单元,然后在着色器中同时访问它们,实现多重纹理混合等效果。
**采样器 (Sampler)**:在 GLSL 着色器中,我们通过 sampler2D (或 sampler3D 等) 这样的特殊 uniform 变量来访问纹理。采样器知道去哪个纹理单元里读取数据。
2. C++ 封装:创建一个 Texture 类 (RAII)为了方便管理和复用,我们将所有与纹理相关的 OpenGL 操作封装在一个 Texture 类中。这个类遵循 RAII (Resource Acquisition Is In ...
着色器脚本整体功能这个脚本的总体目标是:接收一个带有纹理坐标的 2D 几何体(比如一个正方形),并将一个 2D 纹理(图片)“贴”到这个几何体上进行渲染。
第一部分:顶点着色器 (Vertex Shader)123456789101112#shader vertex#version 330 core layout(location = 0) in vec4 position; layout(location = 1) in vec2 texCoord; out vec2 v_TexCoord; void main() { gl_Position = position; v_TexCoord = texCoord;};
# "#shader vertex"
这是一个自定义的标识符(不是标准的 GLSL),用于在 C++ 代码中解析和分离顶点着色器与片段着色器的源码。
# version 330 core
含义: 声明此着色器代码遵循 OpenGL 3.3 版本的核心模式 (Core Profile) 规范。这是现代 ...
[!Question] 我注意到Cherno在indexBuffer和vertexBuffer的构造函数中中都自动调用了Bind(),但是vertexArray和后面的Shader类都没有……这么做是正确的吗,或者有什么思考?
你又提出了一个观察极其细致、直击设计核心的绝佳问题!这背后确实有非常深刻和重要的设计考量。
答案是:Cherno 的做法是完全正确且经过深思熟虑的。 在 VertexBuffer 和 IndexBuffer 的构造函数中自动 Bind(),而在 VertexArray 和 Shader 中不这么做,是基于这些对象在 OpenGL 渲染管线中扮演的不同角色和它们的典型使用模式。
让我们来分析这背后的“为什么”。
Group 1: VertexBuffer 和 IndexBuffer - “数据容器”这两个类的核心职责是:成为一个特定数据的容器,并负责将这些数据上传到 GPU。
为什么在构造函数中 Bind() 是合理的?
紧密的操作流程: 创建一个 VBO/IBO 的典型流程是:glGenBuffers -> glBindBuffer -&g ...
好的,这段代码是现代 OpenGL 中设置几何体数据最核心、最经典的部分。它展示了如何使用顶点缓冲对象 (VBO) 和索引缓冲对象 (IBO/EBO) 来高效地定义一个图形。
我们来一步步分解它的功能、目的以及每一行代码的作用。
核心目标:定义一个正方形这段代码的最终目标是告诉 GPU 如何绘制一个由两个三角形拼接而成的正方形。
没有索引缓冲的情况: 要画一个正方形(2个三角形),你需要定义6个顶点,因为右上角和左下角的顶点被两个三角形共享,需要重复定义。
12345678910float positions[] = { -0.5f, -0.5f, // 左下 0.5f, -0.5f, // 右下 0.5f, 0.5f, // 右上 -- 三角形1 0.5f, 0.5f, // 右上 (重复) -0.5f, 0.5f, // 左上 -0.5f, -0.5f // 左下 (重复)};// 需要 6 * 2 = 12 个 float
使用索引缓冲的情况 (本例): 我们可以只定义4个独一无二的顶点 ...
VAO (Vertex Array Object) 现代 OpenGL 中管理状态的核心利器
加上 glGenVertexArrays 和 glBindVertexArray 之后,整个代码的行为模式和意义发生了根本性的变化。它从一堆临时的、分散的设置,变成了一个可复用的、打包好的“渲染状态快照”。
让我们用一个绝佳的比喻来解释这个变化。
比喻:从“每次都手动搭乐高”到“使用设计图纸”想象一下,你要用乐高积木拼一个复杂的模型(比如一辆车)。
没有 VAO 的情况 (旧方法)没有 VAO,就好像你每次想展示这辆车时,都必须:
拿出“车轮”积木盒 (绑定 VBO)。
告诉你的助手:“从这个盒子里拿4个轮子。” (调用 glVertexAttribPointer for wheels)。
拿出“车身”积木盒 (绑定另一个 VBO)。
告诉助手:“从这个盒子里拿红色的车身零件。” (调用 glVertexAttribPointer for colors)。
拿出“装配说明书” (绑定 IBO)。
大喊一声:“开始组装!” (调用 glDrawElements)。
如果你有10辆不同的车要 ...
你观察得非常仔细,这个 { 和 } 构成的局部作用域(Local Scope)在这里起着一个至关重要的作用,它与 C++ 的一个核心特性——RAII (Resource Acquisition Is Initialization) 紧密相关,并且确实和你提到的栈分配 (Stack Allocation) 有关。
让我们来详细解释这个作用域的目的。
问题的核心:资源的生命周期管理在你的代码中,你创建了一些封装了 OpenGL 对象的 C++ 类,比如:
VertexBuffer vb(...)
IndexBuffer ib(...)
这些类很可能是按照 RAII 原则设计的(这是 Cherno 教程中的一个关键点)。
RAII 的核心思想是:
在对象的构造函数中获取资源(比如调用 glGenBuffers 创建一个 VBO)。
在对象的析构函数中释放资源(比如调用 glDeleteBuffers 删除这个 VBO)。
当一个对象是在栈 (Stack) 上创建时,它的生命周期就和它所在的作用域绑定了。一旦程序执行离开这个作用域(无论是正常结束还是因为异常跳出) ...