VisionPro开发 - 着色器(Shaders)及代码控制


首页:漫游Apple Vision Pro

Code Repo: github.com/xuchi16/vis...

Project Path: github.com/xuchi16/vis...


概述

本文主要包含以下内容:

  • Shader 的基本概念
  • visionOS 中的 Material,Shader,Shader Graph 等基本概念
  • 利用 SwiftUI 代码控制 Shader 的特定节点
  • 一个基本的利用代码控制 Surface Shader 的示例

最终效果:

基本概念

Shader(着色器)是一种用于图形处理的程序,在 3D 模型渲染中,用于确定最终像素的颜色、亮度、对比度等属性。Shader 程序主要运行在 GPU 上,可以高效地处理图形和图像数据。

Material 用来表示 3D 物体的材料,在 visionOS 中主要有 2 种材质:

  • PBR Material(Physically-Based Rendering) :基于物理渲染的材料
  • Custom Material:自定义材料

其中,自定义材料可以让开发者绑定不同的 Shader,从而实现更为丰富的视觉效果。

Shader Graph 是 visionOS 中表示 Shader 的对象。在 Shader Graph 中,visionOS 提供了丰富的 Shader Node 用于构建不同的 Shader。其中还有部分 Node 可以通过程序进行控制。

Shader Node 主要分为 2 类,均可在 Shader Graph 中进行操作:

  • Surface shader: 主要用于控制 PBR 属性

  • Geometry shader: 主要用于控制几何属性

目前 visionOS 提供了 186 个 Shader Node。

基本实现

Shader Graph

  1. 创建一个基本的 visionOS 项目,在 RealityKitContent 下打开 Package.realitycomposerpro

  2. 创建一种新的 Custom Material。打开底部的"Shader Graph"可以看到最初包含 2 个节点:

  • PreviewSurface:MaterialX 版本的 USD Preview Surface
  • Outputs:最终的输出节点,可以看到前置接收 2 个输入,分别对应上述的 Surface 和 Geometry 两类 Shader Node

本文希望实现一种类似上下滑动的窗帘的贴图效果:有前景、后景 2 幅图,用户可以通过一个滑块控制,随着滑块的滑动,图 1 逐渐消失变成图 2。这一效果应用在昼夜变化、历史演变等前后对比效果。

  1. 创建 2 个立方体,并且在右侧的 Inspector 中将 Material Binding 指定为刚刚新建的材料
  1. 编辑 Shader Graph
  • Position 部分:Position 节点表示当前正在进行渲染的点的位置。这里我们希望滑动的效果是从上到下,因此需要在 Position 节点后接一个 Separate3 节点,用来分离坐标,并且只取其中的 y 坐标。
  • Criteria 部分:用来控制两幅图交界的位置,y 坐标大于特定值展示一幅图,否则展示另一幅图。输入采用了 Float 节点,用于控制分界值,输入的值用 0-100 的百分比表示。另外用 Range 节点做了一层映射,将百分比转换成实际的坐标值。这里有一个关键步骤,如果想要后续通过代码控制 Float 节点数值,需要将该节点"Promote",相应地其颜色也会从淡紫色变为淡蓝色。

  • Image 部分:加入 2 个 Image 节点,分别表示 2 幅图。将需要展示的图片导入 XCode,选择 Image 节点的 Filename 即可对应上相应的图片。
  • IfGreaterOrEqual 节点:串联上前面 3 个部分,对比渲染点的 y 坐标(Position 部分)和分界线(Criteria 部分),并选择其中一幅图作为渲染结果。

完成上述步骤后,我们只需要在输入的 Float 节点"ScrollPercentage"中选择不同的输入值,就能实现基本的按照分界线渲染贴图的效果了。

代码逻辑控制

在实现通过 Float 节点控制根据 y 坐标渲染不同图片的效果后,需要通过 SwiftUI 控件控制 Float 的值,从而达到实时变化的效果。

  1. ContentView中添加 Slider,并且将控件的数值与sliderValue绑定。
swift 复制代码
@State private var sliderValue = 0.0

// ...
var body: some View {
        VStack {
            Text("Shaders")
                .font(.title)

            Toggle("Show Immersive Space", isOn: $showImmersiveSpace)
                .toggleStyle(.button)
                .padding(.top, 50)

            HStack {
                Spacer()
                Text("Slider: ")
                Slider(value: $sliderValue, in: 0...100)
                Spacer()
            }
            .padding(.top, 20)
        }
}
  1. 定义ViewModel存储 Slider 的数值状态以及需要修改的实体对象,同时提供一个更新方法,当滑块数值变化时候调用更新方法。
