每日分享最新,最流行的软件开发知识与最新行业趋势,希望大家能够一键三连,多多支持,跪求关注,点赞,留言。

每日分享最新,最流行的软件开发知识与最新行业趋势,希望大家能够一键三连,多多支持,跪求关注,点赞,留言。

DDGI 是实现逼真的照明效果的绝佳功能。了解如何为移动应用配备 DDGI。

DDGI 是实现逼真的照明效果的绝佳功能。了解如何为移动应用配备 DDGI。

为什么我们需要 DDGI
在使 3D 游戏身临其境的所有因素中,全局照明效果(包括反射、折射和阴影)无疑是皇冠上的明珠。简而言之,糟糕的照明会破坏原本出色的游戏体验。
一种用于创建真实照明的技术被称为动态漫反射全局照明(简称 DDGI)。该技术为游戏提供实时渲染,以精致和吸引人的视觉效果装饰游戏场景。换句话说,DDGI通过动态改变光照,呈现出场景中的每一种颜色,实现物体与场景温度之间的明显关系,丰富场景中信息的表示层次。

打开网易新闻 查看精彩图片

使用直接照明渲染的场景与使用 DDGI 渲染的场景
实现具有上图所示照明效果的场景需要强大的技术力量——这不是唯一的挑战。不同的材料以不同的方式对光作出反应。这种差异通过漫反射来表示,漫反射均匀地散射照明信息,包括照度、光移动方向和光移动速度。巧妙地处理所有这些变量需要具有海量计算能力的高性能开发平台。
HMS Core Scene Kit 的 DDGI 插件为所有这些挑战提供了一种解决方案。它支持移动应用程序,并且可以扩展到所有操作系统,无需预烘焙。利用光探针,插件在更新和着色探针时采用了改进的算法。这样,插件的计算负载比传统的DDGI方案低。该插件模拟光对物体表面的多次反射,以支持具有动态、交互式和逼真的照明效果的移动应用程序。

打开网易新闻 查看精彩图片

演示
场景中的美妙灯光效果是使用刚才提到的插件创建的,我没有说谎,只需几个简单的步骤即可完成。然后让我们深入了解如何为应用程序配备此插件的步骤。
开发流程
概述
1.初始化阶段:配置Vulkan环境,初始化DDGIAPI类。
2.准备阶段:
创建两个将存储 DDGI 插件的渲染结果的纹理,并将纹理信息传递给插件。
准备好所需的信息,然后将其传递给插件。此类信息包括网格、材料、光源、相机和分辨率的数据。
为插件设置必要的参数。
3. 渲染阶段:
当应用于网格、光源或相机的变换矩阵信息发生变化时,新信息将传递给 DDGI 插件。
调用Render()函数进行渲染,将DDGI插件的渲染结果保存到准备阶段创建的纹理中。
将 DDGI 插件的渲染结果应用于着色计算。
艺术限制
1、在场景中使用DDGI插件时,将下面过程部分中步骤6中的origin设置为场景的中心坐标,并相应配置探针数和光线行进数。这有助于确保插件的音量可以覆盖整个场景。
2. 要使DDGI插件能够模拟场景中的光障碍,请确保场景中的墙壁都具有适当的厚度(应该大于探针密度)。否则会出现漏光问题。最重要的是,我建议您创建一个由两个单面平面组成的墙。
3. DDGI插件专为移动应用而设计。考虑到性能和功耗,建议(非必需):
传递给 DDGI 插件的网格顶点数小于等于 50,000,以控制网格数。例如,仅传递将创建间接光的主要结构。
探针的密度和数量高达 10 x 10 x 10。
程序
1、下载DDGI插件的包并解压。将获得一个头文件和两个 Android 的 SO 文件。你可以在这里找到包。
2. 使用 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") # Write the code for calling the DDGI plugin by yourself.
include_directories(${PROJ_ROOT}/include) # Import the header file. That is, put the DDGIAPI.h header file in this directory.
# Import two SO files (librtcore.so and 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 # Link the two SO files to the app.
rtcore
android
log
z

3、配置Vulkan环境,初始化DDGIAPI类。
// Set the Vulkan environment information required by the DDGI plugin,
// including logicalDevice, queue, and 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()
// Set the Vulkan environment information.
SetupDDGIDeviceInfo();
// Call the initialization function of the DDGI plugin.
m_ddgiRender->InitDDGI(m_ddgiDeviceInfo);

void DDGIExample::Prepare()

// Create a DDGIAPI object.
std::unique_ptr m_ddgiRender = make_unique();
PrepareDDGI();

4. 创建两个纹理:一个用于存储辐照度结果(即来自摄像机视图的漫反射全局照明),另一个用于存储法线和深度。为了提高渲染性能,您可以为这两个纹理设置较低的分辨率。较低的分辨率会带来更好的渲染性能,但也会导致渲染结果失真,例如锯齿状边缘。
// Create two textures for storing the rendering results.
void DDGIExample::CreateDDGITexture()
VkImageUsageFlags usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
int ddgiTexWidth = width / m_shadingPara.ddgiDownSizeScale; // Texture width.
int ddgiTexHeight = height / m_shadingPara.ddgiDownSizeScale; // Texture height.
glm::ivec2 size(ddgiTexWidth, ddgiTexHeight);
// Create a texture for storing the irradiance results.
m_irradianceTex.CreateAttachment(vulkanDevice,
ddgiTexWidth,
ddgiTexHeight,
VK_FORMAT_R16G16B16A16_SFLOAT,
usage,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
m_defaultSampler);
// Create a texture for storing the normal and depth.
m_normalDepthTex.CreateAttachment(vulkanDevice,
ddgiTexWidth,
ddgiTexHeight,
VK_FORMAT_R16G16B16A16_SFLOAT,
usage,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
m_defaultSampler);
// Set the DDGIVulkanImage information.
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()

