### Description

This is a shader I had in mind during a skiing session, I wanted to explore some ideas of create a lofi forest. I wanted to explore some concept of SDF and ray marching. This article is based on my freetime studying of ray-marching and the mathematics behind it. This is what we will build in this tutorial.

Before diving in it, there are some stuff I would like to review ðŸ™‚

### SDF review

This assume you already have the algorithms implemented in a fragment shader. I will use GLSL, but one can easily just switch to HLSL for DX pipeline.

#### Animation and movement

Rotate a plane

vec3 planeOrientation = vec3(0.0, 1.0, 0.0);
float planeH = dot(pos, normalize(planeOrientation));
float box = sdBox(pos + vec3(1., -1.5, 0.), vec3(1.5));
return min(planeH, box);

Then we can change where the plane is pointing to

vec3 planeOrientation = vec3(0.5 * sin(iTime), 1., 0.0);
float planeH = dot(pos, normalize(planeOrientation));
float box = sdBox(pos + vec3(1., -1.5, 0.), vec3(1.5));
return min(planeH, box);

Move an object from within the SDF Call:

float d1 = sphere(pos + vec3(1.0, 0.0, 0.0), 2.0);

Move an object using time so it moves:

float d1 = sphere(pos + vec3(sin(iTime), 0.0, 0.0), 2.0);

Make the change of position more than between -1 and 1.

float d1 = sphere(pos + vec3(sin(iTime) * 6, 0.0, 0.0), 2.0);

Make the animation quicker by changing the frequency

float d1 = sphere(pos + vec3(sin(iTime * 5.), 0.0, 0.0), 2.0);

Make animation on more than one axis

float d1 = sphere(pos + vec3(sin(iTime * 5.),-abs(cos(iTime * 2.5)) * 12., 0.0), 2.0);

#### Object combination

Chain SDF.

float planeDist = plane(pos);
float d1 = sphere(pos + vec3(3.0, -1.0, 0.), 2.);
float d2 = sphere(pos + vec3(-3.0, -1.0, 0.), 2.);
return min(planeDist, min(d1, d2));

Show the object used by both object

float d1 = sphere(pos + vec3(3.0, -1.0, 0.), 2.);
float d2 = sphere(pos + vec3(3.5, -1.7, 0.), 2.);
return max(d1, d2);

Giving us:

Remove a part from an object

float d1 = sphere(pos + vec3(-1.5, -1., 1.), 2.);
float d2 = sphere(pos, 2.);
return max(-d1,d2);

Letâ€™s say I have two sphere and I want to remove the smaller from the bigger, as it carve a hole.

float d1 = sphere(pos + vec3(-1., -1., 1.), 1.);
float d2 = sphere(pos, 2.);
return max(-d1, d2);

We have this as our main Map return

Create a shell from two object

float d1 = sphere(pos + vec3(3.0, -1.0, 0.), 3.);
float d2 = sphere(pos + vec3(2., -1.7, 1.), 2.);
d2 = abs(d2) â€” .08;
return max(d1, d2);

Shell an object using a plane

vec3 planeOrientation = vec3(
0.5 * sin(iTime),
sin(iTime), 1.2 * (cos(iTime)))
â€” sin(cos(iTime * 5.));
float planeH = dot(pos, normalize(planeOrientation));
float box = sdBox(pos + vec3(1., -1.5, 0.), vec3(1.5));
box = abs(box) â€” .1;
return max(planeH, box);

Slice object and combine them with a plane

vec3 sliceOrientation = vec3(1., 1, -3.);
float pSlice = dot(pos, normalize(sliceOrientation));
float plane = plane(pos);
float box = sdBox(pos + vec3(1., -1.5, 0.), vec3(1.5));
box = abs(box) â€” .1;
return min(plane, max(box, pSlice));

### Basic setup

Let’s start by breaking the main image function:

vec2 uv = (fragCoord-.5*iResolution.xy)/iResolution.y;

I start by taking the fragCoord of the pixel shader and remaping it between -1 and 1. For both x and y.

To create mouse control, I use a simple x and y that map the position between 0 and 1 in screenspace. In short, a vec2 that hold a x and y position of the mouse, between 0 and 1. (We will use it later for camera movement and lookat function)

vec2 m = iMouse.xy/iResolution.xy;

For demonstration, I assigned the m.x to the red channel and the m.y to the create. When both at 1, we get yellow. Just to test it, I assigned the final pixel value to be vec3(m.x, m.y, 0.0f);

