当前位置:   article > 正文

Cherno_游戏引擎系列教程(3):44~73

cherno

44. Shader Asset Files Game Engine series

这里我们会想从一个 shader file 来编译一个 shader。

我们希望最终的 api 可以设计成这样:

Shader::Create("assets/shaders/Texture.glsl");
  • 1

当然未来最好的是可以把 glsl 转换为我们自己的 shader language

45. Shader Library Game Engine series

当有一些示例shader比如pbr shader之类的,我们就可以存在引擎的 Shader Library 中。本质上就是用一个哈希表去存:

std::unordered_map<std::string, Ref<Shader>> m_Shaders;
  • 1

46. How to Build a 2D Renderer Game Engine series

对于2D的情况,最简单的就是所有的都看成一个 quad

比如画一个圆,最好的方式不是画边,而是简单地渲染一个 texture(带alpha通道的那种)。

为了避免混淆,我们命名为 HEngine::Renderer2D 这样的:
在这里插入图片描述

可以参考:
https://blog.csdn.net/alexhu2010q/article/details/122871987

47. Camera Controllers Game Engine series

48. Resizing Game Engine series

对于 resize,我们不只是告诉window,我们还需要告诉我们的图形api,我们的渲染区域改变了。

因此这里我们需要 glviewport

当 window minimize的时候 width 和 height 是0,可以打个断点调试一下。所以我们还需要进行一些边界处理。

我们可以在 OnEvent 函数中自己定制想要的缩放效果:

