Skip to content
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

Post effects #5742

Merged
merged 15 commits into from
Oct 25, 2022
Merged

Post effects #5742

merged 15 commits into from
Oct 25, 2022

Conversation

netpro2k
Copy link
Contributor

@netpro2k netpro2k commented Oct 11, 2022

This PR adds support for Post Processing effects, starting with Bloom. At first this will be double opt-in, with both users needing to enable a preference, and scene authors needing to export with an updated environment-settings component configured for post effects. The plan is to make the user preference default enabled eventually on systems that can handle it. It is currently not supported in VR and the preference will be hidden if we detect the user is on a standalone VR headset.

Currently when opting into the new "HDR pipeline", tonemapping settings will be ignored and ACES will always be used. It's likely we will want this to be configurable eventually but supporting our existing tonemapping options (namely the Blender LUT one) is a bit complicated and requires some changes to the underlying postprocessing library. Its possible we will want to standardize on ACES and then offer additional LUT after tonemaping for artistic effects. We also likely want to explore adaptive exposure, which will be difficult to support for all tonemapping modes.

This PR also slightly re-organizes the graphics options in the preferences screen. Ideally we will want to eventually have a separate tab for this, but doing so requires rethinking the UI a bit for mobile so is out of scope for this PR. That likely would pair well with a PR around better configuration of graphics defaults.

Blender addon: Hubs-Foundation/hubs-blender-exporter#142

scale
)
);
setMatrixWorld(this.avatarRig.object3D, new THREE.Matrix4().compose(position, quat, scale));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to avoid to create a new matrix instance in tick (Sorry, this is an existing issue. Making another PR is ok.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this was just a formatting change. I see a few spots in here we are creating some garbage.

@takahirox
Copy link
Contributor

takahirox commented Oct 12, 2022

Thanks for the PR.

This PR also slightly re-organizes the graphics options in the preferences screen

Would you mind putting the screenshot of the new preferences screen here?

Question: Does the post-processing work even in the immersive (VR) mode?

stencil: true,
premultipliedAlpha: true,
preserveDrawingBuffer: false,
logarithmicDepthBuffer: false,
Copy link
Contributor

@takahirox takahirox Oct 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify, you have removed premultipliedAlpha, preserveDrawingBuffer, and logarithmicDepthBuffer because they use the default values?

https://threejs.org/docs/#api/en/renderers/WebGLRenderer.preserveDrawingBuffer

And you have toggled stencil to false because we don't use and/or it blocks post processing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, exactly

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope we can have a separated clean up PR from a logic addition/change PR because review can be easier.

src/app.ts Outdated
@@ -166,7 +174,7 @@ export class App {

sceneEl.appendChild(renderer.domElement);

const camera = new PerspectiveCamera(80, window.innerWidth / window.innerHeight, 0.05, 10000);
const camera = new PerspectiveCamera(80, canvas.width / canvas.height, 0.05, 10000);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious, what is this change for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah that may not actually be needed anymore. Early on when working on this I was using a static size canvas to simplify things. This value doesn't particularly matter anyway since it gets overwritten again once useResizeViewport runs, but I will ditch the change.

We end up doing way more resizing of the canvas than I would like but its a bit tricky to untangle right now since our initialization is still driven by aframe, then react, so at this point we don't actually know what the dimensions of the canvas will end up being.

(sceneEl as any).addEventListener("rendererresize", function ({ detail }: { detail: DOMRectReadOnly }) {
renderer.setSize(detail.width, detail.height, true);
});
}
Copy link
Contributor

@takahirox takahirox Oct 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does here mean renderer resize handling is done in the effect composer if post effects is enabled? (Maybe adding comments is good)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC the resizing is handled by the composer based on the useViewport hook resize listener. We are setting the canvas style size in the hook but here we are forcing a style update that we weren't doing before and I'm not sure why.

Also in effects.ts we are also forcing a style update when updating the renderer size but we are already doing that in the useViewport hook so we are overriding that update.

MSAA_2X = "MASS_2X",
MSAA_4X = "MASS_4X",
MSAA_8X = "MASS_8X"
}
Copy link
Contributor

@takahirox takahirox Oct 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering whether we really need five aa options because it may add complexity in the preferences. For example aren't just two options (ON(MSAA_4X?)/OFF(NONE)) good? No big preference tho...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want at least off, SMAA, and some form of MSAA. I mostly included all of these so that people can test different options for visual differences and perf on different devices.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did notice the constants for MSAA all have a typo though.. will fix

const scene = sceneEl.object3D;

