鸿蒙开发-想给3D场景加滤镜?PostProcess后处理效果详解
给 3D 场景加滤镜色调映射、泛光、暗角这些后处理怎么玩你有没有注意到很多游戏和 3D 应用的画面看起来特别电影感——高光处有柔和的光晕画面边缘有暗暗的暗角整体色调温暖或者冷峻这些效果不是模型本身的材质做出来的而是渲染完成后在画面上加了一层滤镜专业术语叫后处理Post Process。这篇文章就来聊聊 ArkGraphics 3D 的后处理系统包括色调映射、泛光Bloom、边缘暗角和色晕这几种效果。后处理是什么打个比方你用相机拍了一张照片这是原始画面。然后你用修图软件给它加了滤镜——调了色调、加了光晕、加了暗角。这个加滤镜的过程就是后处理。下面的流程图展示了后处理在 3D 渲染管线中的位置和处理流程3D场景渲染到内部纹理后处理阶段色调映射: HDR转LDR泛光效果: 高亮区域光晕边缘暗角: 画面边缘变暗色晕效果: 模拟镜头色差最终画面输出到屏幕在 3D 渲染中后处理是最后一步。引擎先把 3D 场景渲染到一张内部的图片上然后对这张图片应用各种效果最后才显示到屏幕上。所以后处理不会改变场景中的物体本身只改变最终呈现的画面。后处理设置是通过相机Camera的postProcess属性来配置的。也就是说不同相机可以有不同的后处理效果——这在做分屏或者画中画时很有用。PostProcessSettings后处理设置的容器PostProcessSettings是一个配置对象它包含了所有后处理效果的设置import{ToneMappingType,ToneMappingSettings,BloomSettings,PostProcessSettings}fromkit.ArkGraphics3D;它有四个可选属性toneMapping色调映射设置bloomAPI 18泛光设置vignetteAPI 22边缘暗角设置colorFringeAPI 22色晕设置你只需要设置你想要的效果不需要全部配置。没设置的效果不会生效。色调映射Tone Mapping色调映射是最基础也最重要的后处理效果。它的作用是把 HDR高动态范围的颜色值映射到 LDR低动态范围让画面在普通屏幕上看起来正常。简单说就是3D 渲染出来的颜色值可能超过 1.0比如很亮的光源但屏幕只能显示 0 到 1 的范围。色调映射就是把那些超亮的颜色压缩到屏幕能显示的范围内同时尽量保留画面的层次感。色调映射类型ArkGraphics 3D 提供了三种色调映射算法ACES0基于 Academy Color Encoding System 标准追求电影级色彩还原。如果你想要好莱坞电影那种质感选这个ACES_20201基于 ACES 2020 标准色域更广色彩精度更高。适合对色彩要求严格的 HDR 渲染场景FILMIC2模拟胶片曝光响应曲线高光过渡柔和自然。适合追求写实风格和电影质感的一般 3D 场景ToneMappingSettings色调映射设置有两个属性type色调映射类型就是上面三种之一exposure曝光度大于 0 的数。值越大画面越亮值越小画面越暗。就像相机的曝光补偿来看一个实际的例子import{Scene,SceneResourceFactory,SceneNodeParameters,Camera,CameraParameters,RenderingPipelineType,PostProcessSettings,ToneMappingType}fromkit.ArkGraphics3D;functionsetupToneMapping():void{Scene.load($rawfile(gltf/DamagedHelmet/glTF/DamagedHelmet.glb)).then(async(result:Scene){if(!result.root){return;}letsceneFactory:SceneResourceFactoryresult.getResourceFactory();letnodeParameter:SceneNodeParameters{name:camera1};letcamParameter:CameraParameters{renderingPipeline:RenderingPipelineType.FORWARD};// 创建相机letcamera:CameraawaitsceneFactory.createCamera(nodeParameter,camParameter);camera.enabledtrue;// 配置后处理使用FILMIC色调映射曝光度1.5camera.postProcess{toneMapping:{type:ToneMappingType.FILMIC,exposure:1.5}};});}这段代码创建了一个使用 FORWARD 渲染管线的相机然后给它配置了 FILMIC 色调映射曝光度设为 1.5比默认亮一点。注意renderingPipeline设为了RenderingPipelineType.FORWARD。这是因为如果用默认的FORWARD_LIGHTWEIGHT管线某些后处理效果会受限——轻量级管线只能在着色器中实现逐像素效果不支持泛光等复杂效果。泛光Bloom泛光就是那种发光物体周围有一圈光晕的效果。想象一下夜晚看路灯灯光周围会有一层柔和的光晕扩散开来——这就是泛光。在 3D 渲染中泛光效果的实现原理是找出画面中亮度超过某个阈值的区域把它们模糊处理后叠加回原画面上。BloomSettings泛光设置有四个属性thresholdHard硬阈值亮度超过这个值的像素一定会产生泛光效果。默认值 1.0thresholdSoft软阈值亮度在硬阈值和软阈值之间的像素会产生部分泛光效果。默认值 2.0scaleFactor缩放因子控制泛光的强度。默认值 1.0scatter扩散量控制光晕的扩散范围。默认值 1.0硬阈值和软阈值之间的关系是这样的低于硬阈值的像素不产生泛光高于软阈值的像素完全产生泛光两者之间的像素部分产生泛光。这种渐变过渡让泛光效果看起来更自然。import{Scene,SceneResourceFactory,SceneNodeParameters,Camera,CameraParameters,RenderingPipelineType,PostProcessSettings,ToneMappingType}fromkit.ArkGraphics3D;functionsetupBloom():void{Scene.load($rawfile(gltf/DamagedHelmet/glTF/DamagedHelmet.glb)).then(async(result:Scene){if(!result.root){return;}letsceneFactory:SceneResourceFactoryresult.getResourceFactory();letnodeParameter:SceneNodeParameters{name:camera1};letcamParameter:CameraParameters{renderingPipeline:RenderingPipelineType.FORWARD};letcamera:CameraawaitsceneFactory.createCamera(nodeParameter,camParameter);camera.enabledtrue;// 配置后处理色调映射 泛光camera.postProcess{toneMapping:{type:ToneMappingType.FILMIC,exposure:1.0},bloom:{thresholdHard:0.8,thresholdSoft:1.5,scaleFactor:1.2,scatter:0.8}};});}这段代码同时配置了色调映射和泛光。泛光的硬阈值设为 0.8比较容易触发泛光软阈值 1.5缩放因子 1.2比默认强一点扩散量 0.8比默认收拢一点。需要注意的是泛光效果只能在FORWARD渲染管线下使用FORWARD_LIGHTWEIGHT不支持。边缘暗角Vignette暗角就是画面边缘变暗的效果。很多摄影师和电影人都喜欢用暗角来引导观众的视线——因为人眼自然会被亮的区域吸引暗角会让画面中央的主体更突出。VignetteSettings暗角设置有两个属性roundness暗角的覆盖区域大小范围 [0, 1]。0 表示覆盖区域收缩到最小1 表示覆盖整个画面。默认值约 0.707sqrt(0.5)intensity作用强度范围 [0, 1]。0 表示没有暗角效果1 表示最大暗角强度。默认值 0.4import{Scene,SceneResourceFactory,SceneNodeParameters,Camera,CameraParameters,RenderingPipelineType,ToneMappingType}fromkit.ArkGraphics3D;functionsetupVignette():void{Scene.load($rawfile(gltf/DamagedHelmet/glTF/DamagedHelmet.glb)).then(async(result:Scene){if(!result.root){return;}letsceneFactory:SceneResourceFactoryresult.getResourceFactory();letnodeParameter:SceneNodeParameters{name:camera1};letcamParameter:CameraParameters{renderingPipeline:RenderingPipelineType.FORWARD};letcamera:CameraawaitsceneFactory.createCamera(nodeParameter,camParameter);camera.enabledtrue;// 配置后处理色调映射 暗角camera.postProcess{toneMapping:{type:ToneMappingType.FILMIC,exposure:1.0},vignette:{roundness:0.8,intensity:0.5}};});}这里把暗角的 roundness 设为 0.8覆盖区域比较大intensity 设为 0.5中等强度。色晕Color Fringe色晕效果模拟的是光学镜头的色差现象——画面边缘的物体边缘会出现红、绿、蓝三色的偏移。这种效果在摄影中通常是要避免的但在 3D 渲染中适当加一点色晕可以让画面看起来更有镜头感。ColorFringeSettings色晕设置只有一个属性intensity作用强度范围 [0, 1]。默认值 0.2import{Scene,SceneResourceFactory,SceneNodeParameters,Camera,CameraParameters,RenderingPipelineType,ToneMappingType}fromkit.ArkGraphics3D;functionsetupColorFringe():void{Scene.load($rawfile(gltf/DamagedHelmet/glTF/DamagedHelmet.glb)).then(async(result:Scene){if(!result.root){return;}letsceneFactory:SceneResourceFactoryresult.getResourceFactory();letnodeParameter:SceneNodeParameters{name:camera1};letcamParameter:CameraParameters{renderingPipeline:RenderingPipelineType.FORWARD};letcamera:CameraawaitsceneFactory.createCamera(nodeParameter,camParameter);camera.enabledtrue;// 配置后处理色调映射 色晕camera.postProcess{toneMapping:{type:ToneMappingType.FILMIC,exposure:1.0},colorFringe:{intensity:0.3}};});}色晕效果同样只能在FORWARD管线下使用。把所有效果组合在一起实际项目中你通常会同时使用多种后处理效果来达到理想的画面风格。比如做一个虚拟展厅你可能想要温暖的色调 轻微的泛光 柔和的暗角。import{Scene,SceneResourceFactory,SceneNodeParameters,Camera,CameraParameters,RenderingPipelineType,ToneMappingType}fromkit.ArkGraphics3D;functionsetupFullPostProcess():void{Scene.load($rawfile(gltf/DamagedHelmet/glTF/DamagedHelmet.glb)).then(async(result:Scene){if(!result.root){return;}letsceneFactory:SceneResourceFactoryresult.getResourceFactory();letnodeParameter:SceneNodeParameters{name:camera1};letcamParameter:CameraParameters{renderingPipeline:RenderingPipelineType.FORWARD};letcamera:CameraawaitsceneFactory.createCamera(nodeParameter,camParameter);camera.enabledtrue;// 组合所有后处理效果camera.postProcess{toneMapping:{type:ToneMappingType.FILMIC,exposure:1.2},bloom:{thresholdHard:1.0,thresholdSoft:2.0,scaleFactor:1.0,scatter:1.0},vignette:{roundness:0.707,intensity:0.4},colorFringe:{intensity:0.2}};});}这段代码用了所有默认值来组合四种效果。你可以根据实际需求调整每个参数。渲染管线的选择下面的流程图展示了如何根据需求选择渲染管线是否选择渲染管线需要泛光/色晕等复杂效果?使用FORWARD管线支持所有后处理效果性能开销较大适合虚拟展厅/产品展示使用FORWARD_LIGHTWEIGHT管线只支持色调映射和暗角性能更好适合简单3D模型展示前面反复提到泛光和色晕只能在FORWARD管线下使用。这里总结一下两种管线的区别FORWARD_LIGHTWEIGHT轻量级前向渲染直接渲染到后缓冲区只能在着色器中实现逐像素效果不支持泛光、色晕等复杂后处理性能更好适合简单场景FORWARD高质量前向渲染支持所有后处理效果性能开销更大适合需要电影级画面的场景选择哪种管线取决于你的需求。如果只是展示一个简单的 3D 模型不需要花哨的效果用轻量级管线就好。如果要做虚拟展厅、产品展示这种对画面要求高的场景就用高质量管线。管线类型在创建相机时通过CameraParameters指定import{CameraParameters,RenderingPipelineType}fromkit.ArkGraphics3D;letcamParameter:CameraParameters{renderingPipeline:RenderingPipelineType.FORWARD};也可以在创建相机时不指定使用默认的轻量级管线之后再通过相机的renderingPipeline属性修改。渲染配置除了后处理还有一些全局的渲染配置可以通过RenderConfiguration来设置shadowResolution全局阴影贴图分辨率Vec2 类型单位像素。默认是 1024x1024。值越大阴影越精细但性能开销也越大import{Scene}fromkit.ArkGraphics3D;functionconfigureShadow():void{Scene.load($rawfile(gltf/DamagedHelmet/glTF/DamagedHelmet.glb)).then(async(result:Scene){if(!result){return;}// 提高阴影分辨率result.renderConfiguration.shadowResolution{x:2048,y:2048};});}MSAA 抗锯齿相机还支持 MSAA多重采样抗锯齿通过msaa属性控制。开启后可以让物体的边缘更平滑但会增加性能开销。import{SceneNodeParameters,Camera,SceneResourceFactory,Scene,CameraParameters}fromkit.ArkGraphics3D;functionenableMSAA():void{Scene.load($rawfile(gltf/DamagedHelmet/glTF/DamagedHelmet.glb)).then(async(result:Scene){letsceneFactory:SceneResourceFactoryresult.getResourceFactory();letnodeParameter:SceneNodeParameters{name:camera1};letcamParameter:CameraParameters{msaa:true};letcamera:CameraawaitsceneFactory.createCamera(nodeParameter,camParameter);camera.enabledtrue;});}MSAA 也可以在创建后通过camera.msaa true来开启。小结后处理是 3D 渲染的最后一道工序能显著提升画面质感色调映射把 HDR 颜色映射到屏幕能显示的范围三种算法各有风格泛光高亮区域的柔和光晕增加画面的发光感边缘暗角画面边缘变暗引导视线聚焦中央色晕模拟镜头色差增加镜头感所有后处理都通过相机的postProcess属性配置。泛光和色晕需要FORWARD渲染管线轻量级管线不支持。到这里ArkGraphics 3D 的五个核心模块我们都过了一遍场景管理、节点操作、材质与资源、数据类型、后处理效果。把这些串起来你就能在 HarmonyOS 上搭建出一个完整的 3D 应用了。