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

[Fix] Multiple changes and fixed related to the way UI system handles srgb #7335

Merged
merged 3 commits into from
Feb 7, 2025
Merged
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
Binary file added examples/assets/textures/colors.webp
Binary file not shown.
2 changes: 1 addition & 1 deletion examples/src/examples/graphics/depth-of-field.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ assetListLoader.load(() => {
app.scene.envAtlas = assets.helipad.resource;
app.scene.exposure = 1.2;

// create an instance of the platform and add it to the scene
// create an instance of the apartment and add it to the scene
const platformEntity = assets.apartment.resource.instantiateRenderEntity();
platformEntity.setLocalScale(30, 30, 30);
app.root.addChild(platformEntity);
Expand Down
58 changes: 58 additions & 0 deletions examples/src/examples/graphics/hdr.controls.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import * as pc from 'playcanvas';

/**
* @param {import('../../app/components/Example.mjs').ControlOptions} options - The options.
* @returns {JSX.Element} The returned JSX Element.
*/
export const controls = ({ observer, ReactPCUI, React, jsx, fragment }) => {
const { BindingTwoWay, BooleanInput, SelectInput, LabelGroup, Panel } = ReactPCUI;
return fragment(
jsx(
Panel,
{ headerText: 'Rendering' },
jsx(
LabelGroup,
{ text: 'HDR' },
jsx(BooleanInput, {
type: 'toggle',
binding: new BindingTwoWay(),
link: { observer, path: 'data.hdr' }
})
)
),
jsx(
LabelGroup,
{ text: 'Scene Tonemap' },
jsx(SelectInput, {
binding: new BindingTwoWay(),
link: { observer, path: 'data.sceneTonemapping' },
type: 'number',
options: [
{ v: pc.TONEMAP_LINEAR, t: 'LINEAR' },
{ v: pc.TONEMAP_FILMIC, t: 'FILMIC' },
{ v: pc.TONEMAP_HEJL, t: 'HEJL' },
{ v: pc.TONEMAP_ACES, t: 'ACES' },
{ v: pc.TONEMAP_ACES2, t: 'ACES2' },
{ v: pc.TONEMAP_NEUTRAL, t: 'NEUTRAL' }
]
})
),
jsx(
LabelGroup,
{ text: 'UI Tonemap' },
jsx(SelectInput, {
binding: new BindingTwoWay(),
link: { observer, path: 'data.uiTonemapping' },
type: 'number',
options: [
{ v: pc.TONEMAP_NONE, t: 'NONE' },
{ v: pc.TONEMAP_FILMIC, t: 'FILMIC' },
{ v: pc.TONEMAP_HEJL, t: 'HEJL' },
{ v: pc.TONEMAP_ACES, t: 'ACES' },
{ v: pc.TONEMAP_ACES2, t: 'ACES2' },
{ v: pc.TONEMAP_NEUTRAL, t: 'NEUTRAL' }
]
})
)
);
};
196 changes: 196 additions & 0 deletions examples/src/examples/graphics/hdr.example.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import { data } from 'examples/observer';
import { deviceType, rootPath } from 'examples/utils';
import * as pc from 'playcanvas';

const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas'));
window.focus();

const assets = {
orbit: new pc.Asset('script', 'script', { url: `${rootPath}/static/scripts/camera/orbit-camera.js` }),
apartment: new pc.Asset('apartment', 'container', { url: `${rootPath}/static/assets/models/apartment.glb` }),
love: new pc.Asset('love', 'container', { url: `${rootPath}/static/assets/models/love.glb` }),
colors: new pc.Asset('colors', 'texture', { url: `${rootPath}/static/assets/textures/colors.webp` }, { srgb: true }),
helipad: new pc.Asset(
'helipad-env-atlas',
'texture',
{ url: `${rootPath}/static/assets/cubemaps/helipad-env-atlas.png` },
{ type: pc.TEXTURETYPE_RGBP, mipmaps: false }
)
};

const gfxOptions = {
deviceTypes: [deviceType],
glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`,
twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js`,

// The scene is rendered to an antialiased texture, so we disable antialiasing on the canvas
// to avoid the additional cost. This is only used for the UI which renders on top of the
// post-processed scene, and we're typically happy with some aliasing on the UI.
antialias: false
};

const device = await pc.createGraphicsDevice(canvas, gfxOptions);
device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);

const createOptions = new pc.AppOptions();
createOptions.graphicsDevice = device;
createOptions.mouse = new pc.Mouse(document.body);
createOptions.touch = new pc.TouchDevice(document.body);
createOptions.keyboard = new pc.Keyboard(window);

createOptions.componentSystems = [
pc.RenderComponentSystem,
pc.CameraComponentSystem,
pc.LightComponentSystem,
pc.ScriptComponentSystem,
pc.ScreenComponentSystem,
pc.ButtonComponentSystem,
pc.ElementComponentSystem
];
createOptions.resourceHandlers = [
pc.TextureHandler,
pc.ContainerHandler,
pc.ScriptHandler,
pc.FontHandler
];

const app = new pc.AppBase(canvas);
app.init(createOptions);

// Set the canvas to fill the window and automatically change resolution to be the same as the canvas size
app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
app.setCanvasResolution(pc.RESOLUTION_AUTO);

// Ensure canvas is resized when window changes size
const resize = () => app.resizeCanvas();
window.addEventListener('resize', resize);
app.on('destroy', () => {
window.removeEventListener('resize', resize);
});

const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets);
assetListLoader.load(() => {
app.start();

// setup skydome with low intensity
app.scene.envAtlas = assets.helipad.resource;
app.scene.exposure = 1.2;

// create an instance of the apartment and add it to the scene
const platformEntity = assets.apartment.resource.instantiateRenderEntity();
platformEntity.setLocalScale(30, 30, 30);
app.root.addChild(platformEntity);

// load a love sign model and add it to the scene
const loveEntity = assets.love.resource.instantiateRenderEntity();
loveEntity.setLocalPosition(-80, 30, -20);
loveEntity.setLocalScale(130, 130, 130);
loveEntity.rotate(0, -90, 0);
app.root.addChild(loveEntity);

// make the love sign emissive to bloom
const loveMaterial = loveEntity.findByName('s.0009_Standard_FF00BB_0').render.meshInstances[0].material;
loveMaterial.emissiveIntensity = 200;
loveMaterial.update();

// adjust all materials of the love sign to disable dynamic refraction
loveEntity.findComponents('render').forEach((render) => {
render.meshInstances.forEach((meshInstance) => {
meshInstance.material.useDynamicRefraction = false;
});
});

// Create an Entity with a camera component
const cameraEntity = new pc.Entity();
cameraEntity.addComponent('camera', {
farClip: 1500,
fov: 80
});

const focusPoint = new pc.Entity();
focusPoint.setLocalPosition(-80, 80, -20);

// add orbit camera script with a mouse and a touch support
cameraEntity.addComponent('script');
cameraEntity.script.create('orbitCamera', {
attributes: {
inertiaFactor: 0.2,
focusEntity: focusPoint,
distanceMax: 500,
frameOnStart: false
}
});
cameraEntity.script.create('orbitCameraInputMouse');
cameraEntity.script.create('orbitCameraInputTouch');

cameraEntity.setLocalPosition(-50, 100, 220);
cameraEntity.lookAt(0, 0, 100);
app.root.addChild(cameraEntity);

// Create a 2D screen
const screen = new pc.Entity();
screen.addComponent('screen', {
referenceResolution: new pc.Vec2(1280, 720),
scaleBlend: 0.5,
scaleMode: pc.SCALEMODE_BLEND,
screenSpace: true
});
app.root.addChild(screen);

// Create a new entity for the UI element
const uiElement = new pc.Entity();

// Add a UI component with an image type
const texture = assets.colors.resource;
uiElement.addComponent('element', {
type: 'image',
anchor: [1, 0, 1, 0],
pivot: [1, 0],
width: texture.width * 0.5,
height: texture.height * 0.5,
texture: texture
});
uiElement.setLocalPosition(-0.1 * texture.width, 0.1 * texture.height, 0);
screen.addChild(uiElement);

// ------ Custom render passes set up ------

const cameraFrame = new pc.CameraFrame(app, cameraEntity.camera);
cameraFrame.rendering.samples = 4;
cameraFrame.bloom.intensity = 0.03;
cameraFrame.bloom.blurLevel = 7;
cameraFrame.vignette.inner = 0.5;
cameraFrame.vignette.outer = 1;
cameraFrame.vignette.curvature = 0.5;
cameraFrame.vignette.intensity = 0.5;
cameraFrame.update();

// apply UI changes
data.on('*:set', (/** @type {string} */ path, value) => {

if (path === 'data.hdr') {
cameraFrame.enabled = value;
cameraFrame.update();
}

if (path === 'data.sceneTonemapping') {
// postprocessing tone mapping
cameraFrame.rendering.toneMapping = value;
cameraFrame.update();
}

if (path === 'data.uiTonemapping') {
// camera tone mapping is applied to the UI
cameraEntity.camera.toneMapping = value;
}
});

// set initial values
data.set('data', {
hdr: true,
sceneTonemapping: pc.TONEMAP_ACES,
uiTonemapping: pc.TONEMAP_NONE
});
});

