Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion docs/components/light.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,4 +237,4 @@ system on the `<a-scene>` root element.

| Property | Description | Default Value |
|--------------------|---------------------------------------------------------------------------------------------------------------|---------------|
| type | Defines shadow map type (`basic`, `pcf`, `pcfsoft`) with varying appearance and perforance characteristics. | `pcf` |
| type | Defines shadow map type (`basic`, `pcf`, `pcfsoft`) with varying appearance and performance characteristics. | `pcf` |
63 changes: 63 additions & 0 deletions docs/components/renderer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
title: renderer
type: components
layout: docs
parent_section: components
source_code: src/components/renderer.js
examples: []
---

The `renderer` component configures a scene's
[THREE.WebGLRenderer](https://threejs.org/docs/#api/renderers/WebGLRenderer) instance.

## Example

```html
<a-scene renderer="antialias: true;
gammaOutput: true;
sortObjects: true;
physicallyCorrectLights: true;"></a-scene>
```

## Properties

| Property | Description | Default Value |
|-------------------------|---------------------------------------------------------------------------------|---------------|
| antialias | Whether to perform antialiasing. If `auto`, antialiasing is disabled on mobile. | auto |
| gammaOutput | Whether to pre-multiply gamma on textures and colors before rendering. | false |
| sortObjects | Whether to sort objects before rendering. | false |
| physicallyCorrectLights | Whether to use physically-correct light attenuation. | false |

### antialias

When enabled, smooths jagged edges on curved lines and diagonals at moderate performance cost.
By default, antialiasing is disabled on mobile devices.

> **NOTE:** Once the scene is initialized, `antialias` may no longer be
> changed.

### gammaOutput

Typically, textures are converted to linear colorspace in the renderer for lighting calculations.
Unless post-processing used after the initial render,
[gamma correction](https://en.wikipedia.org/wiki/Gamma_correction) should be applied with
`renderer="gammaOutput: true;"` for best color reproduction. By default, gamma correction is off
in order to preserve backward-compatibility. When changed, adjustments to lighting may be needed.

### sortObjects

Sorting is used to attempt to properly render objects that have some degree of transparency.
Due to various limitations, proper transparency often requires some amount of careful setup.
By default, objects are not sorted, and the order of elements in the DOM determines order of
rendering. Re-ordering DOM elements provides one way of forcing a consistent behavior, whereas
use of `renderer="sortObjects: true"` may cause unwanted changes as the camera moves.

### physicallyCorrectLights

By default, point and spot lights attenuate (or, appear dimmer as they become farther away)
according to a model that is classically common, but physically inaccurate. For more realistic
light attenuation, set `renderer="physicallyCorrectLights: true"`. Light intensities may need to
be adjusted when making this change. Performance is not significantly affected in either mode.

> **NOTE:** When glTF models contain lights, use the physically-correct lighting mode to match
> the results in the original modeling tool.
1 change: 1 addition & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ require('./scene/inspector');
require('./scene/fog');
require('./scene/keyboard-shortcuts');
require('./scene/pool');
require('./scene/renderer');
require('./scene/screenshot');
require('./scene/stats');
require('./scene/vr-mode-ui');
72 changes: 72 additions & 0 deletions src/components/scene/renderer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
var register = require('../../core/component').registerComponent;
var debug = require('../../utils/debug');

var warn = debug('components:renderer:warn');

/**
* Determines state of various renderer properties.
*
* NOTE: Because the `renderer` component is not added to the scene
* automatically, changing default values here has no effect unless
* the same changes are included in `a-scene.js`.
*/
module.exports.Component = register('renderer', {
schema: {
antialias: {default: 'auto', oneOf: ['true', 'false', 'auto']},
gammaOutput: {default: false},
physicallyCorrectLights: {default: false},
sortObjects: {default: false}
},

init: function () {
var el = this.el;

if (!el.isScene) {
warn('Renderer component can only be applied to <a-scene>');
}

if (el.hasAttribute('antialias')) {
warn('Component `antialias` is deprecated. Use `renderer="antialias: true"` instead.');
}
},

update: function (prevData) {
var data = this.data;
var sceneEl = this.el;
var renderer = sceneEl.renderer;
var needsShaderUpdate = false;

if (sceneEl.time > 0 && data.antialias !== prevData.antialias) {
warn('Property "antialias" cannot be changed after scene initialization');
}

if (data.sortObjects !== prevData.sortObjects) {
renderer.sortObjects = data.sortObjects;
}

if (data.gammaOutput !== prevData.gammaOutput) {
renderer.gammaOutput = data.gammaOutput;
needsShaderUpdate = true;
}

if (data.physicallyCorrectLights !== prevData.physicallyCorrectLights) {
renderer.physicallyCorrectLights = data.physicallyCorrectLights;
needsShaderUpdate = true;
}

if (!needsShaderUpdate || sceneEl.time === 0) { return; }

warn('Modifying renderer properties at runtime requires shader update and may drop frames.');

sceneEl.object3D.traverse(function (node) {
if (!node.isMesh) { return; }
if (Array.isArray(node.material)) {
node.material.forEach(function (material) {
material.needsUpdate = true;
});
} else {
node.material.needsUpdate = true;
}
});
}
});
36 changes: 16 additions & 20 deletions src/core/scene/a-scene.js
Original file line number Diff line number Diff line change
Expand Up @@ -487,11 +487,24 @@ module.exports.AScene = registerElement('a-scene', {
value: function () {
var self = this;
var renderer;
renderer = this.renderer = new THREE.WebGLRenderer({
var rendererAttr;
var rendererAttrString;
var rendererConfig = {
canvas: this.canvas,
antialias: shouldAntiAlias(this),
antialias: !isMobile,
alpha: true
});
};
if (this.hasAttribute('antialias')) {
rendererConfig.antialias = this.getAttribute('antialias') === 'true';
}
if (this.hasAttribute('renderer')) {
rendererAttrString = this.getAttribute('renderer');
rendererAttr = utils.styleParser.parse(rendererAttrString);
if (rendererAttr.antialias && rendererAttr.antialias !== 'auto') {
rendererConfig.antialias = rendererAttr.antialias === 'true';
}
}
renderer = this.renderer = new THREE.WebGLRenderer(rendererConfig);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.sortObjects = false;
// We expect camera-set-active to be triggered at least once in the life of an a-frame app. Usually soon after
Expand Down Expand Up @@ -675,23 +688,6 @@ function exitFullscreen () {
}
}

/**
* Determines if renderer anti-aliasing should be enabled.
* Enabled by default if has native WebVR or is desktop.
*
* @returns {bool}
*/
function shouldAntiAlias (sceneEl) {
// Explicitly set.
if (sceneEl.getAttribute('antialias') !== null) {
return sceneEl.getAttribute('antialias') === 'true';
}

// Default not AA for mobile.
return !sceneEl.isMobile;
}
module.exports.shouldAntiAlias = shouldAntiAlias; // For testing.

function setupCanvas (sceneEl) {
var canvasEl;

Expand Down
48 changes: 48 additions & 0 deletions tests/components/scene/renderer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* global THREE, assert, setup, suite, test */

suite('renderer', function () {
setup(function (done) {
var sceneEl = this.sceneEl = document.createElement('a-scene');
sceneEl.time = 0;
document.body.appendChild(sceneEl);
sceneEl.addEventListener('loaded', function () { done(); });
});

test('change renderer gammaOutput', function () {
var sceneEl = this.sceneEl;
assert.notOk(sceneEl.renderer.gammaOutput);
sceneEl.setAttribute('renderer', {gammaOutput: true});
assert.equal(sceneEl.renderer.gammaOutput, true);
});

test('change renderer sortObjects', function () {
var sceneEl = this.sceneEl;
assert.notOk(sceneEl.renderer.sortObjects);
sceneEl.setAttribute('renderer', {sortObjects: true});
assert.equal(sceneEl.renderer.sortObjects, true);
});

test('change renderer physicallyCorrectLights', function () {
var sceneEl = this.sceneEl;
assert.notOk(sceneEl.renderer.physicallyCorrectLights);
sceneEl.setAttribute('renderer', {physicallyCorrectLights: true});
assert.equal(sceneEl.renderer.physicallyCorrectLights, true);
});

test('recompile all materials when needed', function () {
var sceneEl = this.sceneEl;
var mesh = new THREE.Mesh();
mesh.material.needsUpdate = false;
sceneEl.object3D.add(mesh);

sceneEl.setAttribute('renderer', '');
assert.equal(mesh.material.needsUpdate, false);

sceneEl.time = 100;

sceneEl.setAttribute('renderer', {sortObjects: true});
assert.equal(mesh.material.needsUpdate, false);
sceneEl.setAttribute('renderer', {gammaOutput: true});
assert.equal(mesh.material.needsUpdate, true);
});
});
28 changes: 0 additions & 28 deletions tests/core/scene/a-scene.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
var AScene = require('core/scene/a-scene').AScene;
var components = require('core/component').components;
var scenes = require('core/scene/scenes');
var shouldAntiAlias = require('core/scene/a-scene').shouldAntiAlias;
var setupCanvas = require('core/scene/a-scene').setupCanvas;
var systems = require('core/system').systems;

Expand Down Expand Up @@ -735,30 +734,3 @@ suite('setupCanvas', function () {
assert.ok(el.canvas);
});
});

suite('shouldAntiAlias', function () {
var sceneEl;

setup(function () {
sceneEl = document.createElement('a-scene');
});

test('is true if set to true', function () {
sceneEl.setAttribute('antialias', 'true');
assert.ok(shouldAntiAlias(sceneEl));
});

test('is false if set to false', function () {
sceneEl.setAttribute('antialias', 'false');
assert.notOk(shouldAntiAlias(sceneEl));
});

test('is true on desktop by default', function () {
assert.ok(shouldAntiAlias(sceneEl));
});

test('is false on mobile with no native webvr by default', function () {
sceneEl.isMobile = true;
assert.notOk(shouldAntiAlias(sceneEl));
});
});