跟着cherno手搓游戏引擎【14】封装opengl

本节先把代码粘上,后续会慢慢把注释都给加上,先看代码了解个大概(待更新)

前置:

RendererAPI.h:

cpp 复制代码
#pragma once
namespace YOTO {
	enum class RendererAPI {
		None = 0,
		OpenGL=1
	};
	class Renderer {
	public:
		inline static RendererAPI GetAPI() {
			return s_RendererAPI;
		}
		static RendererAPI s_RendererAPI;
	};

}

RendererAPI.cpp:

cpp 复制代码
#include"ytpch.h"
#include"Renderer.h"
namespace YOTO {
	RendererAPI Renderer::s_RendererAPI = RendererAPI::OpenGL;
}

抽象:

buffer.h:

cpp 复制代码
#pragma once
#include <YOTO/Log.h>
namespace YOTO {
	enum class ShaderDataType{
	None=0,
	Float,Float2,Float3,Float4,
	Mat3,Mat4,
	Int,Int2,Int3,Int4,
	Bool,
	};
	static uint32_t  ShaderDataTypeSize(ShaderDataType type) {
		switch (type)
		{
		case YOTO::ShaderDataType::Float:
			return 4;
			break;
		case YOTO::ShaderDataType::Float2:
			return 4*2;
			break;
		case YOTO::ShaderDataType::Float3:
			return 4*3;
			break;
		case YOTO::ShaderDataType::Float4:
			return 4*4;
			break;
		case YOTO::ShaderDataType::Mat3:
			return 4*3*3;
			break;
		case YOTO::ShaderDataType::Mat4:
			return 4*4*4;
			break;
		case YOTO::ShaderDataType::Int:
			return 4;
			break;
		case YOTO::ShaderDataType::Int2:
			return 4*2;
			break;
		case YOTO::ShaderDataType::Int3:
			return 4*3;
			break;
		case YOTO::ShaderDataType::Int4:
			return 4*4;
			break;
		case YOTO::ShaderDataType::Bool:
			return 1;
			break;
		}
		YT_CORE_ASSERT(false, "未知的ShaderDataType!");
		return 0;
	}
	struct BufferElement {
		std::string Name;
		ShaderDataType Type;
		uint32_t Size;
		uint32_t Offset;
		bool Normalized;
		BufferElement(){}
		BufferElement(ShaderDataType type, const std::string& name,bool normalized=false)
			:Name(name), Type(type), Size(ShaderDataTypeSize(type)), Offset(0), Normalized(normalized){}
		uint32_t GetComponentCount() const{
			switch (Type)
			{
			case YOTO::ShaderDataType::Float:
				return 1;
				break;
			case YOTO::ShaderDataType::Float2:
				return 2;
				break;
			case YOTO::ShaderDataType::Float3:
				return 3;
				break;
			case YOTO::ShaderDataType::Float4:
				return 4;
				break;
			case YOTO::ShaderDataType::Mat3:
				return 3*3;
				break;
			case YOTO::ShaderDataType::Mat4:
				return 4*4;
				break;
			case YOTO::ShaderDataType::Int:
				return 1;
				break;
			case YOTO::ShaderDataType::Int2:
				return 2;
				break;
			case YOTO::ShaderDataType::Int3:
				return 3;
				break;
			case YOTO::ShaderDataType::Int4:
				return 4;
				break;
			case YOTO::ShaderDataType::Bool:
				return 1;
				break;
			default:
				break;
			}
			YT_CORE_ASSERT(false, "未知的ShaderDataType!");
			return 0;
		}
	};
	class BufferLayout {
	public:
		BufferLayout(){}
		BufferLayout(const std::initializer_list<BufferElement>elements)
			:m_Elements(elements) 
		{
			CalculateOffsetAndStride();
		} 
		inline uint32_t GetStride()const { return m_Stride; }
		inline const std::vector<BufferElement>& GetElements()const {
			return m_Elements;
		}
		std::vector<BufferElement>::iterator begin() { return m_Elements.begin(); }
		std::vector<BufferElement>::iterator end() { return m_Elements.end(); }
		std::vector<BufferElement>::const_iterator begin() const { return m_Elements.begin(); }
		std::vector<BufferElement>::const_iterator end() const { return m_Elements.end(); }
	private:
		void CalculateOffsetAndStride() {
			uint32_t offset = 0;
			m_Stride = 0;
			for (auto& element : m_Elements) {
				element.Offset = offset;
				offset += element.Size;
				m_Stride += element.Size;
			}
		}
	private:
		std::vector<BufferElement> m_Elements;
		uint32_t m_Stride = 0;
	};
	class VertexBuffer {
	public:
		virtual~VertexBuffer() {}

