W4 Material Creation

From Ciliz|W4

Scope

Assuming that you have read the article W4 Physics for Dummies, and are willing to explore the W4 Engine further study the cases below.

This page describes how to create material and use code from external resources.

Basic Information

As an example, the material is created based on the shader from the www.shadertoy.com.

Take the following code as the basis:

#include "W4Framework.h"

W4_USE_UNSTRICT_INTERFACE

struct ShadertoyDemo : public IGame
{
public:
    void onStart() override
    {
        m_cube = Mesh::create::cube({2.f, 2.f, 2.f});
        Render::getRoot()->addChild(m_cube);
    }

    virtual void onTouch(const event::Touch::Begin&) override
    {
    }

    void onUpdate(float dt) override
    {
        m_cube->rotate(Rotator{dt,dt,dt});
    }

private:
    sptr<Mesh> m_cube;
};

W4_RUN(ShadertoyDemo)

Run the program. A spinning cube will appear, as in the figure below:

Material_01.png

The material will be added on a later stage.

Project Structure

The structure of the project:

.
├── resources
   └── materials
       ├── shaders
          ├── topologica.fs
          └── topologica.vs
       └── topologica.mat
└── sources
    └── sample.cpp

In this case, topologia.fs and topologia.vs are the fragment and vertex shaders, accordingly, while topologia.mat is the material file, and sample.cpp is the program code.

Shader Creation

To test the program, let's start with simple vertex and fragment shaders.

Vertex Shader

Add the following to topologica.vs:

precision highp float;

attribute vec3 position;
attribute vec3 normal;

uniform mat4 projectionView;
uniform mat4 model;
uniform mat3 normalSpace;
uniform vec3 eyePosition;

varying vec3 v_toView;
varying vec3 v_normal;
varying vec3 v_toLight;

void main()
{
    const vec3 lightPosition = vec3(0., 10., -10.);
    gl_Position = projectionView * model * vec4(position , 1.0);
    v_normal  = normalSpace * normal;
    v_toView  = eyePosition - gl_Position.xyz;
    v_toLight = lightPosition - gl_Position.xyz;
}

All parameters passed to the shader are listed in the Built-in Shader Param GLSL article. Let’s analyze the values passed to the shader:

position attribute Vertex position
normal attribute Vertex normal
projectionView uniform This parameter is the result of the multiplication of projection and view matrixes.

Note. The projection matrix is calculated from camera parameters such as Fov, Aspect, Near, Far. View matrix is calculated from the rotation and position of the camera. Since both matrixes are rarely used separately, they reach the shader pre-multiplied

model uniform The model matrix is the matrix that translates, scales and rotates your object
normalSpace uniform Normal matrix is the matrix which is used to transform the normal to the view space (camera space)
eyePosition uniform Camera position

At this point, the vertex shader defines a fixed point of lighting, calculates the position of a point, and passes the normal to the fragment shader (vector from the current point to the camera and vector from the current point to the light source).

Fragment shader

Create a file called topologica.fs with the following lines:

precision highp float;

uniform vec4 baseColor;
uniform vec4 specColor;

varying vec3 v_normal;
varying vec3 v_toView;
varying vec3 v_toLight;

void main()
{
    const float specPower = 10.0;
    vec3 n2 = normalize ( v_normal );
    vec3 v2 = normalize ( v_toView );
    vec3 r  = reflect   ( -v2, n2 );
    vec3 l2 = normalize ( v_toLight );
    vec4 diff = baseColor * max ( dot ( n2, l2 ), 0.0 );
    vec4 spec = specColor * pow ( max ( dot ( l2, r ), 0.0 ), specPower );
    gl_FragColor = vec4(diff.rgb, 1.);
    gl_FragColor.rgb += spec.rgb;
}

This shader paints the surface in a defined color, with a single light source of a defined color. Hereinafter, the baseColor and specColor parameters are set from code.

Add material

Create a file with a description of the material in accordance with the Material File Format article. In our case, topologica.mat contains only the paths to the shader files:

{
    "vertexFile" : "resources/materials/shaders/topologica.vs",
    "fragmentFile" : "resources/materials/shaders/topologica.fs",
}

Program Code (sample.cpp):

#include "W4Framework.h"

W4_USE_UNSTRICT_INTERFACE

