Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Shader Old Film Effect

edited March 2013 in Code Sharing Posts: 454

I can't claim this is mine I got it from a great WebGL tutorial: http://devmaster.net/posts/2989/shader-effects-old-film

Anyway, tweaked into Codea.

One odd thing, I know we have fixed the orientation of textures issue in 1.5... but not for the camera.

--# Main
-- Use this function to perform your initial setup
displayMode(STANDARD)

function setup()

    allTextures = {
                    CAMERA,
                    "Cargo Bot:Codea Icon",
                    "Small World:Store Extra Large",
                    "Small World:Windmill",
                    "Tyrian Remastered:Boss D",
                  }

    cameraSource(CAMERA_FRONT)

    m = mesh()
    m.texture = allTextures[Texture]
    m.shader = shader(FilmShader.FilmVertex, FilmShader.FilmFragment)

    rIdx = m:addRect(0, 0, 0, 0)
    --m:setRectColor(i, 255,0,0)
    parameter.number("FrameRate",1,24,10)
    parameter.integer("Texture",1,5,2, changeTexture)
    parameter.number("SepiaValue",0,1,0.6, changeParameters)
    parameter.number("NoiseValue",0,1,0.1, changeParameters)
    parameter.number("ScratchValue",0,1,0.5, changeParameters)
    parameter.number("InnerVignetting",0,1,0.8, changeParameters)
    parameter.number("OuterVignetting",0,1,0.9, changeParameters)
    timeSinceLastDraw = 0
end

function changeTexture() 
    --this is passing a uniform to fix camera inversion
    if Texture == 1 then
        m.shader.isCamera = true
    else
        m.shader.isCamera = false
    end 
end

function changeParameters() 
    --if because of startup sequencing problem with parameters
    if OuterVignetting ~= nil then
        m.shader.SepiaValue = SepiaValue
        m.shader.NoiseValue = NoiseValue
        m.shader.ScratchValue = ScratchValue
        m.shader.InnerVignetting = InnerVignetting
        m.shader.OuterVignetting = OuterVignetting
    end
end

-- This function gets called once every frame
function draw()
    timeSinceLastDraw = timeSinceLastDraw + DeltaTime
    -- This sets a dark background color
    background(40, 40, 50)

    -- This sets the line thickness
    strokeWidth(5)

    -- Here we set up the rect texture and size
    m.texture = allTextures[Texture]
    local cw,ch = spriteSize(allTextures[Texture])
    m:setRect(rIdx, WIDTH/2, HEIGHT/2, cw, ch)

    --even though redraw is as fast as possible, the noise etc will only shift when we do this update...
    if timeSinceLastDraw > 1/FrameRate then
        m.shader.RandomValue = math.random()
        timeSinceLastDraw = 0
        if math.random() > 0.9 then
            m.shader.TimeLapse = math.random()
        end
    end
    -- Draw the mesh
    m:draw()
end

FilmShader = {
FilmVertex = [[
//
// A basic vertex shader
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;
uniform bool isCamera;

//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

//This is an output variable that will be passed to the fragment shader
varying highp vec2 vTexCoord;

void main()
{
    //Pass the mesh color to the fragment shader
    vTexCoord = texCoord;
    if (isCamera) {
        vTexCoord.y = 1.0 - vTexCoord.y;
    }
    //Multiply the vertex position by our combined transform
    gl_Position = modelViewProjection * position;
}
]],
FilmFragment = [[
precision highp float;

/// Uniform variables. 
uniform vec4 Colour; 
uniform sampler2D texture; 
uniform float SepiaValue; 
uniform float NoiseValue; 
uniform float ScratchValue; 
uniform float InnerVignetting; 
uniform float OuterVignetting; 
uniform float RandomValue; 
uniform float TimeLapse; 

/// Varying variables. 
varying vec2 vTexCoord; 

/// Computes the overlay between the source and destination colours. 
vec3 Overlay (vec3 src, vec3 dst) { 
    // if (dst <= Ω) then: 2 * src * dst 
    // if (dst > Ω) then: 1 - 2 * (1 - dst) * (1 - src) 
    return vec3((dst.x <= 0.5) ? (2.0 * src.x * dst.x) : (1.0 - 2.0 * (1.0 - dst.x) * (1.0 - src.x)), (dst.y <= 0.5) ? (2.0 * src.y * dst.y) : (1.0 - 2.0 * (1.0 - dst.y) * (1.0 - src.y)), (dst.z <= 0.5) ? (2.0 * src.z * dst.z) : (1.0 - 2.0 * (1.0 - dst.z) * (1.0 - src.z))); 
    } 

/// 2D Noise by Ian McEwan, Ashima Arts. 
vec3 mod289(vec3 x) { 
    return x - floor(x * (1.0 / 289.0)) * 289.0; 
} 

vec2 mod289(vec2 x) { 
    return x - floor(x * (1.0 / 289.0)) * 289.0; 
} 

vec3 permute(vec3 x) { 
    return mod289(((x*34.0)+1.0)*x); 
} 

float snoise (vec2 v) { 
    const vec4 C = vec4(
        0.211324865405187, // (3.0-sqrt(3.0))/6.0 
        0.366025403784439, // 0.5*(sqrt(3.0)-1.0) 
        -0.577350269189626, // -1.0 + 2.0 * C.x 
        0.024390243902439); // 1.0 / 41.0 
        // First corner 
    vec2 i = floor(v + dot(v, C.yy) ); 
    vec2 x0 = v - i + dot(i, C.xx); 
    // Other corners 
    vec2 i1; 
    i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); 
    vec4 x12 = x0.xyxy + C.xxzz; x12.xy -= i1; 
    // Permutations 
    i = mod289(i); 
    // Avoid truncation effects in permutation 
    vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 )) + i.x + vec3(0.0, i1.x, 1.0 )); 
    vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0); 
    m = m*m ; 
    m = m*m ; 
    // Gradients: 41 points uniformly over a line, mapped onto a diamond. 
    // The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287) 
    vec3 x = 2.0 * fract(p * C.www) - 1.0; 
    vec3 h = abs(x) - 0.5; 
    vec3 ox = floor(x + 0.5); 
    vec3 a0 = x - ox; 
    // Normalise gradients implicitly by scaling m 
    // Approximation of: m *= inversesqrt( a0*a0 + h*h ); 
    m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h ); 
    // Compute final noise value at P 
    vec3 g; 
    g.x = a0.x * x0.x + h.x * x0.y; 
    g.yz = a0.yz * x12.xz + h.yz * x12.yw; 
    return 130.0 * dot(m, g); 
} 

