首页 > 其他分享 >手把手教你:轻松打造沉浸感十足的动态漫反射全局光照

手把手教你:轻松打造沉浸感十足的动态漫反射全局光照

时间:2022-09-19 10:36:37浏览次数:89  
标签:... 漫反射 手把手 void texture DDGI ddgiRender DDGIExample 光照

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

动态漫反射全局光照(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.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);
}

了解更多详情>>

访问华为开发者联盟官网
获取开发指导文档
华为移动服务开源仓库地址:GitHubGitee

关注我们,第一时间了解 HMS Core 最新技术资讯~

标签:...,漫反射,手把手,void,texture,DDGI,ddgiRender,DDGIExample,光照
From: https://www.cnblogs.com/hmscore/p/16706838.html

相关文章

  • Phong光照模型速记
    Phong光照模型速记Phong是提高图像真实度的模拟光照模型,由环境光,漫反射光,镜面反射光。环境光,物体间反射形成的复杂反射光、环境本身就具有的光。该模型简化为一个环境光......
  • 手把手教你使用dat.gui
    最近在git上看见一个炫酷的3D项目,是使用canvas,根据矩阵变换生成3D场景和动画,效果是这样的:效果图这样的:效果图由于笔者水平有限,炫酷的效果,复杂的矩阵,不属于本次的重点......
  • 手把手教你君正X2000开发板的OpenHarmony环境搭建
    摘要:本文主要介绍基于君正X2000开发板的OpenHarmony环境搭建以及简单介绍网络配置情况本文分享自华为云社区《君正X2000开发板的OpenHarmony环境搭建》,作者:星辰27。本文......
  • 手把手教你安装 官方 Office 2019 家庭和学生版_利用官方渠道重新安装刚购买电脑时的
     此方法仅针对通过正式渠道购买的正版电脑(笔记本、台式机),组装机不可以。目前正版电脑均会预装永久激活的windows10系统以及office2019家庭和学生版套件,版权费用默认添加......
  • 读UnityShader入门精要第六章-Unity中的基础光照
    1.我们如何看到这个世界1.1 光源光是由光源发出的,在实时渲染中,光源被当成一个没有体积的点.在光学中,使用辐照度(irradiance)来量化光.当光打在一个平面上......
  • 使用 Vite 搭建一个 Vue 3 UI 组件库的手把手教程 All In One
    使用Vite搭建一个Vue3UI组件库的手把手教程AllInOneelement-pluselement-uiforvue3.xversionhttps://github.com/element-plus/element-plushttps://el......
  • 手把手教你用SPSSAU做倾向得分匹配PSM
    倾向得分匹配(PSM),是一种模仿RCT随机对照试验随机化分组,提高组间均衡性,进而达到降低混杂因素影响目的一种数据处理策略。PSM在计量研究,临床医学等领域有着广泛的应用。1.......
  • 手把手教你用SPSSAU做K均值聚类分析
    1.案例数据探索案例采用著名的鸢尾花iris数据集,按鸢尾花的三个类别(刚毛,变色,佛吉尼亚),每一类50株,共测得150株鸢尾花的花萼长度,花萼宽度,花瓣长度,花瓣宽度4个属性数据。1.1......
  • 手把手教你用SPSSAU做多重线性逐步回归
    1.案例背景与分析策略1.1案例背景介绍某研究收集到美国50个州关于犯罪率的一组数据,包括人口、面积、收入、文盲率、高中毕业率、霜冻天数、犯罪率共7个指标,现在我们想考......
  • 阅读《计算机图形学编程(使用OpenGL和C++)》12 - 光照
    现在最常见的光照模型称为“ADS”模型,因为它们基于标记为A、D和S的3种类型的反射。●环境光反射(Ambientreflection)模拟低级光照,影响场景中的所有物体。●漫反射(Diffuse......