Skip to content

Fabric-Project/Satin

 
 

Repository files navigation

Satin - A 3D Graphics Framework built on Apple's Metal

Swift Package Manager (SPM) compatible

About

Satin is a 3D graphics framework (inspired by threejs) that helps designers and developers work with Apple's Metal API. Satin provides helpful classes for creating meshes, materials, buffers, uniforms, geometries, pipelines (shaders), compute kernels, and more.

Satin makes simple graphics tasks fun and easy to accomplish quickly and complex graphics tasks easier to accomplish without having to write tons of boilerplate code. It does this by providing structure, opinions, and tons of helpful abstractions on Metal to help you get up and rendering / coding in a few minutes.

Satin is mostly Swift based, however when performing expensive CPU operations, Satin uses SatinCore, which is written in C (for tasks like geometry generation, triangulation, bounds & computational geometry calculations, and more) to make sure things are as fast as possible.

Supported Platforms

  • macOS 15.0+
  • iOS 18.0+
  • visionOS 2.0+

Installation

Swift Package Manager

Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into the Swift compiler. Once you have your Swift package set up, adding Satin as a dependency is as easy as adding it to the dependencies value of your Package.swift.

  dependencies: [
      .package(url: "https://github.com/Fabric-Project/Satin.git", .branch("main"))
  ]

Features

Satin supports modern techniques, like deferred rendering, post processing, instancing, lighting with soft shadows (and projection) model loading and more.

Sponza Scene with lighting, projection

Depth of FieldProcedural GeometryProcedural GeometryLighting and ShadingLighting and ShadingLighting and ShadingLighting and ShadingPBR Materials and LightingSlug Text Rendering

  • Tons of examples that show how to use the API (2D, 3D, Raycasting, Compute, Exporting, Live Coding, AR, etc).
  • Object, Mesh, InstancedMesh, TessellationMesh, Material, Shader, Geometry, Camera and Renderer classes.
  • Builtin Materials (BasicColor, BasicTexture, BasicDiffuse, Normal, UV Color, Skybox, MatCap and more).
  • PBR Support via Standard & Advanced Physical Materials (Based on Disney's PBR Implementation)
  • You can live code shaders.
  • Support for Forward, Forward + and Deferred rendering modes with support for multiple render targets.
  • Built in Post Processing Techniques (Motion Blur, Depth of Field and Post Processor base class)
  • Tons of Geometries (Box, Sphere, IcoSphere, Circle, Cone, Quad, Plane, Capsule, RoundedRect, Text, and more).
  • Cameras (Orthographic, Perspective) & Camera Controllers.
  • Slug Text Rendering
  • Flexible Vertex Structure
  • Run-time & Dynamic Struct creation via Parameters for Buffers and Uniforms.
  • Metal Shader Compiler (useful when live coding, using #include during runtime)
  • Metal Pipeline Caches (Render & Compute)
  • Buffer & Texture Compute Systems make running compute kernels a breeze.
  • Generators for BRDF LUT, Image Based Lighting (HDR -> Specular & Diffuse IBL Textures)
  • Fast raycasting via Bounding Volume Hierachies (very helpful to see what you clicked or tapped on).
  • Hooks for custom Metal rendering via Mesh's preDraw, Material's onBind, Buffer & Texture Computes' preCompute, etc
  • Hooks for custom rendering via the Renderable protocol
  • FileWatcher for checking if a resource or shader file has changed.
  • Platform specific examples for iOS / AR Kit integration

Usage

Satin helps to draw things with Metal. To get up and running quickly without tons of boilerplate code and worrying about triple buffering or event (setup, update, resize, key, mouse, touch) callbacks, The example below shows how to use Satin to render a color changing box that looks at a moving point in the scene.

Simple Example:

import Metal
import SwiftUI
import Satin

  // Subclass Satin's ViewRenderer to get triple-buffered rendering and
  // callbacks for setup, update, draw, resize, and input events
  final class SimpleRenderer: ViewRenderer {
      // A RenderEncoder handles drawing a scene either to the screen
      // or into a texture-backed render pass
      lazy var renderEncoder = RenderEncoder(context: defaultContext)

      // A PerspectiveCamera renders the scene using perspective projection
      // Cameras inherit from Object, so they have transform properties too
      lazy var camera: PerspectiveCamera = {
          let camera = PerspectiveCamera(
              context: defaultContext,
              position: [3.0, 3.0, 3.0],
              near: 0.01,
              far: 100.0,
              fov: 45.0
          )
          camera.lookAt(target: .zero)
          return camera
      }()

      // An Object is an empty node in Satin's scene graph
      // It can have children, a parent, and a transform
      lazy var scene = Object(context: defaultContext, label: "Scene", [boxMesh])

      // Meshes inherit from Object, so they also have transforms
      // A Mesh becomes renderable by pairing Geometry with a Material
      lazy var boxMesh = Mesh(
          context: defaultContext,
          label: "Box",
          geometry: BoxGeometry(context: defaultContext, size: 1.0),
          material: BasicDiffuseMaterial(context: defaultContext, hardness: 0.75)
      )

      // Track time so we can animate the scene
      var time: Float = 0.0

      // Satin calls setup once after the renderer has a valid MetalView
      override func setup() {
          renderEncoder.setClearColor(.one)
      }

      // Satin calls update once per frame before drawing
      override func update() {
          // Advance time so we can animate the box orientation and color
          time += 0.05
          let sx = sin(time)
          let sy = cos(time)

          // Setting a material property updates the material's uniforms
          boxMesh.material?.set("Color", [abs(sx), abs(sy), abs(sx + sy), 1.0])

          // Objects can update their transform directly, or use helpers like
          // lookAt to orient themselves toward a target point
          boxMesh.lookAt([sx, sy, 2.0])
      }

      // Satin calls draw when a new frame is ready to be encoded
      override func draw(renderPassDescriptor: MTLRenderPassDescriptor, commandBuffer: MTLCommandBuffer) {
          // To render a scene into a render pass, call draw and pass
          // in the render pass descriptor, command buffer, scene, and camera
          renderEncoder.draw(
              renderPassDescriptor: renderPassDescriptor,
              commandBuffer: commandBuffer,
              scene: scene,
              camera: camera
          )
      }

      // Satin calls resize whenever the drawable size changes
      override func resize(size: (width: Float, height: Float), scaleFactor: Float) {
          // Update the camera aspect ratio
          camera.aspect = size.width / size.height

          // Update the renderer's viewport and internal textures
          renderEncoder.resize(size)
      }
  }

  struct ContentView: View {
      var body: some View {
          SatinMetalView(renderer: SimpleRenderer())
      }
  }

Credits

Satin was created by Reza Ali with contributions & feedback from Haris Ali and Taylor Holliday

Satin is now forked and maintained by the Fabric Project, led by Anton Marini

License

Satin is released under the MIT license.

About

A 3D Graphics Framework built on Apple's Metal

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages

  • Swift 76.5%
  • Metal 14.6%
  • Objective-C++ 7.7%
  • Other 1.2%