Skip to content

Commit

Permalink
Updated texture reprojection to work on WebGPU (#5021)
Browse files Browse the repository at this point in the history
Co-authored-by: Martin Valigursky <mvaligursky@snapchat.com>
  • Loading branch information
mvaligursky and Martin Valigursky committed Feb 1, 2023
1 parent 4222c29 commit 1428063
Show file tree
Hide file tree
Showing 9 changed files with 547 additions and 475 deletions.
590 changes: 312 additions & 278 deletions examples/src/examples/graphics/box-reflection.tsx

Large diffs are not rendered by default.

192 changes: 107 additions & 85 deletions examples/src/examples/graphics/material-basic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,113 +6,135 @@ class MaterialBasicExample {

example(canvas: HTMLCanvasElement): void {

// Create the application and start the update loop
const app = new pc.Application(canvas, {});

const assets = {
'font': new pc.Asset('font', 'font', { url: '/static/assets/fonts/arial.json' }),
'rocks': new pc.Asset("rocks", "texture", { url: "/static/assets/textures/seaside-rocks01-diffuse-alpha.png" })
};

const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets);
assetListLoader.load(() => {
app.start();
pc.createGraphicsDevice(canvas).then((device: pc.GraphicsDevice) => {

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

createOptions.componentSystems = [
// @ts-ignore
pc.RenderComponentSystem,
// @ts-ignore
pc.CameraComponentSystem,
// @ts-ignore
pc.ElementComponentSystem
];
createOptions.resourceHandlers = [
// @ts-ignore
pc.TextureHandler,
// @ts-ignore
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);

// Create an entity with a camera component
const camera = new pc.Entity();
camera.addComponent("camera", {
clearColor: new pc.Color(0.1, 0.1, 0.1, 1)
});
camera.translate(2, 1, 8);
camera.lookAt(new pc.Vec3(0, -0.3, 0));
app.root.addChild(camera);

const NUM_BOXES = 5;

// alpha blend modes for individual rows
const blendModes = [
pc.BLEND_ADDITIVE,
pc.BLEND_SUBTRACTIVE,
pc.BLEND_SCREEN,
pc.BLEND_NORMAL,
pc.BLEND_NONE
];
const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets);
assetListLoader.load(() => {

const createPrimitive = function (x: number, y: number, z: number) : pc.Entity {
app.start();

// a basic material, which does not have support for lighting
const material = new pc.BasicMaterial();
// Create an entity with a camera component
const camera = new pc.Entity();
camera.addComponent("camera", {
clearColor: new pc.Color(0.1, 0.1, 0.1, 1)
});
camera.translate(2, 1, 8);
camera.lookAt(new pc.Vec3(0, -0.3, 0));
app.root.addChild(camera);

// diffuse color
material.color.set(x, y, 1 - y);
const NUM_BOXES = 5;

// diffuse texture with alpha channel for transparency
material.colorMap = assets.rocks.resource;
// alpha blend modes for individual rows
const blendModes = [
pc.BLEND_ADDITIVE,
pc.BLEND_SUBTRACTIVE,
pc.BLEND_SCREEN,
pc.BLEND_NORMAL,
pc.BLEND_NONE
];

// disable culling to see back faces as well
material.cull = pc.CULLFACE_NONE;
const createPrimitive = function (x: number, y: number, z: number) : pc.Entity {

// set up alpha test value
material.alphaTest = x / NUM_BOXES - 0.1;
// a basic material, which does not have support for lighting
const material = new pc.BasicMaterial();

// alpha blend mode
material.blendType = blendModes[y];
// diffuse color
material.color.set(x, y, 1 - y);

const box = new pc.Entity();
box.addComponent("render", {
material: material,
type: "box",
// diffuse texture with alpha channel for transparency
material.colorMap = assets.rocks.resource;

// Note: basic material cannot currently cast shadows, disable it
castShadows: false
});
box.setLocalPosition(x - (NUM_BOXES - 1) * 0.5, y - (NUM_BOXES - 1) * 0.5, z);
box.setLocalScale(0.7, 0.7, 0.7);
app.root.addChild(box);
// disable culling to see back faces as well
material.cull = pc.CULLFACE_NONE;

// set up alpha test value
material.alphaTest = x / NUM_BOXES - 0.1;

// alpha blend mode
material.blendType = blendModes[y];

return box;
};
const box = new pc.Entity();
box.addComponent("render", {
material: material,
type: "box",

const boxes: Array<pc.Entity> = [];
for (let i = 0; i < NUM_BOXES; i++) {
for (let j = 0; j < NUM_BOXES; j++) {
boxes.push(createPrimitive(j, i, 0));
// Note: basic material cannot currently cast shadows, disable it
castShadows: false
});
box.setLocalPosition(x - (NUM_BOXES - 1) * 0.5, y - (NUM_BOXES - 1) * 0.5, z);
box.setLocalScale(0.7, 0.7, 0.7);
app.root.addChild(box);

return box;
};

const boxes: Array<pc.Entity> = [];
for (let i = 0; i < NUM_BOXES; i++) {
for (let j = 0; j < NUM_BOXES; j++) {
boxes.push(createPrimitive(j, i, 0));
}
}
}

const createText = function (fontAsset: pc.Asset, message: string, x: number, y: number, z: number, rot: number) {
// 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.5,
pivot: [0.5, 0.5],
text: message,
type: pc.ELEMENTTYPE_TEXT
});
text.setLocalPosition(x, y, z);
text.setLocalEulerAngles(0, 0, rot);
app.root.addChild(text);
};

createText(assets.font, 'Alpha Test', 0, -(NUM_BOXES + 1) * 0.5, 0, 0);
createText(assets.font, 'Alpha Blend', -(NUM_BOXES + 1) * 0.5, 0, 0, 90);

// Set an update function on the app's update event
let time = 0;
const rot = new pc.Quat();
app.on("update", function (dt: number) {
time += dt;

// rotate the boxes
rot.setFromEulerAngles(20 * time, 30 * time, 0);
boxes.forEach((box) => {
box.setRotation(rot);

const createText = function (fontAsset: pc.Asset, message: string, x: number, y: number, z: number, rot: number) {
// 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.5,
pivot: [0.5, 0.5],
text: message,
type: pc.ELEMENTTYPE_TEXT
});
text.setLocalPosition(x, y, z);
text.setLocalEulerAngles(0, 0, rot);
app.root.addChild(text);
};

createText(assets.font, 'Alpha Test', 0, -(NUM_BOXES + 1) * 0.5, 0, 0);
createText(assets.font, 'Alpha Blend', -(NUM_BOXES + 1) * 0.5, 0, 0, 90);

// Set an update function on the app's update event
let time = 0;
const rot = new pc.Quat();
app.on("update", function (dt: number) {
time += dt;

// rotate the boxes
rot.setFromEulerAngles(20 * time, 30 * time, 0);
boxes.forEach((box) => {
box.setRotation(rot);
});
});
});
});
Expand Down
10 changes: 10 additions & 0 deletions examples/src/examples/graphics/render-cubemap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ class RenderCubemapExample {
const textureOcta = createReprojectionTexture(pc.TEXTUREPROJECTION_OCTAHEDRAL, 64);
const textureOcta2 = createReprojectionTexture(pc.TEXTUREPROJECTION_OCTAHEDRAL, 32);

// create one envAtlas texture
const textureAtlas = createReprojectionTexture(pc.TEXTUREPROJECTION_OCTAHEDRAL, 512);

// update things each frame
let time = 0;
app.on("update", function (dt) {
Expand Down Expand Up @@ -262,6 +265,13 @@ class RenderCubemapExample {
});
// @ts-ignore engine-tsd
app.drawTexture(0.6, -0.7, 0.6, 0.3, textureEqui2);

// cube -> envAtlas
pc.EnvLighting.generateAtlas(srcCube, {
target: textureAtlas
});
// @ts-ignore engine-tsd
app.drawTexture(0, -0.7, 0.5, 0.4, textureAtlas);
});
});
});
Expand Down
6 changes: 3 additions & 3 deletions src/platform/graphics/bind-group.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class BindGroup {
*/
setUniformBuffer(name, uniformBuffer) {
const index = this.format.bufferFormatsMap.get(name);
Debug.assert(index !== undefined, `Setting a uniform [${name}] on a bind group with id ${this.id} which does not contain in, while rendering [${DebugGraphics.toString()}]`);
Debug.assert(index !== undefined, `Setting a uniform [${name}] on a bind group with id ${this.id} which does not contain in, while rendering [${DebugGraphics.toString()}]`, this);
if (this.uniformBuffers[index] !== uniformBuffer) {
this.uniformBuffers[index] = uniformBuffer;
this.dirty = true;
Expand All @@ -75,7 +75,7 @@ class BindGroup {
*/
setTexture(name, texture) {
const index = this.format.textureFormatsMap.get(name);
Debug.assert(index !== undefined, `Setting a texture [${name}] on a bind group with id: ${this.id} which does not contain in, while rendering [${DebugGraphics.toString()}]`);
Debug.assert(index !== undefined, `Setting a texture [${name}] on a bind group with id: ${this.id} which does not contain in, while rendering [${DebugGraphics.toString()}]`, this);
if (this.textures[index] !== texture) {
this.textures[index] = texture;
this.dirty = true;
Expand All @@ -91,7 +91,7 @@ class BindGroup {
for (let i = 0; i < textureFormats.length; i++) {
const textureFormat = textureFormats[i];
const value = textureFormat.scopeId.value;
Debug.assert(value, `Value was not set when assigning texture slot [${textureFormat.name}] to a bind group, while rendering [${DebugGraphics.toString()}]`);
Debug.assert(value, `Value was not set when assigning texture slot [${textureFormat.name}] to a bind group, while rendering [${DebugGraphics.toString()}]`, this);
this.setTexture(textureFormat.name, value);
}

Expand Down
2 changes: 1 addition & 1 deletion src/platform/graphics/shader-chunks/frag/webgpu.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ layout(location = 0) out highp vec4 pc_fragColor;
#define texture2DBias(res, uv, bias) texture(sampler2D(res, res ## _sampler), uv, bias)
#define texture2DLodEXT(res, uv, lod) textureLod(sampler2D(res, res ## _sampler), uv, lod)
#define textureCube(res, uv) texture(samplerCube(res, res ## _sampler), uv)
#define textureCubeLodEXT(res, uv, lod) textureLod(samplerCube(res, res ## _sampler), uv, lod)
#define textureShadow(res, uv) texture(sampler2DShadow(res, res ## _sampler), uv)
// TODO: implement other texture sampling macros
// #define texture2DProj textureProj
// #define texture2DProjLodEXT textureProjLod
// #define textureCubeLodEXT textureLod
// #define texture2DGradEXT textureGrad
// #define texture2DProjGradEXT textureProjGrad
// #define textureCubeGradEXT textureGrad
Expand Down
4 changes: 4 additions & 0 deletions src/platform/graphics/shader-processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ class UniformLine {
// type
this.type = words.shift();

if (line.includes(',')) {
Debug.error(`A comma on a uniform line is not supported, split it into multiple uniforms: ${line}`, shader);
}

// array of uniforms
if (line.includes('[')) {

Expand Down
8 changes: 6 additions & 2 deletions src/scene/graphics/reproject-texture.js
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ varying vec2 vUv0;
void main(void) {
gl_Position = vec4(vertex_position, 0.5, 1.0);
vUv0 = (vertex_position.xy * 0.5 + 0.5) * uvMod.xy + uvMod.zw;
vUv0 = getImageEffectUV((vertex_position.xy * 0.5 + 0.5) * uvMod.xy + uvMod.zw);
}
`;

Expand Down Expand Up @@ -427,6 +427,7 @@ function reprojectTexture(source, target, options = {}) {
const distribution = options.hasOwnProperty('distribution') ? options.distribution : (specularPower === 1) ? 'none' : 'phong';

const processFunc = funcNames[distribution] || 'reproject';
const prefilterSamples = processFunc.startsWith('prefilterSamples');
const decodeFunc = ChunkUtils.decodeFunc(source.encoding);
const encodeFunc = ChunkUtils.encodeFunc(target.encoding);
const sourceFunc = `sample${getProjectionName(source.projection)}`;
Expand All @@ -442,6 +443,8 @@ function reprojectTexture(source, target, options = {}) {
if (!shader) {
const defines =
`#define PROCESS_FUNC ${processFunc}\n` +
(prefilterSamples ? `#define USE_SAMPLES_TEX\n` : '') +
(source.cubemap ? `#define CUBEMAP_SOURCE\n` : '') +
`#define DECODE_FUNC ${decodeFunc}\n` +
`#define ENCODE_FUNC ${encodeFunc}\n` +
`#define SOURCE_FUNC ${sourceFunc}\n` +
Expand All @@ -460,6 +463,7 @@ function reprojectTexture(source, target, options = {}) {
DebugGraphics.pushGpuMarker(device, "ReprojectTexture");

const constantSource = device.scope.resolve(source.cubemap ? "sourceCube" : "sourceTex");
Debug.assert(constantSource);
constantSource.setValue(source);

const constantParams = device.scope.resolve("params");
Expand Down Expand Up @@ -496,7 +500,7 @@ function reprojectTexture(source, target, options = {}) {
source.width * source.height * (source.cubemap ? 6 : 1)
];

if (processFunc.startsWith('prefilterSamples')) {
if (prefilterSamples) {
// set or generate the pre-calculated samples data
const sourceTotalPixels = source.width * source.height * (source.cubemap ? 6 : 1);
const samplesTex =
Expand Down

0 comments on commit 1428063

Please sign in to comment.