mirror of
https://github.com/4ian/GDevelop.git
synced 2026-10-12 02:01:17 +00:00
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:
@@ -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 {}
|
||||
})();
|
||||
}
|
||||
})()
|
||||
);
|
||||
}
|
||||
@@ -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 {}
|
||||
})();
|
||||
}
|
||||
})()
|
||||
);
|
||||
}
|
||||
@@ -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 {}
|
||||
})();
|
||||
}
|
||||
})()
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+23
-37
@@ -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
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
Generated
+4
-3
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user