For setting the ray origin and the ray direction to be moving, I used those lines:

vec3 ro = vec3(0. + iTime, 7.55 ,-4.5);
ro.yz *= Rot(-m.y+.4);
ro.xz *= Rot(m.x * 12.);
vec3 rd = R(uv, ro, vec3(0,.5,iTime), .7);

The rotation is based on the mouse position in screenspace, we simply alter the ray position

The rotation function is simply returning a 2D rotation matrices.

mat2 rot(float a)
{
float s = sin(a);
float c = cos(a);
return mat2(c, -s,s,c);

}

Now that we have our camera setup we can’t start creating the scene ðŸ™‚

To start creating the scene, I am using the cast ray function. Here is a little trick: I make the castRay returning a vec2 instead of a float. Because we will use different object ID for different SDF, we want to be able to track them both, so the object will have the hit distance, and also an ID assigned to it.

float hitDistance = castRay(ro, rd).x;
float objectId = castRay(ro, rd).y;

To start rendering SDF geometry we need to check if the hitpoint is smaller than a small number. Than we can start creating the scene. Let’s break the castRay function.

vec2 castRay(vec3 ro, vec3 rd)
{

float t = 0.0;
float id = -20.;
float farClippingPlane = 120.0;

for(int i = 0; i < 256; i++)
{

vec3 pos = ro + t * rd;
float h = map(pos).x;
id = map(pos).y;

if(h < 0.001)
{
break;
}
t += h;
if(t > farClippingPlane) break;

}

if(t > farClippingPlane) t = -1.0;

return vec2(t, id);
}

We start by setting t, wich will act as a the position along the ray. The simple equation to get any point along the ray is

p(t) = ro + t * rd;

This way we will be able to walk along the ray in a the query. I walk 256 steps along the ray, as you can see in the the for loop:


for(int i = 0; i < 256; i++)
{

}

Let’s break what is inside this for loop.

We start by creating a position at the origin of the ray wich will be the ray origin.

vec3 pos = ro + t * rd;

pos is the current position in worldspace of the point we want to map. The map function simply map the distance from the closest SDF from our position variable. More on the map function later…

After querying the map function with the current position of our position along the ray I assign 2 variables:

h: The distance of the closest object in the scene

id: the id of the object we hit, if we hitted one.

      float h = map(pos).x;
id = map(pos).y;

If we hit something, we can break this loop. Here, this if condition simply check if we are closer than 0.001 from any object in the scene

if(h < 0.001)
{
break;
}
t += h;

If true: Break, if false increment t by h, to walk the hit distance along the ray. And repeat the step

If we hit the far clipping plane, we also break, wich means we have not hit any object

if(t > farClippingPlane) break;

At this point, we now have the position and the ID of the hit, so we return it

return vec2(t, id);

The Map function:

vec2 map(vec3 pos)
{
}

This function return the distance and the id of object. I won’t cover it as it is quite simple ray marching stuff, and I made a lot of tutorial on them already.

Note: In the ray cast loop we set the hit distance to -1 if we hit the the FCP and this allow us to set the distacne to -1 if we had not hit anything

/* Walking loop */
{
t += h;
if(t > farClippingPlane) break;

}

if(t > farClippingPlane) t = -1.0; 

### Modelling

To model the scene, we use SDF, in short, it is a way to define “geometry” using formulas. Each primitives are based on a distance concept, the closer to the object, the closest it gets to 0. When inside the object, it is negative, when outside the object, it is positive:

Modyfing space from inside a SDF:

When creating scene in ray-marching, chances are you will want to manipulate spaces from the original position you query it, when modifying space, it is alway usefull to have a reference to what was the original position you passed as an argument into the function.

This is why I always use a variable copy from the original position. It then allows me to fuck around to create stuff.

float trunk(vec3 pos)
{
vec3 q = pos;
// Mess around space
q.x += sin(iTime) * 5.0f;
// reset to root pos
q = pos;
// Do more stuff

}

Making the trunk and branches.

To start with the trunk, I made a simple box, then rotated the space to make the box twisted. I also add 3 more boxes to make the branching for the leafes:

To twist it, I used modified the box SDF by manipulate the space with rotation before calling it. The twisting amount can be tweaked to add more

    float twistAmount = 2.0f;
q.xz *= rot(q.y * twistAmount);

