W trzeciej części chciałbym opisać technikalia tworzenia poszczególnych modyfikacji shadera do uzyskania efektów oraz przedstawić kod shadera stanowiącego podstawkę do dalszego rozwoju:

7. Technikalia

  1. Fraktal IFS – Menger-Sponge
  2. Niebo i Słońce
  3. PostProcessing i PreProcessing
  4. Generowanie terenu
  5. Światło i cienie
  6. Ambient occlusion
  7. Odbicia
  8. Mgła
  9. Metaball

8. RayMarch shader

Działającą wersję shadera można zobaczyć tutaj

//--------------------------------------------------
// RayMarch framework v0.1
// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License
// (c) 2014 http://shad.mobi/
//
// Thanks for IQ for useful article 
// about raymarch technology
// http://www.iquilezles.org/
//--------------------------------------------------
#ifdef GL_ES
    precision mediump float;
#endif

uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;

#define RENDER_MODE 0
#define CAMERA_MODE 4
#define SHADOW_MODE 0
#define ENABLE_LIGHT 1
#define ENABLE_AO 1
#define ENABLE_MATERIAL 1
#define ENABLE_TEXTURE 1

const float PI=3.14159;
const int MAX_STEPS=128;
const float STEPS_DELTA=1.0 / 32.;
const float MAX_DISTANCE=100.0;
const float DISTANCE_BOOST=1.0;
const float EPSILON=0.01;
const float NORMAL_EPSILON=0.001;

const float SHADOW_EPSILON=0.001;
const int SHADOW_ITERATION=8;
const float SHADOW_POINT=0.1;
const float SHADOW_MIN_DISTANCE=0.0;
const float SHADOW_MAX_DISTANCE=8.0;
const float SHADOW_BLUR=1.0;
const float SHADOW_DELTA=0.1;
const float SHADOW_BOOST=1.0;

const int AO_ITERATION=5;
const float AO_START=0.05;
const float AO_STEP=0.1;
const float AO_BLUR_START=0.70;
const float AO_BLUR=0.75;

//-------------------------------------
// Data structures
//-------------------------------------
struct Material {
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    float shininess;
    int texture;
};

struct Object {
    float distance;
    float step;
    float grey;
    vec3 pos;
    vec3 normal;
    bool hit;
    Material mat;
};

struct Light {
    vec3 posistion;
    vec3 ambientColor;
    vec3 color;
};

float length2( vec2 p ) {
    return sqrt(p.x * p.x + p.y * p.y);
}

float length6( vec2 p ) {
    p=p * p * p;
    p=p * p;
    return pow(p.x + p.y, 1.0 / 6.0);
}

float length8( vec2 p ) {
    p=p * p;
    p=p * p;
    p=p * p;
    return pow(p.x + p.y, 1.0 / 8.0);
}

#if ENABLE_MATERIAL==1
    Material materials[9];
#endif
//-------------------------------------
// RayMarch primitive object 
// (procedural function)
//-------------------------------------
vec2 box(vec3 p, vec3 pos, vec3 size, float material) {
    vec3 d=abs(p - pos) - size;
    float distance=min(max(d.x, max(d.y, d.z)), 0.0) + length(max(d, 0.0));
    return vec2(distance, material);
}
vec2 sphere(vec3 p, vec3 pos, float radius, float material) {
    return vec2(length(p - pos) - radius, material);
}
vec2 plane(vec3 p, vec3 pos, vec3 normal, float material) {
    return vec2(dot(p - pos, normal), material);
}
vec2 roundBox(vec3 p, vec3 pos, vec3 size, float radius, float material) {
    return vec2(length(max(abs(p - pos) - size, 0.0)) - radius, material);
}
vec2 torus( vec3 p, vec3 pos, vec2 t, float material) {
    p=p - pos;
    vec2 q=vec2(length(p.xy) - t.x, p.z);
    return vec2(length(q) - t.y, material);
}
vec2 hexPrism(vec3 p, vec3 pos, vec2 h, float material) {
    vec3 q=abs(p - pos);
    return vec2(max(q.z - h.y, max(q.x + q.y * 0.57735, q.y * 1.1547) - h.x), material);
}
vec2 triPrism( vec3 p, vec3 pos, vec2 h, float material) {
    vec3 q=abs(p - pos);
    return vec2(max(q.z - h.y, max(q.x * 0.866025 + p.y * 0.5, -p.y) - h.x * 0.5), material);
}
vec2 capsule( vec3 p, vec3 pos, vec3 a, vec3 b, float r, float material) {
    p=p - pos;
    vec3 pa=p - a;
    vec3 ba=b - a;
    float h=clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
    return vec2(length(pa - ba * h) - r, material);
}
vec2 torus82(vec3 p, vec3 pos, vec2 t, float material) {
    p=p - pos;
    vec2 q=vec2(length2(p.xz) - t.x, p.y);
    return vec2(length8(q) - t.y, material);
}
vec2 torus88(vec3 p, vec3 pos, vec2 t, float material) {
    p=p - pos;
    vec2 q=vec2(length8(p.xz) - t.x, p.y);
    return vec2(length8(q) - t.y, material);
}

