mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Add a skybox effect (#7843)
This commit is contained in:
@@ -2063,6 +2063,48 @@ module.exports = {
|
||||
.setType('number')
|
||||
.setGroup(_('Orientation'));
|
||||
}
|
||||
{
|
||||
const effect = extension
|
||||
.addEffect('Skybox')
|
||||
.setFullName(_('Skybox'))
|
||||
.setDescription(
|
||||
_('Display a background on a cube surrounding the scene.')
|
||||
)
|
||||
.markAsNotWorkingForObjects()
|
||||
.markAsOnlyWorkingFor3D()
|
||||
.addIncludeFile('Extensions/3D/Skybox.js');
|
||||
const properties = effect.getProperties();
|
||||
properties
|
||||
.getOrCreate('rightFaceResourceName')
|
||||
.setType('resource')
|
||||
.addExtraInfo('image')
|
||||
.setLabel(_('Right face (X+)'));
|
||||
properties
|
||||
.getOrCreate('leftFaceResourceName')
|
||||
.setType('resource')
|
||||
.addExtraInfo('image')
|
||||
.setLabel(_('Left face (X-)'));
|
||||
properties
|
||||
.getOrCreate('bottomFaceResourceName')
|
||||
.setType('resource')
|
||||
.addExtraInfo('image')
|
||||
.setLabel(_('Bottom face (Y+)'));
|
||||
properties
|
||||
.getOrCreate('topFaceResourceName')
|
||||
.setType('resource')
|
||||
.addExtraInfo('image')
|
||||
.setLabel(_('Top face (Y-)'));
|
||||
properties
|
||||
.getOrCreate('frontFaceResourceName')
|
||||
.setType('resource')
|
||||
.addExtraInfo('image')
|
||||
.setLabel(_('Front face (Z+)'));
|
||||
properties
|
||||
.getOrCreate('backFaceResourceName')
|
||||
.setType('resource')
|
||||
.addExtraInfo('image')
|
||||
.setLabel(_('Back face (Z-)'));
|
||||
}
|
||||
{
|
||||
const effect = extension
|
||||
.addEffect('HueAndSaturation')
|
||||
|
102
Extensions/3D/Skybox.ts
Normal file
102
Extensions/3D/Skybox.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
namespace gdjs {
|
||||
interface SkyboxFilterNetworkSyncData {}
|
||||
gdjs.PixiFiltersTools.registerFilterCreator(
|
||||
'Scene3D::Skybox',
|
||||
new (class implements gdjs.PixiFiltersTools.FilterCreator {
|
||||
makeFilter(
|
||||
target: EffectsTarget,
|
||||
effectData: EffectData
|
||||
): gdjs.PixiFiltersTools.Filter {
|
||||
if (typeof THREE === 'undefined') {
|
||||
return new gdjs.PixiFiltersTools.EmptyFilter();
|
||||
}
|
||||
return new (class implements gdjs.PixiFiltersTools.Filter {
|
||||
_cubeTexture: THREE.CubeTexture;
|
||||
_oldBackground:
|
||||
| THREE.CubeTexture
|
||||
| THREE.Texture
|
||||
| THREE.Color
|
||||
| null = null;
|
||||
_isEnabled: boolean = false;
|
||||
|
||||
constructor() {
|
||||
this._cubeTexture = target
|
||||
.getRuntimeScene()
|
||||
.getGame()
|
||||
.getImageManager()
|
||||
.getThreeCubeTexture(
|
||||
effectData.stringParameters.rightFaceResourceName,
|
||||
effectData.stringParameters.leftFaceResourceName,
|
||||
effectData.stringParameters.topFaceResourceName,
|
||||
effectData.stringParameters.bottomFaceResourceName,
|
||||
effectData.stringParameters.frontFaceResourceName,
|
||||
effectData.stringParameters.backFaceResourceName
|
||||
);
|
||||
}
|
||||
|
||||
isEnabled(target: EffectsTarget): boolean {
|
||||
return this._isEnabled;
|
||||
}
|
||||
setEnabled(target: EffectsTarget, enabled: boolean): boolean {
|
||||
if (this._isEnabled === enabled) {
|
||||
return true;
|
||||
}
|
||||
if (enabled) {
|
||||
return this.applyEffect(target);
|
||||
} else {
|
||||
return this.removeEffect(target);
|
||||
}
|
||||
}
|
||||
applyEffect(target: EffectsTarget): boolean {
|
||||
const scene = target.get3DRendererObject() as
|
||||
| THREE.Scene
|
||||
| null
|
||||
| undefined;
|
||||
if (!scene) {
|
||||
return false;
|
||||
}
|
||||
// TODO Add a background stack in LayerPixiRenderer to allow
|
||||
// filters to stack them.
|
||||
this._oldBackground = scene.background;
|
||||
scene.background = this._cubeTexture;
|
||||
if (!scene.environment) {
|
||||
scene.environment = this._cubeTexture;
|
||||
}
|
||||
this._isEnabled = true;
|
||||
return true;
|
||||
}
|
||||
removeEffect(target: EffectsTarget): boolean {
|
||||
const scene = target.get3DRendererObject() as
|
||||
| THREE.Scene
|
||||
| null
|
||||
| undefined;
|
||||
if (!scene) {
|
||||
return false;
|
||||
}
|
||||
scene.background = this._oldBackground;
|
||||
scene.environment = null;
|
||||
this._isEnabled = false;
|
||||
return true;
|
||||
}
|
||||
updatePreRender(target: gdjs.EffectsTarget): any {}
|
||||
updateDoubleParameter(parameterName: string, value: number): void {}
|
||||
getDoubleParameter(parameterName: string): number {
|
||||
return 0;
|
||||
}
|
||||
updateStringParameter(parameterName: string, value: string): void {}
|
||||
updateColorParameter(parameterName: string, value: number): void {}
|
||||
getColorParameter(parameterName: string): number {
|
||||
return 0;
|
||||
}
|
||||
updateBooleanParameter(parameterName: string, value: boolean): void {}
|
||||
getNetworkSyncData(): SkyboxFilterNetworkSyncData {
|
||||
return {};
|
||||
}
|
||||
updateFromNetworkSyncData(
|
||||
syncData: SkyboxFilterNetworkSyncData
|
||||
): void {}
|
||||
})();
|
||||
}
|
||||
})()
|
||||
);
|
||||
}
|
@@ -56,6 +56,11 @@ namespace gdjs {
|
||||
*/
|
||||
private _loadedThreeTextures: Hashtable<THREE.Texture>;
|
||||
private _loadedThreeMaterials = new ThreeMaterialCache();
|
||||
private _loadedThreeCubeTextures = new Map<string, THREE.CubeTexture>();
|
||||
private _loadedThreeCubeTextureKeysByResourceName = new ArrayMap<
|
||||
string,
|
||||
string
|
||||
>();
|
||||
|
||||
private _diskTextures = new Map<float, PIXI.Texture>();
|
||||
private _rectangleTextures = new Map<string, PIXI.Texture>();
|
||||
@@ -181,7 +186,25 @@ namespace gdjs {
|
||||
if (loadedThreeTexture) {
|
||||
return loadedThreeTexture;
|
||||
}
|
||||
const image = this._getImageSource(resourceName);
|
||||
|
||||
const threeTexture = new THREE.Texture(image);
|
||||
threeTexture.magFilter = THREE.LinearFilter;
|
||||
threeTexture.minFilter = THREE.LinearFilter;
|
||||
threeTexture.wrapS = THREE.RepeatWrapping;
|
||||
threeTexture.wrapT = THREE.RepeatWrapping;
|
||||
threeTexture.colorSpace = THREE.SRGBColorSpace;
|
||||
threeTexture.needsUpdate = true;
|
||||
|
||||
const resource = this._getImageResource(resourceName);
|
||||
|
||||
applyThreeTextureSettings(threeTexture, resource);
|
||||
this._loadedThreeTextures.put(resourceName, threeTexture);
|
||||
|
||||
return threeTexture;
|
||||
}
|
||||
|
||||
private _getImageSource(resourceName: string): HTMLImageElement {
|
||||
// Texture is not loaded, load it now from the PixiJS texture.
|
||||
// TODO (3D) - optimization: don't load the PixiJS Texture if not used by PixiJS.
|
||||
// TODO (3D) - optimization: Ideally we could even share the same WebGL texture.
|
||||
@@ -198,21 +221,86 @@ namespace gdjs {
|
||||
`Can't load texture for resource "${resourceName}" as it's not an image.`
|
||||
);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
const threeTexture = new THREE.Texture(image);
|
||||
threeTexture.magFilter = THREE.LinearFilter;
|
||||
threeTexture.minFilter = THREE.LinearFilter;
|
||||
threeTexture.wrapS = THREE.RepeatWrapping;
|
||||
threeTexture.wrapT = THREE.RepeatWrapping;
|
||||
threeTexture.colorSpace = THREE.SRGBColorSpace;
|
||||
threeTexture.needsUpdate = true;
|
||||
/**
|
||||
* Return the three.js texture associated to the specified resource name.
|
||||
* Returns a placeholder texture if not found.
|
||||
* @param xPositiveResourceName The name of the resource
|
||||
* @returns The requested cube texture, or a placeholder if not found.
|
||||
*/
|
||||
getThreeCubeTexture(
|
||||
xPositiveResourceName: string,
|
||||
xNegativeResourceName: string,
|
||||
yPositiveResourceName: string,
|
||||
yNegativeResourceName: string,
|
||||
zPositiveResourceName: string,
|
||||
zNegativeResourceName: string
|
||||
): THREE.CubeTexture {
|
||||
const key =
|
||||
xPositiveResourceName +
|
||||
'|' +
|
||||
xNegativeResourceName +
|
||||
'|' +
|
||||
yPositiveResourceName +
|
||||
'|' +
|
||||
yNegativeResourceName +
|
||||
'|' +
|
||||
zPositiveResourceName +
|
||||
'|' +
|
||||
zNegativeResourceName;
|
||||
const loadedThreeTexture = this._loadedThreeCubeTextures.get(key);
|
||||
if (loadedThreeTexture) {
|
||||
return loadedThreeTexture;
|
||||
}
|
||||
|
||||
const resource = this._getImageResource(resourceName);
|
||||
const cubeTexture = new THREE.CubeTexture();
|
||||
// Faces on X axis need to be swapped.
|
||||
cubeTexture.images[0] = this._getImageSource(xNegativeResourceName);
|
||||
cubeTexture.images[1] = this._getImageSource(xPositiveResourceName);
|
||||
// Faces on Y keep the same order.
|
||||
cubeTexture.images[2] = this._getImageSource(yPositiveResourceName);
|
||||
cubeTexture.images[3] = this._getImageSource(yNegativeResourceName);
|
||||
// Faces on Z keep the same order.
|
||||
cubeTexture.images[4] = this._getImageSource(zPositiveResourceName);
|
||||
cubeTexture.images[5] = this._getImageSource(zNegativeResourceName);
|
||||
// The images also need to be mirrored horizontally by users.
|
||||
|
||||
applyThreeTextureSettings(threeTexture, resource);
|
||||
this._loadedThreeTextures.put(resourceName, threeTexture);
|
||||
cubeTexture.magFilter = THREE.LinearFilter;
|
||||
cubeTexture.minFilter = THREE.LinearFilter;
|
||||
cubeTexture.colorSpace = THREE.SRGBColorSpace;
|
||||
cubeTexture.needsUpdate = true;
|
||||
|
||||
return threeTexture;
|
||||
const resource = this._getImageResource(xPositiveResourceName);
|
||||
applyThreeTextureSettings(cubeTexture, resource);
|
||||
this._loadedThreeCubeTextures.set(key, cubeTexture);
|
||||
this._loadedThreeCubeTextureKeysByResourceName.add(
|
||||
xPositiveResourceName,
|
||||
key
|
||||
);
|
||||
this._loadedThreeCubeTextureKeysByResourceName.add(
|
||||
xNegativeResourceName,
|
||||
key
|
||||
);
|
||||
this._loadedThreeCubeTextureKeysByResourceName.add(
|
||||
yPositiveResourceName,
|
||||
key
|
||||
);
|
||||
this._loadedThreeCubeTextureKeysByResourceName.add(
|
||||
yNegativeResourceName,
|
||||
key
|
||||
);
|
||||
this._loadedThreeCubeTextureKeysByResourceName.add(
|
||||
zPositiveResourceName,
|
||||
key
|
||||
);
|
||||
this._loadedThreeCubeTextureKeysByResourceName.add(
|
||||
zNegativeResourceName,
|
||||
key
|
||||
);
|
||||
|
||||
return cubeTexture;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -482,6 +570,11 @@ namespace gdjs {
|
||||
for (const threeTexture of threeTextures) {
|
||||
threeTexture.dispose();
|
||||
}
|
||||
for (const cubeTexture of this._loadedThreeCubeTextures.values()) {
|
||||
cubeTexture.dispose();
|
||||
}
|
||||
this._loadedThreeCubeTextures.clear();
|
||||
this._loadedThreeCubeTextureKeysByResourceName.clear();
|
||||
|
||||
this._loadedThreeMaterials.disposeAll();
|
||||
|
||||
@@ -528,12 +621,51 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
this._loadedThreeMaterials.dispose(resourceName);
|
||||
|
||||
const cubeTextureKeys =
|
||||
this._loadedThreeCubeTextureKeysByResourceName.getValuesFor(
|
||||
resourceName
|
||||
);
|
||||
if (cubeTextureKeys) {
|
||||
for (const cubeTextureKey of cubeTextureKeys) {
|
||||
const cubeTexture = this._loadedThreeCubeTextures.get(cubeTextureKey);
|
||||
if (cubeTexture) {
|
||||
cubeTexture.dispose();
|
||||
this._loadedThreeCubeTextures.delete(cubeTextureKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ArrayMap<K, V> {
|
||||
map = new Map<K, Array<V>>();
|
||||
|
||||
getValuesFor(key: K): Array<V> | undefined {
|
||||
return this.map.get(key);
|
||||
}
|
||||
|
||||
add(key: K, value: V): void {
|
||||
let values = this.map.get(key);
|
||||
if (!values) {
|
||||
values = [];
|
||||
this.map.set(key, values);
|
||||
}
|
||||
values.push(value);
|
||||
}
|
||||
|
||||
deleteValuesFor(key: K): void {
|
||||
this.map.delete(key);
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.map.clear();
|
||||
}
|
||||
}
|
||||
|
||||
class ThreeMaterialCache {
|
||||
private _flaggedMaterials = new Map<string, THREE.Material>();
|
||||
private _materialFlaggedKeys = new Map<string, Array<string>>();
|
||||
private _materialFlaggedKeys = new ArrayMap<string, string>();
|
||||
|
||||
/**
|
||||
* Return the three.js material associated to the specified resource name
|
||||
@@ -584,12 +716,7 @@ namespace gdjs {
|
||||
forceBasicMaterial ? 1 : 0
|
||||
}|${vertexColors ? 1 : 0}`;
|
||||
this._flaggedMaterials.set(cacheKey, material);
|
||||
let flaggedKeys = this._materialFlaggedKeys.get(resourceName);
|
||||
if (!flaggedKeys) {
|
||||
flaggedKeys = [];
|
||||
this._materialFlaggedKeys.set(resourceName, flaggedKeys);
|
||||
}
|
||||
flaggedKeys.push(cacheKey);
|
||||
this._materialFlaggedKeys.add(resourceName, cacheKey);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -598,7 +725,7 @@ namespace gdjs {
|
||||
* @param resourceName The name of the resource
|
||||
*/
|
||||
dispose(resourceName: string): void {
|
||||
const flaggedKeys = this._materialFlaggedKeys.get(resourceName);
|
||||
const flaggedKeys = this._materialFlaggedKeys.getValuesFor(resourceName);
|
||||
if (flaggedKeys) {
|
||||
for (const flaggedKey of flaggedKeys) {
|
||||
const threeMaterial = this._flaggedMaterials.get(flaggedKey);
|
||||
@@ -608,7 +735,7 @@ namespace gdjs {
|
||||
this._flaggedMaterials.delete(flaggedKey);
|
||||
}
|
||||
}
|
||||
this._materialFlaggedKeys.delete(resourceName);
|
||||
this._materialFlaggedKeys.deleteValuesFor(resourceName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -18,6 +18,7 @@ namespace gdjs {
|
||||
rendered2DLayersCount: 0,
|
||||
rendered3DLayersCount: 0,
|
||||
};
|
||||
private _backgroundColor: THREE.Color | null = null;
|
||||
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
@@ -209,15 +210,23 @@ namespace gdjs {
|
||||
);
|
||||
threeRenderer.resetState();
|
||||
if (this._runtimeScene.getClearCanvas()) threeRenderer.clear();
|
||||
threeScene.background = new THREE.Color(
|
||||
if (!this._backgroundColor) {
|
||||
this._backgroundColor = new THREE.Color();
|
||||
}
|
||||
this._backgroundColor.set(
|
||||
this._runtimeScene.getBackgroundColor()
|
||||
);
|
||||
if (!threeScene.background) {
|
||||
threeScene.background = this._backgroundColor;
|
||||
}
|
||||
|
||||
isFirstRender = false;
|
||||
} else {
|
||||
// It's important to set the background to null, as maybe the first rendered
|
||||
// layer has changed and so the Three.js scene background must be reset.
|
||||
threeScene.background = null;
|
||||
if (threeScene.background === this._backgroundColor) {
|
||||
threeScene.background = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the depth as each layer is independent and display on top of the previous one,
|
||||
|
Reference in New Issue
Block a user