By changing the twistAmount, we make the cube more twisted. Here is are the result of 1 to 5 twisted amount. Also note we twist on the y axis, while modyfing the q.xz plane, as our rotation function returns a 2d matrices from an angle such defined in:

mat2 rot(float a)
{
float s = sin(a);
float c = cos(a);
return mat2(c, -s,s,c);

}

Here are the trunk twisting result.

The next part was to add branches to this trunk, I used the same technique by altering space with rotation and started by resetting the position to the root sdf argument call.

    q = pos;

q.z += .15;
q.xz = rotate(q.xz, 0.015);
q.xy *= rot(.85);
q.xz *= rot(q.y * ( .15));

This create a single branch,

To make branches in 4 direction, I simply made the position absolute for each axis of q

q = abs(pos);

I also made a new call to the box sdf to combine those both, using the min operator. Let’s see it as a way to combine to SDF together while using the same core function.

    float t2 = sdBox(q + vec3(1.5, -2., 0.0), vec3(.014,1.22, .125));

res = min(res, t2);

return res * .5;

So in short, we have the core trunk, and the branches that are made by altering q with abs pos.

float trunk(vec3 pos)
{
vec3 q = pos;
float res = 1.0f;

q.xz *= rot(q.y * 1.15);

res = sdBox(q, vec3(.24,3.75, .25));
q = pos;
q = abs(pos);
q.z += .15;
q.xz = rotate(q.xz, 0.015);
q.xy *= rot(.85);
q.xz *= rot(q.y * ( .15));

float t2 = sdBox(q + vec3(1.5, -2., 0.0), vec3(.014,1.22, .125));

res = min(res, t2);

return res * .5;
}

Making the leaves:

To model the leaves, I used octahedron. The sdf code for octahedron is as follow:

float octahedron( vec3 p, float s)
{
p = abs(p);
float m = p.x+p.y+p.z-s;
vec3 q;
if( 3.0*p.x < m ) q = p.xyz;
else if( 3.0*p.y < m ) q = p.yzx;
else if( 3.0*p.z < m ) q = p.zxy;
else return m*0.57735027;

float k = clamp(0.5*(q.z-q.y+s),0.0,s);
return length(vec3(q.x,q.y-s+k,q.z-k));
}


We used the same technique from the trunk modelling: Alter the space than query the leaf sdf. The idea here is that each octahedron got it’s own space manipulation before querying it, and we map each of the response using the same min operator.

Note that we make the space to be absolute before chaining the sdf.

pos = abs(pos);

And how we chained the combination of them.

return min(r, min(r2, min(r3, r4)));
float leaf(vec3 pos)
{
float r = 1.0f;
vec3 q = pos;
r = sdOctahedron(pos + vec3(0.0f, -4.4f, 0.0f), 1.0);
pos = abs(pos);
pos.xy = rotate(pos.xy, .2);
float r2 = sdOctahedron(pos + vec3(0.0f, -4.4f, 0.0f), 1.0);
pos.xz = rotate(pos.xz, .5);
float r3 = sdOctahedron(pos + vec3(0.5f, -4.4f, -0.2f), .75);
pos.y -= 1.85;
pos.yz = rotate(pos.yz, 5.);
float r4 = sdOctahedron(pos + vec3(-.5f, -1.4f, 1.f), .75);
pos = q;
return min(r, min(r2, min(r3, r4)));
}

So at this point we have enough of a simple tree. We should now take a look at the ground.

Making the ground

To alter the ground, we query a texture and check the difference between a simple plane and the texture value affecting the height of the plane. Let’s remember that a plane SDF is defined as follow:

float theFloor = sdPlane(pos);

This sdf return the position between the origin of position and the ground plane.

SDF plane function returns the distance from a the position we query and the y distance of the ground plane.

float plane(vec3 pos)
{
vec3 q = pos;

return q.y;
}


This function gives us a very flat plane.

What we want is adding a bit of relief to that plane. To do this, I will simply modify the plane SDF function to alter based on the x and z of the root position and alter it using some function. For now, in will use sin and cos from x and z from the position to alter the y pos:

So let’s draw a bit of what we want to do:

In short, we query the texture to alter the height of the plane

float plane(vec3 pos)
{

vec3 q = pos;
float displacement =  abs(sin(pos.x)) * abs(cos(pos.z));
return (q.y - displacement) * .15;
}

