Brushstroke-Shader

Hey everybody, im learning to write shaders because i want to make a brushstroke shader.
As a first step i manage to make a fragment shader that makes a super simple cellshading look.

So my next step would be to get the alpha of my brush-texture on the different areas/colors. in this case for now just on the yellow maybe.
But it should not wrap around the form or be applied onto the 3d Mesh like i could do it with the regular UVs.
It needs to be 2D screenspace so to say. But also it should move with the mesh if the mesh/cam moves.

Later on i want to try if its possible to make some kind of brush-movement… but for now i just want to apply the texture as 2D on the right place…

Any ideas on how to solve this? :slight_smile:

What you’re looking to do is really, really hard. It’s easy enough to have the texture projected onto the surface in 2D screen space, but much harder to have it move with the object nicely. The simplest solution is to use 2D screen space offset by the object’s pivot position in screen space.
There’s a conversation on doing this kind of thing here, with a couple of different examples:

And here’s an example shader.

Shader "Unlit/Camera Facing UVs"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _ScreenSpaceTex ("Screen Space", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "DisableBatching"="True" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                float4 screenUV : TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            sampler2D _ScreenSpaceTex;
            float4 _ScreenSpaceTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                o.screenUV = o.pos;

                // get object's clip space position
                float4 offset = UnityObjectToClipPos(float4(0.0, 0.0, 0.0, 1.0));

                // offset screenUV by object's position
                o.screenUV.xy -= offset.xy / offset.w * o.pos.w;

                // adjust UVs to be 0.0 to 1.0 vertically and correct for aspect ratio
                o.screenUV.xy *= 0.5;
                o.screenUV.x *= _ScreenParams.x / _ScreenParams.y;
                o.screenUV.xy += o.screenUV.w * 0.5;

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                fixed4 screentex = tex2D(_ScreenSpaceTex, TRANSFORM_TEX((i.screenUV.xy / i.screenUV.w), _ScreenSpaceTex));
                return col * screentex;
            }
            ENDCG
        }
    }
}
1 Like

@bgolus thank you! i will play around with this.
maybe there is other approaches …what i definetly want to avoid is that the brushtexture is repeated like in the image below → i.e. on the cube on every face… the yellow areas should melt together, but the edges should be manipulated in a way. (just using the regular uvs in this image)

I worked on that very problem, so make it quick:

  • open a photoshop equivalent.
  • Duplicate the image onto a layer 4 times
  • move each duplicate 2 pixel on each cardinal direction
  • apply on the duplicate the blending mode lighter color or darker color

that’s will show you a prototype of the effect. with a cross pattern.

How to generalize?
Well you need to sample at each pixel, every pixel of the brush, for the one that define the brush shape, you compare it to the color, you keep it if it’s lighter, darker or whatever test you devise. It will give you a brush look. You can probably also blend the final color if your brush is not binary. You can also just threshold based on parameter to avoid it applied everywhere.

It’s a post process
You can render extra data per pixel to control the brush (like sampling radius). TO have brush only affect edge, you simply sample a sobel rendering of the scene and only keep color that detect a edge, that is only pixel around the edge will “brush”.

How it work?
It’s basically a filter, it’s like a DOF but the circle of confusion is of the brush shape. The effect work in the same way sobel filter work, it emphasize difference.

It’s been used to make watercolor effect by Princess on tigsource, with a small radius brush (no duplicated mesh)

More





Look at full size

I should have kept that for my game lol

1 Like

@bgolus i tried it with the link you mentioned → the magic hat thingy. It does work it moves with the mesh, not 100% but very good to start with.

@neoshaman thanks for this. im currently trying to make it work.

another idea i had about this is… since i have the cellshading derrived from the light i exactly know where the edge is, because i set it. can i not apply the brushtexture along the edge somehow ?
a sobel filter will take anything into account but so far it might be enough to just deal with the light.

It depend, do you want it to “bleed” outside the mesh?

If no,
you can try to just use a texture sample as usual but using the screen UV instead of the mesh UV, combine it with the ndotl, then threshold as usual for cel shading.

If yes,
You can use the same technique, but also with an inverted shell, where instead of mixing down color, you discard like with alpha, since it’s screen space it will be continuous with the mesh screen space texture

Alternative, you can get inspired by that
https://i.pinimg.com/originals/0b/bf/7f/0bbf7f3f68efa1631db44769f8d919dc.gif (doesn’t want to display for some reason, so raw link)

But in order to have internal bleed on teh mesh, just use a fresnel with the screen space texture masked at the edges, and discard pixel like alpha, it will simulate bleed of the ink inside the mesh.

Now I tend to avoid screen space texture due to swimming effect, I haven’t investigate way to bypass it (object position and rotation offsetting of the screen UV, triplanar test, etc …).

1 Like