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

TSL: New color space name functions #29259

Merged
merged 1 commit into from
Aug 29, 2024
Merged

TSL: New color space name functions #29259

merged 1 commit into from
Aug 29, 2024

Conversation

sunag
Copy link
Collaborator

@sunag sunag commented Aug 29, 2024

Related issue: #29248 (comment)

Description

Description updated in this PR

@sunag sunag marked this pull request as ready for review August 29, 2024 11:39
@sunag sunag added this to the r168 milestone Aug 29, 2024
Copy link

📦 Bundle size

Full ESM build, minified and gzipped.

Filesize dev Filesize PR Diff
WebGLRenderer 685.4 kB (169.7 kB) 685.4 kB (169.7 kB) +0 B
WebGPURenderer 818.6 kB (221.2 kB) 818.6 kB (221.2 kB) -27 B

🌳 Bundle size after tree-shaking

Minimal build including a renderer, camera, empty scene, and dependencies.

Filesize dev Filesize PR Diff
WebGLRenderer 462 kB (111.4 kB) 462 kB (111.4 kB) +0 B
WebGPURenderer 564.8 kB (153.4 kB) 564.8 kB (153.4 kB) -20 B

Copy link
Collaborator

@donmccurdy donmccurdy left a comment

Choose a reason for hiding this comment

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

LGTM, thank you! 🫶

@sunag
Copy link
Collaborator Author

sunag commented Aug 29, 2024

Thank you too :)

@sunag sunag merged commit 86a663d into mrdoob:dev Aug 29, 2024
12 checks passed
Comment on lines +81 to +82
export const toOutputColorSpace = ( node, colorSpace = null ) => nodeObject( new ColorSpaceNode( nodeObject( node ), colorSpace, LinearSRGBColorSpace ) );
export const toWorkingColorSpace = ( node, colorSpace = null ) => nodeObject( new ColorSpaceNode( nodeObject( node ), LinearSRGBColorSpace, colorSpace ) );
Copy link
Collaborator

Choose a reason for hiding this comment

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

These methods should not be conflated with outputColorSpace.

Copy link
Collaborator

@WestLangley WestLangley Aug 29, 2024

Choose a reason for hiding this comment

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

@donmccurdy We need to be able to covert to log space in a shader, and then convert back.

export const workingToColorSpace = ( node, colorSpace = null ) => nodeObject( new ColorSpaceNode( nodeObject( node ), colorSpace, workingColorSpace ) );

export const colorSpaceToWorking = ( node, colorSpace = null ) => nodeObject( new ColorSpaceNode( nodeObject( node ), workingColorSpace, colorSpace ) );

Copy link
Collaborator

Choose a reason for hiding this comment

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

toOutputColorSpace could be renamed to toColorSpace, but that begs the question: "from where?"

Copy link
Collaborator Author

@sunag sunag Aug 29, 2024

Choose a reason for hiding this comment

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

These methods should not be conflated with outputColorSpace.

toOutputColorSpace() will use renderer.outputColorSpace as default value, so I think there should be some representation. We have two base nomenclatures, which would be Output Color Space and Working Color Space in general terms.

Copy link
Collaborator Author

@sunag sunag Aug 29, 2024

Choose a reason for hiding this comment

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

Another thing we could take advantage of from TSL is to inform(analyzing the input node) through a context which color space that node is inserted in the code flow, this would avoid redundant conversions. Similar to what we used in node.toTexture() recently.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'll look into adding workingToColorSpace and colorSpaceToWorking.

For toColorSpace ( targetColorSpace, sourceColorSpace ) we should use direct functions like these sRGBToLinearSRGB, linearSRGBTosRGB, ...

Just for us to think about, we shouldn't get used to string as a parameter, because that requires us to create a library to support it and that breaks tree-shaking.

If we have 4 different types of color-space between working/output, we will have to have 16 conversion functions and this growth is exponential for each additional color-space that we add here.

The future of renderer.outputColorSpace type could be a Node instead of string, this is one of the valid reasons for the current abstraction output/working names.

