Shadertoy to PlayCanvas: Sirenian Dawn

  • What is Shadertoy?
  • How to transfer a Shadertoy shader to PlayCanvas?
  • Why transfer Shadertoy shaders to PlayCanvas?

This tutorial aims to introduce a series of shader effects and how to transfer them for use in PlayCanvas. If you find this useful, educational or just cool, support us on Patreon to speed everything up!

Shadertoy: Sirenian Dawn
https://playcanv.as/index/S7QMAyop/

Launch fullscreen!

The original shader, Sirenian Dawn authored by nimitz, can be found here.

This tutorial is for advanced users and assumes that you are already aware of how the PlayCanvas shader chunks system works. You can study this tutorial to learn more on how to begin with PlayCanvas and shaders.

 

What is Shadertoy?

If you haven't heard about it, just head over right now and explore the coolest community of math geniuses sharing their imaginative creations.

Shadertoy Homepage

You can find from simple/single shader effects commonly used in 3D apps to complete full scenes that bring the demo scene online.

About Shadertoy

Shadertoy is the first application to allow developers all over the globe to push pixels from code to screen using WebGL since 2009. This website is the natural evolution of that original idea. On one hand, it has been rebuilt in order to provide the computer graphics developers and hobbyists with a great platform to prototype, experiment, teach, learn, inspire and share their creations with the community. On the other, the expressiveness of the shaders has arisen by allowing different types of inputs such as video, webcam or sound.

The shaders found in Shadertoy all have their code source available to read/study/tweak to your liking. It is a great learning resource.

Now you might want to ask, are those shaders free to use? And on what terms? The first step is to check if the shader comes with a license notice on top. If yes, then that's the license you must read and adhere to if you plan in using that shader. If there isn't any license notice there, then all shaders created in Shadertoy are protected by the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. You should study and take action in making your app compatible with those terms if you use that shader.

What is Creative Commons?

Creative Commons is an American non-profit organization devoted to expanding the range of creative works available for others to build upon legally and to share. The organization has released several copyright-licenses known as Creative Commons licenses free of charge to the public. These licenses allow creators to communicate which rights they reserve, and which rights they waive for the benefit of recipients or other creators.

 

How to transfer a Shadertoy shader to PlayCanvas?

We have posted a tutorial explaining how to transfer a basic Shadertory shader to PlayCanvas back here. Here we will present a more complex shader and the additional steps required to get it running.

Shadertoys are fragment or pixel shaders. A good to place to start in using them is to use the shader chunks system.

1. Select which chunk you will use

Usually that would be the diffuse map of a material, as most shaders return coloured information. That is, something you can render and see directly. On rarer cases, it might be a different channel e.g. as in a shader that calculates the opacity of an object/scene you might want to use the opacity map.

// editor attributes to reference assets
EffectPalette.attributes.add('shader', { type: 'asset', assetType: 'shader' });

...

// usually you write your GLSL as a shader asset and you insert it as a chunk
this.material.chunks.diffuseTexPS = this.shader.resource;

 

2. Fill the missing uniforms

All Shadertoys come with some uniforms that are automatically added in the Shadertoy site. We need to add them back using alternatives that we can come up with.

Common uniforms that are missing and you should add to the top of your GLSL code:

uniform sampler2D iChannel0;
uniform float iTime;
uniform vec3 iResolution;
uniform vec4 iMouse;

iChannel0, 1, 2 etc. are textures that are used in the shader. You will need to use a texture of your own and pass it as a parameter to the shader:

EffectPalette.attributes.add('dithering', { type: 'asset', assetType: 'texture' });

...

// push dithering texture to shader
this.material.setParameter("iChannel0", this.dithering.resource);

iTime is a simple timer counting seconds from the time the app starts. You can recreate it with a simple number variable and the PlayCanvas update loop:

EffectPalette.prototype.initialize = function() {

    this.timer = 0.0;    
};

// update code called every frame
EffectPalette.prototype.update = function(dt) {
    
    this.timer += dt;
    this.material.setParameter("iTime", this.timer);
};

iResolution is the current viewport resolution. We can pass easily the current width and height of the window, and we should take care of window resizing and pass the parameters again:

// push utility uniforms
this.material.setParameter("iResolution", new pc.Vec3(this.app.graphicsDevice.width, this.app.graphicsDevice.height, 0.0).data);

window.onresize = function(event) {

   this.material.setParameter("iResolution", new pc.Vec3(this.app.graphicsDevice.width, this.app.graphicsDevice.height, 0.0).data);

}.bind(this);

 

3. Tweaking the GLSL code

There are some things that are mapped differently in PlayCanvas, especially since we are using the shader chunk system.

  • The fragCoord variable doesn't exist on our context and should be replaced with the $UV variable.
  • The void mainImage() method should be replaced with our shader chunk return method, in the diffuse map case that would be the getAlbedo() method.
  • The fragColor return variable should be replaced with our shader chunk return variable, in the diffuse map case that would be the dAlbedo variable. 

One thing that might be tricky to convert properly are the uv coordinates. In a general case you will find in the original Shadertoy code written:

vec2 p = fragCoord.xy / iResolution.xy;

You should convert this line to:

vec2 p = -1.0 + 2.0 *$UV;
p.x *= iResolution.x/ iResolution.y;

 

4. Input events

Shadertoy exposes an iMouse uniform to help with getting user input to control the shader (e.g. camera).

In PlayCanvas you will be most likely building a more complex app and you will require finer control over input. What I usually do is pass the forward/up vectors of the camera to the shader to control the rendered output. For example:

// update code called every frame
EffectPalette.prototype.update = function(dt) {
   
    // look dir
    this.material.setParameter("cameraForward", this.camera.forward.data);
    this.material.setParameter("cameraUp", this.camera.up.data);
    this.material.setParameter("cameraRight", this.camera.right.data);
      
};

And in the shader use them like this:

uniform vec3 cameraForward;
uniform vec3 cameraUp;
uniform vec3 cameraRight;

...

vec3 eye = cameraForward;
vec3 right = cameraRight;
vec3 up = cameraUp;

 

Why transfer Shadertoy shaders to PlayCanvas?

Come on! Is that a question? Because it is so cool.

Seriously though, combining a high level language/framework as is PlayCanvas and its scripting JS API with the low level access that shaders/WebGL provide is exceptionally powerful.

Shadertoy provides a great resource to learn on how to write your own or extend awesome shaders that are provided by the community.

For example the shader that is provided with this tutorial can be used to calculate and render a planetary atmosphere over a terrain model, when used as a screen space effect:

Planet Atmosphere shader
https://playcanv.as/index/OGIQJeFF/

 

Let's get started!

You can find the public project to fork and play with here.

The original shader can be accessed here.

So enjoy... and don't forget to support us on Patreon!