2014-09-14_21-50-06

http://glsl.heroku.com/e#1159.8

// Coffee & Tablet - @P_Malin

#ifdef GL_ES
precision highp float;
#endif

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

uniform sampler2D backbuffer;

#define PI 3.141592654

#define ENABLE_MONTE_CARLO
#define ENABLE_PERLIN_NOISE
#define ENABLE_REFLECTIONS
#define ENABLE_FOG
#define ENABLE_SPECULAR
#define ENABLE_POINT_LIGHT
#define ENABLE_POINT_LIGHT_FLARE

#ifdef ENABLE_MONTE_CARLO
vec4 gPixelRandom;
vec3 gRandomNormal;

void CalcPixelRandom()
{
    vec4 s1 = sin(time * 3.3422 + gl_FragCoord.xxxx * vec4(324.324234, 563.324234, 657.324234, 764.324234)) * 543.3423;
    vec4 s2 = sin(time * 1.3422 + gl_FragCoord.yyyy * vec4(567.324234, 435.324234, 432.324234, 657.324234)) * 654.5423;
    gPixelRandom = fract(2142.4 + s1 + s2);
    gRandomNormal = normalize( gPixelRandom.xyz - 0.5);
}

#endif

#ifdef ENABLE_PERLIN_NOISE

#define NOISE_TILE_SIZE 64.0
#define NOISE_TILE_COUNT 4.0

#define kNoisePersistence 2.0
#define kNoiseAmplitude 0.04

float GetNoise( const in vec2 vPos )
{
    return fract(sin(vPos.x * 324.324234) * 234.324 + sin(vPos.y * 323.324234) * 122.324);
}

float GetSmoothNoise(const in vec2 vCoord, const in float fWrap)
{
    vec2 vCoordFloor = floor(vCoord);
    vec2 vCoordFract = fract(vCoord);

    vec2 vICoordFract = 1.0 - vCoordFract;

    vec4 vCoordPacked =  vCoordFloor.xyxy;
    vCoordPacked.zw += 1.0;
    vCoordPacked = mod(vCoordPacked, fWrap);

    float tl = GetNoise( vCoordPacked.xy );
    float tr = GetNoise( vCoordPacked.zy );
    float bl = GetNoise( vCoordPacked.xw );
    float br = GetNoise( vCoordPacked.zw );

    return     tl * vICoordFract.x * vICoordFract.y
                    + tr * vCoordFract.x  * vICoordFract.y
                    + bl * vICoordFract.x * vCoordFract.y
                    + br * vCoordFract.x  * vCoordFract.y;
}

// we store the noise texture in the backbuffer alpha channel
float CalculateNoiseTexture()
{
    vec2 vTileIndex = floor(gl_FragCoord.xy / NOISE_TILE_SIZE);
    if(vTileIndex.y > 0.0)
    {
        return 1.0;
    }

    if(vTileIndex.x > NOISE_TILE_COUNT)
    {
        return 0.0;         
    }

    float fFrequency = pow(0.5, vTileIndex.x);

    float fThisNoise = GetSmoothNoise( gl_FragCoord.xy * fFrequency, NOISE_TILE_SIZE * fFrequency);

    float fSampledNoise = texture2D(backbuffer, (gl_FragCoord.xy + vec2(NOISE_TILE_SIZE, 0.0)) / resolution ).a;

    float fAmplitude = kNoiseAmplitude * pow(kNoisePersistence, vTileIndex.x);
    return fThisNoise * fAmplitude + fSampledNoise;
}

float SampleNoiseTexture( vec2 vUV )
{
    return texture2D(backbuffer, (fract(vUV) * NOISE_TILE_SIZE) / resolution ).a;
}
#else
float SampleNoiseTexture( vec2 vUV )
{
    return dot(sin(vUV * vec2(8.0,8.0)), vec2(0.5));
}
#endif

struct C_Ray
{
    vec3 vOrigin;
    vec3 vDir;
};

struct C_HitInfo
{
    vec3 vPos;
    float fDistance;
    vec3 vObjectId;
};

struct C_Material
{
    vec3 cAlbedo;
    float fR0;
    float fSmoothness;
    vec2 vParam;
};

vec3 RotateX( const in vec3 vPos, const in float fAngle )
{
    float s = sin(fAngle);
    float c = cos(fAngle);

    vec3 vResult = vec3( vPos.x, c * vPos.y + s * vPos.z, -s * vPos.y + c * vPos.z);

    return vResult;
}

vec3 RotateY( const in vec3 vPos, const in float fAngle )
{
    float s = sin(fAngle);
    float c = cos(fAngle);

    vec3 vResult = vec3( c * vPos.x + s * vPos.z, vPos.y, -s * vPos.x + c * vPos.z);

    return vResult;
}

vec3 RotateZ( const in vec3 vPos, const in float fAngle )
{
    float s = sin(fAngle);
    float c = cos(fAngle);

    vec3 vResult = vec3( c * vPos.x + s * vPos.y, -s * vPos.x + c * vPos.y, vPos.z);

    return vResult;
}

vec4 DistCombineUnion( const in vec4 v1, const in vec4 v2 )
{
    //if(v1.x < v2.x) return v1; else return v2;
    return mix(v1, v2, step(v2.x, v1.x));
}

vec4 DistCombineIntersect( const in vec4 v1, const in vec4 v2 )
{
    return mix(v2, v1, step(v2.x,v1.x));
}

vec4 DistCombineSubtract( const in vec4 v1, const in vec4 v2 )
{
    return DistCombineIntersect(v1, vec4(-v2.x, v2.yzw));
}

vec3 DomainRepeatXZGetTile( const in vec3 vPos, const in vec2 vRepeat, out vec2 vTile )
{
    vec3 vResult = vPos;
    vec2 vTilePos = (vPos.xz / vRepeat) + 0.5;
    vTile = floor(vTilePos + 1000.0);
    vResult.xz = (fract(vTilePos) - 0.5) * vRepeat;
    return vResult;
}

vec3 DomainRepeatXZ( const in vec3 vPos, const in vec2 vRepeat )
{
    vec3 vResult = vPos;
    vec2 vTilePos = (vPos.xz / vRepeat) + 0.5;
    vResult.xz = (fract(vTilePos) - 0.5) * vRepeat;
    return vResult;
}

