简介

HUAWEI CG Kit提供一套基于Vulkan图形接口的高性能渲染框架,具备PBR材质,模型,纹理,光照,组件等系统。此渲染框架针对华为DDK特性及实现细节进行专属设计,提供华为平台最优的3D渲染能力。此渲染框架具备二次开发能力,可以大大降低应用开发者的开发难度和复杂度,提高开发效率。
开发者需要:

本篇Codelab将实现的内容

在这个Codelab中,你将使用已经创建好的Demo Project实现对HUAWEI CG Kit API的调用,通过Demo Project你可以体验到:

您将会学到什么

硬件要求

软件要求

需要的知识点

集成HUAWEI CG Kit能力,需要完成以下准备工作:

  1. 注册成为开发者
  2. 创建应用
  3. 生成签名证书指纹
  4. 配置签名证书指纹

具体操作,请按照CG Kit 开发指南中详细说明来完成。

集成CG SDK

1. 创建Android Studio Project。
2. 修改/app/build.gradle文件,指定CMake编译C++文件,在/app/build.gradle文件中,创建CMake的编译依赖。

同时配置NDK过滤项:

3. 复制库和头文件。

(1)下载SDK包,获取路径:SDK下载

(2)复制SDK头文件到资源库。

将SDK中头文件复制到Android Studio下的src/main/cpp/include下:

(3)复制SDK so到资源库。

将SDK中"libs\arm64-v8a\libcgkit.so"、"libs\armeabi-v7a\libcgkit.so"分别复制到Android Studio下的/libs/arm64-v8a和/libs/armeabi-v7a,如下:

(4)修改"app/src/main/cpp/CMakeLists.txt",请使用以下代码覆盖原CMakeLists。注意:文件目录CGRenderingFramework可修改为用户自定义名称。

cmake_minimum_required(VERSION 3.4.1) include_directories( ${CMAKE_SOURCE_DIR}/include/CGRenderingFramework ) include_directories( ${CMAKE_SOURCE_DIR}/include/MainApplication ) add_library( main-lib SHARED source/Main.cpp source/MainApplication.cpp) ADD_LIBRARY( cgkit SHARED IMPORTED) set_target_properties(cgkit PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/../../../libs/${ANDROID_ABI}/libcgkit.so ) SET( VULKAN_INCLUDE_DIR "$ENV{VULKAN_SDK}/include") #"${ANDROID_NDK}/sources/third_party/vulkan/src/include") include_directories(${VULKAN_INCLUDE_DIR}) SET( NATIVE_APP_GLUE_DIR "${ANDROID_NDK}/sources/android/native_app_glue") FILE( GLOB NATIVE_APP_GLUE_FILLES "${NATIVE_APP_GLUE_DIR}/*.c" "${NATIVE_APP_GLUE_DIR}/*.h") ADD_LIBRARY(native_app_glue STATIC ${NATIVE_APP_GLUE_FILLES}) TARGET_INCLUDE_DIRECTORIES( native_app_glue PUBLIC ${NATIVE_APP_GLUE_DIR}) find_library( log-lib log ) target_link_libraries( main-lib cgkit native_app_glue android ${log-lib} ) SET( CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")

点击"Sync Project with Gradle Files"即可。

制作模型数据

1. 制作CubeMap。
(1)编写CubeMap配置文件(文本格式,后缀名为cub)。

width=xxx (每个面纹理像素宽度)
height=xxx (每个面纹理像素高度)
depth=xxx (每个面纹理像素深度)
mipmap=xxx (MipMap层级数量)
face=xxx (立方体的面的数量)
channel=xxx(每个面纹理的RGBA通道数,如4)
suffix=xxx (每个面纹理的格式,如.png)

(2)将CubeMap各个面的纹理(png格式)排列在配置文件同一目录下(如assets/cubemaps/env),并保持以下命名格式(i为MipMap层级数,从0开始)。

cubeface_neg_xi(立方体的左侧面) cubeface_neg_yi(立方体的底面) cubeface_neg_zi(立方体的前侧面) cubeface_pos_xi(立方体的右侧面) cubeface_pos_yi(立方体的顶面) cubeface_pos_zi(立方体的后侧面)

2. 编写顶点Shader。

(1)顶点数据结构(location必须与如下代码示例保持一致)。

