Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement: Tunnel Vision #450

Merged
merged 38 commits into from
Sep 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
99adaa7
added tunnel effect into hub
tony056 Jun 27, 2018
0bd5a32
working tunnel effect for 2d, but needs to modify to work in VR
tony056 Jun 27, 2018
ea6f19e
image version of tunnel vision
tony056 Jul 2, 2018
aa824ac
moved tunnel-effect to 'systems' folder.
tony056 Jul 3, 2018
682f89f
removed image approach for tunnel vision.
tony056 Jul 3, 2018
3b84442
added fadingEffect for entering/exiting tunnel vision.
tony056 Jul 3, 2018
661d0c0
changed the path of 'tunnel-effect'.
tony056 Jul 3, 2018
9988451
included post processing essential files.
tony056 Jul 3, 2018
c0d6d55
applied vignetteShader to two eyes.
tony056 Jul 3, 2018
22bcc2f
detect the movement by the change of the camera's position.
tony056 Jul 4, 2018
c834cdf
moved two tunnels closer to each other and combined left and right vi…
tony056 Jul 6, 2018
6e7f751
used the character's velocity to trigger tunnel effect.
tony056 Jul 6, 2018
690d0c5
renamed the func name.
tony056 Jul 6, 2018
6e56358
Adjusted the offset dynamically to make sure two tunnels won't overla…
tony056 Jul 6, 2018
912139a
cleaned up the event listener.
tony056 Jul 6, 2018
4bb5b0f
moved postprocessing & shader files to utils.
tony056 Jul 6, 2018
bdb4b36
deleted unused images.
tony056 Jul 6, 2018
009f802
added comments at some unclear parts.
tony056 Jul 6, 2018
76813ee
Merge branch 'master' into enhancement/locomotion
tony056 Jul 6, 2018
41e267e
style modification.
tony056 Jul 6, 2018
25f0d9c
Merge branch 'enhancement/locomotion' of github.com:mozilla/hubs into…
tony056 Jul 6, 2018
8e0f561
added missing parameters.
tony056 Jul 6, 2018
31914e6
checkout back to previous one commit to undo teleport parameter changes.
tony056 Aug 3, 2018
a5eb72a
enabled the effect while it's in vr mode.
tony056 Aug 6, 2018
d3414d9
Merge branch 'master' of github.com:mozilla/hubs into enhancement/loc…
tony056 Aug 6, 2018
7f19a1d
avoid creating a new function everytime we switched to the postproces…
tony056 Aug 8, 2018
8cdab8b
removed unused var.
tony056 Aug 8, 2018
da5d4bc
disabled tunnel while exit vr.
tony056 Aug 8, 2018
d9c195d
fading out when the v is 0.
tony056 Aug 8, 2018
c3f1976
renamed unit to ratio.
tony056 Aug 8, 2018
08d4470
added disableTunnel parameter to disable the effect with qs_truthy.
tony056 Aug 9, 2018
7a6090b
Add some information about where the effect code came from.
johnshaughnessy Aug 16, 2018
a8203ac
Minor edits. Only call _initComposer once.
johnshaughnessy Aug 16, 2018
a304b8a
init deltaR & deltaS. added disableTuneel param in README.
tony056 Aug 17, 2018
7b1fe60
moved deltaR and deltaS init to the init func.
tony056 Aug 20, 2018
487ae33
deltaR should be TARGET_RADIUS - value of the radius while the user s…
tony056 Aug 20, 2018
c2440b8
Merge branch 'master' into enhancement/locomotion
johnshaughnessy Sep 7, 2018
b52512d
Fix issue where tunnel would "pop" at the end
johnshaughnessy Sep 7, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ This will allow the CSP checks to pass that are served up by Reticulum so you ca
- `disable_telemetry` - If `true` disables Sentry telemetry.
- `log_filter` - A `debug` style filter for setting the logging level.
- `debug` - If `true` performs verbose logging of Janus and NAF traffic.
- `disableTunnel` - Tunnel vision is on by default. Disable the tunnel vision by this parameter.

## Additional Resources

Expand Down
1 change: 1 addition & 0 deletions src/hub.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ import "./components/cardboard-controls";
import "./components/cursor-controller";

import "./components/nav-mesh-helper";
import "./systems/tunnel-effect";

import "./components/tools/pen";
import "./components/tools/networked-drawing";
Expand Down
163 changes: 163 additions & 0 deletions src/systems/tunnel-effect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import "../utils/postprocessing/EffectComposer";
import "../utils/postprocessing/RenderPass";
import "../utils/postprocessing/ShaderPass";
import "../utils/postprocessing/MaskPass";
import "../utils/shaders/CopyShader";
import "../utils/shaders/VignetteShader";
import qsTruthy from "../utils/qs_truthy";

const disabledByQueryString = qsTruthy("disableTunnel");
const CLAMP_SPEED = 0.01;
const CLAMP_RADIUS = 0.001;
const NO_TUNNEL_RADIUS = 10.0;
const NO_TUNNEL_SOFTNESS = 0.0;

