当前位置:首页 » 《随便一记》 » 正文

<2021SC@SDUSC>开源游戏引擎Overload代码分析七:OvEditor——Panels(Asset剩余)_chenxiang_200108的博客

22 人参与  2022年05月19日 16:49  分类 : 《随便一记》  评论

点击全文阅读


2021SC@SDUSC

开源游戏引擎Overload代码分析七:OvEditor——Asset剩余

  • 前言
  • 一、AssetView
    • 1.AssetView.h
    • 2.AssetView.cpp
      • 构造函数
    • _Render_Impl()
      • SetResource()与GetResource()
  • 二、AssetProperties
    • 1.AssetProperties.h
    • 2.AssetProperties.cpp
  • 总结

前言

这是Overload引擎相关的第九篇文章,同时也是OvEditor分析的第四篇。Overload引擎的Github主页在这里。

本篇文章将会介绍OvEditor的Panels文件夹中剩余的与Asset相关的一部分文件,具体应该会涉及AssetProperties和AssetView。

一、AssetView

1.AssetView.h

	/**
	* Provide a view for assets
	*/
	class AssetView : public OvEditor::Panels::AViewControllable
	{
	public:
		using ViewableResource = std::variant<OvRendering::Resources::Model*, OvRendering::Resources::Texture*, OvCore::Resources::Material*>;

		/**
		* Constructor
		* @param p_title
		* @param p_opened
		* @param p_windowSettings
		*/
		AssetView
		(
			const std::string& p_title,
			bool p_opened,
			const OvUI::Settings::PanelWindowSettings& p_windowSettings
		);

		/**
		* Custom implementation of the render method
		*/
		virtual void _Render_Impl() override;

		/**
		* Defines the resource to preview
		* @parma p_resource
		*/
		void SetResource(ViewableResource p_resource);

		/**
		* Return the currently previewed resource
		*/
		ViewableResource GetResource() const;

	private:
		ViewableResource m_resource;
	};

定义了类,用于给资源提供视图,具体函数实现在cpp文件中讲述。

2.AssetView.cpp

按代码编写顺序介绍:

构造函数

OvEditor::Panels::AssetView::AssetView
(
	const std::string& p_title,
	bool p_opened,
	const OvUI::Settings::PanelWindowSettings& p_windowSettings
) : AViewControllable(p_title, p_opened, p_windowSettings)
{
	m_camera.SetClearColor({ 0.098f, 0.098f, 0.098f });
	m_camera.SetFar(5000.0f);

	m_resource = static_cast<OvRendering::Resources::Model*>(nullptr);
	m_image->AddPlugin<OvUI::Plugins::DDTarget<std::pair<std::string, OvUI::Widgets::Layout::Group*>>>("File").DataReceivedEvent += [this](auto p_data)
	{
		std::string path = p_data.first;

		switch (OvTools::Utils::PathParser::GetFileType(path))
		{
		case OvTools::Utils::PathParser::EFileType::MODEL:
			if (auto resource = OvCore::Global::ServiceLocator::Get<OvCore::ResourceManagement::ModelManager>().GetResource(path); resource)
				m_resource = resource;
			break;
		case OvTools::Utils::PathParser::EFileType::TEXTURE:
			if (auto resource = OvCore::Global::ServiceLocator::Get<OvCore::ResourceManagement::TextureManager>().GetResource(path); resource)
				m_resource = resource;
			break;

		case OvTools::Utils::PathParser::EFileType::MATERIAL:
			if (auto resource = OvCore::Global::ServiceLocator::Get<OvCore::ResourceManagement::MaterialManager>().GetResource(path); resource)
				m_resource = resource;
			break;
		}
	};
}

首先两行和camera有关的代码给定了清屏颜色和远裁剪面。之后绑定图片与资源,分别为模型,纹理和材质三个部分。

_Render_Impl()

