SceneKit 在立方体测试中的性能

2023-12-21

在学习游戏 3D 图形编程时,我决定从使用 Scene Kit 3D API 开始简单。我的第一个游戏目标是构建一个非常简化的《我的世界》的模仿版。一个只有立方体的游戏——这有多难。

下面是我编写的一个循环,用于放置 100 x 100 立方体 (10,000) 的游乐设施,但 FPS 性能非常糟糕(~20 FPS)。我最初的游戏目标对于 Scene Kit 来说是否太大了,还是有更好的方法来实现这个目标?

我已经阅读了 StackExchange 上的其他主题,但觉得它们没有回答我的问题。将暴露的表面块转换为单个网格将不起作用,因为 SCNGeometry 是不可变的。

func createBoxArray(scene : SCNScene, lengthCount: Int, depthCount: Int) {
    let startX : CGFloat = -(CGFloat(lengthCount) * CUBE_SIZE) + (CGFloat(lengthCount) * CUBE_MARGIN) / 2.0
    let startY : CGFloat = 0.0
    let startZ : CGFloat = -(CGFloat(lengthCount) * CUBE_SIZE) + (CGFloat(lengthCount) * CUBE_MARGIN) / 2.0

    var currentZ : CGFloat = startZ

    for z in 0 ..< depthCount {
        currentZ += CUBE_SIZE + CUBE_MARGIN

        var currentX = startX
        for x in 0 ..< lengthCount {
            currentX += CUBE_SIZE + CUBE_MARGIN

            createBox(scene, x: currentX, y: startY, z: currentZ)
        }
    }
}


func createBox(scene : SCNScene, x: CGFloat, y: CGFloat, z: CGFloat) {
    var box = SCNBox(width: CUBE_SIZE, height: CUBE_SIZE, length: CUBE_SIZE, chamferRadius: 0.0)
    box.firstMaterial?.diffuse.contents = NSColor.purpleColor()

    var boxNode = SCNNode(geometry: box)
    boxNode.position = SCNVector3Make(x, y, z)
    scene.rootNode.addChildNode(boxNode)
}

2014 年 12 月 30 日更新:我修改了代码,因此 SCNBoxNode 创建一次,然后通过以下方式创建 100 x 100 数组中的每个附加框:

var newBoxNode = firstBoxNode.clone()
newBoxNode.position = SCNVector3Make(x, y, z)

此更改似乎将 FPS 提高到了约 30 fps。其他统计信息如下(来自SCNView中显示的统计信息):

10K(我认为这是绘制调用?) 120K(我认为这是面孔) 360K(假设这是顶点数)

大部分运行循环都在渲染中(我估计 98%)。总循环时间为 26.7 毫秒(哎呀)。我在 2013 年末推出的 Mac Pro(6 核,双 D500 GPU)上运行。

鉴于《我的世界》风格的游戏的景观会根据玩家的动作不断变化,我不知道如何在场景套件的范围内对其进行优化。非常令人失望,因为我真的很喜欢这个框架。我很想听听有人对如何解决这个问题的想法 - 否则,我将被迫使用 OpenGL。

2014 年 12 月 30 日下午 2:00 更新(东部时间):使用 flattenedClone() 时,我发现性能显着提高。即使有更多的框和两次绘图调用,FPS 现在也能保持稳定的 60 fps。然而,适应动态环境(如 MineCraft 支持)仍然存在问题 - 见下文。

由于数组会随着时间的推移而改变组成,因此我添加了一个 keyDown 处理程序,以将更大的框数组添加到现有数组中,并计时添加框数组导致更多调用与添加为 flattenedClone 之间的差异。这是我发现的:

在 keyDown 上,我添加了另一个 120 x 120 个盒子的数组(14,400 个盒子)

// This took .0070333 milliseconds
scene?.rootNode.addChildNode(boxArrayNode)
// This took .02896785 milliseconds
scene?.rootNode.addChildNode(boxArrayNode.flattenedClone())

