Skip to content

Commit

Permalink
Support for KHR_materials_dispersion glTF extension (#6151)
Browse files Browse the repository at this point in the history
* Support for KHR_materials_dispersion glTF extension

* Update src/scene/materials/standard-material.js

Co-authored-by: Will Eastcott <will@playcanvas.com>

* added comment

---------

Co-authored-by: Martin Valigursky <mvaligursky@snapchat.com>
Co-authored-by: Will Eastcott <will@playcanvas.com>
  • Loading branch information
3 people authored Mar 12, 2024
1 parent c46868c commit 607e3a8
Show file tree
Hide file tree
Showing 20 changed files with 183 additions and 21 deletions.
Binary file added examples/assets/models/dispersion-test.glb
Binary file not shown.
6 changes: 6 additions & 0 deletions examples/src/examples/graphics/dispersion/config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* @type {import('../../../../types.mjs').ExampleConfig}
*/
export default {
WEBGPU_ENABLED: true
};
97 changes: 97 additions & 0 deletions examples/src/examples/graphics/dispersion/example.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import * as pc from 'playcanvas';
import { deviceType, rootPath } from '@examples/utils';

const canvas = document.getElementById('application-canvas');
if (!(canvas instanceof HTMLCanvasElement)) {
throw new Error('No canvas found');
}

const assets = {
script: new pc.Asset('script', 'script', { url: rootPath + '/static/scripts/camera/orbit-camera.js' }),
model: new pc.Asset('cube', 'container', { url: rootPath + '/static/assets/models/dispersion-test.glb' }),
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'
};

const device = await pc.createGraphicsDevice(canvas, gfxOptions);
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(document.body);

createOptions.componentSystems = [
pc.RenderComponentSystem,
pc.CameraComponentSystem,
pc.LightComponentSystem,
pc.ScriptComponentSystem
];

createOptions.resourceHandlers = [
pc.TextureHandler,
pc.ContainerHandler,
pc.ScriptHandler
];

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();

// set skybox
app.scene.envAtlas = assets.helipad.resource;
app.scene.toneMapping = pc.TONEMAP_ACES;
app.scene.skyboxMip = 1;

// get the instance of the cube it set up with render component and add it to scene
const glbEntity = assets.model.resource.instantiateRenderEntity();
app.root.addChild(glbEntity);

// Create an Entity with a camera component
const camera = new pc.Entity();
camera.addComponent('camera', {
clearColor: new pc.Color(0.2, 0.2, 0.2)
});

// the color grab pass is needed
camera.camera.requestSceneColorMap(true);

// Adjust the camera position
camera.translate(0, 0.3, 1);

camera.addComponent('script');
camera.script.create('orbitCamera', {
attributes: {
inertiaFactor: 0.2,
distanceMax: 0.15
}
});
camera.script.create('orbitCameraInputMouse');
camera.script.create('orbitCameraInputTouch');
app.root.addChild(camera);
});

export { app };
7 changes: 7 additions & 0 deletions src/framework/parsers/glb-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -1080,6 +1080,12 @@ const extensionIor = (data, material, textures) => {
}
};

const extensionDispersion = (data, material, textures) => {
if (data.hasOwnProperty('dispersion')) {
material.dispersion = data.dispersion;
}
};

