tilt-shift.how
krumza

Hi all. My question - in title. I have scrolable and zooming world, but need tilt-shift effect (ui layer hide from this)

I need to make tilt-shift effect but dont know how it make with very low cost.

I know 2 variants -

1. create iframe with this page from bottom and top, than blur it  - but it be good for promopage or single player game - not for multipayer game

2. on update function save canvas as image, then insert it as sprite on top layer and blur  - but it very expensive i think

I know that Gio work in webgl - may be exist a shader for me?)

 

Comments 1 to 15 (17 total)
Gio

Hi

You are right, neither of those options would be fast enough in practice.

We plan to add a blur effect to WADE in version 3.8, but that's some time away.  The plan is that in WADE 3.8 you'll be able to do wade.setLayerBlur(layerId, blurAmount)

If you need it sooner, it is possible but not very easy.

If you want to make it really fast, it's pretty complicated in fact. You'd need several passes. This involves setting the layer to use an offscreen render target, getting the render target and using it in a custom draw function for a sprite with a blur shader. So pretty complex.

But you can get something that looks ok just by using a post process shader for your layer. This is limited to a single pass. While much faster than the 2 options you mentioned, single-pass gaussian blur is still slow. But maybe you can get a nice effect even if it's not exactly gaussian blur.

For example you could try something like this

// change the value of res to change the look of the effect
const vec2 res = vec2(200., 200.);

vec2 uv = uvAlphaTime.xy;
vec2 uvDelta = vec2(1.) / res;
vec4 color = texture2D(uDiffuseSampler, uvAlphaTime.xy);
vec4 c = vec4(0.);
for (float i=-2.; i<=2.; i++)
{
    for (float j=-2.; j<=2.; j++)
    {
        float weight = 1. - pow((abs(i) + abs(j)) / 4., 5.);
        c += texture2D(uDiffuseSampler, uv + uvDelta * vec2(i, j)) * weight / 20.;
    }
}

float r = length(uv - 0.5);

gl_FragColor = mix(color, c, r * 2.);
gl_FragColor.w *= uvAlphaTime.z;

 

krumza

Gio thanks you are always very helpful.

I tried your Shader - it works, but the shaders are hard for me

I am quite dumb as alas

I certainly try by trial and error to understand the odds to get what you want, similarly can do the same vertical Shader - together they give almost Gaussian blur.

But the peculiarity of what else is - I have two layers - a layer for roads and buildings on each layer to hang up the post-Shader?

Can you figure out this Shader as the picture - so that the object S has received data from the fact that under it and distorted it

is it possible to apply to the object's Shader that would catch pixels from lower layers?

Gio

In WADE 3.6.x, sadly no this is not possible (or rather, it may be possible but very very slow), because each layer has got its own separate webgl context.

From version 3.7 yes, you can technically do that, because those layers will share the same context. We still won't have a nice API to do it, but using some raw webgl, you should be able to get that working. I can post some code here once we've released 3.7.

 

krumza

will wait )

krumza

Gio v 3.7 is avaliable ^)

Please show how can i make tilt-shift for different layers

Gio

This is now possible. However, I would NOT recommend it - the way to do it involves using undocumented functions and private variables. This is very bad practice.

In other words, do it this way only if you MUST do it now and cannot wait for wade 3.8. Because in wade 3.8 there will be a much nicer (and much easier) way of doing this.

Having said that, here's how you can read the pixels from other layers without slowing things down. For this example, let's say that you have a sprite on Layer 1 that wants to read the pixels of Layer 2 and Layer 3.

First of all, you need to set Layer 2 and Layer 3 to use off-screen render targets. You do this by calling wade.setLayerRenderMode after loading your scene. It is important to do it after loading the scene, because the scene loading code sets the layer render mode and may override what you're doing manually

wade.setLayerRenderMode(2, 'webgl', {offScreenTarget: true});
wade.setLayerRenderMode(3, 'webgl', {offScreenTarget: true});

