Cherno_游戏引擎系列教程(4):74~100

74. Where to go next + Code Review Game Engine series

75. Entity Component System Game Engine series

76. Intro to EnTT (ECS) Game Engine series

这里cherno简单介绍了一下 entt 的用法,比如:

class MeshComponent;

static void OnTransformConstruct(entt::registry& registry, entt::entity entity)
{

}

struct TransformComponent
{
    glm::mat4 Transform;

    TransformComponent() = default;
    TransformComponent(const TransformComponent&) = default;
    TransformComponent(const glm::mat4& transform)
        : Transform(transform) {}

    operator glm::mat4& () { return Transform; }
    operator const glm::mat4& () const { return Transform; }
};

entt::entity entity = m_Registry.create();
m_Registry.emplace<TransformComponent>(entity, glm::mat4(1.0f));

m_Registry.on_construct<TransformComponent>().connect<&OnTransformConstruct>();

TransformComponent& transform = m_Registry.get<TransformComponent>(entity);

auto view = m_Registry.view<TransformComponent>();
for (auto entity : view)
{
    TransformComponent& transform = view.get<TransformComponent>(entity);
}

auto group = m_Registry.group<TransformComponent>(entt::get<MeshComponent>);
for (auto entity : group)
{
    auto&[transform, mesh] = group.get<TransformComponent, MeshComponent>(entity);

    Renderer::Submit(mesh, transform);
}

而我们所需要的只不过是:#include "entt.hpp"

77. Entities and Components Game Engine series

78. The ENTITY Class Game Engine series

我们在写一个 ENTITY 类的时候,本质上在这里是对 entt 库的一个包装,包装成我们自己想要的api形式。但是我们的成员中按理不能有一个 Scene,比如不能是 Ref<Scene>,因为一个 entity 并不拥有场景,但是又需要比如场景是否存在来做判定。因此按理来说比较好的做法是弱引用之类的。不过我们这里就简单地写成一个裸指针了。

79. Camera Systems Game Engine series

80. Scene Camera Game Engine series

81. Native Scripting Game Engine series

我们希望可以做成类似这样的:
在这里插入图片描述
这里我们叫做 NativeScriptComponent ,因为之后想让 ScriptComponent 作为 C# 或是 lua 之类的。

82. Native Scripting (now with virtual functions!)

83. Scene Hierarchy Panel Game Engine series

这里提了一下:imgui中有一个 imgui_demo.cpp 文件,给了imgui的所有(?)用法。貌似是和 showdemowindow 对应的?可以先用 showdemowindow 找到想要的功能。

84. Properties Panel Game Engine series

85. Camera Component UI

86. Drawing Component UI

理论上我们最好有一个材质系统,比如我们的组件就可以这样:
在这里插入图片描述
我们现在基本上都是这样写的:

if (entity.HasComponent<SpriteRendererComponent>())
{
    if (ImGui::TreeNodeEx((void*)typeid(SpriteRendererComponent).hash_code(), ImGuiTreeNodeFlags_DefaultOpen, "Sprite Renderer"))
    {
        auto& src = entity.GetComponent<SpriteRendererComponent>();
        ImGui::ColorEdit4("Color", glm::value_ptr(src.Color));
        ImGui::TreePop();
    }
}

对于任何component都是这样去写,很重复。

未来我们会改为一个模板的形式,每次只需要:
在这里插入图片描述
这样,给一个具体的component的模板参数,然后给一个名字,再传入一个我们自定义的lambda的ui方法。而比如上面的 SpriteRendererComponent 代码在这里的lambda函数内部就只需要写这两行:

auto& src = entity.GetComponent<SpriteRendererComponent>();
ImGui::ColorEdit4("Color", glm::value_ptr(src.Color));

在这里插入图片描述

87. Transform Component UI

imgui 有一个 pushid 的功能,就像不同的 namespace 一样,否则我们这样的代码ImGui::DragFloat("##X", &values.x, 0.1f);,会由于都是 ##X 而导致 translation rotation 和 scale 操作相同的 bug。