// We manually call scene.updateMatrixWolrd in mainTick
scene.autoUpdate = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this manual updating for post processing? Can auto update break the post processing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Post processing can call render multiple times, so it makes more sense to just manually update ourselves once. This is also something we wanted to do anyway so that we can easily see where in the frame matrix updates are happening and schedule systems before/after that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Post processing can call render multiple times, so it makes more sense to just manually update ourselves once.

OK, makese sense to me. I thought postprocessing lib set scene.autoUpdate false but if it doesn't, setting scene.autoUpdate false and manually calling update matrix world in our code makes sense.

// In VR mode, three handles canvas resize based on the dimensions returned by
// the getEyeParameters function of the WebVR API. These dimensions are independent of
// the window size, therefore should not be overwritten with the window's width and
// height, // except when in fullscreen mode.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra // after height,

const isVRPresenting = scene.renderer.xr.enabled && isPresenting;
// Do not update renderer, if a camera or a canvas have not been injected.
// In VR mode, three handles canvas resize based on the dimensions returned by
// the getEyeParameters function of the WebVR API. These dimensions are independent of
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean Three.js still handles WebVR API? I thought it has dropped WebVR API support...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment is just out of date. It is still true though that WebXRManager handles the size of its framebuffers internally.

key: "aaMode",
prefType: PREFERENCE_LIST_ITEM_TYPE.SELECT,
disableIfFalse: "enablePostEffects",
promptForRefresh: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do enableBloom and aaMode toggles really require the refresh? Can't we skip the effect passes if they are turned off?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think both of these could probably be made to work but it seemed simpler just to have users refresh for now. Toggling the main option definitely requires a refresh since it changes how we initialize our WebGL context.

) {
const composer = new EffectComposer(renderer, {
frameBufferType: HalfFloatType
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The core post-processing logic is in an external library postprocessing. I don't think I have time to check the lib internal so want to check if it works fine on the test after landing the post-processing feature.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. Its definitely worth us reading through and understanding the internals well. I have read a lot of it myself, particularly for the effects we are using.

bloomFolder.add(debugSettings.bloom, "radius", 0, 1, 0.001).onChange(updateDebug).listen();
bloomFolder.add(debugSettings.bloom, "smoothing", 0, 1, 0.001).onChange(updateDebug).listen();
// bloomFolder.add(bloom.blendMode, "blendFunction", BlendFunction);
// bloomFolder.add(bloom.blendMode.opacity, "value", 0, 1).name("Opacity");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of the scope in the PR: Wondering if we can separate the core logic code/files and debug code/files more clearly. The mix of core and debug codes can make the code less readable especially for new people.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I agree. I was debating if I should just remove this debug stuff altogether but it seems really useful for early testing on this. I agree we should figure out a nicer way to split out debug logic.

(sceneEl as any).addEventListener("rendererresize", function ({ detail }: { detail: DOMRectReadOnly }) {
renderer.setSize(detail.width, detail.height, true);
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC the resizing is handled by the composer based on the useViewport hook resize listener. We are setting the canvas style size in the hook but here we are forcing a style update that we weren't doing before and I'm not sure why.

Also in effects.ts we are also forcing a style update when updating the renderer size but we are already doing that in the useViewport hook so we are overriding that update.

src/effects.ts Outdated
}

(sceneEl as any).addEventListener("rendererresize", function ({ detail }: { detail: DOMRectReadOnly }) {
composer.setSize(detail.width, detail.height, true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to override the canvas style update here? useViewport is already setting the style size so we are overriding it here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah good catch. Updated here and in EffectComposer setSize call as well. As written its actually a bug since sometimes we want to render at a lower resolution than the canvas is sized to.

@keianhzo
Copy link
Contributor

Note: I mistakenly set the review to "request changes" but my comments are mostly questions.

@netpro2k netpro2k merged commit ecadbbc into master Oct 25, 2022
@netpro2k netpro2k deleted the post-effects branch October 25, 2022 00:00
@jywarren
Copy link

Many wows! ❤️ This is very exciting!

Looking at https://www.npmjs.com/package/postprocessing, i see it can support depth of field (#5415), god rays, ambient occlusion, glitch, pixellation, and others.

Nice demos too: https://pmndrs.github.io/postprocessing/public/demo/#depth-of-field

Are there plans to enable many of these?

Also, I noticed a difference in how some items are lit, or maybe opacity differences, I believe, when this is enabled; these are alpha-transparent cloud PNGs in a particle system to simulate smoke; i can just adjust settings to fix but in case it's an issue you want to see reported:

Before:

Screen Shot 2022-12-11 at 2 16 00 PM

After:

Screen Shot 2022-12-11 at 2 16 48 PM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants