Difference between revisions of "W4 Material Creation"
Line 77: | Line 77: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | 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: | + | All parameters passed to the shader are listed in the [[Built-in Shader Param GLSL]] article. All uniforms described above are accessible by default in a shader. The only attribute that was originally added is '''vec3 w4_a_position'''. This attribute is needed to calculate the position in '''w4_getVertexPosition''' |
+ | |||
+ | Let’s analyze the values passed to the shader: | ||
{| class="wikitable" | {| class="wikitable" |
Revision as of 14:35, 7 July 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:
attribute vec3 w4_a_normal;
varying vec3 v_toView;
varying vec3 v_normal;
varying vec3 v_toLight;
void w4_main()
{
const vec3 lightPosition = vec3(0., 10., -10.);
gl_Position = w4_u_projectionView * w4_getVertexPosition();
v_normal = w4_u_normalSpace * w4_a_normal;
v_toView = w4_u_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. All uniforms described above are accessible by default in a shader. The only attribute that was originally added is vec3 w4_a_position. This attribute is needed to calculate the position in w4_getVertexPosition
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 remained unchanged, except for the deleted line with baseColor.
Let's run the program:
As a result of the previous steps the material is added.
Cursor handler
The mouse position can simply be removed from the shader, but in our case we keep it. This can be done by adding a mouse movement handler and passing 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 pressed button, the shader reacts similarly to the shadertoy implementation. Therefore, the functionality is fully transferred!