Jump to content

How to draw a group only where pixels from another group are?


wayfinder
 Share

Recommended Posts

context.globalCompositeOperation = 'source-atop' 

This is your key to alpha masking. 

 

Phaser's BitmapData#alphaMask uses this internally.

http://examples.phaser.io/_site/view_full.html?d=bitmapdata&f=alpha+mask.js&t=alpha%20mask

 

You can use it with any method that draws on the canvas. Just wrap your custom method call with settings/unsetting the globalCompositeOperation.

Your method should then draw group A (which would be the alpha mask) and then draw group B to achieve the alpha mask effect.

 

temp = context.globalCompositeOperationcontext.globalCompositeOperation = 'source-atop'//to whatever is needed to bring the correct textures on the canvas//You might prevent the original mask group from rendering by setting it to visible=false.//in my game I draw a custom textured grid//Grid.draw(texture, context)context.globalCompositeOperation = temp

I don't know about the impact performance wise, but as it's only another native drawing mode it should be fine.

This is canvas 2D. WebGL is another story.

 

Regards

George

 

 

By the way:

I don't know about a build in solution. The blend modes of PIXI already wrap the drawing of a Sprite (see Sprite#_renderCanvas), but there is no source-atop blend mode- maybe because of missing webGL support?

 

from Sprite#_renderCanvas

if(this.blendMode !== renderSession.currentBlendMode)    {        renderSession.currentBlendMode = this.blendMode;        context.globalCompositeOperation = PIXI.blendModesCanvas[renderSession.currentBlendMode];    }

from CanvasRenderer, you can see that source-atop is no valid blend mode

PIXI.blendModesCanvas = [];                if(PIXI.canUseNewCanvasBlendModes())        {            PIXI.blendModesCanvas[PIXI.blendModes.NORMAL]   = "source-over";            PIXI.blendModesCanvas[PIXI.blendModes.ADD]      = "lighter"; //IS THIS OK???            PIXI.blendModesCanvas[PIXI.blendModes.MULTIPLY] = "multiply";            PIXI.blendModesCanvas[PIXI.blendModes.SCREEN]   = "screen";            PIXI.blendModesCanvas[PIXI.blendModes.OVERLAY]  = "overlay";            PIXI.blendModesCanvas[PIXI.blendModes.DARKEN]   = "darken";            PIXI.blendModesCanvas[PIXI.blendModes.LIGHTEN]  = "lighten";            PIXI.blendModesCanvas[PIXI.blendModes.COLOR_DODGE] = "color-dodge";            PIXI.blendModesCanvas[PIXI.blendModes.COLOR_BURN] = "color-burn";            PIXI.blendModesCanvas[PIXI.blendModes.HARD_LIGHT] = "hard-light";            PIXI.blendModesCanvas[PIXI.blendModes.SOFT_LIGHT] = "soft-light";            PIXI.blendModesCanvas[PIXI.blendModes.DIFFERENCE] = "difference";            PIXI.blendModesCanvas[PIXI.blendModes.EXCLUSION] = "exclusion";            PIXI.blendModesCanvas[PIXI.blendModes.HUE]       = "hue";            PIXI.blendModesCanvas[PIXI.blendModes.SATURATION] = "saturation";            PIXI.blendModesCanvas[PIXI.blendModes.COLOR]      = "color";            PIXI.blendModesCanvas[PIXI.blendModes.LUMINOSITY] = "luminosity";        }
Link to comment
Share on other sites

Thank you! The trouble is that I am already drawing the stuff that's to be masked with another blend mode, and they don't combine (nor, as you said, all work in webGL).

Maybe I should take a step back and explain what I want to do in terms of the end goal and not a particular technique: I'm trying to implement dynamic lighting (not even with normal maps or anything, just drawing colored areas over my scenery, but I don't want to light the background.

Link to comment
Share on other sites

When you mentioned shadows, the first thing that came to my mind was this excellent game:

http://nothing-to-hide-demo.s3.amazonaws.com/index.html

 

There is a great tutorial on creating the shadow effect with raytracing. Maybe that's what you're up to?

Anyway it's always ncie to share great posts about html5 gaming insides :)

 

Here we go:

http://ncase.me/sight-and-light/

 

 

Regards George

Link to comment
Share on other sites

I mentioned shadows? ;) No, I don't want to do raytracing, it's more like tinting (except with screen or multiply, overlay etc)... No shadows! So thanks for the link, but that's not what I'm after.

 