		virtual void Bind() const = 0;
		virtual void UnBind() const = 0;
		virtual void SetLayout(const BufferLayout& layout) = 0;
		virtual const BufferLayout& GetLayout()const = 0;
		static  VertexBuffer* Create(float* vertices, uint32_t size);
	};
	class IndexBuffer {
	public:
		virtual~IndexBuffer(){}
		virtual void Bind() const = 0;
		virtual void UnBind() const = 0;
		virtual uint32_t GetCount() const = 0;
		static  IndexBuffer* Create(uint32_t* indices, uint32_t size);
	};
}

buffer.cpp:

cpp 复制代码
#include"ytpch.h"
#include"Buffer.h"
#include "Renderer.h"

#include "Platform/OpenGL/OpenGLBuffer.h"

namespace YOTO {
	VertexBuffer* VertexBuffer::Create(float* vertices, uint32_t size)
	{
		switch (Renderer::GetAPI())
		{
		case RendererAPI::None:
			YT_CORE_ASSERT(false,"Buffer:API为None不支持");
			return nullptr;
		case RendererAPI::OpenGL:
			return new OpenGLVertexBuffer(vertices,size);
		}
		YT_CORE_ASSERT(false,"Buffer:未知API");
		return nullptr;
	}
	IndexBuffer* IndexBuffer::Create(uint32_t* indices, uint32_t size)
	{
		switch (Renderer::GetAPI())
		{
		case RendererAPI::None:
			YT_CORE_ASSERT(false, "Buffer:API为None不支持");
			return nullptr;
		case RendererAPI::OpenGL:
			return new OpenGLIndexBuffer(indices, size);
		}
		YT_CORE_ASSERT(false, "Buffer:未知API");
		return nullptr;
	}
}

VertexArray.h:

cpp 复制代码
#pragma once
#include"YOTO/Renderer/Buffer.h"
namespace YOTO {
	
	class VertexArray {
	public:
		virtual~VertexArray() {}

		virtual void Bind() const = 0;
		virtual void UnBind() const = 0;
		virtual void AddVertexBuffer(const std::shared_ptr<VertexBuffer>&vertexBuffer) = 0;
		virtual void AddIndexBuffer(const std::shared_ptr<IndexBuffer>& indexBuffer) = 0;
		virtual const  std::vector<std::shared_ptr<VertexBuffer>>& GetVertexBuffers()const = 0;
		virtual const std::shared_ptr<IndexBuffer>& GetIndexBuffer()const = 0;
		static  VertexArray* Create();
	};
}

VertexArray.cpp:

cpp 复制代码
#include "ytpch.h"
#include "VertexArray.h"
#include"Renderer.h"
#include "Platform/OpenGL/OpenGLVertexArray.h"
namespace YOTO {
	VertexArray* VertexArray::Create()
	{
		switch (Renderer::GetAPI())
		{
		case RendererAPI::None:
			YT_CORE_ASSERT(false, "Buffer:API为None不支持");
			return nullptr;
		case RendererAPI::OpenGL:
			return new OpenGLVertexArray();
		}
		YT_CORE_ASSERT(false, "Buffer:未知API");
		return nullptr;
	}
}

实现:

OpenGLBuffer.h:

cpp 复制代码
#pragma once
#include "YOTO/Renderer/Buffer.h"
namespace YOTO {
	class OpenGLVertexBuffer : public VertexBuffer {
	public:
		OpenGLVertexBuffer(float* vertices, uint32_t size);
		virtual~OpenGLVertexBuffer();
		virtual void Bind()const override;
		virtual void UnBind()const override;

		virtual void SetLayout(const BufferLayout& layout)  override {
			m_Layout = layout;
		}
		virtual const BufferLayout& GetLayout()const override {
			return m_Layout;
		}
	private: 
		uint32_t m_RendererID;
		BufferLayout m_Layout;
	};

