wayfinder Posted December 13, 2014 Share Posted December 13, 2014 Is there a painless and performant way to draw a group so that it only paints on another group, and not where that group is transparent? Link to comment Share on other sites More sharing options...
wayfinder Posted December 15, 2014 Author Share Posted December 15, 2014 I've looked at masking, but apparently that's only possible with graphics objects... there's a PIXI AlphaMaskFilter, but I can't get it to work, and I fear it will be less than performant Do you have any ideaas? Link to comment Share on other sites More sharing options...
george Posted December 15, 2014 Share Posted December 15, 2014 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 = tempI 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. RegardsGeorge 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#_renderCanvasif(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 modePIXI.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 More sharing options...
wayfinder Posted December 15, 2014 Author Share Posted December 15, 2014 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 More sharing options...
george Posted December 15, 2014 Share Posted December 15, 2014 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 More sharing options...
wayfinder Posted December 15, 2014 Author Share Posted December 15, 2014 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: Oh, yeah, and it has to work in webGL Link to comment Share on other sites More sharing options...
wayfinder Posted December 15, 2014 Author Share Posted December 15, 2014 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 More sharing options...
george Posted December 15, 2014 Share Posted December 15, 2014 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. RegardsGeorge Link to comment Share on other sites More sharing options...
wayfinder Posted December 16, 2014 Author Share Posted December 16, 2014 Sure, here they are: http://i.imgur.com/ckR4O88.jpghttp://i.imgur.com/cqNykgI.pnghttp://i.imgur.com/CgGRZR5.pnghttp://i.imgur.com/dKf8OYe.png Thank you for your interest! Link to comment Share on other sites More sharing options...
george Posted December 16, 2014 Share Posted December 16, 2014 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 contextSee: 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. RegardsGeorge Link to comment Share on other sites More sharing options...
wayfinder Posted December 16, 2014 Author Share Posted December 16, 2014 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 More sharing options...
wayfinder Posted December 27, 2014 Author Share Posted December 27, 2014 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 More sharing options...
george Posted December 27, 2014 Share Posted December 27, 2014 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 More sharing options...
wayfinder Posted December 27, 2014 Author Share Posted December 27, 2014 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; }}); george 1 Link to comment Share on other sites More sharing options...
wayfinder Posted December 28, 2014 Author Share Posted December 28, 2014 I extended the filter to handle a coloration layer and an ambient light map in addition to light sources. Not trivial, and my implementation PROBABLY leaves things to be desired, but I like the results: http://i.imgur.com/6GQF7Nu.gifv Link to comment Share on other sites More sharing options...
Recommended Posts