swift 复制代码
@Observable
class ViewModel {
    var percentage: Double = 0
    var boxes: [ModelEntity] = []
}
  1. ImmersiveView中加载场景,并且根据名称找到需要渲染的实体,将其存储到ViewModel中。
swift 复制代码
struct ImmersiveView: View {
    
    @Environment(ViewModel.self) var model
    
    @State private var sliderValue: Float = 0
    
    var body: some View {
        RealityView { content in
            // Add the initial RealityKit content
            if let scene = try? await Entity(named: "Immersive", in: realityKitContentBundle) {
                if let box1 = scene.findEntity(named: "Box1") as? ModelEntity {
                    model.boxes.append(box1)
                }
                if let box2 = scene.findEntity(named: "Box2") as? ModelEntity {
                    model.boxes.append(box2)
                }
                model.update()
                content.add(scene)
            }
        }
    }
}
  1. ViewModel中提供更新方法,根据其中存储的滑块数值,更新存储的对象 Material。
swift 复制代码
@Observable
class ViewModel {
    var percentage: Double = 0
    var boxes: [ModelEntity] = []
    
    func update() {
        for box in boxes {
            print(box.name)
            if var boxMaterial = box.model?.materials.first as? ShaderGraphMaterial {
                try? boxMaterial.setParameter(name: "ScrollPercentage", value: .float(Float(percentage)))
                box.model?.materials = [boxMaterial]
            }
        }
    }
}
  1. 在此前的ContentView中,注册事件,当滑块初次加载以及发生变化时,将数值传递给ViewModel,并且调用更新方法,更新 shader。
swift 复制代码
@State private var sliderValue = 0.0
@Environment(ViewModel.self) var model

// ...
var body: some View {
        VStack {
            Text("Shaders")
                .font(.title)

            Toggle("Show Immersive Space", isOn: $showImmersiveSpace)
                .toggleStyle(.button)
                .padding(.top, 50)

            HStack {
                Spacer()
                Text("Slider: ")
                Slider(value: $sliderValue, in: 0...100)
                Spacer()
            }
            .padding(.top, 20)
        }
        .padding()
        .onChange(of: showImmersiveSpace) { _, newValue in
            Task {
                if newValue {
                    switch await openImmersiveSpace(id: "ImmersiveSpace") {
                    case .opened:
                        immersiveSpaceIsShown = true
                    case .error, .userCancelled:
                        fallthrough
                    @unknown default:
                        immersiveSpaceIsShown = false
                        showImmersiveSpace = false
                    }
                } else if immersiveSpaceIsShown {
                    await dismissImmersiveSpace()
                    immersiveSpaceIsShown = false
                }
            }
        }
        .onChange(of: sliderValue) { _, newValue in
            model.percentage = newValue
            update()
        }
        .onAppear() {
            update()
        }
    }

这样就能达到开头展示的最终效果。

相关推荐
HarderCoder5 个月前
Apple Vision Pro 学习资料
visionos
苹果API搬运工5 个月前
试玩 RealityComposerPro 中的 Shader Graph:用圆环制作一个 Meta Logo
visionos·增强现实
苹果API搬运工5 个月前
我开源了个手势匹配框架,让你在模拟器调试 visionOS 手部追踪功能!
visionos·增强现实
苹果API搬运工5 个月前
只需三板斧!带你入门 visionOS 空间计算的数学与几何基础
visionos·增强现实
-九月新辰-6 个月前
Unity VisionOS开发流程
unity·游戏引擎·visionos
XR基地6 个月前
XR 世界导览021 | AVP App Store上线网页版、如何在AVP上快速匹配手势、AVP开发岗位
visionos
XR基地6 个月前
XR 世界导览#020-visionOS 1.1/Xcode 17.3/PICO 5.9.0 更新了!
unity3d·visionos
xChester6 个月前
visionOS 应用图标设计
前端·ios·visionos
XR基地6 个月前
XR 世界导览#019-Let's visionOS 大会/在 AVP 上玩手机或查看Spline的3D模型/用代码写ShaderGraph
unity3d·visionos
xChester7 个月前
VisionPro开发 - 轻松实现天空盒并添加光照
前端·ios·visionos