当前位置: 首页 > 后端技术 > Java

一步一步教你:轻松打造沉浸式动态漫反射全局光照

时间:2023-04-02 10:12:59 Java

对于一款沉浸式游戏来说,场景中的全局光照效果一定是少不了的。DynamicDiffuseGlobalIllumination(DDGI)带来的光影变化是一种细腻而延展的视觉语言,它让场景中的每一种颜色都有“多彩”的诠释。它扩大了画面传达的信息层次,画龙点睛。DirectLightRenderingVSDynamicDiffuseGlobalIllumination细腻的光照视觉语言带来的技术挑战不小。不同的材质表面和光照的渲染效果是非常不同的。漫反射(Diffuse)均匀地散布光照信息,例如光的强度、光照动量,以及物体表面材质的变换。面对这些浮动变量,平台性能和计算能力受到考验。针对全局光照需要克服的复杂“症状”,HMSCore图形引擎服务提供了一套实时动态漫反射全局光照(DDGI)技术,面向移动端,无需预编译即可扩展到全平台-烘烤。在LightProbepipeline的基础上,在Probe更新和着色过程中提出了一种改进的算法,以减少原始pipeline的计算量。实现多次反射信息的全局光照,提高渲染的真实感,满足移动终端设备的实时性和交互性要求。而且,要实现充满沉浸感的动态漫反射全局照明,只需几步就可以轻松搞定!Demo样例开发指南步骤说明1.初始化阶段:搭建Vulkan运行环境,初始化DDGIAPI类。2、准备阶段:创建两个Texture用于保存DDGI渲染结果,并将Texture信息传递给DDGI。准备DDGI插件需要的Mesh、Material、Light、Camera、resolution等信息,传递给DDGI。设置DDGI参数。3.在渲染阶段,如果场景的Mesh变换矩阵、Light、Camera信息发生变化,会同步更新到DDGI端。调用Render()函数,DDGI的渲染结果保存在准备阶段创建的Texture中。将DDGI的结果结合到shading计算中。美术限制1.对于想要开启DDGI效果的场景,DDGI的origin参数要设置为场景的中心,并设置相应的步长和Probes数量,使DDGIVolume能够覆盖整个场景。2、为使DDGI获得合适的遮挡效果,请避免使用没有厚度的墙壁;如果壁的厚度与探头的密度相比太薄,则会发生漏光。同时,构成墙体的平面最好是单面的,即墙体由两个单面的平面组成。3、由于是移动端的DDGI方案,从性能和功耗的角度,有以下建议:①控制传给SDK端的geometry数量(建议50000个顶点以内),例如只场景中那些会产生间接光的主要结构传递给SDK;②尽量使用合适的Probe密度和数量,尽量不要超过101010。以上建议以最终呈现结果为准。开发步骤1、下载插件SDK包,解压后得到DDGISDK相关文件,包括1个头文件和2个so文件。Android平台使用的so库文件下载地址可参考:DynamicDiffuseGlobalIlluminationPlug-in。2.插件支持Android平台,使用CMake构建。以下是CMakeLists.txt的部分片段,仅供参考:cmake_minimum_required(VERSION3.4.1FATAL_ERROR)set(NAMEDDGIExample)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(GLOBEXAMPLE_SRC"${PROJ_ROOT}/src/*.cpp")#导入开发者主程序代码。include_directories(${PROJ_ROOT}/include)#导入头文件,可以把DDGIAPI.h头文件放在这个目录下。#导入librtcore.so和libddgi.soADD_LIBRARY(rtcoreSHAREDIMPORTED)SET_TARGET_PROPERTIES(rtcorePROPERTIESIMPORTED_LOCATION${CMAKE_SOURCE_DIR}/src/main/libs/librtcore.so)ADD_LIBRARY(ddgiSHAREDIMPORTED)SET_TARGET_PROPERTIESC{CMAKEEDIRPERTIESCIdgiCMAKEDIRPERTIESCIdgisrc/main/libs/libddgi.so)add_library(native-libSHARED${EXAMPLE_SRC})target_link_libraries(native-lib...ddgi#linkddgilibrary.rtcoreandroidlogz...)3.设置Vulkan环境和初始化DDGIAPI类。//设置DDGISDK需要的Vulkan环境信息。//包括logicalDevice、queue、queueFamilyIndex信息。voidDDGIExample::SetupDDGIDeviceInfo(){m_ddgiDeviceInfo.physicalDevice=physicalDevice;m_ddgiDeviceInfo.logicalDevice=设备;m_ddgiDeviceInfo.queue=队列;m_ddgiDeviceInfo.queueFamilyIndex=vulkanDevice->queueFamilyIndi??ces.graphics;}voidDDGIExample::PrepareDDGI(){//设置Vulkan环境信息。SetupDDGIDeviceInfo();//调用DDGI的初始化函数。m_ddgiRender->InitDDGI(m_ddgiDeviceInfo);...}voidDDGIExample::Prepare(){...//创建一个DDGIAPI对象。std::unique_ptrm_ddgiRender=make_unique();...准备DDGI();...}4.创建两张贴图,分别保存相机视角的漫反射全局光照和法线深度图。为了提高渲染性能,Texture支持降低分辨率的设置。分辨率越小,渲染性能越好,但渲染结果的锯齿现象(如锯齿状边缘)可能会更严重。//创建保存渲染结果的Texture。voidDDGIExample::CreateDDGITexture(){VkImageUsageFlags用法=VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT|VK_IMAGE_USAGE_SAMPLED_BIT;intddgiTexWidth=width/m_shadingPara.ddgiDownSizeScale;//纹理宽度。intddgiTexHeight=height/m_shadingPara.ddgiDownSizeScale;//纹理高度。glm::ivec2大小(ddgiTexWidth,ddgiTexHeight);//创建一个纹理来保存辐照度结果。m_irradianceTex.CreateAttachment(vulkanDevice、ddgiTexWidth、ddgiTexHeight、VK_FORMAT_R16G16B16A16_SFLOAT、用法、VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL、m_defaultSampler);//创建保存法线和深度结果的纹理。m_normalDepthTex.CreateAttachment(vulkanDevice,ddgiTexWidth,ddgiTexHeight,VK_FORMAT_R16G16B16A16_SFLOAT,usage,VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,m_defaultSampler);}//设置DDGIVulkanImage信息。voidDDGIExample::PrepareDDGIOutputTex(constvks::Texture&tex,DDGIVulkanImage*texture)const{texture->image=tex.image;texture->format=tex.format;纹理->类型=VK_IMAGE_TYPE_2D;texture->extent.width=tex.width;texture->extent.height=tex.height;纹理->extent.depth=1;texture->usage=tex.usage;texture->layout=tex.imageLayout;纹理->图层=1;纹理->mipCount=1;纹理->样本=VK_SAMPLE_COUNT_1_BIT;texture->tiling=VK_IMAGE_TILING_OPTIMAL;}voidDDGIExample::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);...}voidDDGIExample::Prepare(){...CreateDDGITexture();...准备DDGI();...}5.准备DDGI渲染所需的网格、材质、光源、相机数据。//网格结构,支持子网格。结构DDGIMesh{std::stringmeshName;std::vector网格顶点;std::vectormeshIndice;std::vector材料;std::vectorsubMeshStartIndexes;...};//方向光结构,目前只支持一种方向光。结构DDGIDirectionalLight{CoordSystemcoordSystem=CoordSystem::RIGHT_HANDED;内部灯号;DDGI::Mat4flocalToWorld;DDGI::Vec4f颜色;DDGI::Vec4fdirAndIntensity;};//主相机结构。结构DDGICamera{DDGI::Vec4fpos;DDGI::Vec4f旋转;DDGI::Mat4f视图垫;DDGI::Mat4fperspectiveMat;};//设置DDGI的光源信息。voidDDGIExample::SetupDDGILights(){m_ddgiDirLight.color=VecInterface(m_dirLight.color);m_ddgiDirLight.dirAndIntensity=VecInterface(m_dirLight.dirAndPower);m_ddgiDirLight.localToWorld=MatInterface(逆(m_dirLight.worldToLocal));m_ddgiDirLight.lightId=0;}//设置DDGI的相机信息。voidDDGIExample::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::mat4yFlip=glm::mat4(1.0f);yFlip[1][1]=-1;m_ddgiCamera.perspectiveMat=MatInterface(m_camera.matrices.perspective*yFlip);}//准备DDGI需要的网格信息。//以gltf格式的渲染场景为例。voidDDGIExample::PrepareDDGIMeshes(){for(constauto&node:m_models.scene.linearNodes){DDGIMeshtmpMesh;tmpMesh.meshName=节点->名称;如果(节点->网格){tmpMesh.meshName=节点->网格->名称;//网格的名称。tmpMesh.localToWorld=MatInterface(node->getMatrix());//网格的变换矩阵。//网格的骨骼蒙皮矩阵。如果(节点->皮肤){tmpMesh.hasAnimation=true;for(auto&matrix:node->skin->inverseBindMatrices){tmpMesh.boneTransforms.emplace_back(MatInterface(matrix));}}//mesh的材质节点,Vertex信息。对于(vkglTF::Primitive*primitive:node->mesh->primitives){...}}m_ddgiMeshes.emplace(std::make_pair(node->index,tmpMesh));}}voidDDGIExample::PrepareDDGI(){...//转换成DDGI要求的数据格式。SetupDDGILights();SetupDDGICamera();PrepareDDGIMeshes();...//将数据传递给DDGI。m_ddgiRender->SetMeshs(m_ddgiMeshes);m_ddgiRender->UpdateDirectionalLight(m_ddgiDirLight);m_ddgiRender->UpdateCamera(m_ddgiCamera);...}6.设置DDGI探针的位置和数量等参数。//设置DDGI算法参数。voidDDGIExample::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);...}voidDDGIExample::PrepareDDGI(){...SetupDDGIParameters();...//将数据传递给DDGI。m_ddgiRender->UpdateDDGIProbes(m_ddgiSettings);...}7.调用DDGI的Prepare()函数对之前传递过来的数据进行分析。voidDDGIExample::PrepareDDGI(){...m_ddgiRender->Prepare();}8.调用DDGI的Render()将场景的间接光信息更新并缓存到步骤4设置的两个DDGITextures中*注意在当前版本中,渲染结果是相机视角下的漫反射间接光结果图和法线深度图。开发者使用双边滤波算法结合法线深度图对漫反射间接光结果进行上采样,计算屏幕尺寸。的漫反射全局照明结果。如果不调用Render()函数,渲染结果就是历史帧的结果。#defineRENDER_EVERY_NUM_FRAME2voidDDGIExample::Draw(){...//每两帧调用DDGIRender()。如果(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中。}...}voidDDGIExample::Render(){if(!prepared){return;}SetupDDGICamera();if(!paused||m_camera.updated){UpdateUniformBuffers();}画();m_frameCnt++;}9、叠加DDGI间接光效果,使用过程如下://最后的shader。//通过上采样计算屏幕空间坐标对应的DDGI值。vec3Bilateral(ivec2uv,vec3normal){...}voidmain(){...vec3result=vec3(0.0);结果+=DirectLighting();结果+=IndirectLighting();vec3DDGIIrradiances=vec3(0.0);ivec2texUV=ivec2(gl_FragCoord.xy);texUV.y=shadingPara.ddgiTexHeight-texUV.y;if(shadingPara.ddgiDownSizeScale==1){//不降低分辨率。DDGIIrradiances=texelFetch(irradianceTex,texUV,0).xyz;}else{//降低分辨率。ivec2inDirectUV=ivec2(vec2(texUV)/vec2(shadingPara.ddgiDownSizeScale));DDGIIrradiances=Bilateral(inDirectUV,N);}结果+=DDGILighting();...Image=vec4(result_t,1.0);}了解更多详情>>访问华为开发者联盟官网获取开发指导文档华为移动服务开源仓库地址:GitHub、Gitee关注我们,第一时间了解HMSCore的最新技术资料~