void OnEvent(HEngine::Event& e) override
{
	m_CameraController.OnEvent(e);

	if (e.GetEventType() == HEngine::EventType::WindowResize)
	{
		auto& re = (HEngine::WindowResizeEvent&)e;

		//float zoom = (float)re.GetWidth() / 1280.0f;
		//m_CameraController.SetZoomLevel(zoom);
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

49. Maintenance Game Engine series

确保你的所有configuration都能够build,所以这次维护修改升级我们还需要build一下release版本来试试。

50. Preparing for 2D Rendering Game Engine series

51. Starting our 2D Renderer Game Engine series

52. 2D Renderer Transforms Game Engine series

53. 2D Renderer Textures Game Engine series

54. Single Shader 2D Renderer Game Engine series

55. Intro to Profiling Game Engine series

55.5. Visual Profiling

目前的Profiling用ImGUI绘制出来了,如上图所示,这种很简陋,缺点有:

  • 只能看实时数据,无法看之前帧的数据
  • 没有Hierarchy信息,比如像Unity那种Profiler,可以看具体函数的Hierarchy的Profile信息
  • 没有搜索功能等

参考链接:
https://blog.csdn.net/alexhu2010q/article/details/122871987

为了方便预览和分析程序里想要分析的代码片段每帧所花的时间,这里选了一个很简单的方法,就是在Runtime把Profile信息写到一个JSON文件里,然后利用Chrome提供的Chrome Tracing工具(只要在谷歌浏览器里输入chrome://tracing即可打开它),来帮忙分析这个JSON文件。

实现写入JSON文件的类叫instrumentor,这个单词在英语里其实并不存在,它源自于单词instrumentation,本意是一套仪器、仪表,在CS里的意思是对程序性能、错误等方面的监测:

In the context of computer programming, instrumentation refers to the measure of a product’s performance, to diagnose errors, and to write trace information.[1] Instrumentation can be of two types: source instrumentation and binary instrumentation.

我们生成好 json 文件后,在谷歌浏览器中进入 chrome://tracing

随后我们 load 文件 HEngineProfile-Runtime.json 进来,就可以看到这样的画面:
在这里插入图片描述

注:每次我都是先 F5 运行,然后先关我们的窗口,再关闭我们的console,否则会json解析失败?暂不清楚原因。

56. Instrumentation Game Engine series

现在的Profiling系统,有个比较大的问题:

  • 它会不断的Profiling,然后写入到JSON文件里,每一帧都跑这个数据,这是不科学的,得到的文件可能会到几百兆,像Unity的Profiling系统,就提供了录制功能,而且它只会录制特定帧数的Profiling数据,就是那个不停滚动的Profiling窗口…

SP. Making a GAME in ONE HOUR using MY ENGINE

57. Improving our 2D Rendering API Game Engine series

58. How I Made a Game in an Hour Using Hazel

59. Hazel 2020

60. Batch Rendering

61. Batch Rendering Textures (+ Debugging!)

62. Drawing Rotated Quads

63. Renderer Stats and Batch Improvements

这一节写了一些记录效率的,比如 draw call 有多少个之类的,通过 imgui 渲染出来。

64. Testing Hazel’s Performance!

65. Let’s Make Something in Hazel!

这一节上了一个 particle system,用的 cherno 这里的代码:

https://github.com/TheCherno/OneHourParticleSystem

其实每个 particle 就是一个 quad,我们只不过是写了一个 particle system 类来定义它每个时刻的位置、大小、旋转和颜色罢了。给一个生命周期比如一秒钟,然后大小依次递减即可,借助之前的 Timestep 就很容易做到。

最后通过我们的粒子池和index来发射粒子:

std::vector<Particle> m_ParticlePool;
uint32_t m_PoolIndex;
  • 1
  • 2
for (int i = 0; i < 50; i++)
	m_ParticleSystem.Emit(m_Particle);
  • 1
  • 2
void ParticleSystem::Emit(const ParticleProps& particleProps)
{
	Particle& particle = m_ParticlePool[m_PoolIndex];
	particle.Active = true;
	particle.Position = particleProps.Position;
	particle.Rotation = Random::Float() * 2.0f * glm::pi<float>();

	// Velocity
	particle.Velocity = particleProps.Velocity;
	particle.Velocity.x += particleProps.VelocityVariation.x * (Random::Float() - 0.5f);
	particle.Velocity.y += particleProps.VelocityVariation.y * (Random::Float() - 0.5f);

	// Color
	particle.ColorBegin = particleProps.ColorBegin;
	particle.ColorEnd = particleProps.ColorEnd;

	particle.LifeTime = particleProps.LifeTime;
	particle.LifeRemaining = particleProps.LifeTime;
	particle.SizeBegin = particleProps.SizeBegin + particleProps.SizeVariation * (Random::Float() - 0.5f);
	particle.SizeEnd = particleProps.SizeEnd;

	m_PoolIndex = --m_PoolIndex % m_ParticlePool.size();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

66. How Sprite Sheets/Texture Atlases Work

资源:https://kenney.nl/assets/rpg-base

因为至多就 32 个slot,所以比如有 128 张 texture,那么至少就要 4 个 draw call

cherno 表示draw call不是问题,主要是 texture 每次都需要 bind 和 rebind 。因此打图集效率是很高的。

所以似乎 spritesheet 就是 texture atlas?

67. SubTextures - Creating a Sprite Sheet API

68. Creating a Map of Tiles

69. Next Steps + Dockspace Game Engine series

我们想做的是搞个 framebuffer,绘制到一张 texture 上,然后就可以用 imgui 的 render image 去绘制了。

比如:

uint32_t textureID = m_CheckerboardTexture->GetRendererID();
ImGui::Image((void*)textureID, ImVec2{ 256.0, 256.0 });
  • 1
  • 2

具体工作要在下一节再做了。

70. Framebuffers Game Engine series

在这里插入图片描述
这里的 bool SwapChainTarget 就是是否是直接渲染到屏幕的意思(是不是GPU端的framebuffer?)

但是这一节发现了一个怪事,就是相机的上下颠倒了,YouTube评论区给出了原因:

If anyone else is having their images rendered upside down in ImGui, add ImVec2{ 0,1 }, ImVec2{1,0} to the parameters of ImGui::Image to change the UV’s of the image. For some reason, ImGui flips them by default

原来是 imgui 的自动处理,我们更改需要在后面加上两个参数 ImVec2{ 0, 1 }, ImVec2{ 1, 0 }:

ImGui::Image((void*)textureID, ImVec2{ 1280, 720 }, ImVec2{ 0, 1 }, ImVec2{ 1, 0 });
  • 1

71. Making a New C++ Project in Hazel Game Engine series

72. Scene Viewport Game Engine series

73. Code Review + ImGui Layer Events Game Engine series

这里注意几点:

  1. 我们的图形api按理应该可以在运行时选择:vulkan or dx or opengl or metal?
  2. 我们的 Input 应该是编译时根据平台来选择的。Android or Windows or xxx,不可能在安卓设备上运行时选择 WindowsInput 吧!

所以参考cherno后续的代码:
https://github.com/TheCherno/Hazel/blob/master/Hazel/src/Hazel/Core/Input.h

不如直接在 HEngine/HEngine/src/Platform/Windows/WindowsInput.cpp 中直接包含头文件 #include "HEngine/Core/Input.h",而不需要 WindowsInput.h

对于 ImGui Layer Events,我们先做如下测试:

我们通过这两行来测试:

HE_CORE_WARN("Focused: {0}", ImGui::IsWindowFocused());
HE_CORE_WARN("Hovered: {1}", ImGui::IsWindowHovered());
  • 1
  • 2

发现 hovered 是看鼠标有没有移动到那个窗口,而focesed就是看是不是选中了那个窗口了,很容易测试。

于是这就可以成为我们是否接收事件的条件判断。

注意我们的 Application 的构造函数中就会 PushOverlay(m_ImGuiLayer)
在这里插入图片描述
而我们对事件的检测是从后向前的:

void Application::OnEvent(Event& e)
{
	EventDispatcher dispatcher(e);
	dispatcher.Dispatch<WindowCloseEvent>(HE_BIND_EVENT_FN(Application::OnWindowClose));
	dispatcher.Dispatch<WindowResizeEvent>(HE_BIND_EVENT_FN(Application::OnWindowResize));

	for (auto it = m_LayerStack.rbegin(); it != m_LayerStack.rend(); ++it)
	{
		if (e.Handled)
			break;
		(*it)->OnEvent(e);
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

我们目前一共就两个层:一个是正常push进去的EditorLayer,一个是构造函数里头默认就会push进去的ImGuiLayer,且是overlay,也就是默认在最后面,因此每次事件都是先 imgui 判断一次,再传播到前面的层。

于是我们在 ImGuiLayer 中新增了一些方法判断:

void BlockEvents(bool block) { m_BlockEvents = block; }
  • 1
void ImGuiLayer::OnEvent(Event& e)
{
    if (m_BlockEvents)
    {
        ImGuiIO& io = ImGui::GetIO();
        e.Handled |= e.IsInCategory(EventCategoryMouse) & io.WantCaptureMouse;
        e.Handled |= e.IsInCategory(EventCategoryKeyboard) & io.WantCaptureKeyboard;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这样我们就可以通过一些设置来判断是否要传播到前面的层了!

比如:

EditorLayer.cpp 中:

m_ViewportFocused = ImGui::IsWindowFocused();
m_ViewportHovered = ImGui::IsWindowHovered();
Application::Get().GetImGuiLayer()->BlockEvents(!m_ViewportFocused || !m_ViewportHovered);
  • 1
  • 2
  • 3
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/97729
推荐阅读
相关标签
  

闽ICP备14008679号