Skip to content
Merged
Prev Previous commit
Next Next commit
Update material.md
  • Loading branch information
machenmusik authored and ngokevin committed Oct 18, 2017
commit 072cda2c781c57d3e84bf4240dfb4a084aa769cd
238 changes: 109 additions & 129 deletions docs/components/material.md
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,115 @@ For a more advanced example, [try realtime vertex displacement](https://glitch.c

![b19320eb-802a-462a-afcd-3d0dd9480aee-861-000004c2a8504498](https://cloud.githubusercontent.com/assets/1848368/24825518/b52e5bf6-1bd4-11e7-8eb2-9a9c1ff82ce9.gif)

## Using a Custom Shader and Component Together

Let's take the realtime vertex displacement shader example above, and
add the capability to apply an offset based upon the camera's position.

We declare that offset as a uniform vec3 value `myOffset`:

```js
AFRAME.registerShader('displacement-offset', {
schema: {
time1000: {type:'time', is:'uniform'},
myOffset: {type:'vec3', is:'uniform'}
},
vertexShader: vertexShader,
fragmentShader: fragmentShader
});
```

that the vertex shader uses:

```glsl
// vertex.glsl

#pragma glslify: pnoise3 = require(glsl-noise/periodic/3d)
Copy link
Member

Choose a reason for hiding this comment

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

I probably wouldn't go to the trouble of explaining how to set it up here, but worth noting somewhere that:

This example uses Browserify and glslify to include glsl-noise as a dependency of our shader.

(similar options are available for rollup etc.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


//
// Based on @thespite's article:
//
// "Vertex displacement with a noise function using GLSL and three.js"
// Source: https://www.clicktorelease.com/blog/vertex-displacement-noise-3d-webgl-glsl-three-js/
//

varying float noise;
uniform float time1000;
uniform vec3 myOffset;

float turbulence( vec3 p ) {

float w = 100.0;
float t = -.5;

for (float f = 1.0 ; f <= 10.0 ; f++ ){
float power = pow( 2.0, f );
t += abs( pnoise3( vec3( power * p ), vec3( 10.0, 10.0, 10.0 ) ) / power );
}

return t;

}

void main() {
float time = time1000 / 1000.0;
noise = 10.0 * -.10 * turbulence( .5 * normal + time / 3.0 );
float b = 5.0 * pnoise3( 0.05 * position, vec3( 100.0 ) );
float displacement = (- 10. * noise + b) / 50.0;

vec3 newPosition = position + normal * displacement + myOffset;
gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 );
}
```

So how do we update `myOffset` to be the camera position,
so the vertex shader behaves correctly?

The typical method to do this in A-Frame is to create a custom component
with the desired functionality, and attach it to the appropriate entity.

Note that the shader property is exposed via the `material` component,
so we modify the single property of interest using a form of `setAttribute()`.

As best practice to avoid creating garbage for performance reasons:
- we do not use the form of `setAttribute` that takes an object as second argument.
- we create a component property to hold the offset, to avoid creating a new THREE.Vector3 every tick.

```js
AFRAME.registerComponent('myoffset-updater', {
init: function () {
this.offset = new THREE.Vector3();
},

tick: function (t, dt) {
this.offset.copy(this.el.sceneEl.camera.el.getAttribute('position'));
this.offset.y = 0;
this.el.setAttribute('material', 'myOffset', this.offset);
}
});
```

We then apply the custom component to the entity with the custom shader:

```
<a-scene>
<a-sphere material="shader:displacement-offset"
myoffset-updater
scale="1 1 1"
radius="0.2"
position="0 1.5 -2"
segments-height="128"
segments-width="128">
<a-animation attribute="scale" direction="alternate-reverse" dur="5000" from="1 1 1" to="4 4 4" repeat="indefinite"></a-animation>
</a-sphere>
<a-box color="#CCC" width="3" depth="3" height="0.1" position="0 0 -2"></a-box>
</a-scene>
```

Voila!

* [Live demo](https://aframe-displacement-offset-registershader.glitch.me/)
* [Remix this on Glitch](https://glitch.com/edit/#!/aframe-displacement-offset-registershader)

## Register a Custom Component Using THREE Material

Expand Down Expand Up @@ -625,132 +734,3 @@ AFRAME.registerShader('line-dashed', {
}
});
```

### Example — GLSL and Shaders, Using Custom Component

For more customized visual effects, we can write GLSL shaders and apply them to A-Frame entities. We'll do this using [THREE.ShaderMaterial](https://threejs.org/docs/#api/materials/ShaderMaterial) and a custom component. GLSL shaders can also be used with the `registerShader` API, but for many cases — here, we need a `tick()` handler to update the shader's clock — using a component can be easier.

> NOTE: GLSL, the syntax used to write shaders, may seem a bit scary at first. For a gentle (and free!) introduction, we recommend [The Book of Shaders](http://thebookofshaders.com/).

Component:

```js
// material-grid-glitch.js

const vertexShader = `
/// PLACEHOLDER ///
`;
const fragmentShader = `
/// PLACEHOLDER ///
`;

AFRAME.registerComponent('material-grid-glitch', {
schema: {color: {type: 'color'}},

/**
* Creates a new THREE.ShaderMaterial using the two shaders defined
* in vertex.glsl and fragment.glsl.
*/
init: function () {
const data = this.data;

this.material = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0.0 },
color: { value: new THREE.Color(data.color) }
},
vertexShader,
fragmentShader
});

this.applyToMesh();
this.el.addEventListener('model-loaded', () => this.applyToMesh());
},


/**
* Update the ShaderMaterial when component data changes.
*/
update: function () {
this.material.uniforms.color.value.set(this.data.color);
},

/**
* Apply the material to the current entity.
*/
applyToMesh: function() {
const mesh = this.el.getObject3D('mesh');
if (mesh) {
mesh.material = this.material;
}
},

/**
* On each frame, update the 'time' uniform in the shaders.
*/
tick: function (t) {
this.material.uniforms.time.value = t / 1000;
}

})
```

Next, we can put our shaders into the placeholders above. Every material will have two shaders: a vertex and a fragment shader.

```glsl
// vertex.glsl