void OvEditor::Panels::AssetView::_Render_Impl()
{
	PrepareCamera();

	auto& baseRenderer = *EDITOR_CONTEXT(renderer).get();

	m_fbo.Bind();

	baseRenderer.SetStencilMask(0xFF);
	baseRenderer.Clear(m_camera);
	baseRenderer.SetStencilMask(0x00);

	uint8_t glState = baseRenderer.FetchGLState();
	baseRenderer.ApplyStateMask(glState);

	m_editorRenderer.RenderGrid(m_cameraPosition, m_gridColor);

	if (auto pval = std::get_if<OvRendering::Resources::Model*>(&m_resource); pval && *pval)
		m_editorRenderer.RenderModelAsset(**pval);
	
	if (auto pval = std::get_if<OvRendering::Resources::Texture*>(&m_resource); pval && *pval)
		m_editorRenderer.RenderTextureAsset(**pval);
	
	if (auto pval = std::get_if<OvCore::Resources::Material*>(&m_resource); pval && *pval)
		m_editorRenderer.RenderMaterialAsset(**pval);

	baseRenderer.ApplyStateMask(glState);

	m_fbo.Unbind();
}

此处定义了增添的渲染方法。首先准备了摄像机,具体是设定了视窗大小和摄像机的观察矩阵。之后获得基础的渲染器,并绑定渲染对象FrameBufferObject。然后对渲染器设置了模板遮罩,实际是改变了写入模板缓冲的方式。清空摄像机后重新设定写入模板缓冲方式。之后得到openGL的状态,并且可以进行更改。再之后渲染了网格,以及模型,纹理和材质,最后更新状态并解绑渲染对象。

SetResource()与GetResource()

void OvEditor::Panels::AssetView::SetResource(ViewableResource p_resource)
{
	m_resource = p_resource;
}

OvEditor::Panels::AssetView::ViewableResource OvEditor::Panels::AssetView::GetResource() const
{
	return m_resource;
}

显而易见的,设定资源和返回资源的函数。

二、AssetProperties

1.AssetProperties.h

	class AssetProperties : public OvUI::Panels::PanelWindow
	{
	public:
		using EditableAssets = std::variant<OvRendering::Resources::Model*, OvRendering::Resources::Texture*>;

		/**
		* Constructor
		* @param p_title
		* @param p_opened
		* @param p_windowSettings
		*/
		AssetProperties
		(
			const std::string& p_title,
			bool p_opened,
			const OvUI::Settings::PanelWindowSettings& p_windowSettings
		);

		/**
		* Defines the target of the asset settings editor
		* @param p_path
		*/
		void SetTarget(const std::string& p_path);

        /**
        * Refresh the panel to show the current target settings
        */
        void Refresh();

		/**
		* Launch the preview of the target asset
		*/
		void Preview();

	private:
		void CreateHeaderButtons();
        void CreateAssetSelector();
		void CreateSettings();
		void CreateInfo();
		void CreateModelSettings();
		void CreateTextureSettings();
		void Apply();

	private:
		std::string m_resource;

        OvTools::Eventing::Event<> m_targetChanged;
        OvUI::Widgets::Layout::Group* m_settings = nullptr;
        OvUI::Widgets::Layout::Group* m_info = nullptr;
        OvUI::Widgets::Buttons::Button* m_applyButton = nullptr;
        OvUI::Widgets::Buttons::Button* m_revertButton = nullptr;
        OvUI::Widgets::Buttons::Button* m_previewButton = nullptr;
        OvUI::Widgets::Buttons::Button* m_resetButton = nullptr;
        OvUI::Widgets::AWidget* m_headerSeparator = nullptr;
        OvUI::Widgets::AWidget* m_headerLineBreak = nullptr;
		OvUI::Widgets::Layout::Columns<2>* m_settingsColumns = nullptr;
		OvUI::Widgets::Layout::Columns<2>* m_infoColumns = nullptr;
        OvUI::Widgets::Texts::Text* m_assetSelector = nullptr;
		std::unique_ptr<OvTools::Filesystem::IniFile> m_metadata;
	};

定义了类,有不少变量和函数,在讨论下面的cpp文件时讲解。

2.AssetProperties.cpp

按代码逻辑顺序讲解,可能要上下翻阅:

OvEditor::Panels::AssetProperties::AssetProperties
(
	const std::string& p_title,
	bool p_opened,
	const OvUI::Settings::PanelWindowSettings& p_windowSettings
) :
	PanelWindow(p_title, p_opened, p_windowSettings)
{
    m_targetChanged += [this]() { SetTarget(m_assetSelector->content); };

	CreateHeaderButtons();

    m_headerSeparator = &CreateWidget<OvUI::Widgets::Visual::Separator>();
    m_headerSeparator->enabled = false;

    CreateAssetSelector();

    m_settings = &CreateWidget<OvUI::Widgets::Layout::GroupCollapsable>("Settings");
	m_settingsColumns = &m_settings->CreateWidget<OvUI::Widgets::Layout::Columns<2>>();
	m_settingsColumns->widths[0] = 150;

    m_info = &CreateWidget<OvUI::Widgets::Layout::GroupCollapsable>("Info");
    m_infoColumns = &m_info->CreateWidget<OvUI::Widgets::Layout::Columns<2>>();
    m_infoColumns->widths[0] = 150;

    m_settings->enabled = m_info->enabled = false;
}