struct ShadertoyDemo : public IGame
{
public:
    void onStart() override
    {
        m_cube = Mesh::create::cube({2.f, 2.f, 2.f});
        auto matInst = Material::get("resources/materials/topologica.mat")->createInstance();
        matInst->setParam("baseColor", vec4(1.f, 0.f, 0.f, 1.f));
        matInst->setParam("specColor", vec4(0.f, 0.f, 1.f, 1.f));
        m_cube->setMaterialInst(matInst);
        Render::getRoot()->addChild(m_cube);
    }

    virtual void onTouch(const event::Touch::Begin&) override
    {
    }

    void onUpdate(float dt) override
    {
        m_cube->rotate(Rotator{dt,dt,dt});
    }

private:
    sptr<Mesh> m_cube;
};

W4_RUN(ShadertoyDemo)

This is enough to retrieve a red cube with blue lighting:

Material_02.png

Adding a complex shader

After you made sure that everything works smoothly, you can switch to the shader from the www.shadertoy.com. Its contents are below:

/*--------------------------------------------------------------------------------------
License CC0 - http://creativecommons.org/publicdomain/zero/1.0/
To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty.
----------------------------------------------------------------------------------------
^ This means do ANYTHING YOU WANT with this code. Because we are programmers, not lawyers.
-Otavio Good
*/

// various noise functions
float Hash2d(vec2 uv)
{
    float f = uv.x + uv.y * 47.0;
    return fract(cos(f*3.333)*100003.9);
}
float Hash3d(vec3 uv)
{
    float f = uv.x + uv.y * 37.0 + uv.z * 521.0;
    return fract(cos(f*3.333)*100003.9);
}
float mixP(float f0, float f1, float a)
{
    return mix(f0, f1, a*a*(3.0-2.0*a));
}
const vec2 zeroOne = vec2(0.0, 1.0);
float noise2d(vec2 uv)
{
    vec2 fr = fract(uv.xy);
    vec2 fl = floor(uv.xy);
    float h00 = Hash2d(fl);
    float h10 = Hash2d(fl + zeroOne.yx);
    float h01 = Hash2d(fl + zeroOne);
    float h11 = Hash2d(fl + zeroOne.yy);
    return mixP(mixP(h00, h10, fr.x), mixP(h01, h11, fr.x), fr.y);
}
float noise2dT(vec2 uv)
{
    vec2 fr = fract(uv);
    vec2 smoothv = fr*fr*(3.0-2.0*fr);
    vec2 fl = floor(uv);
    uv = smoothv + fl;
    return textureLod(iChannel0, (uv + 0.5)/iChannelResolution[0].xy, 0.0).y;	// use constant here instead?
}
float noise(vec3 uv)
{
    vec3 fr = fract(uv.xyz);
    vec3 fl = floor(uv.xyz);
    float h000 = Hash3d(fl);
    float h100 = Hash3d(fl + zeroOne.yxx);
    float h010 = Hash3d(fl + zeroOne.xyx);
    float h110 = Hash3d(fl + zeroOne.yyx);
    float h001 = Hash3d(fl + zeroOne.xxy);
    float h101 = Hash3d(fl + zeroOne.yxy);
    float h011 = Hash3d(fl + zeroOne.xyy);
    float h111 = Hash3d(fl + zeroOne.yyy);
    return mixP(
        mixP(mixP(h000, h100, fr.x), mixP(h010, h110, fr.x), fr.y),
        mixP(mixP(h001, h101, fr.x), mixP(h011, h111, fr.x), fr.y)
        , fr.z);
}

float PI=3.14159265;

vec3 saturate(vec3 a)
{
	return clamp(a, 0.0, 1.0);
}
vec2 saturate(vec2 a)
{
	return clamp(a, 0.0, 1.0);
}
float saturate(float a)
{
	return clamp(a, 0.0, 1.0);
}

