Shaders Implementing a tilt-shift/DOF shader with mouse interaction.

jf_knight

Member
If you're familiar with photo editing smartphone apps, you may have seen the "Tilt-shift" edit option. I'd like to recreate that effect.
I'm primarily having problems with how the shader handles the mouse input; the mouse x and y move a non-blurred circle with the surrounding area is blurred.
I feel the problem is in the step event, or the "vec2 mouse" line on the shader code.
I'd appreciate any help or guidance.

obj_image: CREATE EVENT
Code:
mousex_coor_UNI =  shader_get_uniform(shd_edit_tilt_shift, "MouseX");
mousey_coor_UNI =  shader_get_uniform(shd_edit_tilt_shift, "MouseY");
obj_image: STEP EVENT
Code:
mousex_coor_POSITION =     1 - abs(((mouse_x / window_get_width()) - .5) * 2);
mousey_coor_POSITION = 1 - abs(((mouse_y / window_get_height()) - .5) * 2);

obj_image: DRAW EVENT
Code:
shader_set(shd_dof);
  shader_set_uniform_f(mousex_coor_UNI, (mousex_coor_POSITION))
  shader_set_uniform_f(mousey_coor_UNI, (mousey_coor_POSITION));
draw_self();
shader_reset();

Here is the shader code:

vertex:
Code:
attribute vec3 in_Position;
attribute vec4 in_Colour; 
attribute vec2 in_TextureCoord;

varying vec2 v_texcoord;
varying vec4 v_vColour;

void main()
{
    gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * vec4(in_Position, 1.0);
    v_texcoord = in_TextureCoord;
    v_vColour = in_Colour;
}
Fragment:

Code:
uniform vec2 resolution;
const float PI = 3.1415926535897932384626433832795;
const float DoublePI = 2.0 * PI;

const float CircleSize = 1.0;
const float SmoothPadding = 0.33;

const float Distance = 0.04;
const float DistanceSteps = 8.0;
const float RadialSteps = 8.0;
const float RadialOffset = 0.5;
const float KernelPower = 1.0;

uniform float MouseX;
uniform float MouseY;

vec4 tiltshift (vec4 colors)
{
    vec2 uv = v_texcoord; 
    
    float StepSize = Distance / DistanceSteps;
    vec3 BlurColor = vec3(0);       
    vec2 SubUV = uv;
    float CurDistance = 0.0;
    vec2 CurOffset = vec2(0);
    float SubOffset = 0.0;
    float KernelDistance = 0.0;
    float AccumulatedDistance = 0.0;

    for(int i = 0; i < int(DistanceSteps); i++)
    {
        CurDistance += StepSize;

        for (int j = 0; j < int(RadialSteps); j++)
        {
            SubOffset += 1.0;

            CurOffset.x = cos(DoublePI * (SubOffset / RadialSteps));
            CurOffset.y = sin(DoublePI * (SubOffset / RadialSteps));

            SubUV.x = uv.x + CurOffset.x * CurDistance;
            SubUV.y = uv.y + CurOffset.y * CurDistance;

            KernelDistance = pow(CurDistance, KernelPower);
            BlurColor += texture2D(gm_BaseTexture, SubUV).rgb * KernelDistance;      
            AccumulatedDistance += KernelDistance;
        }

        SubOffset += RadialOffset;
    }

    BlurColor /= AccumulatedDistance;
    vec2 mouse = vec2(mousex, mousey);
  
    //float AspectRatio = resolution.x / resolution.y;
    float AspectRatio = 1.0;
    vec2 VisionCenter = mouse.xy;

    vec2 v = uv - VisionCenter;
    v.x = v.x * AspectRatio;

    float CirleRadius = CircleSize / 2.0;
    float SmoothRadius = CirleRadius * SmoothPadding;
    float CircleMask = smoothstep(CirleRadius, CirleRadius - SmoothRadius, length(v));
    BlurColor = mix(BlurColor.rgb, colors.rgb, CircleMask);  
    
  
    colors = vec4(BlurColor, 1.0);  
    
    return colors;
}

void main( )
{
    vec2 uv = v_texcoord;
    vec4 colors = texture2D(gm_BaseTexture, uv);

gl_FragColor =  tiltshift(colors);
}
 

Tyg

Member
im going to plug it your code and see what it does...but before that i will say how i would do it...just suggesting
i think it could all be done within the shader

i would make a mask that way it could follow the v_vtexcoord

varying vec2 v_vMaskcoord;

float M_Color = v_vColour * texture2D( iFabric, v_vMaskcoord);

but there is another function instead

float M_Color = v_vColour * texture2DLod( iFabric, v_vMaskcoord, focus); // this can be useful for level of detail (LOD)

if your using one sampler
uniform sampler2d ichannel0;

you can now mix the color with the M_color at .5

Ill try your code now...hope that helps :)
 