vec3 DomainRepeatY( const in vec3 vPos, const in float fSize )
{
    vec3 vResult = vPos;
    vResult.y = (fract(vPos.y / fSize + 0.5) - 0.5) * fSize;
    return vResult;
}

vec3 DomainRotateSymmetry( const in vec3 vPos, const in float fSteps )
{
    float angle = atan( vPos.x, vPos.z );

    float fScale = fSteps / (PI * 2.0);
    float steppedAngle = (floor(angle * fScale + 0.5)) / fScale;

    float s = sin(-steppedAngle);
    float c = cos(-steppedAngle);

    vec3 vResult = vec3( c * vPos.x + s * vPos.z,
                 vPos.y,
                -s * vPos.x + c * vPos.z);

    return vResult;
}

float GetDistanceXYTorus( const in vec3 p, const in float r1, const in float r2 )
{
    vec2 q = vec2(length(p.xy)-r1,p.z);
    return length(q)-r2;
}

float GetDistanceYZTorus( const in vec3 p, const in float r1, const in float r2 )
{
    vec2 q = vec2(length(p.yz)-r1,p.x);
    return length(q)-r2;
}

float GetDistanceCylinderY(const in vec3 vPos, const in float r)
{
    return length(vPos.xz) - r;
}

float GetDistanceBox( const in vec3 vPos, const in vec3 vSize )
{
    vec3 vDist = (abs(vPos) - vSize);
    return max(vDist.x, max(vDist.y, vDist.z));
}

float GetDistanceRoundedBox( const in vec3 vPos, const in vec3 vSize, float fRadius )
{
    vec3 vClosest = max(min(vPos, vSize), -vSize);
    return length(vClosest - vPos) - fRadius;
}

float GetDistanceMug( const in vec3 vPos )
{
    float fDistCylinderOutside = length(vPos.xz) - 1.0;
    float fDistCylinderInterior = length(vPos.xz) - 0.9;
    float fTop = vPos.y - 1.0;

    float r1 = 0.6;
    float r2 = 0.15;
    vec2 q = vec2(length(vPos.xy + vec2(1.2, -0.1))-r1,vPos.z);
    float fDistHandle = length(q)-r2;

    float fDistMug = max(max(min(fDistCylinderOutside, fDistHandle), fTop), -fDistCylinderInterior);
    return fDistMug;
}

float GetDistanceCoffee( const in vec3 vPos )
{
    float fTopCoffee = vPos.y - 0.7;
    float fDistCylinderCoffee = length(vPos.xz) - 0.95;

    float fDistCoffee = max(fTopCoffee, fDistCylinderCoffee);
    return fDistCoffee;
}

vec4 GetDistanceTablet( const in vec3 vPos )
{             
    vec3 vBevelPos = vPos - vec3(0.0, 1.71, 0.0);
    float r = 1.0;
    float fBevelDist = GetDistanceRoundedBox( vBevelPos, vec3(1.5, 1.0, 2.0), r );

    vec3 vCasePos = vPos - vec3(0.0, 0.0, 0.0);
    float fCaseDist = GetDistanceRoundedBox( vCasePos, vec3(1.5, 1.0, 2.0), 0.5 );

    vec4 vResult = vec4(max(fBevelDist, fCaseDist), 4.0, vPos.xz);

    vec4 vScreenDist = vec4(-vPos.y, 5.0, vPos.xz);
    vResult = DistCombineSubtract(vResult, vScreenDist);

    vec4 vButtonDist = vec4( length(vPos + vec3(0.0, -0.25, 2.1)) - 0.3, 5.0, vPos.xz);
    vResult = DistCombineSubtract(vResult, vButtonDist);

    return vResult;
}

// result is x=scene distance y=material or object id; zw are material specific parameters (maybe uv co-ordinates)
vec4 GetDistanceScene( const in vec3 vPos )
{           
    vec4 vResult = vec4(10000.0, -1.0, 0.0, 0.0);

    vec3 vMugDomain = vPos + vec3(2.4, 0.0, -2.0);
    vMugDomain = RotateY(vMugDomain, 1.0);

    vec4 vDistMug = vec4( GetDistanceMug(vMugDomain), 2.0, vMugDomain.xy);
    vResult = DistCombineUnion(vResult, vDistMug);

    vec4 vDistCoffee = vec4( GetDistanceCoffee(vMugDomain), 3.0, vMugDomain.xz);
    vResult = DistCombineUnion(vResult, vDistCoffee);

    vec4 vDistFloor = vec4(vPos.y + 1.0, 1.0, vPos.xz);
    vResult = DistCombineUnion(vResult, vDistFloor);

    vec3 vTabletDomain = vPos;
    vTabletDomain += vec3(-0.8, 0.7, 0.0);
    vTabletDomain = RotateY(vTabletDomain, -1.0);
    vec4 vDistTablet = GetDistanceTablet(vTabletDomain);
    vResult = DistCombineUnion(vResult, vDistTablet);

    return vResult;
}

C_Material GetObjectMaterial( const in vec3 vObjId, const in vec3 vPos )
{
    C_Material mat;

    if(vObjId.x < 1.5)
    {
        // floor
        float fBlend = SampleNoiseTexture(vPos.xz * 0.2 + vec2(0.0, sin(mod(vPos.x,4.0))));                    
        mat.fR0 = 0.02;
        mat.fSmoothness = fBlend;
        mat.cAlbedo = mix(vec3(0.7, 0.8, 0.3), vec3(0.5, 0.3, 0.1), fBlend);
    }
    else
    if(vObjId.x < 2.5)
    {
        // mug
        mat.fR0 = 0.05;
        mat.fSmoothness = 0.9;
        mat.cAlbedo = vec3(0.05, 0.35, 0.75);
    }
    else
    if(vObjId.x < 3.5)
    {
        // coffee
        mat.fR0 = 0.01;
        mat.fSmoothness = 1.0;
        mat.cAlbedo = vec3(0.5, 0.3, 0.2);
    }
    else
    if(vObjId.x < 4.5)
    {
        // tablet back
        mat.fR0 = 0.25;
        mat.fSmoothness = 0.0;
        mat.cAlbedo = vec3(0.8, 0.8, 0.8);                            
    }
    else
    {
        // tablet screen
        mat.fR0 = 0.01;
        mat.fSmoothness = 1.0;                               
        mat.cAlbedo = vec3(0.025);
    }

    return mat;
}

