Skip to content

Commit

Permalink
Support for sRGB framebuffer on WebGPU (#6838)
Browse files Browse the repository at this point in the history
* Support for sRGB framebuffer on WebGPU

* lint

* examples lint

---------

Co-authored-by: Martin Valigursky <mvaligursky@snapchat.com>
  • Loading branch information
mvaligursky and Martin Valigursky authored Jul 23, 2024
1 parent 98301f9 commit 0ab8933
Show file tree
Hide file tree
Showing 35 changed files with 374 additions and 140 deletions.
8 changes: 3 additions & 5 deletions examples/src/examples/graphics/area-lights.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,12 @@ assetListLoader.load(() => {
* @param {string} primitiveType - The primitive type.
* @param {pc.Vec3} position - The position.
* @param {pc.Vec3} scale - The scale.
* @param {pc.Color} color - The color.
* @param {any} assetManifest - The asset manifest.
* @returns {pc.Entity} The returned entity.
*/
function createPrimitive(primitiveType, position, scale, color, assetManifest) {
function createPrimitive(primitiveType, position, scale, assetManifest) {
// create material of specified color
const material = new pc.StandardMaterial();
material.diffuse = color;
material.gloss = 0.8;
material.useMetalness = true;

Expand All @@ -81,7 +79,7 @@ assetListLoader.load(() => {
material.update();

// create primitive
const primitive = new pc.Entity();
const primitive = new pc.Entity(primitiveType);
primitive.addComponent('render', {
type: primitiveType,
material: material
Expand Down Expand Up @@ -194,7 +192,7 @@ assetListLoader.load(() => {
app.scene.envAtlas = assets.helipad.resource;

// create ground plane
createPrimitive('plane', new pc.Vec3(0, 0, 0), new pc.Vec3(20, 20, 20), new pc.Color(0.3, 0.3, 0.3), assets);
createPrimitive('plane', new pc.Vec3(0, 0, 0), new pc.Vec3(20, 20, 20), assets);

// get the instance of the statue and set up with render component
const statue = assets.statue.resource.instantiateRenderEntity();
Expand Down
153 changes: 153 additions & 0 deletions examples/src/examples/graphics/material-transparency.example.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import * as pc from 'playcanvas';
import { deviceType, rootPath } from 'examples/utils';

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

const assets = {
font: new pc.Asset('font', 'font', { url: rootPath + '/static/assets/fonts/arial.json' })
};

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

// disable anti-aliasing to make dithering more pronounced
antialias: false,

// use sRGB for display format (only supported on WebGPU, fallbacks to LDR on WebGL2)
displayFormat: pc.DISPLAYFORMAT_LDR_SRGB
};

const device = await pc.createGraphicsDevice(canvas, gfxOptions);

// make dithering more pronounced by rendering to lower resolution
device.maxPixelRatio = 1;

const createOptions = new pc.AppOptions();
createOptions.graphicsDevice = device;

createOptions.componentSystems = [
pc.RenderComponentSystem,
pc.CameraComponentSystem,
pc.LightComponentSystem,
pc.ElementComponentSystem
];
createOptions.resourceHandlers = [
pc.TextureHandler,
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();

app.scene.rendering.toneMapping = pc.TONEMAP_LINEAR;

// Create an entity with a camera component
const camera = new pc.Entity();
camera.addComponent('camera', {
clearColor: pc.Color.BLACK
});
camera.translate(0, -0.5, 14);
camera.rotate(0, 0, 0);
app.root.addChild(camera);

const NUM_SPHERES_X = 4;
const NUM_SPHERES_Z = 10;

const ditherOptions = [
pc.DITHER_NONE,
pc.DITHER_BAYER8,
pc.DITHER_BLUENOISE,
pc.DITHER_IGNNOISE
];

/**
* @param {number} x - The x coordinate.
* @param {number} z - The z coordinate.
*/
const createSphere = function (x, z) {
const material = new pc.StandardMaterial();
material.name = `material-${ditherOptions[x]}-${z}`;
material.emissive = new pc.Color(1, 0, 0);
material.specular = new pc.Color(1, 1, 1);
material.metalness = 0.0;
material.gloss = 0.5;
material.useMetalness = true;

if (ditherOptions[x] === pc.DITHER_NONE) {
// alpha blending material
material.blendType = pc.BLEND_NORMAL;
} else {
// alpha dithering material
material.opacityDither = ditherOptions[x];
}

// we want the spheres to seem to fade out in a linear fashion, so we need to convert
// the perceived opacity value from sRGB to linear space
const perceivedOpacity = (z + 1) / NUM_SPHERES_Z;
const linearOpacity = Math.pow(perceivedOpacity, 2.2);
material.opacity = linearOpacity;

material.update();

const sphere = new pc.Entity(`entity-${ditherOptions[x]}-${z}`);
sphere.addComponent('render', {
material: material,
type: 'sphere'
});
sphere.setLocalPosition(1.5 * (x - (NUM_SPHERES_X - 1) * 0.5), z - (NUM_SPHERES_Z - 1) * 0.5, 0);
sphere.setLocalScale(0.9, 0.9, 0.9);
app.root.addChild(sphere);
};
/**
* @param {pc.Asset} fontAsset - The font asset.
* @param {string} message - The message.
* @param {number} x - The x coordinate.
* @param {number} y - The y coordinate.
*/
const createText = function (fontAsset, message, x, y) {
// Create a text element-based entity
const text = new pc.Entity();
text.addComponent('element', {
anchor: [0.5, 0.5, 0.5, 0.5],
fontAsset: fontAsset,
fontSize: 0.3,
pivot: [0.5, 0.5],
text: message,
type: pc.ELEMENTTYPE_TEXT
});
text.setLocalPosition(x, y, 0);
app.root.addChild(text);
};

for (let i = 0; i < NUM_SPHERES_X; i++) {
for (let j = 0; j < NUM_SPHERES_Z; j++) {
createSphere(i, j);
}
}

const y = (NUM_SPHERES_Z + 1) * -0.5;
createText(assets.font, 'Alpha\nBlend', NUM_SPHERES_X * -0.6, y);
createText(assets.font, 'Bayer8\nDither', NUM_SPHERES_X * -0.2, y);
createText(assets.font, 'Blue-noise\nDither', NUM_SPHERES_X * 0.2, y);
createText(assets.font, 'IGN-noise\nDither', NUM_SPHERES_X * 0.6, y);
});

export { app };
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ window.focus();
const assets = {
particlesNumbers: new pc.Asset('particlesNumbers', 'texture', {
url: rootPath + '/static/assets/textures/particles-numbers.png'
})
}, { srgb: true })
};

const gfxOptions = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ window.focus();
const assets = {
particlesCoinsTexture: new pc.Asset('particlesCoinsTexture', 'texture', {
url: rootPath + '/static/assets/textures/particles-coins.png'
}),
}, { srgb: true }),
particlesBonusTexture: new pc.Asset('particlesBonusTexture', 'texture', {
url: rootPath + '/static/assets/textures/particles-bonus.png'
})
}, { srgb: true })
};

const gfxOptions = {
Expand Down
2 changes: 1 addition & 1 deletion examples/src/examples/graphics/particles-snow.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('applic
window.focus();

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

const gfxOptions = {
Expand Down
2 changes: 1 addition & 1 deletion examples/src/examples/graphics/particles-spark.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('applic
window.focus();

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

const gfxOptions = {
Expand Down
6 changes: 5 additions & 1 deletion examples/src/examples/graphics/post-processing.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,11 @@ assetListLoader.load(() => {
const label = new pc.Entity(name);
label.addComponent('element', {
text: text,
color: new pc.Color(100, 50, 80), // very bright color to affect the bloom

// very bright color to affect the bloom - this is not correct, as this is sRGB color that
// is valid only in 0..1 range, but UI does not expose emissive intensity currently
color: new pc.Color(18, 15, 5),

anchor: new pc.Vec4(x, y, 0.5, 0.5),
fontAsset: assets.font,
fontSize: 28,
Expand Down
4 changes: 0 additions & 4 deletions examples/src/examples/graphics/reflection-planar.shader.frag
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ uniform vec4 uScreenSize;
// reflection texture
uniform sampler2D uDiffuseMap;

vec3 gammaCorrectOutput(vec3 color) {
return pow(color + 0.0000001, vec3(1.0 / 2.2));
}

void main(void)
{
// sample reflection texture
Expand Down
4 changes: 0 additions & 4 deletions examples/src/examples/graphics/shader-burn.shader.frag
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ uniform sampler2D uDiffuseMap;
uniform sampler2D uHeightMap;
uniform float uTime;

vec3 gammaCorrectOutput(vec3 color) {
return pow(color + 0.0000001, vec3(1.0 / 2.2));
}

void main(void)
{
float height = texture2D(uHeightMap, vUv0).r;
Expand Down
9 changes: 3 additions & 6 deletions examples/src/examples/graphics/shader-compile.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,13 @@ assetListLoader.load(() => {
* @param {string} primitiveType - The primitive type.
* @param {pc.Vec3} position - The position.
* @param {pc.Vec3} scale - The scale.
* @param {pc.Color} color - The color.
* @param {any} assetManifest - The asset manifest.
* @param {boolean} [id] - Prevent shader compilation caching.
* @returns {pc.Entity} The entity.
*/
function createPrimitive(primitiveType, position, scale, color, assetManifest, id = false) {
function createPrimitive(primitiveType, position, scale, assetManifest, id = false) {
// create material of specified color
const material = new pc.StandardMaterial();
material.diffuse = color;
material.gloss = 0.4;
material.useMetalness = true;

Expand Down Expand Up @@ -125,7 +123,7 @@ assetListLoader.load(() => {
app.scene.envAtlas = assets.helipad.resource;

// create ground plane
createPrimitive('plane', new pc.Vec3(0, 0, 0), new pc.Vec3(20, 20, 20), new pc.Color(0.3, 0.3, 0.3), assets);
createPrimitive('plane', new pc.Vec3(0, 0, 0), new pc.Vec3(20, 20, 20), assets);

// Create the camera, which renders entities
const camera = new pc.Entity();
Expand All @@ -142,8 +140,7 @@ assetListLoader.load(() => {
for (let x = -10; x <= 10; x += 6) {
for (let y = -10; y <= 10; y += 6) {
const pos = new pc.Vec3(x, 0.6, y);
const color = new pc.Color(0.3 + Math.random() * 0.7, 0.3 + Math.random() * 0.7, 0.3 + Math.random() * 0.7);
createPrimitive('sphere', pos, new pc.Vec3(1, 1, 1), color, assets, true);
createPrimitive('sphere', pos, new pc.Vec3(1, 1, 1), assets, true);
}
}

Expand Down
13 changes: 2 additions & 11 deletions examples/src/examples/graphics/shader-toon.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -87,26 +87,17 @@ assetListLoader.load(() => {
* Set the new material on all meshes in the model, and use original texture from the model on the new material
* @type {pc.Texture | null}
*/
let originalTexture = null;
/** @type {Array<pc.RenderComponent>} */
const renders = entity.findComponents('render');
renders.forEach((render) => {
const meshInstances = render.meshInstances;
for (let i = 0; i < meshInstances.length; i++) {
const meshInstance = meshInstances[i];
if (!originalTexture) {
/** @type {pc.StandardMaterial} */
const originalMaterial = meshInstance.material;
originalTexture = originalMaterial.diffuseMap;
}
render.meshInstances.forEach((meshInstance) => {
meshInstance.material = material;
}
});
});

// material parameters
const lightPosArray = [light.getPosition().x, light.getPosition().y, light.getPosition().z];
material.setParameter('uLightPos', lightPosArray);
material.setParameter('uTexture', originalTexture);
material.update();

// rotate the statue
Expand Down
7 changes: 3 additions & 4 deletions examples/src/examples/graphics/shader-toon.shader.frag
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@

precision mediump float;
uniform sampler2D uTexture;
varying float vertOutTexCoord;
varying vec2 texCoord;
void main(void)
{
float v = vertOutTexCoord;
v = float(int(v * 6.0)) / 6.0;
// vec4 color = texture2D (uTexture, texCoord); // try this to use the diffuse color.
vec4 color = vec4(0.5, 0.47, 0.43, 1.0);
gl_FragColor = color * vec4(v, v, v, 1.0);
vec3 linearColor = vec3(0.218, 0.190, 0.156) * v;
gl_FragColor.rgb = gammaCorrectOutput(linearColor.rgb);
gl_FragColor.a = 1.0;
}
4 changes: 0 additions & 4 deletions examples/src/examples/graphics/shader-wobble.shader.frag
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ uniform sampler2D uDiffuseMap;

varying vec2 vUv0;

vec3 gammaCorrectOutput(vec3 color) {
return pow(color + 0.0000001, vec3(1.0 / 2.2));
}

void main(void)
{
vec4 linearColor = texture2D(uDiffuseMap, vUv0);
Expand Down
18 changes: 5 additions & 13 deletions examples/src/examples/graphics/texture-array.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,10 @@ function generateMipmaps(width, height) {
}

const assets = {
rockyTrail: new pc.Asset('rockyTrail', 'texture', {
url: rootPath + '/static/assets/textures/rocky_trail_diff_1k.jpg'
}),
rockBoulder: new pc.Asset('rockBoulder', 'texture', {
url: rootPath + '/static/assets/textures/rock_boulder_cracked_diff_1k.jpg'
}),
coastSand: new pc.Asset('coastSand', 'texture', {
url: rootPath + '/static/assets/textures/coast_sand_rocks_02_diff_1k.jpg'
}),
aerialRocks: new pc.Asset('aeralRocks', 'texture', {
url: rootPath + '/static/assets/textures/aerial_rocks_02_diff_1k.jpg'
}),
rockyTrail: new pc.Asset('rockyTrail', 'texture', { url: rootPath + '/static/assets/textures/rocky_trail_diff_1k.jpg' }, { srgb: true }),
rockBoulder: new pc.Asset('rockBoulder', 'texture', { url: rootPath + '/static/assets/textures/rock_boulder_cracked_diff_1k.jpg' }, { srgb: true }),
coastSand: new pc.Asset('coastSand', 'texture', { url: rootPath + '/static/assets/textures/coast_sand_rocks_02_diff_1k.jpg' }, { srgb: true }),
aerialRocks: new pc.Asset('aeralRocks', 'texture', { url: rootPath + '/static/assets/textures/aerial_rocks_02_diff_1k.jpg' }, { srgb: true }),
script: new pc.Asset('script', 'script', { url: rootPath + '/static/scripts/camera/orbit-camera.js' })
};

Expand Down Expand Up @@ -112,7 +104,7 @@ assetListLoader.load(() => {

const textureArrayOptions = {
name: 'textureArrayImages',
format: pc.PIXELFORMAT_RGBA8,
format: pc.PIXELFORMAT_SRGBA8,
width: 1024,
height: 1024,
arrayLength: 4, // array texture with 4 textures
Expand Down
2 changes: 1 addition & 1 deletion examples/src/examples/graphics/texture-array.ground.frag
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ void main(void)
{
vec4 data = texture(uDiffuseMap, vec3(vUv0, step(vUv0.x, 0.5) + 2.0 * step(vUv0.y, 0.5)));
data.rgb *= 0.8 * max(dot(worldNormal, vec3(0.1, 1.0, 0.5)), 0.0) + 0.5; // simple lighting
gl_FragColor = vec4(data.rgb, 1.0);
gl_FragColor = vec4(gammaCorrectOutput(data.rgb), 1.0);
}
Loading

0 comments on commit 0ab8933

Please sign in to comment.