Blue's Masks and Shadows!

What is this?

This is a script intended for developers to assist with the creations of real-time drop shadows and an easy to use mask system as an alternative to stencils which scare a lot of people.

Is there any limitations to this?

Well there are some, the biggest limitation is that this is not compatible with 3D rendering, so what that means is if you are not drawing UI stuff, then this is probably not gonna work for you.
How ever it is possible to implement but due to my limited knowledge on rendering I have not been able to make this possible.

What can we use this for?

You can use it for anything you like, including paid addons. You can improve the code (which it can be improved a lot ;D) or distribute how you want, I just ask that you include credits. That’s all :slight_smile:

How do I use the shadows?

This is the easy part. There are only 2 functions you need to use, the first being BSHADOWS.BeginShadow(). This function tell the script to start generating a shadow, after calling this you can draw what ever you want.
Once you have finished drawing the shapes you want to have a shadow you then need to call BSHADOWS.EndShadow(). This function is what tell the script to actually generate and draw the shadow.
There are no arguemnts for BSHADOWS.BeginShadow() but there is for BSHADOWS.EndShadow(), here they are.


BSHADOWS.EndShadow(intensity, spread, blur, opacity=255, direction=0, distance=0, _shadowOnly=false)
--Intensity is how many times it should draw the shadow.
--The higher the number the more dense the shadow.
--How ever this must be an int, any float passed with be rounded. 
--You generally want 1 here but if you want your shadows more visible change it to 2, 3 or ever 4

--Spread is how much to spread the shadow outwards from the shape, 
--the higher the number to bigger the shadow is, this also must be an int

--Blur is the sharpness of the shadow. 0 Would be a solid shadow
--And something along the line of 6 would be a very smooth/soft shadow

--Opcaity (Optional) controls the opcaity of the drawn shadow

--Direction (Optional) controls the direction the shadow should be offset in (0-360)

--Distance (Optional) controls how far to offset the shadow by in the direction of the offset

--_shadowOnly (Optional) If true will only draw the shadow and not the original content you drew.

And thats it, pretty simple right? Heres an example of it, and the result it makes


hook.Add( "HUDPaint", "ShadowExample", function()
	BSHADOWS.BeginShadow()
	draw.RoundedBox(0,100,100,100,100,Color(255,120,120))
	draw.RoundedBox(0,150,150,100,100,Color(120,120,255))
	BSHADOWS.EndShadow(1, 2, 2)
	--Note that the shadow does not overlap the both shapes, it instead treats it as one giant shape.
end)

How do you we use the masks?

Well, masks are a tiny bit more complicated to use than shadows but now by much!
So to begin creating a mask you first need a mask image, this can be made in photoshop or any other editor that supports alpha
The way the mask works is based on the alpha and not the color. Any pixels in your mask that are transparent will make what ever you draw it over transparent.
For example I have this image, as you can see it has solid parts, and non solid parts.

So now that we have an image, lets create the mask.
Here is the function to create a mask (You should only need to create the mask once, not every frame)


BMASKS.CreateMask(maskName, maskPath, maskProperties="")

--maskName is the name of the mask, this can be any string but must be unique
--maskPath is the path to the png of the mask you are creating
--maskProperties is any material properties you want to define, such as noclamp or smooth

--Once a mask is created it returns a string of its name, this is used to indentify it.

So now we have made our mask and created the mask in game, now all we have to do is draw the mask
So to do this its very simular to the shadows. You have to call BMASKS.BeginMask() and BMASKS.EndMask()
Here are the definations of the functions and there arguments


BMASKS.BeginMask(maskName)
--maskName is the mask you want to use, the string name is used.

--This tells the script that you have finished drawing your masked stuff.
BMASKS.EndMask(maskName, x, y, sizex, sizey, opacity=255, rotation=0, dontDraw=false)
--maskName is the mask you want to use, the string name is used.
--x is the x position of the mask
--y is the y position of the mask
--sizex is the width of the mask
--sizey is the height of the mask
--opacity is the total opcaity of the entire drawn mask
--rotation is the rotation of the mask (NOTE THAT WHEN USING A ROTATION THAT IS NOT 0 THE X AND Y PARAMS ARE CENTERED INSTEAD OF TOP LEFT)
--dontDraw - if set to true it will not draw anything, but instead return a ITexture that is ScrW() by ScrH() that contains the drawn content
--dontDraw is not important unless you are doing some more advanced stuff (see below)

