Jump to content


Photo
- - - - -

3d Paint Fill-in Effect


  • Please log in to reply
1 reply to this topic

#1 littlejoe

littlejoe

    GMC Member

  • GMC Member
  • 143 posts
  • Version:GM:Studio

Posted 02 March 2016 - 04:16 AM

This is tutorial overview of how to achieve this paint fill-in effect:

 

 

screenshot.png

 

I thought it would be cool to make a neat effect like this when I thought about how neat the game “Beyond Eyes” looked. This is by no means as good of an effect that Beyond Eyes achieves, but I think this can be expanded quite a bit by someone with the time and desire to make it something really neat.

 

This isn’t going to be a step-by-step or line-by-line tutorial. This is going to be a basic overview of how the effect is achieved, with a few code snippets and a fragment shader.

 

Details:

    Title: Paint Fill-in effect

    Description:  This tutorial gives a general overview on how to achieve the paint fill in effect seen in the screenshot above

    GM Version: Studio

    Registered: Yes

    File Type: .gmz

    File Size:  2 MB

    File LinkDownload the GMZ here!

    Extensions or DLLs:  None required!

    Tags: 3d, surfaces, shaders, paint, artsy

 

So with all that out of the way, here we go!

First off, the ground is drawn as a simple d3d_floor that covers the whole room. The ground’s texture is a global surface called ground_surface. ground_surface is created by taking 3 images and combining them in the proper way:

3images.png

 

But, before you start smashing all these images together, we first need visible_surface.

visible_surface is the combination of player_blob_sprite and black_dark_background. The player_blob_sprite is drawn at the player’s current position. It can also be drawn at other places as well (such as important landmarks, enemies, npcs, etc… Any other place you want a “splash of paint”)

 

Updating the visible_surface is as easy as:

surface_set_target(visible_surface);
draw_clear(c_black);
draw_sprite(player_blob_sprite,0,player.x,player.y);
surface_reset_target();

So now that you’ve got visible_surface ready to go, we’re going to need to set up the shader. We’re going to be passing the visible_surface into the shader as a sampler2d. That means we’ll need to set up a texture stage. Once the shader has been set and the visible_surface has been passed to it, we can draw the ground_texture_background to the ground_surface.

stage = -1;
stage = shader_get_sampler_index(alpha_channel_threshold,"alpha_map");
surface_set_target(ground_surface) //Set the surface
shader_set(alpha_channel_threshold) //Set the shader that the stage will be in
texture_set_stage(stage,surface_get_texture(visible_surface)); //Send the stage to the shader
//Then do all the shader drawing

And here’s the fragment code for the shader needed:

varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform sampler2D alpha_map; //This will hold the black and white "alpha channel"
vec4 alpha_color;

void main()
{
    gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
    alpha_color =  texture2D(alpha_map, v_vTexcoord );
    if ((alpha_color.r+alpha_color.g+alpha_color.b)/3.0 > .6)
    {
    gl_FragColor.a = 1.0;        
    }
    else
    {
    gl_FragColor.a = 0.0;
    }
}

And that’s the backbone of how this whole effect works.

Here’s a quick summary recap of the process:

summary_new.png

 

 

Now for the rocks and the trees.

The basic idea is that each rock and tree copies a small portion of the visible_surface that they cover to a new local surface, object_surface (while the other surfaces were global, this surface should be local to each individual object).

In the example file, I included all of this code inside of the camera_object. At the time it seemed like it would run better, but I’m not too sure that’s the case.

Since there are a ton of people here that are smarter than me, if you can think of a more efficient way to do this, please let me know and I’ll update this guide (while giving you credit, of course).

//Create event for the rock object
object_surface = -1; //Creates it's own cute little surface
my_stage = shader_get_sampler_index(object_shader,"texture_map"); //It adds a stage for the texture
my_visible_stage = shader_get_sampler_index(object_shader,"alpha_map"); //Adds a stage for the alpha_map

//This is all done inside the draw event of the camera object
with (rock_object)
{
if !surface_exists(object_surface)
{
object_surface = surface_create(32,32) //Creates the rock surface if it doesn't exists for this instance of the rock
}
if surface_exists(visible_surface)
{
surface_copy_part(object_surface,0,0,visible_surface,x-16,y-16,32,32) //Get the small section of the visible_surface that the rock is covering
}
shader_set(object_shader) //Set the object shader, almost the same as alpha_channel_threshold, but with two sampler2d's instead of one
texture_set_stage(my_visible_stage,surface_get_texture(object_surface)); //Adds the surface to the shader
texture_set_stage(my_stage,background_get_texture(rock_texture_background)); //Adds the rock surface to the shader
d3d_model_draw(rock_model,x,y,0,background_get_texture(rock_texture_background)) //Draws the rock
shader_reset() //Resets the shader

d3d_model_draw(rock_inside_out_model,x,y,0,background_get_texture(border_background)) //Draws the border
}

Now, it’s worth noting that you can’t just simply import any old 3d model into the game and expect it to line up perfectly with the ground. The best way to align the UV maps for your models is to try and project them from a top-down view as much as possible.

 

 

Here’s a link to the rock blender file: Rock Blender FIle

(and here’s an OBJ file: rock OBJ file )

It’s not completely necessary to align the UV maps like that, but it looks much more natural.

 

The rock model is pretty simplistic, so to do more complex models that can’t simply be flattened for the UV map, I chose to separate parts of the model into separate models with separate UV maps. In this example, the tree leaves and the tree trunk are separate models, both have their own UV maps that are generated differently in order to make the paint fill in look natural while keeping the textures from stretching too much.

 

And that's how it's done. It's pretty simple, but I think it could be expanded for some really incredible looking games.

 

Like I said, I know there are a bunch of people here on the GMC that are smarter than me, so if you see anything in the code that can be simplified or made more efficient, let me know and I'll update the tutorial and credit you. If you make a game using this technique I'd love to see it!


  • 2

TGBTG

"It didn't end at the Roman Colosseum, Christians still die." Voice of the Martyrs
My blog:
http://angelwirestudio.blogspot.com/

My Twitter

Partly Pop Can
Volcosis


#2 chance

chance

    GMC Member

  • Global Moderators
  • 8762 posts
  • Version:GM:Studio

Posted 02 March 2016 - 12:07 PM

This creates a very interesting effect.  It's not a "flood fill" effect within boundaries, as I first thought from the title.  But rather a texture blending effect for textures already assigned.  And it looks nice.

 

There are many separate things going on here, so it's not for beginners.    There is use of surfaces (including drawing the application surface manually in the DRAW GUI event), use of d3d,  use of shaders, etc.  My first thought is that this could be simplified to show the basic shader effect in a more straightforward way.  Maybe without the d3d and the models.  But I haven't spent enough time to be certain of that.

 

Either way, this is a useful addition to the tutorials forum.  Your post has a fairly complete description of how the effect works.  And there are plenty of comments in the code itself.   (Not counting the numerous sayings and quotations sprinkled throughout... what's that about?) 

 

One thing I would ask, though, is to be more consistent with the syntax.  Much of the code is properly formatted with line terminations.  And much is not.  I didn't see any areas that might cause ambiguities or errors.  But it's something you should fix.


  • 0