float Density(vec3 p)
{
    //float ws = 0.06125*0.125;
    //vec3 warp = vec3(noise(p*ws), noise(p*ws + 111.11), noise(p*ws + 7111.11));
    float final = noise(p*0.06125);// + sin(iTime)*0.5-1.95 + warp.x*4.0;
    float other = noise(p*0.06125 + 1234.567);
    other -= 0.5;
    final -= 0.5;
    final = 0.1/(abs(final*final*other));
    final += 0.5;
    return final*0.0001;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
	// ---------------- First, set up the camera rays for ray marching ----------------
	vec2 uv = fragCoord.xy/iResolution.xy * 2.0 - 1.0;// - 0.5;

	// Camera up vector.
	vec3 camUp=vec3(0,1,0); // vuv

	// Camera lookat.
	vec3 camLookat=vec3(0,0.0,0);	// vrp

	float mx=iMouse.x/iResolution.x*PI*2.0 + iTime * 0.01;
	float my=-iMouse.y/iResolution.y*10.0 + sin(iTime * 0.03)*0.2+0.2;//*PI/2.01;
	vec3 camPos=vec3(cos(my)*cos(mx),sin(my),cos(my)*sin(mx))*(200.2); 	// prp

	// Camera setup.
	vec3 camVec=normalize(camLookat - camPos);//vpn
	vec3 sideNorm=normalize(cross(camUp, camVec));	// u
	vec3 upNorm=cross(camVec, sideNorm);//v
	vec3 worldFacing=(camPos + camVec);//vcv
	vec3 worldPix = worldFacing + uv.x * sideNorm * (iResolution.x/iResolution.y) + uv.y * upNorm;//scrCoord
	vec3 relVec = normalize(worldPix - camPos);//scp

	// --------------------------------------------------------------------------------
	float t = 0.0;
	float inc = 0.02;
	float maxDepth = 70.0;
	vec3 pos = vec3(0,0,0);
    float density = 0.0;
	// ray marching time
    for (int i = 0; i < 37; i++)	// This is the count of how many times the ray actually marches.
    {
        if ((t > maxDepth)) break;
        pos = camPos + relVec * t;
        float temp = Density(pos);
        //temp *= saturate(t-1.0);

        inc = 1.9 + temp*0.05;	// add temp because this makes it look extra crazy!
        density += temp * inc;
        t += inc;
    }

	// --------------------------------------------------------------------------------
	// Now that we have done our ray marching, let's put some color on this.
	vec3 finalColor = vec3(0.01,0.1,1.0)* density*0.2;

	// output the final color with sqrt for "gamma correction"
	fragColor = vec4(sqrt(clamp(finalColor, 0.0, 1.0)),1.0);
}

Fragment Shader Updating

Copy the shader from the site to the fragment shader of the project, adjusting some names of parameters. All functions are copied, except for the following:

  • mainImage as it will be copied on the later stage;
  • noise2dT as it not used and leads to an error.

As a result, topologica.fs looks like this:

precision highp float;

uniform vec4 baseColor;
uniform vec4 specColor;

varying vec3 v_normal;
varying vec3 v_toView;
varying vec3 v_toLight;

// various noise functions
float Hash2d(vec2 uv)
{
    float f = uv.x + uv.y * 47.0;
    return fract(cos(f*3.333)*100003.9);
}
float Hash3d(vec3 uv)
{
    float f = uv.x + uv.y * 37.0 + uv.z * 521.0;
    return fract(cos(f*3.333)*100003.9);
}
float mixP(float f0, float f1, float a)
{
    return mix(f0, f1, a*a*(3.0-2.0*a));
}
const vec2 zeroOne = vec2(0.0, 1.0);
float noise2d(vec2 uv)
{
    vec2 fr = fract(uv.xy);
    vec2 fl = floor(uv.xy);
    float h00 = Hash2d(fl);
    float h10 = Hash2d(fl + zeroOne.yx);
    float h01 = Hash2d(fl + zeroOne);
    float h11 = Hash2d(fl + zeroOne.yy);
    return mixP(mixP(h00, h10, fr.x), mixP(h01, h11, fr.x), fr.y);
}
float noise(vec3 uv)
{
    vec3 fr = fract(uv.xyz);
    vec3 fl = floor(uv.xyz);
    float h000 = Hash3d(fl);
    float h100 = Hash3d(fl + zeroOne.yxx);
    float h010 = Hash3d(fl + zeroOne.xyx);
    float h110 = Hash3d(fl + zeroOne.yyx);
    float h001 = Hash3d(fl + zeroOne.xxy);
    float h101 = Hash3d(fl + zeroOne.yxy);
    float h011 = Hash3d(fl + zeroOne.xyy);
    float h111 = Hash3d(fl + zeroOne.yyy);
    return mixP(
        mixP(mixP(h000, h100, fr.x), mixP(h010, h110, fr.x), fr.y),
        mixP(mixP(h001, h101, fr.x), mixP(h011, h111, fr.x), fr.y)
        , fr.z);
}

float PI=3.14159265;

