Expert Tutorial


Custom Shader


15 minutes

Posted on: October 25, 2017

Tags:

shader
Learn Sumerian
Custom Shader

Tags

shader

In this tutorial you will learn about:

Custom Shaders
Materials
Scripting

This tutorial shows how to add a custom shader to an Amazon Sumerian entity.

You’ll learn about:

  • Custom Shaders
  • Materials
  • Scripting

Prerequisites

Before you begin, you should have completed the following tasks and tutorials:

Step 1: Create a New Scene

From the Dashboard, create a scene from the Default Lighting template.

Step 2: Create a Cube

We need an entity to apply the shader to. Choose Create Entity in the top menu, and then select Box.

Step 2: Add a Minimal Custom Shader Script

To add a shader to the Box, we create a custom script.

  1. Select the Box.

  2. Choose Add Component in the Inspector panel.

  3. Choose Script, and then select Custom Script.

  4. Click the edit button on the new script to open the script editor.

  5. Copy the following script, paste it into the script editor, and then click Save.

var setup = function (args, ctx) {

    // The vertex shader source as a string
    var vertexShader = [
        'attribute vec3 vertexPosition;',
        'uniform mat4 viewProjectionMatrix;',
        'uniform mat4 worldMatrix;',
        'void main(void) {',
        ' gl_Position = viewProjectionMatrix * (worldMatrix * vec4(vertexPosition, 1.0));',
        '}'
    ].join('\n');

    // The fragment shader source as a string
    var fragmentShader = [
        'void main(void){',
        ' gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);',
        '}'
    ].join('\n');

    // The shader definition provides fragment and vertex shader source, as
    // well as a list of attributes, defines, uniforms, etc., that the engine
    // needs to use your shader.
    var shaderDefinition = {
        attributes: {
            vertexPosition: sumerian.MeshData.POSITION
        },
        uniforms: {
            viewProjectionMatrix: sumerian.Shader.VIEW_PROJECTION_MATRIX,
            worldMatrix: sumerian.Shader.WORLD_MATRIX
        },
        vshader: vertexShader,
        fshader: fragmentShader
    };

    // Create a Material from the shader definition.
    ctx.material = new sumerian.Material(shaderDefinition);

    // Save the current material for later.
    ctx.oldMaterial = ctx.entity.meshRendererComponent.materials[0];

    // Put the material on the entity.
    ctx.entity.meshRendererComponent.materials[0] = ctx.material;
};

var update = function (args, ctx) {

};

var cleanup = function (args, ctx) {
    // Put the old material back on the entity
    ctx.entity.meshRendererComponent.materials[0] = ctx.oldMaterial;
};

var parameters = [];

Now when you play your scene, the custom shader material replaces the current material on the Box. The Box should only be white.

If you’re using a Mac, you may need to use the following script:

// The vertex shader source as a string
var vertexShader = [
'attribute vec3 vertexPosition;',
'uniform mat4 viewProjectionMatrix;',
'uniform mat4 worldMatrix;',
'void main(void) {',
' gl_Position = viewProjectionMatrix * (worldMatrix * vec4(vertexPosition, 1.0));',
'}'
].join('\n');

// The fragment shader source as a string
var fragmentShader = [
'void main(void){',
    ' gl_FragColor = vec4(1,1,1, 1);',
'}'
].join('\n');


// The shader definition provides fragment and vertex shader source, as
// well as a list of attributes, defines, uniforms, etc., that the engine
// needs to use your shader.
var shaderDefinition = {
defines: {
    PI: Math.PI // <-- add define here
},
attributes: {
    vertexPosition: sumerian.MeshData.POSITION,
},
uniforms: {
    viewProjectionMatrix: sumerian.Shader.VIEW_PROJECTION_MATRIX,
    worldMatrix: sumerian.Shader.WORLD_MATRIX,
},
vshader: vertexShader,
fshader: fragmentShader
};

Step 3: Update the Shader to a Plasma Shader

In the vertex shader source, we want to add the UV coordinates from the mesh as an attribute. Then we’ll pass it to the fragment shader via a varying. Replace the vertexShader, _fragmentShader, and shaderDefinition with the following.

var vertexShader = [
    'attribute vec2 vertexUV0;',  // <-- add UV coords
    'attribute vec3 vertexPosition;',
    'uniform mat4 viewProjectionMatrix;',
    'uniform mat4 worldMatrix;',
    'varying vec2 texCoord0;',  // <-- add varying
    'void main(void) {',
    ' texCoord0 = vertexUV0;', // <-- assign the vertex UV coordinate to the varying
    ' gl_Position = viewProjectionMatrix * (worldMatrix * vec4(vertexPosition, 1.0));',
    '}'
].join('\n');

In the fragment shader, we replace almost everything that we had in the previous shader. We also add some magic formulas to get the plasma effect. We add a couple of uniforms, time and k. Also note that we assume a define named PI.

var fragmentShader = [
    'uniform float time;', // <-- add
    'uniform vec2 k;', // <-- add
    'varying vec2 texCoord0;', // <-- add
    'void main(void){',
    ' float v = 0.0;',  // <-- add all of these magic formulas
    ' vec2 c = texCoord0 * k - k/2.0;',
    ' v += sin(c.x + time);',
    ' v += sin((c.y + time)/2.0);',
    ' v += sin((c.x+c.y + time)/2.0);',
    ' c += k/2.0 * vec2(sin(time/3.0), cos(time/2.0));',
    ' v += sin(sqrt(c.x*c.x+c.y*c.y+1.0) + time);',
    ' v = v/2.0;',
    ' vec3 col = vec3(1, sin(PI*v), cos(PI*v));',
    ' gl_FragColor = vec4(col*.5 + .5, 1);',
    '}'
].join('\n');

Now we have to declare our new define, attributes, and uniforms in the shader definition.

var shaderDefinition = {
    defines: {
        PI: Math.PI // <-- add define here
    },
    attributes: {
        vertexPosition: sumerian.MeshData.POSITION,
        vertexUV0: sumerian.MeshData.TEXCOORD0 // <-- grab the UV coords from the mesh
    },
    uniforms: {
        viewProjectionMatrix: sumerian.Shader.VIEW_PROJECTION_MATRIX,
        worldMatrix: sumerian.Shader.WORLD_MATRIX,
        time: 0, // <-- add the time uniform
        k: [20,20] // <-- pass the vec2 value directly, as an array
    },
    vshader: vertexShader,
    fshader: fragmentShader
};

All we have left to do is update the time value. It’s always zero by default. Pass the updated uniform value via the material into the shader. The material.uniforms overrides the values set on the shader.

var update = function (args, ctx) {
    // Pass the current time via uniforms to the shader.
    // This will override the "time" uniform on the shader.
    ctx.material.uniforms.time = ctx.world.time; // <-- add this!
};

Step 4: Press Play

When you press the Play button, the setup() function runs, and the shader is added to the cube.

Publish and share the scene!

In this tutorial, you’ve learned how to use custom shaders. For more information on this topic, view the following tutorials:

Back to Tutorials

© 2019 Amazon Web Services, Inc or its affiliates. All rights reserved.