Satin - 一个基于 Apple Metal 构建的 3D 图形框架

build status Swift Package Manager (SPM) compatible

关于 👋

Satin 是一个 3D 图形框架 (灵感来源于 threejs),旨在帮助设计师和开发者使用 Apple 的 Metal API。 Satin 提供了有用的类来创建网格、材质、缓冲区、Uniform、几何体、渲染管线(着色器)、计算内核等等。 Satin 的 API 正在快速发展,因此在生产环境中使用时最好坚持使用带有标签的版本或 git 提交。

Satin 使简单的图形任务变得有趣且易于快速完成,并使复杂的图形任务更容易完成,而无需编写大量的样板代码。它通过提供结构、观点以及大量有用的 Metal 抽象来实现这一点,以帮助您在几分钟内启动并运行/编码。 Satin 主要基于 Swift,但是,在执行昂贵的 CPU 操作时,Satin 使用 SatinCore,它是用 C 编写的 (用于几何体生成、三角剖分、边界和计算几何计算等任务),以确保速度尽可能快。 话虽如此,如果您正在寻找使用 Metal 渲染内容的最有效方法,请查看 Metal-cpp 或直接通过 Objective-C 使用 Metal。

Satin 已停止积极开发。

示例 ✨

要求 ⚙️

  • macOS 10.15。
  • Xcode 11.0。
  • Swift 5.0。

支持的平台 💻 📱 📺

  • macOS 10.15。
  • iOS 13.0。
  • tvOS 13.0。

安装

Swift 包管理器

Swift 包管理器 是一个用于自动分发 Swift 代码的工具,并且已集成到 Swift 编译器中。 一旦你设置好了你的 Swift 包,添加 Satin 作为依赖项就像把它添加到你的 Package.swiftdependencies 值中一样简单。

  dependencies: [
      .package(url: "https://github.com/Hi-Rez/Satin.git", .branch("master"))
  ]

特性 📋

  • 大量的示例展示了如何使用 API(2D、3D、光线投射、计算、导出、实时编码、AR 等)。
  • Object、Mesh、InstancedMesh、Material、Shader、Geometry 和 Renderer 类。
  • PBR Standard & Physical Materials(基于 Disney 的 PBR 实现)
  • 你可以实时编码着色器 🔥。
  • 几个内置的材质(BasicColor、BasicTexture、BasicDiffuse、Normal、UV Color、Skybox、MatCap、PBR Standard、PBR Physical 等)。
  • 大量的几何体(Box、Sphere、IcoSphere、Circle、Cone、Quad、Plane、Capsule、RoundedRect、Text 等)。
  • 相机(Orthographic、Perspective)。
  • 2D & 3D 相机控制器。
  • 通过 Buffers 和 Uniforms 的参数进行运行时和动态结构创建。
  • Metal 着色器编译器(在实时编码时很有用),用于 LiveShader(用于 LiveMaterial 中)。
  • 用于快速处理数据的 Buffer & Texture 计算系统。
  • BRDF LUT 的生成器,基于图像的光照(HDR -> 镜面反射和漫反射 IBL 纹理)
  • 通过包围盒层次结构进行快速光线投射(对于查看您点击或轻按的内容非常有用)。
  • 通过 Mesh 的 preDraw、Material 的 onBind、Buffer & Texture Computes 的 preCompute 等等来进行自定义 Metal 渲染的钩子
  • 通过 Renderable 协议进行自定义 Renderable 的钩子
  • FileWatcher 用于检查资源或着色器文件是否已更改。
  • 大量的示例展示了如何使用 API。
  • 展示如何使用 Satin & ARKit 的示例
  • 基本方向阴影

用法 🚀

Satin 帮助使用 Metal 绘制东西。为了快速启动并运行,而无需大量的样板代码并且无需担心三重缓冲或事件(设置、更新、调整大小、键盘、鼠标、触摸)回调,Satin 与 Forge 配合使用效果很好,但也可以在没有它的情况下使用。下面的示例展示了如何一起使用 Forge 和 Satin 来渲染一个颜色变化的盒子,该盒子会看向场景中的一个移动点。