Giving us that kind of bubbly terrain:

To make it more interesting, let’s load a texture into the buffer0 and query that texture to alter the plane displacement.

The texture we are going to sample is this one:

    vec3 q = pos;

float displacement =  abs(sin(pos.x)) * abs(cos(pos.z));

float displacementTexture = texture(iChannel0, pos.xy).x;

return (q.y - displacementTexture) * .15;

It gives us some very weird result:

To fix this problem, we will need to make the sampling of the texture UV a lot smaller, this will then fit the the world position to sample the texture.


float displacementTexture = texture(iChannel0, pos.xz * .05).x;

return (q.y - displacementTexture) * .15;

It then gives us that.

### Lighting

Ok, now we opened a bit the hood to see how the castRay function works, how it allow us to get the distance of the nearest object from any point in worldspace we can start to compose the scene.

So when t is bigger than 0, means we have hit the scene, so we can start to paint stuff. Inside the main loop, you can see that we use the hit point and also have the material. Let’s start by showing all the hit object in the scene.

Inside the if block that check if we hit an object, can set the color to be white, this way we see all hit from the scene, I put it in white for now.

This is quite boring and useless so let’s add worldspace position and normal from every hit point.

if(hitDistance > 0.)
{
vec3 pos = ro + hitDistance * rd;
vec3 normalWS = calcNormal(pos);
}

The properly build some shading inside the main function we need to start with at least the position in worldspace and the normal of the surface.

The position in worldspace could be created using the same formula from previously. we simply create a position variable and apply the hit distance previously calculated to set where we are in 3D space.

If we apply it to every pixel we can see it this!

Calculating the normal (and note on calculating them in SDF):

To calculate the normal, we use the classical technique of ray marching calculation. In short, we want to the the normal of the hitpoint, to do such thing, what we can do is querying the map function with a slight offset based on the position worldspace.

If we analyze it from a theoretical approach, we can take the gradient of the sdf function we created at a certain point(x,y,z), to get the direction that change quicker in all those axis…

In sdf based function, if inside the object, we get negative numbers if outside the object we get positive numbers if on the surface we get 0. The direction to the surface that make you from negative to positive in the quickest way defined the orthogonal of the surface defined by the SDF function.

The gradient is written as follow, where f is the map function from our shader.

Let’s therefore use a simpler way to calculate the normal at the point. The idea is to sample the sdf function we a bit very little offset(epsilon), to get how it changes in every direction.

In short explanation, we take the position we got from last part, and add a bit of offset while querying the sdf function. In both all thre axis

Looking something like that in our code!

vec3 calcNormal(vec3 pos)
{
vec2 e = vec2(0.01, 0.0);

return normalize(
vec3(
map(pos+e.xyy).x - map(pos-e.xyy).x,
map(pos+e.yxy).x - map(pos-e.yxy).x,
map(pos+e.yyx).x - map(pos-e.yyx).x
));
}

To see it in action:

A solution for a simple sunlight is to define a sun position, and then compute what amount of light got from a point to that sun. The sun won’t move, but we can shade all the points respectively from the point we want to share to the sun.

The idea here is to take the normal of the surface(wich we calculated in the last part), and check how it is alligned or not with the sun. We use the dot product to compute that, here is a simple drawing to express that idea

So as the position we want to sunlight is getting perpendicular to the sun pos, as it gets darker. Or actually 0, in the dot product. Dot product is between -1 and 1 so we clamp it between 0 and 1.

        vec3 sunPos = normalize(vec3(6.5, 12.4, 5.52));
float sundif = clamp(dot(normalWS, sunPos), 0.0, 1.0);

This gives is the amount of sun siffuse on a single float. No color is intended in here, it simply describe how does any point we shade got the sunlight, 1 being all lit and 0 being not lit at all.

Giving us this in the image:

Same idea as the sun diffuse, but we simply make the sky diffuse from as +y.

float sky_dif = clamp(dot(normalWS,vec3(0.0,1.0,0.0)), 0.0, 1.0);;

Combining them both with color:

From the sun and sky diffuse factor, we now want to add color to them. To do this, we can multiply each light (such as sun and sky) with a vec3 representing the color of the light.


vec3 debugColor = vec3(0.0f);
vec3 basicLightLayer = vec3(0.0f);
vec3 sunColor = sundif  * /* sun color */ vec3(.5, 0.27, 0.15);
vec3 skyDefCol = sky_dif  *  /* sky color */ vec3(0.0, 0.2, 0.4);