Now if you create an object on layer 1, when it is added to the scene you can do this to bind the layer 2 and layer 3 textures to pixel shader uniforms called t2 and t3 (for example you can do this in the object's onAddToScene):

// first draw all layers at least once
wade.draw();

// then change the draw function of the sprite
var sprite = this.getSprite();
sprite.setDrawFunction(function(gl)
{
    // get layers directly - wade.getLayer is undocumented, so this is BAD
    var l2 = wade.getLayer(2);
    var l3 = wade.getLayer(3);
    
    // get the main render target textures - private variables == BAD
    var t2 = l2._mainRenderTarget.texture;
    var t3 = l3._mainRenderTarget.texture;
    
    // bind layer textures to the gl context and set them up
    gl.activeTexture(gl.TEXTURE1);
    gl.bindTexture(gl.TEXTURE_2D, t2);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.textures.t2 = t2;
    
    gl.activeTexture(gl.TEXTURE2);
    gl.bindTexture(gl.TEXTURE_2D, t3);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.textures.t3 = t3;
    
    gl.activeTexture(gl.TEXTURE0);

    // call the default draw function
    this.drawStatic_gl(gl);
});    

Then edit your object's sprite and add 2 shader uniforms called t2 and t3, both of type sampler2D.

Finally, edit the sprite pixel shader to use textures t2 and t3 to do whatever you like. For example:

vec2 uv = uvAlphaTime.xy;
uv.y = 1. - uv.y;  // flip Y axis
vec4 c2 = texture2D(t2, uv);
vec4 c3 = texture2D(t3, uv);
gl_FragColor = c2 + c3;

 

krumza

well, i try it and will wait new wade)

Gio

I had forgotten about this... I should have said that now that WADE 3.8 is available, this is much easier.

You can get the blur texture for any layer and use it in your shaders directly. You just add shader parameters of type Sampler2D and set their values to "_layerBlur_layerId". So for example _layerBlur_5_layerBlur_6 etc.

The only thing is, blur must be enabled for those layers. So you want to either have a non-zero blur value for those layers

wade.setBlur(5, 0.01);

or just force the blur texture to always update:

wade.alwaysUpdateBlur(5);

However keep in mind that creating these blur textures is a bit expensive, so remember to turn that off when you don't need it.

krumza

