如何在片段着色器(SpriteKit)中绘制纹理的特定部分

How to draw specific part of texture in fragment shader (SpriteKit)

我的节点大小为 64x32,纹理大小为 192x192,我试图在第一个节点绘制该纹理的第一部分,在第二个节点绘制第二部分...

片段着色器(贴图贴图大小为64x32的SKSpriteNode)

void main() {
    float bX = 64.0 / 192.0 * (offset.x + 1);
    float aX = 64.0 / 192.0 * (offset.x );
    float bY = 32.0 / 192.0 * (offset.y + 1);
    float aY = 32.0 / 192.0 * (offset.y);
    float normalizedX = (bX - aX) * v_tex_coord.x + aX;
    float normalizedY = (bY - aY) * v_tex_coord.y + aY;
    gl_FragColor = texture2D(u_temp, vec2(normalizedX, normalizedY));
}

但是结果好像不对:

带有附加纹理的 SKSpriteNode

没有纹理的SKSpriteNode(我想用纹理实现的)

当纹理在 altas 中时,它不再由从 (0,0)(1,1) 的坐标寻址。图集实际上是一个在幕后组装的大型纹理。当您在普通精灵中使用图集中的特定命名图像时,SpriteKit 会在有关图集如何组装的信息中查找该图像名称,然后告诉 GPU 类似“使用 bigAtlasTexture 绘制此精灵,坐标 (0.1632,0.8814)(0.1778, 0.9143)”。如果您打算使用相同的纹理编写自定义着色器,则需要有关它在图集内的位置的信息,您可以从 textureRect:

https://developer.apple.com/documentation/spritekit/sktexture/1519707-texturerect

所以你的纹理实际上不是一个图像,而是由大量纹理打包的大图像中的一个位置 textureRect() 定义的。我发现用 (0,0)(1,1) 来思考是最简单的,所以在编写着色器时,我通常会做 textureRect => 减法和缩放以获得 (0,0)-(1,1) => 计算所需的修改坐标 => 缩放并添加以再次到达 textureRect => texture2D 查找。

由于您的着色器需要了解 textureRect 但您不能从着色器代码中调用它,因此您有两个选择:

  1. 制作一个属性或制服来保存该信息,从外部填充它,然后让着色器引用它。
  2. 如果着色器仅用于特定纹理或少数纹理,那么您可以生成专门用于所需 textureRect 的着色器代码,即它只是在代码中有一些常量用于纹理。

这是使用方法 #2 的示例的一部分:

func myShader(forTexture texture: SKTexture) -> SKShader {
  // Be careful not to assume that the texture has v_tex_coord ranging in (0, 0) to
  // (1, 1)!  If the texture is part of a texture atlas, this is not true.  I could
  // make another attribute or uniform to pass in the textureRect info, but since I
  // only use this with a particular texture, I just pass in the texture and compile
  // in the required v_tex_coord transformations for that texture.
  let rect = texture.textureRect()
  let shaderSource = """
  void main() {
    // Normalize coordinates to (0,0)-(1,1)
    v_tex_coord -= vec2(\(rect.origin.x), \(rect.origin.y));
    v_tex_coord *= vec2(\(1 / rect.size.width), \(1 / rect.size.height));
    // Update the coordinates in whatever way here...
    // v_tex_coord = desired_transform(v_tex_coord)
    // And then go back to the actual coordinates for the real texture
    v_tex_coord *= vec2(\(rect.size.width), \(rect.size.height));
    v_tex_coord += vec2(\(rect.origin.x), \(rect.origin.y));
    gl_FragColor = texture2D(u_texture, v_tex_coord);
  }
  """
  let shader = SKShader(source: shaderSource)
  return shader
}

这是此处一些具体示例的精简版: https://github.com/bg2b/RockRats/blob/master/Asteroids/Hyperspace.swift