diff --git a/.npmignore b/.npmignore index c39012e9e737a6c38a42deb0c22ca3cafd1f232e..5c5952bf978a6a6b6d130dd274b33623ec85c4f5 100644 --- a/.npmignore +++ b/.npmignore @@ -1,3 +1,6 @@ +docs/ +Examples/ + # OSX # .DS_Store diff --git a/Examples/AdvancedEffects/src/TransitionGenerator.js b/Examples/AdvancedEffects/src/TransitionGenerator.js index ae9e17a118ba91b1e3d4da9751191ae0ba6962c0..587002289c3f97eca563bfc6c0f6e81fff4a72c2 100644 --- a/Examples/AdvancedEffects/src/TransitionGenerator.js +++ b/Examples/AdvancedEffects/src/TransitionGenerator.js @@ -4,19 +4,37 @@ GlslTransitions.forEach(function (t) { byName[t.name] = t; }); const transitions = [ + [ "directionalwipe", function () { + const angle = Math.random() * 2 * Math.PI; + return { + direction: [ Math.cos(angle), Math.sin(angle) ] + }; + } ], [ "cube", function () { return { persp: 0.9 - Math.random()*Math.random(), unzoom: Math.random()*Math.random() }; } ], + [ "randomsquares", function () { + const size = Math.round(4 + 20 * Math.random()); + return { + size: [ size, size ], + smoothness: Math.random() + }; + } ], "undulating burn out", [ "CrossZoom", function () { return { strength: 0.5 * Math.random() }; } ], + "swap", + [ "wind", function () { + return { size: 0.1+0.2 * Math.random() }; + } ], "glitch displace", [ "Mosaic", function () { let dx = Math.round(Math.random() * 6 - 3), dy = Math.round(Math.random() * 6 - 3); if (dx===0 && dy===0) dy = -1; return { endx: dx, endy: dy }; } ], + "Dreamy", [ "DoomScreenTransition", function () { return { barWidth: Math.round(6 + 20 * Math.random()), @@ -25,21 +43,23 @@ const transitions = [ frequency: Math.random() }; } ], - [ "colourDistance", function () { - return { interpolationPower: 6 * Math.random() }; - } ], - "swap", [ "doorway", function () { return { perspective: Math.random() * Math.random(), depth: 1 + 10 * Math.random() * Math.random() }; } ], "Star Wipe", "pinwheel", - "SimpleFlip", "TilesScanline", - "Dreamy", + [ "flyeye", function () { + return { + size: Math.random() * Math.random(), + zoom: 200 * Math.random() * Math.random(), + colorSeparation: 0.8 * Math.random() * Math.random() + }; + } ], "Swirl", "burn", "Radial", + "SimpleFlip", [ "ripple", function () { return { amplitude: 200 * Math.random(), @@ -47,40 +67,13 @@ const transitions = [ }; } ], "morph", - ["ButterflyWaveScrawler", function () { - return { - amplitude: Math.random(), - waves: 50 * Math.random() * Math.random(), - colorSeparation: 0.8 * Math.random() * Math.random() - }; - } ], [ "flash", function () { return { flashIntensity: 4 * Math.random() }; } ], - [ "randomsquares", function () { - const size = Math.round(4 + 20 * Math.random()); - return { - size: [ size, size ], - smoothness: Math.random() - }; - } ], - [ "flyeye", function () { - return { - size: Math.random() * Math.random(), - zoom: 200 * Math.random() * Math.random(), - colorSeparation: 0.8 * Math.random() * Math.random() - }; - } ], "squeeze", - [ "directionalwipe", function () { - const angle = Math.random() * 2 * Math.PI; - return { - direction: [ Math.cos(angle), Math.sin(angle) ] - }; - } ], "circleopen", - [ "wind", function () { - return { size: 0.1+0.2 * Math.random() }; + [ "colourDistance", function () { + return { interpolationPower: 6 * Math.random() }; } ] ].map(function (obj) { let name, genUniforms; @@ -104,8 +97,11 @@ const transitions = [ }; }); +let _i = 0; + function random () { - const i = Math.floor(Math.random() * transitions.length); + const i = _i; + _i = _i+1 >= transitions.length ? 0 : _i+1; const t = transitions[i]; const uniforms = t.genUniforms && t.genUniforms() || {}; return { diff --git a/Examples/Simple/HueRotate.js b/Examples/Simple/HueRotate.js index 268f751616df76267595de71b4901cef34fa5d42..d822a42858985ae38bc3d5d16c41f8af32c70955 100644 --- a/Examples/Simple/HueRotate.js +++ b/Examples/Simple/HueRotate.js @@ -31,9 +31,7 @@ class HueRotate extends React.Component { shader={shaders.hueRotate} width={width} height={height} - uniforms={{ - hue - }}> + uniforms={{ hue }}> {children} ; } diff --git a/Examples/Simple/OneFingerResponse.js b/Examples/Simple/OneFingerResponse.js index 43bd608bd567d8f22ed9b214b772d07fa4980bbe..3f360311eb32d49ecad9850baaaecc65cfc300ce 100644 --- a/Examples/Simple/OneFingerResponse.js +++ b/Examples/Simple/OneFingerResponse.js @@ -2,7 +2,7 @@ const React = require("react-native"); const GL = require("gl-react-native"); const shaders = GL.Shaders.create({ - pieProgress: { + oneFingerResponse: { frag: ` precision mediump float; varying vec2 uv; @@ -20,7 +20,7 @@ void main () { } }); -class PieProgress extends React.Component { +class OneFingerResponse extends React.Component { constructor (props) { super(props); this.state = { @@ -65,10 +65,10 @@ class PieProgress extends React.Component { onResponderTerminate={this.onTouchEnd} width={width} height={height} - shader={shaders.pieProgress} + shader={shaders.oneFingerResponse} uniforms={{ pressed, position }} />; } } -module.exports = PieProgress; +module.exports = OneFingerResponse; diff --git a/Examples/Simple/README.md b/Examples/Simple/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c22e2fb15de946bf41651bda8d1611c4792f2321 --- /dev/null +++ b/Examples/Simple/README.md @@ -0,0 +1,18 @@ + + +## Run the example + +``` +npm install +``` + +Then open AdvancedEffects.xcodeproj with XCode and run the application. + +## Developing with the example + +``` +npm install react-native +npm install ../.. # everytime the lib code changes +``` + +(also make sure a `npm install` has been called on the root directory of gl-react-native). diff --git a/docs/README.md b/docs/README.md index 120fda936fca2c2eeb25a46033cdd3eaf61447ba..05ba01c8049209a346adec2f2e947ce5e06a1df3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,10 +4,11 @@ * [GL.Shaders.create](api/Shaders.create.md) * [GL.View](api/View.md) * [GL.Target](api/Target.md) -* Examples - * [Hello World](examples/1.md) - * [Effects over Image](examples/2.md) - * [Effects over anything](examples/3.md) - * [Animation](examples/4.md) - * [Touch responsive](examples/5.md) - * [Upload Indicator](examples/6.md) +* [Simple Examples](examples/simple.md) + * [Hello GL](examples/1.md) + * [Saturate an Image](examples/2.md) + * [Hue Rotate on Text+Image](examples/3.md) + * [Progress Indicator](examples/4.md) + * [Touch Responsive](examples/5.md) + * [Animation](examples/6.md) +* [Advanced Effects Examples](examples/advancedeffects.md) diff --git a/docs/examples/1.jpg b/docs/examples/1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2e6329794c9280231f5675204cd95dcfb6823f63 Binary files /dev/null and b/docs/examples/1.jpg differ diff --git a/docs/examples/1.md b/docs/examples/1.md index 8b137891791fe96927ad78e64b0aad7bded08bdc..1fdd588d3797836b71e940efc07d132d784b2884 100644 --- a/docs/examples/1.md +++ b/docs/examples/1.md @@ -1 +1,46 @@ +# Hello GL +This minimal example shows the classical "Hello World" of OpenGL 2D drawing, showing a nice 2D gradient where: + +- The RED component increases with the X position of the pixel. +- The GREEN component increases with the Y position of the pixel. + +> N.B. a GLSL fragment uses a ["functional rendering"](http://greweb.me/2013/11/functional-rendering/) +paradigm, which means you have to implement a `Position => Color` function. + +![](1.jpg) + +```html + +``` + +## Implementation + +```js +const React = require("react-native"); +const GL = require("gl-react-native"); + +const shaders = GL.Shaders.create({ + helloGL: { + frag: ` +precision highp float; +varying vec2 uv; // This variable vary in all pixel position (normalized from vec2(0.0,0.0) to vec2(1.0,1.0)) + +void main () { // This function is called FOR EACH PIXEL + gl_FragColor = vec4(uv.x, uv.y, 0.5, 1.0); // red vary over X, green vary over Y, blue is 50%, alpha is 100%. +} + ` + } +}); + +class HelloGL extends React.Component { + render () { + const { width, height } = this.props; + return ; + } +} +``` diff --git a/docs/examples/2.gif b/docs/examples/2.gif new file mode 100644 index 0000000000000000000000000000000000000000..8685b526bddfd0fff3706e629a737a0f6134c3c6 Binary files /dev/null and b/docs/examples/2.gif differ diff --git a/docs/examples/2.md b/docs/examples/2.md new file mode 100644 index 0000000000000000000000000000000000000000..8ffdc265a9b663129ffd531e67a00b5b754f5773 --- /dev/null +++ b/docs/examples/2.md @@ -0,0 +1,51 @@ +# Saturate an Image + +Effects on images like *saturation, contrast, brightness, inverse, hue,...* are very easy to implement in GLSL. Here is the example of **Saturation**. + +![](2.gif) + +```html + +``` + +## Implementation + +```js +const React = require("react-native"); +const GL = require("gl-react-native"); + +const shaders = GL.Shaders.create({ + saturation: { + frag: ` +precision highp float; +varying vec2 uv; +uniform sampler2D image; +uniform float factor; + +void main () { + vec4 c = texture2D(image, uv); + // Algorithm from Chapter 16 of OpenGL Shading Language + const vec3 W = vec3(0.2125, 0.7154, 0.0721); + gl_FragColor = vec4(mix(vec3(dot(c.rgb, W)), c.rgb, factor), c.a); +} + ` + } +}); + +class Saturation extends React.Component { + render () { + const { width, height, factor, image } = this.props; + return ; + } +} +``` diff --git a/docs/examples/3.gif b/docs/examples/3.gif new file mode 100644 index 0000000000000000000000000000000000000000..ebdf3f60680d4f45fdcc80c345cb365578db1036 Binary files /dev/null and b/docs/examples/3.gif differ diff --git a/docs/examples/3.md b/docs/examples/3.md new file mode 100644 index 0000000000000000000000000000000000000000..d2f1716df16fd4cff460fa9369dd4accc1108dd5 --- /dev/null +++ b/docs/examples/3.md @@ -0,0 +1,63 @@ +# Hue Rotate on Text+Image + +`gl-react-native` not only allow to add effects on top of images but also on top of any content. This example shows the Hue rotation effect on top of texts and image. + +![](3.gif) + +```html + + + Throw me to the wolves + {text} + +``` + +## Implementation + +```js +const React = require("react-native"); +const GL = require("gl-react-native"); + +const shaders = GL.Shaders.create({ + hueRotate: { + frag: ` +precision highp float; +varying vec2 uv; +uniform sampler2D tex; +uniform float hue; + +const mat3 rgb2yiq = mat3(0.299, 0.587, 0.114, 0.595716, -0.274453, -0.321263, 0.211456, -0.522591, 0.311135); +const mat3 yiq2rgb = mat3(1.0, 0.9563, 0.6210, 1.0, -0.2721, -0.6474, 1.0, -1.1070, 1.7046); + +void main() { + vec3 yColor = rgb2yiq * texture2D(tex, uv).rgb; + float originalHue = atan(yColor.b, yColor.g); + float finalHue = originalHue + hue; + float chroma = sqrt(yColor.b*yColor.b+yColor.g*yColor.g); + vec3 yFinalColor = vec3(yColor.r, chroma * cos(finalHue), chroma * sin(finalHue)); + gl_FragColor = vec4(yiq2rgb*yFinalColor, 1.0); +} + ` + } +}); + +class HueRotate extends React.Component { + render () { + const { width, height, hue, children } = this.props; + return + {children} + ; + } +} +``` + +The `GL.Target` describes which texture uniform is used for the rasterization of its children content. + +Note how powerful it is to compose React Components that way. diff --git a/docs/examples/4.gif b/docs/examples/4.gif new file mode 100644 index 0000000000000000000000000000000000000000..76b7da1ed82e1fcf6010569b7578d6cb5fb7311c Binary files /dev/null and b/docs/examples/4.gif differ diff --git a/docs/examples/4.md b/docs/examples/4.md new file mode 100644 index 0000000000000000000000000000000000000000..54c7679c41d992faf5e27c4984ae4497b1991982 --- /dev/null +++ b/docs/examples/4.md @@ -0,0 +1,125 @@ +# Progress Indicator + +This example show a graphical composant implemented in OpenGL that would not be easy to implement with raw RN components. + +> N.B. the pie is transparent and the background is black with 50% opacity. That would easily allow to put this view on top of an image to show an image upload progress. + +![](4.gif) + +```html + +``` + +*explicit props:* + +```html + +``` + +## Implementation + +```js +const React = require("react-native"); +const GL = require("gl-react-native"); + +const shaders = GL.Shaders.create({ + pieProgress: { + frag: ` +precision mediump float; +varying vec2 uv; + +uniform vec4 colorInside, colorOutside; +uniform float radius; +uniform float progress; +uniform vec2 dim; + +const vec2 center = vec2(0.5); +const float PI = acos(-1.0); + +void main () { + vec2 norm = dim / min(dim.x, dim.y); + vec2 p = uv * norm - (norm-1.0)/2.0; + vec2 delta = p - center; + float inside = + step(length(delta), radius) * + step((PI + atan(delta.y, -delta.x)) / (2.0 * PI), progress); + gl_FragColor = mix( + colorOutside, + colorInside, + inside + ); +} + ` + } +}); + +class PieProgress extends React.Component { + render () { + const { + width, + height, + progress, + colorInside, + colorOutside, + radius + } = this.props; + return ; + } +} +PieProgress.defaultProps = { + colorInside: [0, 0, 0, 0], + colorOutside: [0, 0, 0, 0.5], + radius: 0.4 +}; +``` + +Note the usage of complex uniform types. + + +The JavaScript uniform: +```js +dim: [ width, height ] +``` + +is mapped to GLSL uniform: +```glsl +uniform vec2 dim; +``` + +where dim.x == width and dim.y == height. + + +Same mapping happens for the colors: `[1, 0.5, 0.2, 1]` mapped to a `vec4` will spread into {r,g,b,a}. + +> Complex types in GLSL have "dynamic" properties: +vecN `v` can simultaneously be used using: + +>``` +v[0] v[1] v[2] v[3] // array notation +v.r v.g v.b v.a // color notation +v.x v.y v.z v.w // position notation +v.s v.t v.u v.v // coord notation +``` diff --git a/docs/examples/5.gif b/docs/examples/5.gif new file mode 100644 index 0000000000000000000000000000000000000000..39a35708e203f0ad953193319e2a4b55176f165a Binary files /dev/null and b/docs/examples/5.gif differ diff --git a/docs/examples/5.md b/docs/examples/5.md new file mode 100644 index 0000000000000000000000000000000000000000..b687f90ce753c8699811f3194f4d0f611f387022 --- /dev/null +++ b/docs/examples/5.md @@ -0,0 +1,91 @@ +# Touch Responsive + +This example shows a simple rendering that responds to touch events (one finger only). + +![](5.gif) + +```html + +``` + +## Implementation + +```js +const React = require("react-native"); +const GL = require("gl-react-native"); + +const shaders = GL.Shaders.create({ + oneFingerResponse: { + frag: ` +precision mediump float; +varying vec2 uv; + +uniform float pressed; +uniform vec2 position; + +void main () { + float dist = pow(1.0 - distance(position, uv), 4.0); + float edgeDistX = pow(1.0 - distance(position.x, uv.x), 24.0); + float edgeDistY = pow(1.0 - distance(position.y, uv.y), 24.0); + gl_FragColor = pressed * vec4(0.8 * dist + edgeDistX, 0.7 * dist + edgeDistY, 0.6 * dist, 1.0); +} + ` + } +}); + +class OneFingerResponse extends React.Component { + constructor (props) { + super(props); + this.state = { + pressed: 0, + position: [ 0, 0 ] + }; + this.onTouchStart = this.onTouchStart.bind(this); + this.onTouchEnd = this.onTouchEnd.bind(this); + this.onTouchMove = this.onTouchMove.bind(this); + } + onTouchStart (evt) { + this.setState({ + pressed: 1 + }); + this.onTouchMove(evt); + } + onTouchMove (evt) { + const { width, height } = this.props; + const { locationX, locationY } = evt.nativeEvent; + this.setState({ + position: [ + Math.max(0, Math.min(locationX/width, 1)), + Math.max(0, Math.min(1-locationY/height, 1)) + ] + }); + } + onTouchEnd () { + this.setState({ + pressed: 0 + }); + } + render () { + const { width, height } = this.props; + const { pressed, position } = this.state; + return true} + onMoveShouldSetResponderCapture={() => true} + onResponderTerminationRequest={() => false} + onResponderGrant={this.onTouchStart} + onResponderMove={this.onTouchMove} + onResponderRelease={this.onTouchEnd} + onResponderTerminate={this.onTouchEnd} + width={width} + height={height} + shader={shaders.oneFingerResponse} + uniforms={{ pressed, position }} + />; + } +} +``` + +The GLSL code shown here is a bit more advanced, we recommend you to take a look at the [GLSL specification](https://www.opengl.org/documentation/glsl/) to learn what the primitive function are doing. diff --git a/docs/examples/6.gif b/docs/examples/6.gif new file mode 100644 index 0000000000000000000000000000000000000000..55e46faf917ed3dfd253a0405f08001f64767d94 Binary files /dev/null and b/docs/examples/6.gif differ diff --git a/docs/examples/6.md b/docs/examples/6.md new file mode 100644 index 0000000000000000000000000000000000000000..1dd13020edbfe54b3ab56a6666a2a3fa66e155cc --- /dev/null +++ b/docs/examples/6.md @@ -0,0 +1,59 @@ +# Animation + +Any value can be programmatically animated in a render loop. This example extends the simple [Hello GL](1.md) to add a `value` uniform that is passed in blue color component. `value` is animated over time. + +![](6.gif) + +```html + +``` + +## Implementation + +```js +const React = require("react-native"); +const GL = require("gl-react-native"); + +const shaders = GL.Shaders.create({ + helloGL: { + frag: ` +precision highp float; +varying vec2 uv; + +uniform float value; + +void main () { + gl_FragColor = vec4(uv.x, uv.y, value, 1.0); +} + ` + } +}); + +class HelloGL extends React.Component { + constructor (props) { + super(props); + this.state = { + value: 0 + }; + } + componentDidMount () { + const loop = time => { + requestAnimationFrame(loop); + this.setState({ + value: (1 + Math.cos(time / 1000)) / 2 // cycle between 0 and 1 + }); + }; + requestAnimationFrame(loop); + } + render () { + const { width, height } = this.props; + const { value } = this.state; + return ; + } +} +``` diff --git a/docs/examples/advancedeffects.gif b/docs/examples/advancedeffects.gif new file mode 100644 index 0000000000000000000000000000000000000000..ce9d74c7d8be1ac0b045fe5db4d0aad882fdc187 Binary files /dev/null and b/docs/examples/advancedeffects.gif differ diff --git a/docs/examples/advancedeffects.md b/docs/examples/advancedeffects.md new file mode 100644 index 0000000000000000000000000000000000000000..2992eac90e0aaf2d9cbf20ec24a37acf87c79b62 --- /dev/null +++ b/docs/examples/advancedeffects.md @@ -0,0 +1,9 @@ +# AdvancedEffects examples + +*AdvancedEffects* is a React Native application that shows more advanced `gl-react-native` samples. + +It performs non trivial animated effects that still runs at 60 FPS on a iPhone. + +It is available in [`Examples/AdvancedEffects`](https://github.com/ProjectSeptemberInc/gl-react-native/tree/master/Examples/AdvancedEffects) folder. + +![](advancedeffects.gif) diff --git a/docs/examples/simple.gif b/docs/examples/simple.gif new file mode 100644 index 0000000000000000000000000000000000000000..cbb9c67eb6ee8af9e06d832286d36aea0c544ab1 Binary files /dev/null and b/docs/examples/simple.gif differ diff --git a/docs/examples/simple.md b/docs/examples/simple.md new file mode 100644 index 0000000000000000000000000000000000000000..3829d2e1b6e11fa299bd2f3e472fc83d8da5df38 --- /dev/null +++ b/docs/examples/simple.md @@ -0,0 +1,7 @@ +# Simple examples + +*Simple* is a React Native application that shows minimal `gl-react-native` samples. + +It is available in [`Examples/Simple`](https://github.com/ProjectSeptemberInc/gl-react-native/tree/master/Examples/Simple) folder. + +![](simple.gif)