GRAPHIC SHADERS

Richard Mat­tka con­tin­ues to ex­plore shaders, fo­cus­ing on how to use them in three.js and load from pop­u­lar sources such as Shader­toy

net magazine - - CONTENTS -

Richard Mat­tka ex­plores how you can use shaders in three.js

Shaders en­able a wide range of ef­fects by work­ing with the graph­ics hard­ware of de­vices. This tu­to­rial will show you how to bring our pre­vi­ous Shader­toy ex­per­i­ments into three.js so you can be­gin to use them in your web sites and apps.

Shaders are in­cred­i­bly pow­er­ful for vi­su­al­is­ing com­plex im­agery and an­i­ma­tions be­cause of their op­ti­mised per­for­mance. Three.js is a pop­u­lar We­bGL 3D ren­der­ing li­brary writ­ten in JavaScript. It al­ready uses shaders and en­ables cus­tom shaders as a ma­te­rial type for ob­jects. Com­bine these tech­nolo­gies for amaz­ing real-time an­i­ma­tions.

Set up a Ba­sic HTML page

Start by set­ting up the HTML page and some ba­sic CSS to size things for full screen. We’re go­ing to use three.js to han­dle draw­ing to the can­vas. Add a ref­er­ence to the three.js li­brary. You can grab it here: https://github.com/mr­doob/three.js

<! DOCTYPE html> <html lang=”en”> <head>

< script src=” three. min. js”></script> < style> html, body { mar­gin: 0; pad­ding: 0; } can­vas { width: 100%; height: 100% }

</style> </ head> <body> < script> // main code here </script> </ body> </ html>

Tag Ver­tex and Frag­ment Shaders

You can load shaders in dif­fer­ent ways. Best prac­tice is to sep­a­rate these shaders into their own files and load as needed via JavaScript. You’ll learn how to do that in the next tu­to­rial but, for now, use this in­line tech­nique. It is great for sim­ple pro­to­types and quick tests. Add this code in the <head> of your HTML.

< script id=” ver­texShader” type=“x- shader/ x- ver­tex”> </script>

< script id=” frag­men­tShader” type=“x- shader/ xfrag­ment”> </script>

Cre­ate a Shader­Toy Shader

As in pre­vi­ous ar­ti­cles, you’ll make use of the We­bGL ren­der­ing tool, Shader­toy. It gives you a nice code win­dow to test in and a ren­der win­dow to see your work. Once you see the flow of cre­at­ing your own shader and load­ing it into three.js, you can use pre­vi­ous ones you’ve cre­ated or grab other ones as start­ing points for in­spi­ra­tion.

To be­gin, go here: ( www.shader­toy.com/new) in a browser that sup­ports We­bGL. You will see a sam­ple shader code all ready to go for you in the code win­dow. Delete it, so that you’re able to start writ­ing your own.

Type in this new code and then press the black Play icon at the bot­tom of the win­dow. This will ex­e­cute the shader code.