再次调用 flattenedClone() 比添加数组慢 4 倍。

这导致两次绘图调用具有 293K 个面和 878K 个顶点。我仍在玩这个,如果我发现任何新的东西,我会更新。最重要的是,通过我的额外测试,我仍然觉得 Scene Kit 的不可变几何约束意味着我无法利用该框架。


为什么会出现丢帧现象?

2022 年 9 月 4 日

自从你问这个问题以来已经过去了将近 8 年,但没有太大变化......

1. 多边形的数量

SceneKit 或 RealityKit 场景中的多边形数量不得超过100,000 https://developer.apple.com/documentation/arkit/adding_visual_effects_in_ar_quick_look_and_realitykit?language=objc三角形多边形。能够更快地渲染所有模型的理想 SceneKit 场景应包含少于 50,000 个多边形。您的场景包含 120,000 个多边形。不要忘记 SceneKit 使用单线程渲染模型(与多线程 RealityKit 渲染器不同)。


2. 着色器

在 Xcode 14.0+ 中,SceneKit 的默认设置.lightingModelMaterial Inspector(UI 版本)中任何 3D 库的基元集是.physicallyBased材料。这是计算量最大的着色器。程序化版本.lightingModel https://stackoverflow.com/questions/56828648/how-to-add-material-to-modelentity-programatically-in-realitykit/56849454#56849454对于任何 SCN 程序几何体是.blinn着色模型。计算强度最小的着色器是.constant(它不依赖于照明)。


3. 平截头体内部有什么

如果所有 10,000 个立方体都位于 SceneKit 相机视锥体内,则帧速率将为 20-30 fps。但如果您在立方体矩阵中移动并且看到的不超过其九分之一,则帧速率将为 60 fps。因此,SceneKit 不会渲染那些超出范围的对象。视锥体的边界 https://stackoverflow.com/questions/54894108/placing-an-object-30-meters-above-a-person/54901044#54901044.


4. SCNScene中的网格数量

每个模型网格都会导致一次绘制调用。要实现 60 fps,每次绘制调用的时间应为 16 毫秒或更短。为了获得最佳性能,Apple 工程师建议限制网格中的网格数量.usdz文件到 50 左右。不幸的是,我没有找到一个值.scn官方文档中的文件。


5. 灯光和阴影

照明和阴影(尤其是阴影)是计算量非常大的任务。一般建议如下 - 避免使用.forward https://developer.apple.com/documentation/scenekit/scnshadowmode阴影和带有假阴影的高分辨率纹理。

Look at 这个帖子 https://stackoverflow.com/questions/72038078/scenekit-maximum-size-for-scn-assets-in-one-app/72053804#72053804了解详情。

用于测试的 SwiftUI 代码

Xcode 14.0+、SwiftUI 4.0+、Swift 5.7

import SwiftUI
import SceneKit

struct ContentView: View {

    var scene = SCNScene()
    var options: SceneView.Options = [.allowsCameraControl]

    var body: some View {
        ZStack {
            ForEach(-50...49, id: \.self) { x in
                ForEach(-50...49, id: \.self) { z in
                    let _ = DispatchQueue.global().async {
                        scene.rootNode.addChildNode(createCube(x, 0, z))
                    }
                }
            }
            SceneView(scene: scene, options: options)
                .ignoresSafeArea()
            let _ = scene.background.contents = UIColor.black
        }
    }
    
    func createCube(_ posX: Int, _ posY: Int, _ posZ: Int) -> SCNNode {
        let geo = SCNBox(width: 0.5, height: 0.5, length: 0.5, 
                                           chamferRadius: 0.0)
        geo.firstMaterial?.lightingModel = .constant
        let boxNode = SCNNode(geometry: geo)
        boxNode.position = SCNVector3(posX, posY, posZ)
        return boxNode
    }
}

在这里,所有立方体都在视锥体内,因此有明显的丢帧原因。