vec2 cylinder6(vec3 p, vec3 pos, vec2 h, float material) {
    p=p - pos;
    return vec2(max(length6(p.xz) - h.x, abs(p.y) - h.y), material);
}
//-------------------------------------
// Operation
//-------------------------------------
vec2 smothJoin(vec2 a, vec2 b, float k ) {
    float h=clamp(0.5 + 0.5 * (b.x - a.x) / k, 0.0, 1.0);
    float distance=mix(b.x, a.x, h) - k * h * (1.0 - h);

    if (distance > b.x){
        b.x=distance;
        return b;
    }
    a.x=distance;
    return a;
}

vec2 join(vec2 a, vec2 b) {
    if (a.x > b.x){
        return b;
    }
    return a;
}
vec2 join(vec2 a, vec2 b, vec2 c) {
    if (a.x > b.x){
        if (b.x > c.x){
            return c;
        }
        return b;
    }
    if (a.x > c.x){
        return c;
    }
    return a;
}

vec2 intersect(vec2 a, vec2 b) {
    if (a.x > b.x){
        return a;
    }
    return b;
}
vec2 intersect(vec2 a, vec2 b, vec2 c) {
    if (a.x > b.x){
        if (c.x > a.x){
            return c;
        }
        return a;
    }
    if (c.x > b.x){
        return c;
    }
    return b;
}
vec2 diff(vec2 a, vec2 b) {
    b.x=-b.x;
    if (b.x > a.x){
        return b;
    }
    return a;
}

vec2 diff(vec2 a, vec2 b, vec2 c) {
    b.x=-b.x;
    if (b.x > a.x){
        c.x=-c.x;
        if (c.x > c.x){
            return c;
        }
        return b;
    }
    if (b.x > c.x){
        a.x=-a.x;
        if (a.x > c.x){
            return a;
        }
        return c;
    }
    c.x=-c.x;
    if (c.x > a.x){
        return c;
    }
    return a;
}

vec3 repetition(vec3 pos, vec3 c) {
    return mod(pos, c) - 0.5 * c;
}
vec3 scale(vec3 p, float s) {
    // return primitive(p/s)*s;
    return (p / s) * s;
}

vec3 rotateX(vec3 p, float angle) {
    float c=cos(angle);
    float s=sin(angle);
    return vec3(p.x, c * p.y - s * p.z, s * p.y + c * p.z);
}
vec3 rotateY(vec3 p, float angle) {
    float c=cos(angle);
    float s=sin(angle);
    return vec3(c * p.x - s * p.z, p.y, s * p.x + c * p.z);
}
vec3 rotateZ(vec3 p, float angle) {
    float c=cos(angle);
    float s=sin(angle);
    return vec3(c * p.x - s * p.y, s * p.x + c * p.y, p.z);
}

