Skip to content

Commit

Permalink
WIP: P3 implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
donmccurdy committed Aug 24, 2023
1 parent d1042cb commit cbff8c0
Show file tree
Hide file tree
Showing 16 changed files with 447 additions and 29 deletions.
19 changes: 19 additions & 0 deletions examples/jsm/capabilities/WebGL.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,25 @@ class WebGL {

}

static isColorSpaceAvailable( colorSpace ) {

if ( ! window.matchMedia( '( color-gamut: p3 )' ).matches ) return false;

try {

const canvas = document.createElement( 'canvas' );
const ctx = window.WebGL2RenderingContext && canvas.getContext( 'webgl2' );
ctx.drawingBufferColorSpace = colorSpace;
return ctx.drawingBufferColorSpace === colorSpace;

} catch ( e ) {

return false;

}

}

static getWebGLErrorMessage() {

return this.getErrorMessage( 1 );
Expand Down
Binary file added examples/textures/wide_gamut/webkit_logo_p3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/textures/wide_gamut/webkit_logo_srgb.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/textures/wide_gamut/yellow_flower_p3.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
240 changes: 240 additions & 0 deletions examples/webgl_test_wide_gamut.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - test - wide gamut</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
<style>
.container {
position: absolute;
overflow: hidden;
width: 100%;
height: 100%;
}

.slider {
position: absolute;
cursor: ew-resize;

width: 40px;
height: 40px;
background-color: #F32196;
opacity: 0.7;
border-radius: 50%;

top: calc(50% - 20px);
left: calc(50% - 20px);
}

.label {
position: fixed;
bottom: 50%;
background: rgba(0, 0, 0, 0.5);
padding: 0.2em 0.5em;
border-radius: 4px;
font-size: 14px;
user-select: none;
-webkit-user-select: none;
}
</style>
</head>

<body>

<div id="info">
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - wide gamut test<br />
</div>

<div class="container">
<div class="slider"></div>
<p class="label" style="left: 1em;">sRGB</p>
<p class="label" style="right: 1em;">Display P3</p>
</div>

<!-- Import maps polyfill -->
<!-- Remove this when import maps will be widely supported -->
<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>

<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>

<script type="module">

import * as THREE from 'three';

import WebGL from 'three/addons/capabilities/WebGL.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

let container, camera, renderer, loader;
let sceneL, sceneR, textureL, textureR;

let sliderPos = window.innerWidth / 2;

const IMAGE_OPTIONS = {
'Yellow flower': 'yellow_flower_{colorSpace}.jpg',
'Webkit logo': 'webkit_logo_{colorSpace}.png',
};

const params = {

image: Object.values( IMAGE_OPTIONS )[ 0 ],

};

if ( WebGL.isColorSpaceAvailable( THREE.DisplayP3ColorSpace ) ) {

THREE.ColorManagement.workingColorSpace = THREE.LinearDisplayP3ColorSpace;

}

init();

function init() {

container = document.querySelector( '.container' );

sceneL = new THREE.Scene();
sceneR = new THREE.Scene();

camera = new THREE.PerspectiveCamera( 35, window.innerWidth / window.innerHeight, 0.1, 100 );
camera.position.z = 6;

loader = new THREE.TextureLoader().setPath( 'textures/wide_gamut/' );

const light = new THREE.HemisphereLight( 0xffffff, 0x444444, 3 );
light.position.set( - 2, 2, 2 );
sceneL.add( light.clone() );
sceneR.add( light.clone() );

initTextures();
initSlider();

renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setScissorTest( true );
renderer.setAnimationLoop( render );
container.appendChild( renderer.domElement );

if ( WebGL.isColorSpaceAvailable( THREE.DisplayP3ColorSpace ) ) {

renderer.outputColorSpace = THREE.DisplayP3ColorSpace;

}

const gui = new GUI();

gui.add( params, 'image', IMAGE_OPTIONS ).onChange( initTextures );

window.addEventListener( 'resize', onWindowResize );

}

async function initTextures() {

// TODO: With workingColorSpace="p3-linear", outputColorSpace="srgb", images display quite differently. Expected?
// TODO: Where should .unpackColorSpace be set? Per texture or globally on the renderer?
// TODO: What happens given a Linear-sRGB texture when working color space is Linear-P3? Can we support that?
// TODO: How to detect support for drawingBufferColorSpace="display-p3"?

textureL = await loader.loadAsync( params.image.replace( '{colorSpace}', 'srgb' ) );
textureR = await loader.loadAsync( params.image.replace( '{colorSpace}', 'p3' ) );

textureL.colorSpace = THREE.SRGBColorSpace;
textureR.colorSpace = THREE.DisplayP3ColorSpace;

sceneL.background = containTexture( window.innerWidth / window.innerHeight, textureL );
sceneR.background = containTexture( window.innerWidth / window.innerHeight, textureR );

}

function initSlider() {

const slider = document.querySelector( '.slider' );

function onPointerDown() {

if ( event.isPrimary === false ) return;

window.addEventListener( 'pointermove', onPointerMove );
window.addEventListener( 'pointerup', onPointerUp );

}

function onPointerUp() {

window.removeEventListener( 'pointermove', onPointerMove );
window.removeEventListener( 'pointerup', onPointerUp );

}

function onPointerMove( e ) {

if ( event.isPrimary === false ) return;

sliderPos = Math.max( 0, Math.min( window.innerWidth, e.pageX ) );

slider.style.left = sliderPos - ( slider.offsetWidth / 2 ) + 'px';

}

slider.style.touchAction = 'none'; // disable touch scroll
slider.addEventListener( 'pointerdown', onPointerDown );

}

function onWindowResize() {

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

renderer.setSize( window.innerWidth, window.innerHeight );

containTexture( window.innerWidth / window.innerHeight, sceneL.background );
containTexture( window.innerWidth / window.innerHeight, sceneR.background );

}

function containTexture ( aspect, target ) {

// Sets the matrix uv transform so the texture image is contained in a region having the specified aspect ratio,
// and does so without distortion. Akin to CSS object-fit: contain.

var imageAspect = ( target.image && target.image.width ) ? target.image.width / target.image.height : 1;

if ( aspect > imageAspect ) {

target.matrix.setUvTransform( 0, 0, aspect / imageAspect, 1, 0, 0.5, 0.5 );

} else {

target.matrix.setUvTransform( 0, 0, 1, imageAspect / aspect, 0, 0.5, 0.5 );

}

target.matrixAutoUpdate = false;

return target;

}

function render() {

renderer.setScissor( 0, 0, sliderPos, window.innerHeight );
renderer.render( sceneL, camera );

renderer.setScissor( sliderPos, 0, window.innerWidth, window.innerHeight );
renderer.render( sceneR, camera );

}

</script>
</body>
</html>
6 changes: 6 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,12 @@ export const LinearSRGBColorSpace = 'srgb-linear';
export const DisplayP3ColorSpace = 'display-p3';
export const LinearDisplayP3ColorSpace = 'display-p3-linear';

export const LinearTransferFunction = 'linear';
export const SRGBTransferFunction = 'srgb';

export const Rec709Primaries = 'rec709';
export const P3Primaries = 'p3';

export const ZeroStencilOp = 0;
export const KeepStencilOp = 7680;
export const ReplaceStencilOp = 7681;
Expand Down
67 changes: 64 additions & 3 deletions src/math/ColorManagement.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SRGBColorSpace, LinearSRGBColorSpace, DisplayP3ColorSpace, LinearDisplayP3ColorSpace, } from '../constants.js';
import { SRGBColorSpace, LinearSRGBColorSpace, DisplayP3ColorSpace, LinearDisplayP3ColorSpace, Rec709Primaries, P3Primaries, SRGBTransferFunction, LinearTransferFunction, NoColorSpace, } from '../constants.js';
import { Matrix3 } from './Matrix3.js';

