游戏编程模式-享元模式(Flyweight) - 指南

news/2025/11/9 13:28:52/文章来源:https://www.cnblogs.com/yangykaifa/p/19204174

享元模式(Flyweight)

迷雾散去,显露雄伟的古老森林;阳光洒落,尽显各式的繁枝绿叶。

这是我们游戏开发者梦想的超凡场景,这样的场景通常由一个模式支撑着,它的名字低调至极:享元模式。

森林

我可以用几句话描述一片森林,但是在游戏中实现却是另外一回事。当你看到一整片树林填满屏幕时,图像编程人员所知道的是他们必须每1/60秒就要将数百万个面(多边形)发送给GPU。

我们所说的成千上万的树,一颗树就要数千个面(多边形)去描述。即使有足够多的内存存储这片森林,为了渲染它,数据也必须从CPU传送到GPU,这会造成CPU到GPU的带宽总线紧张(受限)。

那么描述一棵树大概需要哪些元素呢?

如果在代码中描述一棵树,那么大概会是如下所示:

class Tree
{
private:Mesh mesh_;Texture bark_;Texture leaves_;Vector position_;double height_;double thickness_;Color barkTint_;Color leafTint_;
};

从上代码可知,一个Tree对象含有很多数据,其中的网格和纹理数据量尤其大。在游戏更新的一帧时间内是不可能把一片森林的树的对象全部发送给GPU的。幸运的是享元模式可以解决这个问题。

关键就是即使森林中有成千的树,这些树是相似的。树使用了相同的网格和纹理。即不同实例化后的Tree对象中mesh_/bark_/leaves_是一样的。因此,我们可以把这些共享的元素剥离出来,形成下述的分离的两个类:

// 共享的类
class TreeModel
{
private:Mesh mesh_;Texture bark_;Texture leaves_;
};
// 细分的类
class Tree
{
private:TreeModel* model_; // 指向共享的对象Vector position_;double height_;double thickness_;Color barkTint_;Color leafTint_;
};

地形

上面提到了森林中的树木,那么游戏中必然涉及到了树木所位于的地形。为了加深对这个模式的理解,我们继续讨论游戏中的地形问题。

游戏中的地形有各式各样:草地、沙地、丘陵、河流等。不同地形在游戏中可能有以下主要的属性:

  • 人物角色在该地的移速
  • 人物是否可以行走的标识
  • 用于渲染的纹理

那么我们自然可以想到如何优雅地表示地形:

class Terrain
{
public:Terrain(int movementCost,bool isAccess,Texture texture): movementCost_(movementCost),isAccess_(isAccess),texture_(texture){}int getMovementCost() const { return movementCost_; }bool isAccess() const { return isAccess_; }const Texture& getTexture() const { return texture_; }
private:int movementCost_;bool isAccess_;Texture texture_;
};

自然地,那么整个游戏世界上的地形该如何表达呢?最直接简单的如下所示:

class World
{void setTerrain(int x, int y, Terrain dst) {tiles_[x][y] = dst;}
private:Terrain tiles_[WIDTH][HEIGHT];// Other stuff...
};

那么上述的地形如何使用享元模式呢?请看下述的例子:

World wd;
wd.setTerrain(0, 0, Terrain(1, true, GRASS_TEXTURE)); // 草地
wd.setTerrain(0, 1, Terrain(1, true, GRASS_TEXTURE)); // 草地
wd.setTerrain(1, 0, Terrain(3, true, HILL_TEXTURE)); // 丘陵
wd.setTerrain(1, 1, Terrain(3, true, HILL_TEXTURE)); // 丘陵
wd.setTerrain(2, 0, Terrain(1, false, RIVER_TEXTURE)); // 河流
wd.setTerrain(2, 1, Terrain(1, false, RIVER_TEXTURE)); // 河流

这个例子中,[0,0]和[0,1]位置上的都是使用了相同的草地地形,Terrain实例元素完全一致,跟外部的位置因素没有任何关系,因此这个Terrain元素可以分离出来的元素。World实现变为如下所示:

class World
{void setTerrain(int x, int y, Terrain* dst) {tiles_[x][y] = dst;}
private:Terrain* tiles_[WIDTH][HEIGHT];// Other stuff...
};

最终的地形赋值如下所示:

World wd;
Terrain grassTerrain = Terrain(1, true, GRASS_TEXTURE);
Terrain hillTerrain = Terrain(3, true, HILL_TEXTURE);
Terrain riverTerrain = Terrain(1, false, RIVER_TEXTURE);
wd.setTerrain(0, 0, &grassTerrain); // 草地
wd.setTerrain(0, 1, &grassTerrain); // 草地
wd.setTerrain(1, 0, &hillTerrain); // 丘陵
wd.setTerrain(1, 1, &hillTerrain); // 丘陵
wd.setTerrain(2, 0, &riverTerrain); // 河流
wd.setTerrain(2, 1, &riverTerrain); // 河流

享元模式(关键概念)

享元模式(Flyweight Pattern)是一种结构型设计模式,用于通过共享尽可能多的对象,以减少内存占用和提高性能。
它将对象的状态分为内部状态外部状态,从而实现对可共享部分的复用。

概念说明示例
Flyweight(享元对象)表示可以共享的对象,封装内部状态共享的网格、纹理、地形数据
Intrinsic State(内部状态)对象中可被多个实例共享的部分,不随环境变化。同上
Extrinsic State(外部状态)每次使用享元时由外部传入的部分,不可共享。树的位置、颜色、朝向、地形位置
Flyweight Factory(享元工厂)创建并管理享元对象的共享池,确保重复使用已有实例。MeshManager、MaterialCache
Client(客户端)使用享元对象的代码部分,负责维护外部状态。场景渲染器、Entity系统

享元模型在渲染中的作用(OpenGL实践)

上述的两个例子用了享元模式共享了很多数据,目前我们只看到了能够减少内存的作用,那么再来看下该模式是如何提升渲染过程的性能的?

我们以OpenGL为例,OpenGL天然支持享元模式。很多时候渲染的时候可以用共享的顶点数据,在屏幕上进行不同位置的渲染。如下所示

