You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpriteKit中SKShader坐标机制解析及问题修复求助

问题

我一直在尝试理解SpriteKit中着色器(Shader)的坐标工作机制。以下是我的测试程序:创建了一个精灵(Sprite),然后加载并运行一段字符串形式的着色器,该着色器应在白色背景上绘制一个蓝色矩形。我需要这个矩形大小为精灵的90%,且无论精灵在屏幕上的位置如何,都始终位于精灵中心。

但我观察到两个问题:

  1. 蓝色矩形明显小于精灵;
  2. 蓝色矩形似乎固定在屏幕上,不会随精灵移动,当精灵移动到其他位置时,矩形可能会移出精灵区域。我猜测这是使用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
    }
}

问题截图

iPhone 14 Pro Max模拟器截图


解答

SpriteKit着色器坐标机制说明

SpriteKit中常用的着色器坐标有两种核心类型:

  • gl_FragCoord:屏幕空间绝对坐标,原点在屏幕左下角,数值对应像素位置。它和精灵的位置、变换无关,只对应像素在屏幕上的固定位置,这就是你之前矩形固定不动的原因。
  • v_tex_coord:精灵的纹理相对坐标,范围是(0,0)到(1,1),原点在精灵左下角,(1,1)对应精灵右上角。这个坐标完全绑定精灵自身,无论精灵移动、缩放还是旋转,它的范围始终和精灵区域对应,是实现精灵内相对图形的最佳选择。

你之前替换v_tex_coord后无效,是因为没有基于该坐标的特性做正确计算。

问题修复方案

要实现随精灵移动、占精灵90%大小的居中蓝色矩形,我们基于v_tex_coord重构着色器逻辑:

  1. 将纹理坐标转换为以精灵中心为原点的坐标(范围-0.5到0.5)
  2. 基于相对比例判断像素是否在目标矩形内

修改后的着色器代码:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 13:07:49