C++中嵌入C#作脚本引擎(二)
C++中嵌入C#作脚本引擎(一) C++中嵌入C#作脚本引擎(二) 编辑器内的工作流 实现思路 上一篇讨论的内容都是在运行时的,其工作流是:引擎从文件加载场景,在Mono Runtime中创建脚本实例,创建时调用OnCreate方法,之后每帧运行时调用OnUpdate方法,脚本实例销毁时调用OnDestroy方法。 而在编辑器中,情况没有那么简单。在编辑器中,有编辑和运行两个模式,当我们在编辑场景而没有运行场景时,为一个Entity添加脚本,我们并不希望OnCreate方法直接调用,而是在点击"Play"按钮后调用,并开始运行脚本。但在编辑模式中,要能够对Entity上已挂载的脚本的属性进行修改,在运行时,脚本内的对应属性就应该是修改过的版本。由此产生了两种设计思路。 第一种:在编辑模式下,为Entity添加脚本,实际并不在Mono Runtime中创建脚本实例,而是由Mono Runtime中加载的脚本类类型获取反射信息,并用C++的自定义数据结构来存储数据,在编辑器中修改的也是该数据。点击"Play"按钮后,Mono Runtime中创...
软光栅化渲染器学习(二)
软光栅化渲染器学习(一) 深度测试 深度测试通过标准化的z值,判断图元的前后关系,从而判断可见性。 在Renderer中新增一个深度缓冲: 123456789101112Renderer::Renderer(Window &window){ // ... depth_buffer = new float[window.width * window.height]; depth_buffer_size = window.width * window.height;}void Renderer::ClearDepth(){ for (size_t i = 0; i < depth_buffer_size; i++) depth_buffer[i] = 1.0f;} 每次清除深度缓冲时,将值赋为1,因为我们规定深度缓冲中的z值为[0,1][0,1][0,1]的,且0表示最近,1表示最远。 在软渲染器中,我们可以在光栅化调用片段着色器之前,进行深度测试。然而,通常这在GPU渲染管线中是提前深度测试...
软光栅化渲染器学习(一)
本文为笔者入门图形学,学习光栅化渲染的记录。 SDL3框架 使用SDL3,可以轻松地创建一个窗口,作为渲染器的框架。 定义Window类如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445class Window{ public: friend class Renderer; Window(const std::string &name, size_t width, size_t height) : width(width), height(height) { sdl_window = SDL_CreateWindow(name.c_str(), width, height, SDL_WINDOW_RESIZABLE); if (!sdl_window) { SDL_Log("Could not create a win...
在Unity中实现简化的光线追踪
最近在学图形学,想着与其从头开始搓一个光线追踪渲染器,不如在现有的引擎基础上实践,入门更加简单。刚好看到了Unity中实现光追的相关教程,考虑到Unity方便调试,方便预览效果,故选择用Unity作为基础。 注:本文的实现基于Unity2022.3.55f1c1版本。 Unity Shader 基础 Unity Shader文件以.shader结尾,本质上是一个对顶点/片元着色器和其他很多设置提供了一层抽象的语言。可以通过内嵌Cg/HLSL或者GLSL来编写顶点/片元着色器。我们将要实现的光线追踪的核心逻辑也将在Unity Shader中编写。 一个最简单的shader文件大概是这样: 123456789101112131415161718192021222324252627282930313233343536373839404142Shader "ShaderName"{ SubShader { Pass { CGPROGRAM // 在这里嵌入...
C++中嵌入C#作脚本引擎(一)
C++中嵌入C#作脚本引擎(一) C++中嵌入C#作脚本引擎(二) 序言 游戏引擎中引入脚本系统,可以将引擎底层逻辑与Gameplay逻辑分离,由于脚本语言相对简单,也提升了游戏开发效率;除此之外,将脚本视作一种资源进行管理,可以实现热重载。 C#,lua和python是常见的脚本语言,其中,lua和python都是解释型语言,无需编译即时运行,lua的轻量级特性使得它有着比python快的运行效率,广泛用于中小型项目中;而C#虽然使用JIT编译(运行时执行编译),但是可以通过重新加载程序集(Assembly)的方式实现热更新,因此达到了与解释型语言类似的脚本的功能,同时不失性能优势。 对笔者自己来说,C#相比于lua,作为一门编程语言来说更加完备(静态类型检查等),兼顾了性能与开发效率。本文记录学习使用Mono,将C#嵌入C++,实现两者互通的方法。 Mono环境 Mono 是一个跨平台的开源 .NET 运行时和开发框架,现在广泛用在游戏引擎中嵌入C#。之所以选择Mono而非另一个 .NET 实现 .NET Core (CoreCLR 作为运行时),是因为Mono的C/C++ ...
ECS的两种主流实现:基于SparseSet和基于Archetype
序言 游戏中有许多种管理对象的方式。而 ECS (Entity Component System) 能够脱颖而出,被广泛使用,其中经历了一定的发展过程。 最早也是最朴素的一种方式,是通过继承来表达GameObject之间的关系,传统的OOP设计思想。不同的GameObject通过继承来获得特定的功能,但这种方式显然存在问题,无法实现功能之间的任意组合,使用多重继承会导致代码混乱,难以维护;此外,也无法实现功能的动态添加和删除。 还有一种经典的方式是 GC (Gameobject Component) 模式,也是Unity早期使用的模式。软件开发中强调组合优于继承,故由GameObject作为统一的容器,通过插入各种组件Component来实现不同的功能。实践证明,用这种方式组织代码,清晰易读,解决了上一种方法的问题。(注意此处的Component与ECS中的Component不是一个概念,此处的Component包含了所有处理逻辑。) 但是随着游戏的体量越来越大,这种方法中暴露出性能上的缺陷。每个Component由所属的GameObject管理,在内存上是离散存储的,因此每次需...
C++反射系统
在游戏引擎中,很多常用的功能需要用到反射系统。例如在Unity的Inspector面板中,可以直观地看到每个Component的属性,并且在编辑器中实时修改;又比如在保存和加载游戏场景时,需要用到对游戏对象的序列化与反序列化。保存时,通过反射将各组件的属性转化成文本数据存储到json,yml或toml等文件中;加载时,解析文本文件并由反射系统创建游戏物体。 反射(Reflection)是程序在运行时检查和修改自身结构和行为的能力,C++本身不支持反射,虽说有C++26的静态反射提案,但是离主流编译器支持还早。开源社区中有比较知名的反射库如rttr,CPP-Reflection(即GAMES104中的反射系统的参考),在此我想从头开始实现一个很简易的反射系统,并通过Clang实现一个代码生成器,解析并自动生成反射代码,这样的功能正是一个游戏引擎必不可少的。 学习过程中参考了GAMES104的Piccolo引擎以及Nickel Engine中的反射部分。后者的作者也有一套简单的C++反射教程,强烈推荐。详细了解C++反射请看写给C++程序员的反射教程。 动态反射系统 C++反射一般分...