#include 
#include 
#include 
#include 
#include 
#include 
// 顶点着色器
const char* vertexShaderSrc = R"(
#version 330 core
layout(location = 0) in vec2 aPos;     // 顶点坐标(共享)
layout(location = 1) in vec2 iOffset;  // 实例位置
layout(location = 2) in float iScale;  // 实例缩放
layout(location = 3) in vec3 iColor;   // 实例颜色
out vec3 vColor;
void main()
{vec2 pos = aPos * iScale + iOffset;gl_Position = vec4(pos, 0.0, 1.0);vColor = iColor;
}
)";
// 片段着色器
const char* fragmentShaderSrc = R"(
#version 330 core
in vec3 vColor;
out vec4 FragColor;
void main()
{FragColor = vec4(vColor, 1.0);
}
)";
// Shader编译工具函数
GLuint createShader(GLenum type, const char* src)
{GLuint shader = glCreateShader(type);glShaderSource(shader, 1, &src, nullptr);glCompileShader(shader);GLint success;glGetShaderiv(shader, GL_COMPILE_STATUS, &success);if (!success){char log[512];glGetShaderInfoLog(shader, 512, nullptr, log);std::cerr << "Shader compile error:\n" << log << std::endl;}return shader;
}
int main()
{// 初始化GLFWif (!glfwInit()) return -1;GLFWwindow* window = glfwCreateWindow(800, 600, "Two-leaf Grass (Flyweight)", nullptr, nullptr);if (!window) { glfwTerminate(); return -1; }glfwMakeContextCurrent(window);// 初始化GLEWglewExperimental = GL_TRUE;if (glewInit() != GLEW_OK){std::cerr << "Failed to init GLEW\n";return -1;}// -------------------------------// 共享几何(每棵草由两片叶子组成)// -------------------------------float vertices[] = {// 第一片叶子(往左)-0.02f, 0.0f,0.00f, 0.1f,0.02f, 0.0f,// 第二片叶子(往右)0.00f, 0.0f,0.02f, 0.1f,0.04f, 0.0f};GLuint vao, vbo;glGenVertexArrays(1, &vao);glGenBuffers(1, &vbo);glBindVertexArray(vao);glBindBuffer(GL_ARRAY_BUFFER, vbo);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);// -------------------------------// 实例数据(位置 + 缩放 + 颜色)// -------------------------------const int NUM = 150;std::vector instanceData;for (int i = 0; i < NUM; ++i){float x = ((rand() % 200) / 100.0f - 1.0f); // [-1, 1]float y = ((rand() % 100) / 100.0f - 1.0f); // [-1, 0]float scale = 0.4f * ((rand() % 100) / 100.0f + 0.5f);float r = 0.1f + (rand() % 30) / 255.0f;float g = 0.6f + (rand() % 80) / 255.0f;float b = 0.1f + (rand() % 30) / 255.0f;instanceData.insert(instanceData.end(), { x, y, scale, r, g, b });}GLuint instanceVBO;glGenBuffers(1, &instanceVBO);glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);glBufferData(GL_ARRAY_BUFFER, instanceData.size() * sizeof(float), instanceData.data(), GL_STATIC_DRAW);// 实例属性glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);glEnableVertexAttribArray(1);glVertexAttribDivisor(1, 1);glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(2 * sizeof(float)));glEnableVertexAttribArray(2);glVertexAttribDivisor(2, 1);glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));glEnableVertexAttribArray(3);glVertexAttribDivisor(3, 1);// 着色器程序GLuint vs = createShader(GL_VERTEX_SHADER, vertexShaderSrc);GLuint fs = createShader(GL_FRAGMENT_SHADER, fragmentShaderSrc);GLuint shader = glCreateProgram();glAttachShader(shader, vs);glAttachShader(shader, fs);glLinkProgram(shader);glUseProgram(shader);glClearColor(0.3f, 0.6f, 0.9f, 1.0f);// -------------------------------// 渲染循环// -------------------------------while (!glfwWindowShouldClose(window)){glClear(GL_COLOR_BUFFER_BIT);glUseProgram(shader);glBindVertexArray(vao);glDrawArraysInstanced(GL_TRIANGLES, 0, 6, NUM);glfwSwapBuffers(window);glfwPollEvents();}// 清理glDeleteVertexArrays(1, &vao);glDeleteBuffers(1, &vbo);glDeleteBuffers(1, &instanceVBO);glfwTerminate();return 0;
}

解析代码:

  • vao: 使用了一份共同的vertices,代表叶子几何形状(两个三角形)
  • instanceVBO: 表示每个实例的数据(位置 + 缩放 + 颜色)
  • glDrawArraysInstanced:执行对实例的绘制

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/960470.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入解析:css、dom 性能优化方向

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

002 vue3-admin项目的目录及文件说明之package.json文件