So now that we know that, lets look at an example and see the result


hook.Add("HUDPaint", "MaskTest", function() 
	BMASKS.BeginMask(exampleMask) 
	draw.RoundedBox(0, ScrW()/2 - 200, ScrH()/2 - 200, 400, 400, Color(255,255,255,255))
	BMASKS.EndMask(exampleMask, ScrW()/2 - 200, ScrH()/2 - 200, 400, 400)
end)

Why do the shadows not work with the mask?

Using masks and shadows together requires you to do something different
Unfortunatly I had to do it this way due to a bug where drawing the contents of one
render target to the other does not work (at least not for me)

So, if you rememeber I said that BMASKS.EndMask() has that extra arguments called _dontDraw
that I said you probably wont use, well this is what its for. By passing true it does not
draw the result but instead returns a texture of what it should look like. We can use this
texture with a special functions called BSHADOWS.DrawShadowTexture(). This function is almost
the exact same as BSHADOWS.EndShadow() except for two differences. The first being that there is
and extra argument for it that takes a texture, and the second being that you don’t need
to call BSHADOWS.BeginShadow() first. The arguments are as follows


BSHADOWS.DrawShadowTexture(texture, intensity, spread, blur, opacity=255, direction=0, distance=0, _shadowOnly=false)
--Texture takes an ITexture that should be ScrW() x ScrH(). It will then use this to generate a shadow instead of the user drawing
--Intensity is how many times it should draw the shadow.
--The higher the number the more dense the shadow.
--How ever this must be an int, any float passed with be rounded. 
--You generally want 1 here but if you want your shadows more visible change it to 2, 3 or ever 4
--Spread is how much to spread the shadow outwards from the shape, 
--the higher the number to bigger the shadow is, this also must be an int
--Blur is the sharpness of the shadow. 0 Would be a solid shadow
--And something along the line of 6 would be a very smooth/soft shadow
--Opcaity (Optional) controls the opcaity of the drawn shadow
--Direction (Optional) controls the direction the shadow should be offset in (0-360)
--Distance (Optional) controls how far to offset the shadow by in the direction of the offset
--_shadowOnly (Optional) If true will only draw the shadow and not the original content you drew.

All it does it take a texure and make a shadow for it instead of you drawing it.
Now we can use this functions to create shadows that work correctly for our masks by doing this


local exampleMask = BMASKS.CreateMask("DIAMOND_PATTERN", "mask.png", "noclamp smooth")

hook.Add("HUDPaint", "MaskTest", function() 
	--Begin our mask
	BMASKS.BeginMask(exampleMask)

	--Draw our content
	draw.RoundedBox(0, ScrW()/2 - 200, ScrH()/2 - 200, 400, 400, Color(255,255,255,255))

	--Notice we now pass true for _dontDraw, so it instead returns the generated texture isntead of drawing it
	local maskResult = BMASKS.EndMask(exampleMask, ScrW()/2 - 200, ScrH()/2 - 200, 400, 400, 255, 0, true)

	--Now we feed this into the DrawShadowMask function
	BSHADOWS.DrawShadowTexture(maskResult, 1, 2, 2, 255) 
end)

Seems more complicated that it needs to I know and im sorry, if you feel you can improve it feel free :smiley:

Download

Note that if you want to use these you need to include the script first, and then it creates a global table called BSHADOWS or BMASKS.

Blue’s Shadows is available here https://pastebin.com/8u8N1Nkw
Blue’s Masks is available here https://pastebin.com/B4AE1hvD

If you have any questions feel free to ask them, and I want to thank bobblehead for helping me with an
issue I want having with render targets. Enjoy!

Sees that Code Blue made a thread: :incredible:

No GitHub? :frowning:

I’m lazy, cannot be bothered to set up a repository.