export function SRGBToLinear( c ) {
Expand Down Expand Up @@ -79,6 +79,8 @@ const FROM_REFERENCE = {
[ LinearDisplayP3ColorSpace ]: LinearSRGBToLinearDisplayP3,
};

const SUPPORTED_WORKING_COLOR_SPACES = new Set( [ LinearSRGBColorSpace, LinearDisplayP3ColorSpace ] );

export const ColorManagement = {

enabled: true,
Expand Down Expand Up @@ -109,6 +111,12 @@ export const ColorManagement = {

set workingColorSpace( colorSpace ) {

if ( ! SUPPORTED_WORKING_COLOR_SPACES.has( colorSpace ) ) {

throw new Error( `Unsupported working color space, "${ colorSpace }".` );

}

this._workingColorSpace = colorSpace;

},
Expand Down Expand Up @@ -136,14 +144,67 @@ export const ColorManagement = {

fromWorkingColorSpace: function ( color, targetColorSpace ) {

return this.convert( color, this.workingColorSpace, targetColorSpace );
return this.convert( color, this._workingColorSpace, targetColorSpace );

},

toWorkingColorSpace: function ( color, sourceColorSpace ) {

return this.convert( color, sourceColorSpace, this.workingColorSpace );
return this.convert( color, sourceColorSpace, this._workingColorSpace );

},

getPrimaries: function ( colorSpace ) {

switch ( colorSpace ) {

case SRGBColorSpace:
case LinearSRGBColorSpace:
return Rec709Primaries;

case DisplayP3ColorSpace:
case LinearDisplayP3ColorSpace:
return P3Primaries;

default:
throw new Error( `Unsupported color space, "${ colorSpace }."` );

}

},

getTransferFunction: function ( colorSpace ) {

switch ( colorSpace ) {

case SRGBColorSpace:
case DisplayP3ColorSpace:
return SRGBTransferFunction;

case LinearSRGBColorSpace:
case LinearDisplayP3ColorSpace:
case NoColorSpace:
return LinearTransferFunction;

default:
throw new Error( `Unsupported color space, "${ colorSpace }."` );

}

},

getUnpackColorSpace: function () {

switch ( this._workingColorSpace ) {

case LinearSRGBColorSpace:
return SRGBColorSpace;

case LinearDisplayP3ColorSpace:
return DisplayP3ColorSpace;

}

}

};
Loading

0 comments on commit cbff8c0

Please sign in to comment.