vec3 GetSkyGradient( const in vec3 vDir )
{
    float fBlend = vDir.y * 0.5 + 0.5;
    return mix(vec3(0.0, 0.0, 0.0), vec3(0.4, 0.9, 1.0), fBlend);
}

vec3 GetLightPos()
{
    vec3 vLightPos = vec3(0.0, 1.0, 3.0);
    #ifdef ENABLE_MONTE_CARLO         
    vLightPos += gRandomNormal * 0.2;
    #endif
    return vLightPos;
}

vec3 GetLightCol()
{
    return vec3(32.0, 6.0, 1.0);
}

vec3 GetAmbientLight(const in vec3 vNormal)
{
    return GetSkyGradient(vNormal);
}

#define kFogDensity 0.0025
void ApplyAtmosphere(inout vec3 col, const in C_Ray ray, const in C_HitInfo intersection)
{
    #ifdef ENABLE_FOG
    // fog
    float fFogAmount = exp(intersection.fDistance * -kFogDensity);
    vec3 cFog = GetSkyGradient(ray.vDir);
    col = mix(cFog, col, fFogAmount);
    #endif

    // glare from light (a bit hacky - use length of closest approach from ray to light)
    #ifdef ENABLE_POINT_LIGHT_FLARE
    vec3 vToLight = GetLightPos() - ray.vOrigin;
    float fDot = dot(vToLight, ray.vDir);
    fDot = clamp(fDot, 0.0, intersection.fDistance);

    vec3 vClosestPoint = ray.vOrigin + ray.vDir * fDot;
    float fDist = length(vClosestPoint - GetLightPos());
    col += GetLightCol() * 0.01/ (fDist * fDist);
    #endif      
}

vec3 GetSceneNormal( const in vec3 vPos )
{
    // tetrahedron normal
    float fDelta = 0.025;

    vec3 vOffset1 = vec3( fDelta, -fDelta, -fDelta);
    vec3 vOffset2 = vec3(-fDelta, -fDelta,  fDelta);
    vec3 vOffset3 = vec3(-fDelta,  fDelta, -fDelta);
    vec3 vOffset4 = vec3( fDelta,  fDelta,  fDelta);

    float f1 = GetDistanceScene( vPos + vOffset1 ).x;
    float f2 = GetDistanceScene( vPos + vOffset2 ).x;
    float f3 = GetDistanceScene( vPos + vOffset3 ).x;
    float f4 = GetDistanceScene( vPos + vOffset4 ).x;

    vec3 vNormal = vOffset1 * f1 + vOffset2 * f2 + vOffset3 * f3 + vOffset4 * f4;

    return normalize( vNormal );
}

#define kRaymarchEpsilon 0.01
#define kRaymarchMatIter 256
#define kRaymarchStartDistance 0.1

// This is an excellent resource on ray marching -> http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm
void Raymarch( const in C_Ray ray, out C_HitInfo result, const float fMaxDist, const int maxIter )
{          
    result.fDistance = kRaymarchStartDistance;
    result.vObjectId.x = 0.0;

    for(int i=0;i<=kRaymarchMatIter;i++)                
    {
        result.vPos = ray.vOrigin + ray.vDir * result.fDistance;
        vec4 vSceneDist = GetDistanceScene( result.vPos );
        result.vObjectId = vSceneDist.yzw;

        // abs allows backward stepping - should only be necessary for non uniform distance functions
        if((abs(vSceneDist.x) <= kRaymarchEpsilon) || (result.fDistance >= fMaxDist) || (i > maxIter))
        {
            break;
        }                          

        result.fDistance = result.fDistance + vSceneDist.x;      
    }

    if(result.fDistance >= fMaxDist)
    {
        result.vObjectId.x = 0.0;
        result.fDistance = 1000.0;
    }
}

float GetShadow( const in vec3 vPos, const in vec3 vLightDir, const in float fLightDistance )
{
    C_Ray shadowRay;
    shadowRay.vDir = vLightDir;
    shadowRay.vOrigin = vPos;

    C_HitInfo shadowIntersect;
    Raymarch(shadowRay, shadowIntersect, fLightDistance, 32);

    return step(0.0, shadowIntersect.fDistance) * step(fLightDistance, shadowIntersect.fDistance );           
}

// http://en.wikipedia.org/wiki/Schlick's_approximation
float Schlick( const in vec3 vNormal, const in vec3 vView, const in float fR0, const in float fSmoothFactor)
{
    float fDot = dot(vNormal, -vView);
    fDot = min(max((1.0 - fDot), 0.0), 1.0);
    float fDot2 = fDot * fDot;
    float fDot5 = fDot2 * fDot2 * fDot;
    return fR0 + (1.0 - fR0) * fDot5 * fSmoothFactor;
}

float GetDiffuseIntensity(const in vec3 vLightDir, const in vec3 vNormal)
{
    return max(0.0, dot(vLightDir, vNormal));
}

float GetBlinnPhongIntensity(const in C_Ray ray, const in C_Material mat, const in vec3 vLightDir, const in vec3 vNormal)
{            
    vec3 vHalf = normalize(vLightDir - ray.vDir);
    float fNdotH = max(0.0, dot(vHalf, vNormal));

    float fSpecPower = exp2(4.0 + 6.0 * mat.fSmoothness);
    float fSpecIntensity = (fSpecPower + 2.0) * 0.125;

    return pow(fNdotH, fSpecPower) * fSpecIntensity;
}

// use distance field to evaluate ambient occlusion
float GetAmbientOcclusion(const in C_Ray ray, const in C_HitInfo intersection, const in vec3 vNormal)
{
    vec3 vPos = intersection.vPos;

    float fAmbientOcclusion = 1.0;

    float fDist = 0.0;
    for(int i=0; i<=5; i++)
    {
        fDist += 0.1;

        vec4 vSceneDist = GetDistanceScene(vPos + vNormal * fDist);

        fAmbientOcclusion *= 1.0 - max(0.0, (fDist - vSceneDist.x) * 0.2 / fDist );                                    
    }

    return fAmbientOcclusion;
}