简单示例

import SwiftUI
import MetalKit

import Forge
import Satin

// Subclass Forge's Renderer to get triple buffered rendering and
// callbacks for Setup, Update, Draw, Resize and Events
class SimpleRenderer: Forge.Renderer {
    // A Context contains important information that is needed to help compile shaders
    // and ensure we are drawing with the right color and depth pixel formats and sample count

    // Forge's Renderer class provides a MTLDevice and convenience getters for the view's color pixel format,
    // depth pixel format and stencil pixel format, by default a Forge Renderer has depth
    lazy var context = Context(device, sampleCount, colorPixelFormat, depthPixelFormat, stencilPixelFormat)

    // A Satin Renderer handles setting the Content on all the objects in the scene graph
    // and drawing the scene either to a texture or on screen

    // Create a Satin Renderer by passing in a context, scene and camera
    lazy var renderer = Satin.Renderer(context: context)

    // A PerspectiveCamera is used to render the scene using perspective projection
    // All Satin Cameras inherit from Object, so it has
    lazy var camera = PerspectiveCamera(position: [3.0, 3.0, 3.0], near: 0.01, far: 100.0, fov: 45)

    // An Object is just an empty node in Satin's Scene Graph, it can have children and a parent
    // Objects have a position, orientation, scale and label
    lazy var scene: Object = Object("Scene", [boxMesh])

    // Meshes inherit from Object, so they have all the properties an object has.
    // A Mesh has unique properties like geometry, material and rendering properties
    // To create renderable object aka a Mesh, you passing it a Geometry and Material like so
    var boxMesh = Mesh(geometry: BoxGeometry(size: 1.0), material: BasicDiffuseMaterial(0.75))

    // Create a time variable so we can change things in our scene over time
    var time: Float = 0.0

    // Forge calls setup once after it has a valid MTKView (mtkView)
    override func setup() {
        camera.lookAt(.zero)
        // There are many properties you can set on the renderer, this is how to clear to white
        renderer.setClearColor(.one)
    }

    // Forge calls update whenever a new frame is ready to be updated, make scene changes here
    override func update() {
        // We increment our time variable so we can procedurally set the box mesh's orientation and material color
        time += 0.05
        let sx = sin(time)
        let sy = cos(time)

        // Setting a material property done by using the set function, this modifies the material's uniforms
        boxMesh.material?.set("Color", [abs(sx), abs(sy), abs(sx + sy), 1.0])

        // You can manually an object's position, orientation, scale, and localMatrix. Here I'm using a
        // convenience lookAt function to orient the box to face the point passed from its current position
        boxMesh.lookAt([sx, sy, 2.0])
    }

    // Forge calls draw when a new frame is ready to be encoded for drawing
    override func draw(_ view: MTKView, _ commandBuffer: MTLCommandBuffer) {
        guard let renderPassDescriptor = view.currentRenderPassDescriptor else { return }

        // To render a scene into a render pass, just call draw and pass in the render pass descriptor
        // You can also specify a render target and render to a texture instead
        renderer.draw(
            renderPassDescriptor: renderPassDescriptor,
            commandBuffer: commandBuffer,
            scene: scene,
            camera: camera
        )
    }

    // Forge calls resize whenever the view is resized
    override func resize(_ size: (width: Float, height: Float)) {
        // our camera's aspect ratio is set
        camera.aspect = size.width / size.height

        // our renderer's viewport & texture sizes are set
        renderer.resize(size)
        // if you need to render to a custom viewport, you can specify that after the resize call:
        // renderer.viewport = MTLViewport(...)
    }
}

// Using SwiftUI you can use a ForgeView to easily create a MTKView and pass in a Forge.Renderer
struct ContentView: View {
    var body: some View {
        ForgeView(renderer: SimpleRenderer())
    }
}

致谢 😅

许可证 🎓

Satin 在 MIT 许可证下发布。 有关详细信息,请参见 LICENSE