在这里,只有场景的一部分位于视锥体内,因此不存在丢帧。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SceneKit 在立方体测试中的性能 的相关文章

  • iOS 电池监控 Swift

    我已将监控设置为启用 但模拟器和设备中的电池电量仍然为 1 UIDevice currentDevice batteryMonitoringEnabled true var level UIDevice currentDevice batt
  • 设置/覆盖 UICollectionView 中单元格之间的填充

    我有一个 UICollectionView 但在获取单元格之间的填充时遇到了问题 理论上 我应该能够将屏幕除以 4 并且我可以获得包含 4 个图像的单元格大小 完美地占据屏幕宽度 但是 它选择不这样做 相反 它会创建 3 个具有巨大填充的图
  • SwiftUI:状态栏颜色

    有没有办法将 SwiftUI 视图的状态栏更改为白色 我可能错过了一些简单的东西 但我似乎找不到在 SwiftUI 中将状态栏更改为白色的方法 到目前为止我只看到 statusBar hidden Bool 状态栏文本 色调 前景色可以通过
  • 检测 AVAudioPlayer 中的播放结束

    我有几个短的 mp3 声音 我将它们存储在数组中 并希望连续播放它们 有什么方法可以检测 AVAudioPlayer 何时停止播放 以便我可以调用完成处理程序并播放下一个声音 我知道有一个委托 但我正在使用 Playground 和 SKS
  • 所需框架与静态库

    构建现代框架 https developer apple com videos play wwdc2014 416 says 每个应用程序都有自己的自定义框架副本 https stackoverflow com a 15262463 242
  • 在现有 iOS 应用程序中集成 React-native(0.40.0) 后找不到 Yoga/Yoga.h 头文件

    在我的 Swift iOS 应用程序中集成 React Native 后 我无法构建 yoga Yoga h file cannot be found 我已经浏览了文档 查看了react native github页面 检查了类似问题的SO
  • SKNode 上的 runAction 未完成

    我使用 NSOperation 子类来获取串行执行SKAction正如这个问题中所描述的 如何在 Swift 中子类化 NSOperation 以将 SKAction 对象排队以进行串行执行 https stackoverflow com
  • iOS 13 beta 外部屏幕上的 OverscanCompensation

    我正在测试一个应用程序的测试版 但遇到了外部屏幕的问题 我们看到应用程序周围有黑色边框 我们之前可以通过设置来纠正它overscanCompensation to none但在 iOS 13 中 该设置根本没有任何效果 我们曾经看到一个错误
  • 模块化算术和 NTT(有限域 DFT)优化

    我想使用 NTT 进行快速平方 参见快速大数平方计算 https stackoverflow com q 18465326 2521214 但即使对于非常大的数字 结果也很慢 超过 12000 位 所以我的问题是 有没有办法优化我的 NTT
  • iOS:Swift - 如何在触摸时向地图添加精确定位并获取该位置的详细地址?

    我想在 iOS 地图的触摸上添加注释并获取各个位置的详细地址 地标 我如何在 Swift 中实现这一目标 提前致谢 要对地图上的触摸做出反应 您需要为地图视图设置点击识别器 in viewDidLoad let gestureRecogni
  • JavaScript - 无需布尔值即可运行一次

    有没有办法只运行一段JavaScript代码ONCE 而不使用布尔标志变量来记住它是否已经运行过 具体来说not就像是 var alreadyRan false function runOnce if alreadyRan return a
  • 无法将“SDWebImageActivityIndi​​cator”类型的值分配给“ST_SDWebImageIndicator”类型?

    I have multiple flavours targets in my Xcode project I am also using SDWebImage in my app Everything was working fine un
  • 如何防止 RealmSwift 列表中出现重复项?

    如何防止向列表中添加重复项RealmSwift 我有我的User作为领域对象 但真正的数据源是服务器 只是使用领域在本地缓存用户 当我从服务器获取当前用户数据时 我想确保存储在领域中的用户拥有来自服务器的所有播放列表 以及它们的曲目列表等
  • 为什么将模块级代码放入函数中然后调用该函数在Python中速度更快?

    在亚历克斯 马尔泰利的回应中使 Python 脚本面向对象 https stackoverflow com questions 1813117 making a python script object oriented 他提到在 Pyth
  • Swift - 选择值后隐藏 pickerView

    我发现了类似的问题 他们的答案很有帮助 但我坚持最后一件事 我试图在点击字段时显示 pickerView 然后选择数据时 我希望 pickerView 隐藏 我可以从 pickerView 获取数据来隐藏 但是 pickerView 后面仍
  • UISearchController 保留问题

    我正在尝试使用 UISearchController 但是我遇到了无法解决的保留问题 MainTableview 有两个部分 第1节 基于某些正则表达式过滤数据 第2节 All Data 我将 UISearchController 添加到我
  • Swift getnameinfo IPv6 结果不可靠

    我有以下扩展sockaddr extension sockaddr Indicates if this is an IPv4 address var isIPv4 Bool return sa family UInt8 AF INET In
  • 在 Swift 中自动移动 UISlider

    我想在按下按钮时将 UISlider 从 minValue 循环移动到 maxValue 并在再次按下按钮时将其停止在当前位置 我想使用 Swift 我遇到的主要问题是函数 slider setValue 太快了 我希望动画更慢 IBAct
  • 无法转换“UINavigationController”类型的值

    我正在为我的应用程序实现一个搜索界面 因此基本上我会将搜索关键字从一个 ViewController 传递到另一个 ViewController 我已经多次进行过这种类型的参数传递 但这次似乎有些奇怪 目标 ViewController 嵌
  • 将数字分解为单个数字的数组

    如果我有整数 123 并且我想将数字分解为数组 1 2 3 最好的方法是什么 我已经搞乱了很多 并且我有以下工作 var number 123 var digits Array String number map Int strtoul S

