Add bloom and color effects for 3D layers (#6228)

- Add 3D Effects for color adjustment and bloom.
- A lower quality anti-aliasing is used when post-processing is enable (SMAA instead of MSAA).
- Upgrade to Three.js 0.160.0.
This commit is contained in:
D8H
2024-02-16 14:12:46 +01:00
committed by GitHub
parent 6af50ae5c0
commit 901f84daaa
40 changed files with 3105 additions and 202 deletions
+89
View File
@@ -0,0 +1,89 @@
namespace gdjs {
gdjs.PixiFiltersTools.registerFilterCreator(
'Scene3D::Bloom',
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 {
shaderPass: THREE_ADDONS.UnrealBloomPass;
_isEnabled: boolean;
constructor() {
this.shaderPass = new THREE_ADDONS.UnrealBloomPass(
new THREE.Vector2(256, 256),
1,
0,
0
);
this._isEnabled = false;
}
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 {
if (!(target instanceof gdjs.Layer)) {
return false;
}
target.getRenderer().addPostProcessingPass(this.shaderPass);
this._isEnabled = true;
return true;
}
removeEffect(target: EffectsTarget): boolean {
if (!(target instanceof gdjs.Layer)) {
return false;
}
target.getRenderer().removePostProcessingPass(this.shaderPass);
this._isEnabled = false;
return true;
}
updatePreRender(target: gdjs.EffectsTarget): any {}
updateDoubleParameter(parameterName: string, value: number): void {
if (parameterName === 'strength') {
this.shaderPass.strength = value;
}
if (parameterName === 'radius') {
this.shaderPass.radius = value;
}
if (parameterName === 'threshold') {
this.shaderPass.threshold = value;
}
}
getDoubleParameter(parameterName: string): number {
if (parameterName === 'strength') {
return this.shaderPass.strength;
}
if (parameterName === 'radius') {
return this.shaderPass.radius;
}
if (parameterName === 'threshold') {
return this.shaderPass.threshold;
}
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 {}
})();
}
})()
);
}
@@ -0,0 +1,80 @@
namespace gdjs {
gdjs.PixiFiltersTools.registerFilterCreator(
'Scene3D::BrightnessAndContrast',
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 {
shaderPass: THREE_ADDONS.ShaderPass;
_isEnabled: boolean;
constructor() {
this.shaderPass = new THREE_ADDONS.ShaderPass(
THREE_ADDONS.BrightnessContrastShader
);
this._isEnabled = false;
}
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 {
if (!(target instanceof gdjs.Layer)) {
return false;
}
target.getRenderer().addPostProcessingPass(this.shaderPass);
this._isEnabled = true;
return true;
}
removeEffect(target: EffectsTarget): boolean {
if (!(target instanceof gdjs.Layer)) {
return false;
}
target.getRenderer().removePostProcessingPass(this.shaderPass);
this._isEnabled = false;
return true;
}
updatePreRender(target: gdjs.EffectsTarget): any {}
updateDoubleParameter(parameterName: string, value: number): void {
if (parameterName === 'brightness') {
this.shaderPass.uniforms[parameterName].value = value;
}
if (parameterName === 'contrast') {
this.shaderPass.uniforms[parameterName].value = value;
}
}
getDoubleParameter(parameterName: string): number {
if (parameterName === 'brightness') {
return this.shaderPass.uniforms[parameterName].value;
}
if (parameterName === 'contrast') {
return this.shaderPass.uniforms[parameterName].value;
}
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 {}
})();
}
})()
);
}
+74
View File
@@ -0,0 +1,74 @@
namespace gdjs {
gdjs.PixiFiltersTools.registerFilterCreator(
'Scene3D::Exposure',
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 {
shaderPass: THREE_ADDONS.ShaderPass;
_isEnabled: boolean;
constructor() {
this.shaderPass = new THREE_ADDONS.ShaderPass(
THREE_ADDONS.ExposureShader
);
this._isEnabled = false;
}
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 {
if (!(target instanceof gdjs.Layer)) {
return false;
}
target.getRenderer().addPostProcessingPass(this.shaderPass);
this._isEnabled = true;
return true;
}
removeEffect(target: EffectsTarget): boolean {
if (!(target instanceof gdjs.Layer)) {
return false;
}
target.getRenderer().removePostProcessingPass(this.shaderPass);
this._isEnabled = false;
return true;
}
updatePreRender(target: gdjs.EffectsTarget): any {}
updateDoubleParameter(parameterName: string, value: number): void {
if (parameterName === 'exposure') {
this.shaderPass.uniforms[parameterName].value = value;
}
}
getDoubleParameter(parameterName: string): number {
if (parameterName === 'exposure') {
return this.shaderPass.uniforms[parameterName].value;
}
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 {}
})();
}
})()
);
}
+80
View File
@@ -0,0 +1,80 @@
namespace gdjs {
gdjs.PixiFiltersTools.registerFilterCreator(
'Scene3D::HueAndSaturation',
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 {
shaderPass: THREE_ADDONS.ShaderPass;
_isEnabled: boolean;
constructor() {
this.shaderPass = new THREE_ADDONS.ShaderPass(
THREE_ADDONS.HueSaturationShader
);
this._isEnabled = false;
}
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 {
if (!(target instanceof gdjs.Layer)) {
return false;
}
target.getRenderer().addPostProcessingPass(this.shaderPass);
this._isEnabled = true;
return true;
}
removeEffect(target: EffectsTarget): boolean {
if (!(target instanceof gdjs.Layer)) {
return false;
}
target.getRenderer().removePostProcessingPass(this.shaderPass);
this._isEnabled = false;
return true;
}
updatePreRender(target: gdjs.EffectsTarget): any {}
updateDoubleParameter(parameterName: string, value: number): void {
if (parameterName === 'hue') {
this.shaderPass.uniforms[parameterName].value = value / 180;
}
if (parameterName === 'saturation') {
this.shaderPass.uniforms[parameterName].value = value;
}
}
getDoubleParameter(parameterName: string): number {
if (parameterName === 'hue') {
return this.shaderPass.uniforms[parameterName].value * 180;
}
if (parameterName === 'saturation') {
return this.shaderPass.uniforms[parameterName].value;
}
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 {}
})();
}
})()
);
}
+96
View File
@@ -1939,6 +1939,102 @@ module.exports = {
.setType('number')
.setGroup(_('Orientation'));
}
{
const effect = extension
.addEffect('HueAndSaturation')
.setFullName(_('Hue and saturation'))
.setDescription(
_(
'Adjust hue and saturation.'
)
)
.markAsNotWorkingForObjects()
.markAsOnlyWorkingFor3D()
.addIncludeFile('Extensions/3D/HueAndSaturationEffect.js');
const properties = effect.getProperties();
properties
.getOrCreate('hue')
.setValue('0')
.setLabel(_('Hue in degrees (between -180 and 180)'))
.setType('number');
properties
.getOrCreate('saturation')
.setValue('0')
.setLabel(_('Saturation (between -1 and 1)'))
.setType('number');
}
{
const effect = extension
.addEffect('Exposure')
.setFullName(_('Exposure'))
.setDescription(
_(
'Adjust exposure.'
)
)
.markAsNotWorkingForObjects()
.markAsOnlyWorkingFor3D()
.addIncludeFile('Extensions/3D/ExposureEffect.js');
const properties = effect.getProperties();
properties
.getOrCreate('exposure')
.setValue('1')
.setLabel(_('Exposure (positive value)'))
.setType('number');
}
{
const effect = extension
.addEffect('Bloom')
.setFullName(_('Bloom'))
.setDescription(
_(
'Apply a bloom effect.'
)
)
.markAsNotWorkingForObjects()
.markAsOnlyWorkingFor3D()
.addIncludeFile('Extensions/3D/BloomEffect.js');
const properties = effect.getProperties();
properties
.getOrCreate('strength')
.setValue('1')
.setLabel(_('Strength (between 0 and 3)'))
.setType('number');
properties
.getOrCreate('radius')
.setValue('0')
.setLabel(_('Radius (between 0 and 1)'))
.setType('number');
properties
.getOrCreate('threshold')
.setValue('0')
.setLabel(_('Threshold (between 0 and 1)'))
.setType('number');
}
{
const effect = extension
.addEffect('BrightnessAndContrast')
.setFullName(_('Brightness and contrast.'))
.setDescription(
_(
'Adjust brightness and contrast.'
)
)
.markAsNotWorkingForObjects()
.markAsOnlyWorkingFor3D()
.addIncludeFile('Extensions/3D/BrightnessAndContrastEffect.js');
const properties = effect.getProperties();
properties
.getOrCreate('brightness')
.setValue('0')
.setLabel(_('Brightness (between -1 and 1)'))
.setType('number');
properties
.getOrCreate('contrast')
.setValue('0')
.setLabel(_('Contrast (between -1 and 1)'))
.setType('number');
}
// Don't forget to update the alert condition in Model3DEditor.js when
// adding a new light.
@@ -11,7 +11,7 @@ namespace gdjs {
}
};
const removeMetalnessFromMesh = (node: THREE.Object3D<THREE.Event>) => {
const removeMetalnessFromMesh = (node: THREE.Object3D) => {
const mesh = node as THREE.Mesh;
if (!mesh.material) {
return;
@@ -25,9 +25,8 @@ namespace gdjs {
}
};
const traverseToRemoveMetalnessFromMeshes = (
node: THREE.Object3D<THREE.Event>
) => node.traverse(removeMetalnessFromMesh);
const traverseToRemoveMetalnessFromMeshes = (node: THREE.Object3D) =>
node.traverse(removeMetalnessFromMesh);
const convertToBasicMaterial = (
material: THREE.Material
@@ -46,7 +45,7 @@ namespace gdjs {
return basicMaterial;
};
const setBasicMaterialTo = (node: THREE.Object3D<THREE.Event>): void => {
const setBasicMaterialTo = (node: THREE.Object3D): void => {
const mesh = node as THREE.Mesh;
if (!mesh.material) {
return;
@@ -61,9 +60,8 @@ namespace gdjs {
}
};
const traverseToSetBasicMaterialFromMeshes = (
node: THREE.Object3D<THREE.Event>
) => node.traverse(setBasicMaterialTo);
const traverseToSetBasicMaterialFromMeshes = (node: THREE.Object3D) =>
node.traverse(setBasicMaterialTo);
class Model3DRuntimeObject3DRenderer extends gdjs.RuntimeObject3DRenderer {
private _model3DRuntimeObject: gdjs.Model3DRuntimeObject;
+1
View File
@@ -66,6 +66,7 @@ namespace gdjs {
this._runtimeScene.getViewportOriginY() - oldGameResolutionOriginY;
}
this._renderer.updatePosition();
this._renderer.updateResolution();
}
/**
File diff suppressed because one or more lines are too long
@@ -39,6 +39,7 @@ namespace gdjs {
| THREE.OrthographicCamera
| null = null;
private _threeCameraDirty: boolean = false;
private _threeEffectComposer: THREE_ADDONS.EffectComposer | null = null;
// For a 2D+3D layer, the 2D rendering is done on the render texture
// and then must be displayed on a plane in the 3D world:
@@ -138,6 +139,47 @@ namespace gdjs {
return this._threeCamera;
}
getThreeEffectComposer(): THREE_ADDONS.EffectComposer | null {
return this._threeEffectComposer;
}
addPostProcessingPass(pass: THREE_ADDONS.Pass) {
if (!this._threeEffectComposer) {
return;
}
const game = this._layer.getRuntimeScene().getGame();
// TODO Keep the effects in the same order they are defined
// because the order matter for the final result.
// There is the same issue with 2D effects too.
// The composer contains:
// - RenderPass
// - inserted passes for effects
// - SMAAPass (optionally)
// - OutputPass
const index =
this._threeEffectComposer.passes.length -
(game.getAntialiasingMode() === 'none' ? 1 : 2);
this._threeEffectComposer.insertPass(pass, index);
}
removePostProcessingPass(pass: THREE_ADDONS.Pass) {
if (!this._threeEffectComposer) {
return;
}
this._threeEffectComposer.removePass(pass);
}
hasPostProcessingPass() {
if (!this._threeEffectComposer) {
return false;
}
const game = this._layer.getRuntimeScene().getGame();
// RenderPass, OutputPass and optionally SMAAPass are default passes.
const emptyCount = game.getAntialiasingMode() === 'none' ? 2 : 3;
return this._threeEffectComposer.passes.length > emptyCount;
}
/**
* The sprite, displaying the "render texture" of the layer, to display
* for a lighting layer.
@@ -204,6 +246,28 @@ namespace gdjs {
}
this._threeCamera.rotation.order = 'ZYX';
const game = this._layer.getRuntimeScene().getGame();
const threeRenderer = game.getRenderer().getThreeRenderer();
if (threeRenderer) {
// When adding more default passes, make sure to update
// `addPostProcessingPass` and `hasPostProcessingPass` formulas.
this._threeEffectComposer = new THREE_ADDONS.EffectComposer(
threeRenderer
);
this._threeEffectComposer.addPass(
new THREE_ADDONS.RenderPass(this._threeScene, this._threeCamera)
);
if (game.getAntialiasingMode() !== 'none') {
this._threeEffectComposer.addPass(
new THREE_ADDONS.SMAAPass(
game.getGameResolutionWidth(),
game.getGameResolutionHeight()
)
);
}
this._threeEffectComposer.addPass(new THREE_ADDONS.OutputPass());
}
if (
this._layer.getRenderingType() ===
gdjs.RuntimeLayerRenderingType.TWO_D_PLUS_THREE_D
@@ -430,6 +494,16 @@ namespace gdjs {
}
}
updateResolution() {
if (this._threeEffectComposer) {
const game = this._layer.getRuntimeScene().getGame();
this._threeEffectComposer.setSize(
game.getGameResolutionWidth(),
game.getGameResolutionHeight()
);
}
}
isCameraRotatedIn3D() {
return (
this._threeCamera &&
@@ -72,6 +72,7 @@ namespace gdjs {
(this._game.isAntialisingEnabledOnMobile() ||
!gdjs.evtTools.common.isMobile()),
});
this._threeRenderer.useLegacyLights = true;
this._threeRenderer.autoClear = false;
this._threeRenderer.setSize(
this._game.getGameResolutionWidth(),
@@ -154,9 +154,10 @@ namespace gdjs {
// Render a layer with 3D rendering, and possibly some 2D rendering too.
const threeScene = runtimeLayerRenderer.getThreeScene();
const threeCamera = runtimeLayerRenderer.getThreeCamera();
const threeEffectComposer = runtimeLayerRenderer.getThreeEffectComposer();
// Render the 3D objects of this layer.
if (threeScene && threeCamera) {
if (threeScene && threeCamera && threeEffectComposer) {
// TODO (3D) - optimization: do this at the beginning for all layers that are 2d+3d?
// So the second pass is clearer (just rendering 2d or 3d layers without doing PixiJS renders in between).
if (
@@ -218,7 +219,12 @@ namespace gdjs {
// Clear the depth as each layer is independent and display on top of the previous one,
// even 3D objects.
threeRenderer.clearDepth();
threeRenderer.render(threeScene, threeCamera);
if (runtimeLayerRenderer.hasPostProcessingPass()) {
threeEffectComposer.render();
} else {
threeRenderer.render(threeScene, threeCamera);
}
this._layerRenderingMetrics.rendered3DLayersCount++;
lastRenderWas3D = true;
File diff suppressed because one or more lines are too long
+29 -1
View File
@@ -2,8 +2,36 @@ import { GLTFLoader, GLTF } from 'three/examples/jsm/loaders/GLTFLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import * as SkeletonUtils from 'three/examples/jsm/utils/SkeletonUtils';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';
import { Pass } from 'three/examples/jsm/postprocessing/Pass';
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass';
import { SMAAPass } from 'three/examples/jsm/postprocessing/SMAAPass';
import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass';
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass';
import { BrightnessContrastShader } from 'three/examples/jsm/shaders/BrightnessContrastShader';
import { ColorCorrectionShader } from 'three/examples/jsm/shaders/ColorCorrectionShader';
import { HueSaturationShader } from 'three/examples/jsm/shaders/HueSaturationShader';
import { ExposureShader } from 'three/examples/jsm/shaders/ExposureShader';
declare global {
namespace THREE_ADDONS {
export { GLTFLoader, GLTF, DRACOLoader, SkeletonUtils };
export {
GLTFLoader,
GLTF,
DRACOLoader,
SkeletonUtils,
EffectComposer,
Pass,
RenderPass,
ShaderPass,
SMAAPass,
OutputPass,
UnrealBloomPass,
BrightnessContrastShader,
ColorCorrectionShader,
HueSaturationShader,
ExposureShader,
};
}
}
+23 -37
View File
@@ -14,7 +14,7 @@
"@types/mocha": "^5.2.7",
"@types/node": "^14.11.1",
"@types/sinon": "^10.0.13",
"@types/three": "0.152.0",
"@types/three": "0.160.0",
"esbuild": "^0.13.12",
"lebab": "^3.1.0",
"minimist": "^1.2.5",
@@ -491,12 +491,6 @@
"url": "^0.11.0"
}
},
"node_modules/@tweenjs/tween.js": {
"version": "18.6.4",
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-18.6.4.tgz",
"integrity": "sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ==",
"dev": true
},
"node_modules/@types/css-font-loading-module": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz",
@@ -555,16 +549,15 @@
"dev": true
},
"node_modules/@types/three": {
"version": "0.152.0",
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.152.0.tgz",
"integrity": "sha512-9QdaV5bfZEqeQi0xkXLdnoJt7lgYZbppdBAgJSWRicdtZoCYJ34nS2QkdeuzXt+UXExofk4OWqMzdX71HeDOVg==",
"version": "0.160.0",
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.160.0.tgz",
"integrity": "sha512-jWlbUBovicUKaOYxzgkLlhkiEQJkhCVvg4W2IYD2trqD2om3VK4DGLpHH5zQHNr7RweZK/5re/4IVhbhvxbV9w==",
"dev": true,
"dependencies": {
"@tweenjs/tween.js": "~18.6.4",
"@types/stats.js": "*",
"@types/webxr": "*",
"fflate": "~0.6.9",
"lil-gui": "~0.17.0"
"fflate": "~0.6.10",
"meshoptimizer": "~0.18.1"
}
},
"node_modules/@types/webxr": {
@@ -1504,12 +1497,6 @@
"node": ">=0.10.0"
}
},
"node_modules/lil-gui": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/lil-gui/-/lil-gui-0.17.0.tgz",
"integrity": "sha512-MVBHmgY+uEbmJNApAaPbtvNh1RCAeMnKym82SBjtp5rODTYKWtM+MXHCifLe2H2Ti1HuBGBtK/5SyG4ShQ3pUQ==",
"dev": true
},
"node_modules/lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
@@ -1522,6 +1509,12 @@
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
"dev": true
},
"node_modules/meshoptimizer": {
"version": "0.18.1",
"resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz",
"integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==",
"dev": true
},
"node_modules/micromatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
@@ -2425,12 +2418,6 @@
"url": "^0.11.0"
}
},
"@tweenjs/tween.js": {
"version": "18.6.4",
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-18.6.4.tgz",
"integrity": "sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ==",
"dev": true
},
"@types/css-font-loading-module": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz",
@@ -2489,16 +2476,15 @@
"dev": true
},
"@types/three": {
"version": "0.152.0",
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.152.0.tgz",
"integrity": "sha512-9QdaV5bfZEqeQi0xkXLdnoJt7lgYZbppdBAgJSWRicdtZoCYJ34nS2QkdeuzXt+UXExofk4OWqMzdX71HeDOVg==",
"version": "0.160.0",
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.160.0.tgz",
"integrity": "sha512-jWlbUBovicUKaOYxzgkLlhkiEQJkhCVvg4W2IYD2trqD2om3VK4DGLpHH5zQHNr7RweZK/5re/4IVhbhvxbV9w==",
"dev": true,
"requires": {
"@tweenjs/tween.js": "~18.6.4",
"@types/stats.js": "*",
"@types/webxr": "*",
"fflate": "~0.6.9",
"lil-gui": "~0.17.0"
"fflate": "~0.6.10",
"meshoptimizer": "~0.18.1"
}
},
"@types/webxr": {
@@ -3218,12 +3204,6 @@
}
}
},
"lil-gui": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/lil-gui/-/lil-gui-0.17.0.tgz",
"integrity": "sha512-MVBHmgY+uEbmJNApAaPbtvNh1RCAeMnKym82SBjtp5rODTYKWtM+MXHCifLe2H2Ti1HuBGBtK/5SyG4ShQ3pUQ==",
"dev": true
},
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
@@ -3236,6 +3216,12 @@
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
"dev": true
},
"meshoptimizer": {
"version": "0.18.1",
"resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz",
"integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==",
"dev": true
},
"micromatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
+1 -1
View File
@@ -10,7 +10,7 @@
"@types/mocha": "^5.2.7",
"@types/node": "^14.11.1",
"@types/sinon": "^10.0.13",
"@types/three": "0.152.0",
"@types/three": "0.160.0",
"esbuild": "^0.13.12",
"lebab": "^3.1.0",
"minimist": "^1.2.5",
+1 -1
View File
@@ -17,7 +17,7 @@
"dependencies": {
},
"devDependencies": {
"three": "0.151.2",
"three": "0.160.0",
"@rollup/plugin-node-resolve": "^13.1.3",
"rollup": "^2.66.1",
"@rollup/plugin-terser": "^0.4.0",
@@ -1,6 +1,3 @@
// MIT License
// Copyright © 2010-2023 three.js authors
import {
BufferAttribute,
BufferGeometry,
@@ -85,13 +82,14 @@ class DRACOLoader extends Loader {
}
parse( buffer, onLoad, onError ) {
parse( buffer, onLoad, onError = ()=>{} ) {
this.decodeDracoFile( buffer, onLoad, null, null, SRGBColorSpace ).catch( onError );
}
decodeDracoFile( buffer, callback, attributeIDs, attributeTypes, vertexColorSpace = LinearSRGBColorSpace ) {
decodeDracoFile( buffer, callback, attributeIDs, attributeTypes, vertexColorSpace = LinearSRGBColorSpace, onError = () => {} ) {
const taskConfig = {
attributeIDs: attributeIDs || this.defaultAttributeIDs,
@@ -100,7 +98,7 @@ class DRACOLoader extends Loader {
vertexColorSpace: vertexColorSpace,
};
return this.decodeGeometry( buffer, taskConfig ).then( callback );
return this.decodeGeometry( buffer, taskConfig ).then( callback ).catch( onError );
}
@@ -212,6 +210,8 @@ class DRACOLoader extends Loader {
this._assignVertexColorSpace( attribute, result.vertexColorSpace );
attribute.normalized = ( array instanceof Float32Array ) === false;
}
geometry.setAttribute( name, attribute );
@@ -1,6 +1,3 @@
// MIT License
// Copyright © 2010-2023 three.js authors
import {
AnimationClip,
Bone,
@@ -9,6 +6,7 @@ import {
BufferGeometry,
ClampToEdgeWrapping,
Color,
ColorManagement,
DirectionalLight,
DoubleSide,
FileLoader,
@@ -28,6 +26,7 @@ import {
LinearFilter,
LinearMipmapLinearFilter,
LinearMipmapNearestFilter,
LinearSRGBColorSpace,
Loader,
LoaderUtils,
Material,
@@ -63,7 +62,8 @@ import {
Vector2,
Vector3,
VectorKeyframeTrack,
SRGBColorSpace
SRGBColorSpace,
InstancedBufferAttribute
} from 'three';
import { toTrianglesDrawMode } from '../utils/BufferGeometryUtils.js';
@@ -145,6 +145,18 @@ class GLTFLoader extends Loader {
} );
this.register( function ( parser ) {
return new GLTFMaterialsAnisotropyExtension( parser );
} );
this.register( function ( parser ) {
return new GLTFMaterialsBumpExtension( parser );
} );
this.register( function ( parser ) {
return new GLTFLightsExtension( parser );
@@ -177,7 +189,13 @@ class GLTFLoader extends Loader {
} else if ( this.path !== '' ) {
resourcePath = this.path;
// If a base path is set, resources will be relative paths from that plus the relative path of the gltf file
// Example path = 'https://my-cnd-server.com/', url = 'assets/models/model.gltf'
// resourcePath = 'https://my-cnd-server.com/assets/models/'
// referenced resource 'model.bin' will be loaded from 'https://my-cnd-server.com/assets/models/model.bin'
// referenced resource '../textures/texture.png' will be loaded from 'https://my-cnd-server.com/assets/textures/texture.png'
const relativeUrl = LoaderUtils.extractUrlBase( url );
resourcePath = LoaderUtils.resolveURL( relativeUrl, this.path );
} else {
@@ -356,6 +374,9 @@ class GLTFLoader extends Loader {
for ( let i = 0; i < this.pluginCallbacks.length; i ++ ) {
const plugin = this.pluginCallbacks[ i ]( parser );
if ( ! plugin.name ) console.error( 'THREE.GLTFLoader: Invalid plugin found: missing name' );
plugins[ plugin.name ] = plugin;
// Workaround to avoid determining as unknown extension
@@ -475,12 +496,14 @@ const EXTENSIONS = {
KHR_MATERIALS_SPECULAR: 'KHR_materials_specular',
KHR_MATERIALS_TRANSMISSION: 'KHR_materials_transmission',
KHR_MATERIALS_IRIDESCENCE: 'KHR_materials_iridescence',
KHR_MATERIALS_ANISOTROPY: 'KHR_materials_anisotropy',
KHR_MATERIALS_UNLIT: 'KHR_materials_unlit',
KHR_MATERIALS_VOLUME: 'KHR_materials_volume',
KHR_TEXTURE_BASISU: 'KHR_texture_basisu',
KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform',
KHR_MESH_QUANTIZATION: 'KHR_mesh_quantization',
KHR_MATERIALS_EMISSIVE_STRENGTH: 'KHR_materials_emissive_strength',
EXT_MATERIALS_BUMP: 'EXT_materials_bump',
EXT_TEXTURE_WEBP: 'EXT_texture_webp',
EXT_TEXTURE_AVIF: 'EXT_texture_avif',
EXT_MESHOPT_COMPRESSION: 'EXT_meshopt_compression',
@@ -541,7 +564,7 @@ class GLTFLightsExtension {
const color = new Color( 0xffffff );
if ( lightDef.color !== undefined ) color.fromArray( lightDef.color );
if ( lightDef.color !== undefined ) color.setRGB( lightDef.color[ 0 ], lightDef.color[ 1 ], lightDef.color[ 2 ], LinearSRGBColorSpace );
const range = lightDef.range !== undefined ? lightDef.range : 0;
@@ -659,7 +682,7 @@ class GLTFMaterialsUnlitExtension {
const array = metallicRoughness.baseColorFactor;
materialParams.color.fromArray( array );
materialParams.color.setRGB( array[ 0 ], array[ 1 ], array[ 2 ], LinearSRGBColorSpace );
materialParams.opacity = array[ 3 ];
}
@@ -935,7 +958,8 @@ class GLTFMaterialsSheenExtension {
if ( extension.sheenColorFactor !== undefined ) {
materialParams.sheenColor.fromArray( extension.sheenColorFactor );
const colorFactor = extension.sheenColorFactor;
materialParams.sheenColor.setRGB( colorFactor[ 0 ], colorFactor[ 1 ], colorFactor[ 2 ], LinearSRGBColorSpace );
}
@@ -1073,7 +1097,7 @@ class GLTFMaterialsVolumeExtension {
materialParams.attenuationDistance = extension.attenuationDistance || Infinity;
const colorArray = extension.attenuationColor || [ 1, 1, 1 ];
materialParams.attenuationColor = new Color( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ] );
materialParams.attenuationColor = new Color().setRGB( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ], LinearSRGBColorSpace );
return Promise.all( pending );
@@ -1176,7 +1200,7 @@ class GLTFMaterialsSpecularExtension {
}
const colorArray = extension.specularColorFactor || [ 1, 1, 1 ];
materialParams.specularColor = new Color( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ] );
materialParams.specularColor = new Color().setRGB( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ], LinearSRGBColorSpace );
if ( extension.specularColorTexture !== undefined ) {
@@ -1190,6 +1214,125 @@ class GLTFMaterialsSpecularExtension {
}
/**
* Materials bump Extension
*
* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/EXT_materials_bump
*/
class GLTFMaterialsBumpExtension {
constructor( parser ) {
this.parser = parser;
this.name = EXTENSIONS.EXT_MATERIALS_BUMP;
}
getMaterialType( materialIndex ) {
const parser = this.parser;
const materialDef = parser.json.materials[ materialIndex ];
if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null;
return MeshPhysicalMaterial;
}
extendMaterialParams( materialIndex, materialParams ) {
const parser = this.parser;
const materialDef = parser.json.materials[ materialIndex ];
if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) {
return Promise.resolve();
}
const pending = [];
const extension = materialDef.extensions[ this.name ];
materialParams.bumpScale = extension.bumpFactor !== undefined ? extension.bumpFactor : 1.0;
if ( extension.bumpTexture !== undefined ) {
pending.push( parser.assignTexture( materialParams, 'bumpMap', extension.bumpTexture ) );
}
return Promise.all( pending );
}
}
/**
* Materials anisotropy Extension
*
* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_anisotropy
*/
class GLTFMaterialsAnisotropyExtension {
constructor( parser ) {
this.parser = parser;
this.name = EXTENSIONS.KHR_MATERIALS_ANISOTROPY;
}
getMaterialType( materialIndex ) {
const parser = this.parser;
const materialDef = parser.json.materials[ materialIndex ];
if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null;
return MeshPhysicalMaterial;
}
extendMaterialParams( materialIndex, materialParams ) {
const parser = this.parser;
const materialDef = parser.json.materials[ materialIndex ];
if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) {
return Promise.resolve();
}
const pending = [];
const extension = materialDef.extensions[ this.name ];
if ( extension.anisotropyStrength !== undefined ) {
materialParams.anisotropy = extension.anisotropyStrength;
}
if ( extension.anisotropyRotation !== undefined ) {
materialParams.anisotropyRotation = extension.anisotropyRotation;
}
if ( extension.anisotropyTexture !== undefined ) {
pending.push( parser.assignTexture( materialParams, 'anisotropyMap', extension.anisotropyTexture ) );
}
return Promise.all( pending );
}
}
/**
* BasisU Texture Extension
*
@@ -1608,7 +1751,12 @@ class GLTFMeshGpuInstancing {
// Add instance attributes to the geometry, excluding TRS.
for ( const attributeName in attributes ) {
if ( attributeName !== 'TRANSLATION' &&
if ( attributeName === '_COLOR_0' ) {
const attr = attributes[ attributeName ];
instancedMesh.instanceColor = new InstancedBufferAttribute( attr.array, attr.itemSize, attr.normalized );
} else if ( attributeName !== 'TRANSLATION' &&
attributeName !== 'ROTATION' &&
attributeName !== 'SCALE' ) {
@@ -1775,7 +1923,7 @@ class GLTFDracoMeshCompressionExtension {
return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) {
return new Promise( function ( resolve ) {
return new Promise( function ( resolve, reject ) {
dracoLoader.decodeDracoFile( bufferView, function ( geometry ) {
@@ -1790,7 +1938,7 @@ class GLTFDracoMeshCompressionExtension {
resolve( geometry );
}, threeAttributeMap, attributeTypeMap );
}, threeAttributeMap, attributeTypeMap, LinearSRGBColorSpace, reject );
} );
@@ -2258,9 +2406,10 @@ function updateMorphTargets( mesh, meshDef ) {
function createPrimitiveKey( primitiveDef ) {
const dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ];
let geometryKey;
const dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ];
if ( dracoExtension ) {
geometryKey = 'draco:' + dracoExtension.bufferView
@@ -2273,6 +2422,16 @@ function createPrimitiveKey( primitiveDef ) {
}
if ( primitiveDef.targets !== undefined ) {
for ( let i = 0, il = primitiveDef.targets.length; i < il; i ++ ) {
geometryKey += ':' + createAttributesKey( primitiveDef.targets[ i ] );
}
}
return geometryKey;
}
@@ -2462,7 +2621,7 @@ class GLTFParser {
assignExtrasToUserData( result, json );
Promise.all( parser._invokeAll( function ( ext ) {
return Promise.all( parser._invokeAll( function ( ext ) {
return ext.afterRoot && ext.afterRoot( result );
@@ -3307,7 +3466,7 @@ class GLTFParser {
const array = metallicRoughness.baseColorFactor;
materialParams.color.fromArray( array );
materialParams.color.setRGB( array[ 0 ], array[ 1 ], array[ 2 ], LinearSRGBColorSpace );
materialParams.opacity = array[ 3 ];
}
@@ -3399,7 +3558,8 @@ class GLTFParser {
if ( materialDef.emissiveFactor !== undefined && materialType !== MeshBasicMaterial ) {
materialParams.emissive = new Color().fromArray( materialDef.emissiveFactor );
const emissiveFactor = materialDef.emissiveFactor;
materialParams.emissive = new Color().setRGB( emissiveFactor[ 0 ], emissiveFactor[ 1 ], emissiveFactor[ 2 ], LinearSRGBColorSpace );
}
@@ -3432,18 +3592,18 @@ class GLTFParser {
const sanitizedName = PropertyBinding.sanitizeNodeName( originalName || '' );
let name = sanitizedName;
if ( sanitizedName in this.nodeNamesUsed ) {
for ( let i = 1; this.nodeNamesUsed[ name ]; ++ i ) {
return sanitizedName + '_' + ( ++ this.nodeNamesUsed[ sanitizedName ] );
name = sanitizedName + '_' + i;
} else {
this.nodeNamesUsed[ sanitizedName ] = 0;
return sanitizedName;
}
this.nodeNamesUsed[ name ] = true;
return name;
}
/**
@@ -3640,12 +3800,16 @@ class GLTFParser {
if ( meshes.length === 1 ) {
if ( meshDef.extensions ) addUnknownExtensionsToUserData( extensions, meshes[ 0 ], meshDef );
return meshes[ 0 ];
}
const group = new Group();
if ( meshDef.extensions ) addUnknownExtensionsToUserData( extensions, group, meshDef );
parser.associations.set( group, { meshes: meshIndex } );
for ( let i = 0, il = meshes.length; i < il; i ++ ) {
@@ -3774,6 +3938,7 @@ class GLTFParser {
loadAnimation( animationIndex ) {
const json = this.json;
const parser = this;
const animationDef = json.animations[ animationIndex ];
const animationName = animationDef.name ? animationDef.name : 'animation_' + animationIndex;
@@ -3831,103 +3996,22 @@ class GLTFParser {
if ( node === undefined ) continue;
node.updateMatrix();
if ( node.updateMatrix ) {
let TypedKeyframeTrack;
switch ( PATH_PROPERTIES[ target.path ] ) {
case PATH_PROPERTIES.weights:
TypedKeyframeTrack = NumberKeyframeTrack;
break;
case PATH_PROPERTIES.rotation:
TypedKeyframeTrack = QuaternionKeyframeTrack;
break;
case PATH_PROPERTIES.position:
case PATH_PROPERTIES.scale:
default:
TypedKeyframeTrack = VectorKeyframeTrack;
break;
node.updateMatrix();
}
const targetName = node.name ? node.name : node.uuid;
const createdTracks = parser._createAnimationTracks( node, inputAccessor, outputAccessor, sampler, target );
const interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : InterpolateLinear;
if ( createdTracks ) {
const targetNames = [];
for ( let k = 0; k < createdTracks.length; k ++ ) {
if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) {
node.traverse( function ( object ) {
if ( object.morphTargetInfluences ) {
targetNames.push( object.name ? object.name : object.uuid );
}
} );
} else {
targetNames.push( targetName );
}
let outputArray = outputAccessor.array;
if ( outputAccessor.normalized ) {
const scale = getNormalizedComponentScale( outputArray.constructor );
const scaled = new Float32Array( outputArray.length );
for ( let j = 0, jl = outputArray.length; j < jl; j ++ ) {
scaled[ j ] = outputArray[ j ] * scale;
tracks.push( createdTracks[ k ] );
}
outputArray = scaled;
}
for ( let j = 0, jl = targetNames.length; j < jl; j ++ ) {
const track = new TypedKeyframeTrack(
targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ],
inputAccessor.array,
outputArray,
interpolation
);
// Override interpolation with custom factory method.
if ( sampler.interpolation === 'CUBICSPLINE' ) {
track.createInterpolant = function InterpolantFactoryMethodGLTFCubicSpline( result ) {
// A CUBICSPLINE keyframe in glTF has three output values for each input value,
// representing inTangent, splineVertex, and outTangent. As a result, track.getValueSize()
// must be divided by three to get the interpolant's sampleSize argument.
const interpolantType = ( this instanceof QuaternionKeyframeTrack ) ? GLTFCubicSplineQuaternionInterpolant : GLTFCubicSplineInterpolant;
return new interpolantType( this.times, this.values, this.getValueSize() / 3, result );
};
// Mark as CUBICSPLINE. `track.getInterpolation()` doesn't support custom interpolants.
track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline = true;
}
tracks.push( track );
}
}
@@ -4258,6 +4342,141 @@ class GLTFParser {
}
_createAnimationTracks( node, inputAccessor, outputAccessor, sampler, target ) {
const tracks = [];
const targetName = node.name ? node.name : node.uuid;
const targetNames = [];
if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) {
node.traverse( function ( object ) {
if ( object.morphTargetInfluences ) {
targetNames.push( object.name ? object.name : object.uuid );
}
} );
} else {
targetNames.push( targetName );
}
let TypedKeyframeTrack;
switch ( PATH_PROPERTIES[ target.path ] ) {
case PATH_PROPERTIES.weights:
TypedKeyframeTrack = NumberKeyframeTrack;
break;
case PATH_PROPERTIES.rotation:
TypedKeyframeTrack = QuaternionKeyframeTrack;
break;
case PATH_PROPERTIES.position:
case PATH_PROPERTIES.scale:
TypedKeyframeTrack = VectorKeyframeTrack;
break;
default:
switch ( outputAccessor.itemSize ) {
case 1:
TypedKeyframeTrack = NumberKeyframeTrack;
break;
case 2:
case 3:
default:
TypedKeyframeTrack = VectorKeyframeTrack;
break;
}
break;
}
const interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : InterpolateLinear;
const outputArray = this._getArrayFromAccessor( outputAccessor );
for ( let j = 0, jl = targetNames.length; j < jl; j ++ ) {
const track = new TypedKeyframeTrack(
targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ],
inputAccessor.array,
outputArray,
interpolation
);
// Override interpolation with custom factory method.
if ( sampler.interpolation === 'CUBICSPLINE' ) {
this._createCubicSplineTrackInterpolant( track );
}
tracks.push( track );
}
return tracks;
}
_getArrayFromAccessor( accessor ) {
let outputArray = accessor.array;
if ( accessor.normalized ) {
const scale = getNormalizedComponentScale( outputArray.constructor );
const scaled = new Float32Array( outputArray.length );
for ( let j = 0, jl = outputArray.length; j < jl; j ++ ) {
scaled[ j ] = outputArray[ j ] * scale;
}
outputArray = scaled;
}
return outputArray;
}
_createCubicSplineTrackInterpolant( track ) {
track.createInterpolant = function InterpolantFactoryMethodGLTFCubicSpline( result ) {
// A CUBICSPLINE keyframe in glTF has three output values for each input value,
// representing inTangent, splineVertex, and outTangent. As a result, track.getValueSize()
// must be divided by three to get the interpolant's sampleSize argument.
const interpolantType = ( this instanceof QuaternionKeyframeTrack ) ? GLTFCubicSplineQuaternionInterpolant : GLTFCubicSplineInterpolant;
return new interpolantType( this.times, this.values, this.getValueSize() / 3, result );
};
// Mark as CUBICSPLINE. `track.getInterpolation()` doesn't support custom interpolants.
track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline = true;
}
}
/**
@@ -4421,6 +4640,12 @@ function addPrimitiveAttributes( geometry, primitiveDef, parser ) {
}
if ( ColorManagement.workingColorSpace !== LinearSRGBColorSpace && 'COLOR_0' in attributes ) {
console.warn( `THREE.GLTFLoader: Converting vertex colors from "srgb-linear" to "${ColorManagement.workingColorSpace}" not supported.` );
}
assignExtrasToUserData( geometry, primitiveDef );
computeBounds( geometry, primitiveDef, parser );
@@ -0,0 +1,231 @@
import {
Clock,
HalfFloatType,
NoBlending,
Vector2,
WebGLRenderTarget
} from 'three';
import { CopyShader } from '../shaders/CopyShader.js';
import { ShaderPass } from './ShaderPass.js';
import { MaskPass } from './MaskPass.js';
import { ClearMaskPass } from './MaskPass.js';
class EffectComposer {
constructor( renderer, renderTarget ) {
this.renderer = renderer;
this._pixelRatio = renderer.getPixelRatio();
if ( renderTarget === undefined ) {
const size = renderer.getSize( new Vector2() );
this._width = size.width;
this._height = size.height;
renderTarget = new WebGLRenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType } );
renderTarget.texture.name = 'EffectComposer.rt1';
} else {
this._width = renderTarget.width;
this._height = renderTarget.height;
}
this.renderTarget1 = renderTarget;
this.renderTarget2 = renderTarget.clone();
this.renderTarget2.texture.name = 'EffectComposer.rt2';
this.writeBuffer = this.renderTarget1;
this.readBuffer = this.renderTarget2;
this.renderToScreen = true;
this.passes = [];
this.copyPass = new ShaderPass( CopyShader );
this.copyPass.material.blending = NoBlending;
this.clock = new Clock();
}
swapBuffers() {
const tmp = this.readBuffer;
this.readBuffer = this.writeBuffer;
this.writeBuffer = tmp;
}
addPass( pass ) {
this.passes.push( pass );
pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio );
}
insertPass( pass, index ) {
this.passes.splice( index, 0, pass );
pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio );
}
removePass( pass ) {
const index = this.passes.indexOf( pass );
if ( index !== - 1 ) {
this.passes.splice( index, 1 );
}
}
isLastEnabledPass( passIndex ) {
for ( let i = passIndex + 1; i < this.passes.length; i ++ ) {
if ( this.passes[ i ].enabled ) {
return false;
}
}
return true;
}
render( deltaTime ) {
// deltaTime value is in seconds
if ( deltaTime === undefined ) {
deltaTime = this.clock.getDelta();
}
const currentRenderTarget = this.renderer.getRenderTarget();
let maskActive = false;
for ( let i = 0, il = this.passes.length; i < il; i ++ ) {
const pass = this.passes[ i ];
if ( pass.enabled === false ) continue;
pass.renderToScreen = ( this.renderToScreen && this.isLastEnabledPass( i ) );
pass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime, maskActive );
if ( pass.needsSwap ) {
if ( maskActive ) {
const context = this.renderer.getContext();
const stencil = this.renderer.state.buffers.stencil;
//context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff );
stencil.setFunc( context.NOTEQUAL, 1, 0xffffffff );
this.copyPass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime );
//context.stencilFunc( context.EQUAL, 1, 0xffffffff );
stencil.setFunc( context.EQUAL, 1, 0xffffffff );
}
this.swapBuffers();
}
if ( MaskPass !== undefined ) {
if ( pass instanceof MaskPass ) {
maskActive = true;
} else if ( pass instanceof ClearMaskPass ) {
maskActive = false;
}
}
}
this.renderer.setRenderTarget( currentRenderTarget );
}
reset( renderTarget ) {
if ( renderTarget === undefined ) {
const size = this.renderer.getSize( new Vector2() );
this._pixelRatio = this.renderer.getPixelRatio();
this._width = size.width;
this._height = size.height;
renderTarget = this.renderTarget1.clone();
renderTarget.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio );
}
this.renderTarget1.dispose();
this.renderTarget2.dispose();
this.renderTarget1 = renderTarget;
this.renderTarget2 = renderTarget.clone();
this.writeBuffer = this.renderTarget1;
this.readBuffer = this.renderTarget2;
}
setSize( width, height ) {
this._width = width;
this._height = height;
const effectiveWidth = this._width * this._pixelRatio;
const effectiveHeight = this._height * this._pixelRatio;
this.renderTarget1.setSize( effectiveWidth, effectiveHeight );
this.renderTarget2.setSize( effectiveWidth, effectiveHeight );
for ( let i = 0; i < this.passes.length; i ++ ) {
this.passes[ i ].setSize( effectiveWidth, effectiveHeight );
}
}
setPixelRatio( pixelRatio ) {
this._pixelRatio = pixelRatio;
this.setSize( this._width, this._height );
}
dispose() {
this.renderTarget1.dispose();
this.renderTarget2.dispose();
this.copyPass.dispose();
}
}
export { EffectComposer };
@@ -0,0 +1,104 @@
import { Pass } from './Pass.js';
class MaskPass extends Pass {
constructor( scene, camera ) {
super();
this.scene = scene;
this.camera = camera;
this.clear = true;
this.needsSwap = false;
this.inverse = false;
}
render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
const context = renderer.getContext();
const state = renderer.state;
// don't update color or depth
state.buffers.color.setMask( false );
state.buffers.depth.setMask( false );
// lock buffers
state.buffers.color.setLocked( true );
state.buffers.depth.setLocked( true );
// set up stencil
let writeValue, clearValue;
if ( this.inverse ) {
writeValue = 0;
clearValue = 1;
} else {
writeValue = 1;
clearValue = 0;
}
state.buffers.stencil.setTest( true );
state.buffers.stencil.setOp( context.REPLACE, context.REPLACE, context.REPLACE );
state.buffers.stencil.setFunc( context.ALWAYS, writeValue, 0xffffffff );
state.buffers.stencil.setClear( clearValue );
state.buffers.stencil.setLocked( true );
// draw into the stencil buffer
renderer.setRenderTarget( readBuffer );
if ( this.clear ) renderer.clear();
renderer.render( this.scene, this.camera );
renderer.setRenderTarget( writeBuffer );
if ( this.clear ) renderer.clear();
renderer.render( this.scene, this.camera );
// unlock color and depth buffer and make them writable for subsequent rendering/clearing
state.buffers.color.setLocked( false );
state.buffers.depth.setLocked( false );
state.buffers.color.setMask( true );
state.buffers.depth.setMask( true );
// only render where stencil is set to 1
state.buffers.stencil.setLocked( false );
state.buffers.stencil.setFunc( context.EQUAL, 1, 0xffffffff ); // draw if == 1
state.buffers.stencil.setOp( context.KEEP, context.KEEP, context.KEEP );
state.buffers.stencil.setLocked( true );
}
}
class ClearMaskPass extends Pass {
constructor() {
super();
this.needsSwap = false;
}
render( renderer /*, writeBuffer, readBuffer, deltaTime, maskActive */ ) {
renderer.state.buffers.stencil.setLocked( false );
renderer.state.buffers.stencil.setTest( false );
}
}
export { MaskPass, ClearMaskPass };
@@ -0,0 +1,95 @@
import {
ColorManagement,
RawShaderMaterial,
UniformsUtils,
LinearToneMapping,
ReinhardToneMapping,
CineonToneMapping,
AgXToneMapping,
ACESFilmicToneMapping,
SRGBTransfer
} from 'three';
import { Pass, FullScreenQuad } from './Pass.js';
import { OutputShader } from '../shaders/OutputShader.js';
class OutputPass extends Pass {
constructor() {
super();
//
const shader = OutputShader;
this.uniforms = UniformsUtils.clone( shader.uniforms );
this.material = new RawShaderMaterial( {
name: shader.name,
uniforms: this.uniforms,
vertexShader: shader.vertexShader,
fragmentShader: shader.fragmentShader
} );
this.fsQuad = new FullScreenQuad( this.material );
// internal cache
this._outputColorSpace = null;
this._toneMapping = null;
}
render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive */ ) {
this.uniforms[ 'tDiffuse' ].value = readBuffer.texture;
this.uniforms[ 'toneMappingExposure' ].value = renderer.toneMappingExposure;
// rebuild defines if required
if ( this._outputColorSpace !== renderer.outputColorSpace || this._toneMapping !== renderer.toneMapping ) {
this._outputColorSpace = renderer.outputColorSpace;
this._toneMapping = renderer.toneMapping;
this.material.defines = {};
if ( ColorManagement.getTransfer( this._outputColorSpace ) === SRGBTransfer ) this.material.defines.SRGB_TRANSFER = '';
if ( this._toneMapping === LinearToneMapping ) this.material.defines.LINEAR_TONE_MAPPING = '';
else if ( this._toneMapping === ReinhardToneMapping ) this.material.defines.REINHARD_TONE_MAPPING = '';
else if ( this._toneMapping === CineonToneMapping ) this.material.defines.CINEON_TONE_MAPPING = '';
else if ( this._toneMapping === ACESFilmicToneMapping ) this.material.defines.ACES_FILMIC_TONE_MAPPING = '';
else if ( this._toneMapping === AgXToneMapping ) this.material.defines.AGX_TONE_MAPPING = '';
this.material.needsUpdate = true;
}
//
if ( this.renderToScreen === true ) {
renderer.setRenderTarget( null );
this.fsQuad.render( renderer );
} else {
renderer.setRenderTarget( writeBuffer );
if ( this.clear ) renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil );
this.fsQuad.render( renderer );
}
}
dispose() {
this.material.dispose();
this.fsQuad.dispose();
}
}
export { OutputPass };
@@ -0,0 +1,95 @@
import {
BufferGeometry,
Float32BufferAttribute,
OrthographicCamera,
Mesh
} from 'three';
class Pass {
constructor() {
this.isPass = true;
// if set to true, the pass is processed by the composer
this.enabled = true;
// if set to true, the pass indicates to swap read and write buffer after rendering
this.needsSwap = true;
// if set to true, the pass clears its buffer before rendering
this.clear = false;
// if set to true, the result of the pass is rendered to screen. This is set automatically by EffectComposer.
this.renderToScreen = false;
}
setSize( /* width, height */ ) {}
render( /* renderer, writeBuffer, readBuffer, deltaTime, maskActive */ ) {
console.error( 'THREE.Pass: .render() must be implemented in derived pass.' );
}
dispose() {}
}
// Helper for passes that need to fill the viewport with a single quad.
const _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
// https://github.com/mrdoob/three.js/pull/21358
class FullscreenTriangleGeometry extends BufferGeometry {
constructor() {
super();
this.setAttribute( 'position', new Float32BufferAttribute( [ - 1, 3, 0, - 1, - 1, 0, 3, - 1, 0 ], 3 ) );
this.setAttribute( 'uv', new Float32BufferAttribute( [ 0, 2, 0, 0, 2, 0 ], 2 ) );
}
}
const _geometry = new FullscreenTriangleGeometry();
class FullScreenQuad {
constructor( material ) {
this._mesh = new Mesh( _geometry, material );
}
dispose() {
this._mesh.geometry.dispose();
}
render( renderer ) {
renderer.render( this._mesh, _camera );
}
get material() {
return this._mesh.material;
}
set material( value ) {
this._mesh.material = value;
}
}
export { Pass, FullScreenQuad };
@@ -0,0 +1,99 @@
import {
Color
} from 'three';
import { Pass } from './Pass.js';
class RenderPass extends Pass {
constructor( scene, camera, overrideMaterial = null, clearColor = null, clearAlpha = null ) {
super();
this.scene = scene;
this.camera = camera;
this.overrideMaterial = overrideMaterial;
this.clearColor = clearColor;
this.clearAlpha = clearAlpha;
this.clear = true;
this.clearDepth = false;
this.needsSwap = false;
this._oldClearColor = new Color();
}
render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
const oldAutoClear = renderer.autoClear;
renderer.autoClear = false;
let oldClearAlpha, oldOverrideMaterial;
if ( this.overrideMaterial !== null ) {
oldOverrideMaterial = this.scene.overrideMaterial;
this.scene.overrideMaterial = this.overrideMaterial;
}
if ( this.clearColor !== null ) {
renderer.getClearColor( this._oldClearColor );
renderer.setClearColor( this.clearColor );
}
if ( this.clearAlpha !== null ) {
oldClearAlpha = renderer.getClearAlpha();
renderer.setClearAlpha( this.clearAlpha );
}
if ( this.clearDepth == true ) {
renderer.clearDepth();
}
renderer.setRenderTarget( this.renderToScreen ? null : readBuffer );
if ( this.clear === true ) {
// TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600
renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil );
}
renderer.render( this.scene, this.camera );
// restore
if ( this.clearColor !== null ) {
renderer.setClearColor( this._oldClearColor );
}
if ( this.clearAlpha !== null ) {
renderer.setClearAlpha( oldClearAlpha );
}
if ( this.overrideMaterial !== null ) {
this.scene.overrideMaterial = oldOverrideMaterial;
}
renderer.autoClear = oldAutoClear;
}
}
export { RenderPass };
File diff suppressed because one or more lines are too long
@@ -0,0 +1,77 @@
import {
ShaderMaterial,
UniformsUtils
} from 'three';
import { Pass, FullScreenQuad } from './Pass.js';
class ShaderPass extends Pass {
constructor( shader, textureID ) {
super();
this.textureID = ( textureID !== undefined ) ? textureID : 'tDiffuse';
if ( shader instanceof ShaderMaterial ) {
this.uniforms = shader.uniforms;
this.material = shader;
} else if ( shader ) {
this.uniforms = UniformsUtils.clone( shader.uniforms );
this.material = new ShaderMaterial( {
name: ( shader.name !== undefined ) ? shader.name : 'unspecified',
defines: Object.assign( {}, shader.defines ),
uniforms: this.uniforms,
vertexShader: shader.vertexShader,
fragmentShader: shader.fragmentShader
} );
}
this.fsQuad = new FullScreenQuad( this.material );
}
render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
if ( this.uniforms[ this.textureID ] ) {
this.uniforms[ this.textureID ].value = readBuffer.texture;
}
this.fsQuad.material = this.material;
if ( this.renderToScreen ) {
renderer.setRenderTarget( null );
this.fsQuad.render( renderer );
} else {
renderer.setRenderTarget( writeBuffer );
// TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600
if ( this.clear ) renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil );
this.fsQuad.render( renderer );
}
}
dispose() {
this.material.dispose();
this.fsQuad.dispose();
}
}
export { ShaderPass };
@@ -0,0 +1,415 @@
import {
AdditiveBlending,
Color,
HalfFloatType,
MeshBasicMaterial,
ShaderMaterial,
UniformsUtils,
Vector2,
Vector3,
WebGLRenderTarget
} from 'three';
import { Pass, FullScreenQuad } from './Pass.js';
import { CopyShader } from '../shaders/CopyShader.js';
import { LuminosityHighPassShader } from '../shaders/LuminosityHighPassShader.js';
/**
* UnrealBloomPass is inspired by the bloom pass of Unreal Engine. It creates a
* mip map chain of bloom textures and blurs them with different radii. Because
* of the weighted combination of mips, and because larger blurs are done on
* higher mips, this effect provides good quality and performance.
*
* Reference:
* - https://docs.unrealengine.com/latest/INT/Engine/Rendering/PostProcessEffects/Bloom/
*/
class UnrealBloomPass extends Pass {
constructor( resolution, strength, radius, threshold ) {
super();
this.strength = ( strength !== undefined ) ? strength : 1;
this.radius = radius;
this.threshold = threshold;
this.resolution = ( resolution !== undefined ) ? new Vector2( resolution.x, resolution.y ) : new Vector2( 256, 256 );
// create color only once here, reuse it later inside the render function
this.clearColor = new Color( 0, 0, 0 );
// render targets
this.renderTargetsHorizontal = [];
this.renderTargetsVertical = [];
this.nMips = 5;
let resx = Math.round( this.resolution.x / 2 );
let resy = Math.round( this.resolution.y / 2 );
this.renderTargetBright = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } );
this.renderTargetBright.texture.name = 'UnrealBloomPass.bright';
this.renderTargetBright.texture.generateMipmaps = false;
for ( let i = 0; i < this.nMips; i ++ ) {
const renderTargetHorizonal = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } );
renderTargetHorizonal.texture.name = 'UnrealBloomPass.h' + i;
renderTargetHorizonal.texture.generateMipmaps = false;
this.renderTargetsHorizontal.push( renderTargetHorizonal );
const renderTargetVertical = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } );
renderTargetVertical.texture.name = 'UnrealBloomPass.v' + i;
renderTargetVertical.texture.generateMipmaps = false;
this.renderTargetsVertical.push( renderTargetVertical );
resx = Math.round( resx / 2 );
resy = Math.round( resy / 2 );
}
// luminosity high pass material
const highPassShader = LuminosityHighPassShader;
this.highPassUniforms = UniformsUtils.clone( highPassShader.uniforms );
this.highPassUniforms[ 'luminosityThreshold' ].value = threshold;
this.highPassUniforms[ 'smoothWidth' ].value = 0.01;
this.materialHighPassFilter = new ShaderMaterial( {
uniforms: this.highPassUniforms,
vertexShader: highPassShader.vertexShader,
fragmentShader: highPassShader.fragmentShader
} );
// gaussian blur materials
this.separableBlurMaterials = [];
const kernelSizeArray = [ 3, 5, 7, 9, 11 ];
resx = Math.round( this.resolution.x / 2 );
resy = Math.round( this.resolution.y / 2 );
for ( let i = 0; i < this.nMips; i ++ ) {
this.separableBlurMaterials.push( this.getSeperableBlurMaterial( kernelSizeArray[ i ] ) );
this.separableBlurMaterials[ i ].uniforms[ 'invSize' ].value = new Vector2( 1 / resx, 1 / resy );
resx = Math.round( resx / 2 );
resy = Math.round( resy / 2 );
}
// composite material
this.compositeMaterial = this.getCompositeMaterial( this.nMips );
this.compositeMaterial.uniforms[ 'blurTexture1' ].value = this.renderTargetsVertical[ 0 ].texture;
this.compositeMaterial.uniforms[ 'blurTexture2' ].value = this.renderTargetsVertical[ 1 ].texture;
this.compositeMaterial.uniforms[ 'blurTexture3' ].value = this.renderTargetsVertical[ 2 ].texture;
this.compositeMaterial.uniforms[ 'blurTexture4' ].value = this.renderTargetsVertical[ 3 ].texture;
this.compositeMaterial.uniforms[ 'blurTexture5' ].value = this.renderTargetsVertical[ 4 ].texture;
this.compositeMaterial.uniforms[ 'bloomStrength' ].value = strength;
this.compositeMaterial.uniforms[ 'bloomRadius' ].value = 0.1;
const bloomFactors = [ 1.0, 0.8, 0.6, 0.4, 0.2 ];
this.compositeMaterial.uniforms[ 'bloomFactors' ].value = bloomFactors;
this.bloomTintColors = [ new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ) ];
this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors;
// blend material
const copyShader = CopyShader;
this.copyUniforms = UniformsUtils.clone( copyShader.uniforms );
this.blendMaterial = new ShaderMaterial( {
uniforms: this.copyUniforms,
vertexShader: copyShader.vertexShader,
fragmentShader: copyShader.fragmentShader,
blending: AdditiveBlending,
depthTest: false,
depthWrite: false,
transparent: true
} );
this.enabled = true;
this.needsSwap = false;
this._oldClearColor = new Color();
this.oldClearAlpha = 1;
this.basic = new MeshBasicMaterial();
this.fsQuad = new FullScreenQuad( null );
}
dispose() {
for ( let i = 0; i < this.renderTargetsHorizontal.length; i ++ ) {
this.renderTargetsHorizontal[ i ].dispose();
}
for ( let i = 0; i < this.renderTargetsVertical.length; i ++ ) {
this.renderTargetsVertical[ i ].dispose();
}
this.renderTargetBright.dispose();
//
for ( let i = 0; i < this.separableBlurMaterials.length; i ++ ) {
this.separableBlurMaterials[ i ].dispose();
}
this.compositeMaterial.dispose();
this.blendMaterial.dispose();
this.basic.dispose();
//
this.fsQuad.dispose();
}
setSize( width, height ) {
let resx = Math.round( width / 2 );
let resy = Math.round( height / 2 );
this.renderTargetBright.setSize( resx, resy );
for ( let i = 0; i < this.nMips; i ++ ) {
this.renderTargetsHorizontal[ i ].setSize( resx, resy );
this.renderTargetsVertical[ i ].setSize( resx, resy );
this.separableBlurMaterials[ i ].uniforms[ 'invSize' ].value = new Vector2( 1 / resx, 1 / resy );
resx = Math.round( resx / 2 );
resy = Math.round( resy / 2 );
}
}
render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) {
renderer.getClearColor( this._oldClearColor );
this.oldClearAlpha = renderer.getClearAlpha();
const oldAutoClear = renderer.autoClear;
renderer.autoClear = false;
renderer.setClearColor( this.clearColor, 0 );
if ( maskActive ) renderer.state.buffers.stencil.setTest( false );
// Render input to screen
if ( this.renderToScreen ) {
this.fsQuad.material = this.basic;
this.basic.map = readBuffer.texture;
renderer.setRenderTarget( null );
renderer.clear();
this.fsQuad.render( renderer );
}
// 1. Extract Bright Areas
this.highPassUniforms[ 'tDiffuse' ].value = readBuffer.texture;
this.highPassUniforms[ 'luminosityThreshold' ].value = this.threshold;
this.fsQuad.material = this.materialHighPassFilter;
renderer.setRenderTarget( this.renderTargetBright );
renderer.clear();
this.fsQuad.render( renderer );
// 2. Blur All the mips progressively
let inputRenderTarget = this.renderTargetBright;
for ( let i = 0; i < this.nMips; i ++ ) {
this.fsQuad.material = this.separableBlurMaterials[ i ];
this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = inputRenderTarget.texture;
this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionX;
renderer.setRenderTarget( this.renderTargetsHorizontal[ i ] );
renderer.clear();
this.fsQuad.render( renderer );
this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = this.renderTargetsHorizontal[ i ].texture;
this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionY;
renderer.setRenderTarget( this.renderTargetsVertical[ i ] );
renderer.clear();
this.fsQuad.render( renderer );
inputRenderTarget = this.renderTargetsVertical[ i ];
}
// Composite All the mips
this.fsQuad.material = this.compositeMaterial;
this.compositeMaterial.uniforms[ 'bloomStrength' ].value = this.strength;
this.compositeMaterial.uniforms[ 'bloomRadius' ].value = this.radius;
this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors;
renderer.setRenderTarget( this.renderTargetsHorizontal[ 0 ] );
renderer.clear();
this.fsQuad.render( renderer );
// Blend it additively over the input texture
this.fsQuad.material = this.blendMaterial;
this.copyUniforms[ 'tDiffuse' ].value = this.renderTargetsHorizontal[ 0 ].texture;
if ( maskActive ) renderer.state.buffers.stencil.setTest( true );
if ( this.renderToScreen ) {
renderer.setRenderTarget( null );
this.fsQuad.render( renderer );
} else {
renderer.setRenderTarget( readBuffer );
this.fsQuad.render( renderer );
}
// Restore renderer settings
renderer.setClearColor( this._oldClearColor, this.oldClearAlpha );
renderer.autoClear = oldAutoClear;
}
getSeperableBlurMaterial( kernelRadius ) {
const coefficients = [];
for ( let i = 0; i < kernelRadius; i ++ ) {
coefficients.push( 0.39894 * Math.exp( - 0.5 * i * i / ( kernelRadius * kernelRadius ) ) / kernelRadius );
}
return new ShaderMaterial( {
defines: {
'KERNEL_RADIUS': kernelRadius
},
uniforms: {
'colorTexture': { value: null },
'invSize': { value: new Vector2( 0.5, 0.5 ) }, // inverse texture size
'direction': { value: new Vector2( 0.5, 0.5 ) },
'gaussianCoefficients': { value: coefficients } // precomputed Gaussian coefficients
},
vertexShader:
`varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader:
`#include <common>
varying vec2 vUv;
uniform sampler2D colorTexture;
uniform vec2 invSize;
uniform vec2 direction;
uniform float gaussianCoefficients[KERNEL_RADIUS];
void main() {
float weightSum = gaussianCoefficients[0];
vec3 diffuseSum = texture2D( colorTexture, vUv ).rgb * weightSum;
for( int i = 1; i < KERNEL_RADIUS; i ++ ) {
float x = float(i);
float w = gaussianCoefficients[i];
vec2 uvOffset = direction * invSize * x;
vec3 sample1 = texture2D( colorTexture, vUv + uvOffset ).rgb;
vec3 sample2 = texture2D( colorTexture, vUv - uvOffset ).rgb;
diffuseSum += (sample1 + sample2) * w;
weightSum += 2.0 * w;
}
gl_FragColor = vec4(diffuseSum/weightSum, 1.0);
}`
} );
}
getCompositeMaterial( nMips ) {
return new ShaderMaterial( {
defines: {
'NUM_MIPS': nMips
},
uniforms: {
'blurTexture1': { value: null },
'blurTexture2': { value: null },
'blurTexture3': { value: null },
'blurTexture4': { value: null },
'blurTexture5': { value: null },
'bloomStrength': { value: 1.0 },
'bloomFactors': { value: null },
'bloomTintColors': { value: null },
'bloomRadius': { value: 0.0 }
},
vertexShader:
`varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader:
`varying vec2 vUv;
uniform sampler2D blurTexture1;
uniform sampler2D blurTexture2;
uniform sampler2D blurTexture3;
uniform sampler2D blurTexture4;
uniform sampler2D blurTexture5;
uniform float bloomStrength;
uniform float bloomRadius;
uniform float bloomFactors[NUM_MIPS];
uniform vec3 bloomTintColors[NUM_MIPS];
float lerpBloomFactor(const in float factor) {
float mirrorFactor = 1.2 - factor;
return mix(factor, mirrorFactor, bloomRadius);
}
void main() {
gl_FragColor = bloomStrength * ( lerpBloomFactor(bloomFactors[0]) * vec4(bloomTintColors[0], 1.0) * texture2D(blurTexture1, vUv) +
lerpBloomFactor(bloomFactors[1]) * vec4(bloomTintColors[1], 1.0) * texture2D(blurTexture2, vUv) +
lerpBloomFactor(bloomFactors[2]) * vec4(bloomTintColors[2], 1.0) * texture2D(blurTexture3, vUv) +
lerpBloomFactor(bloomFactors[3]) * vec4(bloomTintColors[3], 1.0) * texture2D(blurTexture4, vUv) +
lerpBloomFactor(bloomFactors[4]) * vec4(bloomTintColors[4], 1.0) * texture2D(blurTexture5, vUv) );
}`
} );
}
}
UnrealBloomPass.BlurDirectionX = new Vector2( 1.0, 0.0 );
UnrealBloomPass.BlurDirectionY = new Vector2( 0.0, 1.0 );
export { UnrealBloomPass };
@@ -0,0 +1,56 @@
/**
* Brightness and contrast adjustment
* https://github.com/evanw/glfx.js
* brightness: -1 to 1 (-1 is solid black, 0 is no change, and 1 is solid white)
* contrast: -1 to 1 (-1 is solid gray, 0 is no change, and 1 is maximum contrast)
*/
const BrightnessContrastShader = {
name: 'BrightnessContrastShader',
uniforms: {
'tDiffuse': { value: null },
'brightness': { value: 0 },
'contrast': { value: 0 }
},
vertexShader: /* glsl */`
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader: /* glsl */`
uniform sampler2D tDiffuse;
uniform float brightness;
uniform float contrast;
varying vec2 vUv;
void main() {
gl_FragColor = texture2D( tDiffuse, vUv );
gl_FragColor.rgb += brightness;
if (contrast > 0.0) {
gl_FragColor.rgb = (gl_FragColor.rgb - 0.5) / (1.0 - contrast) + 0.5;
} else {
gl_FragColor.rgb = (gl_FragColor.rgb - 0.5) * (1.0 + contrast) + 0.5;
}
}`
};
export { BrightnessContrastShader };
@@ -0,0 +1,45 @@
/**
* Full-screen textured quad shader
*/
const CopyShader = {
name: 'CopyShader',
uniforms: {
'tDiffuse': { value: null },
'opacity': { value: 1.0 }
},
vertexShader: /* glsl */`
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader: /* glsl */`
uniform float opacity;
uniform sampler2D tDiffuse;
varying vec2 vUv;
void main() {
vec4 texel = texture2D( tDiffuse, vUv );
gl_FragColor = opacity * texel;
}`
};
export { CopyShader };
@@ -0,0 +1,44 @@
/**
* Exposure shader
*/
const ExposureShader = {
name: 'ExposureShader',
uniforms: {
'tDiffuse': { value: null },
'exposure': { value: 1.0 }
},
vertexShader: /* glsl */`
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader: /* glsl */`
uniform float exposure;
uniform sampler2D tDiffuse;
varying vec2 vUv;
void main() {
gl_FragColor = texture2D( tDiffuse, vUv );
gl_FragColor.rgb *= exposure;
}`
};
export { ExposureShader };
@@ -0,0 +1,67 @@
/**
* Hue and saturation adjustment
* https://github.com/evanw/glfx.js
* hue: -1 to 1 (-1 is 180 degrees in the negative direction, 0 is no change, etc.
* saturation: -1 to 1 (-1 is solid gray, 0 is no change, and 1 is maximum contrast)
*/
const HueSaturationShader = {
name: 'HueSaturationShader',
uniforms: {
'tDiffuse': { value: null },
'hue': { value: 0 },
'saturation': { value: 0 }
},
vertexShader: /* glsl */`
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader: /* glsl */`
uniform sampler2D tDiffuse;
uniform float hue;
uniform float saturation;
varying vec2 vUv;
void main() {
gl_FragColor = texture2D( tDiffuse, vUv );
// hue
float angle = hue * 3.14159265;
float s = sin(angle), c = cos(angle);
vec3 weights = (vec3(2.0 * c, -sqrt(3.0) * s - c, sqrt(3.0) * s - c) + 1.0) / 3.0;
float len = length(gl_FragColor.rgb);
gl_FragColor.rgb = vec3(
dot(gl_FragColor.rgb, weights.xyz),
dot(gl_FragColor.rgb, weights.zxy),
dot(gl_FragColor.rgb, weights.yzx)
);
// saturation
float average = (gl_FragColor.r + gl_FragColor.g + gl_FragColor.b) / 3.0;
if (saturation > 0.0) {
gl_FragColor.rgb += (average - gl_FragColor.rgb) * (1.0 - 1.0 / (1.001 - saturation));
} else {
gl_FragColor.rgb += (average - gl_FragColor.rgb) * (-saturation);
}
}`
};
export { HueSaturationShader };
@@ -0,0 +1,66 @@
import {
Color
} from 'three';
/**
* Luminosity
* http://en.wikipedia.org/wiki/Luminosity
*/
const LuminosityHighPassShader = {
name: 'LuminosityHighPassShader',
shaderID: 'luminosityHighPass',
uniforms: {
'tDiffuse': { value: null },
'luminosityThreshold': { value: 1.0 },
'smoothWidth': { value: 1.0 },
'defaultColor': { value: new Color( 0x000000 ) },
'defaultOpacity': { value: 0.0 }
},
vertexShader: /* glsl */`
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader: /* glsl */`
uniform sampler2D tDiffuse;
uniform vec3 defaultColor;
uniform float defaultOpacity;
uniform float luminosityThreshold;
uniform float smoothWidth;
varying vec2 vUv;
void main() {
vec4 texel = texture2D( tDiffuse, vUv );
vec3 luma = vec3( 0.299, 0.587, 0.114 );
float v = dot( texel.xyz, luma );
vec4 outputColor = vec4( defaultColor.rgb, defaultOpacity );
float alpha = smoothstep( luminosityThreshold, luminosityThreshold + smoothWidth, v );
gl_FragColor = mix( outputColor, texel, alpha );
}`
};
export { LuminosityHighPassShader };
@@ -0,0 +1,81 @@
const OutputShader = {
name: 'OutputShader',
uniforms: {
'tDiffuse': { value: null },
'toneMappingExposure': { value: 1 }
},
vertexShader: /* glsl */`
precision highp float;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
attribute vec3 position;
attribute vec2 uv;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader: /* glsl */`
precision highp float;
uniform sampler2D tDiffuse;
#include <tonemapping_pars_fragment>
#include <colorspace_pars_fragment>
varying vec2 vUv;
void main() {
gl_FragColor = texture2D( tDiffuse, vUv );
// tone mapping
#ifdef LINEAR_TONE_MAPPING
gl_FragColor.rgb = LinearToneMapping( gl_FragColor.rgb );
#elif defined( REINHARD_TONE_MAPPING )
gl_FragColor.rgb = ReinhardToneMapping( gl_FragColor.rgb );
#elif defined( CINEON_TONE_MAPPING )
gl_FragColor.rgb = OptimizedCineonToneMapping( gl_FragColor.rgb );
#elif defined( ACES_FILMIC_TONE_MAPPING )
gl_FragColor.rgb = ACESFilmicToneMapping( gl_FragColor.rgb );
#elif defined( AGX_TONE_MAPPING )
gl_FragColor.rgb = AgXToneMapping( gl_FragColor.rgb );
#endif
// color space
#ifdef SRGB_TRANSFER
gl_FragColor = sRGBTransferOETF( gl_FragColor );
#endif
}`
};
export { OutputShader };
@@ -0,0 +1,466 @@
import {
Vector2
} from 'three';
/**
* WebGL port of Subpixel Morphological Antialiasing (SMAA) v2.8
* Preset: SMAA 1x Medium (with color edge detection)
* https://github.com/iryoku/smaa/releases/tag/v2.8
*/
const SMAAEdgesShader = {
name: 'SMAAEdgesShader',
defines: {
'SMAA_THRESHOLD': '0.1'
},
uniforms: {
'tDiffuse': { value: null },
'resolution': { value: new Vector2( 1 / 1024, 1 / 512 ) }
},
vertexShader: /* glsl */`
uniform vec2 resolution;
varying vec2 vUv;
varying vec4 vOffset[ 3 ];
void SMAAEdgeDetectionVS( vec2 texcoord ) {
vOffset[ 0 ] = texcoord.xyxy + resolution.xyxy * vec4( -1.0, 0.0, 0.0, 1.0 ); // WebGL port note: Changed sign in W component
vOffset[ 1 ] = texcoord.xyxy + resolution.xyxy * vec4( 1.0, 0.0, 0.0, -1.0 ); // WebGL port note: Changed sign in W component
vOffset[ 2 ] = texcoord.xyxy + resolution.xyxy * vec4( -2.0, 0.0, 0.0, 2.0 ); // WebGL port note: Changed sign in W component
}
void main() {
vUv = uv;
SMAAEdgeDetectionVS( vUv );
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader: /* glsl */`
uniform sampler2D tDiffuse;
varying vec2 vUv;
varying vec4 vOffset[ 3 ];
vec4 SMAAColorEdgeDetectionPS( vec2 texcoord, vec4 offset[3], sampler2D colorTex ) {
vec2 threshold = vec2( SMAA_THRESHOLD, SMAA_THRESHOLD );
// Calculate color deltas:
vec4 delta;
vec3 C = texture2D( colorTex, texcoord ).rgb;
vec3 Cleft = texture2D( colorTex, offset[0].xy ).rgb;
vec3 t = abs( C - Cleft );
delta.x = max( max( t.r, t.g ), t.b );
vec3 Ctop = texture2D( colorTex, offset[0].zw ).rgb;
t = abs( C - Ctop );
delta.y = max( max( t.r, t.g ), t.b );
// We do the usual threshold:
vec2 edges = step( threshold, delta.xy );
// Then discard if there is no edge:
if ( dot( edges, vec2( 1.0, 1.0 ) ) == 0.0 )
discard;
// Calculate right and bottom deltas:
vec3 Cright = texture2D( colorTex, offset[1].xy ).rgb;
t = abs( C - Cright );
delta.z = max( max( t.r, t.g ), t.b );
vec3 Cbottom = texture2D( colorTex, offset[1].zw ).rgb;
t = abs( C - Cbottom );
delta.w = max( max( t.r, t.g ), t.b );
// Calculate the maximum delta in the direct neighborhood:
float maxDelta = max( max( max( delta.x, delta.y ), delta.z ), delta.w );
// Calculate left-left and top-top deltas:
vec3 Cleftleft = texture2D( colorTex, offset[2].xy ).rgb;
t = abs( C - Cleftleft );
delta.z = max( max( t.r, t.g ), t.b );
vec3 Ctoptop = texture2D( colorTex, offset[2].zw ).rgb;
t = abs( C - Ctoptop );
delta.w = max( max( t.r, t.g ), t.b );
// Calculate the final maximum delta:
maxDelta = max( max( maxDelta, delta.z ), delta.w );
// Local contrast adaptation in action:
edges.xy *= step( 0.5 * maxDelta, delta.xy );
return vec4( edges, 0.0, 0.0 );
}
void main() {
gl_FragColor = SMAAColorEdgeDetectionPS( vUv, vOffset, tDiffuse );
}`
};
const SMAAWeightsShader = {
name: 'SMAAWeightsShader',
defines: {
'SMAA_MAX_SEARCH_STEPS': '8',
'SMAA_AREATEX_MAX_DISTANCE': '16',
'SMAA_AREATEX_PIXEL_SIZE': '( 1.0 / vec2( 160.0, 560.0 ) )',
'SMAA_AREATEX_SUBTEX_SIZE': '( 1.0 / 7.0 )'
},
uniforms: {
'tDiffuse': { value: null },
'tArea': { value: null },
'tSearch': { value: null },
'resolution': { value: new Vector2( 1 / 1024, 1 / 512 ) }
},
vertexShader: /* glsl */`
uniform vec2 resolution;
varying vec2 vUv;
varying vec4 vOffset[ 3 ];
varying vec2 vPixcoord;
void SMAABlendingWeightCalculationVS( vec2 texcoord ) {
vPixcoord = texcoord / resolution;
// We will use these offsets for the searches later on (see @PSEUDO_GATHER4):
vOffset[ 0 ] = texcoord.xyxy + resolution.xyxy * vec4( -0.25, 0.125, 1.25, 0.125 ); // WebGL port note: Changed sign in Y and W components
vOffset[ 1 ] = texcoord.xyxy + resolution.xyxy * vec4( -0.125, 0.25, -0.125, -1.25 ); // WebGL port note: Changed sign in Y and W components
// And these for the searches, they indicate the ends of the loops:
vOffset[ 2 ] = vec4( vOffset[ 0 ].xz, vOffset[ 1 ].yw ) + vec4( -2.0, 2.0, -2.0, 2.0 ) * resolution.xxyy * float( SMAA_MAX_SEARCH_STEPS );
}
void main() {
vUv = uv;
SMAABlendingWeightCalculationVS( vUv );
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader: /* glsl */`
#define SMAASampleLevelZeroOffset( tex, coord, offset ) texture2D( tex, coord + float( offset ) * resolution, 0.0 )
uniform sampler2D tDiffuse;
uniform sampler2D tArea;
uniform sampler2D tSearch;
uniform vec2 resolution;
varying vec2 vUv;
varying vec4 vOffset[3];
varying vec2 vPixcoord;
#if __VERSION__ == 100
vec2 round( vec2 x ) {
return sign( x ) * floor( abs( x ) + 0.5 );
}
#endif
float SMAASearchLength( sampler2D searchTex, vec2 e, float bias, float scale ) {
// Not required if searchTex accesses are set to point:
// float2 SEARCH_TEX_PIXEL_SIZE = 1.0 / float2(66.0, 33.0);
// e = float2(bias, 0.0) + 0.5 * SEARCH_TEX_PIXEL_SIZE +
// e * float2(scale, 1.0) * float2(64.0, 32.0) * SEARCH_TEX_PIXEL_SIZE;
e.r = bias + e.r * scale;
return 255.0 * texture2D( searchTex, e, 0.0 ).r;
}
float SMAASearchXLeft( sampler2D edgesTex, sampler2D searchTex, vec2 texcoord, float end ) {
/**
* @PSEUDO_GATHER4
* This texcoord has been offset by (-0.25, -0.125) in the vertex shader to
* sample between edge, thus fetching four edges in a row.
* Sampling with different offsets in each direction allows to disambiguate
* which edges are active from the four fetched ones.
*/
vec2 e = vec2( 0.0, 1.0 );
for ( int i = 0; i < SMAA_MAX_SEARCH_STEPS; i ++ ) { // WebGL port note: Changed while to for
e = texture2D( edgesTex, texcoord, 0.0 ).rg;
texcoord -= vec2( 2.0, 0.0 ) * resolution;
if ( ! ( texcoord.x > end && e.g > 0.8281 && e.r == 0.0 ) ) break;
}
// We correct the previous (-0.25, -0.125) offset we applied:
texcoord.x += 0.25 * resolution.x;
// The searches are bias by 1, so adjust the coords accordingly:
texcoord.x += resolution.x;
// Disambiguate the length added by the last step:
texcoord.x += 2.0 * resolution.x; // Undo last step
texcoord.x -= resolution.x * SMAASearchLength(searchTex, e, 0.0, 0.5);
return texcoord.x;
}
float SMAASearchXRight( sampler2D edgesTex, sampler2D searchTex, vec2 texcoord, float end ) {
vec2 e = vec2( 0.0, 1.0 );
for ( int i = 0; i < SMAA_MAX_SEARCH_STEPS; i ++ ) { // WebGL port note: Changed while to for
e = texture2D( edgesTex, texcoord, 0.0 ).rg;
texcoord += vec2( 2.0, 0.0 ) * resolution;
if ( ! ( texcoord.x < end && e.g > 0.8281 && e.r == 0.0 ) ) break;
}
texcoord.x -= 0.25 * resolution.x;
texcoord.x -= resolution.x;
texcoord.x -= 2.0 * resolution.x;
texcoord.x += resolution.x * SMAASearchLength( searchTex, e, 0.5, 0.5 );
return texcoord.x;
}
float SMAASearchYUp( sampler2D edgesTex, sampler2D searchTex, vec2 texcoord, float end ) {
vec2 e = vec2( 1.0, 0.0 );
for ( int i = 0; i < SMAA_MAX_SEARCH_STEPS; i ++ ) { // WebGL port note: Changed while to for
e = texture2D( edgesTex, texcoord, 0.0 ).rg;
texcoord += vec2( 0.0, 2.0 ) * resolution; // WebGL port note: Changed sign
if ( ! ( texcoord.y > end && e.r > 0.8281 && e.g == 0.0 ) ) break;
}
texcoord.y -= 0.25 * resolution.y; // WebGL port note: Changed sign
texcoord.y -= resolution.y; // WebGL port note: Changed sign
texcoord.y -= 2.0 * resolution.y; // WebGL port note: Changed sign
texcoord.y += resolution.y * SMAASearchLength( searchTex, e.gr, 0.0, 0.5 ); // WebGL port note: Changed sign
return texcoord.y;
}
float SMAASearchYDown( sampler2D edgesTex, sampler2D searchTex, vec2 texcoord, float end ) {
vec2 e = vec2( 1.0, 0.0 );
for ( int i = 0; i < SMAA_MAX_SEARCH_STEPS; i ++ ) { // WebGL port note: Changed while to for
e = texture2D( edgesTex, texcoord, 0.0 ).rg;
texcoord -= vec2( 0.0, 2.0 ) * resolution; // WebGL port note: Changed sign
if ( ! ( texcoord.y < end && e.r > 0.8281 && e.g == 0.0 ) ) break;
}
texcoord.y += 0.25 * resolution.y; // WebGL port note: Changed sign
texcoord.y += resolution.y; // WebGL port note: Changed sign
texcoord.y += 2.0 * resolution.y; // WebGL port note: Changed sign
texcoord.y -= resolution.y * SMAASearchLength( searchTex, e.gr, 0.5, 0.5 ); // WebGL port note: Changed sign
return texcoord.y;
}
vec2 SMAAArea( sampler2D areaTex, vec2 dist, float e1, float e2, float offset ) {
// Rounding prevents precision errors of bilinear filtering:
vec2 texcoord = float( SMAA_AREATEX_MAX_DISTANCE ) * round( 4.0 * vec2( e1, e2 ) ) + dist;
// We do a scale and bias for mapping to texel space:
texcoord = SMAA_AREATEX_PIXEL_SIZE * texcoord + ( 0.5 * SMAA_AREATEX_PIXEL_SIZE );
// Move to proper place, according to the subpixel offset:
texcoord.y += SMAA_AREATEX_SUBTEX_SIZE * offset;
return texture2D( areaTex, texcoord, 0.0 ).rg;
}
vec4 SMAABlendingWeightCalculationPS( vec2 texcoord, vec2 pixcoord, vec4 offset[ 3 ], sampler2D edgesTex, sampler2D areaTex, sampler2D searchTex, ivec4 subsampleIndices ) {
vec4 weights = vec4( 0.0, 0.0, 0.0, 0.0 );
vec2 e = texture2D( edgesTex, texcoord ).rg;
if ( e.g > 0.0 ) { // Edge at north
vec2 d;
// Find the distance to the left:
vec2 coords;
coords.x = SMAASearchXLeft( edgesTex, searchTex, offset[ 0 ].xy, offset[ 2 ].x );
coords.y = offset[ 1 ].y; // offset[1].y = texcoord.y - 0.25 * resolution.y (@CROSSING_OFFSET)
d.x = coords.x;
// Now fetch the left crossing edges, two at a time using bilinear
// filtering. Sampling at -0.25 (see @CROSSING_OFFSET) enables to
// discern what value each edge has:
float e1 = texture2D( edgesTex, coords, 0.0 ).r;
// Find the distance to the right:
coords.x = SMAASearchXRight( edgesTex, searchTex, offset[ 0 ].zw, offset[ 2 ].y );
d.y = coords.x;
// We want the distances to be in pixel units (doing this here allow to
// better interleave arithmetic and memory accesses):
d = d / resolution.x - pixcoord.x;
// SMAAArea below needs a sqrt, as the areas texture is compressed
// quadratically:
vec2 sqrt_d = sqrt( abs( d ) );
// Fetch the right crossing edges:
coords.y -= 1.0 * resolution.y; // WebGL port note: Added
float e2 = SMAASampleLevelZeroOffset( edgesTex, coords, ivec2( 1, 0 ) ).r;
// Ok, we know how this pattern looks like, now it is time for getting
// the actual area:
weights.rg = SMAAArea( areaTex, sqrt_d, e1, e2, float( subsampleIndices.y ) );
}
if ( e.r > 0.0 ) { // Edge at west
vec2 d;
// Find the distance to the top:
vec2 coords;
coords.y = SMAASearchYUp( edgesTex, searchTex, offset[ 1 ].xy, offset[ 2 ].z );
coords.x = offset[ 0 ].x; // offset[1].x = texcoord.x - 0.25 * resolution.x;
d.x = coords.y;
// Fetch the top crossing edges:
float e1 = texture2D( edgesTex, coords, 0.0 ).g;
// Find the distance to the bottom:
coords.y = SMAASearchYDown( edgesTex, searchTex, offset[ 1 ].zw, offset[ 2 ].w );
d.y = coords.y;
// We want the distances to be in pixel units:
d = d / resolution.y - pixcoord.y;
// SMAAArea below needs a sqrt, as the areas texture is compressed
// quadratically:
vec2 sqrt_d = sqrt( abs( d ) );
// Fetch the bottom crossing edges:
coords.y -= 1.0 * resolution.y; // WebGL port note: Added
float e2 = SMAASampleLevelZeroOffset( edgesTex, coords, ivec2( 0, 1 ) ).g;
// Get the area for this direction:
weights.ba = SMAAArea( areaTex, sqrt_d, e1, e2, float( subsampleIndices.x ) );
}
return weights;
}
void main() {
gl_FragColor = SMAABlendingWeightCalculationPS( vUv, vPixcoord, vOffset, tDiffuse, tArea, tSearch, ivec4( 0.0 ) );
}`
};
const SMAABlendShader = {
name: 'SMAABlendShader',
uniforms: {
'tDiffuse': { value: null },
'tColor': { value: null },
'resolution': { value: new Vector2( 1 / 1024, 1 / 512 ) }
},
vertexShader: /* glsl */`
uniform vec2 resolution;
varying vec2 vUv;
varying vec4 vOffset[ 2 ];
void SMAANeighborhoodBlendingVS( vec2 texcoord ) {
vOffset[ 0 ] = texcoord.xyxy + resolution.xyxy * vec4( -1.0, 0.0, 0.0, 1.0 ); // WebGL port note: Changed sign in W component
vOffset[ 1 ] = texcoord.xyxy + resolution.xyxy * vec4( 1.0, 0.0, 0.0, -1.0 ); // WebGL port note: Changed sign in W component
}
void main() {
vUv = uv;
SMAANeighborhoodBlendingVS( vUv );
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader: /* glsl */`
uniform sampler2D tDiffuse;
uniform sampler2D tColor;
uniform vec2 resolution;
varying vec2 vUv;
varying vec4 vOffset[ 2 ];
vec4 SMAANeighborhoodBlendingPS( vec2 texcoord, vec4 offset[ 2 ], sampler2D colorTex, sampler2D blendTex ) {
// Fetch the blending weights for current pixel:
vec4 a;
a.xz = texture2D( blendTex, texcoord ).xz;
a.y = texture2D( blendTex, offset[ 1 ].zw ).g;
a.w = texture2D( blendTex, offset[ 1 ].xy ).a;
// Is there any blending weight with a value greater than 0.0?
if ( dot(a, vec4( 1.0, 1.0, 1.0, 1.0 )) < 1e-5 ) {
return texture2D( colorTex, texcoord, 0.0 );
} else {
// Up to 4 lines can be crossing a pixel (one through each edge). We
// favor blending by choosing the line with the maximum weight for each
// direction:
vec2 offset;
offset.x = a.a > a.b ? a.a : -a.b; // left vs. right
offset.y = a.g > a.r ? -a.g : a.r; // top vs. bottom // WebGL port note: Changed signs
// Then we go in the direction that has the maximum weight:
if ( abs( offset.x ) > abs( offset.y )) { // horizontal vs. vertical
offset.y = 0.0;
} else {
offset.x = 0.0;
}
// Fetch the opposite color and lerp by hand:
vec4 C = texture2D( colorTex, texcoord, 0.0 );
texcoord += sign( offset ) * resolution;
vec4 Cop = texture2D( colorTex, texcoord, 0.0 );
float s = abs( offset.x ) > abs( offset.y ) ? abs( offset.x ) : abs( offset.y );
// WebGL port note: Added gamma correction
C.xyz = pow(C.xyz, vec3(2.2));
Cop.xyz = pow(Cop.xyz, vec3(2.2));
vec4 mixed = mix(C, Cop, s);
mixed.xyz = pow(mixed.xyz, vec3(1.0 / 2.2));
return mixed;
}
}
void main() {
gl_FragColor = SMAANeighborhoodBlendingPS( vUv, vOffset, tColor, tDiffuse );
}`
};
export { SMAAEdgesShader, SMAAWeightsShader, SMAABlendShader };
@@ -1,6 +1,3 @@
// MIT License
// Copyright © 2010-2023 three.js authors
import {
BufferAttribute,
BufferGeometry,
@@ -307,6 +304,7 @@ function mergeAttributes( attributes ) {
let TypedArray;
let itemSize;
let normalized;
let gpuType = - 1;
let arrayLength = 0;
for ( let i = 0; i < attributes.length; ++ i ) {
@@ -344,6 +342,14 @@ function mergeAttributes( attributes ) {
}
if ( gpuType === - 1 ) gpuType = attribute.gpuType;
if ( gpuType !== attribute.gpuType ) {
console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.gpuType must be consistent across matching attributes.' );
return null;
}
arrayLength += attribute.array.length;
}
@@ -359,7 +365,14 @@ function mergeAttributes( attributes ) {
}
return new BufferAttribute( array, itemSize, normalized );
const result = new BufferAttribute( array, itemSize, normalized );
if ( gpuType !== undefined ) {
result.gpuType = gpuType;
}
return result;
}
@@ -542,7 +555,7 @@ export function deinterleaveGeometry( geometry ) {
}
/**
* @param {Array<BufferGeometry>} geometry
* @param {BufferGeometry} geometry
* @return {number}
*/
function estimateBytesUsed( geometry ) {
@@ -618,8 +631,10 @@ function mergeVertices( geometry, tolerance = 1e-4 ) {
}
// convert the error tolerance to an amount of decimal places to truncate to
const decimalShift = Math.log10( 1 / tolerance );
const shiftMultiplier = Math.pow( 10, decimalShift );
const halfTolerance = tolerance * 0.5;
const exponent = Math.log10( 1 / tolerance );
const hashMultiplier = Math.pow( 10, exponent );
const hashAdditive = halfTolerance * hashMultiplier;
for ( let i = 0; i < vertexCount; i ++ ) {
const index = indices ? indices.getX( i ) : i;
@@ -635,7 +650,7 @@ function mergeVertices( geometry, tolerance = 1e-4 ) {
for ( let k = 0; k < itemSize; k ++ ) {
// double tilde truncates the decimal value
hash += `${ ~ ~ ( attribute[ getters[ k ] ]( index ) * shiftMultiplier ) },`;
hash += `${ ~ ~ ( attribute[ getters[ k ] ]( index ) * hashMultiplier + hashAdditive ) },`;
}
@@ -1216,14 +1231,21 @@ function mergeGroups( geometry ) {
}
// Creates a new, non-indexed geometry with smooth normals everywhere except faces that meet at
// an angle greater than the crease angle.
/**
* Modifies the supplied geometry if it is non-indexed, otherwise creates a new,
* non-indexed geometry. Returns the geometry with smooth normals everywhere except
* faces that meet at an angle greater than the crease angle.
*
* @param {BufferGeometry} geometry
* @param {number} [creaseAngle]
* @return {BufferGeometry}
*/
function toCreasedNormals( geometry, creaseAngle = Math.PI / 3 /* 60 degrees */ ) {
const creaseDot = Math.cos( creaseAngle );
const hashMultiplier = ( 1 + 1e-10 ) * 1e2;
// reusable vertors
// reusable vectors
const verts = [ new Vector3(), new Vector3(), new Vector3() ];
const tempVec1 = new Vector3();
const tempVec2 = new Vector3();
@@ -1240,7 +1262,9 @@ function toCreasedNormals( geometry, creaseAngle = Math.PI / 3 /* 60 degrees */
}
const resultGeometry = geometry.toNonIndexed();
// BufferGeometry.toNonIndexed() warns if the geometry is non-indexed
// and returns the original geometry
const resultGeometry = geometry.index ? geometry.toNonIndexed() : geometry;
const posAttr = resultGeometry.attributes.position;
const vertexMap = {};
@@ -1,6 +1,3 @@
// MIT License
// Copyright © 2010-2023 three.js authors
import {
AnimationClip,
AnimationMixer,
+12 -8
View File
@@ -3,12 +3,16 @@
* @module ThreeAddons
*/
export {
GLTFLoader,
} from "./examples/jsm/loaders/GLTFLoader";
export {
DRACOLoader,
} from "./examples/jsm/loaders/DRACOLoader";
export { GLTFLoader } from "./examples/jsm/loaders/GLTFLoader";
export { DRACOLoader } from "./examples/jsm/loaders/DRACOLoader";
export * as SkeletonUtils from "./examples/jsm/utils/SkeletonUtils";
export { EffectComposer } from "./examples/jsm/postprocessing/EffectComposer";
export { RenderPass } from "./examples/jsm/postprocessing/RenderPass";
export { ShaderPass } from "./examples/jsm/postprocessing/ShaderPass";
export { SMAAPass } from "./examples/jsm/postprocessing/SMAAPass";
export { OutputPass } from "./examples/jsm/postprocessing/OutputPass";
export { UnrealBloomPass } from "./examples/jsm/postprocessing/UnrealBloomPass";
export { BrightnessContrastShader } from "./examples/jsm/shaders/BrightnessContrastShader";
export { HueSaturationShader } from "./examples/jsm/shaders/HueSaturationShader";
export { ExposureShader } from "./examples/jsm/shaders/ExposureShader";
+4 -3
View File
@@ -62,7 +62,7 @@
"semver": "7.0.0",
"slugs": "0.1.3",
"source-map-explorer": "^2.0.1",
"three": "0.152.0",
"three": "0.160.0",
"url-search-params": "^1.0.2",
"xxhashjs": "^0.2.2"
},
@@ -32977,8 +32977,9 @@
}
},
"node_modules/three": {
"version": "0.152.0",
"license": "MIT"
"version": "0.160.0",
"resolved": "https://registry.npmjs.org/three/-/three-0.160.0.tgz",
"integrity": "sha512-DLU8lc0zNIPkM7rH5/e1Ks1Z8tWCGRq6g8mPowdDJpw1CFBJMU7UoJjC6PefXW7z//SSl0b2+GCw14LB+uDhng=="
},
"node_modules/throat": {
"version": "6.0.2",
+1 -1
View File
@@ -92,7 +92,7 @@
"semver": "7.0.0",
"slugs": "0.1.3",
"source-map-explorer": "^2.0.1",
"three": "0.152.0",
"three": "0.160.0",
"url-search-params": "^1.0.2",
"xxhashjs": "^0.2.2"
},
+1
View File
@@ -178,6 +178,7 @@ export default class InstancesEditor extends Component<Props> {
const threeRenderer = new THREE.WebGLRenderer({
canvas: gameCanvas,
});
threeRenderer.useLegacyLights = true;
threeRenderer.autoClear = false;
threeRenderer.setSize(this.props.width, this.props.height);
@@ -155,7 +155,7 @@ const removeMetalness = (material: THREE.Material): void => {
}
};
const removeMetalnessFromMesh = (node: THREE.Object3D<THREE.Event>): void => {
const removeMetalnessFromMesh = (node: THREE.Object3D): void => {
const mesh = (node: THREE.Mesh);
if (!mesh.material) {
return;
@@ -169,9 +169,8 @@ const removeMetalnessFromMesh = (node: THREE.Object3D<THREE.Event>): void => {
}
};
const traverseToRemoveMetalnessFromMeshes = (
node: THREE.Object3D<THREE.Event>
) => node.traverse(removeMetalnessFromMesh);
const traverseToRemoveMetalnessFromMeshes = (node: THREE.Object3D) =>
node.traverse(removeMetalnessFromMesh);
export const readEmbeddedResourcesMapping = (
resource: gdResource