vec3 rotateX(vec3 p, float sinus, float cosinus) {
    return vec3(p.x, cosinus * p.y - sinus * p.z, sinus * p.y + cosinus * p.z);
}
vec3 rotateY(vec3 p, float sinus, float cosinus) {
    return vec3(cosinus * p.x - sinus * p.z, p.y, sinus * p.x + cosinus * p.z);
}
vec3 rotateZ(vec3 p, float sinus, float cosinus) {
    return vec3(cosinus * p.x - sinus * p.y, sinus * p.x + cosinus * p.y, p.z);
}
vec3 twist(vec3 p, float tc, float ts ) {
    float c=-1. * cos(tc * p.y + ts);
    float s=1. * sin(tc * p.y + ts);
    mat2 m=mat2(c, -s, s, c);
    return vec3(m * p.xz, p.y);
}
vec3 bend(vec3 p, float tc, float ts ) {
    float c=-1. * cos(tc * p.y + ts);
    float s=1. * sin(tc * p.y + ts);
    mat2 m=mat2(c, -s, s, c);
    return vec3(m * p.xy, p.z);
}

//-------------------------------------
// Displacement map
//-------------------------------------
vec2 sinDisplace(vec3 p, vec2 primitive, float steps, float scale) {
    primitive.x+=(sin(steps * p.x) * sin(steps * p.y) * sin(steps * p.z)) * scale;
    return primitive;
}
vec2 sinDisplace(vec3 p, vec2 primitive, float steps, float scale, float tim) {
    primitive.x+=(sin(steps * p.x + tim) * sin(steps * p.y + tim) * sin(steps * p.z + tim)) * scale;
    return primitive;
}
//-------------------------------------
// Map function
// Return 2 dimensional vector, where:
// x - distance
// y - material ID
//-------------------------------------
vec2 map(vec3 p) {

    vec2 hit=plane(p, vec3(0.0, 0.0, 0.0), vec3(0.1, 1.0, 0.1), 0.0);
    //vec2 hit=vec2(MAX_DISTANCE);
    hit=join(hit, roundBox(twist(p, 2., 0.), vec3(0.0, 0.0, 0.0), vec3(1.2, 1.2, 1.2), 0.2, 4.0));
    hit=join(hit, roundBox(bend(p, 0.4, 0.2), vec3(4.0, 0.0, 0.0), vec3(1.2, 1.2, 1.2), 0.2, 2.0));
    return hit;

    // return sinDisplace(p, sphere(p, vec3(0.0, 0.0, 0.0), 2.0, 0.0), 5., 0.2, time);
    // return roundBox(bend(p, 0.4, 0.), vec3(0.0,0.0,0.0), vec3(1.2,1.2,1.2), 0.2, 0.0);
    // return roundBox(twist(p, 2., 0.), vec3(0.0,0.0,0.0), vec3(1.2,1.2,1.2), 0.2, 0.0);
    // vec2 ob1=torus(rotateX(p, time), vec3(0.0,0.0,0.0), vec2(2.0,0.5), 0.0);
    // vec2 ob2=roundBox(rotateY(p, time * 2.0), vec3(0.0,0.0,0.0), vec3(1.2,1.2,1.2), 0.2, 0.0);
    // return diff(ob1, ob2);
    // return roundBox(scale(p, 1.0), vec3(0.0,0.0,0.0), vec3(1.2,1.2,1.2), 0.2, 0.0);
    // return roundBox(repetition(p, vec3(5.0, 10.0 ,5.0)), vec3(0.0,0.0,0.0), vec3(1.2,1.2,1.2), 0.2, 0.0);
    //vec2 ob1=torus(p, vec3(0.0,0.0,0.0), vec2(2.0,0.5), 0.0);
    //vec2 ob2=roundBox(p, vec3(0.0,0.0,0.0), vec3(1.2,1.2,1.2), 0.2, 0.0);
    //return intersect(ob1, ob2);
    // return join(ob1, ob2);
    // return smothJoin(ob1, ob2, 0.5);
    // return diff(ob1, ob2);
    // return torus82(p, vec3(0.0,0.0,0.0), vec2(1.2, 0.4), 0.0);
    // return torus88(p, vec3(0.0,0.0,0.0), vec2(1.2, 0.4), 0.0);
    // return capsule(p, vec3(0.0,0.0,0.0), vec3(1.0,1.0,2.0), vec3(-1.0,2.0,1.0), 1.4, 0.0);
    // return triPrism(p, vec3(0.0,0.0,0.0), vec2(1.5,1.0), 0.0);
    // return hexPrism(p, vec3(0.0,0.0,0.0), vec2(1.5,1.0), 0.0);
    // return torus(p, vec3(0.0,0.0,0.0), vec2(2.0,0.5), 0.0);
    // return roundBox(p, vec3(0.0,0.0,0.0), vec3(1.0,1.0,1.0), 0.2, 0.0);
    // return box(p, vec3(0.0,0.0,0.0), vec3(1.0,1.0,1.0), 0.0);
    // return sphere(p, vec3(0.0,0.0,0.0), 2.0, 0.0);
    // return plane(p, vec3(0.0,0.0,0.0), vec3(0.1,1.0,0.1), 0.0);
}