varying vec2 vUv;

void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
```

```glsl
// fragment.glsl

varying vec2 vUv;
uniform vec3 color;
uniform float time;
Copy link
Member

Choose a reason for hiding this comment

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

time1000 is a weird name. Let’s keep time. It is already consistent with other time related variables that use miliseconds in the code base

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So the reason I didn't use time is because that is commonly used to represent time, but in seconds not milliseconds. Unfortunately the time type is processed as milliseconds, so I wanted to pick a different name and convert to the time in seconds that typical sharers expect. Maybe timeMsec or something more descriptive?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To clarify, there is inherent support for type time, without using a tick() handler to update anything, but it is in milliseconds rather than seconds (which is what you generally see in common sharers from Shadertoy, Shaderfrog, etc.)

Copy link
Member

Choose a reason for hiding this comment

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

In the DOM and A-Frame we use miliseconds for time. I would keep the name time

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I may not be doing a good job of explaining.

The shader examples that are time-varying already use a uniform named time that is in seconds. Rather than trying to force already working shaders to rename, what I am illustrating is how to do a simple two line addition that allows the shader code to be reused without modification.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed to timeMsec, and added comments in the shader to explain the conversion to seconds.


void main() {
// Use sin(time), which curves between 0 and 1 over time,
// to determine the mix of two colors:
// (a) Dynamic color where 'R' and 'B' channels come
// from a modulus of the UV coordinates.
// (b) Base color.
//
// The color itself is a vec4 containing RGBA values 0-1.
gl_FragColor = mix(
vec4(mod(vUv , 0.05) * 20.0, 1.0, 1.0),
vec4(color, 1.0),
sin(time)
);
}
```

Finally, here is the HTML markup to put it all together:

```html
<!-- index.html -->

<a-scene>
<a-sphere material-grid-glitch="color: blue;"
radius="0.5"
position="0 1.5 -2">
</a-sphere>
</a-scene>
```

* [Live demo](https://aframe-simple-shader.glitch.me/)
* [Remix this on Glitch](https://glitch.com/edit/#!/aframe-simple-shader)

![5093034e-97f2-40dc-8cb9-28ca75bfd75b-8043-00000dbc2e00268d](https://cloud.githubusercontent.com/assets/1848368/24825516/abb98abe-1bd4-11e7-8262-93d3efb6056f.gif)

***

For a more advanced example, [try realtime vertex displacement](https://glitch.com/edit/#!/aframe-displacement-shader).

![b19320eb-802a-462a-afcd-3d0dd9480aee-861-000004c2a8504498](https://cloud.githubusercontent.com/assets/1848368/24825518/b52e5bf6-1bd4-11e7-8eb2-9a9c1ff82ce9.gif)