basicLightLayer +=  sunColor;
basicLightLayer += skyDefCol;

debugColor  = vec3(basicLightLayer);

Let’s see all layer used in here.

sunColor:

Sky color:

When combined:

If we add them both we got somehow a simple lighting:

        basicLightLayer +=  sunColor;
basicLightLayer += skyDefCol;

We could also add an intensity for each layer:

        float sunLayerIntensity = 1.4f;
float skyLayerIntensity = 2.0f;
basicLightLayer +=  sunColor * sunLayerIntensity;
basicLightLayer += skyDefCol * skyLayerIntensity;

Note: Shadow can be a subject of their own, so I will not cover it deeply! I will write another text on that!.

Our little trees are not somehow lit by some sky and sun, but we do not have shadow so let’s add shadow. The idea here is to define a light source and cast a ray from a position + normal

        vec3 sundir = (vec3(0.2, 0.4, 0.2));
float hardshadow  = castRay(pos + normalWS * 0.001, sundir).x;
dc = vec3(hardshadow);

That looks weird… White should be black and white should be black. I will simply reverse the hardshadow factor. to get this:

        vec3 sundir = (vec3(0.2, 0.4, 0.2));
float hardshadow  = castRay(pos + normalWS * 0.001, sundir).x;
dc = vec3(hardshadow);

That makes more sense ðŸ™‚

So now that we have the shadow concept, let’s make it a bit a smoother…

What we could do is make the shadow smooth between 0 and 1. Let’s do a smoothstep!

        vec3 sundir = (vec3(0.2, 0.4, 0.2));
float sun_sha = smoothstep(castRay(pos + normalWS * 0.001, sundir).x,0., 1.);
dc = vec3(sun_sha);

### Material system

Once we are done with lighting, we can start to to use some different colors and material for every of our scene element. When modelling our scene, we used a vector2 to return both the distance and the material ID. To retrieve this id, we use the same technique to get the distance.

 float objectId = castRay(ro, rd).y;

So for example, let’s start with the trunks, that have objectID 4.

if(objectId == 4.0)
{
col = vec3(1.0f);
} 

So has you expected it would make the trunk white.

In the shader, I made the trunk some brown-ish color, that also change from their position in worldspace. To achieve this effect, I simply took a brown color in RGB and mapped it to 0 and 1.

Then added a sinewave to the red channel, multiply .12 with sin value of worlspace position.

        	if(objectId == 4.0)
{

col = vec3(110.0f / 255.0f, 38.0f / 255.0f, 14.0f / 255.0f) *sundif;
col += vec3(.12 * sin(pos.x), .12, .12);

} 

The leaves and so on are based on the same technique. To change the ID of an object, refers to the SDF function as previously explained.

### Overview:

The full shader code is here:

mat2 rot(float a)
{
float s = sin(a);
float c = cos(a);
return mat2(c, -s,s,c);

}

vec2 rotate(vec2 pos, float angle)
{
float c = cos(angle);
float s = sin(angle);

return mat2(c,s,-s,c) * pos;

}

mat2 Rot(float a) {
float s = sin(a);
float c = cos(a);
return mat2(c, -s, s, c);
}

vec3 R(vec2 uv, vec3 p, vec3 l, float z) {
vec3 f = normalize(l-p),
r = normalize(cross(vec3(0,1,0), f)),
u = cross(f,r),
c = p+f*z,
i = c + uv.x*r + uv.y*u,
d = normalize(i-p);
return d;
}

/* SDF */
float sdCapsule( vec3 p, vec3 a, vec3 b, float r )
{
vec3 pa = p - a, ba = b - a;
float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );
return length( pa - ba*h ) - r;
}

float sdSphere(vec3 pos, float r)
{
return length(pos) - r;
}

float sdPlane(vec3 pos)
{

/* DICK */

float x =  pos.y + (texture(iChannel0, pos.xz * 0.009f  ).x * 2.5) ;
return x * .5;
}
float sdHexPrism( vec3 p, vec2 h )
{
const vec3 k = vec3(-0.8660254, 0.5, 0.57735);
p = abs(p);
p.xy -= 2.0*min(dot(k.xy, p.xy), 0.0)*k.xy;
vec2 d = vec2(
length(p.xy-vec2(clamp(p.x,-k.z*h.x,k.z*h.x), h.x))*sign(p.y-h.x),
p.z-h.y );
return min(max(d.x,d.y),0.0) + length(max(d,0.0));
}

