8

我正在关注本教程:http ://blog.bignerdranch.com/754-scenekit-in-mountain-lion/

我对使用 Scene Kit 很感兴趣,但我的场景可能有数千个球体。为了对 Scene Kit 进行压力测试,我尝试了这个:

SCNSphere *sphere = [SCNSphere sphereWithRadius:0.5];
for (int i=0; i<10; i++) {
    for(int j=0; j<10; j++){
        for(int k=0; k<10; k++){
            SCNNode *myNode = [SCNNode nodeWithGeometry:sphere];
            myNode.position = SCNVector3Make(i,j,k);
            [root addChildNode:myNode];
        }
    }
}

例如,这适用于 1000 个球体 (10^3)​​,但对于 1,000,000 个球体 (100^3) 则失败(可能不足为奇)。我不介意不能使用一百万个球体,但我想弄清楚合理的上限是多少(5,000?15,000?)以及如何增加它。

我能做些什么来减轻这种情况?例如,我尝试了 sphere.segmentCount = 3,虽然这可以加快渲染速度,但它对内存使用没有太大影响,我怀疑这是限制因素。

此外,似乎没有 SCNPoint 类。我正在考虑在球体数量过多时切换到仅显示一个点,但是我无法从 SceneKit 文档中看到如何显示一个简单的点——我能看到的最简单的是一个三角形。

任何帮助深表感谢。

编辑: @toyos 建议将 SCNSphere 对象合并到单个 SCNGeometry 对象中(前提是它们不需要独立动画,它们不需要),但我找不到一个简单的方法来做到这一点。

SCNGeometry 是通过使用此处[SCNGeometry geometryWithSources:(* NSArray)sources geometryWithElements:(* NSArray) elements]记录的方法创建的,但我不清楚如何从我的球体创建对象。SCNGeometry

例如对于单个球体,我可以看到使用sphere.geometryElementCount来获取元素的数量,然后使用它来填充一个数组,使用[sphere geometryElementAtIndex:(NSInteger)elementIndex]它会给我元素,但我不确定如何获取“源”(或者它们甚至是)。获取几何源的方法是[sphere geometrySourcesForSemantic:(NSString*) semantic],但是这个语义字符串是什么?(它的意思是“法线”或“顶点”还是别的什么?文档很有帮助地说语义是“几何源的语义值。”没有说明语义的可能值是什么)

这只是针对单个球体,这将是毫无意义的(因为无论如何SCNSphere只是一个子类SCNGeometry),所以现在我必须添加多个球体。那么在将球体的顶点添加到我的SCNGeometry对象时,我是否必须手动平移它们?

我只是想找出最明智的方法来做到这一点。

4

2 回答 2

8

语义字符串是 SCNGeometrySourceSemanticVertex|Normal|Texcoord ...

对于多个球体,答案是肯定的,您必须在展平之前使用当前节点变换来变换顶点/法线。

下面是一个简化的示例(即,如果它们都具有相同的几何形状,它只支持合并“输入”的孩子)