vec3 GetObjectLighting(const in C_Ray ray, const in C_HitInfo intersection, const in C_Material material, const in vec3 vNormal, const in vec3 cReflection)
{
    vec3 cScene ;

    vec3 vSpecularReflection = vec3(0.0);
    vec3 vDiffuseReflection = vec3(0.0);

    float fAmbientOcclusion = GetAmbientOcclusion(ray, intersection, vNormal);
    vec3 vAmbientLight = GetAmbientLight(vNormal) * fAmbientOcclusion;

    vDiffuseReflection += vAmbientLight;

    vSpecularReflection += cReflection * fAmbientOcclusion;

    #ifdef ENABLE_POINT_LIGHT
    vec3 vLightPos = GetLightPos();
    vec3 vToLight = vLightPos - intersection.vPos;
    vec3 vLightDir = normalize(vToLight);
    float fLightDistance = length(vToLight);

    float fAttenuation = 1.0 / (fLightDistance * fLightDistance);

    float fShadowBias = 0.1;              
    float fShadowFactor = GetShadow( intersection.vPos + vLightDir * fShadowBias, vLightDir, fLightDistance - fShadowBias );
    vec3 vIncidentLight = GetLightCol() * fShadowFactor * fAttenuation;

    vDiffuseReflection += GetDiffuseIntensity( vLightDir, vNormal ) * vIncidentLight;                                                                                  
    vSpecularReflection += GetBlinnPhongIntensity( ray, material, vLightDir, vNormal ) * vIncidentLight;
    #endif ENABLE_POINT_LIGHT

    vDiffuseReflection *= material.cAlbedo;

    // emmissive glow from screen
    if(intersection.vObjectId.x > 4.5)
    {
        vec2 vScreenPos = intersection.vObjectId.zy * vec2(0.25, -0.3) + vec2(0.46, 0.5);

        vec2 vMul = step(vScreenPos, vec2(1.0)) * step(vec2(0.0), vScreenPos);
        float fMul = vMul.x * vMul.y * 0.8;
        vDiffuseReflection += texture2D(backbuffer, vScreenPos).xyz * fMul;
    }

    #ifdef ENABLE_SPECULAR
    float fFresnel = Schlick(vNormal, ray.vDir, material.fR0, material.fSmoothness * 0.9 + 0.1);
    cScene = mix(vDiffuseReflection , vSpecularReflection, fFresnel);
    #else
    cScene = vDiffuseReflection;
    #endif

    return cScene;
}

vec3 GetSceneColourSimple( const in C_Ray ray )
{
    C_HitInfo intersection;
    Raymarch(ray, intersection, 16.0, 32);

    vec3 cScene;

    if(intersection.vObjectId.x < 0.5)
    {
        cScene = GetSkyGradient(ray.vDir);
    }
    else
    {
        C_Material material = GetObjectMaterial(intersection.vObjectId, intersection.vPos);
        vec3 vNormal = GetSceneNormal(intersection.vPos);

        // use sky gradient instead of reflection
        vec3 cReflection = GetSkyGradient(reflect(ray.vDir, vNormal));

        // apply lighting
        cScene = GetObjectLighting(ray, intersection, material, vNormal, cReflection );
    }

    ApplyAtmosphere(cScene, ray, intersection);

    return cScene;
}

vec3 GetSceneColour( const in C_Ray ray )
{                                                           
    C_HitInfo intersection;
    Raymarch(ray, intersection, 30.0, 256);

    vec3 cScene;

    if(intersection.vObjectId.x < 0.5)
    {
        cScene = GetSkyGradient(ray.vDir);
    }
    else
    {
        C_Material material = GetObjectMaterial(intersection.vObjectId, intersection.vPos);
        vec3 vNormal = GetSceneNormal(intersection.vPos);

        #ifdef ENABLE_MONTE_CARLO
        vNormal = normalize(vNormal + gRandomNormal / (5.0 + material.fSmoothness * 200.0));
        #endif

        vec3 cReflection;
        #ifdef ENABLE_REFLECTIONS   
        {
            // get colour from reflected ray
            float fSepration = 0.05;
            C_Ray reflectRay;
            reflectRay.vDir = reflect(ray.vDir, vNormal);
            reflectRay.vOrigin = intersection.vPos + reflectRay.vDir * fSepration;

            cReflection = GetSceneColourSimple(reflectRay);                                                                          
        }
        #else
        cReflection = GetSkyGradient(reflect(ray.vDir, vNormal));                               
        #endif
        // apply lighting
        cScene = GetObjectLighting(ray, intersection, material, vNormal, cReflection );
    }

    ApplyAtmosphere(cScene, ray, intersection);

    return cScene;
}

void GetCameraRay( const in vec3 vPos, const in vec3 vForwards, const in vec3 vWorldUp, out C_Ray ray)
{
    vec2 vPixelCoord = gl_FragCoord.xy;
    #ifdef ENABLE_MONTE_CARLO
    vPixelCoord += gPixelRandom.zw;
    #endif
    vec2 vUV = ( vPixelCoord / resolution.xy );
    vec2 vViewCoord = vUV * 2.0 - 1.0;

    vViewCoord *= 0.75;

    float fRatio = resolution.x / resolution.y;

    vViewCoord.y /= fRatio;                            

    ray.vOrigin = vPos;

    vec3 vRight = normalize(cross(vForwards, vWorldUp));
    vec3 vUp = cross(vRight, vForwards);

    ray.vDir = normalize( vRight * vViewCoord.x + vUp * vViewCoord.y + vForwards);         
}

void GetCameraRayLookat( const in vec3 vPos, const in vec3 vInterest, out C_Ray ray)
{
    vec3 vForwards = normalize(vInterest - vPos);
    vec3 vUp = vec3(0.0, 1.0, 0.0);

    GetCameraRay(vPos, vForwards, vUp, ray);
}

vec3 OrbitPoint( const in float fHeading, const in float fElevation )
{
    return vec3(sin(fHeading) * cos(fElevation), sin(fElevation), cos(fHeading) * cos(fElevation));
}

vec3 Tonemap( const in vec3 cCol )
{
    // simple Reinhard tonemapping operator      
    return cCol / (1.0 + cCol);
}

