SpriteKit中SKShader坐标机制解析及问题修复求助
问题
我一直在尝试理解SpriteKit中着色器(Shader)的坐标工作机制。以下是我的测试程序:创建了一个精灵(Sprite),然后加载并运行一段字符串形式的着色器,该着色器应在白色背景上绘制一个蓝色矩形。我需要这个矩形大小为精灵的90%,且无论精灵在屏幕上的位置如何,都始终位于精灵中心。
但我观察到两个问题:
- 蓝色矩形明显小于精灵;
- 蓝色矩形似乎固定在屏幕上,不会随精灵移动,当精灵移动到其他位置时,矩形可能会移出精灵区域。我猜测这是使用
gl_FragCoord导致的,但将其替换为v_tex_coor后也无效。
能否有人为我解释SpriteKit中着色器的坐标机制,并告诉我如何解决这些问题?
测试代码
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 } }
问题截图

解答
SpriteKit着色器坐标机制说明
SpriteKit中常用的着色器坐标有两种核心类型:
gl_FragCoord:屏幕空间绝对坐标,原点在屏幕左下角,数值对应像素位置。它和精灵的位置、变换无关,只对应像素在屏幕上的固定位置,这就是你之前矩形固定不动的原因。v_tex_coord:精灵的纹理相对坐标,范围是(0,0)到(1,1),原点在精灵左下角,(1,1)对应精灵右上角。这个坐标完全绑定精灵自身,无论精灵移动、缩放还是旋转,它的范围始终和精灵区域对应,是实现精灵内相对图形的最佳选择。
你之前替换v_tex_coord后无效,是因为没有基于该坐标的特性做正确计算。
问题修复方案
要实现随精灵移动、占精灵90%大小的居中蓝色矩形,我们基于v_tex_coord重构着色器逻辑:
- 将纹理坐标转换为以精灵中心为原点的坐标(范围
-0.5到0.5) - 基于相对比例判断像素是否在目标矩形内
修改后的着色器代码:
constant vec2 rectSz = vec2(0.9, 0.9); // 矩形占精灵的比例 void main() { // 转换为以精灵中心为原点的坐标,范围[-0.5, 0.5] vec2 uv = v_tex_coord - vec2(0.5); // 计算矩形的半尺寸(基于精灵比例) vec2 halfRectSize = rectSz / 2.0; // 判断当前像素是否在矩形内 bool inside = abs(uv.x) <= halfRectSize.x && abs(uv.y) <= halfRectSize.y; // 绘制蓝色矩形,白色背景 gl_FragColor = inside ? vec4(0.0, 0.0, 1.0, 1.0) : vec4(1.0); }
同时可以移除原代码中的u_sprite_size uniform,因为现在完全基于相对比例计算,不需要依赖精灵的绝对尺寸。Swift代码中着色器初始化部分简化为:
let shader = SKShader(source: shaderSource) sprite.shader = shader
修复效果
- 蓝色矩形严格占精灵的90%大小,比例精准
- 矩形始终居中于精灵,随精灵的移动、缩放、旋转同步变换
- 代码更通用,无需硬编码精灵尺寸
内容的提问来源于stack exchange,提问作者Tony
相关产品推荐
相关产品推荐