vec3 saturate(vec3 a)
{
	return clamp(a, 0.0, 1.0);
}
vec2 saturate(vec2 a)
{
	return clamp(a, 0.0, 1.0);
}
float saturate(float a)
{
	return clamp(a, 0.0, 1.0);
}

float Density(vec3 p)
{
    //float ws = 0.06125*0.125;
    //vec3 warp = vec3(noise(p*ws), noise(p*ws + 111.11), noise(p*ws + 7111.11));
    float final = noise(p*0.06125);// + sin(iTime)*0.5-1.95 + warp.x*4.0;
    float other = noise(p*0.06125 + 1234.567);
    other -= 0.5;
    final -= 0.5;
    final = 0.1/(abs(final*final*other));
    final += 0.5;
    return final*0.0001;
}

void main()
{
    const float specPower = 10.0;
    vec3 n2 = normalize ( v_normal );
    vec3 v2 = normalize ( v_toView );
    vec3 r  = reflect   ( -v2, n2 );
    vec3 l2 = normalize ( v_toLight );
    vec4 diff = baseColor * max ( dot ( n2, l2 ), 0.0 );
    vec4 spec = specColor * pow ( max ( dot ( l2, r ), 0.0 ), specPower );
    gl_FragColor = vec4(diff.rgb, 1.);
    gl_FragColor.rgb += spec.rgb;
}

The program with the above shader runs without errors, but the cube has not changed, because the mainImage part is missing.

Copy the contents of mainImage to the main of fragment shader and get the following code:

precision highp float;

uniform vec4 baseColor;
uniform vec4 specColor;

varying vec3 v_normal;
varying vec3 v_toView;
varying vec3 v_toLight;

// various noise functions
float Hash2d(vec2 uv)
{
    float f = uv.x + uv.y * 47.0;
    return fract(cos(f*3.333)*100003.9);
}
float Hash3d(vec3 uv)
{
    float f = uv.x + uv.y * 37.0 + uv.z * 521.0;
    return fract(cos(f*3.333)*100003.9);
}
float mixP(float f0, float f1, float a)
{
    return mix(f0, f1, a*a*(3.0-2.0*a));
}
const vec2 zeroOne = vec2(0.0, 1.0);
float noise2d(vec2 uv)
{
    vec2 fr = fract(uv.xy);
    vec2 fl = floor(uv.xy);
    float h00 = Hash2d(fl);
    float h10 = Hash2d(fl + zeroOne.yx);
    float h01 = Hash2d(fl + zeroOne);
    float h11 = Hash2d(fl + zeroOne.yy);
    return mixP(mixP(h00, h10, fr.x), mixP(h01, h11, fr.x), fr.y);
}

float noise(vec3 uv)
{
    vec3 fr = fract(uv.xyz);
    vec3 fl = floor(uv.xyz);
    float h000 = Hash3d(fl);
    float h100 = Hash3d(fl + zeroOne.yxx);
    float h010 = Hash3d(fl + zeroOne.xyx);
    float h110 = Hash3d(fl + zeroOne.yyx);
    float h001 = Hash3d(fl + zeroOne.xxy);
    float h101 = Hash3d(fl + zeroOne.yxy);
    float h011 = Hash3d(fl + zeroOne.xyy);
    float h111 = Hash3d(fl + zeroOne.yyy);
    return mixP(
        mixP(mixP(h000, h100, fr.x), mixP(h010, h110, fr.x), fr.y),
        mixP(mixP(h001, h101, fr.x), mixP(h011, h111, fr.x), fr.y)
        , fr.z);
}

float PI=3.14159265;

vec3 saturate(vec3 a)
{
	return clamp(a, 0.0, 1.0);
}
vec2 saturate(vec2 a)
{
	return clamp(a, 0.0, 1.0);
}
float saturate(float a)
{
	return clamp(a, 0.0, 1.0);
}

float Density(vec3 p)
{
    //float ws = 0.06125*0.125;
    //vec3 warp = vec3(noise(p*ws), noise(p*ws + 111.11), noise(p*ws + 7111.11));
    float final = noise(p*0.06125);// + sin(iTime)*0.5-1.95 + warp.x*4.0;
    float other = noise(p*0.06125 + 1234.567);
    other -= 0.5;
    final -= 0.5;
    final = 0.1/(abs(final*final*other));
    final += 0.5;
    return final*0.0001;
}