除了对继承的进行赋值外,首先有SetTarget():

void OvEditor::Panels::AssetProperties::SetTarget(const std::string& p_path)
{
	m_resource = p_path == "" ? p_path : EDITOR_EXEC(GetResourcePath(p_path));

    if (m_assetSelector)
    {
        m_assetSelector->content = m_resource;
    }

    Refresh();
}

首先会设置路径,没有就获取路径。之后如果存在m_assetSelector,就把得到的资源路径给它。最后进行Refresh():

void OvEditor::Panels::AssetProperties::Refresh()
{
    m_metadata.reset(new OvTools::Filesystem::IniFile(EDITOR_EXEC(GetRealPath(m_resource)) + ".meta"));

    CreateSettings();
    CreateInfo();

    m_applyButton->enabled = m_settings->enabled;
    m_resetButton->enabled = m_settings->enabled;
    m_revertButton->enabled = m_settings->enabled;

    switch (OvTools::Utils::PathParser::GetFileType(m_resource))
    {
    case OvTools::Utils::PathParser::EFileType::MODEL:
    case OvTools::Utils::PathParser::EFileType::TEXTURE:
    case OvTools::Utils::PathParser::EFileType::MATERIAL:
        m_previewButton->enabled = true;
        break;
    default:
        m_previewButton->enabled = false;
        break;
    }

    // Enables the header separator (And the line break) if at least one button is enabled
    m_headerSeparator->enabled = m_applyButton->enabled || m_resetButton->enabled || m_revertButton->enabled || m_previewButton->enabled;
    m_headerLineBreak->enabled = m_headerSeparator->enabled;
}

这个函数首先会设定m_metadata,是得到m_resource的路径后加上后缀.meta。然后调用了CreateSettings():

void OvEditor::Panels::AssetProperties::CreateSettings()
{
	m_settingsColumns->RemoveAllWidgets();

	const auto fileType = OvTools::Utils::PathParser::GetFileType(m_resource);

    m_settings->enabled = true;

	if (fileType == OvTools::Utils::PathParser::EFileType::MODEL)
	{
		CreateModelSettings();
	}
	else if (fileType == OvTools::Utils::PathParser::EFileType::TEXTURE)
	{
		CreateTextureSettings();
	}
    else
    {
        m_settings->enabled = false;
    }
}

这个函数先移除列的所有Widgets,之后得到m_resource的文件类型,然后把m_settings设为开启。之后看文件类型是模型,纹理还是材质,决定各自的操作。如果是模型,执行CreateModelSettings():

