1

I want to add drop shadow effect for a SKShapeNode. I found a Emboss shader here.

Here is my code:

let node = SKShapeNode(rectOf: CGSize(width: w, height: h),cornerRadius: w / 4.0)
node.position = CGPoint(x: x, y: frame.size.height + h / 2)
node.fillColor = color
node.strokeColor = .clear
node.fillShader = createColorEmboss()
let v = simd_make_float2(Float(w),Float(h))
node.setValue(SKAttributeValue(vectorFloat2: v), forAttribute: "a_size")

func createColorEmboss() -> SKShader {
    let source = "void main() {" +
        "vec4 current_color = SKDefaultShading();" +
        "if (current_color.a > 0.0) {" +
            "vec2 pixel_size = 1.0 / a_size;" +
            "vec4 new_color = current_color;" +
            "new_color += texture2D(u_texture, v_tex_coord + pixel_size) * u_strength;" +
            "new_color -= texture2D(u_texture, v_tex_coord - pixel_size) * u_strength;" +
            "gl_FragColor = vec4(new_color.rgb, 1) * current_color.a * v_color_mix.a;" +
        "} else {" +
            "gl_FragColor = current_color;" +
        "}" +
    "}"
    let shader = SKShader(source: source, uniforms: [SKUniform(name: "u_strength", float: 1)])
    shader.attributes = [SKAttribute(name: "a_size", type: .vectorFloat2)]
    return shader  
  }

But nothing happened. Any idea?

Bagusflyer
  • 12,675
  • 21
  • 96
  • 179
  • I haven't tried running your code, but from just looking at it I notice that you haven't set the shader attribute's value. It appears that it's supposed to be set to the node's size. So at a minimum you'd need something along the lines of `node.setValue(SKAttributeValue(vectorFloat2: ...), forAttribute: "a_size")` – bg2b Dec 20 '19 at 12:13
  • Still doesn't work after I add this. – Bagusflyer Dec 20 '19 at 14:06
  • Looking again, you haven't set fillTexture either. Probably u_texture in the shader is nothing. It might be easier to get a feel for things if you're just getting started with shaders by trying an SKSpriteNode made from a texture. Shader debugging can be an exercise in pulling your hair out, and I don't have the experience using them with SKShapeNodes to tell you exactly what you're missing. – bg2b Dec 20 '19 at 14:38

1 Answers1

1

There are some big differences between a textured (*) SKShapeNode and SKSpriteNode here:

  • If the SKSpriteNode has a texture and a custom shader (shader property), then the shader will have access to the original texture via the u_texture uniform, and it will be able use it for rendering the node, i.e. by applying various effects to it like emboss, blur, etc.
  • SKShapeNode may have a fillTexture and a fillShader, but the way they work is different:
    • fillTexture is a rectangular texture that's converted to grayscale and used to set the luminosity of fillColor in the areas of the shape node which are filled.
    • fillShader can only see fillTexture as this rectangular image before it gets used for the final render, with no way to modify, or even see, which areas of the rectangle will be visible (part of the fill), and how the final render will look.
    • If there is no explicit fillTexture set, the u_texture uniform seems to pretend that it's an endless expanse of whiteness: whatever coordinate you ask about, it will return the color white, even if you go out of its bounds.

(*) You can also create a SKSpriteNode with just a color; its behavior will be a weird mix of SKShapeNode and a textured SKSpriteNode, and it's not helpful for our discussion, so I'll be ignoring this kind.


What you can do is to rasterize your SKShapeNode into a texture, and create an SKSpriteNode from it. It's actually very simple:

let node = SKShapeNode(rectOf: CGSize(width: w, height: h),cornerRadius: w / 4.0)
node.fillColor = color
node.strokeColor = .clear

let theTexture = view.texture(from:node)
let spriteNode = SKSpriteNode(texture:theTexture)

spriteNode.position = CGPoint(x: x, y: frame.size.height + h / 2)
spriteNode.shader = createColorEmboss()
let v = simd_make_float2(Float(w),Float(h))
spriteNode.setValue(SKAttributeValue(vectorFloat2: v), forAttribute: "a_size")

As you can see, you don't even have to add the node to the scene; the view will render it into a texture, with a clear background. You can also use view.texture(from:crop:) to specify a larger crop that can accommodate a drop shadow if it extends beyond the original shape.

I must warn you, however, that this particular shader you are trying to use may not be what you need for a drop shadow…

pua666
  • 326
  • 1
  • 7