void main()
{
    	// ---------------- First, set up the camera rays for ray marching ----------------
    	vec2 uv = fragCoord.xy/iResolution.xy * 2.0 - 1.0;// - 0.5;

    	// Camera up vector.
    	vec3 camUp=vec3(0,1,0); // vuv

    	// Camera lookat.
    	vec3 camLookat=vec3(0,0.0,0);	// vrp

    	float mx=iMouse.x/iResolution.x*PI*2.0 + iTime * 0.01;
    	float my=-iMouse.y/iResolution.y*10.0 + sin(iTime * 0.03)*0.2+0.2;//*PI/2.01;
    	vec3 camPos=vec3(cos(my)*cos(mx),sin(my),cos(my)*sin(mx))*(200.2); 	// prp

    	// Camera setup.
    	vec3 camVec=normalize(camLookat - camPos);//vpn
    	vec3 sideNorm=normalize(cross(camUp, camVec));	// u
    	vec3 upNorm=cross(camVec, sideNorm);//v
    	vec3 worldFacing=(camPos + camVec);//vcv
    	vec3 worldPix = worldFacing + uv.x * sideNorm * (iResolution.x/iResolution.y) + uv.y * upNorm;//scrCoord
    	vec3 relVec = normalize(worldPix - camPos);//scp

    	// --------------------------------------------------------------------------------
    	float t = 0.0;
    	float inc = 0.02;
    	float maxDepth = 70.0;
    	vec3 pos = vec3(0,0,0);
        float density = 0.0;
    	// ray marching time
        for (int i = 0; i < 37; i++)	// This is the count of how many times the ray actually marches.
        {
            if ((t > maxDepth)) break;
            pos = camPos + relVec * t;
            float temp = Density(pos);
            //temp *= saturate(t-1.0);

            inc = 1.9 + temp*0.05;	// add temp because this makes it look extra crazy!
            density += temp * inc;
            t += inc;
        }

    	// --------------------------------------------------------------------------------
    	// Now that we have done our ray marching, let's put some color on this.
    	vec3 finalColor = vec3(0.01,0.1,1.0)* density*0.2;

    	// output the final color with sqrt for "gamma correction"
    	fragColor = vec4(sqrt(clamp(finalColor, 0.0, 1.0)),1.0);

        const float specPower = 10.0;
        vec3 n2 = normalize ( v_normal );
        vec3 v2 = normalize ( v_toView );
        vec3 r  = reflect   ( -v2, n2 );
        vec3 l2 = normalize ( v_toLight );
        vec4 diff = fragColor * max ( dot ( n2, l2 ), 0.0 );
        vec4 spec = specColor * pow ( max ( dot ( l2, r ), 0.0 ), specPower );
        gl_FragColor = vec4(diff.rgb, 1.);
        gl_FragColor.rgb += spec.rgb;
}

Debugging

If you run the program now, errors will appear with the missing fragCoord, iResolution, iTime and iMouse variables. Let's consider them in order:

1) fragCoord and iResolution are used to calculate uv. uv can be taken from the vertex shader (v_uv). Do not forget to write the following in the headers of both shaders:

 varying vec2 v_uv;

and add the following to the vertex shader:

 attribute vec2 uv0;
 ...
 v_uv = uv0;

Also iTime - this is the time attribute. Add it to the fragment shader:

uniform float time;

and replace iTime with time.

Hardcode iMouse (for later use).

vec2 iMouse = vec2(0, 0);

Also iResolution is used to calculate the aspect ratio. To optimize the execution, let's calculate it in the vertex shader.

uniform vec2 resolution;
...
varying float v_aspect;
...
v_aspect = resolution.x / resolution.y;

Note. Don't forget to remove baseColor from the shader and source code. Since it is no longer used.

Result

The resulting shaders:

topologica.vs

precision highp float;

attribute vec3 position;
attribute vec3 normal;
attribute vec2 uv0;

uniform mat4 projectionView;
uniform mat4 model;
uniform mat3 normalSpace;
uniform vec3 eyePosition;
uniform vec2 resolution;

varying vec3 v_toView;
varying vec3 v_normal;
varying vec3 v_toLight;
varying vec2 v_uv;
varying float v_aspect;

void main()
{
    v_uv = uv0;

    const vec3 lightPosition = vec3(0., 10., -10.);
    gl_Position = projectionView * model * vec4(position , 1.0);
    v_normal  = normalSpace * normal;
    v_toView  = eyePosition - gl_Position.xyz;
    v_toLight = lightPosition - gl_Position.xyz;
    v_aspect = resolution.x / resolution.y;
}