- (SCNNode *) flattenNodeHierarchy:(SCNNode *) input
{
    SCNNode *result = [SCNNode node];

    NSUInteger nodeCount = [[input childNodes] count];
    if(nodeCount > 0){
    SCNNode *node = [[input childNodes] objectAtIndex:0];

        NSArray *vertexArray = [node.geometry geometrySourcesForSemantic:SCNGeometrySourceSemanticVertex];
        SCNGeometrySource *vertex = [vertexArray objectAtIndex:0];

        SCNGeometryElement *element = [node.geometry geometryElementAtIndex:0]; //todo: support multiple elements
        NSUInteger primitiveCount = element.primitiveCount;
        NSUInteger newPrimitiveCount = primitiveCount * nodeCount;
        size_t elementBufferLength = newPrimitiveCount * 3 * sizeof(int); //nTriangle x 3 vertex * size of int
        int* elementBuffer = (int*)malloc(elementBufferLength);

        /* simple case: here we consider that all the objects to flatten are the same
         In the regular case we should iterate on every geometry and accumulate the number of vertex/triangles etc...*/

        NSUInteger vertexCount = [vertex vectorCount];
        NSUInteger newVertexCount = vertexCount * nodeCount;

        SCNVector3 *newVertex = malloc(sizeof(SCNVector3) * newVertexCount);        
        SCNVector3 *newNormal = malloc(sizeof(SCNVector3) * newVertexCount); //assume same number of normal/vertex

        //fill
        NSUInteger vertexFillIndex = 0;
        NSUInteger primitiveFillIndex = 0;
        for(NSUInteger index=0; index< nodeCount; index++){
            NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

            node = [[input childNodes] objectAtIndex:index];

            NSArray *vertexArray = [node.geometry geometrySourcesForSemantic:SCNGeometrySourceSemanticVertex];
            NSArray *normalArray = [node.geometry geometrySourcesForSemantic:SCNGeometrySourceSemanticNormal];
            SCNGeometrySource *vertex = [vertexArray objectAtIndex:0];
            SCNGeometrySource *normals = [normalArray objectAtIndex:0];

            if([vertex bytesPerComponent] != sizeof(float)){
                NSLog(@"todo: support other byte per component");
                continue;
            }

            float *vertexBuffer = (float *)[[vertex data] bytes];
            float *normalBuffer = (float *)[[normals data] bytes];

            CATransform3D t = [node transform];
            GLKMatrix4 matrix = MyGLKMatrix4FromCATransform3D(t);

            //append source
            for(NSUInteger vIndex = 0; vIndex < vertexCount; vIndex++, vertexFillIndex++){
                GLKVector3 v = GLKVector3Make(vertexBuffer[vIndex * 3], vertexBuffer[vIndex * 3+1], vertexBuffer[vIndex * 3 + 2]);
                GLKVector3 n = GLKVector3Make(normalBuffer[vIndex * 3], normalBuffer[vIndex * 3+1], normalBuffer[vIndex * 3 + 2]);

                //transform
                v = GLKMatrix4MultiplyVector3WithTranslation(matrix, v);
                n = GLKMatrix4MultiplyVector3(matrix, n);

                newVertex[vertexFillIndex] = SCNVector3Make(v.x, v.y, v.z);
                newNormal[vertexFillIndex] = SCNVector3Make(n.x, n.y, n.z);
            }

            //append elements
            //here we assume that all elements are SCNGeometryPrimitiveTypeTriangles
            SCNGeometryElement *element = [node.geometry geometryElementAtIndex:0];
            const void *inputPrimitive = [element.data bytes];
            size_t bpi = element.bytesPerIndex;

            NSUInteger offset = index * vertexCount;

            for(NSUInteger pIndex = 0; pIndex < primitiveCount; pIndex++, primitiveFillIndex+=3){                
                elementBuffer[primitiveFillIndex] = offset + _getIndex(inputPrimitive, bpi, pIndex*3);
                elementBuffer[primitiveFillIndex+1] = offset + _getIndex(inputPrimitive, bpi, pIndex*3+1);
                elementBuffer[primitiveFillIndex+2] = offset + _getIndex(inputPrimitive, bpi, pIndex*3+2);
            }

            [pool drain];
        }

        NSArray *sources = @[[SCNGeometrySource geometrySourceWithVertices:newVertex count:newVertexCount],
                             [SCNGeometrySource geometrySourceWithNormals:newNormal count:newVertexCount]];

        NSData *newElementData = [NSMutableData dataWithBytesNoCopy:elementBuffer length:elementBufferLength freeWhenDone:YES];
        NSArray *elements = @[[SCNGeometryElement geometryElementWithData:newElementData
                                                            primitiveType:SCNGeometryPrimitiveTypeTriangles
                                                           primitiveCount:newPrimitiveCount bytesPerIndex:sizeof(int)]];

        result.geometry = [SCNGeometry geometryWithSources:sources elements:elements];

        //cleanup
        free(newVertex);
        free(newNormal);
    }

    return result;
}

//helpers:
GLKMatrix4 MyGLKMatrix4FromCATransform3D(CATransform3D transform) {
    GLKMatrix4 m = {{transform.m11, transform.m12, transform.m13, transform.m14,
        transform.m21, transform.m22, transform.m23, transform.m24,
        transform.m31, transform.m32, transform.m33, transform.m34,
        transform.m41, transform.m42, transform.m43, transform.m44}};
    return m;
}



GLKVector3 MySCNVector3ToGLKVector3(SCNVector3 vector) {
    GLKVector3 v = {{vector.x, vector.y, vector.z}};
    return v;
}
于 2013-02-14T14:26:46.427 回答
3

如何最好地做到这一点取决于您要完成的工作。

这数千个点(也许是外太空场景的星空背景)是静态的,还是需要相对于彼此移动?它们真的需要是球体吗?他们需要多少细节?

如果它们不需要独立移动,将它们合并成一个几何体是个好主意。在 Mavericks (OS X 10.9) 上,您不需要自己弄乱几何数据来做到这一点 - 为每个节点创建一个节点,然后将它们全部作为单个节点(不是场景的根节点)的父节点,然后调用flattenedClone以获取该节点的几何图形组合的副本。

如果他们不需要太多的细节,有几个选项可以提高性能。

一种是减少segmentCount球体的几何形状——你不需要 5000 个三角形来绘制一个在渲染时只有几个像素宽的球体,这与默认段数 48 所得到的差不多。(如果在减少段数后,您将立即弄乱几何数据或展平节点,请务必调用[SCNTransaction flush]以确保它得到更新。)

另一个是进一步减少三角形数量。恒星(或其他)是否足够小以至于它们甚至需要成为球体?如果您的场景可以设置为始终朝向相机,则SCNPlane可能会更好——它的最小段数只有两个三角形。

他们甚至需要是三角形吗?Scene Kit 可以渲染点——它们没有SCNGeometry子类,因为独立定位和转换单个点通常没有用。但是您可以使用顶点位置数组和SCNGeometryPrimitiveTypePoint几何元素类型创建自定义几何。如果您想自定义单点的渲染,您可以将着色器(或着色器修改器)附加到几何体。

于 2014-03-05T19:30:24.547 回答