void main( void )
{
    #ifdef ENABLE_MONTE_CARLO              
    CalcPixelRandom();
    #endif

    C_Ray ray;

    vec3 vCameraPos = OrbitPoint(-mouse.x * 7.0, mouse.y * PI * 0.5) * 7.0 - vec3(0.0, 0.9, 0.0);
    #ifdef ENABLE_MONTE_CARLO              
    float fDepthOfField = 0.1;
    vCameraPos += gRandomNormal * 0.05;
    #endif

    GetCameraRayLookat( vCameraPos, vec3(0.0, 0.0, 0.0), ray);
    //GetCameraRayLookat(vec3(0.0, 0.0, -5.0), vec3(0.0, 0.0, 0.0), ray);

    vec3 cScene = GetSceneColour( ray );    

    float fExposure = 2.5;
    cScene = cScene * fExposure;
    vec3 cCurr = Tonemap(cScene );

    #ifdef ENABLE_MONTE_CARLO                              
    vec3 cPrev = texture2D(backbuffer, gl_FragCoord.xy / resolution).xyz;
    // would be nice to combine before tonemap but undoing a gamma=2.0 will do instead
    cPrev = cPrev * cPrev;
    // add noise to pixel value (helps values converge)
    cPrev += (gPixelRandom.xyz - 0.5) * (1.0 / 255.0);
    cCurr = cCurr * cCurr;
    // converge speed
    vec3 cFinal = mix(cPrev, cCurr, 8.0/255.0);
    // re-apply gamma 2.0
    cFinal = sqrt(cFinal);
    #else
    vec3 cFinal = cCurr;
    #endif

    float fAlpha = 1.0;
    #ifdef ENABLE_PERLIN_NOISE
    fAlpha = CalculateNoiseTexture();          
    #endif

    gl_FragColor = vec4( cFinal, fAlpha );

    //gl_FragColor = vec4(CalculateNoiseTexture()); // output noise texture
}

2014-09-14_21-50-58

http://glsl.heroku.com/e#6254.0

// 704 Remake - @PauloFalcao
// based on Blank Slate - @P_Malin (http://glsl.heroku.com/e#2540.09)
//
// Tonight was very tired from work, and decided to do some graphics fun to clear the mind! :)
// I opened the http://glsl.heroku.com/ and saw a copy of @P_Malin framework
// I Remembered my old 704 (http://www.backtothepixel.com/demos/js/webgl/704_webgl.html)
// and thought... how it would look like if i used the 704 object...
// I loved the results!!! @P_Malin framework is awesome!!!
// Very complete, with nice variable names, really nice! :)
// Colors, and the object are the same as the original 704,
// but time is slower to give to the stuff time to cook... ;)
//

#ifdef GL_ES
precision highp float;
#endif

uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;
uniform sampler2D backbuffer;

// somehow these enable pan/zoom controls (using magic)
uniform vec2 surfaceSize;
varying vec2 surfacePosition;

float kPI = acos(0.0);
float kHalfPi = asin(1.0);
float kTwoPI = kPI * 2.0;

// Removed so that it works again
//float cos(float v) // workaround for AMD Radeon on OS X
//{ 
//  return sin(v+kHalfPi);
//}

#define ENABLE_MONTE_CARLO
#define ENABLE_REFLECTIONS
#define ENABLE_FOG
#define ENABLE_SPECULAR
#define ENABLE_POINT_LIGHT
//#define ENABLE_POINT_LIGHT_FLARE

#ifdef ENABLE_MONTE_CARLO
vec4 gPixelRandom;
vec3 gRandomNormal;

void CalcPixelRandom()
{
    // Nothing special here, just numbers generated by bashing keyboard
    vec4 s1 = sin(time * 3.3422 + gl_FragCoord.xxxx * vec4(324.324234, 563.324234, 657.324234, 764.324234)) * 543.3423;
    vec4 s2 = sin(time * 1.3422 + gl_FragCoord.yyyy * vec4(567.324234, 435.324234, 432.324234, 657.324234)) * 654.5423;
    gPixelRandom = fract(2142.4 + s1 + s2);
    gRandomNormal = normalize( gPixelRandom.xyz - 0.5);
}
#endif

struct C_Ray
{
    vec3 vOrigin;
    vec3 vDir;
};

struct C_HitInfo
{
    vec3 vPos;
    float fDistance;
    vec3 vObjectId;
};

struct C_Material
{
    vec3 cAlbedo;
    float fR0;
    float fSmoothness;
    vec2 vParam;
};

vec3 RotateX( const in vec3 vPos, const in float fAngle )
{
    float s = sin(fAngle);
    float c = cos(fAngle);

    vec3 vResult = vec3( vPos.x, c * vPos.y + s * vPos.z, -s * vPos.y + c * vPos.z);

    return vResult;
}

vec3 RotateY( const in vec3 vPos, const in float fAngle )
{
    float s = sin(fAngle);
    float c = cos(fAngle);

    vec3 vResult = vec3( c * vPos.x + s * vPos.z, vPos.y, -s * vPos.x + c * vPos.z);

    return vResult;
}

vec3 RotateZ( const in vec3 vPos, const in float fAngle )
{
    float s = sin(fAngle);
    float c = cos(fAngle);

    vec3 vResult = vec3( c * vPos.x + s * vPos.y, -s * vPos.x + c * vPos.y, vPos.z);

    return vResult;
}

vec4 DistCombineUnion( const in vec4 v1, const in vec4 v2 )
{
    //if(v1.x < v2.x) return v1; else return v2;
    return mix(v1, v2, step(v2.x, v1.x));
}

vec4 DistCombineIntersect( const in vec4 v1, const in vec4 v2 )
{
    return mix(v2, v1, step(v2.x,v1.x));
}

vec4 DistCombineSubtract( const in vec4 v1, const in vec4 v2 )
{
    return DistCombineIntersect(v1, vec4(-v2.x, v2.yzw));
}

vec3 DomainRepeatXZGetTile( const in vec3 vPos, const in vec2 vRepeat, out vec2 vTile )
{
    vec3 vResult = vPos;
    vec2 vTilePos = (vPos.xz / vRepeat) + 0.5;
    vTile = floor(vTilePos + 1000.0);
    vResult.xz = (fract(vTilePos) - 0.5) * vRepeat;
    return vResult;
}