float sdBox( vec3 p, vec3 b )
{
vec3 q = abs(p) - b;
return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0);
}

vec2 opu(vec2 d1, vec2 d2)
{
return (d1.x < d2.x) ? d1 : d2;
}

float sdCone( in vec3 p, in vec2 c, float h )
{
// c is the sin/cos of the angle, h is height
// Alternatively pass q instead of (c,h),
// which is the point at the base in 2D
vec2 q = h*vec2(c.x/c.y,-1.0);

vec2 w = vec2( length(p.xz), p.y );
vec2 a = w - q*clamp( dot(w,q)/dot(q,q), 0.0, 1.0 );
vec2 b = w - q*vec2( clamp( w.x/q.x, 0.0, 1.0 ), 1.0 );
float k = sign( q.y );
float d = min(dot( a, a ),dot(b, b));
float s = max( k*(w.x*q.y-w.y*q.x),k*(w.y-q.y)  );
return sqrt(d)*sign(s);
}

float sdOctahedron( vec3 p, float s)
{
p = abs(p);
float m = p.x+p.y+p.z-s;
vec3 q;
if( 3.0*p.x < m ) q = p.xyz;
else if( 3.0*p.y < m ) q = p.yzx;
else if( 3.0*p.z < m ) q = p.zxy;
else return m*0.57735027;

float k = clamp(0.5*(q.z-q.y+s),0.0,s);
return length(vec3(q.x,q.y-s+k,q.z-k));
}

float h(vec2 p)
{

float ba = texture(iChannel1, p * 0.055 * sin(p.y)).x ;
float b = (sin(p.x * 2.))  ;

b*=  ba * sin( cos( 2.)) * abs(sin(1.) + 1.25);
b *= b - abs(sin(iTime));
return -b - 1.2;
}

float sdEllipsoid( vec3 p, vec3 r )
{
float k0 = length(p/r);
float k1 = length(p/(r*r));
return k0*(k0-1.0)/k1;
}

float leaf(vec3 pos)
{
float r = 1.0f;
vec3 q = pos;
r = sdOctahedron(pos + vec3(0.0f, -4.4f, 0.0f), 1.0);
pos = abs(pos);
pos.xy = rotate(pos.xy, .2);
float r2 = sdOctahedron(pos + vec3(0.0f, -4.4f, 0.0f), 1.0);
pos.xz = rotate(pos.xz, .5);
float r3 = sdOctahedron(pos + vec3(0.5f, -4.4f, -0.2f), .75);
pos.y -= 1.85;
pos.yz = rotate(pos.yz, 5.);
float r4 = sdOctahedron(pos + vec3(-.5f, -1.4f, 1.f), .75);
pos = q;
return min(r, min(r2, min(r3, r4)));
}

float trunk(vec3 pos)
{
vec3 q = pos;
float res = 1.0f;

float twistAmount = 5.0f;
q.xz *= rot(q.y * twistAmount);

res = sdBox(q, vec3(.24,3.75, .25));
q = pos;
q = abs(pos);
q.z += .15;
q.xz = rotate(q.xz, 0.015);
q.xy *= rot(.85);
q.xz *= rot(q.y * ( .15));

float t2 = sdBox(q + vec3(1.5, -2., 0.0), vec3(.014,1.22, .125));

res = min(res, t2);

return res * .5;
}

/* Pine tree */

float pineLeaf()
{
return 0.0f;
}

vec2 map(vec3 pos)
{

float theFloor = sdPlane(pos);

float box = sdBox(pos, vec3(1.));
float sphere = sdSphere(pos + vec3(0.0, -1., 0.), 1.0);

vec2 res =     vec2(theFloor,       2.0 /*Object ID*/);

pos.x = mod(pos.x+ 5., 10.) -5.;
pos.z += sin(iTime);
pos.z = mod(pos.z+ 7.5f, 15.0f) -7.5f;

float trunk = trunk(pos);
float leaf= leaf(pos);

res = opu(res, vec2(trunk,     4.0 /*Object ID*/));
res = opu(res, vec2(leaf,     5.0 /*Object ID*/));

//    res = opu(res, vec2(arms, 3.));
return res;
}

