-
-
Notifications
You must be signed in to change notification settings - Fork 36k
EXRLoader: Added lossyDctChannelDecode #31482
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
Conversation
I'm not gonna lie, that's quite impressive. At a quick glance everything looks good, there's one minor detail that isn't exactly correct: // Handle multi-channel RGB sets
if ( cscSet.idx[ 0 ] !== undefined && channelData[ cscSet.idx[ 0 ] ] ) {
lossyDctDecode( cscSet, rowOffsets, channelData, acBuffer, dcBuffer, outBuffer );
} In theory this check doesn't handle some "possible" cases, like when the input file has multiple sets of RGB channels. But in reality, I have yet to see a file that requires this, and the rest of the loader would also need to be reworked to output multiple textures instead of just one. So, while not strictly correct, I think this is good enough for THREE.js needs.
We had this discussion at some point in the past, as to what should happen when the file outputs a single channel. IIRC, at the time, @Mugen87 recommended we default to outputting I ultimately have no say in this, so I defer the decision to you guys, but utilizing just a single channel texture does reduce the overall memory footprint by a huge margin. So it is the preferred option. Perhaps it would be possible to extend the shader chunks to handle single-channel case, or just keep things as they are, and require users to build a custom shader / use |
The preference for |
Should we make our shaders treat People will be confused if they load a EXR in GIMP and Blender and shows white but it shows red in Three.js... |
We can't check in the shader for If we want to focus on easiness, I would recommend |
Can you share some of those use cases? 🤔 |
Maybe I misunderstood this line. Are you suggesting to not expanding the data to four channels in the loader but to "emulate" in some way how a single channel texture is sampled in the shader? The use cases I was originally referring to are memory/bandwidth sensitive applications where expanding data to four channels is an unwanted operation. |
Actually, I've asked Gemini about it and seems like WebGL 2 has a solution for this. This is what it suggested: // Assuming 'renderer' is your WebGL2Renderer and 'texture' is your RedFormat texture
const properties = renderer.properties.get(texture);
if (properties) {
renderer.getContext().bindTexture(renderer.getContext().TEXTURE_2D, properties.__webglTexture);
// Tell the sampler to use the RED channel for the R, G, and B components
renderer.getContext().texParameteri(renderer.getContext().TEXTURE_2D, renderer.getContext().TEXTURE_SWIZZLE_G, renderer.getContext().RED);
renderer.getContext().texParameteri(renderer.getContext().TEXTURE_2D, renderer.getContext().TEXTURE_SWIZZLE_B, renderer.getContext().RED);
renderer.getContext().bindTexture(renderer.getContext().TEXTURE_2D, null);
} I'm going to give it a try 👀 |
Um, I've never seen these enum values used with
|
I guess the only way would be to do this: export default /* glsl */`
#ifdef USE_MAP
vec4 sampledDiffuseColor = texture2D( map, vMapUv );
#ifdef MAP_RED_AS_LUMINANCE
sampledDiffuseColor.rgb = sampledDiffuseColor.rrr;
#endif
#ifdef DECODE_VIDEO_TEXTURE
// use inline sRGB decode until browsers properly support SRGB8_ALPHA8 with video textures (#26516)
sampledDiffuseColor = sRGBTransferEOTF( sampledDiffuseColor );
#endif
diffuseColor *= sampledDiffuseColor;
#endif
`; And then in if ( parameters.mapRed )
_programLayers.enable( 0 );
if ( parameters.alphaMapRed )
_programLayers.enable( 1 );
if ( parameters.lightMapRed )
_programLayers.enable( 2 );
if ( parameters.aoMapRed )
_programLayers.enable( 3 );
if ( parameters.bumpMapRed )
_programLayers.enable( 4 );
if ( parameters.normalMapRed )
_programLayers.enable( 5 );
if ( parameters.displacementMapRed )
_programLayers.enable( 6 );
if ( parameters.emissiveMapRed )
_programLayers.enable( 7 );
if ( parameters.metalnessMapRed )
_programLayers.enable( 8 );
if ( parameters.roughnessMapRed )
_programLayers.enable( 9 );
if ( parameters.anisotropyMapRed )
_programLayers.enable( 10 );
if ( parameters.clearcoatMapRed )
_programLayers.enable( 11 );
if ( parameters.clearcoatNormalMapRed )
_programLayers.enable( 12 );
if ( parameters.clearcoatRoughnessMapRed )
_programLayers.enable( 13 );
if ( parameters.iridescenceMapRed )
_programLayers.enable( 14 );
if ( parameters.iridescenceThicknessMapRed )
_programLayers.enable( 15 );
if ( parameters.sheenColorMapRed )
_programLayers.enable( 16 );
if ( parameters.sheenRoughnessMapRed )
_programLayers.enable( 17 );
if ( parameters.specularMapRed )
_programLayers.enable( 18 );
if ( parameters.specularColorMapRed )
_programLayers.enable( 19 );
if ( parameters.specularIntensityMapRed )
_programLayers.enable( 20 );
if ( parameters.transmissionMapRed )
_programLayers.enable( 21 );
if ( parameters.thicknessMapRed )
_programLayers.enable( 22 ); Definitely annoying, but maybe worth it? |
I think this boils down to a larger discussion. Which is how much it is expected of the library to just work without much in-depth knowledge of computer graphics. These render optimizations are inherently unique to each project and use cases. I would say that for the vast majority of projects this reduction of memory footprint won't matter, cause their projects are just too simple. But these things do compound as projects grow in scale. However, if you are working on an optimized project, you aren't even likely to use an EXR with just a single channel, nor use the default shaders. You are probably gonna author your own texture with, say, different channels packed into a single block-compressed texture (e.g. metalness, ao, roughness, specular ), and you are also gonna modify the default shader library or create your own shader to handle your specific use-case. Which is to say, any seasoned graphics developer will know how to deal with a But not your average user, and I believe most of the library behaviour is geared towards making things simpler for newbies. So while handling this at a shader generation is annoying, it is likely the best way to make things just work by default. |
I was hoping to not patch the shaders in that way 🙈. The implementation will end up quite messy in Interpreting texture samples as luminance values will be easier to support with TSL. I've hacked around a bit and there are multiple places where we can support this feature for all color textures (not depth textures of course). We could add an extension to the three.js/src/nodes/accessors/TextureNode.js Line 475 in e4139cf
Or we enhance the node builder and implement something in I've tested this approach with |
That's true. We can just copy the values to the other channels and return We can revisit |
Okay, I'll merge this now and I'll do another PR with the |
Should be as simple as removing three.js/examples/jsm/loaders/EXRLoader.js Lines 2466 to 2476 in f9591c4
Remove this. three.js/examples/jsm/loaders/EXRLoader.js Lines 2577 to 2582 in f9591c4
And after decoding is done, three.js/examples/jsm/loaders/EXRLoader.js Line 2632 in f9591c4
PS: if I can probably manage this PR over the weekend, if you prefer. Edit: #31511 |
he's alive <3 |
Related issue: #31429
Description
@sciecode Was curious if Claude Code could implement your comment. What do you think?
It creates a texture using
THREE.RedFormat
astexture.format
and loads this:With a custom shader that map the other channels it looks as it does in GIMP:
Should
EXRLoader
return aRedFormat
texture?Or should it duplicate the values and alwasys return a
RGBAFormat
texture?/cc @drone1 @WestLangley