/// Fragment shader entry. 

void main () { 
    // Sepia RGB value 
    vec3 sepia = vec3(112.0 / 255.0, 66.0 / 255.0, 20.0 / 255.0); 

    // Step 1: Convert to grayscale 
    vec3 colour = texture2D(texture, vTexCoord).xyz; 
    float gray = (colour.x + colour.y + colour.z) / 3.0; 
    vec3 grayscale = vec3(gray);

    // Step 2: Appy sepia overlay 
    vec3 finalColour = Overlay(sepia, grayscale); 

    // Step 3: Lerp final sepia colour 
    finalColour = grayscale + SepiaValue * (finalColour - grayscale); 

    // Step 4: Add noise 
    float noise = snoise(vTexCoord * vec2(1024.0 + RandomValue * 512.0, 1024.0 + RandomValue * 512.0)) * 0.5; 
    finalColour += noise * NoiseValue; 

    // Optionally add noise as an overlay, simulating ISO on the camera 
    //vec3 noiseOverlay = Overlay(finalColour, vec3(noise)); 
    //finalColour = finalColour + NoiseValue * (finalColour - noiseOverlay); 

    // Step 5: Apply scratches 
    if ( RandomValue < ScratchValue ) { 
        // Pick a random spot to show scratches 
        float dist = 1.0 / ScratchValue; 
        float d = distance(vTexCoord, vec2(RandomValue * dist, RandomValue * dist)); 
        if ( d < 0.4 ) { 
            // Generate the scratch 
            float xPeriod = 8.0; 
            float yPeriod = 1.0; 
            float pi = 3.141592; 
            float phase = TimeLapse; 
            float turbulence = snoise(vTexCoord * 2.5); 
            float vScratch = 0.5 + (sin(((vTexCoord.x * xPeriod + vTexCoord.y * yPeriod + turbulence)) * pi + phase) * 0.5); 
            vScratch = clamp((vScratch * 10000.0) + 0.35, 0.0, 1.0); 
            finalColour.xyz *= vScratch; 
        } 
    } 

    // Step 6: Apply vignetting 
    // Max distance from centre to corner is ~0.7. Scale that to 1.0. 
    float d = distance(vec2(0.5, 0.5), vTexCoord) * 1.414213; 
    float vignetting = clamp((OuterVignetting - d) / (OuterVignetting - InnerVignetting), 0.0, 1.0); 
    finalColour.xyz *= vignetting; 
    // Apply colour 
    gl_FragColor.xyz = finalColour; 
    gl_FragColor.w = 1.0; 
}
]]
}

Comments

  • edited March 2013 Posts: 454

    OK, so this was a first experiment with chaining shaders together, I took my texture lit version of @Xavier's 3d tile based game and then passed it from there to the vintage film shader...

    Works, although the performance is also vintage.

    One fault I got was that when rendering the initial stuff to an image (setContext(image)) rather than the back buffer, the Z order seems to get ignored/messed up. Look at the right edges of boxes in the left side of the screen... @Simeon / TLL if you are watching, any thoughts?

    Full code of the vintaged version here:
    https://gist.github.com/sp4cemonkey/5078136

  • IgnatzIgnatz Mod
    edited March 2013 Posts: 5,396

    I don't know how you did it, but I could see a hair on the picture, and it took me right back....

    Those were the days

  • Jmv38Jmv38 Mod
    Posts: 3,295

    .@spacemonkey i've read some comments of Andrew_Stacey reporting (in a beta thread) the same as you: with setcontext() the z-index does not work. To be corrected in a future version. Be patient.

  • SimeonSimeon Admin Mod
    Posts: 4,837

    .@spacemonkey @Jmv38 that's correct, currently setContext() targets don't have depth buffers. We are still adding this and it will come soon.

    Thanks for discovering the camera texture flip issue, will be corrected in the next version.

    Amazing demo.

Sign In or Register to comment.