void mainI­mage( out vec4 fragColor, in vec2 fragCo­ord ){

vec2 uv = fragCo­ord. xy/ iRes­o­lu­tion. xy; // nor­mal­ize vec2 p = ( 2. * uv - 1.); // cen­ter float d=. 01; // in­ten­sity vec3 col; // color

float l = length( p); float t = iTime*. 25; // ad­just speed for( int i= 0; i<=3; i++) { uv+= p/ l*( sin( l - t)); col[ i]= d/ length(mod(uv,1.0)-. 5); } fragColor= vec4(col/ l, 0.); }

Cre­ate a Ver­tex Shader

Shader­toy shaders are con­cerned with the frag­ment shader por­tion of the pipe­line. That’s what you just cre­ated. In three.js you also can work on the ver­tex shader. Add a sim­ple ver­tex shader to your code now; this will pass through the po­si­tion data to the frag­ment shader. In fu­ture tu­to­ri­als you will learn to do more with this shader to ma­nip­u­late the ge­om­e­try of ob­jects and much more. For now, add this ver­tex shader code be­tween your ver­tex shader script tags:

vary­ing vec2 vUv; void main() { vUv = uv; vec4 mvPo­si­tion = mod­elViewMa­trix * vec4( po­si­tion, 1.0 );

gl_Po­si­tion = pro­jec­tionMa­trix * mvPo­si­tion; }

Add and Up­date Frag­ment Shader

Copy and paste the code you cre­ated in Shader­toy into the frag­ment shader sec­tion of your HTML.

There are a few things to ad­just when mi­grat­ing code from ex­ter­nal sources. First, ref­er­ences to FragCo­ord need to be up­dated to gl_FragCo­ord, and FragColor to gl_FragColor to match up to three. js’s in­ter­nal ref­er­ences. If you use func­tions for load­ing tex­tures such as “tex­ture”, it needs to use the sup­ported “tex­ture2D”.

Some code may use vari­ables (uni­forms) such as iGlob­alTime in­stead of iTime or res­o­lu­tion in­stead of iRes­o­lu­tion. Make sure those are con­sis­tent in your shader code and the uni­forms you’ll set up later. Lastly, up­date the mainI­mage func­tion dec­la­ra­tion to use the sim­pler form “main” with no pa­ram­e­ters.

Your new frag­ment shader code be­tween the shader’s script tags should now look like this:

uni­form float iTime; uni­form vec2 iRes­o­lu­tion; void main(){ vec2 uv = gl_FragCo­ord. xy/ iRes­o­lu­tion. xy; // nor­mailze vec2 p = ( 2. * uv - 1.); // cen­ter float d=. 01; // in­ten­sity vec3 col; // color float l = length( p); float t = iTime*. 25; // ad­just speed for( int i= 0; i<=3; i++) { uv+= p/ l*( sin( l - t)); col[ i]= d/ length(mod(uv,1.0)-. 5); } gl_FragColor= vec4(col/ l, 0.); }

Global Vari­abl es and Init Func­tion

Be­tween the main script tags you de­fined ear­lier, add these global vari­ables for your cam­era, scene and ren­derer. You will also use the three.js clock class to up­date the iTime Uni­form in your shader. You’ll also call your main init func­tion and cre­ate its dec­la­ra­tion. Add this code:

var cam­era, scene, ren­derer; var uni­forms, ma­te­rial, mesh; var startTime = Date. now(); var clock = new THREE. Clock(); init(); func­tion init() {

// init code }

Set up the Three.js Scene

In­side the init func­tion you need to set up a scene, cam­era and ren­derer. Then you can add a can­vas

el­e­ment via the ren­derer’s “domEle­ment” prop­erty. This process is sim­i­lar in all set­ups you’ll do in three. js. Add this code next:

//cre­ate a Three. js scene scene = new THREE. Scene(); // add a cam­era cam­era = new THREE. Per­spec­tiveCam­era( 45, win­dow. in­nerWidth / win­dow. in­nerHeight, 1, 1000000 ); cam­era. po­si­tion. z = 1; // add the ren­derer ren­derer = new THREE.We­bGLRen­derer({an­tialias: true}); ren­derer. setSize( win­dow. in­nerWidth, win­dow. in­nerHeight ); doc­u­ment. body. ap­pendChild( ren­derer. domEle­ment );

We moved the cam­era back a lit­tle us­ing the po­si­tion prop­erty so it would not oc­cupy the same po­si­tion as the plane we are go­ing to ren­der our shader on.

De­fine the Uni­forms

Next, you need to cre­ate an JSON ob­ject to hold the uni­form values that cor­re­spond to your frag­ment shader’s uni­forms. You’ve used iTime and iRes­o­lu­tion in your shader but if you mod­i­fied your shader code, be sure to in­clude all the uni­forms you wish to up­date. Add this code also in­side the init func­tion.

// set up uni­forms uni­forms = { iTime: { type: “f”, value: 10000.0 }, iRes­o­lu­tion: { type: “v2”, value: new THREE.Vec­tor2() } }; uni­forms. iRes­o­lu­tion.value. x = win­dow. in­nerWidth; uni­forms. iRes­o­lu­tion.value.y = win­dow. in­nerHeight;

Cre­ate A Cust om Shader Ma­te­rial

Three.js en­ables you to cre­ate shaders as ma­te­ri­als for ob­jects. De­clare the ShaderMa­te­rial in­side your init func­tion, and as­sign your uni­forms JSON ob­ject and the two shaders you cre­ated pre­vi­ously, like this:

// cre­ate cus­tom shader ma­te­rial ma­te­rial = new THREE. ShaderMa­te­rial( { uni­forms: uni­forms, ver­texShader: doc­u­ment.getEle­men­tById( ‘ ver­texShader’ ).tex­tCon­tent,

frag­men­tShader: doc­u­ment.getEle­men­tById( ‘ frag­men­tShader’ ).tex­tCon­tent });

3D Plane with the Shader Ma­te­rial

Next, cre­ate a 3D mesh by com­bin­ing the built-in three.js PlaneGeom­e­try and the new ma­te­rial you cre­ated. Set the scale of the plane to match the full size of the screen. Then add it to the scene. Do that by adding this code in­side your init func­tion:

// cre­ate ob­ject mesh var ge­om­e­try = new THREE. PlaneGeom­e­try( 1, 1 ); var mesh = new THREE. Mesh( ge­om­e­try, ma­te­rial ); mesh. scale. x = win­dow. in­nerWidth; mesh. scale.y = win­dow. in­nerHeight; scene. add(mesh); You now have a 3D ob­ject mesh with a cus­tom shader ma­te­rial. You’re just about ready to see it in ac­tion.

Cre­ate a Ren­der An­i­ma­tion Loop

To see the scene ren­dered by the cam­era, you need to ren­der a frame. You need to call and cre­ate your an­i­ma­tion loop. You bind this to the re­questAn­i­ma­tionFrame func­tion to en­sure that it runs as op­ti­mally as pos­si­ble for the browser (ide­ally this should be 60fps).

First, add the call to the an­i­ma­tion func­tion as the last line in your init func­tion: an­i­mate(); Then, cre­ate a new func­tion for an­i­ma­tion out­side the init func­tion like this:

func­tion an­i­mate() { re­questAn­i­ma­tionFrame( an­i­mate ); ren­der(); }

Up­date Shader Uni­forms for Each Ren­der

For each frame, you need to in­cre­ment the uni­form’s iTime by the amount of time that has passed since the last frame. You will use the clock class that you de­clared pre­vi­ously to do this. Add this fi­nal code as a new func­tion:

func­tion ren­der() { uni­forms. iTime.value += clock. getDelta() ; ren­derer. ren­der( scene, cam­era ); }

Now, when you run the code you will see your shader, con­verted from Shader­toy run­ning in three.js. It is just the start but you can tweak and run your own shaders in your own code. This means you can in­clude them as back­grounds on your web page or app or as other lay­ers where you wish. Next tu­to­rial, we’ll ex­plore us­ing shaders as ma­te­rial on more com­plex ob­jects and make the shader move with the ob­ject in 3D space.

Newspapers in English

Newspapers from Australia

© PressReader. All rights reserved.