动态漫反射全局光照(DDGI)带来的光影变化,是细腻延展的视觉语言,让场景中每种颜色都有了“五彩斑斓”的诠释,场景布局光影,物体关系立显,环境温度降临,拓展了画面信息传达的层次,点睛之笔。
直接光渲染 VS 动态漫反射全局光照
针对全局光照需克服的复杂“症状”,HMS Core图形引擎服务提供了一套实时动态漫反射全局光照(DDGI)技术,面向移动端,可扩展到全平台,无需预烘培。基于光照探针(Light Probe)管线,在Probe更新和着色时提出改进算法,降低原有管线的计算负载。实现多次反射信息的全局光照,提升了渲染真实感,满足移动终端设备实时性、互动性要求。
漫反射全局光照,几步就能轻松搞定!
Demo示例
开发指南
步骤说明
2.准备阶段:
准备DDGI插件需要的Mesh、Material、Light、Camera、分辨率等信息,并将其传递给DDGI。
3.渲染阶段
调用Render(函数,DDGI的渲染结果保存在准备阶段创建的Texture中。
美术限制
1.对于想要使能DDGI效果的场景,DDGI的origin参数应该设置为场景的中心,并设置相应步长和Probe数量使得DDGI Volume能覆盖整个场景。
3.由于是移动端的DDGI方案,因此从性能和功耗角度出发,有以下建议:①控制传到SDK侧的几何数量(建议5万顶点以内),比如只将场景中的会产生间接光的主体结构传到SDK;②尽量使用合适的Probe密度和数量,尽量不要超过101010。以上建议以最终的呈现结果为主。
开发步骤
动态漫反射全局光照插件。
cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR
set(NAME DDGIExample
project(${NAME}
set(PROJ_ROOT ${CMAKE_CURRENT_SOURCE_DIR}
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -O2 -DNDEBUG -DVK_USE_PLATFORM_ANDROID_KHR"
file(GLOB EXAMPLE_SRC "${PROJ_ROOT}/src/*.cpp" # 引入开发者主程序代码。
include_directories(${PROJ_ROOT}/include # 引入头文件,可以将DDGIAPI.h头文件放在此目录。
# 导入librtcore.so和libddgi.so
ADD_LIBRARY(rtcore SHARED IMPORTED
SET_TARGET_PROPERTIES(rtcore
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/libs/librtcore.so
ADD_LIBRARY(ddgi SHARED IMPORTED
SET_TARGET_PROPERTIES(ddgi
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/libs/libddgi.so
add_library(native-lib SHARED ${EXAMPLE_SRC}
target_link_libraries(
native-lib
...
ddgi # 链接ddgi库。
rtcore
android
log
z
...
3、设置Vulkan环境,初始化DDGIAPI类。
// 设置DDGI SDK需要的Vulkan环境信息。
// 包括logicalDevice, queue, queueFamilyIndex信息。
void DDGIExample::SetupDDGIDeviceInfo(
{
m_ddgiDeviceInfo.physicalDevice = physicalDevice;
m_ddgiDeviceInfo.logicalDevice = device;
m_ddgiDeviceInfo.queue = queue;
m_ddgiDeviceInfo.queueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics;
}
void DDGIExample::PrepareDDGI(
{
// 设置Vulkan环境信息。
SetupDDGIDeviceInfo(;
// 调用DDGI的初始化函数。
m_ddgiRender->InitDDGI(m_ddgiDeviceInfo;
...
}
void DDGIExample::Prepare(
{
...
// 创建DDGIAPI对象。
std::unique_ptr<DDGIAPI> m_ddgiRender = make_unique<DDGIAPI>(;
...
PrepareDDGI(;
...
}
4、创建两张Texture,用于保存相机视角的漫反射全局光照和法线深度图。为提高渲染性能,Texture支持降分辨率的设置。分辨率越小,渲染性能表现越好,但渲染结果的走样,例如边缘的锯齿等问题可能会更加严重。
// 创建用于保存渲染结果的Texture。
void DDGIExample::CreateDDGITexture(
{
VkImageUsageFlags usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
int ddgiTexWidth = width / m_shadingPara.ddgiDownSizeScale; // 纹理宽度。
int ddgiTexHeight = height / m_shadingPara.ddgiDownSizeScale; // 纹理高度。
glm::ivec2 size(ddgiTexWidth, ddgiTexHeight;
// 创建保存irradiance结果的Texture。
m_irradianceTex.CreateAttachment(vulkanDevice,
ddgiTexWidth,
ddgiTexHeight,
VK_FORMAT_R16G16B16A16_SFLOAT,
usage,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
m_defaultSampler;
// 创建保存normal和depth结果的Texture。
m_normalDepthTex.CreateAttachment(vulkanDevice,
ddgiTexWidth,
ddgiTexHeight,
VK_FORMAT_R16G16B16A16_SFLOAT,
usage,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
m_defaultSampler;
}
// 设置DDGIVulkanImage信息。
void DDGIExample::PrepareDDGIOutputTex(const vks::Texture& tex, DDGIVulkanImage *texture const
{
texture->image = tex.image;
texture->format = tex.format;
texture->type = VK_IMAGE_TYPE_2D;
texture->extent.width = tex.width;
texture->extent.height = tex.height;
texture->extent.depth = 1;
texture->usage = tex.usage;
texture->layout = tex.imageLayout;
texture->layers = 1;
texture->mipCount = 1;
texture->samples = VK_SAMPLE_COUNT_1_BIT;
texture->tiling = VK_IMAGE_TILING_OPTIMAL;
}
void DDGIExample::PrepareDDGI(
{
...
// 设置Texture分辨率。
m_ddgiRender->SetResolution(width / m_downScale, height / m_downScale;
// 设置用于保存渲染结果的DDGIVulkanImage信息。
PrepareDDGIOutputTex(m_irradianceTex, &m_ddgiIrradianceTex;
PrepareDDGIOutputTex(m_normalDepthTex, &m_ddgiNormalDepthTex;
m_ddgiRender->SetAdditionalTexHandler(m_ddgiIrradianceTex, AttachmentTextureType::DDGI_IRRADIANCE;
m_ddgiRender->SetAdditionalTexHandler(m_ddgiNormalDepthTex, AttachmentTextureType::DDGI_NORMAL_DEPTH;
...
}
void DDGIExample::Prepare(
{
...
CreateDDGITexture(;
...
PrepareDDGI(;
...
}
5、准备DDGI渲染所需的网格、材质、光源、相机数据。
// mesh结构体,支持submesh。
struct DDGIMesh {
std::string meshName;
std::vector<DDGIVertex> meshVertex;
std::vector<uint32_t> meshIndice;
std::vector<DDGIMaterial> materials;
std::vector<uint32_t> subMeshStartIndexes;
...
};
// 方向光结构体,当前仅支持1个方向光。
struct DDGIDirectionalLight {
CoordSystem coordSystem = CoordSystem::RIGHT_HANDED;
int lightId;
DDGI::Mat4f localToWorld;
DDGI::Vec4f color;
DDGI::Vec4f dirAndIntensity;
};
// 主相机结构体。
struct DDGICamera {
DDGI::Vec4f pos;
DDGI::Vec4f rotation;
DDGI::Mat4f viewMat;
DDGI::Mat4f perspectiveMat;
};
// 设置DDGI的光源信息。
void DDGIExample::SetupDDGILights(
{
m_ddgiDirLight.color = VecInterface(m_dirLight.color;
m_ddgiDirLight.dirAndIntensity = VecInterface(m_dirLight.dirAndPower;
m_ddgiDirLight.localToWorld = MatInterface(inverse(m_dirLight.worldToLocal;
m_ddgiDirLight.lightId = 0;
}
// 设置DDGI的相机信息。
void DDGIExample::SetupDDGICamera(
{
m_ddgiCamera.pos = VecInterface(m_camera.viewPos;
m_ddgiCamera.rotation = VecInterface(m_camera.rotation, 1.0;
m_ddgiCamera.viewMat = MatInterface(m_camera.matrices.view;
glm::mat4 yFlip = glm::mat4(1.0f;
yFlip[1][1] = -1;
m_ddgiCamera.perspectiveMat = MatInterface(m_camera.matrices.perspective * yFlip;
}
// 准备DDGI需要的网格信息。
// 以gltf格式的渲染场景为例。
void DDGIExample::PrepareDDGIMeshes(
{
for (constauto& node : m_models.scene.linearNodes {
DDGIMesh tmpMesh;
tmpMesh.meshName = node->name;
if (node->mesh {
tmpMesh.meshName = node->mesh->name; // 网格的名称。
tmpMesh.localToWorld = MatInterface(node->getMatrix(; // 网格的变换矩阵。
// 网格的骨骼蒙皮矩阵。
if (node->skin {
tmpMesh.hasAnimation = true;
for (auto& matrix : node->skin->inverseBindMatrices {
tmpMesh.boneTransforms.emplace_back(MatInterface(matrix;
}
}
// 网格的材质节点、顶点信息。
for (vkglTF::Primitive *primitive : node->mesh->primitives {
...
}
}
m_ddgiMeshes.emplace(std::make_pair(node->index, tmpMesh;
}
}
void DDGIExample::PrepareDDGI(
{
...
// 转换成DDGI需要的数据格式。
SetupDDGILights(;
SetupDDGICamera(;
PrepareDDGIMeshes(;
...
// 向DDGI传递数据。
m_ddgiRender->SetMeshs(m_ddgiMeshes;
m_ddgiRender->UpdateDirectionalLight(m_ddgiDirLight;
m_ddgiRender->UpdateCamera(m_ddgiCamera;
...
}
6、设置DDGI探针的位置、数量等参数。
// 设置DDGI算法参数。
void DDGIExample::SetupDDGIParameters(
{
m_ddgiSettings.origin = VecInterface(3.5f, 1.5f, 4.25f, 0.f;
m_ddgiSettings.probeStep = VecInterface(1.3f, 0.55f, 1.5f, 0.f;
...
}
void DDGIExample::PrepareDDGI(
{
...
SetupDDGIParameters(;
...
// 向DDGI传递数据。
m_ddgiRender->UpdateDDGIProbes(m_ddgiSettings;
...
}
7、调用DDGI的Prepare(函数解析前面传递的数据。
void DDGIExample::PrepareDDGI(
{
...
m_ddgiRender->Prepare(;
}
8、调用DDGI的Render(,将场景的间接光信息更新缓存到步骤4中设置的两张DDGI Texture中。
说明
如果不调用Render(函数,那么渲染结果为历史帧的结果。
#define RENDER_EVERY_NUM_FRAME 2
void DDGIExample::Draw(
{
...
// 每两帧调用一次DDGIRender(。
if (m_ddgiON && m_frameCnt % RENDER_EVERY_NUM_FRAME == 0 {
m_ddgiRender->UpdateDirectionalLight(m_ddgiDirLight; // 更新光源信息。
m_ddgiRender->UpdateCamera(m_ddgiCamera; // 更新相机信息。
m_ddgiRender->DDGIRender(; // DDGI渲染(执行一次,渲染结果保存在步骤4创建的Texture中。
}
...
}
void DDGIExample::Render(
{
if (!prepared {
return;
}
SetupDDGICamera(;
if (!paused || m_camera.updated {
UpdateUniformBuffers(;
}
Draw(;
m_frameCnt++;
}
9、叠加DDGI间接光结果,使用流程如下:
// 最终着色shader。
// 通过上采样计算屏幕空间坐标对应的DDGI值。
vec3 Bilateral(ivec2 uv, vec3 normal
{
...
}
void main(
{
...
vec3 result = vec3(0.0;
result += DirectLighting(;
result += IndirectLighting(;
vec3 DDGIIrradiances = vec3(0.0;
ivec2 texUV = ivec2(gl_FragCoord.xy;
texUV.y = shadingPara.ddgiTexHeight - texUV.y;
if (shadingPara.ddgiDownSizeScale == 1 { // 未降低分辨率。
DDGIIrradiances = texelFetch(irradianceTex, texUV, 0.xyz;
} else { // 降低分辨率。
ivec2 inDirectUV = ivec2(vec2(texUV / vec2(shadingPara.ddgiDownSizeScale;
DDGIIrradiances = Bilateral(inDirectUV, N;
}
result += DDGILighting(;
...
Image = vec4(result_t, 1.0;
}
了解更多详情>>
华为开发者联盟官网
获取开发指导文档
华为移动服务开源仓库地址:GitHub、Gitee
关注我们,第一时间了解 HMS Core 最新技术资讯~