vec3 DomainRepeatXZ( const in vec3 vPos, const in vec2 vRepeat )
{
    vec3 vResult = vPos;
    vec2 vTilePos = (vPos.xz / vRepeat) + 0.5;
    vResult.xz = (fract(vTilePos) - 0.5) * vRepeat;
    return vResult;
}

vec3 DomainRepeatY( const in vec3 vPos, const in float fSize )
{
    vec3 vResult = vPos;
    vResult.y = (fract(vPos.y / fSize + 0.5) - 0.5) * fSize;
    return vResult;
}

vec3 DomainRotateSymmetry( const in vec3 vPos, const in float fSteps )
{
    float angle = atan( vPos.x, vPos.z );

    float fScale = fSteps / (kTwoPI);
    float steppedAngle = (floor(angle * fScale + 0.5)) / fScale;

    float s = sin(-steppedAngle);
    float c = cos(-steppedAngle);

    vec3 vResult = vec3( c * vPos.x + s * vPos.z,
                 vPos.y,
                -s * vPos.x + c * vPos.z);

    return vResult;
}

float GetDistanceXYTorus( const in vec3 p, const in float r1, const in float r2 )
{
    vec2 q = vec2(length(p.xy)-r1,p.z);
    return length(q)-r2;
}
float GetDistanceYZTorus( const in vec3 p, const in float r1, const in float r2 )
{
    vec2 q = vec2(length(p.yz)-r1,p.x);
    return length(q)-r2;
}
float GetDistanceCylinderY(const in vec3 vPos, const in float r)
{
    return length(vPos.xz) - r;
}
float GetDistanceBox( const in vec3 vPos, const in vec3 vSize )
{
    vec3 vDist = (abs(vPos) - vSize);
    return max(vDist.x, max(vDist.y, vDist.z));
}

float GetDistanceRoundedBox( const in vec3 vPos, const in vec3 vSize, float fRadius )
{
    vec3 vClosest = max(min(vPos, vSize), -vSize);
    return length(vClosest - vPos) - fRadius;
}

// result is x=scene distance y=material or object id; zw are material specific parameters (maybe uv co-ordinates)
vec4 GetDistanceScene( const in vec3 vPos )
{          
    vec4 vResult = vec4(10000.0, -1.0, 0.0, 0.0);

    float oP=length(vPos);
    vec3 vSphereDomain=vPos;
    float tt=time*0.05+10.0;
    vSphereDomain.x=sin(vSphereDomain.x)+sin(tt);
    vSphereDomain.z=sin(vSphereDomain.z)+cos(tt);

    vec4 vDistSphere = vec4( length(length(vSphereDomain))-1.5-sin(oP-tt*4.0), 2.0, vSphereDomain.xy);

    vResult = DistCombineUnion(vResult, vDistSphere);

    vec4 vDistFloor = vec4(vPos.y + 1.0, 1.0, vPos.xz);
    vResult = DistCombineUnion(vResult, vDistFloor);

    return vResult;
}

C_Material GetObjectMaterial( const in vec3 vObjId, const in vec3 vPos )
{
    C_Material mat;

    if(vObjId.x < 1.5)
    {
        // floor
        mat.fR0 = 0.01;
        mat.fSmoothness = 0.0;
        if (fract(vPos.x*.5)>.5)
            if (fract(vPos.z*.5)>.5)
                mat.cAlbedo=vec3(0,0,0);
            else
                mat.cAlbedo=vec3(1,1,1);
            else
            if (fract(vPos.z*.5)>.5)
                mat.cAlbedo = vec3(1,1,1);
            else
                mat.cAlbedo = vec3(0,0,0);
    }
    else
    if(vObjId.x < 2.5)
    {
        // sphere
        mat.fR0 = 0.5;
        mat.fSmoothness = 0.9;
        float tt=time*0.05+10.0;
        float d=length(vPos);
        mat.cAlbedo = vec3((sin(d*.25-tt*4.0)+1.0)/2.0,(sin(tt)+1.0)/2.0,(sin(d-tt*4.0)+1.0)/2.0);
    }

    return mat;
}
vec3 GetSkyGradient( const in vec3 vDir )
{
    float fBlend = vDir.y * 0.5 + 0.5;
    return mix(vec3(0.0, 0.0, 0.0), vec3(0.5, 0.6, 0.7), fBlend);
}
vec3 GetLightPos()
{
    vec3 vLightPos = vec3(2.0, 9.0, 2.0);
    #ifdef ENABLE_MONTE_CARLO        
    vLightPos += gRandomNormal * 0.2;
    #endif
    return vLightPos;
}
vec3 GetLightCol()
{
    return vec3(32.0, 6.0, 1.0) * 10.0;
}

vec3 GetAmbientLight(const in vec3 vNormal)
{
    return GetSkyGradient(vNormal);
}

#define kFogDensity 0.035
void ApplyAtmosphere(inout vec3 col, const in C_Ray ray, const in C_HitInfo intersection)
{
    #ifdef ENABLE_FOG
    // fog
    float fFogAmount = exp(intersection.fDistance * -kFogDensity);
    vec3 cFog = GetSkyGradient(ray.vDir);
    col = mix(cFog, col, fFogAmount);
    #endif

    // glare from light (a bit hacky - use length of closest approach from ray to light)
    #ifdef ENABLE_POINT_LIGHT_FLARE
    vec3 vToLight = GetLightPos() - ray.vOrigin;
    float fDot = dot(vToLight, ray.vDir);
    fDot = clamp(fDot, 0.0, intersection.fDistance);

    vec3 vClosestPoint = ray.vOrigin + ray.vDir * fDot;
    float fDist = length(vClosestPoint - GetLightPos());
    col += GetLightCol() * 0.01/ (fDist * fDist);
    #endif     
}
vec3 GetSceneNormal( const in vec3 vPos )
{
    // tetrahedron normal
    float fDelta = 0.025;

    vec3 vOffset1 = vec3( fDelta, -fDelta, -fDelta);
    vec3 vOffset2 = vec3(-fDelta, -fDelta,  fDelta);
    vec3 vOffset3 = vec3(-fDelta,  fDelta, -fDelta);
    vec3 vOffset4 = vec3( fDelta,  fDelta,  fDelta);

    float f1 = GetDistanceScene( vPos + vOffset1 ).x;
    float f2 = GetDistanceScene( vPos + vOffset2 ).x;
    float f3 = GetDistanceScene( vPos + vOffset3 ).x;
    float f4 = GetDistanceScene( vPos + vOffset4 ).x;

    vec3 vNormal = vOffset1 * f1 + vOffset2 * f2 + vOffset3 * f3 + vOffset4 * f4;

    return normalize( vNormal );
}