void OvEditor::Panels::AssetProperties::CreateModelSettings()
{
	m_metadata->Add("CALC_TANGENT_SPACE", true);
	m_metadata->Add("JOIN_IDENTICAL_VERTICES", true);
	m_metadata->Add("MAKE_LEFT_HANDED", false);
	m_metadata->Add("TRIANGULATE", true);
	m_metadata->Add("REMOVE_COMPONENT", false);
	m_metadata->Add("GEN_NORMALS", false);
	m_metadata->Add("GEN_SMOOTH_NORMALS", true);
	m_metadata->Add("SPLIT_LARGE_MESHES", false);
	m_metadata->Add("PRE_TRANSFORM_VERTICES", true);
	m_metadata->Add("LIMIT_BONE_WEIGHTS", false);
	m_metadata->Add("VALIDATE_DATA_STRUCTURE", false);
	m_metadata->Add("IMPROVE_CACHE_LOCALITY", true);
	m_metadata->Add("REMOVE_REDUNDANT_MATERIALS", false);
	m_metadata->Add("FIX_INFACING_NORMALS", false);
	m_metadata->Add("SORT_BY_PTYPE", false);
	m_metadata->Add("FIND_DEGENERATES", false);
	m_metadata->Add("FIND_INVALID_DATA", true);
	m_metadata->Add("GEN_UV_COORDS", true);
	m_metadata->Add("TRANSFORM_UV_COORDS", false);
	m_metadata->Add("FIND_INSTANCES", true);
	m_metadata->Add("OPTIMIZE_MESHES", true);
	m_metadata->Add("OPTIMIZE_GRAPH", true);
	m_metadata->Add("FLIP_UVS", false);
	m_metadata->Add("FLIP_WINDING_ORDER", false);
	m_metadata->Add("SPLIT_BY_BONE_COUNT", false);
	m_metadata->Add("DEBONE", true);
	m_metadata->Add("GLOBAL_SCALE", true);
	m_metadata->Add("EMBED_TEXTURES", false);
	m_metadata->Add("FORCE_GEN_NORMALS", false);
	m_metadata->Add("DROP_NORMALS", false);
	m_metadata->Add("GEN_BOUNDING_BOXES", false);

	MODEL_FLAG_ENTRY("CALC_TANGENT_SPACE");
	MODEL_FLAG_ENTRY("JOIN_IDENTICAL_VERTICES");
	MODEL_FLAG_ENTRY("MAKE_LEFT_HANDED");
	MODEL_FLAG_ENTRY("TRIANGULATE");
	MODEL_FLAG_ENTRY("REMOVE_COMPONENT");
	MODEL_FLAG_ENTRY("GEN_NORMALS");
	MODEL_FLAG_ENTRY("GEN_SMOOTH_NORMALS");
	MODEL_FLAG_ENTRY("SPLIT_LARGE_MESHES");
	MODEL_FLAG_ENTRY("PRE_TRANSFORM_VERTICES");
	MODEL_FLAG_ENTRY("LIMIT_BONE_WEIGHTS");
	MODEL_FLAG_ENTRY("VALIDATE_DATA_STRUCTURE");
	MODEL_FLAG_ENTRY("IMPROVE_CACHE_LOCALITY");
	MODEL_FLAG_ENTRY("REMOVE_REDUNDANT_MATERIALS");
	MODEL_FLAG_ENTRY("FIX_INFACING_NORMALS");
	MODEL_FLAG_ENTRY("SORT_BY_PTYPE");
	MODEL_FLAG_ENTRY("FIND_DEGENERATES");
	MODEL_FLAG_ENTRY("FIND_INVALID_DATA");
	MODEL_FLAG_ENTRY("GEN_UV_COORDS");
	MODEL_FLAG_ENTRY("TRANSFORM_UV_COORDS");
	MODEL_FLAG_ENTRY("FIND_INSTANCES");
	MODEL_FLAG_ENTRY("OPTIMIZE_MESHES");
	MODEL_FLAG_ENTRY("OPTIMIZE_GRAPH");
	MODEL_FLAG_ENTRY("FLIP_UVS");
	MODEL_FLAG_ENTRY("FLIP_WINDING_ORDER");
	MODEL_FLAG_ENTRY("SPLIT_BY_BONE_COUNT");
	MODEL_FLAG_ENTRY("DEBONE");
	MODEL_FLAG_ENTRY("GLOBAL_SCALE");
	MODEL_FLAG_ENTRY("EMBED_TEXTURES");
	MODEL_FLAG_ENTRY("FORCE_GEN_NORMALS");
	MODEL_FLAG_ENTRY("DROP_NORMALS");
	MODEL_FLAG_ENTRY("GEN_BOUNDING_BOXES");
};

明显的增添属性和赋值。

如果是纹理,执行CreateTextureSettings():

