Skip to content

generate mipmaps for planar reflection #15629

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion EngineErrorMap.md
Original file line number Diff line number Diff line change
Expand Up @@ -3411,4 +3411,8 @@ Skin material needs floating-point render target, please check ENABLE_FLOAT_OUTP

### 16304

Skin material may need more accurate calculations, please select a head model of standard size, check the isGlobalStandardSkinObject option in the MeshRender component.
Skin material may need more accurate calculations, please select a head model of standard size, check the isGlobalStandardSkinObject option in the MeshRender component.

### 16305

The reflection camera is invalid, please set the reflection camera.
4 changes: 2 additions & 2 deletions cocos/3d/reflection-probe/reflection-probe-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*/
import { ccclass, executeInEditMode, menu, playOnFocus, serializable, tooltip, type, visible } from 'cc.decorator';
import { EDITOR, EDITOR_NOT_IN_PREVIEW } from 'internal:constants';
import { CCBoolean, CCObject, Color, Enum, Vec3 } from '../../core';
import { CCBoolean, CCObject, Color, Enum, Vec3, warnID } from '../../core';

import { TextureCube } from '../../asset/assets';
import { scene } from '../../render-scene';
Expand Down Expand Up @@ -151,7 +151,7 @@ export class ReflectionProbe extends Component {
this._objFlags ^= CCObject.Flags.IsRotationLocked;
}
if (!this._sourceCamera) {
console.warn('the reflection camera is invalid, please set the reflection camera');
warnID(16305);
} else {
this.probe.switchProbeType(value, this._sourceCamera.camera);
}
Expand Down
6 changes: 3 additions & 3 deletions cocos/3d/reflection-probe/reflection-probe-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ export class ReflectionProbeManager {
if (!this._probes[i].realtimePlanarTexture) {
this.updatePlanarMap(this._probes[i], null);
} else {
this.updatePlanarMap(this._probes[i], this._probes[i].realtimePlanarTexture!.getGFXTexture());
this.updatePlanarMap(this._probes[i], this._probes[i].getPlanarReflectionTexture());
}
}
}
Expand Down Expand Up @@ -327,7 +327,7 @@ export class ReflectionProbeManager {
const meshRender = probe.previewPlane.getComponent(MeshRenderer);
if (meshRender) {
if (probe.realtimePlanarTexture) {
this.updatePlanarMap(probe, probe.realtimePlanarTexture.getGFXTexture());
this.updatePlanarMap(probe, probe.getPlanarReflectionTexture());
}
}
}
Expand Down Expand Up @@ -379,7 +379,7 @@ export class ReflectionProbeManager {
buffer[bufferOffset + 6] = 0.0;
buffer[bufferOffset + 7] = 0.0;
//mipCount;
buffer[bufferOffset + 8] = 1.0;
buffer[bufferOffset + 8] = probe.planarMipmapCount;
}
bufferOffset += 4 * dataWidth;
}
Expand Down
4 changes: 2 additions & 2 deletions cocos/asset/assets/simple-texture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ const _regions: BufferTextureCopy[] = [new BufferTextureCopy()];
export type PresumedGFXTextureInfo = Pick<TextureInfo, 'usage' | 'flags' | 'format' | 'levelCount'>;
export type PresumedGFXTextureViewInfo = Pick<TextureViewInfo, 'texture' | 'format' | 'baseLevel' | 'levelCount'>;