export { app };
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const assets = {
font: new pc.Asset('font', 'font', { url: `${rootPath}/static/assets/fonts/courier.json` }),
red_button_atlas: new pc.Asset('red_button_atlas', 'texture', {
url: `${rootPath}/static/assets/button/red_button_atlas.png`
})
}, { srgb: true })
};

const gfxOptions = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('applic
window.focus();

const assets = {
playcanvas: new pc.Asset('playcanvas', 'texture', { url: `${rootPath}/static/assets/textures/playcanvas.png` })
playcanvas: new pc.Asset('playcanvas', 'texture', { url: `${rootPath}/static/assets/textures/playcanvas.png` }, { srgb: true })
};

const gfxOptions = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@
* varying vec2 vUv0: for the UV.
*/

// The following line is for setting the shader precision for floats. It is commented out because, ideally, it must be configured
// on a per-device basis before loading the Shader. Please check the accompanying TypeScript code and look for 'app.graphicsDevice.precision'.

// precision mediump float;
#include "gammaPS"

// Additional varying from vertex shader
varying vec2 vUv0;
Expand All @@ -26,5 +23,5 @@ void main(void)
{
vec4 color = texture2D(uDiffuseMap, vUv0);
vec3 roloc = 1.0 - color.rgb;
gl_FragColor = vec4(mix(color.rgb, roloc, amount), color.a);
gl_FragColor = vec4(gammaCorrectOutput(mix(color.rgb, roloc, amount)), color.a);
}
Binary file added examples/thumbnails/graphics_hdr_large.webp
Binary file not shown.
Binary file added examples/thumbnails/graphics_hdr_small.webp
Binary file not shown.
4 changes: 3 additions & 1 deletion scripts/esm/camera-frame.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -351,10 +351,12 @@ class CameraFrame extends Script {
*/
dof = new Dof();

engineCameraFrame = new EngineCameraFrame(this.app, this.entity.camera);
engineCameraFrame;

initialize() {

this.engineCameraFrame = new EngineCameraFrame(this.app, this.entity.camera);

this.on('enable', () => {
this.engineCameraFrame.enabled = true;
});
Expand Down
4 changes: 0 additions & 4 deletions src/extras/render-passes/camera-frame.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Debug } from '../../core/debug.js';
import { Color } from '../../core/math/color.js';
import { math } from '../../core/math/math.js';
import { PIXELFORMAT_111110F, PIXELFORMAT_RGBA16F, PIXELFORMAT_RGBA32F } from '../../platform/graphics/constants.js';
import { GAMMA_SRGB } from '../../scene/constants.js';
import { SSAOTYPE_NONE } from './constants.js';
import { CameraFrameOptions, RenderPassCameraFrame } from './render-pass-camera-frame.js';

Expand Down Expand Up @@ -335,9 +334,6 @@ class CameraFrame {

cameraComponent.jitter = 0;

// no longer HDR rendering
cameraComponent.gammaCorrection = GAMMA_SRGB;

// disable SSAO included in the lighting pass
cameraComponent.shaderParams.ssaoEnabled = false;

Expand Down
12 changes: 8 additions & 4 deletions src/extras/render-passes/render-pass-camera-frame.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,6 @@ class RenderPassCameraFrame extends RenderPass {
// bloom and DOF needs half resolution scene texture
this._sceneHalfEnabled = this._bloomEnabled || options.dofEnabled;

// camera renders in HDR mode (linear output, no tonemapping)
cameraComponent.gammaCorrection = GAMMA_NONE;
cameraComponent.toneMapping = TONEMAP_NONE;

// set up internal rendering parameters - this affect the shader generation to apply SSAO during forward pass
cameraComponent.shaderParams.ssaoEnabled = options.ssaoType === SSAOTYPE_LIGHTING;

Expand Down Expand Up @@ -323,6 +319,12 @@ class RenderPassCameraFrame extends RenderPass {
}
}

setupScenePassSettings(pass) {
// forward passes render in HDR
pass.gammaCorrection = GAMMA_NONE;
pass.toneMapping = TONEMAP_NONE;
}

setupScenePass(options) {

const { app, device, cameraComponent } = this;
Expand All @@ -333,6 +335,7 @@ class RenderPassCameraFrame extends RenderPass {
// matches the back-buffer size with the optional scale. Note that the scale parameters
// allow us to render the 3d scene at lower resolution, improving performance.
this.scenePass = new RenderPassForward(device, composition, scene, renderer);
this.setupScenePassSettings(this.scenePass);
this.scenePass.init(this.rt, this.sceneOptions);

// layers this pass renders depend on the grab pass being used
Expand All @@ -356,6 +359,7 @@ class RenderPassCameraFrame extends RenderPass {

// if grab pass is used, render the layers after it (otherwise they were already rendered)
this.scenePassTransparent = new RenderPassForward(device, composition, scene, renderer);
this.setupScenePassSettings(this.scenePassTransparent);
this.scenePassTransparent.init(this.rt);
ret.lastAddedIndex = this.scenePassTransparent.addLayers(composition, cameraComponent, ret.lastAddedIndex, ret.clearRenderTarget, options.lastSceneLayerId, options.lastSceneLayerIsTransparent);

Expand Down
4 changes: 2 additions & 2 deletions src/framework/components/element/system.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Color } from '../../../core/math/color.js';
import { Vec2 } from '../../../core/math/vec2.js';
import { Vec4 } from '../../../core/math/vec4.js';
import { PIXELFORMAT_RGBA8 } from '../../../platform/graphics/constants.js';
import { PIXELFORMAT_SRGBA8 } from '../../../platform/graphics/constants.js';
import { Texture } from '../../../platform/graphics/texture.js';
import { BLEND_PREMULTIPLIED, SPRITE_RENDERMODE_SLICED, SPRITE_RENDERMODE_TILED } from '../../../scene/constants.js';
import { StandardMaterial } from '../../../scene/materials/standard-material.js';
Expand Down Expand Up @@ -44,7 +44,7 @@ class ElementComponentSystem extends ComponentSystem {
this._defaultTexture = new Texture(app.graphicsDevice, {
width: 1,
height: 1,
format: PIXELFORMAT_RGBA8,
format: PIXELFORMAT_SRGBA8,
name: 'element-system'
});
const pixels = this._defaultTexture.lock();
Expand Down
Loading