That's not a concern right now, I just wanted to clarify those points.

Copy link
Collaborator

@donmccurdy donmccurdy Sep 15, 2024

Choose a reason for hiding this comment

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

@sunag I share your concern about the growing number of color space pairs, and their effect on tree-shaking. But I don't think I agree about using direct 1-to-1 functions or nodes for the conversions. For one thing, we have to import those functions, and call them conditionally, and if we have 4x4=16 functions it's going to be hard to ensure they're tree-shakeable for internal renderer usage.

A solution I'd prefer would be to continue using the predefined color space strings from WebGPU and CSS Color Module 4, but to allow users to define their own, and to import color spaces from three/addons. For example:

import { ColorManagement } from 'three';
import { Rec2100HLGColorSpace, Rec2100HLGImplementation } from 'three/addons/math/colorspaces/Rec2100HLG.js';

ColorManagement.addColorSpace( Rec2100HLGColorSpace, Rec2100HLGImplementation );

Each definition would be similar to what you see in ColorManagement.js, but represented as data properties, not JavaScript, so it's easy to do conversions in JS, GLSL, or WGSL. There's a similar approach in nanocolor that I believe is well done: https://github.com/meshula/Nanocolor/blob/main/nanocolor.c#L150

Once registered, a color space could be used for any compatible purpose. Any color space conversions to/from a color space not registered with THREE.ColorManagement may be considered an error. We could probably drop Display P3 and Linear Display P3 from the core library.

Copy link
Collaborator Author

@sunag sunag Sep 15, 2024

Choose a reason for hiding this comment

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

The conversion solution for direct functions is just the suggested function toColorSpace(), where it would be cleaner to do sRGBToLinearSRGB( a ) instead of a.toColorSpace( THREE.SRGBColorSpace, THREE.LinearSRGBColorSpace ) apart from the benefit of tree-shaking.

I don't think there was an appropriate solution in this sense for working/output so I created the PRs workingToColorSpace and colorSpaceToWorking following the suggestions.

I created a system based on this principle, it should already be functional. I think we can encourage users to add functions like a library to their projects instead of adding them all to the core, it would be a solution.

This could be added externally with:

const nodeLibrary = renderer.nodes.library;

nodeLibrary.addColorSpace( linearSRGBTosRGB, getColorSpaceMethod( LinearSRGBColorSpace, SRGBColorSpace ) ); 
nodeLibrary.addColorSpace( sRGBToLinearSRGB, getColorSpaceMethod( SRGBColorSpace, LinearSRGBColorSpace ) );

Looking at the code again, I would just make it simpler, e.g:

const nodeLibrary = renderer.nodes.library;

nodeLibrary.addColorSpace( linearSRGBTosRGB, THREE.LinearSRGBColorSpace, THREE.SRGBColorSpace ); 
nodeLibrary.addColorSpace( sRGBToLinearSRGB, THREE.SRGBColorSpace, THREE.LinearSRGBColorSpace );

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think we can ask users to foresee all possible source/target color space pairs, though. And with only a function/node defining a color space, it won't be possible to:

  1. Apply CPU-side calculations, like converting new Color( 0x808080 ) from sRGB to a user-defined working color space
  2. Determine whether a texture in a user-defined color space requires unpacking with gl.unpackColorSpace or not. For this we need to be able to query, for a particular texture's color space, what are the associated transfer functions?

I think the workingToColorSpace and colorSpaceToWorking functions will fit into either solution well. But I would like to refactor THREE.ColorManagement a little, so that the node system can infer how to convert between two previously unknown color spaces based on their definitions (primaries, white point, transfer functions), without needing to have a conversion function/node registered in advance.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't think we can ask users to foresee all possible source/target color space pairs, though. And with only a function/node defining a color space, it won't be possible to:

The idea of ​​the user adding other ColorSpaces would be related to the colorspaces currently in effect in the project or in your library, not in a global sense.

It would be great to have your collaboration in this regard :)

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.

3 participants