// Set the texture resolution.
m_ddgiRender->SetResolution(width / m_downScale, height / m_downScale);
// Set the DDGIVulkanImage information, which tells your app how and where to store the rendering results.
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、material、lightsource、camera等信息。
// Mesh structure, which supports submeshes.
struct DDGIMesh {
std::string meshName;
std::vector meshVertex;
std::vector meshIndice;
std::vector materials;
std::vector subMeshStartIndexes;

// Directional light structure. Currently, only one directional light is supported.
struct DDGIDirectionalLight {
CoordSystem coordSystem = CoordSystem::RIGHT_HANDED;
int lightId;
DDGI::Mat4f localToWorld;
DDGI::Vec4f color;
DDGI::Vec4f dirAndIntensity;
// Main camera structure.
struct DDGICamera {
DDGI::Vec4f pos;
DDGI::Vec4f rotation;
DDGI::Mat4f viewMat;
DDGI::Mat4f perspectiveMat;
// Set the light source information for the DDGI plugin.
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;
// Set the camera information for the DDGI plugin.
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);
// Prepare the mesh information required by the DDGI plugin.
// The following is an example of a scene in glTF format.
void DDGIExample::PrepareDDGIMeshes()
for (constauto& node : m_models.scene.linearNodes) {
DDGIMesh tmpMesh;
tmpMesh.meshName = node->name;
if (node->mesh) {
tmpMesh.meshName = node->mesh->name; // Mesh name.
tmpMesh.localToWorld = MatInterface(node->getMatrix()); // Transformation matrix of the mesh.
// Skeletal skinning matrix of the mesh.
if (node->skin) {
tmpMesh.hasAnimation = true;
for (auto& matrix : node->skin->inverseBindMatrices) {
tmpMesh.boneTransforms.emplace_back(MatInterface(matrix));

// Material node information and vertex buffer of the mesh.
for (vkglTF::Primitive *primitive : node->mesh->primitives) {

m_ddgiMeshes.emplace(std::make_pair(node->index, tmpMesh));

void DDGIExample::PrepareDDGI()

// Convert these settings into the format required by the DDGI plugin.
SetupDDGILights();
SetupDDGICamera();
PrepareDDGIMeshes();
// Pass the settings to the DDGI plugin.
m_ddgiRender->SetMeshs(m_ddgiMeshes);
m_ddgiRender->UpdateDirectionalLight(m_ddgiDirLight);
m_ddgiRender->UpdateCamera(m_ddgiCamera);

6.设置DDGI探针的位置和数量等参数。
// Set the DDGI algorithm parameters.
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();
// Pass the settings to the DDGI plugin.
m_ddgiRender->UpdateDDGIProbes(m_ddgiSettings);

7.调用DDGI插件的Prepare()函数解析接收到的数据。
void DDGIExample::PrepareDDGI()

m_ddgiRender->Prepare();
8.调用DDGI插件的Render()函数,缓存漫反射全局光照更新到第4步创建的贴图。
笔记:
在这个版本中,渲染结果是两种纹理:一种用于存储辐照度结果,另一种用于存储法线和深度。然后,可以使用双边滤波算法和存储法线和深度的纹理对存储辐照度结果的纹理进行上采样,通过一定的计算得到最终的漫反射全局光照结果。
如果未调用Render()函数,则渲染结果为更改发生之前的场景。
#define RENDER_EVERY_NUM_FRAME 2
void DDGIExample::Draw()

// Call DDGIRender() once every two frames.
if (m_ddgiON && m_frameCnt % RENDER_EVERY_NUM_FRAME == 0) {
m_ddgiRender->UpdateDirectionalLight(m_ddgiDirLight); // Update the light source information.
m_ddgiRender->UpdateCamera(m_ddgiCamera); // Update the camera information.
m_ddgiRender->DDGIRender(); // Use the DDGI plugin to perform rendering once and save the rendering results to the textures created in step 4.

void DDGIExample::Render()
if (!prepared) {
return;
SetupDDGICamera();
if (!paused || m_camera.updated) {
UpdateUniformBuffers();
Draw();
m_frameCnt++;
9. 应用DDGI插件的全局光照(也叫间接光照)效果如下。

打开网易新闻 查看精彩图片

// Apply the rendering results of the DDGI plugin to shading calculations.
// Perform upsampling to calculate the DDGI results based on the screen space coordinates.
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) { // Use the original resolution.
DDGIIrradiances = texelFetch(irradianceTex, texUV, 0).xyz;
} else { // Use a lower resolution.
ivec2 inDirectUV = ivec2(vec2(texUV) / vec2(shadingPara.ddgiDownSizeScale));
DDGIIrradiances = Bilateral(inDirectUV, N);
result += DDGILighting();
Image = vec4(result_t, 1.0);
现在集成了DDGI插件,应用程序可以释放动态光效。
带走
DDGI 是一种在 3D 游戏中广泛采用的技术,通过提供动态照明效果,使游戏感觉更加身临其境和真实。然而,传统的 DDGI 解决方案要求很高,并且很难将其集成到移动应用程序中。Scene Kit 通过引入其 DDGI 插件打破了这些障碍。