function lerp(start, end, t) {
return (1 - t) * start + t * end;
}

function f(t) {
const x = t - 1;
return 1 + x * x * x * x * x;
}

AFRAME.registerSystem("tunneleffect", {
schema: {
targetComponent: { type: "string", default: "character-controller" },
radius: { type: "number", default: 1.0, min: 0.25 },
minRadius: { type: "number", default: 0.25, min: 0.1 },
maxSpeed: { type: "number", default: 0.5, min: 0.1 },
softest: { type: "number", default: 0.1, min: 0.0 },
opacity: { type: "number", default: 1, min: 0.0 }
},

init: function() {
this.scene = this.el;
this.isMoving = false;
this.isVR = false;
this.dt = 0;
this.isPostProcessingReady = false;
this.characterEl = document.querySelector(`a-entity[${this.data.targetComponent}]`);
if (this.characterEl) {
this._initPostProcessing = this._initPostProcessing.bind(this);
this.characterEl.addEventListener("componentinitialized", this._initPostProcessing);
} else {
console.warn("Could not find target component.");
}
this._enterVR = this._enterVR.bind(this);
this._exitVR = this._exitVR.bind(this);
this.scene.addEventListener("enter-vr", this._enterVR);
this.scene.addEventListener("exit-vr", this._exitVR);
},

pause: function() {
if (!this.characterEl) {
return;
}
this.characterEl.removeEventListener("componentinitialized", this._initPostProcessing);
this.scene.removeEventListener("enter-vr", this._enterVR);
this.scene.removeEventListener("exit-vr", this._exitVR);
},

play: function() {
this.scene.addEventListener("enter-vr", this._enterVR);
this.scene.addEventListener("exit-vr", this._exitVR);
},

tick: function(t, dt) {
this.dt = dt;

if (disabledByQueryString || !this.isPostProcessingReady || !this.isVR) {
return;
}

const { maxSpeed, minRadius, softest } = this.data;
const characterSpeed = this.characterComponent.velocity.length();
const shaderRadius = this.vignettePass.uniforms["radius"].value || NO_TUNNEL_RADIUS;
if (!this.enabled && characterSpeed > CLAMP_SPEED) {
this.enabled = true;
this._bindRenderFunc();
} else if (
this.enabled &&
characterSpeed < CLAMP_SPEED &&
Math.abs(NO_TUNNEL_RADIUS - shaderRadius) < CLAMP_RADIUS
) {
this.enabled = false;
this._exitTunnel();
}
if (this.enabled) {
const clampedSpeed = characterSpeed > maxSpeed ? maxSpeed : characterSpeed;
const speedRatio = clampedSpeed / maxSpeed;
this.targetRadius = lerp(NO_TUNNEL_RADIUS, minRadius, f(speedRatio));
this.targetSoftness = lerp(NO_TUNNEL_SOFTNESS, softest, f(speedRatio));
this._updateVignettePass(this.targetRadius, this.targetSoftness, this.data.opacity);
}
},

_exitTunnel: function() {
this.scene.renderer.render = this.originalRenderFunc;
this.isMoving = false;
},

_initPostProcessing: function(event) {
if (event.detail.name === this.data.targetComponent) {
this.characterEl.removeEventListener("componentinitialized", this._initPostProcessing);
this.characterComponent = this.characterEl.components[this.data.targetComponent];
this._initComposer();
}
},

_enterVR: function() {
this.isVR = true; //TODO: This is called in 2D mode when you press "f", which is bad
},

_exitVR: function() {
this._exitTunnel();
this.isVR = false;
},

_initComposer: function() {
this.renderer = this.scene.renderer;
this.camera = this.scene.camera;
this.originalRenderFunc = this.scene.renderer.render;
this.isDigest = false;
const render = this.scene.renderer.render;
const system = this;
this.postProcessingRenderFunc = function() {
if (system.isDigest) {
render.apply(this, arguments);
} else {
system.isDigest = true;
system.composer.render(system.dt);
system.isDigest = false;
}
};
this.composer = new THREE.EffectComposer(this.renderer);
this.composer.resize();
this.scenePass = new THREE.RenderPass(this.scene.object3D, this.camera);
this.vignettePass = new THREE.ShaderPass(THREE.VignetteShader);
this._updateVignettePass(this.data.radius, this.data.softness, this.data.opacity);
this.composer.addPass(this.scenePass);
this.composer.addPass(this.vignettePass);
this.isPostProcessingReady = true;
},

_updateVignettePass: function(radius, softness, opacity) {
const { width, height } = this.renderer.getSize();
const pixelRatio = this.renderer.getPixelRatio();
this.vignettePass.uniforms["radius"].value = radius;
this.vignettePass.uniforms["softness"].value = softness;
this.vignettePass.uniforms["opacity"].value = opacity;
this.vignettePass["resolution"] = new THREE.Uniform(new THREE.Vector2(width * pixelRatio, height * pixelRatio));
if (!this.vignettePass.renderToScreen) {
this.vignettePass.renderToScreen = true;
}
},

/**
* use the render func of the effect composer when we need the postprocessing
*/
_bindRenderFunc: function() {
this.scene.renderer.render = this.postProcessingRenderFunc;
}
});
167 changes: 167 additions & 0 deletions src/utils/postprocessing/EffectComposer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
THREE.EffectComposer = function(renderer, renderTarget) {
this.renderer = renderer;
this.delta = 0;
window.addEventListener("vrdisplaypresentchange", this.resize.bind(this));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to disable this effect when not in VR.


if (renderTarget === undefined) {
const parameters = {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
format: THREE.RGBAFormat,
stencilBuffer: false
};

const size = renderer.getDrawingBufferSize();
renderTarget = new THREE.WebGLRenderTarget(size.width, size.height, parameters);
renderTarget.texture.name = "EffectComposer.rt1";
}

this.renderTarget1 = renderTarget;
this.renderTarget2 = renderTarget.clone();
this.renderTarget2.texture.name = "EffectComposer.rt2";

this.writeBuffer = this.renderTarget1;
this.readBuffer = this.renderTarget2;

this.passes = [];
this.maskActive = false;

// dependencies

if (THREE.CopyShader === undefined) {
console.error("THREE.EffectComposer relies on THREE.CopyShader");
}

if (THREE.ShaderPass === undefined) {
console.error("THREE.EffectComposer relies on THREE.ShaderPass");
}

this.copyPass = new THREE.ShaderPass(THREE.CopyShader);
};