Last edited:

Tyg

Member
Ok..there are lots of things wrong with that code (Don't worry, ill post the corrected code for you)

first you should add this at the top of the fragment shader

varying vec2 v_vTexcoord;
varying vec4 v_vColour;

you should leave the default vsh because you dont need to use it

next there a few spots you use v_Texcoord; and it should be v_vTexcoord; ///notice the second v
the mouse should be brought in one uniform is use set the uniform with shader_set_uniform_f_array
uniform vec2 iMouse;
i use i for all my inputs but thats just my convention now you can use iMouse.x or iMouse.y
you use mouse and Mouse interchangeably ....shaders are case specific mouse has to be mouse...and Mouse has to be Mouse
your uv needs to be corrected to center
your blur seems to be working fine
just need to center the circle

vec2 VisionCenter = vec2(0.5,0.5) // center the circle now we have to add the mouse coordinates;

Now this gives the vignette effect at this point i dont know what your trying to do with the mouse?
ok..now if its the idea to have the circle move around or the circle always in the center and picture move around?
if your trying to move the circle around you need to scale it down

ok..so to move the circle we have to add a value from -.5 to +.5
the mouse coordinates are big numbers so we have to scale the mouse coordinates down and clamp them
i see you are trying to do this in your objects step event, shaders are really fast i would do it in there
basically mouse.x/resolution.x and then clamp it

so..if your trying to just get a vignette effect, just center the circle
if you want a moving circle scale it down

if you post a picture of the effect you want i can help you more...have a good day :)
 
Last edited:

Tyg

Member
float CirleRadius = CircleSize / 5.0;
will scale the circle down

I did some research on tilt-shift and i think it focuses on closer objects ...correct?
this pseudo works but i think to make it better you need depth levels and use LOD

This works :)
This is the code i used

vec4 tiltshift (vec4 colors)
{
vec2 uv = v_vTexcoord;

float StepSize = Distance / DistanceSteps;
vec3 BlurColor = vec3(0);
vec2 SubUV = uv;
float CurDistance = 0.0;
vec2 CurOffset = vec2(0);
float SubOffset = 0.0;
float KernelDistance = 0.0;
float AccumulatedDistance = 0.0;

for(int i = 0; i < int(DistanceSteps); i++)
{
CurDistance += StepSize;

for (int j = 0; j < int(RadialSteps); j++)
{
SubOffset += 1.0;

CurOffset.x = cos(DoublePI * (SubOffset / RadialSteps));
CurOffset.y = sin(DoublePI * (SubOffset / RadialSteps));

SubUV.x = uv.x + CurOffset.x * CurDistance;
SubUV.y = uv.y + CurOffset.y * CurDistance;

KernelDistance = pow(CurDistance, KernelPower);
BlurColor += texture2D(gm_BaseTexture, SubUV).rgb * KernelDistance;
AccumulatedDistance += KernelDistance;
}

SubOffset += RadialOffset;
}

BlurColor /= AccumulatedDistance;

//float AspectRatio = resolution.x / resolution.y;
float AspectRatio = 1.0;

vec2 Mc = iMouse-iRez/2.; // using the room_width and room_height...Mouse is 0,0 center room
// can use display width or sprite width depending on how you use it
// Mc.x = iMouse.x-iRez.x/2.;
// Mc.y = iMouse.y-iRez.y/2.;

clamp(Mc,-.5,.5); // clamps to edges of room
// clamp(Mc.x,-.5,.5); // can use different clamp here
// clamp(Mc.x,-.5,.5);

vec2 VisionCenter = vec2(0.5+Mc.x/iRez.x,0.5+Mc.y/iRez.y);
// VisionCenter = vec2(0.5,0.5);
// VisionCenter.x += Mc.x/iRez.x;
// VisionCenter.y += Mc.y/iRez.y;

vec2 v = uv - VisionCenter;
v.x = v.x * AspectRatio;
float CirleRadius = CircleSize / 5.0;
float SmoothRadius = CirleRadius * SmoothPadding;
float CircleMask = smoothstep(CirleRadius, CirleRadius - SmoothRadius, length(v));
BlurColor = mix(BlurColor.rgb, colors.rgb, CircleMask);
colors = vec4(BlurColor, 1.0);
return colors;
}

void main( )
{
vec2 uv = v_vTexcoord;
vec4 colors = texture2D(gm_BaseTexture, uv);
gl_FragColor = tiltshift(colors);
}
 
Last edited:

Tyg

Member
I did some research on tilt-shift and i think it focuses on closer objects ...correct?
this pseudo works but i think to make it better you need depth levels and use LOD
going to watch some youtube on tilt-shift :)

Hold on i have a better one that i think has the effect your looking for, just need to code in the mouse stuffRed_Hat.jpg
much nicer on the right
 
Last edited:
D

Deleted member 13992