I don't really know how to describe it, which is probably why my google-fu has failed me for techniques (I did find loads of stuff on the raycasting thing and stuff like sprite lamp, all very interesting but not what I was looking for). Perhaps I can better show it as a picture:

 

Hjcuchb.png

 

 

Oh, yeah, and it has to work in webGL :)

Link to comment
Share on other sites

I have currently implemented this functionality for static geometry/light in a most memory-hogging way (ie creating a new bitmapdata object for every triangle of scenery and lighting it separately), but that doesn't work for dynamic actors (not to mention dynamic lights) and I'm afraid it will not scale well when my levels grow large, as they eventually will. 

 

I was thinking about perhaps creating one viewport-sized texture, rendering the lights into it and then cutting out the stuff in front of the lights and wherever the background is visible with a mask of sorts. But I'm not super confident that this would be performant enough to do every frame, and as I mentioned in the original post, masking is tricky.

 

The other promising train of thought I had was to use filters/shaders, but I don't know the first thing about how to even get started on writing those.

Link to comment
Share on other sites

Sorry for that imagined shadow I've seen in your post :)

Your problem looks pretty easy at first glance. But after thinking about it, it's a nice problem to work on! I will look into this tomorrow. Would you mind providing me the four layers from your example. This would make my testing a little more convenient.

 

Regards

George

Link to comment
Share on other sites

Hey,

this is the current state. Nothing new yet. Still stuck with with the source-atop approach and not working in WebGL. But it's still a first step.

http://jsfiddle.net/georgie/wmba4976/2/embedded/result/

 

The first problem I wanted to solve is the interfering of the main canvas with the actual composition between the content and the light during rendering. My goal was to create an easy implementation to merge the content with the lights with whatever blend and compositing modes you want. I created a second canvas through PIXI.CanvasBuffer to accomplish this. The content and the lights are never rendered on the main canvas. Instead the second canvas does the stop and get displayed in the main canvas through a separate texture again. But it's not fully working as I want. You can't combine 'lighten' with 'source-atop' which I tried to fix in the first place with this. So I've created nothing but complex code until now :)

 

Regarding performance:

I think the performance of my example will be fine even with more complex scenes. We prevent PIXI to render the content and do it our way with different compositions rules. This shouldn't be noticeable in the end.

 

Do not let us talk about WebGL yet. I'm not even able to make my custom rendering working with the web gl renderer.

 

What I'm into next:

I will try to find some infos about the canvas composition group layering. There _must_ be a simple way to isolate and combine different blendings and compositions with context.globalCompositeOperation in the canvas2D context

See: http://dev.w3.org/fxtf/compositing-1/#groupcompositing. If there is an easy solution in canvas2d it should certainly be doable in webgl too.

 

I really need some more knowledge about the webgl rendering internals. It's too easy to use PIXI & Co. nowadays so that I never looked under the hood to understand webgl.

 

Regards

George

Link to comment
Share on other sites

Man, you are going way above and beyond the call, thank you so much for your efforts! The way I've been able to combine screen and source-atop is by rendering the scenery into a render target first, then superimposing the light with screen, then superimposing the result onto the scenery with source-atop. This is fairly expensive, however. My precalc takes a good 12 seconds on a level of moderate size. Here's a link: https://dl.dropboxusercontent.com/u/14053711/skedgy/index.html. Static lights with various blend modes on the stone platforms. I've zoomed out to 0.5 x 0.5 so you can better gauge the size of the level.

Link to comment
Share on other sites

  • 2 weeks later...

I've been working on this and I think I have an approach that will work now. I wrote a webGL shader (wrapped in a filter) that takes a light map and applies it to a group with the desired blending mode. The light map is a texture I generate every frame from my light sources in screen space, and I can then apply the filter to any group in the scene graph that I want. I'll try and see whether it's feasible to use an ambient map, a color map, the light map and perhaps a normal map together - maybe one each the size of the viewport, or the four of them at a quarter resolution and stuffed into a single texture? We'll see. For the moment I'm pretty happy with what I have, but I don't know yet just how performant it will actually be when there are dozens of lights and dozens of elements to be lit...

Link to comment
Share on other sites

Hey that sounds pretty advanced. Would you mind to share a small example of it? I'm very interested in it! So you would basically drop canvas2d support or stick to a basic source-atop solution ? I thought about your problem a lot but didn't have enough time to try out some ideas yet - but if I get a an enlightenment I will drop my solution here ;)

 