//-------------------------------------
// Ray Marching
//-------------------------------------
vec3 pointNormal(vec3 pos) {
    vec3 eps=vec3(NORMAL_EPSILON, 0.0, 0.0);
    vec3 nor=vec3(
            map(pos + eps.xyy).x - map(pos - eps.xyy).x, 
            map(pos + eps.yxy).x - map(pos - eps.yxy).x, 
            map(pos + eps.yyx).x - map(pos - eps.yyx).x
    );
    return normalize(nor);
}

Object rayMarching(vec3 ray, vec3 rayDir) {

    float distance=0.0;
    Object sceneObject;
    vec2 mapOut;

    sceneObject.hit=false;

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

        mapOut=map(ray);
        ray += rayDir * mapOut.x;
        distance += mapOut.x;
        sceneObject.step += STEPS_DELTA;

        if (mapOut.x < EPSILON){             sceneObject.hit=true;           distance=0.0;           break;      }       if (distance > MAX_DISTANCE){
            sceneObject.hit=false;
            distance=MAX_DISTANCE;
            break;
        }
    }

    sceneObject.distance=distance / MAX_DISTANCE;
    sceneObject.pos=ray;
    sceneObject.grey=1.0 - sceneObject.distance;

    if (sceneObject.hit){
        sceneObject.normal=pointNormal(ray);

        //
        // Assign material to object
        //
        #if ENABLE_MATERIAL==1
            int matIndex=int(floor(mapOut.y));
            for(int i=0; i < 9; i++) {
                if (matIndex == i){
                    sceneObject.mat=materials[i];
                    break;
                }
            }
        #else       
            sceneObject.mat.ambient=vec3(0.3);
            sceneObject.mat.diffuse=vec3(0.6);
            sceneObject.mat.specular=vec3(1.0);
            sceneObject.mat.shininess=4.0;
        #endif
    }
    return sceneObject;
}

//-------------------------------------
// Light, shadows
//-------------------------------------

vec3 pointLight(vec3 color, Light light, Object sceneObject) {
    vec3 lightDir=normalize(light.posistion - sceneObject.pos);
    float lightPower=dot(sceneObject.normal, lightDir);

    // Ambient
    color=color + light.ambientColor;

    // Diffuse
    color=color + lightPower * light.color;

    // Specular
    color=color + (light.color * sceneObject.mat.specular * pow(lightPower, sceneObject.mat.shininess));
    return clamp(color, 0.0, 1.0);
}

float pointShadow(in vec3 ro, in vec3 rd) {
    float t=SHADOW_MIN_DISTANCE;
    for(int i=0; i < SHADOW_ITERATION; i++) {
        float h=map(ro + rd * t).x;
        if (h < SHADOW_EPSILON){             return 0.0;         }       t += h * SHADOW_BOOST;      if (t > SHADOW_MAX_DISTANCE){
            break;
        }
    }
    return 1.0;
}

float pointSoftShadow(in vec3 ro, in vec3 rd, float k) {
    float res=1.0;
    float t=SHADOW_MIN_DISTANCE;
    for(int i=0; i < SHADOW_ITERATION; i++) {
        float h=map(ro + rd * t).x;
        if (h < SHADOW_EPSILON){             return 0.0;         }       res=min(res, k * h / t);        t += SHADOW_DELTA + h * SHADOW_BOOST;       if (t > SHADOW_MAX_DISTANCE){
            break;
        }
    }
    return res;
}