	class OpenGLIndexBuffer : public IndexBuffer {
	public:
		OpenGLIndexBuffer(uint32_t* indices, uint32_t count);
		virtual~OpenGLIndexBuffer();
		virtual void Bind()const;
		virtual void UnBind()const;
		virtual uint32_t GetCount() const {
			return m_Count;
		};
	private:
		uint32_t m_RendererID;
		uint32_t m_Count;
	};
}

OpenGLBuffer.cpp:

cpp 复制代码
#include"ytpch.h"
#include"OpenGLBuffer.h"
#include <glad/glad.h>
namespace YOTO {

// VertexBuffer 

	OpenGLVertexBuffer::OpenGLVertexBuffer(float* vertices, uint32_t size)
	{	 
		
		glCreateBuffers(1, &m_RendererID);
		glBindBuffer(GL_ARRAY_BUFFER, m_RendererID);
		glBufferData(GL_ARRAY_BUFFER, size, vertices, GL_STATIC_DRAW);
	}
	OpenGLVertexBuffer::~OpenGLVertexBuffer()
	{ 
		glDeleteBuffers(1, &m_RendererID);
	}
	void OpenGLVertexBuffer::Bind() const
	{
		glBindBuffer(GL_ARRAY_BUFFER, m_RendererID);
	}
	void OpenGLVertexBuffer::UnBind() const
	{
		glBindBuffer(GL_ARRAY_BUFFER, 0);
	}

	
	// IndexBuffer /
	
	OpenGLIndexBuffer::OpenGLIndexBuffer(uint32_t* indices, uint32_t count)
		:m_Count(count)
	{
		glCreateBuffers(1, &m_RendererID);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_RendererID);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, count*sizeof(uint32_t), indices, GL_STATIC_DRAW);
	}
	OpenGLIndexBuffer::~OpenGLIndexBuffer()
	{
		glDeleteBuffers(1, &m_RendererID);
	}
	void OpenGLIndexBuffer::Bind() const
	{
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_RendererID);
	}
	void OpenGLIndexBuffer::UnBind() const
	{
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	}
}

OpenGLVertexArray.h:

cpp 复制代码
#pragma once
#include"YOTO/Renderer/VertexArray.h"
namespace YOTO {
	class OpenGLVertexArray:public VertexArray
	{
	public :
		OpenGLVertexArray();
		virtual ~OpenGLVertexArray();
		virtual void Bind() const override;
		virtual void UnBind() const override;

		virtual void AddVertexBuffer(const std::shared_ptr<VertexBuffer>& vertexBuffer)override;
		virtual void AddIndexBuffer(const std::shared_ptr<IndexBuffer>& indexBuffer)override;

		virtual const std::vector<std::shared_ptr<VertexBuffer>>& GetVertexBuffers()const {
			return m_VertexBuffers;
		}
		virtual const std::shared_ptr<IndexBuffer>& GetIndexBuffer()const {
			return m_IndexBuffers;
		}
	private:
		uint32_t m_RendererID;
		std::vector<std::shared_ptr<VertexBuffer>> m_VertexBuffers;
		std::shared_ptr<IndexBuffer> m_IndexBuffers;
	};

}

OpenGLVertexArray.cpp:

cpp 复制代码
#include "ytpch.h"
#include "OpenGLVertexArray.h"
#include <glad/glad.h>
namespace YOTO {
	static GLenum ShaderDataTypeToOpenGLBaseType(ShaderDataType type) {
		switch (type)
		{
		case YOTO::ShaderDataType::Float:
			return GL_FLOAT;
		case YOTO::ShaderDataType::Float2:
			return GL_FLOAT;
		case YOTO::ShaderDataType::Float3:
			return GL_FLOAT;
		case YOTO::ShaderDataType::Float4:
			return GL_FLOAT;
		case YOTO::ShaderDataType::Mat3:
			return GL_FLOAT;
		case YOTO::ShaderDataType::Mat4:
			return GL_FLOAT;
		case YOTO::ShaderDataType::Int:
			return GL_INT;
		case YOTO::ShaderDataType::Int2:
			return GL_INT;
		case YOTO::ShaderDataType::Int3:
			return GL_INT;
		case YOTO::ShaderDataType::Int4:
			return GL_INT;
		case YOTO::ShaderDataType::Bool:
			return GL_BOOL;
		}
		return 0;
	}
	OpenGLVertexArray::OpenGLVertexArray()
	{
		glCreateVertexArrays(1, &m_RendererID);
	}
	OpenGLVertexArray::~OpenGLVertexArray()
	{
		glDeleteVertexArrays(1, &m_RendererID);
	}
	void OpenGLVertexArray::Bind() const
	{
		glBindVertexArray(m_RendererID);
	}
	void OpenGLVertexArray::UnBind() const
	{
		glBindVertexArray(0);
	}
	void OpenGLVertexArray::AddVertexBuffer(const std::shared_ptr<VertexBuffer>& vertexBuffer)
	{
		YT_CORE_ASSERT(vertexBuffer->GetLayout().GetElements().size(), "OpenGLVertexArray:VertexBuffer没有布局(Layout)")
		glBindVertexArray(m_RendererID);
		vertexBuffer->Bind();
		
		uint32_t index = 0;
		const auto& layout = vertexBuffer->GetLayout();
		for (const auto& element : layout) {
			glEnableVertexAttribArray(index);
			//设置缓冲区数据格式:缓冲区序号、顶点属性的大小、什么数据类型、会不会被归一化、
			glVertexAttribPointer(index,
				element.GetComponentCount(),
				ShaderDataTypeToOpenGLBaseType(element.Type),
				element.Normalized ? GL_TRUE : GL_FALSE,
				layout.GetStride(),
				(const void*)element.Offset);
			index++;
		}
		m_VertexBuffers.push_back(vertexBuffer);
	}
	void OpenGLVertexArray::AddIndexBuffer(const std::shared_ptr<IndexBuffer>& indexBuffer)
	{
		glBindVertexArray(m_RendererID);
		indexBuffer->Bind();
		m_IndexBuffers = indexBuffer;
	}
}

调用:

Application.h:

cpp 复制代码
#pragma once
#include"Core.h"
#include"Event/Event.h"
#include"Event/ApplicationEvent.h"
#include "YOTO/Window.h"
#include"YOTO/LayerStack.h"
#include"YOTO/ImGui/ImGuiLayer.h"

#include <YOTO/Renderer/Shader.h>
#include <YOTO/Renderer/Buffer.h>
#include <YOTO/Renderer/VertexArray.h>
namespace YOTO {
	class YOTO_API Application
	{
	public:
		Application();
		virtual ~Application();
		void Run();
		void OnEvent(Event &e);
		void PushLayer(Layer* layer);
		void PushOverlay(Layer* layer);

		inline static Application& Get() {return * s_Instance;}
		inline Window& GetWindow() { return *m_Window; }
	private:
		bool  OnWindowClosed(WindowCloseEvent& e);
		std::unique_ptr<Window>  m_Window;
		ImGuiLayer *  m_ImGuiLayer;
		bool m_Running = true;
		LayerStack m_LayerStack;
		
		//unsigned int m_VertexArray;
		std::shared_ptr<Shader> m_Shader;
		std::shared_ptr<VertexArray> m_VertexArray;


		std::shared_ptr<Shader> m_BlueShader;
		std::shared_ptr<VertexArray> m_SquareVA;



		static Application* s_Instance;
	};
	//在客户端定义
	Application* CreateApplication();
}

Application.cpp:

cpp 复制代码
#include"ytpch.h"
#include "Application.h"

#include"Log.h"
#include<glad/glad.h>
#include"Input.h"


namespace YOTO {
#define BIND_EVENT_FN(x) std::bind(&x, this, std::placeholders::_1)

	 Application* Application::s_Instance = nullptr;