i found this in pixi (https://github.com/pixijs/pixi-filters/blob/master/filters/tilt-shift/src/tilt-shift.frag):

varying vec2 vTextureCoord;

uniform sampler2D uSampler;
uniform float blur;
uniform float gradientBlur;
uniform vec2 start;
uniform vec2 end;
uniform vec2 delta;
uniform vec2 texSize;

float random(vec3 scale, float seed)
{
    return fract(sin(dot(gl_FragCoord.xyz + seed, scale)) * 43758.5453 + seed);
}

void main(void)
{
    vec4 color = vec4(0.0);
    float total = 0.0;

    float offset = random(vec3(12.9898, 78.233, 151.7182), 0.0);
    vec2 normal = normalize(vec2(start.y - end.y, end.x - start.x));
    float radius = smoothstep(0.0, 1.0, abs(dot(vTextureCoord * texSize - start, normal)) / gradientBlur) * blur;

    for (float t = -30.0; t <= 30.0; t++)
    {
        float percent = (t + offset - 0.5) / 30.0;
        float weight = 1.0 - abs(percent);
        vec4 sample = texture2D(uSampler, vTextureCoord + delta / texSize * percent * radius);
        sample.rgb *= sample.a;
        color += sample * weight;
        total += weight;
    }

    gl_FragColor = color / total;
    gl_FragColor.rgb /= gl_FragColor.a + 0.00001;
}

but as usual in my case i have black screen:

Gio

I wouldn't do it that way - it's super slow, and unnecessarily complicated.

It's slow because it's sampling the texture 61 (sixtyone!) times for each pixel to generate a blurred texture. You don't need that because you already have a blur texture. One that has been generated in a reasonable, sensible way - not sampling the original texture 61 times per pixel :)

For simplicity, let's say that you have your objects on layer 2. You can then apply a tilt shift effect by using a simple post process shader on Layer 1 (for example). The shader is very simple:

vec2 uv = uvAlphaTime.xy;
vec4 color = texture2D(normalTexture, uv);
vec4 blurColor = texture2D(blurTexture, uv);
gl_FragColor = mix(color, blurColor, abs(uv.y - 0.5) * 2.);

With these shader uniforms:

To see it properly in the editor, remember to hide layer 2 (so only layer 1 with the tilt shift effect is visible). Also, set blur to 1 on layer 2.

To make sure it works as it should when you run the game, just call

wade.alwaysUpdateBlur(2);

after loading the scene.

krumza

gio I am very very grateful

no words

krumza

i cant it work out.

i create this construction:

this.tiltshiftOn = function(){
		var shaderSource = "vec2 uv = uvAlphaTime.xy;vec4 color = texture2D(normalTexture, uv);vec4 blurColor = texture2D(blurTexture, uv);gl_FragColor = mix(color, blurColor, abs(uv.y - 0.5) * 2.);";
		var shaderUniforms = {
			normalTexture: "sampler2D",
			blurTexture: "sampler2D"
		};
		var properties15 = {
			normalTexture: "_layerRenderTarget_15",
			blurTexture: "_layerBlur_15"
		};		
		var properties16 = {
			normalTexture: "_layerRenderTarget_16",
			blurTexture: "_layerBlur_16"
		};

	wade.setLayerRenderMode(9, 'webgl');
	wade.setLayerCustomProperties(9, properties16);
	wade.setPostProcessShader(9, shaderSource, shaderUniforms);

	wade.setLayerRenderMode(8, 'webgl');
	wade.setLayerCustomProperties(8, properties15)				
	wade.setPostProcessShader(8, shaderSource, shaderUniforms);
};

this.tiltshiftOff = function(){
    wade.setLayerRenderMode(9, '2d');
    wade.setLayerRenderMode(8, '2d');
};

this.init = function(){
	wade.setLayerRenderMode(16, 'webgl', {offScreenTarget: true});	    
	wade.setLayerSorting(16,'bottomToTop');
	wade.alwaysUpdateBlur(16);

	wade.setLayerRenderMode(15, 'webgl', {offScreenTarget: true});	    
	wade.setLayerSorting(15,'bottomToTop');
	wade.alwaysUpdateBlur(15);

}

I have a layer of 15 and 16 - if I apply a Shader directly to them then it works, but the layers are becoming a bit transparent that I do not like, however, if I add layers 8 and 9 to add a Shader to them, nothing happens/ I see in console this:

wade_3.8.1.js:458 Warning: Trying to get image _layerRenderTarget_16 without loading it first
wade_3.8.1.js:458 Warning: Trying to get image _layerBlur_16 without loading it first
wade_3.8.1.js:458 Warning: Trying to get image _layerRenderTarget_15 without loading it first
wade_3.8.1.js:458 Warning: Trying to get image _layerBlur_15 without loading it first

If i make only one layer, as example 9 it almost works, but sometimes a message appears in the console

what am I doing wrong again

Gio

What you have there looks alright, however there are a few things to consider:

1. Layers 8 and 9 must use the same WebGL context as layers 15 and 16. This means that you cannot have a '2d' layer in between. For example if you also had layer 12, and layer 12 was set to '2d', this would not work.

2. You don't need to set render mode to '2d' to turn off the effect, you can keep it in WebGL mode, just set the post process shader to null.

3. When you load a scene, the "always update blur" may be overwritten by the scene settings. If you are loading a scene with wade.loadScene, you may want to call wade.alwaysUpdateBlur(...) after the scene has been loaded.

4. This is more of an optimization than an actual problem, but you probably want to do this tilt shif effect on one single layer. You can read textures from layer 15 and 16 and combine them in the same shader.

I will see if I can create a simple scene for you to use as an example, later today.

Gio

Here it is - attached. I hope this helps.

krumza

no matter how much I tried to do this with 2 levels - I get an error.

wade_3.8.1.js:458 Warning: Trying to get image _layerRenderTarget_16 without loading it first
wade_3.8.1.js:458 Warning: Trying to get image _layerBlur_16 without loading it first
wade_3.8.1.js:458 Warning: Trying to get image _layerRenderTarget_15 without loading it first
wade_3.8.1.js:458 Warning: Trying to get image _layerBlur_15 without loading it first

I'm trying to do with 2 layers in your tiltshift and get an error

An error occurred compiling a pixel shader: ERROR: 0:2: 'texture2D' : no matching overloaded function found
ERROR: 0:2: '=' : dimension mismatch
ERROR: 0:2: '=' : cannot convert from 'const mediump float' to 'mediump 4-component vector of float'
ERROR: 0:3: 'texture2D' : no matching overloaded function found
ERROR: 0:3: '=' : dimension mismatch
ERROR: 0:3: '=' : cannot convert from 'const mediump float' to 'mediump 4-component vector of float'

Is it possible to get the webgl context from two layers?

And one more question - how to do this only in one layer of effects (as an example)?

I try something like:

vec2 uv = uvAlphaTime.xy;
vec4 color15 = texture2D(normalTexture15, uv);
vec4 color16 = texture2D(normalTexture16, uv);
vec4 blurColor15 = texture2D(blurTexture15, uv);
vec4 blurColor16 = texture2D(blurTexture16, uv);

gl_FragColor = mix(color15+color16, blurColor15+blurColor16, abs(uv.y - 0.5) * 2.);

But it does not turn out exactly what I want

My project is here

Post a reply
Add Attachment
Submit Reply
Login to Reply