Difference between revisions of "W4 Material Creation"
Line 180: | Line 180: | ||
== Adding a complex shader == | == Adding a complex shader == | ||
− | After you made sure that everything works smoothly, you can switch to the [https://www.shadertoy.com/view/4djXzz shader] from | + | After you made sure that everything works smoothly, you can switch to the [https://www.shadertoy.com/view/4djXzz shader] from www.shadertoy.com. Its contents are below: |
<syntaxhighlight lang="GLSL"> | <syntaxhighlight lang="GLSL"> | ||
/*-------------------------------------------------------------------------------------- | /*-------------------------------------------------------------------------------------- | ||
Line 323: | Line 323: | ||
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: | 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 | + | * '''mainImage''' as it will be copied on later stage; |
− | * '''noise2dT''' as it not used and leads to an error. | + | * '''noise2dT''' as it will is not used and leads to an error. |
− | As a result, '''topologica.fs''' looks | + | As a result, '''topologica.fs''' looks the following way: |
<syntaxhighlight lang="GLSL"> | <syntaxhighlight lang="GLSL"> | ||
precision highp float; | precision highp float; | ||
Line 422: | Line 422: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | The program with the above shader runs without errors, but the cube has not changed, | + | The program with the above shader runs without errors, but the cube has not changed, as the '''mainImage''' part is missing. |
− | Copy the contents of '''mainImage''' to the '''main''' of fragment shader and get the following code: | + | Copy the contents of '''mainImage''' to the '''main''' of fragment shader and you will get the following code: |
<syntaxhighlight lang="GLSL"> | <syntaxhighlight lang="GLSL"> | ||
precision highp float; | precision highp float; | ||
Line 569: | Line 569: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==== Debugging ==== | ==== Debugging ==== | ||
− | If you run the program now, errors will appear with the missing '''fragCoord''', '''iResolution''', '''iTime''' and '''iMouse''' variables. Let's | + | If you run the program now, errors will appear with the missing variables: '''fragCoord''', '''iResolution''', '''iTime''' and '''iMouse''' variables. Let's review them case by case: |
− | 1) '''fragCoord''' and '''iResolution''' are used to calculate '''uv'''. '''uv''' can be | + | 1) '''fragCoord''' and '''iResolution''' are used to calculate '''uv'''. '''uv''' can be retrieved from the vertex shader ('''v_uv'''). Do not forget to complete the headers of both shaders with lines accordingly: |
<syntaxhighlight lang="GLSL"> | <syntaxhighlight lang="GLSL"> | ||
varying vec2 v_uv; | varying vec2 v_uv; | ||
Line 581: | Line 581: | ||
v_uv = uv0; | v_uv = uv0; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | '''iTime''' is the '''time''' attribute. Add it to the fragment shader: | |
<syntaxhighlight lang="GLSL"> | <syntaxhighlight lang="GLSL"> | ||
uniform float time; | uniform float time; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | replace '''iTime''' with '''time'''. | |
Hardcode '''iMouse''' (for later use). | Hardcode '''iMouse''' (for later use). | ||
Line 591: | Line 591: | ||
vec2 iMouse = vec2(0, 0); | vec2 iMouse = vec2(0, 0); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | '''iResolution''' is also used to calculate the aspect ratio. To optimize the execution, let's calculate it in the vertex shader. | |
<syntaxhighlight lang="GLSL"> | <syntaxhighlight lang="GLSL"> | ||
uniform vec2 resolution; | uniform vec2 resolution; | ||
Line 599: | Line 599: | ||
v_aspect = resolution.x / resolution.y; | v_aspect = resolution.x / resolution.y; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | Note. | + | Note. Since '''baseColor''' is no longer used, don't forget to remove it from the shader and source code. |
+ | |||
== Result == | == Result == | ||
The resulting shaders: | The resulting shaders: |
Revision as of 14:38, 29 June 2020
Contents
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:
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 named 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 specified color, taking into account that there is only one light source of a defined color. Hereinafter, the baseColor and specColor parameters are set from the 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:
Adding a complex shader
After you made sure that everything works smoothly, you can switch to the shader from 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 later stage;
- noise2dT as it will is not used and leads to an error.
As a result, topologica.fs looks the following way:
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, as the mainImage part is missing.
Copy the contents of mainImage to the main of fragment shader and you will 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 variables: fragCoord, iResolution, iTime and iMouse variables. Let's review them case by case:
1) fragCoord and iResolution are used to calculate uv. uv can be retrieved from the vertex shader (v_uv). Do not forget to complete the headers of both shaders with lines accordingly:
varying vec2 v_uv;
and add the following to the vertex shader:
attribute vec2 uv0;
...
v_uv = uv0;
iTime is the time attribute. Add it to the fragment shader:
uniform float time;
replace iTime with time.
Hardcode iMouse (for later use).
vec2 iMouse = vec2(0, 0);
iResolution is also 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. Since baseColor is no longer used, don't forget to remove it from the shader and source code.
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:
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!