Avoid loading cubemap as DDS file?

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;

};
2 Likes