vec2 castRay(vec3 ro, vec3 rd)
{

float t = 0.0;
float id = -20.;
float farClippingPlane = 120.0;

for(int i = 0; i < 256; i++)
{

vec3 pos = ro + t * rd;
float h = map(pos).x;
id = map(pos).y;

if(h < 0.001)
{
break;
}
t += h;
if(t > farClippingPlane) break;

}

if(t > farClippingPlane) t = -1.0;

return vec2(t, id);
}

vec3 calcNormal(vec3 pos)
{
vec2 e = vec2(0.01, 0.0);

return normalize(vec3(
map(pos+e.xyy).x - map(pos-e.xyy).x,
map(pos+e.yxy).x - map(pos-e.yxy).x,
map(pos+e.yyx).x - map(pos-e.yyx).x
));
}

void resetToZero(inout vec3 r)
{
r = vec3(0.0);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{

vec2 uv = (fragCoord-.5*iResolution.xy)/iResolution.y;

vec2 m = iMouse.xy/iResolution.xy;

vec3 ro = vec3(0. + iTime, 7.55 ,-4.5);
//vec3 rd = normalize(vec3(uv.x, uv.y - .2,1.));
ro.yz *= Rot(-m.y+.4);
ro.xz *= Rot(m.x * 12.);
vec3 rd = R(uv, ro, vec3(0,.5,iTime), .7);

vec3 col = vec3(sin(uv.x * ro.y), cos(uv.y * ro.y), cos(uv.y + ro.x));

vec3 ttt = texture(iChannel1, uv * sin(ro.y)).rgb;
col *= ttt;

float hitDistance = castRay(ro, rd).x;
float objectId = castRay(ro, rd).y;

vec3 unfinishedSky;

if(hitDistance > 0.)
{
vec3 debugColor = vec3(1.);
vec3 pos = ro + hitDistance * rd;
vec3 normalWS = calcNormal(pos);

/* Global Lighting section */

// Sky
vec3 skyColor = vec3(.4, 0.75, 1.0);
float skyMovement = .5;
vec3 skyColFactor = skyColor - abs( .5 + sin(iTime * skyMovement)) * rd.y * rd.x;

// Basic lighting
vec3 sunPos = normalize(vec3(6.5, 12.4, 5.52));
vec3 sundir = normalize(vec3(0.2, 0.4, 0.2));
float sundif = clamp(dot(normalWS, sunPos), 0.0, 1.0);
float sun_sha = smoothstep(castRay(pos + normalWS * 0.001, sundir).x,0., 1.);
float sky_dif = clamp(dot(normalWS,vec3(0.0,1.0,0.0)), 0.0, 1.0);;

col = vec3(1.0, 0.7, 0.5) * sundif * sun_sha;
col += vec3(0.0, 0.2, 0.4) * sky_dif;

//col = skyColFactor;
/*End of Global Lighting section */

bool materialSystem = true;

if(materialSystem)
{
float blendingFactor = .5;

if(objectId == 2.0)
{
col *= vec3(.21, .25, .15) * sundif * sun_sha;
}

// trunk
if(objectId == 4.0)
{

col = vec3(110.0f / 255.0f, 38.0f / 255.0f, 14.0f / 255.0f) *sundif;
col += vec3(.12 * sin(pos.x), .12, .12);

}

if(objectId == 5.0)
{
//165,42,42
//rgb(110, 38, 14)

for(int i = 0; i < 4; i++)
{
col += vec3(0.0f, .1 * sin(float(i)), .0f);
}

//col += vec3(sin(pos).x, sin(pos).y + 1.5, .0f);
}

}

col = col;

/*
vec3 sundir = normalize(vec3(0.2, 0.4, 0.2));
float sundif = clamp(dot(nor, sundir), 0.0, 1.0);
float sun_sha = smoothstep(castRay(pos + nor * 0.001, sundir).x,0., 1.);
float sky_dif = clamp(dot(nor,vec3(0.0,1.0,0.0)), 0.0, 1.0);;

col = vec3(1.0, 0.7, 0.5) * sundif * sun_sha;
col += vec3(0.0, 0.2, 0.4) * sky_dif;
castRay(ro, rd).x */
}
// Output to screen
fragColor = vec4(col,1.0);
}

Happy
0 %
0 %
Excited
0 %
Sleepy
0 %
Angry
0 %
Surprise
0 %

5 Star
0%
4 Star
0%
3 Star
0%
2 Star
0%
1 Star
0%