Regards  George

Link to comment
Share on other sites

Well yeah, I dropped canvas. 

 

Here's the filter code:

 

/** * The LightFilter class applies dynamic color areas ("lights") with a variety of blend modes *  * @class LightFilter * @extends AbstractFilter * @constructor * @param texture {Texture} The texture used for the light map */PIXI.LightFilter = function(texture){    PIXI.AbstractFilter.call( this );     this.passes = [this];     this.uniforms = {        lightMap: {type: 'sampler2D', value:texture},        scale:           {type: '2f', value:{x:30, y:30}},        offset:          {type: '2f', value:{x:0, y:0}},        mapDimensions:   {type: '2f', value:{x:1, y:1}},    };     if(texture.baseTexture.hasLoaded)    {        this.uniforms.mapDimensions.value.x = texture.width;        this.uniforms.mapDimensions.value.y = texture.height;    }    else    {        this.boundLoadedFunction = this.onTextureLoaded.bind(this);         texture.baseTexture.on('loaded', this.boundLoadedFunction);    }     this.fragmentSrc = [        'precision mediump float;',        'varying vec2 vTextureCoord;',        'varying vec4 vColor;',        'uniform sampler2D lightMap;',        'uniform sampler2D uSampler;',        'uniform vec2 mapDimensions;',         'void main(void) {',         '   vec2 coords = vTextureCoord;',        '   vec4 texColor = texture2D(uSampler, coords);',        '   vec4 lightColor = texture2D(lightMap, vec2(gl_FragCoord.x/1280.0, 1.0 - gl_FragCoord.y/720.0));',        '   lightColor.a *= texColor.a;',        '   lightColor.rgb *= lightColor.a;', // Blend Modes                // Multiply        // '   gl_FragColor = texColor * lightColor + texColor * (1.0 - lightColor.a) + lightColor * (1.0 - texColor.a);',                                                  // Screen        // '   gl_FragColor.rgb = 1.0 - (1.0 - texColor.rgb) * (1.0 - lightColor.rgb);',                                   // Overlay        '   gl_FragColor.rgb = vec3((2.0 * texColor.r < texColor.a) ? (2.0 * lightColor.r * texColor.r + lightColor.r * (1.0 - texColor.a) + texColor.r * (1.0 - lightColor.a)) : (lightColor.a * texColor.a - 2.0 * (texColor.a - texColor.r) * (lightColor.a - lightColor.r) + lightColor.r * (1.0 - texColor.a) + texColor.r * (1.0 - lightColor.a)), (2.0 * texColor.g < texColor.a) ? (2.0 * lightColor.g * texColor.g + lightColor.g * (1.0 - texColor.a) + texColor.g * (1.0 - lightColor.a)) : (lightColor.a * texColor.a - 2.0 * (texColor.a - texColor.g) * (lightColor.a - lightColor.g) + lightColor.g * (1.0 - texColor.a) + texColor.g * (1.0 - lightColor.a)), (2.0 * texColor.b < texColor.a) ? (2.0 * lightColor.b * texColor.b + lightColor.b * (1.0 - texColor.a) + texColor.b * (1.0 - lightColor.a)) : (lightColor.a * texColor.a - 2.0 * (texColor.a - texColor. * (lightColor.a - lightColor. + lightColor.b * (1.0 - texColor.a) + texColor.b * (1.0 - lightColor.a)));', // Blend Modes End                '   gl_FragColor.a = texColor.a;',        '}'    ];}; PIXI.LightFilter.prototype = Object.create( PIXI.AbstractFilter.prototype );PIXI.LightFilter.prototype.constructor = PIXI.LightFilter; /** * Sets the map dimensions uniforms when the texture becomes available. * * @method onTextureLoaded */PIXI.LightFilter.prototype.onTextureLoaded = function(){    this.uniforms.mapDimensions.value.x = this.uniforms.lightMap.value.width;    this.uniforms.mapDimensions.value.y = this.uniforms.lightMap.value.height;     this.uniforms.lightMap.value.baseTexture.off('loaded', this.boundLoadedFunction);}; /** * The texture used for the light map. * * @property map * @type Texture */Object.defineProperty(PIXI.LightFilter.prototype, 'map', {    get: function() {        return this.uniforms.lightMap.value;    },    set: function(value) {        this.uniforms.lightMap.value = value;    }});
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...