Guest
I did some research on tilt-shift and i think it focuses on closer objects ...correct?

In principal, far-away objects can be out-of-focus at the same time as the closest objects, with focus in the middle.

In photography, the larger the depth of the scenery you want to capture, the less background/foreground "blur" you can get. You can increase it with larger film formats and a larger lens aperture, but that has limits (not to mention being much more expensive if you want a sensor size larger than 35mm). Tilt-shift is the act of physically tilting the lens off the film (or sensor) plane, forcing a macro effect to scenery that would otherwise be too large to support such a narrow depth-of-field.

TLDR: Make large real-life scenes look like miniatures. Especially when viewed from high up (like in isometric games).
 
Last edited by a moderator:
  • Like
Reactions: Tyg

Tyg

Member
so the blur is dependant on the pitch and height and the focus stays in the center?
if thats the case i would just keep shader in the center and put it on the camera object
the one on the right would work fine and dont use the mouse stuff...camera will move the shader
kinda like a filter :)

so i need to know if the background is scrolling or 3D because it would depend on how you would implement the shader,
and camera movement
if the camera is static the shader could be put on the room (no need to keep blurring the camera)
or just the instance layer for gui not to be blurred
for 3D you would put it right on the camera

like so?
Game_pic.jpg
If thats what you want, ill post the code
 
Last edited:

jf_knight

Member
so the blur is dependant on the pitch and height and the focus stays in the center?
if thats the case i would just keep shader in the center and put it on the camera object
the one on the right would work fine and dont use the mouse stuff...camera will move the shader
kinda like a filter :)

so i need to know if the background is scrolling or 3D because it would depend on how you would implement the shader,
and camera movement
if the camera is static the shader could be put on the room (no need to keep blurring the camera)
or just the instance layer for gui not to be blurred
for 3D you would put it right on the camera

like so?
View attachment 37757
If thats what you want, ill post the code
Thank you for your reply! The explanation was very helpful as I am still new to shaders.
So this is what I'm going for;
Exactly like this functions. Moving the non-blurry circle around with the mouse on a flat static 2d image.
The mouse xy keeps its last position as well.
 
Last edited:

Tyg

Member
ok..thats easy enough to do, just locks when the mouse_clicks
that is the blur shader i posted exact one off shadertoy
i just have a to make a few uniforms that will pick up mouse clicks
 
Last edited:
D

Deleted member 13992

Guest
Disregard my earlier post, that shadertoy example isn't even close to being tilt-shift. :(
 

Tyg

Member
Ok i managed to get that shader working, but had probs with the shader reseting the mouse coords
even tried passing in an iMclick boolean (which works)
You cant make global vars in a shader the only global is uniforms, but they are read only
theres probably a way to do it...but lord knows i tried and it keeps resetting
i made a workaround
so i pass the saved coordinates back through iMouse like this

i put 2 vars in the create event of the object MsaveX and MsaveY
you can make them global if your going to use the shader on more than 1 object
----------------------------------------
uMouse = shader_get_uniform(sha, "iMouse");
if(mouse_check_button(mb_left) == true)
{
shader_set_uniform_f_array(uMouse, [mouse_x,mouse_y]);
MsaveX = mouse_x;
MsaveY = mouse_y;
}
else
{
shader_set_uniform_f_array(uMouse, [MsaveX,MsaveY]);
}
}
----------------------------------------
Here is the shader:

varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform vec2 iRez;
uniform vec2 iMouse;

const float PI = 3.1415926535897932384626433832795;
const float DoublePI = 2.0 * PI;

const float CircleSize = 1.0;
const float SmoothPadding = 0.33;

const float Distance = 0.04;
const float DistanceSteps = 8.0;
const float RadialSteps = 8.0;
const float RadialOffset = 0.5;
const float KernelPower = 1.0;

void main()
{
vec2 uv = v_vTexcoord;
vec3 ClearColor = texture2D(gm_BaseTexture,uv).rgb;

// Blur using UV access
float StepSize = Distance / DistanceSteps;
vec3 BlurColor = vec3(0);
vec2 SubUV = uv;
float CurDistance = 0.0;
vec2 CurOffset = vec2(0);
float SubOffset = 0.0;
float KernelDistance = 0.0;
float AccumulatedDistance = 0.0;

for(int i = 0; i < int(DistanceSteps); i++)
{
CurDistance += StepSize;
for (int j = 0; j < int(RadialSteps); j++)
{
SubOffset += 1.0;
CurOffset.x = cos(DoublePI * (SubOffset / RadialSteps));
CurOffset.y = sin(DoublePI * (SubOffset / RadialSteps));
SubUV.x = uv.x + CurOffset.x * CurDistance;
SubUV.y = uv.y + CurOffset.y * CurDistance;
KernelDistance = pow(CurDistance, KernelPower);
BlurColor += texture2D(gm_BaseTexture,SubUV).rgb * KernelDistance;
AccumulatedDistance += KernelDistance;
}
SubOffset += RadialOffset;
}
BlurColor /= AccumulatedDistance;

// Calculate clear vision circle
float AspectRatio = iRez.x / iRez.y;
vec2 VC = (iMouse.xy/iRez.xy);
vec2 v = uv - VC ;
v.x = v.x * AspectRatio;
float CirleRadius = CircleSize / 2.0;
float SmoothRadius = CirleRadius * SmoothPadding;
float CircleMask = smoothstep(CirleRadius, CirleRadius - SmoothRadius, length(v));
BlurColor.rgb = mix(BlurColor.rgb, ClearColor.rgb, CircleMask);

gl_FragColor = vec4(BlurColor, 1.0);
}

