赞
踩
学习 Git 的网站
https://learngitbranching.js.org/?locale=zh_CN
学习 cmake
cmake 官网和
https://github.com/SFUMECJF/cmake-examples-Chinese
一般拿到手都是先编译吗,得到他要的那个 json 文件
但是也可以用空项目
如果出现有些文件没有识别出来,看一看报错信息,大概率是有些头文件找不到
那就是头文件的路径不对
我看别人还有出现 C++ 版本不对,或者自己是 mac 系统但是包含了 win 或 linux 的文件
源文件就看 source 就行了
头文件这里,直接用 auto-detect
选择要找的文件夹的时候记得要多选择到 engine 文件夹
因为调用的第三方库没有放在 source 而是放在 source 外和 source 平级,所以干脆选择 engine 文件夹,一定不会出错
之后再生成的 sourcetrail 项目就没有报错了,没有报错就不会缺少文件信息
engine\source\runtime\engine.h
class PiccoloEngine
{
...
public:
void startEngine(const std::string& config_file_path);
void shutdownEngine();
void initialize();
void clear();
bool isQuit() const { return m_is_quit; }
void run();
bool tickOneFrame(float delta_time);
startEngine
里面注册反射,使用 g_runtime_global_context
启动各个系统
shutdownEngine()
里面使用 g_runtime_global_context
结束各个系统,然后注销反射
run()
在 while 中判断时都要关闭窗口,如果不关闭,那么执行一个 tickOneFrame(delta_time);
一个 tick 里面有逻辑帧的 tick 和渲染帧的 tick
所以 engine 最主要的是使用 g_runtime_global_context
启动和关闭各个系统
void RuntimeGlobalContext::startSystems(const std::string& config_file_path)
中初始化了各个 system 和 manager
system 偏向于使用者的物理支持,例如窗口,输入,渲染
manager 偏向于游戏逻辑,例如物理,粒子
bool PiccoloEngine::tickOneFrame(float delta_time)
里面分为逻辑帧和渲染帧
先算逻辑帧,然后在逻辑帧和渲染帧之间交换数据,然后算渲染帧
然后还有一些不方便划分到逻辑或者渲染中,但是又需要更新的东西,例如物理,事件,应用窗口标题
void PiccoloEngine::logicalTick(float delta_time)
中首先 tick 了 world 然后 tick 了 input
world 是逻辑实体的载体,world 由一个个 level 组成
void WorldManager::tick(float delta_time)
如果不存在 world,那么根据路径去加载 world;tick 关卡的 level
一个 world 的 tick 中只会执行一个 active_level 的 tick
void Level::tick(float delta_time)
中,tick 物体,角色,物理
GameObject 的 tick void GObject::tick(float delta_time)
只是遍历这个 GameObject 的所有 Component 的 tick
下载 lua 和 sol2
在 3rdparty 中的 CMakeList 中添加 lua 和 sol2 的编译语句
参考 Jolt 的写法
if(NOT TARGET Jolt) option(TARGET_HELLO_WORLD "" OFF) option(TARGET_PERFORMANCE_TEST "" OFF) option(TARGET_SAMPLES "" OFF) option(TARGET_UNIT_TESTS "" OFF) option(TARGET_VIEWER "" OFF) if(ENABLE_PHYSICS_DEBUG_RENDERER) option(TARGET_TEST_FRAMEWORK "" ON) else() option(TARGET_TEST_FRAMEWORK "" OFF) endif() add_subdirectory(JoltPhysics/Build) if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") include(CheckCXXCompilerFlag) check_cxx_compiler_flag("-Wno-unqualified-std-cast-call" COMPILER_CHECK_UNQUALIFIED) if(COMPILER_CHECK_UNQUALIFIED) target_compile_options(Jolt PRIVATE "-Wno-unqualified-std-cast-call") endif() endif() if(ENABLE_PHYSICS_DEBUG_RENDERER) set_target_properties(Jolt TestFramework PROPERTIES FOLDER ${third_party_folder}/JoltPhysics MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") else() set_target_properties(Jolt PROPERTIES FOLDER ${third_party_folder}/JoltPhysics MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") endif() endif()
只需要写成
if(NOT TARGET sol2)
add_subdirectory(sol2-3.3.0)
endif()
if(NOT TARGET lua)
include(lua.cmake)
endif()
这些拉取的最新版的 piccolo 都有
然后这个 lua.cmake 也是有的……他这里好像是把 lua 文件夹下的 .c 大部分包括了
然后要在 Piccolo-main\engine\source\runtime 的 CMakeList.txt 中链接
target_link_libraries(${TARGET_NAME} PUBLIC lua_static sol2)
好高级……我都不懂
然后他在 engine\source\runtime\function\framework\component\lua\lua_component.cpp 新建了 lua 的 component
给 component 新建了一个 string,设置了这个 string 字段的反射类型位 Enabled,配合这个类的反射类型为白名单,表示这个字段可以被反射,然后现在我们要配置这个 component 到场景中
默认的场景是 engine\asset\world\hello.world.json
其中设置了关卡 “default_level_url”: “asset/level/1-1.level.json”
关卡的 json 中开头是一些琐碎的设置,例如重力,角色,然后是 objects 列表
看里面的名字为 Player 的物体,定义了一个transform component 和一个 definition
“definition”: “asset/objects/character/player/player.object.json”
definition 也是一个 json
查看这个 player 的 json,可以添加一个 component
component 的 json 定义由两个部分组成,context 和 typeName
typeName 是组建的名字
context 里面是序列化的字段
之前我们定义了 m_lua_script
字段,这里就写 lua_script
直接把 lua_script
赋为 lua 脚本
反射的时候会自动去掉 m_
前缀
之后在 lua component 的头文件中添加 sol 的头文件,添加一个 sol::state m_lua_state;
,通过这个 state 就可以载入函数库,运行 lua 脚本
优点是方便在任何地方访问这个全局变量,缺点是使用到这个全局变量的函数的可读性可移植性可能不好,与这个全局变量相关的逻辑可能很复杂,写新逻辑的人需要知道这个全局变量的所有使用情况,才能保证自己写的逻辑不会和别人的冲突
这样使得游戏的运行结果是不确定的,可能在物理模拟,渲染,网络同步等需要确定性的系统上出现问题
在 level 的 tick 中,对每一个物体的 tick,而物体的 tick 是对这个物体的所有 component 进行 tick
物体的 tick 之间的顺序应该由物体之间的父子关系决定,而每一个物体中的 component 的 tick 之间的顺序可以通过排序来保证,例如在初始化的时候排一次序,在物体的 component 变动的时候排一次序
我感觉是可以人为设定一个字典,设置每一种 component 对应的 key,根据这个 key 进行排序
lua 脚本的生命周期我觉得就参考 Unity 脚本的生命周期吧,在 GObject 创建的时候调用 Lua 脚本中的 Awake 函数,在关卡第一次遍历的时候调用 Lua 脚本的 Start 函数,在 LuaComponent tick 的时候调用 Lua 脚本的 Tick 函数,在 Component Disable Enable 的时候……
lua 脚本与引擎各系统的交互方式:可以 C++ 定义一个函数,这个函数通过自定义的反射系统来访问那些允许反射的实例,然后将这个函数绑定到 lua 函数上
最新版的 piccolo 给出了使用反射的示例
首先根据 parser 源码构建 piccolo parser
然后收集 runtime 所有头文件的路径,然后生成一个 parser_header.h
cmake 构建 picoolo runtime 工程之前会构建 piccolo precompile 工程
在构建 precompile 工程时,会调用 piccolo parser 去解析 parser_header.h
解析方式是使用 clang 库,得到所有类的抽象语法树,根据在类上打的标签,筛选出我们感兴趣的信息,例如类需要反射的类型和变量名
根据得到的信息生成反射代码,然后将这些代码与 runtime 源码一起构建 piccolo runtime
第一部分是 json 相关
第二部分是取基类反射示例
第三部分是对每一个反射的字段生成的代码
取这个字段的类型名,取变量名,get set,判断是否为数组(如果是 std::vector 那么返回 true)
显然,如果这个类有多个需要反射的字段,那么在第三部分,就会对每一个字段都生成相应的函数
例如现在有一个方法,两个属性需要反射,那么在第三部分生成的代码就会是:
接下来是一个 Register 函数,以反射的类的类名为后缀,这里是 TypeWrapperRegister_MotorComponent
对于每一个要反射的字段、方法、基类,把生成的函数合成一个 tuple,放进 map 中
属性放属性的 map,方法放方法的 map,数组,基类也是
这些宏定义只是单纯的在 map 中增删
从整体来看比较简单的反射代码:
可以看更复杂的反射代码的样子:
这里看上去不知道为什么会多了这些东西,于是去看这份反射代码对应的源码头文件
可见,这是因为一份头文件里面塞下了多个要反射的类
那么按照常理来说,之前那个复杂的反射生成代码的结构应该只是简单反射生成代码的重复
实际上确实是这样,只是默认的折叠遮盖了结构而已
最终在注册的时候包含了三个要注册的函数
再看 reflection 的目录,除了 engine\source_generated\reflection\all_reflection.h 其他反射生成的代码都是以 reflection.gen.h 结尾,而且里面的结构都是一样的
all_reflection.h 里面把所有反射生成的代码的头文件 include 进来,然后创建了一个 metaRegister
函数,装了所有反射生成的那个要把 tuple 注册到 map 的函数
这个 metaRegister
就是在 startEngine 里面调用的
根据字符串可以找到某一个组件的某一个成员
一开始这里是找到某个组件
bool find_component_field(std::weak_ptr<GObject> game_object,
const char* field_name,
Reflection::FieldAccessor& field_accessor,
void*& target_instance)
{
auto components = game_object.lock()->getComponents();
std::istringstream iss(field_name);
std::string current_name;
std::getline(iss, current_name, '.');
auto component_iter = std::find_if(
components.begin(), components.end(), [current_name](auto c) { return c.getTypeName() == current_name; });
这个 current_name
其实是根据 '.'
对 field_name
导出的 string 流的第一个 string
看官方的用例
set_float(GameObject, \"MotorComponent.m_motor_res.m_jump_height\", 10)
可见,第一个得到的是 MotorComponent
这就很神奇,因为其实我们一直都是想着对一个类得到这个类的变量,但是他这里开头是一个类型名
实际上是因为一开始从一个 Component 出发,我们只知道这个 Component 所在的 GObject
之前在 void LuaComponent::postLoadResource(std::weak_ptr<GObject> parent_object)
给 state 的 “GameObject” 键设为 m_parent_object 的值
m_lua_state["GameObject"] = m_parent_object;
就是在 Lua 脚本中设置了一些内置变量,很方便
if (component_iter != components.end()) { auto meta = Reflection::TypeMeta::newMetaFromName(current_name); void* field_instance = component_iter->getPtr(); // find target field while (std::getline(iss, current_name, '.')) { Reflection::FieldAccessor* fields; int fields_count = meta.getFieldsList(fields); auto field_iter = std::find_if( fields, fields + fields_count, [current_name](auto f) { return f.getFieldName() == current_name; }); if (field_iter == fields + fields_count) // not found { delete[] fields; return false; } field_accessor = *field_iter; delete[] fields; target_instance = field_instance; // for next iteration field_instance = field_accessor.get(target_instance); field_accessor.getTypeMeta(meta); } return true; } return false; }
他这里每一次循环都是用一个 TypeMeta
类的实例,得到这个实例的 Reflection::FieldAccessor
数组,在这个数组中寻找字段名与待查找的字段名相同的 Reflection::FieldAccessor
成员
找到之后,用这个 Reflection::FieldAccessor
成员从实例中获得目标字段的实例
如此循环,直到字符串读取完成,如果仍然能够读到字段实例,说明这个 GObject 中有这个字段
例如 "MotorComponent.m_motor_res.m_jump_height"
一开始是从 GObject 获得 Component 的指针,然后获得这个 Component 的 void*
类型的指针,获得这个 Component 的 name,加入循环:根据 name 创建 TypeMeta
类的一个变量 meta,meta 可以根据 name 获得相应的字段访问器,例如我输入 "MotorComponent"
创建的 TypeMeta
类的变量可以获得 MotorComponent 类里面可反射字段的字段访问器,然后在这些字段访问器中查找,如果找到名称为 "m_motor_res"
的,就可以使用根据 void*
类型的指向实例的指针,配合这个字段访问器,得到字段的实例,类型为 void*
如此循环
这里 void*
是方便泛型编程
void*
的指针使用的时候必须强转为显式类型的指针,也就是说这个反射要用到 field_instance 的时候……?
然后 lua 组件的自定义 get set 就会使用这个 find_component_field
来找到字段
之后有价值的是,得到一个 void* 的字段实例和一个字段访问器之后,就用这个字段访问器去访问字段实例,得到实际的值
template<typename T> void LuaComponent::set(std::weak_ptr<GObject> game_object, const char* name, T value) { LOG_INFO(name); Reflection::FieldAccessor field_accessor; void* target_instance; if (find_component_field(game_object, name, field_accessor, target_instance)) { field_accessor.set(target_instance, &value); } else { LOG_ERROR("Can't find target field."); } } template<typename T> T LuaComponent::get(std::weak_ptr<GObject> game_object, const char* name) { LOG_INFO(name); Reflection::FieldAccessor field_accessor; void* target_instance; if (find_component_field(game_object, name, field_accessor, target_instance)) { return *(T*)field_accessor.get(target_instance); } else { LOG_ERROR("Can't find target field."); } }
然后 FieldAccessor
的 get set 的实现,具体是怎么实现访问字段实例的?
void* FieldAccessor::get(void* instance)
{
// todo: should check validation
return static_cast<void*>((std::get<1>(*m_functions))(instance));
}
void FieldAccessor::set(void* instance, void* value)
{
// todo: should check validation
(std::get<0>(*m_functions))(instance, value);
}
他这里是使用了之前在反射生成代码里面捆绑的字段函数 tuple 中的第二个函数实现 get,字段函数 tuple 中的第一个函数实现 get
但是我这是已经看了即便所以才这么想的,具体到这个代码里面,还需要知道 m_functions
这个 tuple 是从哪来的
直接查找引用是只能找到 FieldAccessor
的构造函数中对这个 m_functions
赋值了
那就只能去看这个 FieldAccessor
类型的变量是怎么来的了,还是回到 find_component_field
函数
这里是用 TypeMeta 的 meta.getFieldsList(fields);
才找到的 FieldAccessor
类型的变量
而 TypeMeta 的这个获取字段列表也只是拷贝自己的变量 m_fields
出去
int TypeMeta::getFieldsList(FieldAccessor*& out_list)
{
int count = m_fields.size();
out_list = new FieldAccessor[count];
for (int i = 0; i < count; ++i)
{
out_list[i] = m_fields[i];
}
return count;
}
而这个变量在 TypeMeta
的构造函数中被赋值
TypeMeta::TypeMeta(std::string type_name) : m_type_name(type_name) { m_is_valid = false; m_fields.clear(); m_methods.clear(); auto fileds_iter = m_field_map.equal_range(type_name); while (fileds_iter.first != fileds_iter.second) { FieldAccessor f_field(fileds_iter.first->second); m_fields.emplace_back(f_field); m_is_valid = true; ++fileds_iter.first; } auto methods_iter = m_method_map.equal_range(type_name); while (methods_iter.first != methods_iter.second) { MethodAccessor f_method(methods_iter.first->second); m_methods.emplace_back(f_method); m_is_valid = true; ++methods_iter.first; } }
这里用的是 FieldAccessor
的构造函数 FieldAccessor(FieldFunctionTuple* functions);
equal_range
返回的是 key 等于给定值的 pair<迭代器表示的下界,迭代器表示的上界>
fileds_iter.first
一开始表示的是下界,它也可以自增,用于迭代,直到等于上界
那么其实这个函数就是从 m_field_map
取出键为字段名的所有字段函数 tuple,构造 FieldAccessor
,装入 TypeMeta
实例的 m_fields
成员
方法也是同理
m_field_map
就是反射代码最终将自己的 tuple 放到的容器中
这样,反射代码和 TypeMeta 之间的数据流动就串起来了
这里还有一点是,TypeMeta 自己有一个名字,FieldAccessor
其实可以通过 tuple 中的函数也能得到自己的名字,所以就能够通过字符串去创建 TypeMeta,也可以根据字符串去查找某一个 TypeMeta 类的实例所含有的 FieldAccessor
数组中的某一个元素
首先看解析器的 main 函数
engine\source\meta_parser\parser\main.cpp
MetaParser::prepare();
result = parse(argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]);
首先使用了 Parser 的预处理,然后选六个参数传入 parse
函数
parse
函数用这六个参数创建一个 MetaParser
类的实例,调用这个对象的 parse
函数,获得结果,如果结果编号不为 0,也就是如果出错了就返回这个编码,没有出错就生成反射文件
int parse(std::string project_input_file_name, std::string source_include_file_name, std::string include_path, std::string sys_include, std::string module_name, std::string show_errors) { std::cout << std::endl; std::cout << "Parsing meta data for target \"" << module_name << "\"" << std::endl; std::fstream input_file; bool is_show_errors = "0" != show_errors; MetaParser parser( project_input_file_name, source_include_file_name, include_path, sys_include, module_name, is_show_errors); std::cout << "Parsing in " << include_path << std::endl; int result = parser.parse(); if (0 != result) { return result; } parser.generateFiles(); return 0; }
先跳过构造函数,看 int MetaParser::parse(void)
其中主要在
m_translation_unit = clang_createTranslationUnitFromSourceFile(
m_index, m_source_include_file_name.c_str(), static_cast<int>(arguments.size()), arguments.data(), 0, nullptr);
auto cursor = clang_getTranslationUnitCursor(m_translation_unit);
Namespace temp_namespace;
buildClassAST(cursor, temp_namespace);
调用 clang 然后生成类的抽象语法树
void MetaParser::buildClassAST(const Cursor& cursor, Namespace& current_namespace) { for (auto& child : cursor.getChildren()) { auto kind = child.getKind(); // actual definition and a class or struct if (child.isDefinition() && (kind == CXCursor_ClassDecl || kind == CXCursor_StructDecl)) { auto class_ptr = std::make_shared<Class>(child, current_namespace); TRY_ADD_LANGUAGE_TYPE(class_ptr, classes); } else { RECURSE_NAMESPACES(kind, child, buildClassAST, current_namespace); } } }
它在所有的根节点中找到类声明和结构体的声明,然后构造了 Class
类的实例,传递到了 m_schema_modules
但是那个 classes
变量,好神奇,我一开始去找一个名字叫作 classes
的变量,没找到,给我整晕了
之后才想到这是在宏定义里面,他其实只是宏定义中的一个字符串而已
#define TRY_ADD_LANGUAGE_TYPE(handle, container) \
{ \
if (handle->shouldCompile()) \
{ \
auto file = handle->getSourceFile(); \
m_schema_modules[file].container.emplace_back(handle); \
m_type_table[handle->m_display_name] = file; \
} \
}
std::unordered_map<std::string, SchemaMoudle> m_schema_modules;
struct SchemaMoudle
{
std::string name;
std::vector<std::shared_ptr<Class>> classes;
};
然后看 Class 类是怎么样的
Class 类的构造函数里面,对基类、字段、方法做特殊处理
Class::Class(const Cursor& cursor, const Namespace& current_namespace) : TypeInfo(cursor, current_namespace), m_name(cursor.getDisplayName()), m_qualified_name(Utils::getTypeNameWithoutNamespace(cursor.getType())), m_display_name(Utils::getNameWithoutFirstM(m_qualified_name)) { Utils::replaceAll(m_name, " ", ""); Utils::replaceAll(m_name, "Piccolo::", ""); for (auto& child : cursor.getChildren()) { switch (child.getKind()) { case CXCursor_CXXBaseSpecifier: { auto base_class = new BaseClass(child); m_base_classes.emplace_back(base_class); } break; // field case CXCursor_FieldDecl: m_fields.emplace_back(new Field(child, current_namespace, this)); break; // method case CXCursor_CXXMethod: m_methods.emplace_back(new Method(child, current_namespace, this)); default: break; } } }
参考 Field 类可以实现 Method 类,大部分是复制的 Field 类,有点看不懂
然后回到 main,看 parser.generateFiles();
怎么生成反射代码的
看到
int ReflectionGenerator::generate(std::string path, SchemaMoudle schema)
其中关键代码是 genClassRenderData(class_temp, class_def);
使用 engine\template 中的代码模板生成反射代码
之后他又根据 field 的反射生成逻辑做了 method 的反射生成逻辑
呃……全程没看懂
各个系统在 startEngine 的时候初始化
void RenderSystem::initialize(RenderSystemInitInfo init_info)
渲染系统的初始化,首先设置渲染内容,然后设置相机,场景,渲染管线
将渲染的功能抽象成一个个 pass
在 pipeline 的初始化中会调用各个 pass 的初始化
在渲染系统的 tick 中,首先调用 void RenderSystem::processSwapData()
查看 RenderSwapData
这个结构
struct RenderSwapData { std::optional<LevelResourceDesc> m_level_resource_desc; std::optional<GameObjectResourceDesc> m_game_object_resource_desc; std::optional<GameObjectResourceDesc> m_game_object_to_delete; std::optional<CameraSwapData> m_camera_swap_data; std::optional<ParticleSubmitRequest> m_particle_submit_request; std::optional<EmitterTickRequest> m_emitter_tick_request; std::optional<EmitterTransformRequest> m_emitter_transform_request; void addDirtyGameObject(GameObjectDesc&& desc); void addDeleteGameObject(GameObjectDesc&& desc); void addNewParticleEmitter(ParticleEmitterDesc& desc); void addTickParticleEmitter(ParticleEmitterID id); void updateParticleTransform(ParticleEmitterTransformDesc& desc); };
m_level_resource_desc
用于指定 colorgrading 和 天空盒 的贴图
m_game_object_resource_desc
和 m_game_object_to_delete
分别指定要添加和删除的 GameObject
m_camera_swap_data
更新相机参数
m_particle_submit_request
, m_emitter_tick_request
, m_emitter_transform_request
指定要创建的粒子发射器,要 tick 的粒子发射器,更新粒子发射器的位置
void RenderSystem::processSwapData()
这个函数之后的部分就是根据 RenderSwapData
这个结构体中的内容进行处理
这里的 swap 不是特别准确,因为现在只有渲染从逻辑拿数据
在 void RenderSystem::tick(float delta_time)
中,首先调用 processSwapData
从逻辑拿数据
void RenderSystem::tick(float delta_time) { // process swap data between logic and render contexts processSwapData(); // prepare render command context m_rhi->prepareContext(); // update per-frame buffer m_render_resource->updatePerFrameBuffer(m_render_scene, m_render_camera); // update per-frame visible objects m_render_scene->updateVisibleObjects(std::static_pointer_cast<RenderResource>(m_render_resource), m_render_camera); // prepare pipeline's render passes data m_render_pipeline->preparePassData(m_render_resource); g_runtime_global_context.m_debugdraw_manager->tick(delta_time); // render one frame if (m_render_pipeline_type == RENDER_PIPELINE_TYPE::FORWARD_PIPELINE) { m_render_pipeline->forwardRender(m_rhi, m_render_resource); } else if (m_render_pipeline_type == RENDER_PIPELINE_TYPE::DEFERRED_PIPELINE) { m_render_pipeline->deferredRender(m_rhi, m_render_resource); } else { LOG_ERROR(__FUNCTION__, "unsupported render pipeline type"); } }
RHI(Render Hardware Interface) 图形学 api 的函数抽象(VulkanRHI 与 RHI 的关系就是继承,外界代码调用 RHI 接口,不需要考虑内部实现是 DirectX 或者 Vulkan)
然后调用 RHI 的 prepareContext
函数
这里 RHI 是基类,VulkanRHI 是子类,m_rhi
是基类的指针,这样我们就可以不用考虑内部实现是 Vulkan 还是 DirectX
接下来的 RenderResource::updatePerFrameBuffer
是准备每帧 pass 共用的数据
RenderScene::updateVisibleObjects
是为每个 pass 筛选每个 pass 具体所需的数据
RenderPipelineBase::preparePassData
会调用各个 pass 的 preparePassData
进行数据准备
最后判断是使用前向渲染还是延迟渲染
对于前向渲染:
engine\source\runtime\function\render\render_pipeline.cpp
在 RenderPipeline::forwardRender
中,先准备渲染对象,然后调用相机 Pass 的 drawForward 函数
在相机 Pass 的 drawForward 函数中,会执行各个 subPass 的 draw 函数
如果是延迟渲染,那么调用 MainCameraPass::draw
函数
这个函数中,先会进行一次 MainCameraPass::drawMeshGbuffer()
的绘制,将物体的几何信息存到 G-Buffer 中,然后利用 G-Buffer 里缓存的光照信息进行光照计算
然后它就用 renderdoc 一看就发现了 color grading 的条纹问题是 mipmap 的精度问题
应该只生成一级 mipmap
在生成纹理的地方指定生成一级 mipmap
engine\source\runtime\function\render\render_resource.cpp
// create color grading texture
rhi->createGlobalImage(
m_global_render_resource._color_grading_resource._color_grading_LUT_texture_image,
m_global_render_resource._color_grading_resource._color_grading_LUT_texture_image_view,
m_global_render_resource._color_grading_resource._color_grading_LUT_texture_image_allocation,
color_grading_map->m_width,
color_grading_map->m_height,
color_grading_map->m_pixels,
color_grading_map->m_format, 1);
ColorGradingPass
是 RenderPass
的一个子类
看 RenderPass
类,其中的
GlobalRenderResource* m_global_render_resource {nullptr};
定义了可能用到的子类
std::vector<Descriptor> m_descriptor_infos;
定义了输入和输出的资源在 shader 中的布局信息
std::vector<RenderPipelineBase> m_render_pipelines;
定义了渲染管线的信息
static VisiableNodes m_visiable_nodes;
记录了这个 pass 可见的物体,例如场景中可能有多个物体,该 pass 只会用到其中一部分
这里 color grading pass 被设计为 main camera pass 的一个 sub pass
enum
{
_main_camera_subpass_basepass = 0,
_main_camera_subpass_deferred_lighting,
_main_camera_subpass_forward_lighting,
_main_camera_subpass_tone_mapping,
_main_camera_subpass_color_grading,
_main_camera_subpass_fxaa,
_main_camera_subpass_ui,
_main_camera_subpass_combine_ui,
_main_camera_subpass_count
};
sub pass 是 vulkan api 的一个设计,作为 vulkan render pass 对象的一个部分,一个 vulkan render pass 可以包含多个串行的 subpass,前一个 subpass 的输出作为后一个 subpass 的输入
在这里添加 color_grading pass
engine\source\runtime\function\render\passes\main_camera_pass.cpp
RHISubpassDescription& color_grading_pass = subpasses[_main_camera_subpass_color_grading];
对于 color grading pass 的初始化函数
一开始拿到了一个 InitInfo 也就是拿到了 lookuptable 这张贴图
void ColorGradingPass::initialize(const RenderPassInitInfo* init_info)
{
RenderPass::initialize(nullptr);
const ColorGradingPassInitInfo* _init_info = static_cast<const ColorGradingPassInitInfo*>(init_info);
m_framebuffer.render_pass = _init_info->render_pass;
setupDescriptorSetLayout();
setupPipelines();
setupDescriptorSet();
updateAfterFramebufferRecreate(_init_info->input_attachment);
}
setupDescriptorSetLayout();
描述了输入输出的资源排布
这里用到两个资源,一个是 tone mapping pass 的输出,一个是调色纹理
setupPipelines();
描述管线,color grading pass 使用的是 graphics 管线,主体部分是顶点着色器和片元着色器
RHIShader* vert_shader_module = m_rhi->createShaderModule(POST_PROCESS_VERT);
RHIShader* frag_shader_module = m_rhi->createShaderModule(COLOR_GRADING_FRAG);
视口裁剪,朝向剔除,混合模式,深度模板
setupDescriptorSet();
描述实际使用到的资源
DescriptorSet 可以理解为资源 handle 的集合
updateAfterFramebufferRecreate
视口变化时进行一些更新操作
color grading pass 的 draw()
会在 main camera pass 的 draw()
中被调用
draw 函数就是提交了一些 vulkan 的命令
仿照 color grading 添加 vignette pass
复制粘贴 color grading 的源文件和头文件,重命名
在文件夹中搜索 color_grading,根据搜索结果,复制粘贴 color_grading 相关的代码并修改:
在 engine\source\runtime\function\render\render_pass.h 中添加 _main_camera_subpass_vignette
的 enum
在 engine\source\runtime\function\render\render_pipeline_base.h 中添加
std::shared_ptr<RenderPassBase> m_vignette_pass;
在 engine\source\runtime\function\render\render_pipeline.cpp 中添加
#include "runtime/function/render/passes/vignette_pass.h"
void RenderPipeline::initialize(RenderPipelineInitInfo init_info)
中添加
m_vignette_pass = std::make_shared<VignettePass>();
m_vignette_pass->setCommonInfo(pass_common_info);
VignettePassInitInfo vignette_init_info;
vignette_init_info.render_pass = _main_camera_pass->getRenderPass();
vignette_init_info.input_attachment =
_main_camera_pass->getFramebufferImageViews()[_main_camera_pass_backup_buffer_even];
m_vignette_pass->initialize(&vignette_init_info);
void RenderPipeline::forwardRender(std::shared_ptr<RHI> rhi, std::shared_ptr<RenderResourceBase> render_resource)
中添加:
VignettePass& vignette_pass = *(static_cast<VignettePass*>(m_vignette_pass.get()));
修改:
static_cast<MainCameraPass*>(m_main_camera_pass.get())
->drawForward(color_grading_pass,
vignette_pass,
fxaa_pass,
tone_mapping_pass,
ui_pass,
combine_ui_pass,
particle_pass,
vulkan_rhi->m_current_swapchain_image_index);
void RenderPipeline::deferredRender(std::shared_ptr<RHI> rhi, std::shared_ptr<RenderResourceBase> render_resource)
中
添加
VignettePass& vignette_pass = *(static_cast<VignettePass*>(m_vignette_pass.get()));
修改
static_cast<MainCameraPass*>(m_main_camera_pass.get())
->draw(color_grading_pass,
vigntee_pass,
fxaa_pass,
tone_mapping_pass,
ui_pass,
combine_ui_pass,
particle_pass,
vulkan_rhi->m_current_swapchain_image_index);
void RenderPipeline::passUpdateAfterRecreateSwapchain()
添加
VignettePass& vignette_pass = *(static_cast<VignettePass*>(m_vignette_pass.get()));
修改
tone_mapping_pass.updateAfterFramebufferRecreate(
main_camera_pass.getFramebufferImageViews()[_main_camera_pass_backup_buffer_odd]);
color_grading_pass.updateAfterFramebufferRecreate(
main_camera_pass.getFramebufferImageViews()[_main_camera_pass_backup_buffer_even]);
vignette_pass.updateAfterFramebufferRecreate(
main_camera_pass.getFramebufferImageViews()[_main_camera_pass_backup_buffer_odd]);
fxaa_pass.updateAfterFramebufferRecreate(
main_camera_pass.getFramebufferImageViews()[_main_camera_pass_post_process_buffer_even]);
combine_ui_pass.updateAfterFramebufferRecreate(
main_camera_pass.getFramebufferImageViews()[_main_camera_pass_backup_buffer_even],
main_camera_pass.getFramebufferImageViews()[_main_camera_pass_backup_buffer_odd]);
这里的 buffer_odd buffer_even 是 subpass 之间的缓冲区,所以新加了一个 vignette_pass 之后对后面的 sub pass 的奇偶做一下替换
在 engine\source\runtime\function\render\passes\main_camera_pass.cpp
void MainCameraPass::setupRenderPass()
中
添加
RHIAttachmentReference vignette_pass_input_attachment_reference {}; vignette_pass_input_attachment_reference.attachment = &backup_even_color_attachment_description - attachments; vignette_pass_input_attachment_reference.layout = RHI_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; RHIAttachmentReference vignette_pass_color_attachment_reference {}; if (m_enable_fxaa) { vignette_pass_color_attachment_reference.attachment = &post_process_odd_color_attachment_description - attachments; } else { vignette_pass_color_attachment_reference.attachment = &backup_odd_color_attachment_description - attachments; } vignette_pass_color_attachment_reference.layout = RHI_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; RHISubpassDescription& vignette_pass = subpasses[_main_camera_subpass_vignette]; vignette_pass.pipelineBindPoint = RHI_PIPELINE_BIND_POINT_GRAPHICS; vignette_pass.inputAttachmentCount = 1; vignette_pass.pInputAttachments = &vignette_pass_input_attachment_reference; vignette_pass.colorAttachmentCount = 1; vignette_pass.pColorAttachments = &vignette_pass_color_attachment_reference; vignette_pass.pDepthStencilAttachment = NULL; vignette_pass.preserveAttachmentCount = 0; vignette_pass.pPreserveAttachments = NULL;
还要管理依赖关系
添加一个 dependencies,8 改为 9
RHISubpassDependency dependencies[9] = {};
添加并修改
RHISubpassDependency& vignette_pass_depend_on_color_grading_pass = dependencies[5]; vignette_pass_depend_on_color_grading_pass.srcSubpass = _main_camera_subpass_color_grading; vignette_pass_depend_on_color_grading_pass.dstSubpass = _main_camera_subpass_vignette; vignette_pass_depend_on_color_grading_pass.srcStageMask = RHI_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | RHI_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; vignette_pass_depend_on_color_grading_pass.dstStageMask = RHI_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | RHI_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; vignette_pass_depend_on_color_grading_pass.srcAccessMask = RHI_ACCESS_SHADER_WRITE_BIT | RHI_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; vignette_pass_depend_on_color_grading_pass.dstAccessMask = RHI_ACCESS_SHADER_READ_BIT | RHI_ACCESS_COLOR_ATTACHMENT_READ_BIT; vignette_pass_depend_on_color_grading_pass.dependencyFlags = RHI_DEPENDENCY_BY_REGION_BIT; RHISubpassDependency& fxaa_pass_depend_on_vignette_pass = dependencies[6]; fxaa_pass_depend_on_vignette_pass.srcSubpass = _main_camera_subpass_vignette; fxaa_pass_depend_on_vignette_pass.dstSubpass = _main_camera_subpass_fxaa; fxaa_pass_depend_on_vignette_pass.srcStageMask = RHI_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | RHI_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; fxaa_pass_depend_on_vignette_pass.dstStageMask = RHI_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | RHI_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; fxaa_pass_depend_on_vignette_pass.srcAccessMask = RHI_ACCESS_SHADER_WRITE_BIT | RHI_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; fxaa_pass_depend_on_vignette_pass.dstAccessMask = RHI_ACCESS_SHADER_READ_BIT | RHI_ACCESS_COLOR_ATTACHMENT_READ_BIT;
之后的序号也要变
修改函数定义:
void MainCameraPass::draw(ColorGradingPass& color_grading_pass,
VignettePass& vignette_pass,
FXAAPass& fxaa_pass,
ToneMappingPass& tone_mapping_pass,
UIPass& ui_pass,
CombineUIPass& combine_ui_pass,
ParticlePass& particle_pass,
uint32_t current_swapchain_image_index)
添加:
m_rhi->cmdNextSubpassPFN(m_rhi->getCurrentCommandBuffer(), RHI_SUBPASS_CONTENTS_INLINE);
vigneete_pass.draw();
修改函数定义:
void MainCameraPass::drawForward(ColorGradingPass& color_grading_pass,
VignettePass& vignette_pass,
FXAAPass& fxaa_pass,
ToneMappingPass& tone_mapping_pass,
UIPass& ui_pass,
CombineUIPass& combine_ui_pass,
ParticlePass& particle_pass,
uint32_t current_swapchain_image_index)
添加:
m_rhi->cmdNextSubpassPFN(m_rhi->getCurrentCommandBuffer(), RHI_SUBPASS_CONTENTS_INLINE);
vignette_pass.draw();
在 engine\source\runtime\function\render\passes\main_camera_pass.h
添加
#include "runtime/function/render/passes/vignette_pass.h"
修改函数声明
void draw(ColorGradingPass& color_grading_pass, VignettePass& vignette_pass, FXAAPass& fxaa_pass, ToneMappingPass& tone_mapping_pass, UIPass& ui_pass, CombineUIPass& combine_ui_pass, ParticlePass& particle_pass, uint32_t current_swapchain_image_index); void drawForward(ColorGradingPass& color_grading_pass, VignettePass& vignette_pass, FXAAPass& fxaa_pass, ToneMappingPass& tone_mapping_pass, UIPass& ui_pass, CombineUIPass& combine_ui_pass, ParticlePass& particle_pass, uint32_t current_swapchain_image_index);
全都是照着视频做的,但是我在编译的时候出了问题
[ 1%] Building CXX object engine/source/meta_parser/CMakeFiles/PiccoloParser.dir/parser/cursor/cursor.cpp.obj
g++.exe: error: /O2: No such file or directory
g++.exe: error: /Ob2: No such file or directory
mingw32-make.exe[2]: *** [engine\source\meta_parser\CMakeFiles\PiccoloParser.dir\build.make:76: engine/source/meta_parser/CMakeFiles/PiccoloParser.dir/parser/cursor/cursor.cpp.obj] Error 1
mingw32-make.exe[1]: *** [CMakeFiles\Makefile2:1434: engine/source/meta_parser/CMakeFiles/PiccoloParser.dir/all] Error 2
mingw32-make.exe: *** [Makefile:135: all] Error 2
我一看,那个源文件确实在那里,也没报错,不知道为什么没找到 obj
之后删除了 build 文件夹,然后重新编译,就出现了 vignette pass 相关的报错
之后查询了一下 cmake 指令
cmake -S . -B build
在 help 中的信息:
-S <path-to-source> = Explicitly specify a source directory.
-B <path-to-build> = Explicitly specify a build directory.
那么其实这个命令就是以当前文件夹(.
)为源码根目录,以 build 文件夹为构建根目录
之后就是缺少 vignette_frag.h
这个是在构建过程中由 vignette.frag 转化成的
在 engine\source\runtime\function\render\passes\vignette_pass.cpp
将小写改成大写
RHIShader* frag_shader_module = m_rhi->createShaderModule(VIGNETTE_FRAG);
将 vignette 的贴图资源改回 color_grading 的贴图
因为暂时没有为 vignette 提供贴图
vignette_LUT_image_info.imageView =
m_global_render_resource->_color_grading_resource._vignette_LUT_texture_image_view;
其实 vignette 暗角效果是不需要贴图的
然后我不知道为什么教学视频里面 render doc 能够显示 render pass 的名字,而我自己捕捉的没有
实际测试小引擎,加了一个 vignette pass,shader 是复制 color_grading 的,所以理论上我们走了两个 color grading,得到的颜色应该比一次 color grading 更明显,但是实际运行出来,两次跟一次的效果是一样的
所以我们打开 render doc 来查找原因,可以看到,vkCmdNextSubpass() => 4 和 vkCmdNextSubpass() => 5 之间的 vkCmdDraw(3, 1) 就是 color_grading shader,而 vkCmdNextSubpass() => 5 和 vkCmdNextSubpass() => 6 之间的 vkCmdDraw(3, 1) 就是 vignette shader
然后可以发现这两次 draw 的材质输入和输出都是一样的,这就导致运行的结果变得一样
这应该是 subpass 传递贴图的 buffer 之间没有设置对
之前在 void RenderPipeline::passUpdateAfterRecreateSwapchain()
中,我们修改了 sub pass 的 buffer
然后用到这个 buffer 的不止一处,还有在别的地方有用
之后他这里在源码控制这里创建了一个新的分支,这样就可以方便比较某一个分支和当前修改的区别
这就很方便,尤其是在有一些琐碎的修改的时候,比如像现在这种修改 even 和 odd 的,很容易看少
像这样,一开始少了一些
根据对比,就知道哪些代码自己改过,哪些没改过
在 engine\source\runtime\function\render\passes\main_camera_pass.cpp
vignette_pass 之后那一大片都要改
RHIAttachmentReference vignette_pass_input_attachment_reference {}; vignette_pass_input_attachment_reference.attachment = &backup_odd_color_attachment_description - attachments; vignette_pass_input_attachment_reference.layout = RHI_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; RHIAttachmentReference vignette_pass_color_attachment_reference {}; if (m_enable_fxaa) { vignette_pass_color_attachment_reference.attachment = &post_process_even_color_attachment_description - attachments; } else { vignette_pass_color_attachment_reference.attachment = &backup_even_color_attachment_description - attachments; } vignette_pass_color_attachment_reference.layout = RHI_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; RHISubpassDescription& vignette_pass = subpasses[_main_camera_subpass_vignette]; vignette_pass.pipelineBindPoint = RHI_PIPELINE_BIND_POINT_GRAPHICS; vignette_pass.inputAttachmentCount = 1; vignette_pass.pInputAttachments = &vignette_pass_input_attachment_reference; vignette_pass.colorAttachmentCount = 1; vignette_pass.pColorAttachments = &vignette_pass_color_attachment_reference; vignette_pass.pDepthStencilAttachment = NULL; vignette_pass.preserveAttachmentCount = 0; vignette_pass.pPreserveAttachments = NULL; RHIAttachmentReference fxaa_pass_input_attachment_reference {}; if (m_enable_fxaa) { fxaa_pass_input_attachment_reference.attachment = &post_process_even_color_attachment_description - attachments; } else { fxaa_pass_input_attachment_reference.attachment = &backup_odd_color_attachment_description - attachments; } fxaa_pass_input_attachment_reference.layout = RHI_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; RHIAttachmentReference fxaa_pass_color_attachment_reference {}; fxaa_pass_color_attachment_reference.attachment = &backup_even_color_attachment_description - attachments; fxaa_pass_color_attachment_reference.layout = RHI_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; RHISubpassDescription& fxaa_pass = subpasses[_main_camera_subpass_fxaa]; fxaa_pass.pipelineBindPoint = RHI_PIPELINE_BIND_POINT_GRAPHICS; fxaa_pass.inputAttachmentCount = 1; fxaa_pass.pInputAttachments = &fxaa_pass_input_attachment_reference; fxaa_pass.colorAttachmentCount = 1; fxaa_pass.pColorAttachments = &fxaa_pass_color_attachment_reference; fxaa_pass.pDepthStencilAttachment = NULL; fxaa_pass.preserveAttachmentCount = 0; fxaa_pass.pPreserveAttachments = NULL; RHIAttachmentReference ui_pass_color_attachment_reference {}; ui_pass_color_attachment_reference.attachment = &backup_odd_color_attachment_description - attachments; ui_pass_color_attachment_reference.layout = RHI_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; uint32_t ui_pass_preserve_attachment = &backup_even_color_attachment_description - attachments; RHISubpassDescription& ui_pass = subpasses[_main_camera_subpass_ui]; ui_pass.pipelineBindPoint = RHI_PIPELINE_BIND_POINT_GRAPHICS; ui_pass.inputAttachmentCount = 0; ui_pass.pInputAttachments = NULL; ui_pass.colorAttachmentCount = 1; ui_pass.pColorAttachments = &ui_pass_color_attachment_reference; ui_pass.pDepthStencilAttachment = NULL; ui_pass.preserveAttachmentCount = 1; ui_pass.pPreserveAttachments = &ui_pass_preserve_attachment; RHIAttachmentReference combine_ui_pass_input_attachments_reference[2] = {}; combine_ui_pass_input_attachments_reference[0].attachment = &backup_even_color_attachment_description - attachments; combine_ui_pass_input_attachments_reference[0].layout = RHI_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; combine_ui_pass_input_attachments_reference[1].attachment = &backup_odd_color_attachment_description - attachments; combine_ui_pass_input_attachments_reference[1].layout = RHI_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
engine\source\runtime\function\render\render_pipeline.cpp
中也是一大片要改
VignettePassInitInfo vignette_init_info; vignette_init_info.render_pass = _main_camera_pass->getRenderPass(); vignette_init_info.input_attachment = _main_camera_pass->getFramebufferImageViews()[_main_camera_pass_backup_buffer_odd]; m_vignette_pass->initialize(&vignette_init_info); UIPassInitInfo ui_init_info; ui_init_info.render_pass = _main_camera_pass->getRenderPass(); m_ui_pass->initialize(&ui_init_info); CombineUIPassInitInfo combine_ui_init_info; combine_ui_init_info.render_pass = _main_camera_pass->getRenderPass(); combine_ui_init_info.scene_input_attachment = _main_camera_pass->getFramebufferImageViews()[_main_camera_pass_backup_buffer_even]; combine_ui_init_info.ui_input_attachment = _main_camera_pass->getFramebufferImageViews()[_main_camera_pass_backup_buffer_odd]; m_combine_ui_pass->initialize(&combine_ui_init_info); PickPassInitInfo pick_init_info; pick_init_info.per_mesh_layout = descriptor_layouts[MainCameraPass::LayoutType::_per_mesh]; m_pick_pass->initialize(&pick_init_info); FXAAPassInitInfo fxaa_init_info; fxaa_init_info.render_pass = _main_camera_pass->getRenderPass(); fxaa_init_info.input_attachment = _main_camera_pass->getFramebufferImageViews()[_main_camera_pass_post_process_buffer_even]; m_fxaa_pass->initialize(&fxaa_init_info);
vignette.frag 写为
这里新传入了一个 uv 参数,然后使用了一个距离场
#version 310 es #extension GL_GOOGLE_include_directive : enable #include "constants.h" layout(input_attachment_index = 0, set = 0, binding = 0) uniform highp subpassInput in_color; layout(location = 0) in highp vec2 in_uv; layout(location = 0) out highp vec4 out_color; void main() { highp vec4 color = subpassLoad(in_color).rgba; highp float cutoff = 0.4f; highp float exponent = 1.5f; highp float len = length(in_uv - 0.5f); highp float ratio = pow(cutoff/len, exponent); out_color = color * min(ratio, 1.0f); }
复制 post_process.vert 改为
engine\shader\glsl\vignette.vert
传出 uv 参数
#version 310 es
#extension GL_GOOGLE_include_directive : enable
#include "constants.h"
layout(location = 0) out vec2 out_uv;
void main()
{
const vec3 fullscreen_triangle_positions[3] =
vec3[3](vec3(3.0, 1.0, 0.5), vec3(-1.0, 1.0, 0.5), vec3(-1.0, -3.0, 0.5));
gl_Position = vec4(fullscreen_triangle_positions[gl_VertexIndex], 1.0);
out_uv = (fullscreen_triangle_positions[gl_VertexIndex].xy + 1.0f)/2.0f;
}
在 engine\source\runtime\function\render\passes\vignette_pass.cpp 中使用 vignette 顶点着色器
RHIShader* vert_shader_module = m_rhi->createShaderModule(VIGNETTE_VERT);
void VignettePass::setupDescriptorSetLayout()
中
由
RHIDescriptorSetLayoutBinding post_process_global_layout_bindings[2] = {};
RHIDescriptorSetLayoutBinding& post_process_global_layout_input_attachment_binding =
post_process_global_layout_bindings[0];
post_process_global_layout_input_attachment_binding.binding = 0;
post_process_global_layout_input_attachment_binding.descriptorType = RHI_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
post_process_global_layout_input_attachment_binding.descriptorCount = 1;
post_process_global_layout_input_attachment_binding.stageFlags = RHI_SHADER_STAGE_FRAGMENT_BIT;
RHIDescriptorSetLayoutBinding& post_process_global_layout_LUT_binding = post_process_global_layout_bindings[1];
post_process_global_layout_LUT_binding.binding = 1;
post_process_global_layout_LUT_binding.descriptorType = RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
post_process_global_layout_LUT_binding.descriptorCount = 1;
post_process_global_layout_LUT_binding.stageFlags = RHI_SHADER_STAGE_FRAGMENT_BIT;
改为
RHIDescriptorSetLayoutBinding post_process_global_layout_bindings[1] = {};
RHIDescriptorSetLayoutBinding& post_process_global_layout_input_attachment_binding =
post_process_global_layout_bindings[0];
post_process_global_layout_input_attachment_binding.binding = 0;
post_process_global_layout_input_attachment_binding.descriptorType = RHI_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
post_process_global_layout_input_attachment_binding.descriptorCount = 1;
post_process_global_layout_input_attachment_binding.stageFlags = RHI_SHADER_STAGE_FRAGMENT_BIT;
删去贴图的输入
void VignettePass::updateAfterFramebufferRecreate(RHIImageView* input_attachment)
中
RHIDescriptorImageInfo post_process_per_frame_input_attachment_info = {}; post_process_per_frame_input_attachment_info.sampler = m_rhi->getOrCreateDefaultSampler(Default_Sampler_Nearest); post_process_per_frame_input_attachment_info.imageView = input_attachment; post_process_per_frame_input_attachment_info.imageLayout = RHI_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; RHIDescriptorImageInfo vignette_LUT_image_info = {}; vignette_LUT_image_info.sampler = m_rhi->getOrCreateDefaultSampler(Default_Sampler_Linear); vignette_LUT_image_info.imageView = m_global_render_resource->_color_grading_resource._color_grading_LUT_texture_image_view; vignette_LUT_image_info.imageLayout = RHI_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; RHIWriteDescriptorSet post_process_descriptor_writes_info[2]; RHIWriteDescriptorSet& post_process_descriptor_input_attachment_write_info = post_process_descriptor_writes_info[0]; post_process_descriptor_input_attachment_write_info.sType = RHI_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; post_process_descriptor_input_attachment_write_info.pNext = NULL; post_process_descriptor_input_attachment_write_info.dstSet = m_descriptor_infos[0].descriptor_set; post_process_descriptor_input_attachment_write_info.dstBinding = 0; post_process_descriptor_input_attachment_write_info.dstArrayElement = 0; post_process_descriptor_input_attachment_write_info.descriptorType = RHI_DESCRIPTOR_TYPE_INPUT_ATTACHMENT; post_process_descriptor_input_attachment_write_info.descriptorCount = 1; post_process_descriptor_input_attachment_write_info.pImageInfo = &post_process_per_frame_input_attachment_info; RHIWriteDescriptorSet& post_process_descriptor_LUT_write_info = post_process_descriptor_writes_info[1]; post_process_descriptor_LUT_write_info.sType = RHI_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; post_process_descriptor_LUT_write_info.pNext = NULL; post_process_descriptor_LUT_write_info.dstSet = m_descriptor_infos[0].descriptor_set; post_process_descriptor_LUT_write_info.dstBinding = 1; post_process_descriptor_LUT_write_info.dstArrayElement = 0; post_process_descriptor_LUT_write_info.descriptorType = RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; post_process_descriptor_LUT_write_info.descriptorCount = 1; post_process_descriptor_LUT_write_info.pImageInfo = &vignette_LUT_image_info;
改为
RHIDescriptorImageInfo post_process_per_frame_input_attachment_info = {}; post_process_per_frame_input_attachment_info.sampler = m_rhi->getOrCreateDefaultSampler(Default_Sampler_Nearest); post_process_per_frame_input_attachment_info.imageView = input_attachment; post_process_per_frame_input_attachment_info.imageLayout = RHI_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; RHIWriteDescriptorSet post_process_descriptor_writes_info[1]; RHIWriteDescriptorSet& post_process_descriptor_input_attachment_write_info = post_process_descriptor_writes_info[0]; post_process_descriptor_input_attachment_write_info.sType = RHI_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; post_process_descriptor_input_attachment_write_info.pNext = NULL; post_process_descriptor_input_attachment_write_info.dstSet = m_descriptor_infos[0].descriptor_set; post_process_descriptor_input_attachment_write_info.dstBinding = 0; post_process_descriptor_input_attachment_write_info.dstArrayElement = 0; post_process_descriptor_input_attachment_write_info.descriptorType = RHI_DESCRIPTOR_TYPE_INPUT_ATTACHMENT; post_process_descriptor_input_attachment_write_info.descriptorCount = 1; post_process_descriptor_input_attachment_write_info.pImageInfo = &post_process_per_frame_input_attachment_info;
删去贴图的输入
顶部的头文件改成
#include <vignette_vert.h>
然后就做完了
总之还是有很多不懂……
可见,
别人的答案:
哈西法
感觉是不是可以考虑render graph的方式,每个render pass 定义shader、输入、输出,根据这些信息可以排序render pass,然后每个输入输出都定义一个资源,使用一个resource pool进行这些资源的管理。为了简化代码,需要保证每个renderpass的输入输出名字不同,虽然会增加内存占用,但是可以减少很多问题
哎哟小Z
第一个问题参考UE的RDG,将Pass进行封装处理
第二个问题参考UE的材质,提供几种shader框架,然后在材质中记录好的蓝图图表编译为shader,插入到固定的几个计算位置编译成一个完整的shader(即着色器编译)
完全不懂……
真的要学 opengl 和 vulkan 了
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。