Skip to content
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

Small internal refactor of shader variants handling by renderer #4394

Merged
merged 9 commits into from
Jul 1, 2022
71 changes: 65 additions & 6 deletions examples/src/examples/graphics/render-to-texture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ class RenderToTextureExample {
// - camera - this camera renders into main framebuffer, objects from World, Excluded and also Skybox layers

// Create the app and start the update loop
const app = new pc.Application(canvas, {});
const app = new pc.Application(canvas, {
mouse: new pc.Mouse(document.body),
touch: new pc.TouchDevice(document.body)
});

const assets = {
'helipad.dds': new pc.Asset('helipad.dds', 'cubemap', { url: '/static/assets/cubemaps/helipad.dds' }, { type: pc.TEXTURETYPE_RGBM })
'helipad.dds': new pc.Asset('helipad.dds', 'cubemap', { url: '/static/assets/cubemaps/helipad.dds' }, { type: pc.TEXTURETYPE_RGBM }),
'script': new pc.Asset('script', 'script', { url: '/static/scripts/camera/orbit-camera.js' })
};

const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets);
Expand Down Expand Up @@ -55,6 +59,45 @@ class RenderToTextureExample {
return primitive;
}

// helper function to create a basic particle system
function createParticleSystem(position: pc.Vec3) {

// make particles move in different directions
const localVelocityCurve = new pc.CurveSet([
[0, 0, 0.5, 8],
[0, 0, 0.5, 8],
[0, 0, 0.5, 8]
]);
const localVelocityCurve2 = new pc.CurveSet([
[0, 0, 0.5, -8],
[0, 0, 0.5, -8],
[0, 0, 0.5, -8]
]);

// increasing gravity
const worldVelocityCurve = new pc.CurveSet([
[0, 0],
[0, 0, 0.2, 6, 1, -48],
[0, 0]
]);

// Create entity for particle system
const entity = new pc.Entity();
app.root.addChild(entity);
entity.setLocalPosition(position);

// add particlesystem component to entity
entity.addComponent("particlesystem", {
numParticles: 200,
lifetime: 1,
rate: 0.01,
scaleGraph: new pc.Curve([0, 0.5]),
velocityGraph: worldVelocityCurve,
localVelocityGraph: localVelocityCurve,
localVelocityGraph2: localVelocityCurve2
});
}