topologica.fs

precision highp float;

uniform vec4 specColor;
uniform float time;

varying vec3 v_normal;
varying vec3 v_toView;
varying vec3 v_toLight;
varying vec2 v_uv;
varying float v_aspect;

// various noise functions
float Hash2d(vec2 uv)
{
    float f = uv.x + uv.y * 47.0;
    return fract(cos(f*3.333)*100003.9);
}
float Hash3d(vec3 uv)
{
    float f = uv.x + uv.y * 37.0 + uv.z * 521.0;
    return fract(cos(f*3.333)*100003.9);
}
float mixP(float f0, float f1, float a)
{
    return mix(f0, f1, a*a*(3.0-2.0*a));
}
const vec2 zeroOne = vec2(0.0, 1.0);
float noise2d(vec2 uv)
{
    vec2 fr = fract(uv.xy);
    vec2 fl = floor(uv.xy);
    float h00 = Hash2d(fl);
    float h10 = Hash2d(fl + zeroOne.yx);
    float h01 = Hash2d(fl + zeroOne);
    float h11 = Hash2d(fl + zeroOne.yy);
    return mixP(mixP(h00, h10, fr.x), mixP(h01, h11, fr.x), fr.y);
}

float noise(vec3 uv)
{
    vec3 fr = fract(uv.xyz);
    vec3 fl = floor(uv.xyz);
    float h000 = Hash3d(fl);
    float h100 = Hash3d(fl + zeroOne.yxx);
    float h010 = Hash3d(fl + zeroOne.xyx);
    float h110 = Hash3d(fl + zeroOne.yyx);
    float h001 = Hash3d(fl + zeroOne.xxy);
    float h101 = Hash3d(fl + zeroOne.yxy);
    float h011 = Hash3d(fl + zeroOne.xyy);
    float h111 = Hash3d(fl + zeroOne.yyy);
    return mixP(
        mixP(mixP(h000, h100, fr.x), mixP(h010, h110, fr.x), fr.y),
        mixP(mixP(h001, h101, fr.x), mixP(h011, h111, fr.x), fr.y)
        , fr.z);
}

float PI=3.14159265;

vec3 saturate(vec3 a)
{
	return clamp(a, 0.0, 1.0);
}
vec2 saturate(vec2 a)
{
	return clamp(a, 0.0, 1.0);
}
float saturate(float a)
{
	return clamp(a, 0.0, 1.0);
}

float Density(vec3 p)
{
    //float ws = 0.06125*0.125;
    //vec3 warp = vec3(noise(p*ws), noise(p*ws + 111.11), noise(p*ws + 7111.11));
    float final = noise(p*0.06125);// + sin(iTime)*0.5-1.95 + warp.x*4.0;
    float other = noise(p*0.06125 + 1234.567);
    other -= 0.5;
    final -= 0.5;
    final = 0.1/(abs(final*final*other));
    final += 0.5;
    return final*0.0001;
}