#define kRaymarchEpsilon 0.01
#define kRaymarchMatIter 256
#define kRaymarchStartDistance 0.1
// This is an excellent resource on ray marching -> http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm
void Raymarch( const in C_Ray ray, out C_HitInfo result, const float fMaxDist, const int maxIter )
{         
    result.fDistance = kRaymarchStartDistance;
    result.vObjectId.x = 0.0;

    for(int i=0;i<=kRaymarchMatIter;i++)               
    {
        result.vPos = ray.vOrigin + ray.vDir * result.fDistance;
        vec4 vSceneDist = GetDistanceScene( result.vPos );
        result.vObjectId = vSceneDist.yzw;

        // abs allows backward stepping - should only be necessary for non uniform distance functions
        if((abs(vSceneDist.x) <= kRaymarchEpsilon) || (result.fDistance >= fMaxDist) || (i > maxIter))
        {
            break;
        }                         

        result.fDistance = result.fDistance + vSceneDist.x;     
    }

    if(result.fDistance >= fMaxDist)
    {
        result.vPos = ray.vOrigin + ray.vDir * result.fDistance;
        result.vObjectId.x = 0.0;
        result.fDistance = 1000.0;
    }
}

float GetShadow( const in vec3 vPos, const in vec3 vLightDir, const in float fLightDistance )
{
    C_Ray shadowRay;
    shadowRay.vDir = vLightDir;
    shadowRay.vOrigin = vPos;

    C_HitInfo shadowIntersect;
    Raymarch(shadowRay, shadowIntersect, fLightDistance, 32);

    return step(0.0, shadowIntersect.fDistance) * step(fLightDistance, shadowIntersect.fDistance );          
}

// http://en.wikipedia.org/wiki/Schlick's_approximation
float Schlick( const in vec3 vNormal, const in vec3 vView, const in float fR0, const in float fSmoothFactor)
{
    float fDot = dot(vNormal, -vView);
    fDot = min(max((1.0 - fDot), 0.0), 1.0);
    float fDot2 = fDot * fDot;
    float fDot5 = fDot2 * fDot2 * fDot;
    return fR0 + (1.0 - fR0) * fDot5 * fSmoothFactor;
}

float GetDiffuseIntensity(const in vec3 vLightDir, const in vec3 vNormal)
{
    return max(0.0, dot(vLightDir, vNormal));
}

float GetBlinnPhongIntensity(const in C_Ray ray, const in C_Material mat, const in vec3 vLightDir, const in vec3 vNormal)
{           
    vec3 vHalf = normalize(vLightDir - ray.vDir);
    float fNdotH = max(0.0, dot(vHalf, vNormal));

    float fSpecPower = exp2(4.0 + 6.0 * mat.fSmoothness);
    float fSpecIntensity = (fSpecPower + 2.0) * 0.125;

    return pow(fNdotH, fSpecPower) * fSpecIntensity;
}

// use distance field to evaluate ambient occlusion
float GetAmbientOcclusion(const in C_Ray ray, const in C_HitInfo intersection, const in vec3 vNormal)
{
    vec3 vPos = intersection.vPos;

    float fAmbientOcclusion = 1.0;

    float fDist = 0.0;
    for(int i=0; i<=5; i++)
    {
        fDist += 0.1;

        vec4 vSceneDist = GetDistanceScene(vPos + vNormal * fDist);

        fAmbientOcclusion *= 1.0 - max(0.0, (fDist - vSceneDist.x) * 0.2 / fDist );                                   
    }

    return fAmbientOcclusion;
}

vec3 GetObjectLighting(const in C_Ray ray, const in C_HitInfo intersection, const in C_Material material, const in vec3 vNormal, const in vec3 cReflection)
{
    vec3 cScene ;

    vec3 vSpecularReflection = vec3(0.0);
    vec3 vDiffuseReflection = vec3(0.0);

    float fAmbientOcclusion = GetAmbientOcclusion(ray, intersection, vNormal);
    vec3 vAmbientLight = GetAmbientLight(vNormal) * fAmbientOcclusion;

    vDiffuseReflection += vAmbientLight;

    vSpecularReflection += cReflection * fAmbientOcclusion;

    #ifdef ENABLE_POINT_LIGHT
    vec3 vLightPos = GetLightPos();
    vec3 vToLight = vLightPos - intersection.vPos;
    vec3 vLightDir = normalize(vToLight);
    float fLightDistance = length(vToLight);

    float fAttenuation = 1.0 / (fLightDistance * fLightDistance);

    float fShadowBias = 0.1;             
    float fShadowFactor = GetShadow( intersection.vPos + vLightDir * fShadowBias, vLightDir, fLightDistance - fShadowBias );
    vec3 vIncidentLight = GetLightCol() * fShadowFactor * fAttenuation;

    vDiffuseReflection += GetDiffuseIntensity( vLightDir, vNormal ) * vIncidentLight;                                                                                 
    vSpecularReflection += GetBlinnPhongIntensity( ray, material, vLightDir, vNormal ) * vIncidentLight;
    #endif ENABLE_POINT_LIGHT

    vDiffuseReflection *= material.cAlbedo;              

    #ifdef ENABLE_SPECULAR
    float fFresnel = Schlick(vNormal, ray.vDir, material.fR0, material.fSmoothness * 0.9 + 0.1);
    cScene = mix(vDiffuseReflection , vSpecularReflection, fFresnel);
    #else
    cScene = vDiffuseReflection;
    #endif

    return cScene;
}