88. Adding Removing Entities and Components UI

89. Making the Hazelnut Editor Look Good!

if (ImGui::InputText("##Tag", buffer, sizeof(buffer))) 

貌似 imgui 中这里的 ## 是隐藏的意思?

我们在这里这样改 docking 窗口的最小的宽度:
在这里插入图片描述
这里我设置的和 cherno 不一样,我设的是 470.0f,因为我发现我的情况只有这个值才能全装得下。

90. Saving and Loading Scenes!

这里我们希望是人可读的,所以放弃二进制,而是序列化成一个 txt 的形式。

这里我们没有选择 json 而是 yaml:
https://www.runoob.com/w3cnote/yaml-intro.html

这里注意 yaml 要使用静态库的方式,需要添加预定义 YAML_CPP_STATIC_DEFINE,这是 cherno 的yaml的premake没有做的一点。

并且还需要把这个宏加在 HEngine 的 project 里:
在这里插入图片描述
这里我们希望 yaml 可以重载我们的形式,我们这样写:

YAML::Emitter& operator<<(YAML::Emitter& out, const glm::vec3& v)
{
	out << YAML::Flow;
	out << YAML::BeginSeq << v.x << v.y << v.z << YAML::EndSeq;
	return out;
}

YAML::Emitter& operator<<(YAML::Emitter& out, const glm::vec4& v)
{
	out << YAML::Flow;
	out << YAML::BeginSeq << v.x << v.y << v.z << v.w << YAML::EndSeq;
	return out;
}

这里 YAML::Flow 可以让格式变得更直观。

91. Open Save File Dialogs

92. Transformation Gizmos

93. Editor Camera

94. Multiple Render Targets and Framebuffer Refactor

我们想要的效果如下:

FramebufferSpecification fbSpec;
fbSpec.Attachments = { FramebufferTextureFormat::RGBA8, FramebufferTextureFormat::RGBA8, FramebufferTextureFormat::Depth };
fbSpec.Width = 1280;
fbSpec.Height = 720;
m_Framebuffer = Framebuffer::Create(fbSpec);

就是可以像这样通过一个描述,比如上面的 { FramebufferTextureFormat::RGBA8, FramebufferTextureFormat::RGBA8, FramebufferTextureFormat::Depth },从而我们理应知道有两个 color buffer 和一个 depth buffer

做法很简单,首先封装一下:

enum class FramebufferTextureFormat
{
	None = 0, 

	// Color
	RGBA8,

	// Depth/stencil
	DEPTH24STENCIL8,

	// Defaults
	Depth = DEPTH24STENCIL8
};

struct FramebufferTextureSpecification
{
	FramebufferTextureSpecification() = default;
	FramebufferTextureSpecification(FramebufferTextureFormat format)
		: TextureFormat(format) {}

	FramebufferTextureFormat TextureFormat = FramebufferTextureFormat::None;
	// TODO: filtering/wrap
};

struct FramebufferAttachmentSpecification
{
	FramebufferAttachmentSpecification() = default;
	FramebufferAttachmentSpecification(std::initializer_list<FramebufferTextureSpecification> attachments)
		: Attachments(attachments) {}

	std::vector<FramebufferTextureSpecification> Attachments;
};

   struct FramebufferSpecification
   {
       uint32_t Width, Height;
	FramebufferAttachmentSpecification Attachments;
       uint32_t Samples = 1;

       bool SwapChainTarget = false;
   };

但是我个人感觉这样封装的层太多了,与其封装一个 vector 还不如直接封装一个 initializer_list 或者 vector<FramebufferTextureSpecification>,比如这样:

enum class FramebufferTextureFormat
{
	None = 0, 

	// Color
	RGBA8,

	// Depth/stencil
	DEPTH24STENCIL8,

	// Defaults
	Depth = DEPTH24STENCIL8
};

