I have been trying to understand how the coordinates of shaders work with SpriteKit. Below is my test program. I created a sprite and then loaded and run a shader (as a string) over that sprite. The shader should create a blue rectangle on a white background. I need the rectangle close to the sprite size (equal to 0.9 or 90% of its size) and to stay in the center of the sprite regardless of the position of the sprite on the screen. However, I observed:
Could someone explain to me the coordination of shaders when working with SpriteKit and how to fix those issues? Thanks.
import SpriteKit
class ViewController: UIViewController {
// Shader as a string
let shaderSource =
"""
// the center point of the rectangle, not really center vec2(0.0) but just for testing
constant vec2 center = vec2(0.5, 0.5);
constant vec2 rectSz = vec2(0.9, 0.9); // size of the rectangle
void main()
{
/// uv from -1 to 1 in Y height
vec2 uv = (gl_FragCoord.xy - u_sprite_size.xy * 0.5) / u_sprite_size.y;
vec2 sz = rectSz / 2.0; // half size to the center point
vec2 d = abs(uv - center);
bool inside = d.x <= sz.x && d.y <= sz.y;
// draw blue rectangle, white background
gl_FragColor = inside ? vec4(0.0, 0.0, 1.0, 1.) : vec4(1.0);
}
"""
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let sceneView = SKView(frame: view.bounds)
print("view.bounds = \(view.bounds)")
self.view.addSubview(sceneView)
let scene = SKScene(size: view.bounds.size)
sceneView.presentScene(scene)
// sprite in yellow if shader is not working
let sprite = SKSpriteNode(color: UIColor.yellow, size: CGSize(width: 300, height: 300))
sprite.position = CGPoint(x: 150, y: view.bounds.height - 150)
scene.addChild(sprite)
let spriteSize = vector_float2(Float(sprite.frame.size.width),
Float(sprite.frame.size.height))
let shader = SKShader(source: shaderSource,
uniforms: [
SKUniform(name: "u_sprite_size", vectorFloat2: spriteSize)
])
sprite.shader = shader
}
}
passing the sprite size as a uniform can be useful in some cases. but here you're actually overthinking it. if you want shader coords in a 0-1 number space flush to the size of the sprite, it's sufficient to grab your coords as vec2 st = v_tex_coord.xy;
and simply use them without scaling them. here is an example drawing a circle in two sprites of different sizes.
let shaderSource = """
constant vec2 center = vec2(0.5, 0.5);
void main() {
vec2 st = v_tex_coord.xy;
float d = distance(st,vec2(0.5)); //distance to center
vec3 color = (d > center.x/2) ? vec3(1.0) : vec3(0.0); //interpret distance as color value
gl_FragColor = vec4( color, 1.0 );
}
"""
class GameScene: SKScene {
override func didMove(to view: SKView) {
let shader = SKShader(source: shaderSource)
let sprite1 = SKSpriteNode(color: UIColor.yellow, size: CGSize(width: 300, height: 300))
sprite1.position.y = 150
sprite1.shader = shader
addChild(sprite1)
let sprite2 = SKSpriteNode(color: UIColor.red, size: CGSize(width: 100, height: 100))
sprite2.position.y = -150
sprite2.shader = shader
addChild(sprite2)
}
}