diff --git a/package-lock.json b/package-lock.json
index 45cbc96d867..86327c6ea82 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4653,9 +4653,9 @@
       }
     },
     "gl-plot3d": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/gl-plot3d/-/gl-plot3d-2.3.0.tgz",
-      "integrity": "sha512-qg269QiLpaw16d2D5Gz9fa8vsLcA8kbX/cv1u9S7BsH6jD9qGYxsY8iWJ8ea9/68WhPS5En2kUavkXINkmHsOQ==",
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/gl-plot3d/-/gl-plot3d-2.4.0.tgz",
+      "integrity": "sha512-ZPs7gvWaCqK99GXoB0XJTMTLpChB/CiDUt3MthIawVlFhknSJLNPAJSbUU3f4pWzKCbbGtVARQr/i6XdM3MnKA==",
       "requires": {
         "3d-view": "^2.0.0",
         "a-big-triangle": "^1.0.3",
diff --git a/package.json b/package.json
index 528ff8959f7..52c6dc3fe7c 100644
--- a/package.json
+++ b/package.json
@@ -82,7 +82,7 @@
     "gl-mat4": "^1.2.0",
     "gl-mesh3d": "^2.2.0",
     "gl-plot2d": "^1.4.2",
-    "gl-plot3d": "^2.3.0",
+    "gl-plot3d": "^2.4.0",
     "gl-pointcloud2d": "^1.0.2",
     "gl-scatter3d": "^1.2.2",
     "gl-select-box": "^1.0.3",
@@ -93,6 +93,7 @@
     "glslify": "^7.0.0",
     "has-hover": "^1.0.1",
     "has-passive-events": "^1.0.0",
+    "is-mobile": "^2.1.0",
     "mapbox-gl": "1.3.2",
     "matrix-camera-controller": "^2.1.3",
     "mouse-change": "^1.4.0",
diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js
index 7385e8cf1c4..4f12b0dc4da 100644
--- a/src/plots/gl3d/scene.js
+++ b/src/plots/gl3d/scene.js
@@ -15,6 +15,7 @@ var createPlot = glPlot3d.createScene;
 
 var getContext = require('webgl-context');
 var passiveSupported = require('has-passive-events');
+var isMobile = require('is-mobile')({ tablet: true });
 
 var Registry = require('../../registry');
 var Lib = require('../../lib');
@@ -33,7 +34,233 @@ var computeTickMarks = require('./layout/tick_marks');
 
 var STATIC_CANVAS, STATIC_CONTEXT;
 
-function render(scene) {
+function Scene(options, fullLayout) {
+    // create sub container for plot
+    var sceneContainer = document.createElement('div');
+    var plotContainer = options.container;
+
+    // keep a ref to the graph div to fire hover+click events
+    this.graphDiv = options.graphDiv;
+
+    // create SVG container for hover text
+    var svgContainer = document.createElementNS(
+        'http://www.w3.org/2000/svg',
+        'svg');
+    svgContainer.style.position = 'absolute';
+    svgContainer.style.top = svgContainer.style.left = '0px';
+    svgContainer.style.width = svgContainer.style.height = '100%';
+    svgContainer.style['z-index'] = 20;
+    svgContainer.style['pointer-events'] = 'none';
+    sceneContainer.appendChild(svgContainer);
+    this.svgContainer = svgContainer;
+
+    // Tag the container with the sceneID
+    sceneContainer.id = options.id;
+    sceneContainer.style.position = 'absolute';
+    sceneContainer.style.top = sceneContainer.style.left = '0px';
+    sceneContainer.style.width = sceneContainer.style.height = '100%';
+    plotContainer.appendChild(sceneContainer);
+
+    this.fullLayout = fullLayout;
+    this.id = options.id || 'scene';
+    this.fullSceneLayout = fullLayout[this.id];
+
+    // Saved from last call to plot()
+    this.plotArgs = [ [], {}, {} ];
+
+    /*
+     * Move this to calc step? Why does it work here?
+     */
+    this.axesOptions = createAxesOptions(fullLayout, fullLayout[this.id]);
+    this.spikeOptions = createSpikeOptions(fullLayout[this.id]);
+    this.container = sceneContainer;
+    this.staticMode = !!options.staticPlot;
+    this.pixelRatio = this.pixelRatio || options.plotGlPixelRatio || 2;
+
+    // Coordinate rescaling
+    this.dataScale = [1, 1, 1];
+
+    this.contourLevels = [ [], [], [] ];
+
+    this.convertAnnotations = Registry.getComponentMethod('annotations3d', 'convert');
+    this.drawAnnotations = Registry.getComponentMethod('annotations3d', 'draw');
+
+    this.initializeGLPlot();
+}
+
+var proto = Scene.prototype;
+
+proto.tryCreatePlot = function() {
+    var scene = this;
+    var opts = {
+        canvas: scene.canvas,
+        gl: scene.gl,
+        glOptions: {
+            preserveDrawingBuffer: isMobile,
+            premultipliedAlpha: true,
+            antialias: true
+        },
+        container: scene.container,
+        axes: scene.axesOptions,
+        spikes: scene.spikeOptions,
+        pickRadius: 10,
+        snapToData: true,
+        autoScale: true,
+        autoBounds: false,
+        cameraObject: scene.camera,
+        pixelRatio: scene.pixelRatio
+    };
+
+    // for static plots, we reuse the WebGL context
+    //  as WebKit doesn't collect them reliably
+    if(scene.staticMode) {
+        if(!STATIC_CONTEXT) {
+            STATIC_CANVAS = document.createElement('canvas');
+            STATIC_CONTEXT = getContext({
+                canvas: STATIC_CANVAS,
+                preserveDrawingBuffer: true,
+                premultipliedAlpha: true,
+                antialias: true
+            });
+            if(!STATIC_CONTEXT) {
+                throw new Error('error creating static canvas/context for image server');
+            }
+        }
+
+        opts.gl = STATIC_CONTEXT;
+        opts.canvas = STATIC_CANVAS;
+    }
+
+    var failed = 0;
+
+    try {
+        scene.glplot = createPlot(opts);
+    } catch(e) {
+        failed++;
+        try { // try second time to fix issue with Chrome 77 https://github.com/plotly/plotly.js/issues/4233
+            scene.glplot = createPlot(opts);
+        } catch(e) {
+            failed++;
+        }
+    }
+
+    return failed < 2;
+};
+
+proto.initializeGLCamera = function() {
+    var scene = this;
+    var cameraData = scene.fullSceneLayout.camera;
+    var isOrtho = (cameraData.projection.type === 'orthographic');
+
+    scene.camera = createCamera(scene.container, {
+        center: [cameraData.center.x, cameraData.center.y, cameraData.center.z],
+        eye: [cameraData.eye.x, cameraData.eye.y, cameraData.eye.z],
+        up: [cameraData.up.x, cameraData.up.y, cameraData.up.z],
+        _ortho: isOrtho,
+        zoomMin: 0.01,
+        zoomMax: 100,
+        mode: 'orbit'
+    });
+};
+
+proto.initializeGLPlot = function() {
+    var scene = this;
+
+    scene.initializeGLCamera();
+
+    var success = scene.tryCreatePlot();
+    /*
+    * createPlot will throw when webgl is not enabled in the client.
+    * Lets return an instance of the module with all functions noop'd.
+    * The destroy method - which will remove the container from the DOM
+    * is overridden with a function that removes the container only.
+    */
+    if(!success) return showNoWebGlMsg(scene);
+
+    // List of scene objects
+    scene.traces = {};
+
+    scene.make4thDimension();
+
+    var gd = scene.graphDiv;
+    var layout = gd.layout;
+
+    var makeUpdate = function() {
+        var update = {};
+
+        if(scene.isCameraChanged(layout)) {
+            // camera updates
+            update[scene.id + '.camera'] = scene.getCamera();
+        }
+
+        if(scene.isAspectChanged(layout)) {
+            // scene updates
+            update[scene.id + '.aspectratio'] = scene.glplot.getAspectratio();
+        }
+
+        return update;
+    };
+
+    var relayoutCallback = function(scene) {
+        if(scene.fullSceneLayout.dragmode === false) return;
+
+        var update = makeUpdate();
+        scene.saveLayout(layout);
+        scene.graphDiv.emit('plotly_relayout', update);
+    };
+
+    scene.glplot.canvas.addEventListener('mouseup', function() {
+        relayoutCallback(scene);
+    });
+
+    scene.glplot.canvas.addEventListener('wheel', function(e) {
+        if(gd._context._scrollZoom.gl3d) {
+            if(scene.camera._ortho) {
+                var s = (e.deltaX > e.deltaY) ? 1.1 : 1.0 / 1.1;
+                var o = scene.glplot.getAspectratio();
+                scene.glplot.setAspectratio({
+                    x: s * o.x,
+                    y: s * o.y,
+                    z: s * o.z
+                });
+            }
+
+            relayoutCallback(scene);
+        }
+    }, passiveSupported ? {passive: false} : false);
+
+    scene.glplot.canvas.addEventListener('mousemove', function() {
+        if(scene.fullSceneLayout.dragmode === false) return;
+        if(scene.camera.mouseListener.buttons === 0) return;
+
+        var update = makeUpdate();
+        scene.graphDiv.emit('plotly_relayouting', update);
+    });
+
+    if(!scene.staticMode) {
+        scene.glplot.canvas.addEventListener('webglcontextlost', function(event) {
+            if(gd && gd.emit) {
+                gd.emit('plotly_webglcontextlost', {
+                    event: event,
+                    layer: scene.id
+                });
+            }
+        }, false);
+    }
+
+    scene.glplot.oncontextloss = function() {
+        scene.recoverContext();
+    };
+
+    scene.glplot.onrender = function() {
+        scene.render();
+    };
+
+    return true;
+};
+
+proto.render = function() {
+    var scene = this;
     var gd = scene.graphDiv;
     var trace;
 
@@ -128,7 +355,7 @@ function render(scene) {
             }
             tx = vectorTx.join('<br>');
         } else if(trace.type === 'isosurface' || trace.type === 'volume') {
-            labels.valueLabel = Axes.tickText(scene.mockAxis, scene.mockAxis.d2l(selection.traceCoordinate[3]), 'hover').text;
+            labels.valueLabel = Axes.tickText(scene._mockAxis, scene._mockAxis.d2l(selection.traceCoordinate[3]), 'hover').text;
             vectorTx.push('value: ' + labels.valueLabel);
             if(selection.textLabel) {
                 vectorTx.push(selection.textLabel);
@@ -195,242 +422,25 @@ function render(scene) {
     }
 
     scene.drawAnnotations(scene);
-}
-
-function tryCreatePlot(scene, cameraObject, pixelRatio, canvas, gl) {
-    var glplotOptions = {
-        canvas: canvas,
-        gl: gl,
-        container: scene.container,
-        axes: scene.axesOptions,
-        spikes: scene.spikeOptions,
-        pickRadius: 10,
-        snapToData: true,
-        autoScale: true,
-        autoBounds: false,
-        cameraObject: cameraObject,
-        pixelRatio: pixelRatio
-    };
-
-    // for static plots, we reuse the WebGL context
-    //  as WebKit doesn't collect them reliably
-    if(scene.staticMode) {
-        if(!STATIC_CONTEXT) {
-            STATIC_CANVAS = document.createElement('canvas');
-            STATIC_CONTEXT = getContext({
-                canvas: STATIC_CANVAS,
-                preserveDrawingBuffer: true,
-                premultipliedAlpha: true,
-                antialias: true
-            });
-            if(!STATIC_CONTEXT) {
-                throw new Error('error creating static canvas/context for image server');
-            }
-        }
-        glplotOptions.pixelRatio = scene.pixelRatio;
-        glplotOptions.gl = STATIC_CONTEXT;
-        glplotOptions.canvas = STATIC_CANVAS;
-    }
-
-    var failed = 0;
-
-    try {
-        scene.glplot = createPlot(glplotOptions);
-    } catch(e) {
-        failed++;
-        try { // try second time to fix issue with Chrome 77 https://github.com/plotly/plotly.js/issues/4233
-            scene.glplot = createPlot(glplotOptions);
-        } catch(e) {
-            failed++;
-        }
-    }
-
-    return failed < 2;
-}
-
-function initializeGLPlot(scene, canvas, gl) {
-    scene.initializeGLCamera();
-
-    var success = tryCreatePlot(scene, scene.camera, scene.pixelRatio, canvas, gl);
-    /*
-    * createPlot will throw when webgl is not enabled in the client.
-    * Lets return an instance of the module with all functions noop'd.
-    * The destroy method - which will remove the container from the DOM
-    * is overridden with a function that removes the container only.
-    */
-    if(!success) return showNoWebGlMsg(scene);
-
-    var gd = scene.graphDiv;
-    var layout = gd.layout;
-
-    var makeUpdate = function() {
-        var update = {};
-
-        if(scene.isCameraChanged(layout)) {
-            // camera updates
-            update[scene.id + '.camera'] = scene.getCamera();
-        }
-
-        if(scene.isAspectChanged(layout)) {
-            // scene updates
-            update[scene.id + '.aspectratio'] = scene.glplot.getAspectratio();
-        }
-
-        return update;
-    };
-
-    var relayoutCallback = function(scene) {
-        if(scene.fullSceneLayout.dragmode === false) return;
-
-        var update = makeUpdate();
-        scene.saveLayout(layout);
-        scene.graphDiv.emit('plotly_relayout', update);
-    };
-
-    scene.glplot.canvas.addEventListener('mouseup', function() {
-        relayoutCallback(scene);
-    });
-
-    scene.glplot.canvas.addEventListener('wheel', function(e) {
-        if(gd._context._scrollZoom.gl3d) {
-            if(scene.glplot.camera._ortho) {
-                var s = (e.deltaX > e.deltaY) ? 1.1 : 1.0 / 1.1;
-                var o = scene.glplot.getAspectratio();
-                scene.glplot.setAspectratio({
-                    x: s * o.x,
-                    y: s * o.y,
-                    z: s * o.z
-                });
-            }
-
-            relayoutCallback(scene);
-        }
-    }, passiveSupported ? {passive: false} : false);
-
-    scene.glplot.canvas.addEventListener('mousemove', function() {
-        if(scene.fullSceneLayout.dragmode === false) return;
-        if(scene.camera.mouseListener.buttons === 0) return;
-
-        var update = makeUpdate();
-        scene.graphDiv.emit('plotly_relayouting', update);
-    });
-
-    if(!scene.staticMode) {
-        scene.glplot.canvas.addEventListener('webglcontextlost', function(event) {
-            if(gd && gd.emit) {
-                gd.emit('plotly_webglcontextlost', {
-                    event: event,
-                    layer: scene.id
-                });
-            }
-        }, false);
-    }
-
-    scene.glplot.camera = scene.camera;
-
-    scene.glplot.oncontextloss = function() {
-        scene.recoverContext();
-    };
-
-    scene.glplot.onrender = render.bind(null, scene);
-
-    // List of scene objects
-    scene.traces = {};
-
-    scene.make4thDimension();
-
-    return true;
-}
-
-function Scene(options, fullLayout) {
-    // create sub container for plot
-    var sceneContainer = document.createElement('div');
-    var plotContainer = options.container;
-
-    // keep a ref to the graph div to fire hover+click events
-    this.graphDiv = options.graphDiv;
-
-    // create SVG container for hover text
-    var svgContainer = document.createElementNS(
-        'http://www.w3.org/2000/svg',
-        'svg');
-    svgContainer.style.position = 'absolute';
-    svgContainer.style.top = svgContainer.style.left = '0px';
-    svgContainer.style.width = svgContainer.style.height = '100%';
-    svgContainer.style['z-index'] = 20;
-    svgContainer.style['pointer-events'] = 'none';
-    sceneContainer.appendChild(svgContainer);
-    this.svgContainer = svgContainer;
-
-    // Tag the container with the sceneID
-    sceneContainer.id = options.id;
-    sceneContainer.style.position = 'absolute';
-    sceneContainer.style.top = sceneContainer.style.left = '0px';
-    sceneContainer.style.width = sceneContainer.style.height = '100%';
-    plotContainer.appendChild(sceneContainer);
-
-    this.fullLayout = fullLayout;
-    this.id = options.id || 'scene';
-    this.fullSceneLayout = fullLayout[this.id];
-
-    // Saved from last call to plot()
-    this.plotArgs = [ [], {}, {} ];
-
-    /*
-     * Move this to calc step? Why does it work here?
-     */
-    this.axesOptions = createAxesOptions(fullLayout, fullLayout[this.id]);
-    this.spikeOptions = createSpikeOptions(fullLayout[this.id]);
-    this.container = sceneContainer;
-    this.staticMode = !!options.staticPlot;
-    this.pixelRatio = this.pixelRatio || options.plotGlPixelRatio || 2;
-
-    // Coordinate rescaling
-    this.dataScale = [1, 1, 1];
-
-    this.contourLevels = [ [], [], [] ];
-
-    this.convertAnnotations = Registry.getComponentMethod('annotations3d', 'convert');
-    this.drawAnnotations = Registry.getComponentMethod('annotations3d', 'draw');
-
-    initializeGLPlot(this);
-}
-
-var proto = Scene.prototype;
-
-proto.initializeGLCamera = function() {
-    var cameraData = this.fullSceneLayout.camera;
-    var isOrtho = (cameraData.projection.type === 'orthographic');
-
-    this.camera = createCamera(this.container, {
-        center: [cameraData.center.x, cameraData.center.y, cameraData.center.z],
-        eye: [cameraData.eye.x, cameraData.eye.y, cameraData.eye.z],
-        up: [cameraData.up.x, cameraData.up.y, cameraData.up.z],
-        _ortho: isOrtho,
-        zoomMin: 0.01,
-        zoomMax: 100,
-        mode: 'orbit'
-    });
 };
 
 proto.recoverContext = function() {
     var scene = this;
-    var gl = this.glplot.gl;
-    var canvas = this.glplot.canvas;
 
-    this.glplot.dispose();
+    scene.glplot.dispose();
 
-    function tryRecover() {
-        if(gl.isContextLost()) {
+    var tryRecover = function() {
+        if(scene.glplot.gl.isContextLost()) {
             requestAnimationFrame(tryRecover);
             return;
         }
-        if(!initializeGLPlot(scene, canvas, gl)) {
+        if(!scene.initializeGLPlot()) {
             Lib.error('Catastrophic and unrecoverable WebGL error. Context lost.');
             return;
         }
         scene.plot.apply(scene, scene.plotArgs);
-    }
+    };
+
     requestAnimationFrame(tryRecover);
 };
 
@@ -498,39 +508,35 @@ function computeAnnotationBounds(scene, bounds) {
 }
 
 proto.plot = function(sceneData, fullLayout, layout) {
+    var scene = this;
+
     // Save parameters
-    this.plotArgs = [sceneData, fullLayout, layout];
+    scene.plotArgs = [sceneData, fullLayout, layout];
 
-    if(this.glplot.contextLost) return;
+    if(scene.glplot.contextLost) return;
 
     var data, trace;
     var i, j, axis, axisType;
-    var fullSceneLayout = fullLayout[this.id];
-    var sceneLayout = layout[this.id];
-
-    if(fullSceneLayout.bgcolor) this.glplot.clearColor = str2RGBAarray(fullSceneLayout.bgcolor);
-    else this.glplot.clearColor = [0, 0, 0, 0];
-
-    this.glplot.snapToData = true;
+    var fullSceneLayout = fullLayout[scene.id];
+    var sceneLayout = layout[scene.id];
 
     // Update layout
-    this.fullLayout = fullLayout;
-    this.fullSceneLayout = fullSceneLayout;
+    scene.fullLayout = fullLayout;
+    scene.fullSceneLayout = fullSceneLayout;
 
-    this.glplotLayout = fullSceneLayout;
-    this.axesOptions.merge(fullLayout, fullSceneLayout);
-    this.spikeOptions.merge(fullSceneLayout);
+    scene.axesOptions.merge(fullLayout, fullSceneLayout);
+    scene.spikeOptions.merge(fullSceneLayout);
 
     // Update camera and camera mode
-    this.setViewport(fullSceneLayout);
-    this.updateFx(fullSceneLayout.dragmode, fullSceneLayout.hovermode);
-    this.camera.enableWheel = this.graphDiv._context._scrollZoom.gl3d;
+    scene.setViewport(fullSceneLayout);
+    scene.updateFx(fullSceneLayout.dragmode, fullSceneLayout.hovermode);
+    scene.camera.enableWheel = scene.graphDiv._context._scrollZoom.gl3d;
 
-    // Update scene
-    this.glplot.update({});
+    // Update scene background
+    scene.glplot.setClearColor(str2RGBAarray(fullSceneLayout.bgcolor));
 
     // Update axes functions BEFORE updating traces
-    this.setConvert(axis);
+    scene.setConvert(axis);
 
     // Convert scene data
     if(!sceneData) sceneData = [];
@@ -560,10 +566,10 @@ proto.plot = function(sceneData, fullLayout, layout) {
     }
 
     // Save scale
-    this.dataScale = dataScale;
+    scene.dataScale = dataScale;
 
     // after computeTraceBounds where ax._categories are filled in
-    this.convertAnnotations(this);
+    scene.convertAnnotations(this);
 
     // Update traces
     for(i = 0; i < sceneData.length; ++i) {
@@ -571,24 +577,24 @@ proto.plot = function(sceneData, fullLayout, layout) {
         if(data.visible !== true || data._length === 0) {
             continue;
         }
-        trace = this.traces[data.uid];
+        trace = scene.traces[data.uid];
         if(trace) {
             if(trace.data.type === data.type) {
                 trace.update(data);
             } else {
                 trace.dispose();
                 trace = data._module.plot(this, data);
-                this.traces[data.uid] = trace;
+                scene.traces[data.uid] = trace;
             }
         } else {
             trace = data._module.plot(this, data);
-            this.traces[data.uid] = trace;
+            scene.traces[data.uid] = trace;
         }
         trace.name = data.name;
     }
 
     // Remove empty traces
-    var traceIds = Object.keys(this.traces);
+    var traceIds = Object.keys(scene.traces);
 
     traceIdLoop:
     for(i = 0; i < traceIds.length; ++i) {
@@ -598,13 +604,13 @@ proto.plot = function(sceneData, fullLayout, layout) {
                 continue traceIdLoop;
             }
         }
-        trace = this.traces[traceIds[i]];
+        trace = scene.traces[traceIds[i]];
         trace.dispose();
-        delete this.traces[traceIds[i]];
+        delete scene.traces[traceIds[i]];
     }
 
     // order object per trace index
-    this.glplot.objects.sort(function(a, b) {
+    scene.glplot.objects.sort(function(a, b) {
         return a._trace.data.index - b._trace.data.index;
     });
 
@@ -631,8 +637,8 @@ proto.plot = function(sceneData, fullLayout, layout) {
             sceneBounds[0][i] = Infinity;
             sceneBounds[1][i] = -Infinity;
 
-            var objects = this.glplot.objects;
-            var annotations = this.fullSceneLayout.annotations || [];
+            var objects = scene.glplot.objects;
+            var annotations = scene.fullSceneLayout.annotations || [];
             var axLetter = axis._name.charAt(0);
 
             for(j = 0; j < objects.length; j++) {
@@ -690,8 +696,10 @@ proto.plot = function(sceneData, fullLayout, layout) {
         axisDataRange[i] = sceneBounds[1][i] - sceneBounds[0][i];
 
         // Update plot bounds
-        this.glplot.bounds[0][i] = sceneBounds[0][i] * dataScale[i];
-        this.glplot.bounds[1][i] = sceneBounds[1][i] * dataScale[i];
+        scene.glplot.setBounds(i, {
+            min: sceneBounds[0][i] * dataScale[i],
+            max: sceneBounds[1][i] * dataScale[i]
+        });
     }
 
     var axesScaleRatio = [1, 1, 1];
@@ -746,11 +754,11 @@ proto.plot = function(sceneData, fullLayout, layout) {
      * Finally assign the computed aspecratio to the glplot module. This will have an effect
      * on the next render cycle.
      */
-    this.glplot.setAspectratio(fullSceneLayout.aspectratio);
+    scene.glplot.setAspectratio(fullSceneLayout.aspectratio);
 
     // save 'initial' camera view settings for modebar button
-    if(!this.viewInitial.aspectratio) {
-        this.viewInitial.aspectratio = {
+    if(!scene.viewInitial.aspectratio) {
+        scene.viewInitial.aspectratio = {
             x: fullSceneLayout.aspectratio.x,
             y: fullSceneLayout.aspectratio.y,
             z: fullSceneLayout.aspectratio.z
@@ -762,7 +770,7 @@ proto.plot = function(sceneData, fullLayout, layout) {
     var size = fullLayout._size || null;
 
     if(domain && size) {
-        var containerStyle = this.container.style;
+        var containerStyle = scene.container.style;
         containerStyle.position = 'absolute';
         containerStyle.left = (size.l + domain.x[0] * size.w) + 'px';
         containerStyle.top = (size.t + (1 - domain.y[1]) * size.h) + 'px';
@@ -771,18 +779,19 @@ proto.plot = function(sceneData, fullLayout, layout) {
     }
 
     // force redraw so that promise is returned when rendering is completed
-    this.glplot.redraw();
+    scene.glplot.redraw();
 };
 
 proto.destroy = function() {
-    if(!this.glplot) return;
-
-    this.camera.mouseListener.enabled = false;
-    this.container.removeEventListener('wheel', this.camera.wheelListener);
-    this.camera = this.glplot.camera = null;
-    this.glplot.dispose();
-    this.container.parentNode.removeChild(this.container);
-    this.glplot = null;
+    var scene = this;
+
+    if(!scene.glplot) return;
+    scene.camera.mouseListener.enabled = false;
+    scene.container.removeEventListener('wheel', scene.camera.wheelListener);
+    scene.camera = null;
+    scene.glplot.dispose();
+    scene.container.parentNode.removeChild(scene.container);
+    scene.glplot = null;
 };
 
 // getCameraArrays :: plotly_coords -> gl-plot3d_coords
@@ -808,42 +817,34 @@ function getLayoutCamera(camera) {
 
 // get camera position in plotly coords from 'gl-plot3d' coords
 proto.getCamera = function() {
-    this.glplot.camera.view.recalcMatrix(this.camera.view.lastT());
-    return getLayoutCamera(this.glplot.camera);
+    var scene = this;
+    scene.camera.view.recalcMatrix(scene.camera.view.lastT());
+    return getLayoutCamera(scene.camera);
 };
 
 // set gl-plot3d camera position and scene aspects with a set of plotly coords
 proto.setViewport = function(sceneLayout) {
+    var scene = this;
     var cameraData = sceneLayout.camera;
 
-    this.glplot.camera.lookAt.apply(this, getCameraArrays(cameraData));
-    this.glplot.setAspectratio(sceneLayout.aspectratio);
+    scene.camera.lookAt.apply(this, getCameraArrays(cameraData));
+    scene.glplot.setAspectratio(sceneLayout.aspectratio);
 
     var newOrtho = (cameraData.projection.type === 'orthographic');
-    var oldOrtho = this.glplot.camera._ortho;
+    var oldOrtho = scene.camera._ortho;
 
     if(newOrtho !== oldOrtho) {
-        this.glplot.redraw();
-
-        var RGBA = this.glplot.clearColor;
-        this.glplot.gl.clearColor(
-            RGBA[0], RGBA[1], RGBA[2], RGBA[3]
-        );
-        this.glplot.gl.clear(
-            this.glplot.gl.DEPTH_BUFFER_BIT |
-            this.glplot.gl.COLOR_BUFFER_BIT
-        );
-
-        this.glplot.dispose();
-
-        initializeGLPlot(this);
-        this.glplot.camera._ortho = newOrtho;
+        scene.glplot.redraw(); // TODO: figure out why we need to redraw here?
+        scene.glplot.clearRGBA();
+        scene.glplot.dispose();
+        scene.initializeGLPlot();
     }
 };
 
 proto.isCameraChanged = function(layout) {
-    var cameraData = this.getCamera();
-    var cameraNestedProp = Lib.nestedProperty(layout, this.id + '.camera');
+    var scene = this;
+    var cameraData = scene.getCamera();
+    var cameraNestedProp = Lib.nestedProperty(layout, scene.id + '.camera');
     var cameraDataLastSave = cameraNestedProp.get();
 
     function same(x, y, i, j) {
@@ -876,8 +877,9 @@ proto.isCameraChanged = function(layout) {
 };
 
 proto.isAspectChanged = function(layout) {
-    var aspectData = this.glplot.getAspectratio();
-    var aspectNestedProp = Lib.nestedProperty(layout, this.id + '.aspectratio');
+    var scene = this;
+    var aspectData = scene.glplot.getAspectratio();
+    var aspectNestedProp = Lib.nestedProperty(layout, scene.id + '.aspectratio');
     var aspectDataLastSave = aspectNestedProp.get();
 
     return (
@@ -890,7 +892,8 @@ proto.isAspectChanged = function(layout) {
 
 // save camera to user layout (i.e. gd.layout)
 proto.saveLayout = function(layout) {
-    var fullLayout = this.fullLayout;
+    var scene = this;
+    var fullLayout = scene.fullLayout;
 
     var cameraData;
     var cameraNestedProp;
@@ -900,42 +903,42 @@ proto.saveLayout = function(layout) {
     var aspectNestedProp;
     var aspectDataLastSave;
 
-    var cameraChanged = this.isCameraChanged(layout);
-    var aspectChanged = this.isAspectChanged(layout);
+    var cameraChanged = scene.isCameraChanged(layout);
+    var aspectChanged = scene.isAspectChanged(layout);
 
     var hasChanged = cameraChanged || aspectChanged;
     if(hasChanged) {
         var preGUI = {};
         if(cameraChanged) {
-            cameraData = this.getCamera();
-            cameraNestedProp = Lib.nestedProperty(layout, this.id + '.camera');
+            cameraData = scene.getCamera();
+            cameraNestedProp = Lib.nestedProperty(layout, scene.id + '.camera');
             cameraDataLastSave = cameraNestedProp.get();
 
-            preGUI[this.id + '.camera'] = cameraDataLastSave;
+            preGUI[scene.id + '.camera'] = cameraDataLastSave;
         }
         if(aspectChanged) {
-            aspectData = this.glplot.getAspectratio();
-            aspectNestedProp = Lib.nestedProperty(layout, this.id + '.aspectratio');
+            aspectData = scene.glplot.getAspectratio();
+            aspectNestedProp = Lib.nestedProperty(layout, scene.id + '.aspectratio');
             aspectDataLastSave = aspectNestedProp.get();
 
-            preGUI[this.id + '.aspectratio'] = aspectDataLastSave;
+            preGUI[scene.id + '.aspectratio'] = aspectDataLastSave;
         }
         Registry.call('_storeDirectGUIEdit', layout, fullLayout._preGUI, preGUI);
 
         if(cameraChanged) {
             cameraNestedProp.set(cameraData);
 
-            var cameraFullNP = Lib.nestedProperty(fullLayout, this.id + '.camera');
+            var cameraFullNP = Lib.nestedProperty(fullLayout, scene.id + '.camera');
             cameraFullNP.set(cameraData);
         }
 
         if(aspectChanged) {
             aspectNestedProp.set(aspectData);
 
-            var aspectFullNP = Lib.nestedProperty(fullLayout, this.id + '.aspectratio');
+            var aspectFullNP = Lib.nestedProperty(fullLayout, scene.id + '.aspectratio');
             aspectFullNP.set(aspectData);
 
-            this.glplot.redraw();
+            scene.glplot.redraw();
         }
     }
 
@@ -943,7 +946,8 @@ proto.saveLayout = function(layout) {
 };
 
 proto.updateFx = function(dragmode, hovermode) {
-    var camera = this.camera;
+    var scene = this;
+    var camera = scene.camera;
     if(camera) {
         // rotate and orbital are synonymous
         if(dragmode === 'orbit') {
@@ -957,15 +961,15 @@ proto.updateFx = function(dragmode, hovermode) {
             // The setter for camera.mode animates the transition to z-up,
             // but only if we *don't* explicitly set z-up earlier via the
             // relayout. So push `up` back to layout & fullLayout manually now.
-            var gd = this.graphDiv;
+            var gd = scene.graphDiv;
             var fullLayout = gd._fullLayout;
-            var fullCamera = this.fullSceneLayout.camera;
+            var fullCamera = scene.fullSceneLayout.camera;
             var x = fullCamera.up.x;
             var y = fullCamera.up.y;
             var z = fullCamera.up.z;
             // only push `up` back to (full)layout if it's going to change
             if(z / Math.sqrt(x * x + y * y + z * z) < 0.999) {
-                var attr = this.id + '.camera.up';
+                var attr = scene.id + '.camera.up';
                 var zUp = {x: 0, y: 0, z: 1};
                 var edits = {};
                 edits[attr] = zUp;
@@ -981,19 +985,20 @@ proto.updateFx = function(dragmode, hovermode) {
     }
 
     // to put dragmode and hovermode on the same grounds from relayout
-    this.fullSceneLayout.hovermode = hovermode;
+    scene.fullSceneLayout.hovermode = hovermode;
 };
 
 proto.toImage = function(format) {
-    if(!format) format = 'png';
+    var scene = this;
 
-    if(this.staticMode) this.container.appendChild(STATIC_CANVAS);
+    if(!format) format = 'png';
+    if(scene.staticMode) scene.container.appendChild(STATIC_CANVAS);
 
     // Force redraw
-    this.glplot.redraw();
+    scene.glplot.redraw();
 
     // Grab context and yank out pixels
-    var gl = this.glplot.gl;
+    var gl = scene.glplot.gl;
     var w = gl.drawingBufferWidth;
     var h = gl.drawingBufferHeight;
 
@@ -1034,31 +1039,32 @@ proto.toImage = function(format) {
             dataURL = canvas.toDataURL('image/png');
     }
 
-    if(this.staticMode) this.container.removeChild(STATIC_CANVAS);
+    if(scene.staticMode) scene.container.removeChild(STATIC_CANVAS);
 
     return dataURL;
 };
 
 proto.setConvert = function() {
+    var scene = this;
     for(var i = 0; i < 3; i++) {
-        var ax = this.fullSceneLayout[axisProperties[i]];
-        Axes.setConvert(ax, this.fullLayout);
+        var ax = scene.fullSceneLayout[axisProperties[i]];
+        Axes.setConvert(ax, scene.fullLayout);
         ax.setScale = Lib.noop;
     }
 };
 
 proto.make4thDimension = function() {
-    var _this = this;
-    var gd = _this.graphDiv;
+    var scene = this;
+    var gd = scene.graphDiv;
     var fullLayout = gd._fullLayout;
 
     // mock axis for hover formatting
-    _this.mockAxis = {
+    scene._mockAxis = {
         type: 'linear',
         showexponent: 'all',
         exponentformat: 'B'
     };
-    Axes.setConvert(_this.mockAxis, fullLayout);
+    Axes.setConvert(scene._mockAxis, fullLayout);
 };
 
 module.exports = Scene;
diff --git a/test/jasmine/tests/gl3d_plot_interact_test.js b/test/jasmine/tests/gl3d_plot_interact_test.js
index 91915c621f8..5f60b2e3130 100644
--- a/test/jasmine/tests/gl3d_plot_interact_test.js
+++ b/test/jasmine/tests/gl3d_plot_interact_test.js
@@ -93,7 +93,7 @@ describe('Test gl3d before/after plot', function() {
         })
         .then(delay(20))
         .then(function() {
-            var cameraBefore = gd._fullLayout.scene._scene.glplot.camera;
+            var cameraBefore = gd._fullLayout.scene._scene.camera;
             expect(cameraBefore.up[0]).toBeCloseTo(0, 2, 'cameraBefore.up[0]');
             expect(cameraBefore.up[1]).toBeCloseTo(0, 2, 'cameraBefore.up[1]');
             expect(cameraBefore.up[2]).toBeCloseTo(1, 2, 'cameraBefore.up[2]');
@@ -108,7 +108,7 @@ describe('Test gl3d before/after plot', function() {
         .then(_clickThere)
         .then(delay(20))
         .then(function() {
-            var cameraAfter = gd._fullLayout.scene._scene.glplot.camera;
+            var cameraAfter = gd._fullLayout.scene._scene.camera;
             expect(cameraAfter.up[0]).toBeCloseTo(0, 2, 'cameraAfter.up[0]');
             expect(cameraAfter.up[1]).toBeCloseTo(0, 2, 'cameraAfter.up[1]');
             expect(cameraAfter.up[2]).toBeCloseTo(1, 2, 'cameraAfter.up[2]');
@@ -123,7 +123,7 @@ describe('Test gl3d before/after plot', function() {
         .then(_clickOtherplace)
         .then(delay(20))
         .then(function() {
-            var cameraFinal = gd._fullLayout.scene._scene.glplot.camera;
+            var cameraFinal = gd._fullLayout.scene._scene.camera;
             expect(cameraFinal.up[0]).toBeCloseTo(0, 2, 'cameraFinal.up[0]');
             expect(cameraFinal.up[1]).toBeCloseTo(0, 2, 'cameraFinal.up[1]');
             expect(cameraFinal.up[2]).toBeCloseTo(1, 2, 'cameraFinal.up[2]');
@@ -300,7 +300,7 @@ describe('Test gl3d plots', function() {
         .then(delay(20))
         .then(function() {
             expect(gd._fullLayout.scene.camera.projection.type === 'perspective').toBe(true);
-            expect(gd._fullLayout.scene._scene.glplot.camera._ortho === false).toBe(true);
+            expect(gd._fullLayout.scene._scene.camera._ortho === false).toBe(true);
         })
         .then(done);
     });
@@ -326,7 +326,7 @@ describe('Test gl3d plots', function() {
         .then(delay(20))
         .then(function() {
             expect(gd._fullLayout.scene.camera.projection.type === 'orthographic').toBe(true);
-            expect(gd._fullLayout.scene._scene.glplot.camera._ortho === true).toBe(true);
+            expect(gd._fullLayout.scene._scene.camera._ortho === true).toBe(true);
         })
         .then(done);
     });
@@ -355,28 +355,28 @@ describe('Test gl3d plots', function() {
         })
         .then(function() {
             expect(gd._fullLayout.scene.camera.projection.type === 'orthographic').toBe(true);
-            expect(gd._fullLayout.scene._scene.glplot.camera._ortho === true).toBe(true);
+            expect(gd._fullLayout.scene._scene.camera._ortho === true).toBe(true);
         })
         .then(function() {
             return Plotly.relayout(gd, 'scene.camera.eye.z', 2);
         })
         .then(function() {
             expect(gd._fullLayout.scene.camera.projection.type === 'orthographic').toBe(true);
-            expect(gd._fullLayout.scene._scene.glplot.camera._ortho === true).toBe(true);
+            expect(gd._fullLayout.scene._scene.camera._ortho === true).toBe(true);
         })
         .then(function() {
             return Plotly.relayout(gd, 'scene.camera.projection.type', 'perspective');
         })
         .then(function() {
             expect(gd._fullLayout.scene.camera.projection.type === 'perspective').toBe(true);
-            expect(gd._fullLayout.scene._scene.glplot.camera._ortho === false).toBe(true);
+            expect(gd._fullLayout.scene._scene.camera._ortho === false).toBe(true);
         })
         .then(function() {
             return Plotly.relayout(gd, 'scene.camera.eye.z', 3);
         })
         .then(function() {
             expect(gd._fullLayout.scene.camera.projection.type === 'perspective').toBe(true);
-            expect(gd._fullLayout.scene._scene.glplot.camera._ortho === false).toBe(true);
+            expect(gd._fullLayout.scene._scene.camera._ortho === false).toBe(true);
         })
         .then(done);
     });