基本概念 package.json 是 Node.js 项目的核心配置文件,包含了项目的元数据、依赖信息、脚本命令等关键配置。它是 npm/yarn/pnpm 等包管理器的基础。文件结构示例{"name": "vue3-admin","p…

2025年比较好的双缓冲三节轨用户口碑最好的厂家榜

2025年双缓冲三节轨用户口碑最好的厂家排行榜行业背景与市场趋势随着全屋定制家居市场的持续升温,作为核心五金配件的滑轨产品也迎来了快速发展期。据中国五金制品协会最新数据显示,2024年中国滑轨市场规模已达到186…

2025年知名的中空板厂家推荐及选购指南

2025年知名的中空板厂家推荐及选购指南行业背景与市场趋势中空板作为一种轻质、高强度、环保的新型包装材料,近年来在光伏、新能源、电子、物流等领域应用日益广泛。根据中国包装联合会最新数据显示,2024年中国中空板…

[ docker del imags containers ]

要彻底删除所有容器和所有镜像,可以按照以下步骤操作(操作前请确认数据已备份,此操作不可逆): 步骤1:停止并删除所有容器 首先确保所有容器都已停止,然后删除所有容器: # 停止所有正在运行的容器 docker stop …

2025年评价高的冷库提升机TOP品牌厂家排行榜

2025年评价高的冷库提升机TOP品牌厂家排行榜冷库提升机行业背景与市场趋势随着冷链物流行业的快速发展,冷库提升机作为冷链仓储系统中的核心设备,其市场需求持续增长。据中国冷链物流联盟最新数据显示,2024年我国冷…

英语_阅读_Comic books_待读

Comic books are an unusual kind of storybook. 漫画书是一种不同寻常的故事书。 They tell a story using only pictures and speech bubbles or captions. 它们只用图片和对话气泡或文字说明来讲述故事。 The chara…

Flask的核心知识点如下

1. 简介与特点微框架: 核心简单,高度可扩展,不强制使用特定工具或库。优点:学习曲线平缓: 易于上手,适合小型项目和快速开发。 灵活性高: 开发者可以自由选择组件和技术栈。 社区活跃: 拥有丰富的第三方扩展和…

2025年质量好的发热管缩管机厂家选购指南与推荐

2025年质量好的发热管缩管机厂家选购指南与推荐行业背景与市场趋势发热管缩管机作为电热设备制造领域的关键设备,近年来随着家电、汽车、工业设备等行业的快速发展,市场需求持续增长。据中国电器工业协会2024年数据显…

2025年热门的防尘式工业型测力称重变送器厂家最新推荐权威榜

2025年热门的防尘式工业型测力称重变送器厂家最新推荐权威榜行业背景与市场趋势随着工业4.0的深入推进和智能制造需求的持续增长,防尘式工业型测力称重变送器作为工业自动化领域的关键组件,其市场需求呈现稳定上升态…

2025年评价高的MC减速机厂家最新推荐排行榜

2025年评价高的MC减速机厂家最新推荐排行榜行业背景与市场趋势减速机作为工业传动系统的核心部件,在机械制造、自动化生产线、新能源装备等领域扮演着不可或缺的角色。根据中国机械工业联合会最新发布的《2024-2025年…

C++网络编程(十)epoll模型与select的区别 - 实践

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025年口碑好的压榨机设备行业内知名厂家排行榜

2025年口碑好的压榨机设备行业内知名厂家排行榜行业背景与市场趋势压榨机设备作为固液分离领域的核心装备,近年来随着环保政策趋严和资源回收需求增长,市场规模持续扩大。据中国环保产业协会2024年数据显示,国内压榨…

2025年质量好的圆管犁厂家最新权威推荐排行榜

2025年质量好的圆管犁厂家最新权威推荐排行榜行业背景与市场趋势随着全球农业机械化进程的加速推进,圆管犁作为拖拉机配套农机具的重要组成部分,市场需求持续增长。据《2024-2025全球农业机械市场分析报告》显示,20…

2025年口碑好的消防转子泵实力厂家TOP推荐榜

2025年口碑好的消防转子泵实力厂家TOP推荐榜行业背景与市场趋势消防转子泵作为消防系统的核心设备,其性能直接关系到火灾救援的效率和安全性。根据中国消防协会最新发布的《2024-2025中国消防设备行业发展报告》,202…

2025年比较好的行星减速器厂家选购指南与推荐

2025年比较好的行星减速器厂家选购指南与推荐行业背景与市场趋势行星减速器作为工业自动化领域的核心传动部件,近年来随着智能制造和工业4.0的快速发展,市场需求持续增长。据《2024-2029年中国行星减速器行业市场调研…

2025年评价高的耐硫酸涂层TOP实力厂家推荐榜

2025年评价高的耐硫酸涂层TOP实力厂家推荐榜行业背景与市场趋势随着化工、冶金、电力等工业领域的快速发展,耐硫酸涂层的市场需求呈现稳定增长态势。根据中国防腐行业协会最新发布的《2024-2025年中国工业防腐涂层市场…

2025年质量好的密闭式压滤机高评价厂家推荐榜

2025年质量好的密闭式压滤机高评价厂家推荐榜行业背景与市场趋势随着环保法规日益严格和工业废水处理需求持续增长,密闭式压滤机作为固液分离的关键设备,在2025年迎来了新一轮发展机遇。根据中国环保产业协会最新发布…