Object.assign(THREE.EffectComposer.prototype, {
swapBuffers: function(pass) {
if (pass.needsSwap) {
if (this.maskActive) {
const context = this.renderer.context;
context.stencilFunc(context.NOTEQUAL, 1, 0xffffffff);
this.copyPass.render(this.renderer, this.writeBuffer, this.readBuffer, this.delta);
context.stencilFunc(context.EQUAL, 1, 0xffffffff);
}

const tmp = this.readBuffer;
this.readBuffer = this.writeBuffer;
this.writeBuffer = tmp;
}

if (THREE.MaskPass !== undefined) {
if (pass instanceof THREE.MaskPass) {
this.maskActive = true;
} else if (pass instanceof THREE.ClearMaskPass) {
this.maskActive = false;
}
}
},

addPass: function(pass) {
this.passes.push(pass);
const size = this.renderer.getDrawingBufferSize();
pass.setSize(size.width, size.height);
},

insertPass: function(pass, index) {
this.passes.splice(index, 0, pass);
},

render: function(delta, starti) {
const maskActive = this.maskActive;
let pass;
let i;
const il = this.passes.length;
const scope = this;
let currentOnAfterRender;
this.delta = delta;

for (i = starti || 0; i < il; i++) {
pass = this.passes[i];
if (pass.enabled === false) continue;

// If VR mode is enabled and rendering the whole scene is required.
// The pass renders the scene and and postprocessing is resumed before
// submitting the frame to the headset by using the onAfterRender callback.
if (this.renderer.vr.enabled && pass.scene) {
currentOnAfterRender = pass.scene.onAfterRender;
pass.scene.onAfterRender = function() {
// Disable stereo rendering when doing postprocessing
// on a render target.
scope.renderer.vr.enabled = false;
scope.render(delta, i + 1, maskActive);

// Renable vr mode.
scope.renderer.vr.enabled = true;
};

pass.render(this.renderer, this.writeBuffer, this.readBuffer);

// Restore onAfterRender
pass.scene.onAfterRender = currentOnAfterRender;
this.swapBuffers(pass);
return;
}

pass.render(this.renderer, this.writeBuffer, this.readBuffer);
this.swapBuffers(pass);
}
},

reset: function(renderTarget) {
if (renderTarget === undefined) {
const size = this.renderer.getDrawingBufferSize();
renderTarget = this.renderTarget1.clone();
renderTarget.setSize(size.width, size.height);
}

this.renderTarget1.dispose();
this.renderTarget2.dispose();
this.renderTarget1 = renderTarget;
this.renderTarget2 = renderTarget.clone();

this.writeBuffer = this.renderTarget1;
this.readBuffer = this.renderTarget2;
},

setSize: function(width, height) {
this.renderTarget1.setSize(width, height);
this.renderTarget2.setSize(width, height);
for (let i = 0; i < this.passes.length; i++) {
this.passes[i].setSize(width, height);
}
},

resize: function() {
const rendererSize = this.renderer.getDrawingBufferSize();
this.setSize(rendererSize.width, rendererSize.height);
}
});

THREE.Pass = function() {
// 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.renderToScreen = false;
};

Object.assign(THREE.Pass.prototype, {
setSize: function() {},

render: function() {
console.error("THREE.Pass: .render() must be implemented in derived pass.");
}
});
Loading