	Application::Application() {

		YT_CORE_ASSERT(!s_Instance, "Application需要为空!")
		s_Instance = this;
		//智能指针
		m_Window = std::unique_ptr<Window>(Window::Creat());
		//设置回调函数
		m_Window->SetEventCallback(BIND_EVENT_FN(Application::OnEvent));
		//new一个Layer,放在最后层进行渲染
		m_ImGuiLayer = new ImGuiLayer();
		PushOverlay(m_ImGuiLayer);  
		//unsigned int id;
		//glGenBuffers(1, &id);

		uint32_t indices[3] = { 0,1,2 };
		float vertices[3 * 7] = {
			-0.5f,-0.5f,0.0f, 0.8f,0.2f,0.8f,1.0f,
			0.5f,-0.5f,0.0f,  0.2f,0.3f,0.8f,1.0f,
			0.0f,0.5f,0.0f,   0.8f,0.8f,0.2f,1.0f,
		};
		m_VertexArray.reset(VertexArray::Create());


		//顶点数组:
		//glGenVertexArrays(1, &m_VertexArray);
		//glBindVertexArray(m_VertexArray);
		//顶点缓冲区
		//glGenBuffers(1, &m_VertexBuffer);
		//glBindBuffer(GL_ARRAY_BUFFER,m_VertexBuffer);
		//把数据传送给gpu,GL_STATIC_DRAW不断的用新数据刷新数组。告诉opengl这个缓冲区的数据布局
		//glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
		std::shared_ptr<VertexBuffer> m_VertexBuffer;
		m_VertexBuffer.reset(VertexBuffer::Create(vertices, sizeof(vertices)));

	

		//启用数据的索引0
		//glEnableVertexAttribArray(0);
		//设置缓冲区数据格式:缓冲区序号、顶点属性的大小、什么数据类型、会不会被归一化、
		//glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float),nullptr);
		{
			BufferLayout setlayout = {

	{ShaderDataType::Float3,"a_Position"},
		{ShaderDataType::Float4,"a_Color"}
			};
			m_VertexBuffer->SetLayout(setlayout);

		}

		//uint32_t index = 0;
		//const auto& layout = m_VertexBuffer->GetLayout();
		//for (const auto& element : layout) {
		//	glEnableVertexAttribArray(index);
		//	//设置缓冲区数据格式:缓冲区序号、顶点属性的大小、什么数据类型、会不会被归一化、
		//	glVertexAttribPointer(index,
		//		element.GetComponentCount(), 
		//		ShaderDataTypeToOpenGLBaseType(element.Type), 
		//		element.Normalized?GL_TRUE:GL_FALSE,
		//		layout.GetStride() ,
		//		(const void *)element.Offset);
		//	index++;
		//}
		 
		m_VertexArray->AddVertexBuffer(m_VertexBuffer);
		
		 
		//创建索引缓冲区
		//glGenBuffers(1, &m_IndexBuffer);
		//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_IndexBuffer);
		//设置缓冲区格式
		//glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
		std::shared_ptr<IndexBuffer>m_IndexBuffer;
		m_IndexBuffer.reset(IndexBuffer::Create(indices, sizeof(indices)/sizeof(uint32_t)));

		m_VertexArray->AddIndexBuffer(m_IndexBuffer);
		//着色器
		//顶点布局
		std::string vertexSource = R"(
		#version 330 core
		layout(location = 0) in vec3 a_Position;
		layout(location = 1) in vec4 a_Color;
		out vec3 v_Position;
		out vec4 v_Color;
		void main(){
		v_Position=a_Position;
		v_Color=a_Color;
		gl_Position =vec4( a_Position+0.5,1.0);
		}
		)";
		//绘制颜色
		std::string fragmentSource = R"(
		#version 330 core
		layout(location = 0) out vec4 color;
		in vec3 v_Position;
		in vec4 v_Color;
		void main(){
		color=vec4(v_Color);
		}
		)";
		m_Shader.reset(new Shader(vertexSource, fragmentSource));

		///测试/

		m_SquareVA.reset(VertexArray::Create());

		float squareVertices[3 * 4] = {
			-0.5f,-0.5f,0.0f,
			0.5f,-0.5f,0.0f, 
			0.5f,0.5f,0.0f,
			-0.5f,0.5f,0.0f
		};
		std::shared_ptr<VertexBuffer> squareVB;
		squareVB.reset(VertexBuffer::Create(squareVertices, sizeof(squareVertices)));
		squareVB->SetLayout({
			{ShaderDataType::Float3,"a_Position"}
		});
		m_SquareVA->AddVertexBuffer(squareVB);
		uint32_t squareIndices[6] = { 0,1,2,2,3,0 };
		std::shared_ptr<IndexBuffer> squareIB; 

		squareIB.reset((IndexBuffer::Create(squareIndices, sizeof(squareIndices) / sizeof(uint32_t))));

		m_SquareVA->AddIndexBuffer(squareIB);
		

		//测试:
		std::string BlueShaderVertexSource = R"(
		#version 330 core
		layout(location = 0) in vec3 a_Position;
		out vec3 v_Position;
		void main(){
		v_Position=a_Position;
		gl_Position =vec4( a_Position,1.0);
		}
		)";
		//绘制颜色
		std::string BlueShaderFragmentSource = R"(
		#version 330 core
		layout(location = 0) out vec4 color;
		in vec3 v_Position;
		void main(){
		color=vec4(0.2,0.3,0.8,1.0);
		}
		)";
		m_BlueShader.reset(new Shader(BlueShaderVertexSource, BlueShaderFragmentSource));
		
		//shader
	}
	Application::~Application() {

	}
	/// <summary>
	/// 所有的Window事件都会在这触发,作为参数e
	/// </summary>
	/// <param name="e"></param>
	void Application::OnEvent(Event& e) {
		//根据事件类型绑定对应事件
		EventDispatcher dispatcher(e);
		dispatcher.Dispatch<WindowCloseEvent>(BIND_EVENT_FN(Application::OnWindowClosed));
		//输出事件信息
		YT_CORE_INFO("Application:{0}",e);
		for (auto it = m_LayerStack.end(); it != m_LayerStack.begin();) {
			(*--it)->OnEvent(e);
			if (e.m_Handled)
				break;
		}
	}

	bool Application::OnWindowClosed(WindowCloseEvent& e) {
		m_Running = false;
		return true;
	}
	void Application::Run() {
		WindowResizeEvent e(1280, 720);
		if (e.IsInCategory(EventCategoryApplication)) {
			YT_CORE_TRACE(e);
		}
		if (e.IsInCategory(EventCategoryInput)) {
			YT_CORE_ERROR(e);
		}

		while (m_Running)
		{
			glClearColor(0.2f, 0.2f, 0.2f,1);
			glClear(GL_COLOR_BUFFER_BIT);
			
			m_BlueShader->Bind();
			m_SquareVA->Bind();
			glDrawElements(GL_TRIANGLES, m_SquareVA->GetIndexBuffer()->GetCount(), GL_UNSIGNED_INT, nullptr);
			//glBindVertexArray(m_VertexArray);
			m_Shader->Bind();
			m_VertexArray->Bind();
			glDrawElements(GL_TRIANGLES,m_VertexArray->GetIndexBuffer()->GetCount(),GL_UNSIGNED_INT,nullptr); 

			for (Layer* layer : m_LayerStack) {
				layer->OnUpdate();
			}
			//将ImGui的刷新放到APP中,与Update分开
			m_ImGuiLayer->Begin();
			
			for (Layer* layer : m_LayerStack) {
				layer->OnImGuiRender();
			}
			m_ImGuiLayer->End();
			m_Window->OnUpdate();
		}
	}
	void Application::PushLayer(Layer* layer) {
		m_LayerStack.PushLayer(layer);
		layer->OnAttach();
	}
	void Application::PushOverlay(Layer* layer) {
		m_LayerStack.PushOverlay(layer);
		layer->OnAttach();
	}
}

测试:

shader就不改了,随便一测试画出来个矩形就对了

相关推荐
虾球xz13 小时前
游戏引擎学习第五天
学习·算法·游戏引擎
逐·風15 小时前
Unity编辑器的高级扩展技术
unity·编辑器·游戏引擎
虾球xz16 小时前
游戏引擎学习第六天
学习·游戏引擎
霸王•吕布19 小时前
游戏引擎中LOD渲染技术
游戏引擎·lod·高低模渲染·blender制作lod模型·lod层级·lod原理
夜梦说开发(VR)1 天前
【Unity】Game Framework框架学习使用
学习·unity·游戏引擎
虾球xz2 天前
游戏引擎学习第四天
单片机·学习·游戏引擎
虾球xz2 天前
游戏引擎学习第三天
学习·游戏引擎
虾球xz2 天前
游戏引擎学习第一天
学习·游戏引擎
虾球xz2 天前
游戏引擎学习第二天
学习·游戏引擎
小白要加油哈2 天前
Mesh网格
学习·unity·游戏引擎