function getMipLevel (width: number, height: number): number {
export function getMipLevel (width: number, height: number): number {
let size = Math.max(width, height);
let level = 0;
while (size) { size >>= 1; level++; }
return level;
}

function isPOT (n: number): boolean | 0 { return n && (n & (n - 1)) === 0; }
function canGenerateMipmap (device: Device, w: number, h: number): boolean | 0 {
export function canGenerateMipmap (device: Device, w: number, h: number): boolean | 0 {
const needCheckPOT = device.gfxAPI === API.WEBGL;
if (needCheckPOT) { return isPOT(w) && isPOT(h); }
return true;
Expand Down
5 changes: 3 additions & 2 deletions cocos/render-scene/scene/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1028,7 +1028,7 @@ export class Model {
const sampler = this._device.getSampler(new SamplerInfo(
Filter.LINEAR,
Filter.LINEAR,
Filter.NONE,
Filter.LINEAR,
Address.CLAMP,
Address.CLAMP,
Address.CLAMP,
Expand Down Expand Up @@ -1110,7 +1110,8 @@ export class Model {
sv[UBOLocal.REFLECTION_PROBE_DATA2] = 1.0;
sv[UBOLocal.REFLECTION_PROBE_DATA2 + 1] = 0.0;
sv[UBOLocal.REFLECTION_PROBE_DATA2 + 2] = 0.0;
sv[UBOLocal.REFLECTION_PROBE_DATA2 + 3] = 1.0;
//mipCount;
sv[UBOLocal.REFLECTION_PROBE_DATA2 + 3] = probe.planarMipmapCount;
} else {
sv[UBOLocal.REFLECTION_PROBE_DATA1] = probe.node.worldPosition.x;
sv[UBOLocal.REFLECTION_PROBE_DATA1 + 1] = probe.node.worldPosition.y;
Expand Down
83 changes: 69 additions & 14 deletions cocos/render-scene/scene/reflection-probe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ import { Camera, CameraAperture, CameraFOVAxis, CameraISO, CameraProjection, Cam
import { Node } from '../../scene-graph/node';
import { Color, Quat, Rect, toRadian, Vec2, Vec3, geometry, cclegacy, Vec4 } from '../../core';
import { CAMERA_DEFAULT_MASK } from '../../rendering/define';
import { ClearFlagBit, Framebuffer } from '../../gfx';
import { ClearFlagBit, Filter, Format, Framebuffer, Texture, TextureBlit, TextureFlagBit, TextureInfo, TextureType, TextureUsageBit, deviceManager } from '../../gfx';
import { TextureCube } from '../../asset/assets/texture-cube';
import { RenderTexture } from '../../asset/assets/render-texture';
import { canGenerateMipmap, getMipLevel } from '../../asset/assets/simple-texture';
import { legacyCC } from '../../core/global-exports';

export enum ProbeClearFlag {
SKYBOX = SKYBOX_FLAG | ClearFlagBit.DEPTH_STENCIL,
Expand All @@ -53,7 +55,6 @@ const cameraDir: Vec3[] = [

export class ReflectionProbe {
public bakedCubeTextures: RenderTexture[] = [];

public realtimePlanarTexture: RenderTexture | null = null;

protected _resolution = 256;
Expand All @@ -63,7 +64,13 @@ export class ReflectionProbe {
protected _probeType = ProbeType.CUBE;
protected _cubemap: TextureCube | null = null;
protected readonly _size = new Vec3(1, 1, 1);

/**
* @en Reflection probe cube pattern preview sphere
* @zh 反射探针cube模式的预览小球
*/
protected _previewSphere: Node | null = null;
protected _previewPlane: Node | null = null;
protected _planarReflectionTexture: Texture | null = null;
/**
* @en Render cubemap's camera
* @zh 渲染cubemap的相机
Expand Down Expand Up @@ -113,13 +120,9 @@ export class ReflectionProbe {
*/
private _up = new Vec3();

/**
* @en Reflection probe cube pattern preview sphere
* @zh 反射探针cube模式的预览小球
*/
protected _previewSphere: Node | null = null;
private _mipmapCount = 1;

protected _previewPlane: Node | null = null;
private _textureRegion: TextureBlit = new TextureBlit();

/**
* @en Set probe type,cube or planar.
Expand Down Expand Up @@ -273,6 +276,10 @@ export class ReflectionProbe {
return this._previewPlane!;
}

get planarMipmapCount (): number {
return this._mipmapCount;
}

constructor (id: number) {
this._probeId = id;
}
Expand All @@ -283,6 +290,26 @@ export class ReflectionProbe {
const pos = this.node.getWorldPosition();
this._boundingBox = geometry.AABB.create(pos.x, pos.y, pos.z, this._size.x, this._size.y, this._size.z);
this._createCamera(cameraNode);

const width = legacyCC.view.getViewportRect().width;
const height = legacyCC.view.getViewportRect().height;
this.realtimePlanarTexture = this._createTargetTexture(width, height);

if (canGenerateMipmap(deviceManager.gfxDevice, width, height)) {
this._mipmapCount = 1;
this._mipmapCount = getMipLevel(width, height);

this._planarReflectionTexture = deviceManager.gfxDevice.createTexture(new TextureInfo(
TextureType.TEX2D,
TextureUsageBit.SAMPLED | TextureUsageBit.TRANSFER_DST,
Format.RGBA8,
width,
height,
TextureFlagBit.GEN_MIPMAP,
1,
this._mipmapCount,
));
}
}

public initBakedTextures (): void {
Expand All @@ -307,11 +334,6 @@ export class ReflectionProbe {
*/
public renderPlanarReflection (sourceCamera: Camera): void {
if (!sourceCamera) return;
if (!this.realtimePlanarTexture) {
const canvasSize = cclegacy.view.getDesignResolutionSize();
this.realtimePlanarTexture = this._createTargetTexture(canvasSize.width, canvasSize.height);
cclegacy.internal.reflectionProbeManager.updatePlanarMap(this, this.realtimePlanarTexture.getGFXTexture());
}
this._syncCameraParams(sourceCamera);
this._transformReflectionCamera(sourceCamera);
this._needRender = true;
Expand Down Expand Up @@ -363,6 +385,10 @@ export class ReflectionProbe {
this.realtimePlanarTexture.destroy();
this.realtimePlanarTexture = null;
}
if (this._planarReflectionTexture) {
this._planarReflectionTexture.destroy();
this._planarReflectionTexture = null;
}
}
public enable (): void {
}
Expand Down Expand Up @@ -404,6 +430,35 @@ export class ReflectionProbe {
//todo: realtime do not use rgbe
return true;
}
/**
* @engineInternal
*/
public copyTextureToMipmap (): void {
if (!this.realtimePlanarTexture || !this._planarReflectionTexture || !deviceManager.gfxDevice) {
return;
}
let width = this.realtimePlanarTexture.width;
let height = this.realtimePlanarTexture.height;

this._textureRegion.srcExtent.width = width;
this._textureRegion.srcExtent.height = height;
const srcTexture = this.realtimePlanarTexture.getGFXTexture()!;
for (let i = 0; i < this._mipmapCount; i++) {
this._textureRegion.dstExtent.width = width;
this._textureRegion.dstExtent.height = height;
this._textureRegion.dstSubres.mipLevel = i;
deviceManager.gfxDevice.commandBuffer.blitTexture(srcTexture, this._planarReflectionTexture, [this._textureRegion], Filter.LINEAR);
width >>= 1;
height >>= 1;
}
}

/**
* @engineInternal
*/
public getPlanarReflectionTexture (): Texture | null {
return this._planarReflectionTexture ? this._planarReflectionTexture : this.realtimePlanarTexture!.getGFXTexture();
}

private _syncCameraParams (camera: Camera): void {
this.camera.projectionType = camera.projectionType;
Expand Down
1 change: 1 addition & 0 deletions cocos/rendering/post-process/passes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export * from './tone-mapping-pass';
export * from './forward-transparency-pass';
export * from './forward-transparency-simple-pass';
export * from './skin-pass';
export * from './reflection-probe-pass';
74 changes: 74 additions & 0 deletions cocos/rendering/post-process/passes/reflection-probe-pass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Vec4, cclegacy } from '../../../core';

import { ClearFlagBit, Color, Format, StoreOp, Viewport } from '../../../gfx';
import { Camera, ProbeType, ReflectionProbe } from '../../../render-scene/scene';
import { AttachmentType, LightInfo, QueueHint, ResourceResidency, SceneFlags } from '../../custom/types';
import { Pipeline } from '../../custom/pipeline';
import { passContext } from '../utils/pass-context';
import { BasePass } from './base-pass';
import { RenderWindow } from '../../../render-scene/core/render-window';
import { getLoadOpOfClearFlag, updateCameraUBO } from '../../custom/define';

export class ReflectionProbePass extends BasePass {
name = 'ReflectionProbePass';
outputNames = ['ReflectionProbeColor', 'ReflectionProbeDS']

enableInAllEditorCamera = true;
depthBufferShadingScale = 1;

slotName (camera: Camera, index = 0): string {
return this.lastPass!.slotName(camera, index);
}

public render (camera: Camera, ppl: Pipeline): void {
if (!cclegacy.internal.reflectionProbeManager) return;
const probes = cclegacy.internal.reflectionProbeManager.getProbes();
if (probes.length === 0) return;
for (let i = 0; i < probes.length; i++) {
const probe = probes[i];
if (probe.needRender) {
if (probe.probeType === ProbeType.PLANAR) {
this.buildReflectionProbePass(camera, ppl, probe, probe.realtimePlanarTexture.window!, 0);
probe.copyTextureToMipmap();
} else if (probe.probeType === ProbeType.CUBE) {
for (let faceIdx = 0; faceIdx < probe.bakedCubeTextures.length; faceIdx++) {
probe.updateCameraDir(faceIdx);
this.buildReflectionProbePass(camera, ppl, probe, probe.bakedCubeTextures[faceIdx].window!, faceIdx);
}
probe.needRender = false;
}
}
}
}

public buildReflectionProbePass (camera: Camera,
ppl: Pipeline, probe: ReflectionProbe, renderWindow: RenderWindow, faceIdx: number): void {
const cameraName = `Camera${faceIdx}`;
const area = probe.renderArea();
const width = area.x;
const height = area.y;
const probeCamera = probe.camera;

const probePassRTName = `reflectionProbePassColor${cameraName}`;
const probePassDSName = `reflectionProbePassDS${cameraName}`;

if (!ppl.containsResource(probePassRTName)) {
ppl.addRenderWindow(probePassRTName, Format.RGBA8, width, height, renderWindow);
ppl.addDepthStencil(probePassDSName, Format.DEPTH_STENCIL, width, height, ResourceResidency.EXTERNAL);
}
ppl.updateRenderWindow(probePassRTName, renderWindow);
ppl.updateDepthStencil(probePassDSName, width, height);

const probePass = ppl.addRenderPass(width, height, 'default');

probePass.name = `ReflectionProbePass${faceIdx}`;
probePass.setViewport(new Viewport(0, 0, width, height));
probePass.addRenderTarget(probePassRTName, getLoadOpOfClearFlag(probeCamera.clearFlag, AttachmentType.RENDER_TARGET),
StoreOp.STORE, new Color(probeCamera.clearColor.x, probeCamera.clearColor.y, probeCamera.clearColor.z, probeCamera.clearColor.w));
probePass.addDepthStencil(probePassDSName, getLoadOpOfClearFlag(probeCamera.clearFlag, AttachmentType.DEPTH_STENCIL),
StoreOp.STORE, probeCamera.clearDepth, probeCamera.clearStencil, probeCamera.clearFlag);
const passBuilder = probePass.addQueue(QueueHint.RENDER_OPAQUE);
passBuilder.addSceneOfCamera(camera, new LightInfo(), SceneFlags.REFLECTION_PROBE);
updateCameraUBO(passBuilder as unknown as any, probeCamera, ppl);
}
}
7 changes: 4 additions & 3 deletions cocos/rendering/post-process/post-process-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { PostProcess } from './components/post-process';
import { director } from '../../game';

import { Camera as CameraComponent } from '../../misc';
import { BloomPass, ColorGradingPass, ForwardTransparencyPass, ForwardTransparencySimplePass, FxaaPass, SkinPass, ToneMappingPass } from './passes';
import { BloomPass, ColorGradingPass, ForwardTransparencyPass, ForwardTransparencySimplePass, FxaaPass, ReflectionProbePass, SkinPass, ToneMappingPass } from './passes';
import { PipelineEventType } from '../pipeline-event';

export class PostProcessBuilder implements PipelineBuilder {
Expand All @@ -46,6 +46,9 @@ export class PostProcessBuilder implements PipelineBuilder {
this.addPass(new SkinPass());
this.addPass(new ForwardTransparencyPass());

//reflection probe
this.addPass(new ReflectionProbePass());

// pipeline related
this.addPass(new HBAOPass());
this.addPass(new ToneMappingPass());
Expand Down Expand Up @@ -165,8 +168,6 @@ export class PostProcessBuilder implements PipelineBuilder {
this.applyPreviewCamera(camera);
}

buildReflectionProbePasss(camera, ppl);

passContext.postProcess = camera.postProcess || globalPP;

director.root!.pipelineEvent.emit(PipelineEventType.RENDER_CAMERA_BEGIN, camera);
Expand Down
3 changes: 1 addition & 2 deletions cocos/rendering/reflection-probe/reflection-probe-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,9 @@ export class ReflectionProbeFlow extends RenderFlow {
for (let i = 0; i < this._stages.length; i++) {
const probeStage = this._stages[i] as ReflectionProbeStage;
if (probe.probeType === ProbeType.PLANAR) {
cclegacy.internal.reflectionProbeManager.updatePlanarMap(probe, null);
probeStage.setUsageInfo(probe, probe.realtimePlanarTexture!.window!.framebuffer);
probeStage.render(camera);
cclegacy.internal.reflectionProbeManager.updatePlanarMap(probe, probe.realtimePlanarTexture!.getGFXTexture());
probe.copyTextureToMipmap();
} else {
for (let faceIdx = 0; faceIdx < 6; faceIdx++) {
const renderTexture = probe.bakedCubeTextures[faceIdx];
Expand Down
3 changes: 2 additions & 1 deletion editor/assets/chunks/builtin/functionalities/probe.chunk
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ void GetPlanarReflectionProbeData(out vec4 plane, out float planarReflectionDept
float dataMapWidth = 12.0;
vec4 texData1 = GetTexData(cc_reflectionProbeDataMap, dataMapWidth, 0.0, uv_y);
vec4 texData2 = GetTexData(cc_reflectionProbeDataMap, dataMapWidth, 4.0, uv_y);
vec4 texData3 = GetTexData(cc_reflectionProbeDataMap, dataMapWidth, 8.0, uv_y);
plane.xyz = texData1.xyz;
plane.w = texData2.x;
planarReflectionDepthScale = texData2.y;
mipCount = texData2.z;
mipCount = texData3.x;
#else
plane = cc_reflectionProbeData1;
planarReflectionDepthScale = cc_reflectionProbeData2.x;
Expand Down
5 changes: 5 additions & 0 deletions editor/assets/chunks/common/texture/cubemap.chunk
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ float RoughnessToPerceptualRoughness(float roughness)
return sqrt(roughness);
}

float GetRoughnessWithoutConvolution(float roughness)
{
return pow(roughness, 0.5);
}

// for legacy only
#pragma extension([GL_OES_standard_derivatives, __VERSION__ < 110])
vec3 EnvReflectionWithMipFiltering(vec3 R, float roughness, float mipCount, float denoiseIntensity) {
Expand Down
Loading