//
// Ambient occlusion
//
float pointAO(in vec3 pos, in vec3 nor) {
    float totao=0.0;
    float sca=AO_BLUR_START;
    for(int aoi=0;
    aoi < AO_ITERATION;
    aoi++) {
        float hr=AO_START + AO_STEP * float(aoi);
        vec3 aoPos=nor * hr + pos;
        float distance=map(aoPos).x;
        totao += -(distance - hr) * sca;
        sca *= AO_BLUR;
    }
    return clamp(1.0 - 4.0 * totao, 0.0, 1.0);
}

//-------------------------------------
// Texture
//-------------------------------------
vec3 debugSteps(float distance) {
    return vec3(
            (distance < 0.25) ? 1. - distance * 4. : ((distance > 0.24 && distance < 0.50) ? 0.0 + distance * 2. : 0.), 
            (distance < 0.25) ? 1. - distance * 4. : ((distance > 0.49 && distance < 0.75) ? distance * 1.2 : 0.), 
            (distance < 0.15) ? 1. - distance * 4. : ((distance > 0.75) ? distance : 0.)
    );
}

vec3 checkers(vec3 pos, vec3 normal, float size) {
    vec3 p=cross(pos, normal);
    float c=floor(mod((p.x / size) + floor(p.y / size) + floor(p.z / size), 2.0)) * size;
    return vec3(clamp(c, 0.3, 1.0));
}

vec3 distancecolor(vec3 pos, vec3 normal) {
    return normalize(pos);
}

vec3 plasma(vec3 pos, vec3 normal) {
    return vec3(
        pos.x * sin(pos.y * 50. + time / 0.4), 
        pos.y * cos(pos.z * 10. + time / 0.2), 
        pos.x * sin(pos.y * 15. + time / 0.10)
    );
}