void OvEditor::Panels::AssetProperties::CreateTextureSettings()
{
	m_metadata->Add("MIN_FILTER", static_cast<int>(OvRendering::Settings::ETextureFilteringMode::LINEAR_MIPMAP_LINEAR));
	m_metadata->Add("MAG_FILTER", static_cast<int>(OvRendering::Settings::ETextureFilteringMode::LINEAR));
	m_metadata->Add("ENABLE_MIPMAPPING", true);

    std::map<int, std::string> filteringModes
    {
        {0x2600, "NEAREST"},
        {0x2601, "LINEAR"},
        {0x2700, "NEAREST_MIPMAP_NEAREST"},
        {0x2703, "LINEAR_MIPMAP_LINEAR"},
        {0x2701, "LINEAR_MIPMAP_NEAREST"},
        {0x2702, "NEAREST_MIPMAP_LINEAR"}
    };

	OvCore::Helpers::GUIDrawer::CreateTitle(*m_settingsColumns, "MIN_FILTER");
	auto& minFilter = m_settingsColumns->CreateWidget<OvUI::Widgets::Selection::ComboBox>(m_metadata->Get<int>("MIN_FILTER"));
	minFilter.choices = filteringModes;
	minFilter.ValueChangedEvent += [this](int p_choice)
	{
		m_metadata->Set("MIN_FILTER", p_choice);
	};

	OvCore::Helpers::GUIDrawer::CreateTitle(*m_settingsColumns, "MAG_FILTER");
	auto& magFilter = m_settingsColumns->CreateWidget<OvUI::Widgets::Selection::ComboBox>(m_metadata->Get<int>("MAG_FILTER"));
	magFilter.choices = filteringModes;
	magFilter.ValueChangedEvent += [this](int p_choice)
	{
		m_metadata->Set("MAG_FILTER", p_choice);
	};

	OvCore::Helpers::GUIDrawer::DrawBoolean(*m_settingsColumns, "ENABLE_MIPMAPPING", [&]() { return m_metadata->Get<bool>("ENABLE_MIPMAPPING"); }, [&](bool value) { m_metadata->Set<bool>("ENABLE_MIPMAPPING", value); });
}

其实也是一些属性的设置。
此处的DrawBoolean为:

#define MODEL_FLAG_ENTRY(setting) OvCore::Helpers::GUIDrawer::DrawBoolean(*m_settingsColumns, setting, [&]() { return m_metadata->Get<bool>(setting); }, [&](bool value) { m_metadata->Set<bool>(setting, value); })

接下来,如果是材质,则把m_settings设为关闭。

之后看CreateInfo():

void OvEditor::Panels::AssetProperties::CreateInfo()
{
    const auto realPath = EDITOR_EXEC(GetRealPath(m_resource));

    m_infoColumns->RemoveAllWidgets();

    if (std::filesystem::exists(realPath))
    {
        m_info->enabled = true;

        OvCore::Helpers::GUIDrawer::CreateTitle(*m_infoColumns, "Path");
        m_infoColumns->CreateWidget<OvUI::Widgets::Texts::Text>(realPath);

        OvCore::Helpers::GUIDrawer::CreateTitle(*m_infoColumns, "Size");
        const auto [size, unit] = OvTools::Utils::SizeConverter::ConvertToOptimalUnit(static_cast<float>(std::filesystem::file_size(realPath)), OvTools::Utils::SizeConverter::ESizeUnit::BYTE);
        m_infoColumns->CreateWidget<OvUI::Widgets::Texts::Text>(std::to_string(size) + " " + OvTools::Utils::SizeConverter::UnitToString(unit));

        OvCore::Helpers::GUIDrawer::CreateTitle(*m_infoColumns, "Metadata");
        m_infoColumns->CreateWidget<OvUI::Widgets::Texts::Text>(std::filesystem::exists(realPath + ".meta") ? "Yes" : "No");
    }
    else
    {
        m_info->enabled = false;
    }
}

获得路径并移除Widgets,如果路径存在,那么开启m_info,设置一些属性并创建Widget,否则关闭m_info。

之后如果m_settings被开启,那么就把按钮也开启,确认m_resource的路径类型,不属于模型,纹理,材质中的一个就关闭m_previewButton,否则开启。最后看有没有一个按钮是开启的,有就启用m_headerLineBreak。

之后继续构造函数,CreateHeaderButtons():