// create texture and render target for rendering into, including depth buffer
const texture = new pc.Texture(app.graphicsDevice, {
width: 512,
Expand All @@ -74,19 +117,22 @@ class RenderToTextureExample {
samples: 2
});

// create a layer for object that do not render into texture
// create a layer for object that do not render into texture, add it right after the world layer
const excludedLayer = new pc.Layer({ name: "Excluded" });
app.scene.layers.push(excludedLayer);
app.scene.layers.insert(excludedLayer, 1);

// get world and skybox layers
const worldLayer = app.scene.layers.getLayerByName("World");
const skyboxLayer = app.scene.layers.getLayerByName("Skybox");

// create ground plane and 3 primitives, visible in world layer
createPrimitive("plane", new pc.Vec3(0, 0, 0), new pc.Vec3(20, 20, 20), new pc.Color(0.2, 0.4, 0.2), [worldLayer.id]);
const plane = createPrimitive("plane", new pc.Vec3(0, 0, 0), new pc.Vec3(20, 20, 20), new pc.Color(0.2, 0.4, 0.2), [worldLayer.id]);
createPrimitive("sphere", new pc.Vec3(-2, 1, 0), new pc.Vec3(2, 2, 2), pc.Color.RED, [worldLayer.id]);
createPrimitive("box", new pc.Vec3(2, 1, 0), new pc.Vec3(2, 2, 2), pc.Color.YELLOW, [worldLayer.id]);
createPrimitive("cone", new pc.Vec3(0, 1, -2), new pc.Vec3(2, 2, 2), pc.Color.CYAN, [worldLayer.id]);
createPrimitive("box", new pc.Vec3(2, 1, 0), new pc.Vec3(2, 2, 2), pc.Color.YELLOW, [worldLayer.id]);

// particle system
createParticleSystem(new pc.Vec3(2, 3, 0));

// Create main camera, which renders entities in world, excluded and skybox layers
const camera = new pc.Entity("Camera");
Expand All @@ -98,6 +144,19 @@ class RenderToTextureExample {
camera.lookAt(1, 4, 0);
app.root.addChild(camera);

// add orbit camera script with a mouse and a touch support
camera.addComponent("script");
camera.script.create("orbitCamera", {
attributes: {
inertiaFactor: 0.2,
focusEntity: plane,
distanceMax: 20,
frameOnStart: false
}
});
camera.script.create("orbitCameraInputMouse");
camera.script.create("orbitCameraInputTouch");

// Create texture camera, which renders entities in world and skybox layers into the texture
const textureCamera = new pc.Entity("TextureCamera");
textureCamera.addComponent("camera", {
Expand Down
10 changes: 5 additions & 5 deletions src/graphics/program-library.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,10 @@ class ProgramLibrary {

getProgram(name, options) {
const generator = this._generators[name];
if (generator === undefined) {
if (!generator) {
Debug.warn(`ProgramLibrary#getProgram: No program library functions registered for: ${name}`);
return null;
}
const gd = this._device;
const key = generator.generateKey(options);
let shader = this._cache[key];
if (!shader) {
Expand All @@ -74,9 +73,10 @@ class ProgramLibrary {
if (this._precached)
console.warn(`ProgramLibrary#getProgram: Cache miss for shader ${name} key ${key} after shaders precaching`);

const shaderDefinition = generator.createShaderDefinition(gd, options);
shaderDefinition.name = `standard-pass:${options.pass}`;
shader = this._cache[key] = new Shader(gd, shaderDefinition);
const device = this._device;
const shaderDefinition = generator.createShaderDefinition(device, options);
shaderDefinition.name = `${name}-pass:${options.pass}`;
shader = this._cache[key] = new Shader(device, shaderDefinition);
}
return shader;
}
Expand Down
9 changes: 6 additions & 3 deletions src/scene/layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -607,10 +607,13 @@ class Layer {

if (!skipShadowCasters && m.castShadow && casters.indexOf(m) < 0) casters.push(m);

if (!this.passThrough && sceneShaderVer >= 0 && mat._shaderVersion !== sceneShaderVer) { // clear old shader if needed
if (mat.updateShader !== Material.prototype.updateShader) {
// clear old shader variants if necessary
if (!this.passThrough && sceneShaderVer >= 0 && mat._shaderVersion !== sceneShaderVer) {

// skip this for materials not using variants
if (mat.getShaderVariant !== Material.prototype.getShaderVariant) {
// clear shader variants on the material and also on mesh instances that use it
mat.clearVariants();
mat.shader = null;
}
mat._shaderVersion = sceneShaderVer;
}
Expand Down
1 change: 0 additions & 1 deletion src/scene/lightmapper/lightmapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,6 @@ class Lightmapper {
material.cull = CULLFACE_NONE;
material.forceUv1 = true; // provide data to xformUv1
material.update();
material.updateShader(device, scene);

return material;
}
Expand Down
14 changes: 12 additions & 2 deletions src/scene/materials/basic-material.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Debug } from '../../core/debug.js';
import { Color } from '../../math/color.js';
import {
SHADERDEF_INSTANCING, SHADERDEF_MORPH_NORMAL, SHADERDEF_MORPH_POSITION, SHADERDEF_MORPH_TEXTURE_BASED,
Expand Down Expand Up @@ -50,6 +51,15 @@ class BasicMaterial extends Material {
this.vertexColors = false;
}

set shader(shader) {
Debug.warn('BasicMaterial#shader property is not implemented, and should not be used.');
}

get shader() {
Debug.warn('BasicMaterial#shader property is not implemented, and should not be used.');
return null;
}

/**
* Copy a `BasicMaterial`.
*
Expand Down Expand Up @@ -79,7 +89,7 @@ class BasicMaterial extends Material {
}
}

updateShader(device, scene, objDefs, staticLightList, pass, sortedLights) {
getShaderVariant(device, scene, objDefs, staticLightList, pass, sortedLights) {
const options = {
skin: objDefs && (objDefs & SHADERDEF_SKIN) !== 0,
screenSpace: objDefs && (objDefs & SHADERDEF_SCREENSPACE) !== 0,
Expand All @@ -94,7 +104,7 @@ class BasicMaterial extends Material {
pass: pass
};
const library = device.getProgramLibrary();
this.shader = library.getProgram('basic', options);
return library.getProgram('basic', options);
}
}

Expand Down
28 changes: 22 additions & 6 deletions src/scene/materials/material.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Debug } from '../../core/debug.js';
import { getDefaultMaterial } from './default-material.js';

/** @typedef {import('../../graphics/texture.js').Texture} Texture */
/** @typedef {import('../../graphics/shader.js').Shader} Shader */

let id = 0;

Expand Down Expand Up @@ -114,14 +115,22 @@ let id = 0;
* slope of the triangle relative to the camera.
*/
class Material {
/**
* A shader used to render the material. Note that this is used only by materials where the user
* specifies the shader. Most material types generate multiple shader variants, and do not set this.
*
* @type {Shader}
* @private
*/
_shader = null;

/**
* Create a new Material instance.
*/
constructor() {
this.name = 'Untitled';
this.id = id++;

this._shader = null;
this.variants = {};
this.parameters = {};

Expand Down Expand Up @@ -311,7 +320,7 @@ class Material {
*/
copy(source) {
this.name = source.name;
this.shader = source.shader;
this._shader = source._shader;

// Render states
this.alphaTest = source.alphaTest;
Expand Down Expand Up @@ -371,8 +380,10 @@ class Material {
updateUniforms(device, scene) {
}

updateShader(device, scene, objDefs, staticLightList, pass, sortedLights) {
// For vanilla materials, the shader can only be set by the user
getShaderVariant(device, scene, objDefs, staticLightList, pass, sortedLights) {
// return the shader specified by the user of the material
Debug.assert(this._shader, 'Material does not have shader set', this);
return this._shader;
}

/**
Expand All @@ -393,11 +404,16 @@ class Material {
}

clearVariants() {

// clear variants on the material
this.variants = {};

// but also clear them from all materials that reference them
for (let i = 0; i < this.meshInstances.length; i++) {
const meshInstance = this.meshInstances[i];
for (let j = 0; j < meshInstance._shader.length; j++) {
meshInstance._shader[j] = null;
const shaders = meshInstance._shader;
for (let j = 0; j < shaders.length; j++) {
mvaligursky marked this conversation as resolved.
Show resolved Hide resolved
shaders[j] = null;
}
}
}
Expand Down
23 changes: 15 additions & 8 deletions src/scene/materials/standard-material.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Debug } from '../../core/debug.js';

import { Color } from '../../math/color.js';
import { Vec2 } from '../../math/vec2.js';
import { Quat } from '../../math/quat.js';
Expand Down Expand Up @@ -506,6 +508,15 @@ class StandardMaterial extends Material {
this._uniformCache = { };
}

set shader(shader) {
Debug.warn('StandardMaterial#shader property is not implemented, and should not be used.');
}

get shader() {
Debug.warn('StandardMaterial#shader property is not implemented, and should not be used.');
return null;
}

/**
* Object containing custom shader chunks that will replace default ones.
*
Expand Down Expand Up @@ -689,7 +700,6 @@ class StandardMaterial extends Material {
this._processParameters('_activeParams');

if (this._dirtyShader) {
this.shader = null;
this.clearVariants();
}
}
Expand All @@ -706,7 +716,7 @@ class StandardMaterial extends Material {
this._processParameters('_activeLightingParams');
}

updateShader(device, scene, objDefs, staticLightList, pass, sortedLights) {
getShaderVariant(device, scene, objDefs, staticLightList, pass, sortedLights) {
// update prefiltered lighting data
this.updateEnvUniforms(device, scene);

Expand All @@ -719,19 +729,16 @@ class StandardMaterial extends Material {
else
this.shaderOptBuilder.updateRef(options, scene, this, objDefs, staticLightList, pass, sortedLights);

// execute user callback to modify the options
if (this.onUpdateShader) {
options = this.onUpdateShader(options);
}

const library = device.getProgramLibrary();
this.shader = library.getProgram('standard', options);

if (!objDefs) {
this.clearVariants();
this.variants[0] = this.shader;
}
const shader = library.getProgram('standard', options);

this._dirtyShader = false;
return shader;
}

/**
Expand Down
14 changes: 14 additions & 0 deletions src/scene/mesh-instance.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { LightmapCache } from './lightmapper/lightmap-cache.js';
/** @typedef {import('../math/vec3.js').Vec3} Vec3 */
/** @typedef {import('./materials/material.js').Material} Material */
/** @typedef {import('./mesh.js').Mesh} Mesh */
/** @typedef {import('./scene.js').Scene} Scene */
/** @typedef {import('./morph-instance.js').MorphInstance} MorphInstance */
/** @typedef {import('./skin-instance.js').SkinInstance} SkinInstance */

Expand Down Expand Up @@ -654,6 +655,19 @@ class MeshInstance {
}
}

/**
* Obtain a shader variant required to render the mesh instance within specified pass.
*
* @param {Scene} scene - The scene.
* @param {number} pass - The render pass.
* @param {any} staticLightList - List of static lights.
* @param {any} sortedLights - Array of array of lights.
* @ignore
*/
updatePassShader(scene, pass, staticLightList, sortedLights) {
this._shader[pass] = this.material.getShaderVariant(this.mesh.device, scene, this._shaderDefs, staticLightList, pass, sortedLights);
}

// Parameter management
clearParameters() {
this.parameters = {};
Expand Down
9 changes: 5 additions & 4 deletions src/scene/particle-system/particle-emitter.js
Original file line number Diff line number Diff line change
Expand Up @@ -833,8 +833,8 @@ class ParticleEmitter {
if (this.lighting) {
this.normalOption = hasNormal ? 2 : 1;
}
// updateShader is also called by pc.Scene when all shaders need to be updated
this.material.updateShader = function () {
// getShaderVariant is also called by pc.Scene when all shaders need to be updated
this.material.getShaderVariant = function () {

// The app works like this:
// 1. Emitter init
Expand Down Expand Up @@ -878,9 +878,10 @@ class ParticleEmitter {
pack8: this.emitter.pack8,
customFace: this.emitter.orientation !== PARTICLEORIENTATION_SCREEN
});
this.shader = shader;

return shader;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

previous line can (should) be deleted?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, I moved it outside of this function, as that's the consistent way to do it.

};
this.material.updateShader();
this.material.shader = this.material.getShaderVariant();
}

resetMaterial() {
Expand Down
Loading