随机推荐

  • 无法启动服务应用程序:OCI 运行时创建失败:container_linux.go:349

    当我尝试使用 docker 启动 go 应用程序时遇到一些麻烦 ERROR for app Cannot start service app OCI runtime create failed container linux go 349
  • 在 jquery 中使表行可点击,但表头不可点击

    目前我正在使用 mytable tr click function blah blah 这使得所有行 包括标题 都可单击 如何排除标题或 th s 使用将标题和正文分开 thead and tbody 标签 并将选择器更改为 mytable
  • 以编程方式找出谁对我们的 iPhone 应用程序给予了 5 星评级

    我看到很多应用程序都说 如果您给我们的应用程序评分 5 星 您将获得 1000 个金币 用户可以在下载的应用程序中使用这些硬币 我想实现相同的功能 我们如何以编程方式确定谁对我们的 iPhone 应用程序给予了 5 星评级 以便我们可以在他
  • 安装nodejs后未定义require

    首先我从下载了node jslink https nodejs org en 然后我安装了browserifynpm install g browserify 然后我安装了fsnpm install fs 我收到以
  • 将 FindBugs 配置从 Sonar 导入到 Maven

    如何使用 Sonar 中的 FindBugs 配置文件作为规则集Maven FindBugs 插件 https gleclaire github io findbugs maven plugin 您可以放置 您的文件 即findbugs s
  • 非常大且非常稀疏的非负矩阵分解

    我有一个非常大且稀疏的矩阵 531K x 315K 单元格总数约为 1670 亿 非零值仅为 1s 非零值总数约为 45K 有高效的 NMF 包来解决我的问题吗 我知道有几个软件包可以实现这一点 并且它们仅适用于小尺寸的数据矩阵 任何想法都
  • WPF 将父绑定对象传递给转换器

    我有 ItemsControl 绑定到 Student 类型的集合 在 ItemTemplate 内部 我有一个 TextBox 它使用 IValueConverter 来执行一些自定义计算和逻辑 我想将实际的 Student 对象传递给值
  • 如何使用jquery在加载时滑动整个页面

    我想在更改时将整个页面向下滑动 我想做到这一点的方法是创建一个垂直幻灯片 在单击链接时播放 并在页面加载时再次播放 到目前为止 我只能创建影响特定 DIV 的幻灯片 我还希望它能够垂直滑入 任何想法将不胜感激 只需将所有内容包装在一个 di
  • 为什么应该仅在 1 个元素张量上或使用变量的梯度来调用向后函数?

    我是 pytorch 的新手 我想了解为什么我们不能对包含大小为 2 2 的张量的变量调用后向函数 如果我们确实想在包含大小为 2 2 的张量的变量上调用它 我们必须首先定义一个梯度张量 然后在包含张量 w r t 定义的梯度的变量上调用后
  • C# 如何使用 DataAnnotations StringLength 和 SubString 删除文本

    我有一个模型类 它有一个描述属性 其数据注释属性为 StringLength 长度设置为 100 个字符 当此属性超过 100 个字符并且实体框架尝试保存此属性时 我收到以下错误 StringLength 100 ErrorMessage
  • SQL:条件 AND in where

    我正在尝试创建一个允许省略参数的存储过程 但如果提供了参数则进行 AND 操作 CREATE PROCEDURE MyProcedure LastName Varchar 30 NULL FirstName Varchar 30 NULL
  • Apache Commons CLI - 订购帮助选项?

    我正在使用 Apache Commons CLI 默认情况下 它按字母顺序对命令行上的帮助选项进行排序 那么 出现的就是 csv ip msisdn xml 但我想按如下方式订购它们 csv xml ip msisdn 我知道您可以使用一个
  • 通过 Access 中的 ODBC 链接表更新 SQLite 数据库

    我在使用 SQLite 数据库时遇到问题 我正在使用 SQLite ODBChttp www ch werner de sqliteodbc http www ch werner de sqliteodbc 安装 64 位版本并使用以下设置
  • OpenERP (Odoo) 在哪里找到模块路径?

    我正在使用 Odoo v 8 我想找出 Odoo 在哪里找到模块所在的信息并加载它们 我知道文件 openerp server conf 中有一个变量 addons path 我找到该变量的唯一文件是 opt odoo odoo debia
  • Python:按索引过滤列表

    在Python中我有一个元素列表aList和索引列表myIndices 有什么方法可以一次检索所有这些项目aList将以下值作为索引myIndices Example gt gt gt aList a b c d e f g gt gt g
  • 在 JavaScript 中保存哈希/锚点更改的历史记录

    我目前正在实现一个 JavaScript 库 用于跟踪地址栏中哈希部分的更改历史记录 这个想法是 您可以在哈希部分保留一个状态 然后使用后退按钮返回到之前的状态 在大多数最新的浏览器中 这是自动的 您只需轮询location hash属性进
  • npm run 运行缓慢

    我曾经通过 Makefile 运行各种命令 但对于 Nodejs 项目来说 package json 是更合适的地方 通过 npm 运行命令效果很好 但与命令时间执行相比非常慢 time node modules bin jshint no
  • IIS 7.5 修复尝试加载格式不正确的程序问题?

    我遇到一个异常问题 抛出异常 尝试加载格式不正确的程序 错误 我在同一台 IIS 服务器上有两个相同的网站 我的构建配置适用于其中一个 但不适用于另一个 我的 C MVC 2 Web 应用程序可以部署到驻留在同一 IIS 7 5 Web 服
  • 有没有比 dict() 更快的东西?

    我需要一种更快的方式来存储和访问大约 3GB 的数据k v对 在哪里k是一个字符串或一个整数并且v is an np array 可以有不同的形状 是否有任何对象在存储和访问这样的表时比标准 python 字典更快 例如 一个pandas
  • SceneKit 在立方体测试中的性能

    在学习游戏 3D 图形编程时 我决定从使用 Scene Kit 3D API 开始简单 我的第一个游戏目标是构建一个非常简化的 我的世界 的模仿版 一个只有立方体的游戏 这有多难 下面是我编写的一个循环 用于放置 100 x 100 立方体