struct FramebufferTextureSpecification
{
	FramebufferTextureSpecification() = default;
	FramebufferTextureSpecification(FramebufferTextureFormat format)
		: TextureFormat(format) {}

	FramebufferTextureFormat TextureFormat = FramebufferTextureFormat::None;
	// TODO: filtering/wrap
};

   struct FramebufferSpecification
   {
       uint32_t Width, Height;
	std::initializer_list<FramebufferTextureSpecification> Attachments;
       uint32_t Samples = 1;

       bool SwapChainTarget = false;
   };

这样少一层封装我个人感觉清爽一些,使用的时候我们只需要这样填充:

OpenGLFramebuffer::OpenGLFramebuffer(const FramebufferSpecification& spec)
    : m_Specification(spec)
{
	for (auto spec : m_Specification.Attachments)
	{
		if (!Utils::IsDepthFormat(spec.TextureFormat))
			m_ColorAttachmentSpecifications.emplace_back(spec);
		else
			m_DepthAttachmentSpecification = spec;
	}

    Invalidate();
}

即在对应的图形 api 初始化的时候就去判断一下,每个描述是 color buffer 还是 depth buffer,然后通过类的内部成员:

std::vector<FramebufferTextureSpecification> m_ColorAttachmentSpecifications;
FramebufferTextureSpecification m_DepthAttachmentSpecification;

std::vector<uint32_t> m_ColorAttachments;
uint32_t m_DepthAttachment = 0;

调api对应一下就好了。

不过目前先按着 cherno 的版本来好了,未来或许我会重构一下。

95. Preparing Framebuffers for Mouse Picking

我们希望 Multiple Render Targets 的第二个渲染目标可以返回每个 pixel 对应的 entity id。我们可以用 uint,但是 cherno 喜欢让 -1 为 非entity 的情况:

layout(location = 0) out vec4 color;
layout(location = 1) out int color2;

(当然我们仍然可以用 uint,习惯不同罢了)

96. Clearing Framebuffer Texture Attachments + Git Branching

97. Mouse Picking

98. Clicking to Select Entities

这里的原理是通过多重渲染目标(Multiple Render Targets),每次都把 entity id 给传入到着色器uniform,然后让第二个 framebuffer 的值就为那个 entity id

然后每次都去读这个 framebuffer,写函数:

int OpenGLFramebuffer::ReadPixel(uint32_t attachmentIndex, int x, int y)
{
	HE_CORE_ASSERT(attachmentIndex < m_ColorAttachments.size());

	glReadBuffer(GL_COLOR_ATTACHMENT0 + attachmentIndex);
	int pixelData;
	glReadPixels(x, y, 1, 1, GL_RED_INTEGER, GL_INT, &pixelData);
	return pixelData;
}

传入 x y 为鼠标在对应视图的位置即可。

99. SPIR-V and the New Shader System

我们未来想支持 vulkan,但是虽然都是glsl,然而 vulkan 的 glsl 和 OpenGL 的 glsl 有一些差别,比如uniform

参考:
https://blog.csdn.net/qwertyu1234/article/details/50163847
https://www.khronos.org/spir/

SPIR-V是以一种二进制格式存在的,并且函数还是以控制流图CFG的形式存在;数据结构也保留了高级语言里的层级关系。
这样做的目的是为了更好的在目标平台上进行优化;同时Khronos也放出了官方标准的开源编译器Glslang。

在这里插入图片描述
在这里插入图片描述
按 cherno 的意思,貌似可以先编译成中间字节码 spir-v,再告诉OpenGL绑定这个spir-v。于是 vulkan 和 OpenGL 就都可以按照 vulkan 的glsl方式去写,然后都先编译成 spir-v,我看games104貌似也是用的spir-v:
在这里插入图片描述

ue4 shader 系统补充:
https://zhuanlan.zhihu.com/p/462659745

https://zhuanlan.zhihu.com/p/49069689

注:

上面对应的 YouTube 集数少一集,也就是 99 对应 YouTube 的 100 集

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值