Maybe post the code here just in case Pastebin goes down or the links break?

2D UI is never going to be the same thanks to you. Excellent job.

absolutely fantastic stuff
thanxxx

Sorry, but can someone explain to me the use of this? All it does is draw squares on the screen at a 45 degree angle? I fail to see a “general” scenario where that’s needed. Not trying to be rude, just genuinely curious, because I honestly don’t know.

It draws ANYTHING at ANY angle for ANY shadow or mask you want. This is incredibly useful.

If you read the actual post:

ninja’d

:snip: automerge

Just like what MPan1 said it has many uses. The pictures you see are an example of how using it you can cut out a pattern from a square, and draw shadows around the shape.

Btw if anyone makes anything with this please share it here I would love to see what you have done.

[editline]2nd October 2017[/editline]

Also MPan1, using this you can rewrite most of your rainbow functions to run in realtime, by using 3 masks for the R, G and B channels you can create a gradient for it, just an idea I had.

Great work!

Does this work prior to rendering on 3d2d work space?

I’ll stick with my 1fps functions thank you very much
Also, you need more than 3 channels or it’ll look like this:

3 is all you would need to create a gradient for them all, i’ll try to make it now and show you

[editline]2nd October 2017[/editline]

Here you go, I messed up the gradients for the mask in photoshop but as you can see its possible, and all realtime. If I adjusted the masks more I could get the same pattern XD
This is only using 3 channels to, R,G,B

and the code is here, the masks are just gradients in photoshop so I dont need to show them.


hook.Add("HUDPaint", "RGB", function() 

	--Draw background
	draw.RoundedBox(0,ScrW()/2 - 200, ScrH()/2 - 200, 400, 400, Color(0,0,0,255))

	--Draw red box and mask it
	BMASKS.BeginMask(mask_r)
	draw.RoundedBox(0,ScrW()/2 - 200, ScrH()/2 - 200, 400, 400, Color(255,0,0,255))
	BMASKS.EndMask(mask_r, ScrW()/2 - 200, ScrH()/2 - 200, 400, 400 + 120)

	--Draw blue box
	BMASKS.BeginMask(mask_b)
	draw.RoundedBox(0,ScrW()/2 - 200, ScrH()/2 - 200, 400, 400, Color(0,0,255,255))
	BMASKS.EndMask(mask_b, ScrW()/2 - 200, ScrH()/2 - 200 - 120, 400, 400 + 120)

	--Draw green box
	BMASKS.BeginMask(mask_g)
	draw.RoundedBox(0,ScrW()/2 - 200, ScrH()/2 - 200, 400, 400, Color(0,255,0,255))
	BMASKS.EndMask(mask_g, ScrW()/2 - 200, ScrH()/2 - 200 - 100, 400, 400 + 200)
end)

Am I missing something? :V



local f = vgui.Create( "DFrame" )
f:SetSize( 200, 200 )
f:Center()
f:MakePopup()
function f:Paint( w,h )
    draw.RoundedBox( 0, 0, 0, w, h, color_black )
    BSHADOWS.BeginShadow()
        draw.RoundedBox( 0, 0, h/4, w, h/2, Color( 125, 125, 0 ) )
    BSHADOWS.EndShadow(1, 2, 2)
end


Draw bounds are limited to the size of the panel. Use

Panel:NoClipping or

DisableClipping.

Thanks, but it looks almost exactly the same as the image I posted before, which was meant to show the lack of yellow, cyan and magenta if you only use red, green and blue channels.

https://i.imgur.com/v2jUyvr.png[/t]

Compare these two:
[t]https://i.imgur.com/9i6GApi.png[/t]
[t]https://i.imgur.com/Jnt72cX.png[/t]

And it’s clear that the yellow, cyan and magenta are lost. You need to increase the gamma a lot for them to look normal.

[t]https://i.imgur.com/rmWUTFn.png

[editline]3rd October 2017[/editline]

yes

The actual problem is that you need to use screen coordinates, not local coordinates.

Panel:LocalToScreen

Well this is a godsend.

fuck off it was for proof of concept hahahaha, but ty