void OvEditor::Panels::AssetProperties::CreateHeaderButtons()
{
	m_applyButton = &CreateWidget<OvUI::Widgets::Buttons::Button>("Apply");
    m_applyButton->idleBackgroundColor = { 0.0f, 0.5f, 0.0f };
    m_applyButton->enabled = false;
    m_applyButton->lineBreak = false;
    m_applyButton->ClickedEvent += std::bind(&AssetProperties::Apply, this);

	m_revertButton = &CreateWidget<OvUI::Widgets::Buttons::Button>("Revert");
	m_revertButton->idleBackgroundColor = { 0.7f, 0.5f, 0.0f };
    m_revertButton->enabled = false;
    m_revertButton->lineBreak = false;
    m_revertButton->ClickedEvent += std::bind(&AssetProperties::SetTarget, this, m_resource);

	m_previewButton = &CreateWidget<OvUI::Widgets::Buttons::Button>("Preview");
	m_previewButton->idleBackgroundColor = { 0.7f, 0.5f, 0.0f };
    m_previewButton->enabled = false;
	m_previewButton->lineBreak = false;
	m_previewButton->ClickedEvent += std::bind(&AssetProperties::Preview, this);

	m_resetButton = &CreateWidget<OvUI::Widgets::Buttons::Button>("Reset to default");
	m_resetButton->idleBackgroundColor = { 0.5f, 0.0f, 0.0f };
    m_resetButton->enabled = false;
    m_resetButton->lineBreak = false;
	m_resetButton->ClickedEvent += [this]
	{
		m_metadata->RemoveAll();
		CreateSettings();
	};

    m_headerLineBreak = &CreateWidget<OvUI::Widgets::Layout::NewLine>();
    m_headerLineBreak->enabled = false;
}

创建各种按钮并且赋初值,给定按钮的颜色和功能,比如m_applyButton是绿色,并且点击后会调用Apply函数。同时定义了m_headerLineBreak 。

Apply函数为:

void OvEditor::Panels::AssetProperties::Apply()
{
	m_metadata->Rewrite();

	const auto resourcePath = EDITOR_EXEC(GetResourcePath(m_resource));
	const auto fileType = OvTools::Utils::PathParser::GetFileType(m_resource);

	if (fileType == OvTools::Utils::PathParser::EFileType::MODEL)
	{
		auto& modelManager = OVSERVICE(OvCore::ResourceManagement::ModelManager);
		if (modelManager.IsResourceRegistered(resourcePath))
		{
			modelManager.AResourceManager::ReloadResource(resourcePath);
		}
	}
	else if (fileType == OvTools::Utils::PathParser::EFileType::TEXTURE)
	{
		auto& textureManager = OVSERVICE(OvCore::ResourceManagement::TextureManager);
		if (textureManager.IsResourceRegistered(resourcePath))
		{
			textureManager.AResourceManager::ReloadResource(resourcePath);
		}
	}

    Refresh();
}

用当前参数重写了整个m_metadata对应文件,然后获得资源路径和类型,如果是模型和纹理,如果此资源存在,那么会按当前路径重新载入,最后Refresh()。

m_previewButton调用的Preview函数为:

void OvEditor::Panels::AssetProperties::Preview()
{
	auto& assetView = EDITOR_PANEL(OvEditor::Panels::AssetView, "Asset View");

	const auto fileType = OvTools::Utils::PathParser::GetFileType(m_resource);

	if (fileType == OvTools::Utils::PathParser::EFileType::MODEL)
	{
		if (auto resource = OVSERVICE(OvCore::ResourceManagement::ModelManager).GetResource(m_resource))
		{
			assetView.SetResource(resource);
		}
	}
	else if (fileType == OvTools::Utils::PathParser::EFileType::TEXTURE)
	{
		if (auto resource = OVSERVICE(OvCore::ResourceManagement::TextureManager).GetResource(m_resource))
		{
			assetView.SetResource(resource);
		}
	}

	assetView.Open();
}

顾名思义,用于预览。如果文件类型是模型和纹理,那么把资源视图面板设置为对应文件的资源,最后打开资源视图。

构造函数之后定义了m_headerSeparator,又调用CreateAssetSelector():

void OvEditor::Panels::AssetProperties::CreateAssetSelector()
{
    auto& columns = CreateWidget<OvUI::Widgets::Layout::Columns<2>>();
    columns.widths[0] = 150;
    m_assetSelector = &OvCore::Helpers::GUIDrawer::DrawAsset(columns, "Target", m_resource, &m_targetChanged);
}

这个函数用于创建资源选择器,定义了宽度,并且会绘制资源的显示。

构造函数最后调用各自的构造函数创建了m_settings,m_settingsColumns,m_info,m_infoColumns,并且初始都未启用。

总结

本篇文章讲了Panels文件夹中剩余的Asset相关的部分,当然这只是主体与Asset相关的,其他有一些关联的并没有讲到,会在之后的文章中继续说明。

Diana


点击全文阅读


本文链接:http://m.zhangshiyu.com/post/40474.html

函数  纹理  路径  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1