void main()
{
    	// ---------------- First, set up the camera rays for ray marching ----------------
    	// Camera up vector.
    	vec3 camUp=vec3(0,1,0); // vuv

    	// Camera lookat.
    	vec3 camLookat=vec3(0,0.0,0);	// vrp

        vec2 iMouse = vec2(0, 0);
        vec2 iResolution = vec2(1, 1);

    	float mx=iMouse.x/iResolution.x*PI*2.0 + time * 0.01;
    	float my=-iMouse.y/iResolution.y*10.0 + sin(time * 0.03)*0.2+0.2;//*PI/2.01;
    	vec3 camPos=vec3(cos(my)*cos(mx),sin(my),cos(my)*sin(mx))*(200.2); 	// prp

    	// Camera setup.
    	vec3 camVec=normalize(camLookat - camPos);//vpn
    	vec3 sideNorm=normalize(cross(camUp, camVec));	// u
    	vec3 upNorm=cross(camVec, sideNorm);//v
    	vec3 worldFacing=(camPos + camVec);//vcv
    	vec3 worldPix = worldFacing + v_uv.x * sideNorm * v_aspect + v_uv.y * upNorm;//scrCoord
    	vec3 relVec = normalize(worldPix - camPos);//scp

    	// --------------------------------------------------------------------------------
    	float t = 0.0;
    	float inc = 0.02;
    	float maxDepth = 70.0;
    	vec3 pos = vec3(0,0,0);
        float density = 0.0;
    	// ray marching time
        for (int i = 0; i < 37; i++)	// This is the count of how many times the ray actually marches.
        {
            if ((t > maxDepth)) break;
            pos = camPos + relVec * t;
            float temp = Density(pos);
            //temp *= saturate(t-1.0);

            inc = 1.9 + temp*0.05;	// add temp because this makes it look extra crazy!
            density += temp * inc;
            t += inc;
        }

    	// --------------------------------------------------------------------------------
    	// Now that we have done our ray marching, let's put some color on this.
    	vec3 finalColor = vec3(0.01,0.1,1.0)* density*0.2;

    	// output the final color with sqrt for "gamma correction"
    	vec4 fragColor = vec4(sqrt(clamp(finalColor, 0.0, 1.0)),1.0);

        const float specPower = 10.0;
        vec3 n2 = normalize ( v_normal );
        vec3 v2 = normalize ( v_toView );
        vec3 r  = reflect   ( -v2, n2 );
        vec3 l2 = normalize ( v_toLight );
        vec4 diff = fragColor * max ( dot ( n2, l2 ), 0.0 );
        vec4 spec = specColor * pow ( max ( dot ( l2, r ), 0.0 ), specPower );
        gl_FragColor = vec4(diff.rgb, 1.);
        gl_FragColor.rgb += spec.rgb;
}

sample.cpp has not changed, except for the deleted line with baseColor.

Let's run the program:

Material_03.png

The material added!

Cursor handler

The mouse position can simply be removed from the shader, but let's implement it. To do this, add a mouse movement handler and pass the normalized cursor position to the shader.

The resulting code: sample.cpp

#include "W4Framework.h"

W4_USE_UNSTRICT_INTERFACE

struct ShadertoyDemo : public IGame
{
public:
    void onStart() override
    {
        m_cube = Mesh::create::cube({2.f, 2.f, 2.f});
        m_matInst = Material::get("resources/materials/topologica.mat")->createInstance();
        m_matInst->setParam("specColor", vec4(0.f, 0.f, 1.f, 1.f));
        m_cube->setMaterialInst(m_matInst);
        Render::getRoot()->addChild(m_cube);

        event::Touch::Move::subscribe(std::bind(&ShadertoyDemo::onMove, this, std::placeholders::_1));
    }

    void onMove(const event::Touch::Move& evt)
    {
        const auto& screenSize = Platform::instance().getSize();
        m_matInst->setParam("mousePos", vec2(static_cast<float>(evt.point.x) / screenSize.w, static_cast<float>(evt.point.y) / screenSize.h));
    }

    void onUpdate(float dt) override
    {
        m_cube->rotate(Rotator{dt,dt,dt});
    }

private:
    sptr<Mesh> m_cube;
    sptr<MaterialInst> m_matInst;
};

W4_RUN(ShadertoyDemo)

topologica.fs

precision highp float;

uniform vec4 specColor;
uniform float time;
uniform vec2 mousePos;

varying vec3 v_normal;
varying vec3 v_toView;
varying vec3 v_toLight;
varying vec2 v_uv;
varying float v_aspect;

// various noise functions
float Hash2d(vec2 uv)
{
    float f = uv.x + uv.y * 47.0;
    return fract(cos(f*3.333)*100003.9);
}
float Hash3d(vec3 uv)
{
    float f = uv.x + uv.y * 37.0 + uv.z * 521.0;
    return fract(cos(f*3.333)*100003.9);
}
float mixP(float f0, float f1, float a)
{
    return mix(f0, f1, a*a*(3.0-2.0*a));
}
const vec2 zeroOne = vec2(0.0, 1.0);
float noise2d(vec2 uv)
{
    vec2 fr = fract(uv.xy);
    vec2 fl = floor(uv.xy);
    float h00 = Hash2d(fl);
    float h10 = Hash2d(fl + zeroOne.yx);
    float h01 = Hash2d(fl + zeroOne);
    float h11 = Hash2d(fl + zeroOne.yy);
    return mixP(mixP(h00, h10, fr.x), mixP(h01, h11, fr.x), fr.y);
}