layout(location=0) in vec3 position; layout(location=1) in vec2 texcoord; layout(location=2) in vec3 normal; layout(location=3) in vec3 tangent;

(2)全局常量数据结构(set、binding设置及数据结构与如下代码示例保持一致)。

layout(set=0, binding=0) uniform GlobalUniform { mat4 view; mat4 projection; mat4 projectionOrtho; mat4 viewProjection; mat4 viewProjectionInv; mat4 viewProjectionOrtho; vec4 resolution; vec4 cameraPosition; }

3. 编写像素Shader。

(1)光照数据结构(set、binding设置及数据结构与如下代码示例保持一致)。

#define MAX_FORWARD_LIGHT_COUNT = 16 #define DIRECTIONAL_LIGHT 0 #define POINT_LIGHT 1 #define SPOT_LIGHT 2 struct LightData { vec4 color; // xyz为颜色信息,w为强度信息 vec4 position; // xyz为位置信息 vec4 direction; // xyz为方向信息 vec4 type_angle; // x为类型信息 } layout(set=0, binding=6) uniform LightsInfos { LightData lights[MAX_FORWARD_LIGHT_COUNT]; uint count; // 当前光源个数 }

(2)纹理通道(名称与如下代码示例保持一致)。

albedoTexture(基本贴图,对应Material的TEXTURE_TYPE_ALBEDO类型) normalTexture(法线贴图,对应Material的TEXTURE_TYPE_NORMAL类型) pbrTexture(pbr参数贴图(x通道为ao数据,y通道为粗糙度数据,z通道为金属的数据),对应Material的TEXTURE_TYPE_PBRTEXTURE类型) emissionTexture(自发光贴图,对应Material的TEXTURE_TYPE_EMISSION类型) envTexture(环境贴图,对应Material的TEXTURE_TYPE_ENVIRONMENTMAP类型)

演示Demo开发步骤

1. 创建运行实例对象。
Main.cpp是演示Demo的入口类。
实例化MainApplication,该对象继承BaseApplication对象。启动渲染主流程MainLoop()。示例如下:

void android_main(android_app* state) { auto app = CreateMainApplication(); // 实例化演示Demo主界面 if (app == nullptr) { return; } app->Start(reinterpret_cast<void*>(state)); // 启动平台渲染 app->MainLoop(); // 启动渲染主流程 CG_SAFE_DELETE(app); }

2. 开启日志Log用以打印调试信息。

由CGKIT_LOG 宏控制,须在文件开始定义CGKIT_LOG,然后在start函数中设置日志的级别。日志的级别:LOG_VERBOSE(详细信息日志)、LOG_DEBUG(debug信息日志)、LOG_INFO(提示信息日志)、LOG_WARNING(warning信息日志)、LOG_ERROR(error信息日志)。具体使用请参考第3步。

void MainApplication::Start(void* param) { BaseApplication::Start(param); // 这里设置日志等级为LOG_VERBOSE,用以覆盖cgkit默认的日志级别 // 请注意调用顺序,用户自定义的日志级别应该在cgkit的Start之后,否则会被覆盖 Log::SetLogLevel(LOG_VERBOSE); }

3. Demo功能实现类MainApplication.cpp中创建Camera实例并设置各项参数。

您需要通过实例化Camera场景对象cameraObj来获取Camera对象指针mainCamera,并设置mainCamera参数(投射角度和类型、目标位置、视点位置等),最终将实例mainCamera加入到全局的场景控制实例gSceneManager中。,如果实例化失败,则对已实例化的对象进行安全删除。示例如下:

void MainApplication::InitScene() { LOGINFO("MainApplication InitScene."); BaseApplication::InitScene(); // step 1:Add camera LOGINFO("Enter init main camera."); SceneObject* cameraObj = CG_NEW SceneObject(nullptr); if (cameraObj == nullptr) { LOGERROR("Failed to create camera object."); return; } Camera* mainCamera = cameraObj->AddComponent<Camera>(); if (mainCamera == nullptr) { CG_SAFE_DELETE(cameraObj); LOGERROR("Failed to create main camera."); return; } const f32 FOV = 60.f; const f32 NEAR = 0.1f; const f32 FAR = 500.0f; const Vector3 EYE_POSITION(0.0f, 0.0f, 0.0f); cameraObj->SetPosition(EYE_POSITION); mainCamera->SetProjectionType(ProjectionType::PROJECTION_TYPE_PERSPECTIVE); mainCamera->SetPerspective(FOV, gCGKitInterface.GetAspectRatio(), NEAR, FAR); mainCamera->SetViewport(0, 0, gCGKitInterface.GetScreenWidth(), gCGKitInterface.GetScreenHeight()); gSceneManager.SetMainCamera(mainCamera); }

4. 创建场景对象并加载模型、纹理等数据。
示例如下:

void MainApplication::InitScene() { // step 2:Load default model String modelName = "models/Avatar/body.obj"; // 此处可替换成您生成的模型数据存放路径 Model* model = dynamic_cast<Model*>(gResourceManager.Get(modelName)); // step 3:New SceneObject and add SceneObject to SceneManager MeshRenderer* meshRenderer = nullptr; SceneObject* object = gSceneManager.CreateSceneObject(); if (object != nullptr) { // step 4:Add MeshRenderer Component to SceneObject meshRenderer = object->AddComponent<MeshRenderer>(); // step 5:Relate model's submesh to MeshRenderer if (meshRenderer != nullptr && model != nullptr && model->GetMesh() != nullptr) { meshRenderer->SetMesh(model->GetMesh()); } else { LOGERROR("Failed to add mesh renderer."); } } else { LOGERROR("Failed to create scene object."); } if (model != nullptr) { const Mesh* mesh = model->GetMesh(); if (mesh != nullptr) { LOGINFO("Model submesh count %d.", mesh->GetSubMeshCount()); LOGINFO("Model vertex count %d.", mesh->GetVertexCount()); // step 6:Load Texture String texAlbedo = "models/Avatar/Albedo_01.png"; String texNormal = "models/Avatar/Normal_01.png"; String texPbr = "models/Avatar/Pbr_01.png"; String texEmissive = "shaders/pbr_brdf.png"; u32 subMeshCnt = mesh->GetSubMeshCount(); for (u32 i = 0; i < subMeshCnt; ++i) { SubMesh* subMesh = mesh->GetSubMesh(i); if (subMesh == nullptr) { LOGERROR("Failed to get submesh."); continue; } // step 7:Add Material Material *material = dynamic_cast<Material*>( gResourceManager.Get(ResourceType::RESOURCE_TYPE_MATERIAL)); if (material == nullptr) { LOGERROR("Failed to create new material."); return; } material->Init(); material->SetSubMesh(subMesh); material->SetTexture(TextureType::TEXTURE_TYPE_ALBEDO, texAlbedo); material->SetSamplerParam(TextureType::TEXTURE_TYPE_ALBEDO, SAMPLER_FILTER_BILINEAR, SAMPLER_FILTER_BILINEAR, SAMPLER_MIPMAP_BILINEAR, SAMPLER_ADDRESS_CLAMP); material->SetTexture(TextureType::TEXTURE_TYPE_NORMAL, texNormal); material->SetSamplerParam(TextureType::TEXTURE_TYPE_NORMAL, SAMPLER_FILTER_BILINEAR, SAMPLER_FILTER_BILINEAR, SAMPLER_MIPMAP_BILINEAR, SAMPLER_ADDRESS_CLAMP); material->SetTexture(TextureType::TEXTURE_TYPE_PBRTEXTURE, texPbr); material->SetSamplerParam(TextureType::TEXTURE_TYPE_PBRTEXTURE, SAMPLER_FILTER_BILINEAR, SAMPLER_FILTER_BILINEAR, SAMPLER_MIPMAP_BILINEAR, SAMPLER_ADDRESS_CLAMP); material->SetTexture(TextureType::TEXTURE_TYPE_EMISSION, texEmissive); material->SetSamplerParam(TextureType::TEXTURE_TYPE_EMISSION, SAMPLER_FILTER_BILINEAR, SAMPLER_FILTER_BILINEAR, SAMPLER_MIPMAP_BILINEAR, SAMPLER_ADDRESS_CLAMP); material->SetTexture(TextureType::TEXTURE_TYPE_ENVIRONMENTMAP, m_envMap); material->SetSamplerParam(TextureType::TEXTURE_TYPE_ENVIRONMENTMAP, SAMPLER_FILTER_BILINEAR, SAMPLER_FILTER_BILINEAR, SAMPLER_MIPMAP_BILINEAR, SAMPLER_ADDRESS_CLAMP); material->AttachShaderStage(ShaderStageType::SHADER_STAGE_TYPE_VERTEX, "shaders/pbr_vert.spv"); material->AttachShaderStage(ShaderStageType::SHADER_STAGE_TYPE_FRAGMENT, "shaders/pbr_frag.spv"); material->SetCullMode(CULL_MODE_NONE); material->SetDepthTestEnable(true); material->SetDepthWriteEnable(true); material->Create(); meshRenderer->SetMaterial(i, material); } } else { LOGERROR("Failed to get mesh."); } } else { LOGERROR("Failed to load model."); } m_sceneObject = object; if (m_sceneObject != nullptr){ m_sceneObject->SetPosition(SCENE_OBJECT_POSITION); m_sceneObject->SetScale(SCENE_OBJECT_SCALE); } m_objectRotation = Math::PI; }

5. 创建天空盒。
示例如下:

void MainApplication::InitScene() { // step 8:create sky box SceneObject* skyboxObj = CreateSkybox(); if(skyboxObj != nullptr) { skyboxObj->SetScale(Vector3(100.f, 100.f, 100.f)); } } SceneObject* MainApplication::CreateSkybox() { String modelName = "models/test-cube.obj"; Model* model = dynamic_cast<Model *>(gResourceManager.Get(modelName)); const Mesh* mesh = model->GetMesh(); // load Texture u32 subMeshCnt = mesh->GetSubMeshCount(); // Add to scene SceneObject* sceneObj = gSceneManager.CreateSceneObject(); MeshRenderer* meshRenderer = sceneObj->AddComponent<MeshRenderer>(); meshRenderer->SetMesh(model->GetMesh()); for (u32 i = 0; i < subMeshCnt; ++i) { SubMesh* subMesh = mesh->GetSubMesh(i); // add Material Material* material = dynamic_cast<Material*>(gResourceManager.Get(ResourceType::RESOURCE_TYPE_MATERIAL)); material->Init(); material->SetSubMesh(subMesh); material->SetTexture(TextureType::TEXTURE_TYPE_ENVIRONMENTMAP, m_envMap); material->SetSamplerParam(TextureType::TEXTURE_TYPE_ENVIRONMENTMAP, SAMPLER_FILTER_BILINEAR, SAMPLER_FILTER_BILINEAR, SAMPLER_MIPMAP_BILINEAR, SAMPLER_ADDRESS_CLAMP); material->AttachShaderStage(ShaderStageType::SHADER_STAGE_TYPE_VERTEX, "shaders/sky_vert.spv"); material->AttachShaderStage(ShaderStageType::SHADER_STAGE_TYPE_FRAGMENT, "shaders/sky_frag.spv"); material->SetCullMode(CULL_MODE_NONE); material->SetDepthTestEnable(true); material->SetDepthWriteEnable(true); material->Create(); meshRenderer->SetMaterial(i, material); } sceneObj->SetScale(Vector3(1.f, 1.f, 1.f)); sceneObj->SetPosition(Vector3(0.0f, 0.0f, 0.0f)); sceneObj->SetRotation(Vector3(0.0f, 0.0, 0.0)); return sceneObj; }

6. 创建Light。
示例如下:

void MainApplication::InitScene() { // step 9:Add light LOGINFO("Enter init light."); SceneObject* lightObject = CG_NEW SceneObject(nullptr); if (lightObject != nullptr) { Light* lightCom = lightObject->AddComponent<Light>(); if (lightCom != nullptr) { lightCom->SetColor(Vector3::ONE); const Vector3 DIRECTION_LIGHT_DIR(0.1f, 0.2f, 1.0f); lightCom->SetDirection(DIRECTION_LIGHT_DIR); lightCom->SetLightType(LIGHT_TYPE_DIRECTIONAL); LOGINFO("Left init light."); } else { LOGERROR("Failed to add component light."); } } else { LOG_ALLOC_ERROR("New light object failed."); } SceneObject* pointLightObject = CG_NEW SceneObject(nullptr); if (pointLightObject != nullptr) { m_pointLightObject = pointLightObject; Light* lightCom = pointLightObject->AddComponent<Light>(); if (lightCom != nullptr) { const Vector3 POINT_LIGHT_COLOR(0.0, 10000.0f, 10000.0f); lightCom->SetColor(POINT_LIGHT_COLOR); lightCom->SetLightType(LIGHT_TYPE_POINT); } else { LOGERROR("Failed to add component light."); } } else { LOG_ALLOC_ERROR("New light object failed."); } }

7. 处理手势动作。

示例如下:

void MainApplication::ProcessInputEvent(const InputEvent *inputEvent) { BaseApplication::ProcessInputEvent(inputEvent); LOGINFO("MainApplication ProcessInputEvent."); EventSource source = inputEvent->GetSource(); if (source == EVENT_SOURCE_TOUCHSCREEN) { const TouchInputEvent* touchEvent = reinterpret_cast<const TouchInputEvent *>(inputEvent); if (touchEvent->GetAction() == TOUCH_ACTION_DOWN) { // Action为触摸按下,将touch事件开始标志位置为true,计算移动和缩放数据。 LOGINFO("Action move start."); m_touchBegin = true; } else if (touchEvent->GetAction() == TOUCH_ACTION_MOVE) { // Action为触摸移动,设置模型旋转、缩放参数 float touchPosDeltaX = touchEvent->GetPosX(touchEvent->GetTouchIndex()) - m_touchPosX; float touchPosDeltaY = touchEvent->GetPosY(touchEvent->GetTouchIndex()) - m_touchPosY; if (m_touchBegin) { // 设置模型旋转 if (fabs(touchPosDeltaX) > 2.f) { if (touchPosDeltaX > 0.f) { m_objectRotation -= 2.f * m_deltaTime; } else { m_objectRotation += 2.f * m_deltaTime; } LOGINFO("Set rotation start."); } // 设置模型缩放 if (fabs(touchPosDeltaY) > 3.f) { if (touchPosDeltaY > 0.f) { m_objectScale -= 0.25f * m_deltaTime; } else { m_objectScale += 0.25f * m_deltaTime; } m_objectScale = std::min(1.25f, std::max(0.75f, m_objectScale)); LOGINFO("Set scale start."); } } } else if (touchEvent->GetAction() == TOUCH_ACTION_UP) { // Action为触摸抬起,则touch事件结束,标志位置为false LOGINFO("Action up."); m_touchBegin = false; } else if (touchEvent->GetAction() == TOUCH_ACTION_CANCEL) { LOGINFO("Action cancel."); m_touchBegin = false; } m_touchPosX = touchEvent->GetPosX(touchEvent->GetTouchIndex()); m_touchPosY = touchEvent->GetPosY(touchEvent->GetTouchIndex()); } }

8. 实现模型旋转&缩放。
示例如下:

void MainApplication::Update(float deltaTime) { LOGINFO("Update %f.", deltaTime); m_deltaTime = deltaTime; m_deltaAccumulate += m_deltaTime; if (m_sceneObject != nullptr) { m_sceneObject->SetRotation(Vector3(0.0, m_objectRotation, 0.0)); // 模型旋转 m_sceneObject->SetScale(SCENE_OBJECT_SCALE * m_objectScale); // 模型缩放 } const float POINT_HZ_X = 0.2f; const float POINT_HZ_Y = 0.5f; const float POINT_LIGHT_CIRCLE = 50.f; if (m_pointLightObject) { m_pointLightObject->SetPosition(Vector3(sin(m_deltaAccumulate * POINT_HZ_X) * POINT_LIGHT_CIRCLE, sin(m_deltaAccumulate * POINT_HZ_Y) * POINT_LIGHT_CIRCLE + POINT_LIGHT_CIRCLE, cos(m_deltaAccumulate * POINT_HZ_X) * POINT_LIGHT_CIRCLE)); } BaseApplication::Update(deltaTime); }

9. 渲染效果
Kirin 990芯片渲染效果如下。

干得好,您已经成功完成了Codelab并学到了:

您可以阅读下面链接,了解更多相关的信息。
相关文档

本Codelab中所用Demo源码下载地址如下:

下载Source Code

已复制代码