const extensionTransmission = (data, material, textures) => {
material.blendType = BLEND_NORMAL;
material.useDynamicRefraction = true;
Expand Down Expand Up @@ -1312,6 +1318,7 @@ const createMaterial = (gltfMaterial, textures, flipV) => {
"KHR_materials_clearcoat": extensionClearCoat,
"KHR_materials_emissive_strength": extensionEmissiveStrength,
"KHR_materials_ior": extensionIor,
"KHR_materials_dispersion": extensionDispersion,
"KHR_materials_iridescence": extensionIridescence,
"KHR_materials_pbrSpecularGlossiness": extensionPbrSpecGlossiness,
"KHR_materials_sheen": extensionSheen,
Expand Down
1 change: 1 addition & 0 deletions src/platform/graphics/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -1830,3 +1830,4 @@ export const CHUNKAPI_1_58 = '1.58';
export const CHUNKAPI_1_60 = '1.60';
export const CHUNKAPI_1_62 = '1.62';
export const CHUNKAPI_1_65 = '1.65';
export const CHUNKAPI_1_70 = '1.70';
1 change: 1 addition & 0 deletions src/scene/materials/lit-material-options-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class LitMaterialOptionsBuilder {
litOptions.useIridescence = material.hasIrridescence;
litOptions.useMetalness = material.hasMetalness;
litOptions.useDynamicRefraction = material.dynamicRefraction;
litOptions.dispersion = material.dispersion > 0;

litOptions.vertexColors = false;
litOptions.lightMapEnabled = material.hasLighting;
Expand Down
1 change: 1 addition & 0 deletions src/scene/materials/standard-material-options-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ class StandardMaterialOptionsBuilder {
options.litOptions.useIridescence = stdMat.useIridescence && stdMat.iridescence !== 0.0;
options.litOptions.useMetalness = stdMat.useMetalness;
options.litOptions.useDynamicRefraction = stdMat.useDynamicRefraction;
options.litOptions.dispersion = stdMat.dispersion > 0;
}

_updateEnvOptions(options, stdMat, scene) {
Expand Down
1 change: 1 addition & 0 deletions src/scene/materials/standard-material-parameters.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ const standardMaterialParameterTypes = {
refractionTint: 'boolean',
..._textureParameter('refraction'),
refractionIndex: 'number',
dispersion: 'number',
thickness: 'number',
thicknessTint: 'boolean',
..._textureParameter('thickness'),
Expand Down
7 changes: 7 additions & 0 deletions src/scene/materials/standard-material.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,8 @@ let _params = new Set();
* indices of refraction, the one around the object and the one of its own surface. In most
* situations outer medium is air, so outerIor will be approximately 1. Then you only need to do
* (1.0 / surfaceIor).
* @property {number} dispersion The strength of the angular separation of colors (chromatic
* aberration) transmitting through a volume. Defaults to 0, which is equivalent to no dispersion.
* @property {boolean} useDynamicRefraction Enables higher quality refractions using the grab pass
* instead of pre-computed cube maps for refractions.
* @property {number} thickness The thickness of the medium, only used when useDynamicRefraction
Expand Down Expand Up @@ -787,6 +789,10 @@ class StandardMaterial extends Material {
this._setParameter('material_refraction', this.refraction);
}

if (this.dispersion > 0) {
this._setParameter('material_dispersion', this.dispersion);
}

if (this.useDynamicRefraction) {
this._setParameter('material_thickness', this.thickness);
this._setParameter('material_attenuation', getUniform('attenuation'));
Expand Down Expand Up @@ -1171,6 +1177,7 @@ function _defineMaterialProps() {
_defineFloat('occludeSpecularIntensity', 1);
_defineFloat('refraction', 0);
_defineFloat('refractionIndex', 1.0 / 1.5); // approx. (air ior / glass ior)
_defineFloat('dispersion', 0);
_defineFloat('thickness', 0);
_defineFloat('attenuationDistance', 0);
_defineFloat('metalness', 1);
Expand Down
6 changes: 3 additions & 3 deletions src/scene/shader-lib/chunks/chunk-validation.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CHUNKAPI_1_51, CHUNKAPI_1_55, CHUNKAPI_1_56, CHUNKAPI_1_57, CHUNKAPI_1_60, CHUNKAPI_1_62, CHUNKAPI_1_65 } from '../../../platform/graphics/constants.js';
import { CHUNKAPI_1_51, CHUNKAPI_1_55, CHUNKAPI_1_56, CHUNKAPI_1_57, CHUNKAPI_1_60, CHUNKAPI_1_62, CHUNKAPI_1_65, CHUNKAPI_1_70 } from '../../../platform/graphics/constants.js';
import { Debug } from '../../../core/debug.js';
import { shaderChunks } from './chunks.js';

Expand Down Expand Up @@ -79,8 +79,8 @@ const chunkVersions = {
iridescenceDiffractionPS: CHUNKAPI_1_65,
lightmapAddPS: CHUNKAPI_1_65,
lightmapDirAddPS: CHUNKAPI_1_65,
refractionCubePS: CHUNKAPI_1_65,
refractionDynamicPS: CHUNKAPI_1_65
refractionCubePS: CHUNKAPI_1_70,
refractionDynamicPS: CHUNKAPI_1_70
};

// removed
Expand Down
3 changes: 2 additions & 1 deletion src/scene/shader-lib/chunks/lit/frag/refractionCube.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ void addRefraction(
vec3 specularity,
vec3 albedo,
float transmission,
float refractionIndex
float refractionIndex,
float dispersion
#if defined(LIT_IRIDESCENCE)
, vec3 iridescenceFresnel,
float iridescenceIntensity
Expand Down
54 changes: 38 additions & 16 deletions src/scene/shader-lib/chunks/lit/frag/refractionDynamic.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,29 @@ export default /* glsl */`
uniform float material_invAttenuationDistance;
uniform vec3 material_attenuation;
vec3 evalRefractionColor(vec3 refractionVector, float gloss, float refractionIndex) {
// The refraction point is the entry point + vector to exit point
vec4 pointOfRefraction = vec4(vPositionW + refractionVector, 1.0);
// Project to texture space so we can sample it
vec4 projectionPoint = matrix_viewProjection * pointOfRefraction;
// use built-in getGrabScreenPos function to convert screen position to grab texture uv coords
vec2 uv = getGrabScreenPos(projectionPoint);
#ifdef SUPPORTS_TEXLOD
// Use IOR and roughness to select mip
float iorToRoughness = (1.0 - gloss) * clamp((1.0 / refractionIndex) * 2.0 - 2.0, 0.0, 1.0);
float refractionLod = log2(uScreenSize.x) * iorToRoughness;
vec3 refraction = texture2DLodEXT(uSceneColorMap, uv, refractionLod).rgb;
#else
vec3 refraction = texture2D(uSceneColorMap, uv).rgb;
#endif
return refraction;
}
void addRefraction(
vec3 worldNormal,
vec3 viewDir,
Expand All @@ -10,7 +33,8 @@ void addRefraction(
vec3 specularity,
vec3 albedo,
float transmission,
float refractionIndex
float refractionIndex,
float dispersion
#if defined(LIT_IRIDESCENCE)
, vec3 iridescenceFresnel,
float iridescenceIntensity
Expand All @@ -24,24 +48,22 @@ void addRefraction(
modelScale.z = length(vec3(matrix_model[2].xyz));
// Calculate the refraction vector, scaled by the thickness and scale of the object
vec3 refractionVector = normalize(refract(-viewDir, worldNormal, refractionIndex)) * thickness * modelScale;
vec3 scale = thickness * modelScale;
vec3 refractionVector = normalize(refract(-viewDir, worldNormal, refractionIndex)) * scale;
vec3 refraction = evalRefractionColor(refractionVector, gloss, refractionIndex);
// The refraction point is the entry point + vector to exit point
vec4 pointOfRefraction = vec4(vPositionW + refractionVector, 1.0);
#ifdef DISPERSION
// based on the dispersion material property, calculate modified refraction index values
// for R and B channels and evaluate the refraction color for them.
float halfSpread = (1.0 / refractionIndex - 1.0) * 0.025 * dispersion;
// Project to texture space so we can sample it
vec4 projectionPoint = matrix_viewProjection * pointOfRefraction;
float refractionIndexR = refractionIndex - halfSpread;
refractionVector = normalize(refract(-viewDir, worldNormal, refractionIndexR)) * scale;
refraction.r = evalRefractionColor(refractionVector, gloss, refractionIndexR).r;
// use built-in getGrabScreenPos function to convert screen position to grab texture uv coords
vec2 uv = getGrabScreenPos(projectionPoint);
#ifdef SUPPORTS_TEXLOD
// Use IOR and roughness to select mip
float iorToRoughness = (1.0 - gloss) * clamp((1.0 / refractionIndex) * 2.0 - 2.0, 0.0, 1.0);
float refractionLod = log2(uScreenSize.x) * iorToRoughness;
vec3 refraction = texture2DLodEXT(uSceneColorMap, uv, refractionLod).rgb;
#else
vec3 refraction = texture2D(uSceneColorMap, uv).rgb;
float refractionIndexB = refractionIndex + halfSpread;
refractionVector = normalize(refract(-viewDir, worldNormal, refractionIndexB)) * scale;
refraction.b = evalRefractionColor(refractionVector, gloss, refractionIndexB).b;
#endif
// Transmittance is our final refraction color
Expand Down
3 changes: 3 additions & 0 deletions src/scene/shader-lib/chunks/standard/frag/litShaderArgs.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ float litArgs_thickness;
// Index of refraction
float litArgs_ior;
// Dispersion, range [0..1] typically, but can be higher
float litArgs_dispersion;
// Iridescence effect intensity, range [0..1]
float litArgs_iridescence_intensity;
Expand Down
2 changes: 2 additions & 0 deletions src/scene/shader-lib/programs/lit-shader-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ class LitShaderOptions {

useDynamicRefraction = false;

dispersion = false;

/**
* The type of fog being applied in the shader. See {@link Scene#fog} for the list of possible
* values.
Expand Down
7 changes: 6 additions & 1 deletion src/scene/shader-lib/programs/lit-shader.js
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,10 @@ class LitShader {

if (options.useRefraction) {
if (options.useDynamicRefraction) {
if (options.dispersion) {
decl.append("uniform float material_dispersion;");
decl.append('#define DISPERSION\n');
}
func.append(chunks.refractionDynamicPS);
} else if (this.reflections) {
func.append(chunks.refractionCubePS);
Expand Down Expand Up @@ -1451,7 +1455,8 @@ class LitShader {
litArgs_specularity,
litArgs_albedo,
litArgs_transmission,
litArgs_ior
litArgs_ior,
litArgs_dispersion
#if defined(LIT_IRIDESCENCE)
, iridescenceFresnel,
litArgs_iridescence_intensity
Expand Down
4 changes: 4 additions & 0 deletions src/scene/shader-lib/programs/standard.js
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,10 @@ class ShaderGeneratorStandard extends ShaderGenerator {
code.append(this._addMap("thickness", "thicknessPS", options, litShader.chunks, textureMapping));
func.append("getThickness();");
args.append("litArgs_thickness = dThickness;");

if (options.litOptions.dispersion) {
args.append("litArgs_dispersion = material_dispersion;");
}
}

if (options.litOptions.useIridescence) {
Expand Down
1 change: 1 addition & 0 deletions test/scene/materials/standard-material.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ describe('StandardMaterial', function () {
expect(material.reflectivity).to.equal(1);
expect(material.refraction).to.equal(0);
expect(material.refractionIndex).to.equal(1.0 / 1.5);
expect(material.dispersion).to.equal(0);
expect(material.shadingModel).to.equal(SPECULAR_BLINN);

expect(material.specular).to.be.instanceof(Color);
Expand Down
1 change: 1 addition & 0 deletions test/test-assets/box/1/Box Material.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
],
"reflectivity": 1,
"refractionIndex": 0.6666666666666666,
"dispersion": 0,
"cubeMapProjectionBox": {
"center": [
0,
Expand Down
1 change: 1 addition & 0 deletions tests/test-assets/box/1/Box Material.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
],
"reflectivity": 1,
"refractionIndex": 0.6666666666666666,
"dispersion": 0,
"cubeMapProjectionBox": {
"center": [
0,
Expand Down
1 change: 1 addition & 0 deletions utils/types-fixup.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ const standardMaterialProps = [
['reflectivity', 'number'],
['refraction', 'number'],
['refractionIndex', 'number'],
['dispersion', 'number'],
['shadingModel', 'number'],
['specular', 'Color'],
['specularMap', 'Texture|null'],
Expand Down

0 comments on commit 607e3a8

Please sign in to comment.