// You you need to also pass in the iRez :)
That should do it .. Happy Trails
 

jf_knight

Member
Ok i managed to get that shader working, but had probs with the shader reseting the mouse coords
even tried passing in an iMclick boolean (which works)
You cant make global vars in a shader the only global is uniforms, but they are read only
theres probably a way to do it...but lord knows i tried and it keeps resetting
i made a workaround
so i pass the saved coordinates back through iMouse like this

i put 2 vars in the create event of the object MsaveX and MsaveY
you can make them global if your going to use the shader on more than 1 object
----------------------------------------
uMouse = shader_get_uniform(sha, "iMouse");
if(mouse_check_button(mb_left) == true)
{
shader_set_uniform_f_array(uMouse, [mouse_x,mouse_y]);
MsaveX = mouse_x;
MsaveY = mouse_y;
}
else
{
shader_set_uniform_f_array(uMouse, [MsaveX,MsaveY]);
}
}
----------------------------------------
Here is the shader:

varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform vec2 iRez;
uniform vec2 iMouse;

const float PI = 3.1415926535897932384626433832795;
const float DoublePI = 2.0 * PI;

const float CircleSize = 1.0;
const float SmoothPadding = 0.33;

const float Distance = 0.04;
const float DistanceSteps = 8.0;
const float RadialSteps = 8.0;
const float RadialOffset = 0.5;
const float KernelPower = 1.0;

void main()
{
vec2 uv = v_vTexcoord;
vec3 ClearColor = texture2D(gm_BaseTexture,uv).rgb;

// Blur using UV access
float StepSize = Distance / DistanceSteps;
vec3 BlurColor = vec3(0);
vec2 SubUV = uv;
float CurDistance = 0.0;
vec2 CurOffset = vec2(0);
float SubOffset = 0.0;
float KernelDistance = 0.0;
float AccumulatedDistance = 0.0;

for(int i = 0; i < int(DistanceSteps); i++)
{
CurDistance += StepSize;
for (int j = 0; j < int(RadialSteps); j++)
{
SubOffset += 1.0;
CurOffset.x = cos(DoublePI * (SubOffset / RadialSteps));
CurOffset.y = sin(DoublePI * (SubOffset / RadialSteps));
SubUV.x = uv.x + CurOffset.x * CurDistance;
SubUV.y = uv.y + CurOffset.y * CurDistance;
KernelDistance = pow(CurDistance, KernelPower);
BlurColor += texture2D(gm_BaseTexture,SubUV).rgb * KernelDistance;
AccumulatedDistance += KernelDistance;
}
SubOffset += RadialOffset;
}
BlurColor /= AccumulatedDistance;

// Calculate clear vision circle
float AspectRatio = iRez.x / iRez.y;
vec2 VC = (iMouse.xy/iRez.xy);
vec2 v = uv - VC ;
v.x = v.x * AspectRatio;
float CirleRadius = CircleSize / 2.0;
float SmoothRadius = CirleRadius * SmoothPadding;
float CircleMask = smoothstep(CirleRadius, CirleRadius - SmoothRadius, length(v));
BlurColor.rgb = mix(BlurColor.rgb, ClearColor.rgb, CircleMask);

gl_FragColor = vec4(BlurColor, 1.0);
}

// You you need to also pass in the iRez :)
That should do it .. Happy Trails
ALL RIGHT! That works perfectly!
Thank you again for the thorough explanation.
My understanding of shaders has improved immensely.đź‘Ť
 

Tyg

Member
I have a nice function i wrote to pass in uniforms, if you have lots of shaders that need the same parameters
then you just pass in true or false it really helps speed up shader development,
so like if you just need the time and rez stuff you just check off the boxes
im going to keep the mouse_savex stuff with my mouse stuff, so i gained and learned also so thank you
if you want some code for it let me know, it really saves alot of time for redundant stuff like res and time and channel stuff
then i just have a template for all the uniforms, plug it into my shader fsh and just delete the uniforms i dont need for that shader :)

GSI_live.jpg
 
Top