diff --git a/cc.config.json b/cc.config.json index a0549df564..6fe8abe187 100644 --- a/cc.config.json +++ b/cc.config.json @@ -257,12 +257,13 @@ "ff9b0199-ce04-4cfe-86cc-6c719f08d6e4", "de1c2107-70c8-4021-8459-6399f24d01c6", "cbf30902-517f-40dc-af90-a550bac27cf1", - "5c601d96-e4c7-4698-991b-7ee674b11079" + "5c601d96-e4c7-4698-991b-7ee674b11079", + "a3f78b2e-6c91-4d5a-b7f3-8e2d4a1c0b96" ] }, "custom-pipeline-post-process": { "modules": ["custom-pipeline-post-process"], - "dependentAssets":[ + "dependentAssets":[ "15049ccd-4dd7-451e-a8ae-af66735c929e", "521c5f6e-1a26-42e2-8108-4400c912d9bf", "4c3ce6de-e6d1-47f7-aa36-36b9b58f72d3", @@ -277,7 +278,8 @@ "45e7c0c8-2699-4912-b45f-d42bb8384189", "84ac6f69-3086-455a-86a4-561da8ee710b", "5c601d96-e4c7-4698-991b-7ee674b11079", - "bf0a6d94-58f0-4ad2-bdf2-c4a31c7dd856" + "bf0a6d94-58f0-4ad2-bdf2-c4a31c7dd856", + "a3f78b2e-6c91-4d5a-b7f3-8e2d4a1c0b96" ], "dependentModules": ["custom-pipeline"] }, diff --git a/cocos/3d/reflection-probe/reflection-probe-component.ts b/cocos/3d/reflection-probe/reflection-probe-component.ts index 9025984940..2ebe74f50f 100644 --- a/cocos/3d/reflection-probe/reflection-probe-component.ts +++ b/cocos/3d/reflection-probe/reflection-probe-component.ts @@ -98,6 +98,9 @@ export class ReflectionProbe extends Component { @serializable private _fastBake = false; + @serializable + private _supportTransparency = true; + protected _probe: scene.ReflectionProbe | null = null; protected _previewSphere: Node | null = null; @@ -266,6 +269,26 @@ export class ReflectionProbe extends Component { this._fastBake = val; } + /** + * @en Whether to support transparent objects in reflection probe rendering. + * When enabled, uses RGBA16F intermediate RT for correct alpha blending. + * @zh 是否支持半透明物体参与反射探针渲染。 + * 开启后使用 RGBA16F 中间 RT 以支持正确的 alpha 混合。 + */ + @visible(function (this: ReflectionProbe) { return this.probeType === ProbeType.CUBE; }) + @type(CCBoolean) + @tooltip('i18n:reflection_probe.supportTransparency') + get supportTransparency (): boolean { + return this._supportTransparency; + } + + set supportTransparency (val) { + this._supportTransparency = val; + if (this._probe) { + this._probe.supportTransparency = val; + } + } + set cubemap (val: TextureCube | null) { this._cubemap = val; this.probe.cubemap = val; @@ -441,6 +464,7 @@ export class ReflectionProbe extends Component { this._probe.probeType = this._probeType; this._probe.size = this._size; this._probe.cubemap = this._cubemap!; + this._probe.supportTransparency = this._supportTransparency; } } } diff --git a/cocos/render-scene/scene/reflection-probe.ts b/cocos/render-scene/scene/reflection-probe.ts index 89a8e80f00..7d07f3842d 100644 --- a/cocos/render-scene/scene/reflection-probe.ts +++ b/cocos/render-scene/scene/reflection-probe.ts @@ -26,7 +26,14 @@ import { Camera, CameraAperture, CameraFOVAxis, CameraISO, CameraProjection, Cam import { Node } from '../../scene-graph/node'; import { Color, Quat, Rect, toRadian, Vec2, Vec3, geometry, cclegacy, Vec4, Size, v3, quat } from '../../core'; import { CAMERA_DEFAULT_MASK } from '../../rendering/define'; -import { ClearFlagBit, Framebuffer } from '../../gfx'; +import { + ClearFlagBit, Framebuffer, + ColorAttachment, DepthStencilAttachment, + Format, LoadOp, StoreOp, + RenderPassInfo, RenderPass, + Texture, TextureInfo, TextureType, TextureUsageBit, + FramebufferInfo, FormatFeatureBit, +} from '../../gfx'; import { TextureCube } from '../../asset/assets/texture-cube'; import { RenderTexture } from '../../asset/assets/render-texture'; @@ -125,6 +132,18 @@ export class ReflectionProbe { protected _previewPlane: Node | null = null; + private _supportTransparency = false; + private _intermediateRenderPass: RenderPass | null = null; + private _intermediateDepthStencil: Texture | null = null; + + private _intermediateTextures: Texture[] = []; + + /** + * @engineInternal + * @mangle + */ + public intermediateFramebuffers: Framebuffer[] = []; + /** * @en Set probe type,cube or planar. * @zh 设置探针类型,cube或者planar @@ -280,6 +299,37 @@ export class ReflectionProbe { return this._previewPlane!; } + /** + * @engineInternal + * @mangle + */ + set supportTransparency (value: boolean) { + this._supportTransparency = value; + } + /** + * @engineInternal + * @mangle + */ + get supportTransparency (): boolean { + return this._supportTransparency; + } + + /** + * @en Whether to use RGBA16F intermediate render target for transparency support. + * @zh 是否使用 RGBA16F 中间渲染目标以支持半透明渲染。 + * @engineInternal + * @mangle + */ + public useFloatIntermediateRT (): boolean { + if (!this._supportTransparency || this._probeType === ProbeType.PLANAR) { return false; } + // Check device RGBA16F capability + const device = cclegacy.director.root?.device; + if (!device) { return false; } + const features = device.getFormatFeatures(Format.RGBA16F); + return (features & (FormatFeatureBit.RENDER_TARGET | FormatFeatureBit.SAMPLED_TEXTURE)) + === (FormatFeatureBit.RENDER_TARGET | FormatFeatureBit.SAMPLED_TEXTURE); + } + constructor (id: number) { this._probeId = id; } @@ -300,6 +350,9 @@ export class ReflectionProbe { this.bakedCubeTextures.push(renderTexture); } } + if (this.useFloatIntermediateRT() && this._intermediateTextures.length === 0) { + this.initIntermediateTextures(this._resolution, this._resolution, 6); + } } public captureCubemap (): void { @@ -319,6 +372,9 @@ export class ReflectionProbe { const canvasSize = cclegacy.view.getDesignResolutionSize() as Size; this.realtimePlanarTexture = this._createTargetTexture(canvasSize.width, canvasSize.height); cclegacy.internal.reflectionProbeManager.updatePlanarMap(this, this.realtimePlanarTexture.getGFXTexture()); + if (this.useFloatIntermediateRT()) { + this.initIntermediateTextures(canvasSize.width, canvasSize.height, 1); + } } this._syncCameraParams(sourceCamera); this._transformReflectionCamera(sourceCamera); @@ -392,6 +448,8 @@ export class ReflectionProbe { this.realtimePlanarTexture.destroy(); this.realtimePlanarTexture = null; } + + this._destroyIntermediateTextures(); } // eslint-disable-next-line @typescript-eslint/no-empty-function public enable (): void { @@ -504,6 +562,80 @@ export class ReflectionProbe { this.camera.update(true); } + /** + * @en Create RGBA16F intermediate textures and framebuffers using GFX API directly. + * @zh 直接使用 GFX API 创建 RGBA16F 中间纹理和 Framebuffer。 + */ + private initIntermediateTextures (width: number, height: number, count: number): void { + this._destroyIntermediateTextures(); + + const root = cclegacy.director.root; + const device = root.device; + + const colorAttachment = new ColorAttachment(); + colorAttachment.format = Format.RGBA16F; + colorAttachment.loadOp = LoadOp.CLEAR; + colorAttachment.storeOp = StoreOp.STORE; + + const depthStencilAttachment = new DepthStencilAttachment(); + depthStencilAttachment.format = Format.DEPTH_STENCIL; + depthStencilAttachment.depthLoadOp = LoadOp.CLEAR; + depthStencilAttachment.depthStoreOp = StoreOp.STORE; + depthStencilAttachment.stencilLoadOp = LoadOp.CLEAR; + depthStencilAttachment.stencilStoreOp = StoreOp.STORE; + + const renderPassInfo = new RenderPassInfo([colorAttachment], depthStencilAttachment); + this._intermediateRenderPass = device.createRenderPass(renderPassInfo) as RenderPass; + + this._intermediateDepthStencil = device.createTexture(new TextureInfo( + TextureType.TEX2D, + TextureUsageBit.DEPTH_STENCIL_ATTACHMENT, + Format.DEPTH_STENCIL, + width, + height, + )) as Texture; + + for (let i = 0; i < count; i++) { + const colorTex: Texture = device.createTexture(new TextureInfo( + TextureType.TEX2D, + TextureUsageBit.COLOR_ATTACHMENT | TextureUsageBit.SAMPLED, + Format.RGBA16F, + width, + height, + )) as Texture; + this._intermediateTextures.push(colorTex); + + const fb: Framebuffer = device.createFramebuffer(new FramebufferInfo( + this._intermediateRenderPass, + [colorTex], + this._intermediateDepthStencil, + )); + this.intermediateFramebuffers.push(fb); + } + } + + private _destroyIntermediateTextures (): void { + for (let i = 0; i < this.intermediateFramebuffers.length; i++) { + this.intermediateFramebuffers[i].destroy(); + } + this.intermediateFramebuffers.length = 0; + + for (let i = 0; i < this._intermediateTextures.length; i++) { + this._intermediateTextures[i].destroy(); + } + this._intermediateTextures.length = 0; + + if (this._intermediateDepthStencil) { + this._intermediateDepthStencil.destroy(); + this._intermediateDepthStencil = null; + } + + if (this._intermediateRenderPass) { + this._intermediateRenderPass.destroy(); + this._intermediateRenderPass = null; + } + } + private _createTargetTexture (width: number, height: number): RenderTexture { const rt = new RenderTexture(); rt.reset({ width, height }); diff --git a/cocos/rendering/custom/scene-culling.ts b/cocos/rendering/custom/scene-culling.ts index 39e8685756..a9a3f24c24 100644 --- a/cocos/rendering/custom/scene-culling.ts +++ b/cocos/rendering/custom/scene-culling.ts @@ -229,6 +229,9 @@ function addRenderObject ( ): void { const probeQueue = queue.probeQueue; if (isDrawProbe) { + if (!model.bakeToReflectionProbe) { + return; + } probeQueue.addToProbeQueue(model, phaseLayoutId); } const subModels = model.subModels; @@ -477,6 +480,19 @@ export class SceneCulling { // Get or create render queue const renderQueueID = this.getOrCreateRenderQueue(renderQueueKey, sceneData.flags, sceneData.camera); + if (sceneData.flags & SceneFlags.REFLECTION_PROBE) { + const probeManager = cclegacy.internal.reflectionProbeManager; + if (probeManager) { + const probes = probeManager.getProbes() as ReflectionProbe[]; + for (let i = 0; i < probes.length; i++) { + if (probes[i].camera === sceneData.camera && probes[i].useFloatIntermediateRT()) { + this.renderQueues[renderQueueID].probeQueue.useFloatOutput = true; + break; + } + } + } + } + // add render queue query const renderQueueQuery = this.cullingPools.renderQueueQueryRecycle.add(); renderQueueQuery.update(frustumCulledResultID, lightBoundsCullingID, renderQueueID); diff --git a/cocos/rendering/custom/web-pipeline-types.ts b/cocos/rendering/custom/web-pipeline-types.ts index d52bd8489c..4e39a66386 100644 --- a/cocos/rendering/custom/web-pipeline-types.ts +++ b/cocos/rendering/custom/web-pipeline-types.ts @@ -203,6 +203,7 @@ export class DrawInstance { export const instancePool = new RecyclePool(() => new DrawInstance(), 8); const CC_USE_RGBE_OUTPUT = 'CC_USE_RGBE_OUTPUT'; +const CC_USE_FLOAT_PROBE_OUTPUT = 'CC_USE_FLOAT_PROBE_OUTPUT'; function getLayoutId (passLayout: string, phaseLayout: string): number { const r = cclegacy.rendering; // eslint-disable-next-line @typescript-eslint/no-unsafe-return @@ -221,15 +222,18 @@ function getPassIndexFromLayout (subModel: SubModel, phaseLayoutId: number): num export class ProbeHelperQueue { probeMap: Array = new Array(); defaultId: number = getLayoutId('default', 'default'); + useFloatOutput = false; clear (): void { this.probeMap.length = 0; + this.useFloatOutput = false; } applyMacro (): void { + const macroName = this.useFloatOutput ? CC_USE_FLOAT_PROBE_OUTPUT : CC_USE_RGBE_OUTPUT; for (const subModel of this.probeMap) { let patches: IMacroPatch[] = [ - { name: CC_USE_RGBE_OUTPUT, value: true }, + { name: macroName, value: true }, ]; if (subModel.patches) { patches = patches.concat(subModel.patches); @@ -241,7 +245,7 @@ export class ProbeHelperQueue { for (const subModel of this.probeMap) { if (!subModel.patches) continue; const patches = subModel.patches.filter( - (patch) => patch.name !== CC_USE_RGBE_OUTPUT, + (patch) => patch.name !== CC_USE_RGBE_OUTPUT && patch.name !== CC_USE_FLOAT_PROBE_OUTPUT, ); if (patches.length === 0) { subModel.onMacroPatchesStateChanged(null); @@ -255,10 +259,11 @@ export class ProbeHelperQueue { for (let j = 0; j < subModels.length; j++) { const subModel: SubModel = subModels[j]; - //Filter transparent objects - const isTransparent = subModel.passes[0].blendState.targets[0].blend; - if (isTransparent) { - continue; + if (!this.useFloatOutput) { + const isTransparent = subModel.passes[0].blendState.targets[0].blend; + if (isTransparent) { + continue; + } } let passIdx = getPassIndexFromLayout(subModel, probeLayoutId); @@ -269,7 +274,7 @@ export class ProbeHelperQueue { bUseReflectPass = false; } if (passIdx < 0) { continue; } - if (!bUseReflectPass) { + if (!bUseReflectPass || this.useFloatOutput) { this.probeMap.push(subModel); } } diff --git a/cocos/rendering/forward/forward-pipeline.ts b/cocos/rendering/forward/forward-pipeline.ts index 92ffc17bda..2a3660507d 100644 --- a/cocos/rendering/forward/forward-pipeline.ts +++ b/cocos/rendering/forward/forward-pipeline.ts @@ -145,6 +145,12 @@ export class ForwardPipeline extends RenderPipeline { descriptorSet.update(); + // Fullscreen quad for reflection probe RGBE convert pass + const inputAssemblerData = this._createQuadInputAssembler(); + this._quadVBOffscreen = inputAssemblerData.quadVB; + this._quadIB = inputAssemblerData.quadIB; + this._quadIAOffscreen = inputAssemblerData.quadIA; + return true; } diff --git a/cocos/rendering/reflection-probe/reflection-probe-flow.ts b/cocos/rendering/reflection-probe/reflection-probe-flow.ts index 5246d7c45c..7c7326e5e0 100644 --- a/cocos/rendering/reflection-probe/reflection-probe-flow.ts +++ b/cocos/rendering/reflection-probe/reflection-probe-flow.ts @@ -82,20 +82,26 @@ export class ReflectionProbeFlow extends RenderFlow { super.destroy(); } private _renderStage (camera: Camera, probe: ReflectionProbe, reflectionCamera?: Camera): void { + const useFloatRT = probe.useFloatIntermediateRT() && probe.intermediateFramebuffers.length > 0; for (let i = 0; i < this._stages.length; i++) { const probeStage = this._stages[i] as ReflectionProbeStage; if (probe.probeType === ProbeType.PLANAR) { + // Unbind planar map before rendering to prevent self-reflection cclegacy.internal.reflectionProbeManager.updatePlanarMap(probe, null); - probeStage.setUsageInfo(probe, probe.realtimePlanarTexture!.window!.framebuffer, reflectionCamera); + const outputFb = probe.realtimePlanarTexture!.window!.framebuffer; + const fb = useFloatRT ? probe.intermediateFramebuffers[0] : outputFb; + probeStage.setUsageInfo(probe, fb, useFloatRT ? outputFb : null, reflectionCamera); probeStage.render(camera); + // Rebind the updated planar map for main camera rendering cclegacy.internal.reflectionProbeManager.updatePlanarMap(probe, probe.realtimePlanarTexture!.getGFXTexture()); } else { for (let faceIdx = 0; faceIdx < 6; faceIdx++) { const renderTexture = probe.bakedCubeTextures[faceIdx]; if (!renderTexture) return; - //update camera dirction probe.updateCameraDir(faceIdx); - probeStage.setUsageInfo(probe, renderTexture.window!.framebuffer); + const outputFb = renderTexture.window!.framebuffer; + const fb = useFloatRT ? probe.intermediateFramebuffers[faceIdx] : outputFb; + probeStage.setUsageInfo(probe, fb, useFloatRT ? outputFb : null); probeStage.render(camera); } probe.needRender = false; diff --git a/cocos/rendering/reflection-probe/reflection-probe-stage.ts b/cocos/rendering/reflection-probe/reflection-probe-stage.ts index 66ac1cfdd6..c3c8813219 100644 --- a/cocos/rendering/reflection-probe/reflection-probe-stage.ts +++ b/cocos/rendering/reflection-probe/reflection-probe-stage.ts @@ -23,7 +23,7 @@ */ import { ccclass } from 'cc.decorator'; -import { Color, Rect, Framebuffer, ClearFlagBit } from '../../gfx'; +import { Color, Rect, Framebuffer, ClearFlagBit, Device, CommandBuffer } from '../../gfx'; import { IRenderStageInfo, RenderStage } from '../render-stage'; import { ForwardStagePriority } from '../enum'; import { ForwardPipeline } from '../forward/forward-pipeline'; @@ -33,6 +33,8 @@ import { Camera, ReflectionProbe } from '../../render-scene/scene'; import { RenderReflectionProbeQueue } from '../render-reflection-probe-queue'; import { Vec3 } from '../../core'; import { packRGBE } from '../../core/math/color'; +import { Material } from '../../asset/assets/material'; +import { PipelineStateManager } from '../pipeline-state-manager'; const colors: Color[] = [new Color(1, 1, 1, 1)]; @@ -53,11 +55,13 @@ export class ReflectionProbeStage extends RenderStage { }; private _frameBuffer: Framebuffer | null = null; + private _outputFrameBuffer: Framebuffer | null = null; private _renderArea = new Rect(); private _probe: ReflectionProbe | null = null; private _reflectionCamera: Camera | null = null; private _probeRenderQueue!: RenderReflectionProbeQueue; private _rgbeColor = new Vec3(); + private _convertMaterial: Material | null = null; constructor () { super(); @@ -69,9 +73,15 @@ export class ReflectionProbeStage extends RenderStage { * @param probe * @param frameBuffer */ - public setUsageInfo (probe: ReflectionProbe, frameBuffer: Framebuffer, reflectionCamera?: Camera): void { + public setUsageInfo ( + probe: ReflectionProbe, + frameBuffer: Framebuffer, + outputFrameBuffer: Framebuffer | null = null, + reflectionCamera?: Camera, + ): void { this._probe = probe; this._frameBuffer = frameBuffer; + this._outputFrameBuffer = outputFrameBuffer; this._reflectionCamera = reflectionCamera ?? null; } @@ -122,14 +132,21 @@ export class ReflectionProbeStage extends RenderStage { const renderPass = this._frameBuffer!.renderPass; if (probeCamera.clearFlag & ClearFlagBit.COLOR) { - this._rgbeColor.x = probeCamera.clearColor.x; - this._rgbeColor.y = probeCamera.clearColor.y; - this._rgbeColor.z = probeCamera.clearColor.z; - const rgbe = packRGBE(this._rgbeColor); - colors[0].x = rgbe.x; - colors[0].y = rgbe.y; - colors[0].z = rgbe.z; - colors[0].w = rgbe.w; + if (this._probe!.useFloatIntermediateRT()) { + colors[0].x = probeCamera.clearColor.x; + colors[0].y = probeCamera.clearColor.y; + colors[0].z = probeCamera.clearColor.z; + colors[0].w = probeCamera.clearColor.w; + } else { + this._rgbeColor.x = probeCamera.clearColor.x; + this._rgbeColor.y = probeCamera.clearColor.y; + this._rgbeColor.z = probeCamera.clearColor.z; + const rgbe = packRGBE(this._rgbeColor); + colors[0].x = rgbe.x; + colors[0].y = rgbe.y; + colors[0].z = rgbe.z; + colors[0].w = rgbe.w; + } } const device = pipeline.device; cmdBuff.beginRenderPass( @@ -145,9 +162,81 @@ export class ReflectionProbeStage extends RenderStage { this._probeRenderQueue.recordCommandBuffer(device, renderPass, cmdBuff); cmdBuff.endRenderPass(); + if (this._outputFrameBuffer) { + this._renderConvertPass(device, cmdBuff); + } + pipeline.pipelineUBO.updateCameraUBO(camera); } + private _renderConvertPass (device: Device, cmdBuff: CommandBuffer): void { + const mat = this._getConvertMaterial(); + if (!mat || !mat.passes.length) { return; } + + const pass = mat.passes[0]; + const shader = pass.getShaderVariant(); + if (!shader) { return; } + + const fwdPipeline = this._pipeline as ForwardPipeline; + const inputAssembler = fwdPipeline.quadIAOffscreen; + if (!inputAssembler) { return; } + + const intermediateColorTex = this._frameBuffer!.colorTextures[0]!; + const binding = pass.getBinding('probeInputTex'); + if (binding < 0) { return; } + + const w = this._renderArea.width; + const h = this._renderArea.height; + const minX = this._renderArea.x / w; + const maxX = (this._renderArea.x + w) / w; + let minY = this._renderArea.y / h; + let maxY = (this._renderArea.y + h) / h; + if (device.capabilities.screenSpaceSignY > 0) { + const temp = maxY; + maxY = minY; + minY = temp; + } + const vbData = new Float32Array(16); + let n = 0; + vbData[n++] = -1.0; vbData[n++] = -1.0; vbData[n++] = minX; vbData[n++] = maxY; + vbData[n++] = 1.0; vbData[n++] = -1.0; vbData[n++] = maxX; vbData[n++] = maxY; + vbData[n++] = -1.0; vbData[n++] = 1.0; vbData[n++] = minX; vbData[n++] = minY; + vbData[n++] = 1.0; vbData[n++] = 1.0; vbData[n++] = maxX; vbData[n++] = minY; + inputAssembler.vertexBuffers[0].update(vbData.buffer); + + const outputRenderPass = this._outputFrameBuffer!.renderPass; + + colors[0].x = 0; + colors[0].y = 0; + colors[0].z = 0; + colors[0].w = 0; + + cmdBuff.beginRenderPass(outputRenderPass, this._outputFrameBuffer!, this._renderArea, colors, 1.0, 0); + cmdBuff.bindDescriptorSet(SetIndex.GLOBAL, fwdPipeline.descriptorSet); + + const sampler = fwdPipeline.globalDSManager.linearSampler; + pass.descriptorSet.bindTexture(binding, intermediateColorTex); + pass.descriptorSet.bindSampler(binding, sampler); + pass.descriptorSet.update(); + cmdBuff.bindDescriptorSet(SetIndex.MATERIAL, pass.descriptorSet); + + const pso = PipelineStateManager.getOrCreatePipelineState(device, pass, shader, outputRenderPass, inputAssembler); + cmdBuff.bindPipelineState(pso); + cmdBuff.bindInputAssembler(inputAssembler); + cmdBuff.draw(inputAssembler); + + cmdBuff.endRenderPass(); + } + + private _getConvertMaterial (): Material { + if (!this._convertMaterial) { + this._convertMaterial = new Material(); + this._convertMaterial._uuid = 'reflection-probe-rgbe-convert-material'; + this._convertMaterial.initialize({ effectName: 'pipeline/probe-rgbe-convert', technique: 1 }); + } + return this._convertMaterial; + } + public activate (pipeline: ForwardPipeline, flow: ReflectionProbeFlow): void { super.activate(pipeline, flow); this._probeRenderQueue = new RenderReflectionProbeQueue(pipeline); diff --git a/cocos/rendering/render-reflection-probe-queue.ts b/cocos/rendering/render-reflection-probe-queue.ts index f9560194f2..760631b488 100644 --- a/cocos/rendering/render-reflection-probe-queue.ts +++ b/cocos/rendering/render-reflection-probe-queue.ts @@ -36,6 +36,7 @@ import { RenderInstancedQueue } from './render-instanced-queue'; import { cclegacy, geometry } from '../core'; const CC_USE_RGBE_OUTPUT = 'CC_USE_RGBE_OUTPUT'; +const CC_USE_FLOAT_PROBE_OUTPUT = 'CC_USE_FLOAT_PROBE_OUTPUT'; let _phaseID = getPhaseID('default'); let _phaseReflectMapID = getPhaseID('reflect-map'); function getPassIndex (subModel: SubModel): number { @@ -72,15 +73,20 @@ export class RenderReflectionProbeQueue { private _subModelsArray: SubModel[] = []; private _passArray: Pass[] = []; private _shaderArray: Shader[] = []; + private _transparentSubModelsArray: SubModel[] = []; + private _transparentPassArray: Pass[] = []; + private _transparentShaderArray: Shader[] = []; private _rgbeSubModelsArray: SubModel[] = []; private _instancedQueue: RenderInstancedQueue = new RenderInstancedQueue(); private _patches: IMacroPatch[] = []; + private _useFloatRT = false; public constructor (pipeline: PipelineRuntime) { this._pipeline = pipeline; } public gatherRenderObjects (probe: ReflectionProbe, camera: Camera, cmdBuff: CommandBuffer, reflectionCamera?: Camera): void { this.clear(); + this._useFloatRT = probe.useFloatIntermediateRT(); const scene = camera.scene!; const sceneData = this._pipeline.pipelineSceneData; const skybox = sceneData.skybox; @@ -118,6 +124,9 @@ export class RenderReflectionProbeQueue { this._subModelsArray.length = 0; this._shaderArray.length = 0; this._passArray.length = 0; + this._transparentSubModelsArray.length = 0; + this._transparentShaderArray.length = 0; + this._transparentPassArray.length = 0; this._instancedQueue.clear(); this._rgbeSubModelsArray.length = 0; } @@ -127,9 +136,8 @@ export class RenderReflectionProbeQueue { for (let j = 0; j < subModels.length; j++) { const subModel = subModels[j]; - //Filter transparent objects const isTransparent = subModel.passes[0].blendState.targets[0].blend; - if (isTransparent) { + if (isTransparent && !this._useFloatRT) { continue; } @@ -144,21 +152,24 @@ export class RenderReflectionProbeQueue { const pass = subModel.passes[passIdx]; const batchingScheme = pass.batchingScheme; - if (!bUseReflectPass) { + if (!bUseReflectPass || this._useFloatRT) { + const macroName = this._useFloatRT ? CC_USE_FLOAT_PROBE_OUTPUT : CC_USE_RGBE_OUTPUT; this._patches = []; this._patches = this._patches.concat(subModel.patches!); - const useRGBEPatchs: IMacroPatch[] = [ - { name: CC_USE_RGBE_OUTPUT, value: true }, - ]; - this._patches = this._patches.concat(useRGBEPatchs); + this._patches.push({ name: macroName, value: true }); subModel.onMacroPatchesStateChanged(this._patches); this._rgbeSubModelsArray.push(subModel); } - if (batchingScheme === BatchingSchemes.INSTANCING) { // instancing + if (batchingScheme === BatchingSchemes.INSTANCING && !isTransparent) { const buffer = pass.getInstancedBuffer(); buffer.merge(subModel, passIdx); this._instancedQueue.queue.add(buffer); + } else if (isTransparent) { + const shader = subModel.shaders[passIdx]; + this._transparentSubModelsArray.push(subModel); + if (shader) this._transparentShaderArray.push(shader); + this._transparentPassArray.push(pass); } else { const shader = subModel.shaders[passIdx]; this._subModelsArray.push(subModel); @@ -189,19 +200,35 @@ export class RenderReflectionProbeQueue { cmdBuff.bindInputAssembler(ia); cmdBuff.draw(ia); } - this.resetRGBEMacro(); + + for (let i = 0; i < this._transparentSubModelsArray.length; ++i) { + const subModel = this._transparentSubModelsArray[i]; + const shader = this._transparentShaderArray[i]; + const pass = this._transparentPassArray[i]; + const ia = subModel.inputAssembler; + const pso = PipelineStateManager.getOrCreatePipelineState(device, pass, shader, renderPass, ia); + const descriptorSet = pass.descriptorSet; + + cmdBuff.bindPipelineState(pso); + cmdBuff.bindDescriptorSet(SetIndex.MATERIAL, descriptorSet); + cmdBuff.bindDescriptorSet(SetIndex.LOCAL, subModel.descriptorSet); + cmdBuff.bindInputAssembler(ia); + cmdBuff.draw(ia); + } + + this.resetProbeMacro(); this._instancedQueue.clear(); } - public resetRGBEMacro (): void { + + public resetProbeMacro (): void { for (let i = 0; i < this._rgbeSubModelsArray.length; i++) { this._patches = []; const subModel = this._rgbeSubModelsArray[i]; - // eslint-disable-next-line prefer-const this._patches = this._patches.concat(subModel.patches!); if (!this._patches) continue; for (let j = 0; j < this._patches.length; j++) { const patch = this._patches[j]; - if (patch.name === CC_USE_RGBE_OUTPUT) { + if (patch.name === CC_USE_RGBE_OUTPUT || patch.name === CC_USE_FLOAT_PROBE_OUTPUT) { this._patches.splice(j, 1); break; } diff --git a/editor/assets/chunks/legacy/output-standard.chunk b/editor/assets/chunks/legacy/output-standard.chunk index 52cb818e9a..c07db1e601 100644 --- a/editor/assets/chunks/legacy/output-standard.chunk +++ b/editor/assets/chunks/legacy/output-standard.chunk @@ -4,7 +4,9 @@ #include vec4 CCFragOutput (vec4 color) { - #if CC_USE_RGBE_OUTPUT + #if CC_USE_FLOAT_PROBE_OUTPUT + // Float probe RT: output linear HDR with alpha for transparency blending + #elif CC_USE_RGBE_OUTPUT color = packRGBE(color.rgb); #elif !CC_USE_FLOAT_OUTPUT #if CC_USE_HDR && CC_TONE_MAPPING_TYPE == TONE_MAPPING_ACES diff --git a/editor/assets/chunks/shading-entries/main-functions/misc/silhouette-edge-fs.chunk b/editor/assets/chunks/shading-entries/main-functions/misc/silhouette-edge-fs.chunk index 75f283169a..fe055c686e 100644 --- a/editor/assets/chunks/shading-entries/main-functions/misc/silhouette-edge-fs.chunk +++ b/editor/assets/chunks/shading-entries/main-functions/misc/silhouette-edge-fs.chunk @@ -7,7 +7,9 @@ void main () { //#uniformStyle need sync here // Color output - #if CC_USE_RGBE_OUTPUT + #if CC_USE_FLOAT_PROBE_OUTPUT + // output linear HDR for float probe RT + #elif CC_USE_RGBE_OUTPUT color = packRGBE(color.rgb); #elif !CC_USE_FLOAT_OUTPUT color.rgb = LinearToSRGB(color.rgb); diff --git a/editor/assets/chunks/shading-entries/main-functions/misc/sky-fs.chunk b/editor/assets/chunks/shading-entries/main-functions/misc/sky-fs.chunk index 4b19e669c8..ecd745728d 100644 --- a/editor/assets/chunks/shading-entries/main-functions/misc/sky-fs.chunk +++ b/editor/assets/chunks/shading-entries/main-functions/misc/sky-fs.chunk @@ -23,7 +23,9 @@ void main() { CC_APPLY_FOG_BASE(color, fogFactor); #endif - #if CC_USE_RGBE_OUTPUT + #if CC_USE_FLOAT_PROBE_OUTPUT + // output linear HDR for float probe RT + #elif CC_USE_RGBE_OUTPUT color = packRGBE(color.rgb); #else//todo: change to #elif !CC_USE_FLOAT_OUTPUT when sky render queue has been fixed with custom pipeline color.rgb = HDRToLDR(color.rgb); diff --git a/editor/assets/chunks/shading-entries/main-functions/render-to-reflectmap/fs.chunk b/editor/assets/chunks/shading-entries/main-functions/render-to-reflectmap/fs.chunk index 4e31ab6d6d..d111b70ec2 100644 --- a/editor/assets/chunks/shading-entries/main-functions/render-to-reflectmap/fs.chunk +++ b/editor/assets/chunks/shading-entries/main-functions/render-to-reflectmap/fs.chunk @@ -48,7 +48,11 @@ layout(location = 0) out vec4 fragColorX; CC_APPLY_FOG_BASE(color, fogFactor); #endif - // Color output (RGBE) - fragColorX = packRGBE(color.rgb); + // Color output + #if CC_USE_FLOAT_PROBE_OUTPUT + fragColorX = color; + #else + fragColorX = packRGBE(color.rgb); + #endif } #endif diff --git a/editor/assets/chunks/shading-entries/main-functions/render-to-scene/pipeline/forward-fs.chunk b/editor/assets/chunks/shading-entries/main-functions/render-to-scene/pipeline/forward-fs.chunk index 386daaf090..9c04a93079 100644 --- a/editor/assets/chunks/shading-entries/main-functions/render-to-scene/pipeline/forward-fs.chunk +++ b/editor/assets/chunks/shading-entries/main-functions/render-to-scene/pipeline/forward-fs.chunk @@ -141,7 +141,9 @@ void main() { #endif // Color output - #if CC_USE_RGBE_OUTPUT + #if CC_USE_FLOAT_PROBE_OUTPUT + // Float probe RT: output linear HDR with alpha for transparency blending + #elif CC_USE_RGBE_OUTPUT color = packRGBE(color.rgb); // for reflection-map #else color = CCSurfacesDebugDisplayInvalidNumber(color); diff --git a/editor/assets/default_renderpipeline/builtin-pipeline.ts b/editor/assets/default_renderpipeline/builtin-pipeline.ts index 39adb6fa89..2deb029a36 100644 --- a/editor/assets/default_renderpipeline/builtin-pipeline.ts +++ b/editor/assets/default_renderpipeline/builtin-pipeline.ts @@ -653,6 +653,7 @@ export class BuiltinForwardPassBuilder implements rendering.PipelinePassBuilder if (!cameraConfigs.enablePlanarReflectionProbe) { continue; } + const useFloatRT = probe.useFloatIntermediateRT(); let reflectionCamera = probe.camera; if (EDITOR && sourceCamera.cameraUsage === CameraUsage.PREVIEW) { reflectionCamera = probe.renderPreviewPlanarReflection(sourceCamera); @@ -660,34 +661,55 @@ export class BuiltinForwardPassBuilder implements rendering.PipelinePassBuilder const window: renderer.RenderWindow = probe.realtimePlanarTexture!.window!; const colorName = `PlanarProbeRT${probeID}`; const depthStencilName = `PlanarProbeDS${probeID}`; - // ProbeResource ppl.addRenderWindow(colorName, cameraConfigs.radianceFormat, width, height, window); ppl.addDepthStencil(depthStencilName, gfx.Format.DEPTH_STENCIL, width, height, ResourceResidency.MEMORYLESS); + let sceneColorName = colorName; + if (useFloatRT) { + sceneColorName = `PlanarProbeFloatRT${probeID}`; + ppl.addRenderTarget(sceneColorName, gfx.Format.RGBA16F, width, height); + } + // Rendering const probePass = ppl.addRenderPass(width, height, 'default'); probePass.name = `PlanarReflectionProbe${probeID}`; this._buildReflectionProbePass(probePass, cameraConfigs, id, reflectionCamera, - colorName, depthStencilName, mainLight, scene); + sceneColorName, depthStencilName, mainLight, scene, useFloatRT); + + if (useFloatRT) { + this._buildProbeConvertPass(ppl, width, height, + sceneColorName, colorName, `PlanarProbeConvert${probeID}`); + } } else if (EDITOR) { + const useFloatRT = probe.useFloatIntermediateRT(); for (let faceIdx = 0; faceIdx < probe.bakedCubeTextures.length; faceIdx++) { probe.updateCameraDir(faceIdx); const window: renderer.RenderWindow = probe.bakedCubeTextures[faceIdx].window!; const colorName = `CubeProbeRT${probeID}${faceIdx}`; const depthStencilName = `CubeProbeDS${probeID}${faceIdx}`; - // ProbeResource ppl.addRenderWindow(colorName, cameraConfigs.radianceFormat, width, height, window); ppl.addDepthStencil(depthStencilName, gfx.Format.DEPTH_STENCIL, width, height, ResourceResidency.MEMORYLESS); + let sceneColorName = colorName; + if (useFloatRT) { + sceneColorName = `CubeProbeFloatRT${probeID}${faceIdx}`; + ppl.addRenderTarget(sceneColorName, gfx.Format.RGBA16F, width, height); + } + // Rendering const probePass = ppl.addRenderPass(width, height, 'default'); probePass.name = `CubeProbe${probeID}${faceIdx}`; this._buildReflectionProbePass(probePass, cameraConfigs, id, probe.camera, - colorName, depthStencilName, mainLight, scene); + sceneColorName, depthStencilName, mainLight, scene, useFloatRT); + + if (useFloatRT) { + this._buildProbeConvertPass(ppl, width, height, + sceneColorName, colorName, `CubeProbeConvert${probeID}${faceIdx}`); + } } probe.needRender = false; } @@ -697,6 +719,23 @@ export class BuiltinForwardPassBuilder implements rendering.PipelinePassBuilder } } } + + private _buildProbeConvertPass( + ppl: rendering.BasicPipeline, + width: number, + height: number, + floatColorName: string, + outputColorName: string, + passName: string, + ): void { + if (!this._probeRGBEConvertMaterial) { return; } + const convertPass = ppl.addRenderPass(width, height, 'copy-pass'); + convertPass.name = passName; + convertPass.addRenderTarget(outputColorName, LoadOp.CLEAR, StoreOp.STORE, sClearColorTransparentBlack); + convertPass.addTexture(floatColorName, 'outputResultMap'); + convertPass.addQueue(rendering.QueueHint.OPAQUE) + .addFullscreenQuad(this._probeRGBEConvertMaterial, 0); + } private _buildReflectionProbePass( pass: rendering.BasicRenderPassBuilder, cameraConfigs: Readonly, @@ -706,6 +745,7 @@ export class BuiltinForwardPassBuilder implements rendering.PipelinePassBuilder depthStencilName: string, mainLight: renderer.scene.DirectionalLight | null, scene: renderer.RenderScene | null = null, + useFloatRT = false, ): void { const QueueHint = rendering.QueueHint; const SceneFlags = rendering.SceneFlags; @@ -714,14 +754,21 @@ export class BuiltinForwardPassBuilder implements rendering.PipelinePassBuilder // bind output render target if (forwardNeedClearColor(camera)) { - this._reflectionProbeClearColor.x = camera.clearColor.x; - this._reflectionProbeClearColor.y = camera.clearColor.y; - this._reflectionProbeClearColor.z = camera.clearColor.z; - const clearColor = rendering.packRGBE(this._reflectionProbeClearColor); - this._clearColor.x = clearColor.x; - this._clearColor.y = clearColor.y; - this._clearColor.z = clearColor.z; - this._clearColor.w = clearColor.w; + if (useFloatRT) { + this._clearColor.x = camera.clearColor.x; + this._clearColor.y = camera.clearColor.y; + this._clearColor.z = camera.clearColor.z; + this._clearColor.w = camera.clearColor.w; + } else { + this._reflectionProbeClearColor.x = camera.clearColor.x; + this._reflectionProbeClearColor.y = camera.clearColor.y; + this._reflectionProbeClearColor.z = camera.clearColor.z; + const clearColor = rendering.packRGBE(this._reflectionProbeClearColor); + this._clearColor.x = clearColor.x; + this._clearColor.y = clearColor.y; + this._clearColor.z = clearColor.z; + this._clearColor.w = clearColor.w; + } pass.addRenderTarget(colorName, LoadOp.CLEAR, colorStoreOp, this._clearColor); } else { pass.addRenderTarget(colorName, LoadOp.LOAD, colorStoreOp); @@ -754,6 +801,14 @@ export class BuiltinForwardPassBuilder implements rendering.PipelinePassBuilder SceneFlags.OPAQUE | SceneFlags.MASK | SceneFlags.REFLECTION_PROBE, mainLight || undefined, scene ? scene : undefined); + + if (useFloatRT) { + pass.addQueue(QueueHint.BLEND, 'reflect-map') + .addScene(camera, + SceneFlags.BLEND | SceneFlags.REFLECTION_PROBE, + mainLight || undefined, + scene ? scene : undefined); + } } private _addForwardRadiancePasses( ppl: rendering.BasicPipeline, @@ -988,6 +1043,8 @@ export class BuiltinForwardPassBuilder implements rendering.PipelinePassBuilder private readonly _viewport = new Viewport(); private readonly _clearColor = new Color(0, 0, 0, 1); private readonly _reflectionProbeClearColor = new Vec3(0, 0, 0); + private _probeRGBEConvertMaterial: Material | null = null; + public set probeConvertMaterial(mat: Material) { this._probeRGBEConvertMaterial = mat; } } export interface BloomPassConfigs { @@ -1733,6 +1790,7 @@ if (rendering) { private readonly _cameraConfigs = new CameraConfigs(); // Materials private readonly _copyAndTonemapMaterial = new Material(); + private readonly _probeRGBEConvertMaterial = new Material(); // Internal States private _initialized = false; // TODO(zhouzhenglong): Make default effect asset loading earlier and remove this flag @@ -2040,7 +2098,11 @@ if (rendering) { this._copyAndTonemapMaterial._uuid = `builtin-pipeline-tone-mapping-material`; this._copyAndTonemapMaterial.initialize({ effectName: 'pipeline/post-process/tone-mapping' }); + this._probeRGBEConvertMaterial._uuid = `builtin-pipeline-probe-rgbe-convert-material`; + this._probeRGBEConvertMaterial.initialize({ effectName: 'pipeline/probe-rgbe-convert' }); + if (this._copyAndTonemapMaterial.effectAsset) { + this._forwardPass.probeConvertMaterial = this._probeRGBEConvertMaterial; this._initialized = true; } diff --git a/editor/assets/effects/pipeline/probe-rgbe-convert.effect b/editor/assets/effects/pipeline/probe-rgbe-convert.effect new file mode 100644 index 0000000000..4ead564e8c --- /dev/null +++ b/editor/assets/effects/pipeline/probe-rgbe-convert.effect @@ -0,0 +1,70 @@ +// Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + +CCEffect %{ +techniques: + - passes: + - vert: probe-rgbe-convert-vs + frag: probe-rgbe-convert-fs-custom + pass: copy-pass + depthStencilState: + depthTest: false + depthWrite: false + rasterizerState: + cullMode: none + - passes: + - vert: probe-rgbe-convert-vs + frag: probe-rgbe-convert-fs-legacy + pass: copy-pass + depthStencilState: + depthTest: false + depthWrite: false + rasterizerState: + cullMode: none + +}% + +CCProgram probe-rgbe-convert-vs %{ +precision highp float; + +#include +#include +out vec2 v_uv; + +void main() { + StandardVertInput In; + CCDecode(In); + gl_Position = In.position; + v_uv = a_texCoord; +} +}% + +CCProgram probe-rgbe-convert-fs-custom %{ +precision highp float; +in vec2 v_uv; +#include + +#pragma rate outputResultMap pass +uniform sampler2D outputResultMap; + +layout(location = 0) out vec4 fragColor; + +void main() { + vec4 color = texture(outputResultMap, v_uv); + fragColor = packRGBE(color.rgb); +} +}% + +CCProgram probe-rgbe-convert-fs-legacy %{ +precision highp float; +in vec2 v_uv; +#include + +uniform sampler2D probeInputTex; + +layout(location = 0) out vec4 fragColor; + +void main() { + vec4 color = texture(probeInputTex, v_uv); + fragColor = packRGBE(color.rgb); +} +}% diff --git a/editor/assets/effects/pipeline/probe-rgbe-convert.effect.meta b/editor/assets/effects/pipeline/probe-rgbe-convert.effect.meta new file mode 100644 index 0000000000..6bf6aae377 --- /dev/null +++ b/editor/assets/effects/pipeline/probe-rgbe-convert.effect.meta @@ -0,0 +1,11 @@ +{ + "ver": "1.7.1", + "importer": "effect", + "imported": true, + "uuid": "a3f78b2e-6c91-4d5a-b7f3-8e2d4a1c0b96", + "files": [ + ".json" + ], + "subMetas": {}, + "userData": {} +} diff --git a/editor/assets/effects/pipeline/skybox.effect b/editor/assets/effects/pipeline/skybox.effect index d9b90812ab..2a97400695 100644 --- a/editor/assets/effects/pipeline/skybox.effect +++ b/editor/assets/effects/pipeline/skybox.effect @@ -78,7 +78,9 @@ CCProgram sky-fs %{ #endif vec4 color = vec4(c * cc_ambientSky.w, 1.0); - #if CC_USE_RGBE_OUTPUT + #if CC_USE_FLOAT_PROBE_OUTPUT + // output linear HDR for float probe RT + #elif CC_USE_RGBE_OUTPUT color = packRGBE(color.rgb); #else color.rgb = HDRToLDR(color.rgb);