游戏人生
首页
(current)
GameDevTools
登陆
|
注册
个人中心
注销
游戏引擎 浅入浅出
Introduction
Introduction
前言
前言
1. 游戏引擎框架介绍
1. 游戏引擎框架介绍
1.1 Unity的组成
1.2 游戏引擎组成
2. Opengl开发环境搭建
2. Opengl开发环境搭建
2.1 Opengl到底是什么
2.2 搭建Opengl开发环境
2.3 使用VisualStudio开发
3. 绘制多边形
3. 绘制多边形
3.1 画个三角形
3.2 画个正方形
3.3 画个立方体
4. 着色器
4. 着色器
4.1 Unity Shader和OpenGL Shader
4.2 顶点着色器
4.3 片段着色器
5. 绘制贴图
5. 绘制贴图
5.1 颜色和贴图
5.2 贴图文件介绍
5.3 CPU与GPU的通信方式
5.4 使用stb_image解析图片
5.5 绘制带贴图的立方体盒子
5.6 压缩纹理
5.7 图片压缩工具
5.8 使用压缩纹理
5.9 DXT压缩纹理扩展
6. 索引与缓冲区对象
6. 索引与缓冲区对象
6.1 顶点索引
6.2 缓冲区对象
6.3 OpenGL Core Profile
6.4 顶点数组对象
7. 绘制Mesh和材质
7. 绘制Mesh和材质
7.1 导出Mesh文件
7.2 使用Mesh文件
7.3 Shader文件创建与使用
7.4 创建材质
7.5 使用材质
7.6 MeshRenderer
8. 绘制静态模型
8. 绘制静态模型
8.1 Blender安装与配置
8.2 Blender制作模型
8.3 Blender Python设置开发环境
8.4 Blender Python创建物体
8.5 Blender Python导出顶点数据
8.6 加载导出的Mesh
9. 基于组件开发
9. 基于组件开发
9.1 基于RTTR实现反射
9.2 实现GameObject-Component
10. 相机
10. 相机
10.1 最简单的相机
10.2 多相机渲染
10.3 相机排序
10.4 CullingMask
11. 控制系统
11. 控制系统
11.1 键盘控制
11.2 鼠标控制
12. 拆分引擎和项目
12. 拆分引擎和项目
13. 绘制文字
13. 绘制文字
13.1 TrueType简介
13.2 绘制单个字符
13.3 绘制多个文字
13.4 彩色字
14. GUI
14. GUI
14.1 正交相机
14.2 UIImage
14.3 UIMask
14.4 UIText
14.5 UIButton
15. 播放音效
15. 播放音效
15.1 播放2D音效
15.2 播放3D音效
15.3 使用FMOD Studio音频引擎
16. Profiler
16. Profiler
16.1 初识easy_profiler
16.2 集成easy_profiler
17. 嵌入Lua
17. 嵌入Lua
17.1 Sol2与C++交互
17.2 更加友好的Lua框架设计
17.3 引擎集成sol2
17.4 调试Lua
18. 骨骼动画
18. 骨骼动画
18.1 Blender制作骨骼动画
18.2 Blender导出骨骼动画
18.3 解析骨骼动画
18.4 矩阵的主序
19. 骨骼蒙皮动画
19. 骨骼蒙皮动画
19.1 骨骼蒙皮动画实现
19.2 骨骼权重
19.3 Blender蒙皮刷权重
19.4 Blender导出蒙皮权重
19.5 加载权重文件
20. 解析FBX文件
20. 解析FBX文件
20.1 导出Mesh
20.2 导出骨骼动画
20.3 导出权重
20.4 渲染骨骼蒙皮动画
21. 多线程渲染
21. 多线程渲染
21.1 GLFW多线程渲染
21.2 基于任务队列的多线程渲染
21.3 完全异步的多线程模型
21.4 引擎支持多线程渲染
22. Physx物理引擎
22. Physx物理引擎
22.1 Physx实例-小球掉落
22.2 物理材质
22.3 碰撞检测
22.4 连续碰撞检测
22.5 场景查询
22.6 引擎集成Physx
23. 经典光照
23. 经典光照
23.1 环境光
23.2 漫反射光照模型
23.3 镜面高光光照模型
23.4 高光贴图
23.5 Shader结构体
23.6 Uniform Buffer Object
23.7 方向光
23.8 点光源
23.9 多光源
24. 引擎编辑器的实现
24. 引擎编辑器的实现
24.1 分析Godot引擎编辑器
24.2 FBO RenderTexture GameTurbo DLSS
24.3 ImGui介绍与使用
24.4 分离引擎核心层和应用层
24.5 使用ImGui实现引擎编辑器
24.6 Hierarchy与Inspector面板
24.7 Geometry Buffer
25. Shadow Mapping
25. Shadow Mapping
25.1 深度图
25.2 简单阴影
88. VSCode扩展开发与定制
88. VSCode扩展开发与定制
88.1 第一个VSCode扩展程序
88.2 从源码编译VSCode
88.3 打包VSCode内置扩展
88.4 打包LuaHelper到Code-OSS
89. Doxygen生成API文档
89. Doxygen生成API文档
90. GPU分析工具
90. GPU分析工具
90.1 RenderDoc分析不显示bug
98. SubstancePainter插件开发
98. SubstancePainter插件开发
98.1 SP插件开发环境
98.2 开发SP功能性插件
98.3 开发SP渲染插件
99. Toolbag插件开发
99. Toolbag插件开发
99.1 插件开发环境
99.2 API介绍
99.3 命令行调用Toolbag
99.4 更多实现
99.5 代码参考
附录1. Wwise音频引擎
附录1. Wwise音频引擎
1.1 Wwise名词概念
1.2 Wwise制作音效导出SoundBank
1.3 集成Wwise
1.4 封装Wwise播放3D音效
1.5 Wwise性能分析器介绍
1.6 猎人开发后记
代码资源下载
点我下载
Github
点赞、收藏、关注
目录
<< 21.1 GLFW多线程渲染
21.3 完全异步的多线程模型 >>
## 21.1 基于任务队列的多线程渲染 ```text 「游戏引擎 浅入浅出」是一本开源电子书,PDF/随书代码/资源下载: https://github.com/ThisisGame/cpp-game-engine-book ``` ```bash CLion项目文件位于 samples\multithread_render\engine_render_queue ``` 上一节测试了GLFW对多线程的支持,证实了在单独子线程调用OpenGL API渲染是可行的。 那么这一节就来对其进行细化,将渲染三角形这一目标,拆分多个子任务,主线程以命令的形式与渲染线程进行通信。 任务有两种,阻塞性任务和非阻塞性任务。 任务可以拆分为任务命令和任务参数。 * 案例一 你在蹲坑,发现没有纸了,打电话叫你老婆拿纸,这是一个任务。 任务命令:拿纸 任务参数:无 你是主线程,发出命令。 老婆是渲染线程,接受命令,执行,给你结果。 这是一个阻塞式任务,你(主线程)必须等老婆(渲染线程)执行任务(拿纸),返回结果(给你纸)。 就如下面图中的`编译Shader`命令。 ![](md/cpp-game-engine-book/imgs/multithread_render/multithread_render/multithread_render.jpg) <br> * 案例二 你在外面闲逛,你老婆打电话过来叫你去买一斤西瓜,这是一个任务。 任务命令:买东西 任务参数有2个:物品:西瓜 重量:一斤 老婆是主线程,发出命令并带参数。 你是渲染线程,接受命令,解析参数,执行。 买西瓜是比较复杂的操作,要去多个店里询问,比价,拿西瓜,称重,最后付款,才算完成了买西瓜这个任务。 老婆(主线程)不管这些步骤,她只要发命令,然后等结果。 这是一个非阻塞任务,老婆不会一直等你买瓜回去,她可能在打王者荣耀。 等你买好瓜到家,老婆又发出命令,让你去切瓜。 主线程发出渲染命令后,也不会等渲染结果,而是转身就去执行其他逻辑。 就如案例一图中的`绘制`命令。 <br> * 案例三 你老婆在蹲坑,没有纸了,打电话叫你拿纸,叫你买一斤西瓜,然后再去拿两个顺丰快递,然后再去取1000块钱。 第一个是阻塞任务,然后几个都是非阻塞任务。 命令太多,参数太多,人到中年,记忆力衰退,你记不住了。 你需要一个任务队列来保存这些任务,然后从任务队列里面取任务来做。 就如同下图,`编译shader`是阻塞性任务,完成之后才能去做`绘制`这个非阻塞任务。 ![](md/cpp-game-engine-book/imgs/multithread_render/render_task_queue/render_task_queue.jpg) <br> 那么案例三是我们所需要的,来看下如何实现。 ### 1. 渲染任务 非阻塞性任务由 `任务命令` `任务参数` 组成。 阻塞性任务由 `任务命令` `任务参数` `回传结果` 组成。 **任务命令** ```c++ //file:render_command.h line:9 /// 渲染命令 enum RenderCommand { NONE, COMPILE_SHADER,//编译着色器 CREATE_VAO,//创建缓冲区 DRAW_ARRAY,//绘制 END_FRAME,//帧结束 }; ``` 针对每一个操作,创建一个命令。 并不是对每个OpenGL API创建一个命令,不用搞这么细,毕竟每个命令都对应一个任务,任务太多也会造成性能损耗。 渲染一个三角形,简单分三步: 1.编译着色器 2.创建缓冲区 3.绘制 对每一步创建一个命令任务即可。 当需要渲染的任务全部发送之后,还需要一个发出特殊任务`END_FRAME`来标志这一帧结束,这样在渲染线程中,收到这个特殊任务后,就可以去交换缓冲区了。 **任务参数&回传结果** ```c++ //file:render_task_type.h line:11 /// 渲染任务基类 class RenderTaskBase{ public: RenderTaskBase(){} virtual ~RenderTaskBase(){} public: RenderCommand render_command_;//渲染命令 bool need_return_result_ = false;//是否需要回传结果 bool return_result_set_ = false;//是否设置好了回传结果 }; /// 需要回传结果的阻塞性任务 class RenderTaskNeedReturnResult: public RenderTaskBase{ public: RenderTaskNeedReturnResult(){ render_command_=RenderCommand::NONE; need_return_result_=true; } ~RenderTaskNeedReturnResult(){} /// 等待任务在渲染线程执行完毕,并设置回传结果。主线程拿到结果后才能执行下一步代码。 virtual void Wait(){ while(return_result_set_==false){ std::this_thread::sleep_for(std::chrono::microseconds(1)); } } }; ``` 对于阻塞性任务,在主线程中将参数`need_return_result_`设置为`true`。 渲染线程中处理这个任务后,要设置回传结果,然后设置`return_result_set_=true`,标记任务完成。 主线程则调用`Wait()`,等待这个任务执行完毕才继续下一步。 本小节实例中有4个任务: 1. RenderTaskCompileShader (编译着色器任务) 2. RenderTaskCreateVAO (创建VAO) 3. RenderTaskDrawArray (绘制任务) 4. RenderTaskEndFrame (特殊任务:帧结束标志) 任务结构如下: ```c++ //file:render_task_type.h line:40 /// 编译着色器任务 class RenderTaskCompileShader: public RenderTaskNeedReturnResult{ public: RenderTaskCompileShader(){ render_command_=RenderCommand::COMPILE_SHADER; } ~RenderTaskCompileShader(){} public: const char* vertex_shader_source_= nullptr; const char* fragment_shader_source_= nullptr; public: GLuint result_shader_program_id_=0;//存储编译Shader结果的程序ID }; /// 创建VAO class RenderTaskCreateVAO: public RenderTaskNeedReturnResult{ public: RenderTaskCreateVAO(){ render_command_=RenderCommand::CREATE_VAO; } ~RenderTaskCreateVAO(){} public: GLuint shader_program_id_=0;//着色器程序ID const void* positions_=nullptr;//顶点位置 GLsizei positions_stride_=0;//顶点数据大小 const void* colors_=nullptr;//顶点颜色 GLsizei colors_stride_=0;//颜色数据大小 public: GLuint result_vao_=0;//回传创建好的VAO }; /// 绘制任务 class RenderTaskDrawArray: public RenderTaskBase { public: RenderTaskDrawArray(){ render_command_=RenderCommand::DRAW_ARRAY; } ~RenderTaskDrawArray(){} public: GLuint shader_program_id_=0;//着色器程序ID GLuint vao_=0; }; /// 特殊任务:帧结束标志,渲染线程收到这个任务后,刷新缓冲区,设置帧结束。 class RenderTaskEndFrame: public RenderTaskNeedReturnResult { public: RenderTaskEndFrame(){ render_command_=RenderCommand::END_FRAME; } ~RenderTaskEndFrame(){} }; ``` RenderTaskCompileShader (编译着色器任务),是阻塞性任务。 在渲染线程编译着色器之后,需要将编译Shader结果的`ProgramID`设置到 `result_program_id_`,主线程拿到它之后,才能进入渲染。 RenderTaskCreateVAO (创建VAO),是阻塞性任务。 在渲染线程创建好VAO之后,将VAO设置到`result_vao_`,主线程拿到它之后,才能进入渲染。 RenderTaskDrawArray (绘制任务),是非阻塞性的。 绘制一个三角形也没有什么好返回的,主线程源源不断的发出任务,渲染线程不断取出,然后执行即可。 RenderTaskEndFrame (特殊任务:帧结束标志),也是阻塞性任务。 主线程发出一堆渲染任务后,需要等待渲染线程渲染完毕,并且调用`glfwSwapBuffers`交换缓冲区,然后才能往下走执行其他逻辑代码。 那主线程是如何知道渲染线程渲染完毕呢,没办法知道! 主线程和渲染线程通信是通过阻塞性任务的回传结果,如果在主线程中能确定某个任务是最后一个任务,就可以在这个任务上加一个标志,表示渲染结束。渲染线程执行这个任务后,交换缓冲区,并且设置标志。主线程判断标志后,就可以继续下一步了。 普通的渲染任务是没办法确定是否最后一个任务的,所以只能创建一个特殊任务,在主线程发出所有渲染命令后,再发出这个命令。 ### 2. 任务队列 我们口头上叫多线程渲染,但是其实只是在另外一个线程进行渲染,这个模型中只有2个线程,主线程发出命令,渲染线程接受命令做出处理,所以这其实是一个单生产者单消费者的模型。 这种模型在游戏里最常见的应用就是逻辑线程与网络线程的交互,一般就是用RingBuffer来做。 我这里找了一个开源的实现,Github地址:`https://github.com/rigtorp/SPSCQueue`,简单封装了一下。 ```c++ //file:render_task_queue.h #include <spscqueue/include/rigtorp/SPSCQueue.h> class RenderTaskBase; /// 定义一个渲染任务队列 class RenderTaskQueue { public: /// 添加任务到队列 /// \param render_task static void Push(RenderTaskBase* render_task){ render_task_queue_.push(render_task); } /// 队列中是否没有了任务 /// \return static bool Empty(){ return render_task_queue_.empty(); } /// 获取队列中第一个任务 /// \return static RenderTaskBase* Front(){ return *(render_task_queue_.front()); } /// 弹出队列中第一个任务 static void Pop(){ render_task_queue_.pop(); } /// 获取队列中的任务数量 static size_t Size(){ return render_task_queue_.size(); } private: static rigtorp::SPSCQueue<RenderTaskBase*> render_task_queue_;//渲染任务队列 }; ``` 主线程创建任务,把指针扔到队列尾。<a id="antiCollectorAdTxt" href="https://github.com/ThisisGame/cpp-game-engine-book">「游戏引擎 浅入浅出」是一本开源电子书,PDF/随书代码/资源下载: https://github.com/ThisisGame/cpp-game-engine-book</a> 渲染线程从队列头取任务。 ### 3. 任务生产者 生产者负责创建任务,把指针扔到队列尾。 ```c++ //file:render_task_producer.cpp #include <glm/glm.hpp> #include "render_task_producer.h" #include "render_task_type.h" #include "render_task_queue.h" /// 发出阻塞型任务:编译Shader /// \param vertex_shader_source 顶点shader源码 /// \param fragment_shader_source 片段shader源码 /// \param result_shader_program_id 回传的Shader程序ID void RenderTaskProducer::ProduceRenderTaskCompileShader(const char* vertex_shader_source,const char* fragment_shader_source,GLuint& result_shader_program_id){ RenderTaskCompileShader* render_task_compile_shader=new RenderTaskCompileShader(); render_task_compile_shader->vertex_shader_source_=vertex_shader_source; render_task_compile_shader->fragment_shader_source_=fragment_shader_source; render_task_compile_shader->need_return_result_=true;//需要返回结果 RenderTaskQueue::Push(render_task_compile_shader); //等待编译Shader任务结束并设置回传结果 render_task_compile_shader->Wait(); result_shader_program_id=render_task_compile_shader->result_shader_program_id_; delete render_task_compile_shader;//需要等待结果的渲染任务,需要在获取结果后删除。 } /// 发出任务:创建VAO /// \param shader_program_id 使用的Shader程序ID /// \param positions 绘制的顶点位置 /// \param positions_stride 顶点位置数组的stride /// \param colors 绘制的顶点颜色 /// \param colors_stride 顶点颜色数组的stride /// \param result_vao 回传的VAO void RenderTaskProducer::ProduceRenderTaskCreateVAO(GLuint shader_program_id, const void *positions, GLsizei positions_stride, const void *colors, GLsizei colors_stride, GLuint& result_vao) { RenderTaskCreateVAO* render_task_create_vao=new RenderTaskCreateVAO(); render_task_create_vao->shader_program_id_=shader_program_id; render_task_create_vao->positions_=positions; render_task_create_vao->positions_stride_=positions_stride; render_task_create_vao->colors_=colors; render_task_create_vao->colors_stride_=colors_stride; RenderTaskQueue::Push(render_task_create_vao); //等待任务结束并设置回传结果 render_task_create_vao->Wait(); result_vao=render_task_create_vao->result_vao_; delete render_task_create_vao; } /// 发出任务:绘制 /// \param shader_program_id 使用的Shader程序ID /// \param vao void RenderTaskProducer::ProduceRenderTaskDrawArray(GLuint shader_program_id, GLuint vao){ RenderTaskDrawArray* render_task_draw_array=new RenderTaskDrawArray(); render_task_draw_array->shader_program_id_=shader_program_id; render_task_draw_array->vao_=vao; RenderTaskQueue::Push(render_task_draw_array);//普通非阻塞型任务,交由RenderTaskConsumer使用后删除。 } void RenderTaskProducer::ProduceRenderTaskEndFrame() { RenderTaskEndFrame* render_task_frame_end=new RenderTaskEndFrame(); RenderTaskQueue::Push(render_task_frame_end); //等待渲染结束任务,说明渲染线程渲染完了这一帧所有的东西。 render_task_frame_end->Wait(); delete render_task_frame_end;//需要等待结果的任务,需要在获取结果后删除。 } ``` 主线程中只负责初始化GLFW,然后发出命令。 ```c++ //file:main.cpp line:17 GLuint program_id_=0; GLuint vao_=0; int main(void) { //设置错误回调 glfwSetErrorCallback(error_callback); if (!glfwInit()) exit(EXIT_FAILURE); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); #ifdef __APPLE__ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); #endif //创建窗口 GLFWwindow* window = glfwCreateWindow(960, 640, "Simple example", NULL, NULL); if (!window) { glfwTerminate(); exit(EXIT_FAILURE); } //初始化渲染任务消费者(独立线程) RenderTaskConsumer::Init(window); //编译Shader任务 RenderTaskProducer::ProduceRenderTaskCompileShader(vertex_shader_text,fragment_shader_text,program_id_); //创建缓冲区任务 RenderTaskProducer::ProduceRenderTaskCreateVAO(program_id_, kPositions, sizeof(glm::vec3), kColors, sizeof(glm::vec4), vao_); //主线程 渲染循环逻辑 while (!glfwWindowShouldClose(window)) { Render(); //发出特殊任务:渲染结束 RenderTaskProducer::ProduceRenderTaskEndFrame(); //非渲染相关的API,例如处理系统事件,就放到主线程中。 glfwPollEvents(); } RenderTaskConsumer::Exit(); glfwDestroyWindow(window); glfwTerminate(); exit(EXIT_SUCCESS); } void Render(){ //绘制任务 RenderTaskProducer::ProduceRenderTaskDrawArray(program_id_,vao_); } ``` ### 4. 任务消费者 主线程中初始化`RenderTaskConsumer`后,在`RenderTaskConsumer`中就创建了渲染线程。 ```c++ //file:render_task_consumer.cpp line:19 void RenderTaskConsumer::Init(GLFWwindow *window) { window_ = window; render_thread_ = std::thread(&RenderTaskConsumer::ProcessTask); render_thread_.detach(); } void RenderTaskConsumer::Exit() { if (render_thread_.joinable()) { render_thread_.join();//等待渲染线程结束 } } ``` 渲染线程苏醒后,一直从队列中取任务执行。 ```c++ //file:render_task_consumer.cpp line:147 void RenderTaskConsumer::ProcessTask() { //渲染相关的API调用需要放到渲染线程中。 glfwMakeContextCurrent(window_); gladLoadGL(glfwGetProcAddress); glfwSwapInterval(1); while (!glfwWindowShouldClose(window_)) { float ratio; int width, height; glm::mat4 model,view, projection, mvp; //获取画面宽高 glfwGetFramebufferSize(window_, &width, &height); ratio = width / (float) height; glViewport(0, 0, width, height); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glClearColor(49.f/255,77.f/255,121.f/255,1.f); view = glm::lookAt(glm::vec3(0, 0, 10), glm::vec3(0, 0,0), glm::vec3(0, 1, 0)); projection=glm::perspective(glm::radians(60.f),ratio,1.f,1000.f); while(true){ if(RenderTaskQueue::Empty()){//渲染线程一直等待主线程发出任务。没有了任务Sleep 1微秒。 std::this_thread::sleep_for(std::chrono::microseconds(1)); continue; } RenderTaskBase* render_task = RenderTaskQueue::Front(); RenderCommand render_command=render_task->render_command_; switch (render_command) {//根据主线程发来的命令,做不同的处理 case RenderCommand::NONE:break; case RenderCommand::COMPILE_SHADER:{ CompileShader(render_task); break; } case RenderCommand::CREATE_VAO:{ CreateVAO(render_task); break; } case RenderCommand::DRAW_ARRAY:{ DrawArray(render_task, projection, view); break; } case RenderCommand::END_FRAME:{ EndFrame(render_task); break; } } RenderTaskQueue::Pop(); //如果这个任务不需要返回参数,那么用完就删掉。 if(render_task->need_return_result_==false){ delete render_task; } //如果是帧结束任务,就交换缓冲区。 if(render_command==RenderCommand::END_FRAME){ break; } } std::cout<<"task in queue:"<<RenderTaskQueue::Size()<<std::endl; } } ``` `CompileShader(编译Shader)` `CreateVAO(创建VAO)` `DrawArray(绘制)` 这三个再熟悉不过了。 `EndFrame (特殊任务:帧结束标志)`用来标记渲染线程渲染一帧结束,代码如下: ```c++ //file:render_task_consumer.cpp line:120 /// 结束一帧 /// \param task_base void RenderTaskConsumer::EndFrame(RenderTaskBase* task_base) { RenderTaskEndFrame *task = dynamic_cast<RenderTaskEndFrame *>(task_base); glfwSwapBuffers(window_); task->return_result_set_=true; } ``` ### 5. 线程同步 主线程发出任务,渲染线程从队列取任务,那么主线程是一定比渲染线程快的。 下一帧的逻辑,可能是需要对上一帧渲染结果做后处理,那么主线程就一定要等待渲染线程这一帧结束。 如果渲染任务特别重,那么渲染线程会比主线程慢很多,主线程就需要一直等待。 这就是在Unity中常见到的`Gfx.WaitForPresent`,CPU正在等待GPU渲染这一帧。 我们这里是用`RenderTaskEndFrame (特殊任务:帧结束标志)`来作为帧结束标志的。 主线程发出这个任务后,就开始等待这个任务完成。 渲染线程将`RenderTaskEndFrame.return_result_set_`设置为`true`后,主线程才知道渲染线程完成了这一帧的渲染,才继续往下走。 发出任务,到判断任务完成的这一段时间,就是Unity中的`Gfx.WaitForPresent`。 ### 6. 测试 运行项目测试,结果正常。 ![](md/cpp-game-engine-book/imgs/multithread_render/render_task_queue/draw_triangle.jpg)
<< 21.1 GLFW多线程渲染
21.3 完全异步的多线程模型 >>
12
代码资源下载
点我下载
Github
点赞、收藏、关注
目录
Introduction
Introduction
前言
前言
1. 游戏引擎框架介绍
1. 游戏引擎框架介绍
1.1 Unity的组成
1.2 游戏引擎组成
2. Opengl开发环境搭建
2. Opengl开发环境搭建
2.1 Opengl到底是什么
2.2 搭建Opengl开发环境
2.3 使用VisualStudio开发
3. 绘制多边形
3. 绘制多边形
3.1 画个三角形
3.2 画个正方形
3.3 画个立方体
4. 着色器
4. 着色器
4.1 Unity Shader和OpenGL Shader
4.2 顶点着色器
4.3 片段着色器
5. 绘制贴图
5. 绘制贴图
5.1 颜色和贴图
5.2 贴图文件介绍
5.3 CPU与GPU的通信方式
5.4 使用stb_image解析图片
5.5 绘制带贴图的立方体盒子
5.6 压缩纹理
5.7 图片压缩工具
5.8 使用压缩纹理
5.9 DXT压缩纹理扩展
6. 索引与缓冲区对象
6. 索引与缓冲区对象
6.1 顶点索引
6.2 缓冲区对象
6.3 OpenGL Core Profile
6.4 顶点数组对象
7. 绘制Mesh和材质
7. 绘制Mesh和材质
7.1 导出Mesh文件
7.2 使用Mesh文件
7.3 Shader文件创建与使用
7.4 创建材质
7.5 使用材质
7.6 MeshRenderer
8. 绘制静态模型
8. 绘制静态模型
8.1 Blender安装与配置
8.2 Blender制作模型
8.3 Blender Python设置开发环境
8.4 Blender Python创建物体
8.5 Blender Python导出顶点数据
8.6 加载导出的Mesh
9. 基于组件开发
9. 基于组件开发
9.1 基于RTTR实现反射
9.2 实现GameObject-Component
10. 相机
10. 相机
10.1 最简单的相机
10.2 多相机渲染
10.3 相机排序
10.4 CullingMask
11. 控制系统
11. 控制系统
11.1 键盘控制
11.2 鼠标控制
12. 拆分引擎和项目
12. 拆分引擎和项目
13. 绘制文字
13. 绘制文字
13.1 TrueType简介
13.2 绘制单个字符
13.3 绘制多个文字
13.4 彩色字
14. GUI
14. GUI
14.1 正交相机
14.2 UIImage
14.3 UIMask
14.4 UIText
14.5 UIButton
15. 播放音效
15. 播放音效
15.1 播放2D音效
15.2 播放3D音效
15.3 使用FMOD Studio音频引擎
16. Profiler
16. Profiler
16.1 初识easy_profiler
16.2 集成easy_profiler
17. 嵌入Lua
17. 嵌入Lua
17.1 Sol2与C++交互
17.2 更加友好的Lua框架设计
17.3 引擎集成sol2
17.4 调试Lua
18. 骨骼动画
18. 骨骼动画
18.1 Blender制作骨骼动画
18.2 Blender导出骨骼动画
18.3 解析骨骼动画
18.4 矩阵的主序
19. 骨骼蒙皮动画
19. 骨骼蒙皮动画
19.1 骨骼蒙皮动画实现
19.2 骨骼权重
19.3 Blender蒙皮刷权重
19.4 Blender导出蒙皮权重
19.5 加载权重文件
20. 解析FBX文件
20. 解析FBX文件
20.1 导出Mesh
20.2 导出骨骼动画
20.3 导出权重
20.4 渲染骨骼蒙皮动画
21. 多线程渲染
21. 多线程渲染
21.1 GLFW多线程渲染
21.2 基于任务队列的多线程渲染
21.3 完全异步的多线程模型
21.4 引擎支持多线程渲染
22. Physx物理引擎
22. Physx物理引擎
22.1 Physx实例-小球掉落
22.2 物理材质
22.3 碰撞检测
22.4 连续碰撞检测
22.5 场景查询
22.6 引擎集成Physx
23. 经典光照
23. 经典光照
23.1 环境光
23.2 漫反射光照模型
23.3 镜面高光光照模型
23.4 高光贴图
23.5 Shader结构体
23.6 Uniform Buffer Object
23.7 方向光
23.8 点光源
23.9 多光源
24. 引擎编辑器的实现
24. 引擎编辑器的实现
24.1 分析Godot引擎编辑器
24.2 FBO RenderTexture GameTurbo DLSS
24.3 ImGui介绍与使用
24.4 分离引擎核心层和应用层
24.5 使用ImGui实现引擎编辑器
24.6 Hierarchy与Inspector面板
24.7 Geometry Buffer
25. Shadow Mapping
25. Shadow Mapping
25.1 深度图
25.2 简单阴影
88. VSCode扩展开发与定制
88. VSCode扩展开发与定制
88.1 第一个VSCode扩展程序
88.2 从源码编译VSCode
88.3 打包VSCode内置扩展
88.4 打包LuaHelper到Code-OSS
89. Doxygen生成API文档
89. Doxygen生成API文档
90. GPU分析工具
90. GPU分析工具
90.1 RenderDoc分析不显示bug
98. SubstancePainter插件开发
98. SubstancePainter插件开发
98.1 SP插件开发环境
98.2 开发SP功能性插件
98.3 开发SP渲染插件
99. Toolbag插件开发
99. Toolbag插件开发
99.1 插件开发环境
99.2 API介绍
99.3 命令行调用Toolbag
99.4 更多实现
99.5 代码参考
附录1. Wwise音频引擎
附录1. Wwise音频引擎
1.1 Wwise名词概念
1.2 Wwise制作音效导出SoundBank
1.3 集成Wwise
1.4 封装Wwise播放3D音效
1.5 Wwise性能分析器介绍
1.6 猎人开发后记