一个沉迷感十足的游戏,其场景中的全局光照成果肯定功不可没。

动静漫反射全局光照(DDGI)带来的光影变动,是细腻延展的视觉语言,让场景中每种色彩都有了“五彩斑斓”的诠释,场景布局光影,物体关系立显,环境温度来临,拓展了画面信息传播的档次,点睛之笔。

间接光渲染 VS 动静漫反射全局光照

细腻的光照视觉语言带来的技术挑战不小。不同材质外表与光照产生的出现成果千差万别,漫反射(Diffuse)将光照信息平均散射,光线的强弱、光照动势、物体外表材质的变换等,面对这些浮动的变量,平台性能和算力备受考验。

针对全局光照需克服的简单“症状”,HMS Core图形引擎服务提供了一套实时动静漫反射全局光照(DDGI)技术,面向挪动端,可扩大到全平台,无需预烘培。基于光照探针(Light Probe)管线,在Probe更新和着色时提出改良算法,升高原有管线的计算负载。实现屡次反射信息的全局光照,晋升了渲染真实感,满足挪动终端设备实时性、互动性要求。

并且,实现一个沉迷感满满的动静漫反射全局光照,几步就能轻松搞定!

Demo示例

开发指南

步骤阐明

1.初始化阶段:设置Vulkan运行环境,初始化DDGIAPI类。

2.筹备阶段:

创立用于保留DDGI渲染后果的两张Texture,并将Texture的信息传递给DDGI。

筹备DDGI插件须要的Mesh、Material、Light、Camera、分辨率等信息,并将其传递给DDGI。

设置DDGI参数。

3.渲染阶段

如果场景的Mesh变换矩阵、Light、Camera信息有变动,则同步更新到DDGI侧。

调用Render()函数,DDGI的渲染后果保留在筹备阶段创立的Texture中。

将DDGI的后果融入到着色计算中。

美术限度

1.对于想要使能DDGI成果的场景,DDGI的origin参数应该设置为场景的核心,并设置相应步长和Probe数量使得DDGI Volume能笼罩整个场景。

2.为了让DDGI取得适合的遮挡成果,请防止用没有厚度的墙;如果墙的厚度绝对于Probe的密度显得太薄了,会呈现漏光(light leaking)景象。同时,形成墙的立体最好是单面(single-sided)的,也即墙是由两个单面立体组成。

3.因为是挪动端的DDGI计划,因而从性能和功耗角度登程,有以下倡议:①管制传到SDK侧的几何数量(倡议5万顶点以内),比方只将场景中的会产生间接光的主体构造传到SDK;②尽量应用适合的Probe密度和数量,尽量不要超过101010。以上倡议以最终的出现后果为主。

开发步骤

1、下载插件的SDK包,解压后获取DDGI SDK相干文件,其中包含1个头文件和2个so文件。Android平台应用的so库文件下载地址请参见:动静漫反射全局光照插件。

2、该插件反对Android平台,应用CMake进行构建。以下是CMakeLists.txt局部片段仅供参考:

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.soADD_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 2void 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 最新技术资讯~