Figured it out, this post by @Leonidas was the most helpful: How to create skybox programmatically? - #12 by Leonidas, pointing out the PlayCanvas glTF has an example of doing this.
Here’s a minimal script you can attach to your Root
entity that’ll programmatically attach a skybox if anyone needs a quick solution. This assumes your skybox images are called ['px', 'nx', 'py', 'ny', 'pz', 'nz']
with .png
suffix, and note it sets the intensity to 0.05 at the end.
var ProgrammaticSkybox = pc.createScript('programmaticSkybox');
// initialize code called once per entity
ProgrammaticSkybox.prototype.initialize = function() {
// Required to make skybox work in Newgrounds
// See: https://forum.playcanvas.com/t/avoid-loading-cubemap-as-dds-file/21214
const app = this.app;
const textures = ['px', 'nx', 'py', 'ny', 'pz', 'nz'].map(name => this.app.assets.find(name + '.png'));
this.textures = textures;
window.skyboxScript = this;
const cubemapAsset = new pc.Asset('skybox_cubemap', 'cubemap', null, {
textures: textures.map(function (faceAsset) {
return faceAsset.id;
})
});
cubemapAsset.loadFaces = true;
cubemapAsset.on('load', function () {
this.initSkyboxFromTexture(cubemapAsset.resource);
}.bind(this));
app.assets.add(cubemapAsset);
app.assets.load(cubemapAsset);
};
ProgrammaticSkybox.prototype.initSkyboxFromTexture = function(skybox) {
const app = this.app;
const device = app.graphicsDevice;
const cubemaps = [];
const reprojectToCubemap = function (src, size) {
// generate faces cubemap
const faces = new pc.Texture(device, {
name: 'skyboxFaces',
cubemap: true,
width: size,
height: size,
type: pc.TEXTURETYPE_RGBM.toString(),
addressU: pc.ADDRESS_CLAMP_TO_EDGE,
addressV: pc.ADDRESS_CLAMP_TO_EDGE,
fixCubemapSeams: false,
mipmaps: false
});
pc.reprojectTexture(src, faces);
return faces;
};
if (skybox.cubemap) {
if (skybox.type === pc.TEXTURETYPE_DEFAULT || skybox.type === pc.TEXTURETYPE_RGBM) {
// cubemap format is acceptible, use it directly
cubemaps.push(skybox);
} else {
// cubemap must be rgbm or default to be used on the skybox
cubemaps.push(reprojectToCubemap(skybox, skybox.width));
}
} else {
skybox.projection = pc.TEXTUREPROJECTION_EQUIRECT;
// reproject equirect to cubemap for skybox
cubemaps.push(reprojectToCubemap(skybox, skybox.width / 4));
}
// generate prefiltered lighting data
const sizes = [128, 64, 32, 16, 8, 4];
const specPower = [undefined, 512, 128, 32, 8, 2];
for (let i = 0; i < sizes.length; ++i) {
const prefilter = new pc.Texture(device, {
cubemap: true,
name: 'skyboxPrefilter' + i,
width: sizes[i],
height: sizes[i],
type: pc.TEXTURETYPE_RGBM,
addressU: pc.ADDRESS_CLAMP_TO_EDGE,
addressV: pc.ADDRESS_CLAMP_TO_EDGE,
fixCubemapSeams: true,
mipmaps: false
});
// @ts-ignore
pc.reprojectTexture(device, cubemaps[1] || skybox, prefilter, specPower[i], 4096);
cubemaps.push(prefilter);
}
// assign the textures to the scene
app.scene.gammaCorrection = pc.GAMMA_SRGB;
app.scene.skyboxMip = this.skyboxMip; // Set the skybox to the 128x128 cubemap mipmap level
app.scene.setSkybox(cubemaps);
app.renderNextFrame = true; // ensure we render again when the cubemap arrives
app.scene.skyboxIntensity = 0.05;
};