void main() {

    vec2 pos=(gl_FragCoord.xy * 2.0 - resolution.xy) / resolution.y;
    vec2 uv=pos * vec2(resolution.x / resolution.y, 1.0);

    //
    // Light config
    //
    Light light1;
    light1.posistion=vec3(sin(time * 2.25) * 4.0, 5.0, cos(time * 1.25) * 5.0);
    light1.ambientColor=vec3(0.0, 0.0, 0.0);
    light1.color=vec3(1.0, 1.0, 1.0);

    //
    // Material config
    //
    #if ENABLE_MATERIAL==1
        materials[0]=Material(vec3(0.0, 0.0, 0.0), vec3(0.5, 0.5, 0.5), vec3(1.0, 1.0, 1.0), 16., 1);
        materials[1]=Material(vec3(0.0, 0.0, 0.0), vec3(0.6, 0.6, 0.6), vec3(1.0, 0.0, 0.0), 16., 3);
        materials[2]=Material(vec3(0.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0), vec3(1.0, 1.0, 1.0), 16., 0);
        materials[3]=Material(vec3(0.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(1.0, 1.0, 1.0), 16., 0);
        materials[4]=Material(vec3(0.0, 0.0, 0.0), vec3(0.0, 0.0, 1.0), vec3(1.0, 1.0, 1.0), 16., 0);
        materials[5]=Material(vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 0.0), vec3(1.0, 1.0, 1.0), 16., 0);
        materials[6]=Material(vec3(0.0, 0.0, 0.0), vec3(0.0, 1.0, 1.0), vec3(1.0, 1.0, 1.0), 16., 0);
        materials[7]=Material(vec3(0.0, 0.0, 0.0), vec3(1.0, 0.0, 1.0), vec3(1.0, 1.0, 1.0), 16., 0);
    #endif
    //
    // Camera config
    //
    #if CAMERA_MODE==0
        vec3 camPos=vec3(12.0, 5.0, 12.0);
        vec3 camTarget=vec3(0.0, 0.0, 0.0);
    #elif CAMERA_MODE==1
        vec3 camPos=vec3(10.0, 0.0, 10.0);
        vec3 camTarget=vec3(0.0, 0.0, 0.0);
    #elif CAMERA_MODE==2
        vec3 camPos=vec3(mouse.x, mouse.y, 5.0);
        vec3 camTarget=vec3(0.0, 0.0, 0.0);
    #elif CAMERA_MODE==3
        vec3 camPos=vec3(-sin(time / 10.0) * 5.0, 5, cos(time / 10.0) * 5.0);
        vec3 camTarget=vec3(0.0, 0.0, 0.0);
    #else
        vec3 camPos=vec3(cos(time * 0.5) * 4., 3. + sin(time * 0.5) * 3., 4.0);
        vec3 camTarget=vec3(0.0, 0.0, 0.0);
    #endif
    //
    // Calculate camera vectors
    //
    vec3 camUp=normalize(vec3(0.0, 1.0, 0.0));
    vec3 camDir=normalize(camTarget - camPos);
    vec3 camSide=cross(camDir, camUp);
    float focus=1.8;

    vec3 rayDir=normalize(camSide * pos.x + camUp * pos.y + camDir * focus);
    vec3 ray=camPos;

    //
    // RayMarching
    //
    Object sceneObject=rayMarching(ray, rayDir);

    //
    // Color process
    //
    vec3 outColor=vec3(0.0, 0.0, 0.0);
    if (sceneObject.hit){
        #if RENDER_MODE==0
            outColor=sceneObject.mat.diffuse;
        #elif RENDER_MODE==1    
            outColor=sceneObject.mat.diffuse * sceneObject.grey;
        #elif RENDER_MODE==2
            outColor=vec3(sceneObject.grey);
        #elif RENDER_MODE==3    
            outColor=sceneObject.normal;
        #elif RENDER_MODE==4    
            outColor=vec3(sceneObject.distance);
        #elif RENDER_MODE==5    
            outColor=sceneObject.pos;
        #endif          
        #if ENABLE_TEXTURE
            if (sceneObject.mat.texture == 1){
                outColor=outColor * checkers(sceneObject.pos, sceneObject.normal, 2.0);
            } else if (sceneObject.mat.texture == 2){
                outColor=outColor * distancecolor(sceneObject.pos, sceneObject.normal);
            } else if (sceneObject.mat.texture == 3){
                outColor=outColor * plasma(sceneObject.pos, sceneObject.normal);
            }
        #endif
        #if ENABLE_LIGHT
            vec3 lightColor=vec3(0.0);
            lightColor=pointLight(lightColor, light1, sceneObject);
        #else
            vec3 lightColor=vec3(1.0);
        #endif  

        float baseShadow=1.0;
        #if ENABLE_AO
            float ao=pointAO(sceneObject.pos, sceneObject.normal);
            baseShadow=baseShadow * ao;
        #endif          
        #if SHADOW_MODE==1
            float shadow=pointShadow(sceneObject.pos + SHADOW_POINT * sceneObject.normal, light1.posistion);
            baseShadow=baseShadow * shadow;
        #endif
        #if SHADOW_MODE==2
            float shadow=pointSoftShadow(sceneObject.pos + SHADOW_POINT * sceneObject.normal, light1.posistion, SHADOW_BLUR);
            baseShadow=baseShadow * shadow;
        #endif

        outColor=outColor * lightColor * baseShadow + sceneObject.mat.ambient;
    } else {
        //
        // Background color
        //
        //outColor=mix(vec3(0.0, 0.0, 0.0), vec3(1.0,0.0,0.0), 20.0/abs(sceneObject.pos.y));
        //outColor=mix(vec3(0.0, 0.0, 0.0), vec3(1.0,0.0,0.0), sceneObject.pos.y/MAX_DISTANCE);
        //outColor=mix(vec3(0.0, 0.0, 1.0), vec3(1.0,0.0,0.0), rayDir.x*5.);
        //outColor=rayDir;
        //outColor=abs(rayDir);
        outColor=mix(vec3(0.0, 0.0, 0.5), vec3(0.0, 0.4, 0.5), rayDir.y * 3.);
    }

    gl_FragColor=vec4(outColor.r, outColor.g, outColor.b, 1.0);
}

9. Literatura

Pozostałe części: Część I – wstep, opis algorytmu Część II – obiekty podstawowe, modyfikatory