float noise(vec3 uv)
{
    vec3 fr = fract(uv.xyz);
    vec3 fl = floor(uv.xyz);
    float h000 = Hash3d(fl);
    float h100 = Hash3d(fl + zeroOne.yxx);
    float h010 = Hash3d(fl + zeroOne.xyx);
    float h110 = Hash3d(fl + zeroOne.yyx);
    float h001 = Hash3d(fl + zeroOne.xxy);
    float h101 = Hash3d(fl + zeroOne.yxy);
    float h011 = Hash3d(fl + zeroOne.xyy);
    float h111 = Hash3d(fl + zeroOne.yyy);
    return mixP(
        mixP(mixP(h000, h100, fr.x), mixP(h010, h110, fr.x), fr.y),
        mixP(mixP(h001, h101, fr.x), mixP(h011, h111, fr.x), fr.y)
        , fr.z);
}

float PI=3.14159265;

vec3 saturate(vec3 a)
{
	return clamp(a, 0.0, 1.0);
}
vec2 saturate(vec2 a)
{
	return clamp(a, 0.0, 1.0);
}
float saturate(float a)
{
	return clamp(a, 0.0, 1.0);
}

float Density(vec3 p)
{
    //float ws = 0.06125*0.125;
    //vec3 warp = vec3(noise(p*ws), noise(p*ws + 111.11), noise(p*ws + 7111.11));
    float final = noise(p*0.06125);// + sin(iTime)*0.5-1.95 + warp.x*4.0;
    float other = noise(p*0.06125 + 1234.567);
    other -= 0.5;
    final -= 0.5;
    final = 0.1/(abs(final*final*other));
    final += 0.5;
    return final*0.0001;
}

void main()
{
    	// ---------------- First, set up the camera rays for ray marching ----------------
    	// Camera up vector.
    	vec3 camUp=vec3(0,1,0); // vuv

    	// Camera lookat.
    	vec3 camLookat=vec3(0,0.0,0);	// vrp

    	float mx=mousePos.x*PI*2.0 + time * 0.01;
    	float my=-mousePos.y*10.0 + sin(time * 0.03)*0.2+0.2;//*PI/2.01;
    	vec3 camPos=vec3(cos(my)*cos(mx),sin(my),cos(my)*sin(mx))*(200.2); 	// prp

    	// Camera setup.
    	vec3 camVec=normalize(camLookat - camPos);//vpn
    	vec3 sideNorm=normalize(cross(camUp, camVec));	// u
    	vec3 upNorm=cross(camVec, sideNorm);//v
    	vec3 worldFacing=(camPos + camVec);//vcv
    	vec3 worldPix = worldFacing + v_uv.x * sideNorm * v_aspect + v_uv.y * upNorm;//scrCoord
    	vec3 relVec = normalize(worldPix - camPos);//scp

    	// --------------------------------------------------------------------------------
    	float t = 0.0;
    	float inc = 0.02;
    	float maxDepth = 70.0;
    	vec3 pos = vec3(0,0,0);
        float density = 0.0;
    	// ray marching time
        for (int i = 0; i < 37; i++)	// This is the count of how many times the ray actually marches.
        {
            if ((t > maxDepth)) break;
            pos = camPos + relVec * t;
            float temp = Density(pos);
            //temp *= saturate(t-1.0);

            inc = 1.9 + temp*0.05;	// add temp because this makes it look extra crazy!
            density += temp * inc;
            t += inc;
        }

    	// --------------------------------------------------------------------------------
    	// Now that we have done our ray marching, let's put some color on this.
    	vec3 finalColor = vec3(0.01,0.1,1.0)* density*0.2;

    	// output the final color with sqrt for "gamma correction"
    	vec4 fragColor = vec4(sqrt(clamp(finalColor, 0.0, 1.0)),1.0);

        const float specPower = 10.0;
        vec3 n2 = normalize ( v_normal );
        vec3 v2 = normalize ( v_toView );
        vec3 r  = reflect   ( -v2, n2 );
        vec3 l2 = normalize ( v_toLight );
        vec4 diff = fragColor * max ( dot ( n2, l2 ), 0.0 );
        vec4 spec = specColor * pow ( max ( dot ( l2, r ), 0.0 ), specPower );
        gl_FragColor = vec4(diff.rgb, 1.);
        gl_FragColor.rgb += spec.rgb;
}

The vertex shader has remained unchanged.

Run the program:

http://demo.w4-dev.ciliz.com/wiki-files/Material_final.mov

When you move the mouse with the button held down, the shader reacts similarly to the shadertoy implementation. Therefore, the functionality is fully transferred!