vec3 GetSceneColourSimple( const in C_Ray ray )
{
    C_HitInfo intersection;
    Raymarch(ray, intersection, 16.0, 32);

    vec3 cScene;

    if(intersection.vObjectId.x < 0.5)
    {
        cScene = GetSkyGradient(ray.vDir);
    }
    else
    {
        C_Material material = GetObjectMaterial(intersection.vObjectId, intersection.vPos);
        vec3 vNormal = GetSceneNormal(intersection.vPos);

        // use sky gradient instead of reflection
        vec3 cReflection = GetSkyGradient(reflect(ray.vDir, vNormal));

        // apply lighting
        cScene = GetObjectLighting(ray, intersection, material, vNormal, cReflection );
    }

    ApplyAtmosphere(cScene, ray, intersection);

    return cScene;
}

vec3 GetSceneColour( const in C_Ray ray )
{                                                           
    C_HitInfo intersection;
    Raymarch(ray, intersection, 60.0, 256);

    vec3 cScene;

    if(intersection.vObjectId.x < 0.5)
    {
        cScene = GetSkyGradient(ray.vDir);
    }
    else
    {
        C_Material material = GetObjectMaterial(intersection.vObjectId, intersection.vPos);
        vec3 vNormal = GetSceneNormal(intersection.vPos);

        #ifdef ENABLE_MONTE_CARLO
        vNormal = normalize(vNormal + gRandomNormal / (5.0 + material.fSmoothness * 200.0));
        #endif

        vec3 cReflection;
        #ifdef ENABLE_REFLECTIONS    
        {
            // get colour from reflected ray
            float fSepration = 0.05;
            C_Ray reflectRay;
            reflectRay.vDir = reflect(ray.vDir, vNormal);
            reflectRay.vOrigin = intersection.vPos + reflectRay.vDir * fSepration;

            cReflection = GetSceneColourSimple(reflectRay);                                                                         
        }
        #else
        cReflection = GetSkyGradient(reflect(ray.vDir, vNormal));                               
        #endif
        // apply lighting
        cScene = GetObjectLighting(ray, intersection, material, vNormal, cReflection );
    }

    ApplyAtmosphere(cScene, ray, intersection);

    return cScene;
}

void GetCameraRay( const in vec3 vPos, const in vec3 vForwards, const in vec3 vWorldUp, out C_Ray ray)
{
    vec2 vPixelCoord = gl_FragCoord.xy;
    #ifdef ENABLE_MONTE_CARLO
    vPixelCoord += gPixelRandom.zw;
    #endif
    vec2 vUV = ( vPixelCoord / resolution.xy );
    vec2 vViewCoord = vUV * 2.0 - 1.0;

    vViewCoord *= 0.75;

    float fRatio = resolution.x / resolution.y;

    vViewCoord.y /= fRatio;                           

    ray.vOrigin = vPos;

    vec3 vRight = normalize(cross(vForwards, vWorldUp));
    vec3 vUp = cross(vRight, vForwards);

    ray.vDir = normalize( vRight * vViewCoord.x + vUp * vViewCoord.y + vForwards);        
}

void GetCameraRayLookat( const in vec3 vPos, const in vec3 vInterest, out C_Ray ray)
{
    vec3 vForwards = normalize(vInterest - vPos);
    vec3 vUp = vec3(0.0, 1.0, 0.0);

    GetCameraRay(vPos, vForwards, vUp, ray);
}

vec3 OrbitPoint( const in float fHeading, const in float fElevation )
{
    return vec3(sin(fHeading) * cos(fElevation), sin(fElevation), cos(fHeading) * cos(fElevation));
}

vec3 Gamma( const in vec3 cCol )
{
    return cCol * cCol;
}

vec3 InvGamma( const in vec3 cCol )
{
    return sqrt(cCol);
}

vec3 Tonemap( const in vec3 cCol )
{
    // simple Reinhard tonemapping operator     
    vec3 vResult = cCol / (1.0 + cCol);

    return Gamma(vResult);
}

vec3 InvTonemap( const in vec3 cCol )
{
    vec3 vResult = cCol;
    vResult = clamp(vResult, 0.01, 0.99);
    vResult = InvGamma(vResult);
    return - (vResult / (vResult - 1.0));
}

void main( void )
{
    #ifdef ENABLE_MONTE_CARLO             
    CalcPixelRandom();
    #endif

    C_Ray ray;

    const float fCamreaInitialHeading = 0.5;
    const float fCamreaInitialElevation = 0.5;
    const float fCamreaInitialDist = 20.0;
    const float fCameraHeight = 0.01;
    const float fOrbitSpeed = 1.0;

    // This magic stolen from other 3d pan/zoom examples
    float fZoom = surfaceSize.y * 0.5 + 0.4;

    vec2 vCenterPosition = (0.5 - ( gl_FragCoord.xy / resolution )) * surfaceSize + surfacePosition;
    float fHeading = vCenterPosition.x * fOrbitSpeed + fCamreaInitialHeading;
    float fElevation = (vCenterPosition.y * fOrbitSpeed + fCamreaInitialElevation);

    vec3 vCameraPos = OrbitPoint(fHeading, fElevation) * fCamreaInitialDist * fZoom;

    vCameraPos += vec3(0.0, -fCameraHeight, 0.0);
    #ifdef ENABLE_MONTE_CARLO             
    float fDepthOfField = 0.1;
    vCameraPos += gRandomNormal * fDepthOfField;
    #endif

    GetCameraRayLookat( vCameraPos, vec3(0.0, 0.0, 0.0), ray);
    //GetCameraRayLookat(vec3(0.0, 0.0, -5.0), vec3(0.0, 0.0, 0.0), ray);

    vec3 cScene = GetSceneColour( ray );   

    float fExposure = 3.5;
    cScene = cScene * fExposure;

    #ifdef ENABLE_MONTE_CARLO                              
    vec3 cPrev = texture2D(backbuffer, gl_FragCoord.xy / resolution).xyz;
    // add noise to pixel value (helps values converge)
    cPrev += (gPixelRandom.xyz - 0.5) * (1.0 / 255.0);
    cPrev = InvTonemap(cPrev);
    // converge speep
    float fBlend = 0.1;
    vec3 cFinal = mix(cPrev, cScene, fBlend);
    #else
    vec3 cFinal = cScene;
    #endif

    cFinal = Tonemap(cFinal);

    float fAlpha = 1.0;

    gl_FragColor = vec4( cFinal, fAlpha );
}
Tags : #shader