SceneKit 自定义几何纹理错误

Posted

技术标签:

【中文标题】SceneKit 自定义几何纹理错误【英文标题】:SceneKit Custom Geometry Texture Wrong 【发布时间】:2018-03-05 03:40:01 【问题描述】:

在 SceneKit 中,我试图创建一个带有纹理的简单自定义几何体。我只是想制作一个立方体,但每个面的方向都正确,所以 SCNBox 不起作用。

形状非常好,但纹理映射完全错误。

这是几何图形的样子:

根据我的理解,每个面的纹理坐标应该是相同的。但是当我以相同的顺序为每个面添加点时,它同样会错误地绘制。所以我玩弄了坐标的顺序,让正面、背面和一侧的脸大部分都正确绘制。不过,我无法找出导致这种情况的模式或逻辑。

正面是每张脸的样子。

我的做法是这样的:

public struct face 
     var topLeft: SCNVector3
     var topRight: SCNVector3
     var bottomLeft: SCNVector3
     var bottomRight: SCNVector3


 func createCube(startFace: face, endFace: face) -> SCNGeometry 
    var vertices: [SCNVector3] = []
    var indices: [Int32] = []
    var textCords: [vector_float2] = []

    // Add table of contents to indices array because we're using polygons
    let polygons = 6 // cube so 6 faces
    indices.append(4) // front face
    indices.append(4) // left face
    indices.append(4) // right face
    indices.append(4) // top face
    indices.append(4) // bottom face
    indices.append(4) // back face

    //  - Convenience Values -

    // Indices offsets
    let startFaceVertex: Int32 = 0
    let endFaceVertex: Int32 = 4

    // Vertex indices
    let bottomLeftPos: Int32 = 0
    let bottomRightPos: Int32 = 1
    let topRightPos: Int32 = 2
    let topLeftPos: Int32 = 3

    // Texture Coordinates
    let topLeft = CGPoint(x: 0, y: 1)
    let topRight = CGPoint(x: 1, y: 1)
    let bottomLeft = CGPoint(x: 0, y: 0)
    let bottomRight = CGPoint(x: 1, y: 0)


    // Add vertices

    vertices.append(startFace.bottomLeft)
    vertices.append(startFace.bottomRight)
    vertices.append(startFace.topRight)
    vertices.append(startFace.topLeft)

    vertices.append(endFace.bottomLeft)
    vertices.append(endFace.bottomRight)
    vertices.append(endFace.topRight)
    vertices.append(endFace.topLeft)


    // Front Face

    indices.append(startFaceVertex + bottomLeftPos)
    indices.append(startFaceVertex + bottomRightPos)
    indices.append(startFaceVertex + topRightPos)
    indices.append(startFaceVertex +  topLeftPos)

    textCords.append(vector_float2(Float(topLeft.x), Float(topLeft.y)))
    textCords.append(vector_float2(Float(topRight.x), Float(topRight.y)))
    textCords.append(vector_float2(Float(bottomRight.x), Float(bottomRight.y)))
    textCords.append(vector_float2(Float(bottomLeft.x), Float(bottomLeft.y)))

    // Left Face

    indices.append(startFaceVertex + bottomLeftPos)
    indices.append(endFaceVertex + bottomLeftPos)
    indices.append(endFaceVertex + topLeftPos)
    indices.append(startFaceVertex + topLeftPos)

    textCords.append(vector_float2(Float(bottomRight.x), Float(bottomRight.y)))
    textCords.append(vector_float2(Float(topLeft.x), Float(topLeft.y)))
    textCords.append(vector_float2(Float(bottomLeft.x), Float(bottomLeft.y)))
    textCords.append(vector_float2(Float(topRight.x), Float(topRight.y)))

    // Top Face

    indices.append(endFaceVertex + topLeftPos)
    indices.append(endFaceVertex + topRightPos)
    indices.append(startFaceVertex + topRightPos)
    indices.append(startFaceVertex + topLeftPos)

    textCords.append(vector_float2(Float(topLeft.x), Float(topLeft.y)))
    textCords.append(vector_float2(Float(topRight.x), Float(topRight.y)))
    textCords.append(vector_float2(Float(bottomRight.x), Float(bottomRight.y)))
    textCords.append(vector_float2(Float(bottomLeft.x), Float(bottomLeft.y)))

    // Right Face

    indices.append(endFaceVertex + bottomRightPos)
    indices.append(startFaceVertex + bottomRightPos)
    indices.append(startFaceVertex + topRightPos)
    indices.append(endFaceVertex + topRightPos)

    textCords.append(vector_float2(Float(bottomRight.x), Float(bottomRight.y)))
    textCords.append(vector_float2(Float(topLeft.x), Float(topLeft.y)))
    textCords.append(vector_float2(Float(bottomLeft.x), Float(bottomLeft.y)))
    textCords.append(vector_float2(Float(topRight.x), Float(topRight.y)))

    // Bottom Face

    indices.append(startFaceVertex + bottomLeftPos)
    indices.append(startFaceVertex + bottomRightPos)
    indices.append(endFaceVertex + bottomRightPos)
    indices.append(endFaceVertex + bottomLeftPos)

    textCords.append(vector_float2(Float(topLeft.x), Float(topLeft.y)))
    textCords.append(vector_float2(Float(topRight.x), Float(topRight.y)))
    textCords.append(vector_float2(Float(bottomRight.x), Float(bottomRight.y)))
    textCords.append(vector_float2(Float(bottomLeft.x), Float(bottomLeft.y)))


    // Back Face

    indices.append(endFaceVertex + bottomLeftPos)
    indices.append(endFaceVertex + bottomRightPos)
    indices.append(endFaceVertex + topRightPos)
    indices.append(endFaceVertex + topLeftPos)

    textCords.append(vector_float2(Float(topLeft.x), Float(topLeft.y)))
    textCords.append(vector_float2(Float(topRight.x), Float(topRight.y)))
    textCords.append(vector_float2(Float(bottomRight.x), Float(bottomRight.y)))
    textCords.append(vector_float2(Float(bottomLeft.x), Float(bottomLeft.y)))


    // Create geometry

    let verticesSource = SCNGeometrySource(vertices: vertices)

    let uvData = Data(bytes: textCords, count: textCords.count * MemoryLayout<vector_float2>.size)
    let textureSource = SCNGeometrySource(data: uvData,
                                          semantic: .texcoord,
                                          vectorCount: textCords.count,
                                          usesFloatComponents: true,
                                          componentsPerVector: 2,
                                          bytesPerComponent: MemoryLayout<Float>.size,
                                          dataOffset: 0,
                                          dataStride: MemoryLayout<vector_float2>.size)

    let indexData = Data(bytes: indices,
                         count: indices.count * MemoryLayout<Int32>.size)
    let elements = SCNGeometryElement(data: indexData,
                                      primitiveType: .polygon,
                                      primitiveCount: polygons,
                                      bytesPerIndex: MemoryLayout<Int32>.size)

    return SCNGeometry(sources: [verticesSource, textureSource], elements: [elements])

解决方案

得到它的工作,这是我的工作版本的代码:

public struct face 
    var topLeft: SCNVector3
    var topRight: SCNVector3
    var bottomLeft: SCNVector3
    var bottomRight: SCNVector3


let topLeft = CGPoint(x: 0, y: 1)
let topRight = CGPoint(x: 1, y: 1)
let bottomLeft = CGPoint(x: 0, y: 0)
let bottomRight = CGPoint(x: 1, y: 0)

func createCube(startFace: face, endFace: face) -> SCNGeometry 
    var vertices: [SCNVector3] = []
    var indexTable: [Int32] = []
    var indices: [Int32] = []
    var textCords: [vector_float2] = []

    // Front Face
    addFace(face: startFace, textureOffset: CGPoint.zero, textureSize: CGSize(width: 1, height: 1), toVertices: &vertices, indexTable: &indexTable, indices: &indices, textCords: &textCords)

    // Left Face

    let leftFace = face(topLeft: endFace.topLeft, topRight: startFace.topLeft, bottomLeft: endFace.bottomLeft, bottomRight: startFace.bottomLeft, center: SCNVector3Zero, originOffset: startFace.originOffset)
    addFace(face: leftFace, textureOffset: CGPoint.zero, textureSize: CGSize(width: 1, height: 1), toVertices: &vertices, indexTable: &indexTable, indices: &indices, textCords: &textCords)

    // Top Face

    //let topFace = face(topLeft: startFace.topLeft, topRight: endFace.topLeft, bottomLeft: startFace.topRight, bottomRight: endFace.topRight, center: SCNVector3Zero, originOffset: startFace.originOffset)
    let topFace = face(topLeft: startFace.topLeft, topRight: endFace.topLeft, bottomLeft: startFace.topRight, bottomRight: endFace.topRight, center: SCNVector3Zero, originOffset: startFace.originOffset)
    addFace(face: topFace, textureOffset: CGPoint.zero, textureSize: CGSize(width: 1, height: 1), toVertices: &vertices, indexTable: &indexTable, indices: &indices, textCords: &textCords)

    // Right Face

    let rightFace = face(topLeft: startFace.topRight, topRight: endFace.topRight, bottomLeft: startFace.bottomRight, bottomRight: endFace.bottomRight, center: SCNVector3Zero, originOffset: startFace.originOffset)
    addFace(face: rightFace, textureOffset: CGPoint.zero, textureSize: CGSize(width: 1, height: 1), toVertices: &vertices, indexTable: &indexTable, indices: &indices, textCords: &textCords)

    // Bottom Face

    let bottomFace = face(topLeft: endFace.bottomLeft, topRight: startFace.bottomLeft, bottomLeft: endFace.bottomRight, bottomRight: startFace.bottomRight, center: SCNVector3Zero, originOffset: startFace.originOffset)
    addFace(face: bottomFace, textureOffset: CGPoint.zero, textureSize: CGSize(width: 1, height: 1), toVertices: &vertices, indexTable: &indexTable, indices: &indices, textCords: &textCords)

    // Back Face

    addFace(face: endFace, textureOffset: CGPoint.zero, textureSize: CGSize(width: 1, height: 1), toVertices: &vertices, indexTable: &indexTable, indices: &indices, textCords: &textCords)

    // Create geometry

    let verticesSource = SCNGeometrySource(vertices: vertices)

    let uvData = Data(bytes: textCords, count: textCords.count * MemoryLayout<vector_float2>.size)
    let textureSource = SCNGeometrySource(data: uvData,
                                          semantic: .texcoord,
                                          vectorCount: textCords.count,
                                          usesFloatComponents: true,
                                          componentsPerVector: 2,
                                          bytesPerComponent: MemoryLayout<Float>.size,
                                          dataOffset: 0,
                                          dataStride: MemoryLayout<vector_float2>.size)

    var finalIndices: [Int32] = []
    finalIndices.append(contentsOf: indexTable)
    finalIndices.append(contentsOf: indices)

    let indexData = Data(bytes: finalIndices,
                         count: finalIndices.count * MemoryLayout<Int32>.size)
    let elements = SCNGeometryElement(data: indexData,
                                      primitiveType: .polygon,
                                      primitiveCount: indexTable.count,
                                      bytesPerIndex: MemoryLayout<Int32>.size)

    return SCNGeometry(sources: [verticesSource, textureSource], elements: [elements])

fileprivate func addFace(face: face, textureOffset: CGPoint, textureSize: CGSize, toVertices: inout [SCNVector3], indexTable: inout [Int32], indices: inout [Int32], textCords: inout [vector_float2]) 
    toVertices.append(face.topRight)
    toVertices.append(face.topLeft)
    toVertices.append(face.bottomLeft)
    toVertices.append(face.bottomRight)

    let polygonPointCount: Int32 = 4
    indexTable.append(polygonPointCount)
    for _ in 0..<polygonPointCount 
        indices.append(Int32(indices.count))
    

    textCords.append(vector_float2(Float(bottomRight.x + textureOffset.x + textureSize.width), Float(bottomRight.y + textureOffset.y)))
    textCords.append(vector_float2(Float(bottomLeft.x + textureOffset.x + textureSize.width), Float(bottomLeft.y + textureOffset.y)))
    textCords.append(vector_float2(Float(topLeft.x + textureOffset.x + textureSize.width), Float(topLeft.y + textureOffset.y)))
    textCords.append(vector_float2(Float(topRight.x + textureOffset.x + textureSize.width), Float(topRight.y + textureOffset.y)))

【问题讨论】:

【参考方案1】:

一个顶点位置和所有相关属性形成一个记录。这意味着,如果必须使用不同的纹理坐标多次使用顶点位置,则必须为每个纹理坐标将顶点位置添加到缓冲区一次。无法将元素数组与纹理坐标相关联。 您必须为立方体的每一侧创建一个具有 4 个顶点的顶点位置缓冲区,并为立方体的每一侧创建一个具有 4 个纹理坐标的纹理坐标缓冲区:

texureCoordsSide =[
    vector_float2(Float(bottomLeft.x), Float(bottomLeft.y)),
    vector_float2(Float(bottomRight.x), Float(bottomRight.y)),
    vector_float2(Float(topRight.x), Float(topRight.y)),
    vector_float2(Float(topLeft.x), Float(topLeft.y))]

// Front Face
vertices += [startFace.bottomLeft, startFace.bottomRight, startFace.topRight, startFace.topLeft];
textCords += texureCoordsSide

// Left Face
vertices += [endFace.bottomLeft, startFace.bottomLeft, startFace.topLeft, endFace.topLeft];
textCords += texureCoordsSide

// Top Face
vertices += [startFace.topLeft, startFace.topRight, endFace.topRight, endFace.topLeft];
textCords += texureCoordsSide

// Right Face
vertices += [startFace.bottomRight, endFace.bottomRight, endFace.topRight, startFace.topRight];
textCords += texureCoordsSide

// Bottom Face
vertices += [endFace.bottomRight, endFace.bottomLeft, startFace.bottomLeft, startFace.bottomRight];
textCords += texureCoordsSide

// Back Face
vertices += [endFace.bottomRight, endFace.bottomLeft, endFace.topLeft, endFace.topRight];
textCords += texureCoordsSide

元素数组 (indices) 必须包含从 0 到 23 的连续顺序的 24 个索引(立方体的 6 个边各 4 个)。

【讨论】:

非常感谢!我更新了我的代码,效果很好

以上是关于SceneKit 自定义几何纹理错误的主要内容,如果未能解决你的问题,请参考以下文章

iOS 上 Swift 中的自定义 SceneKit 几何图形不起作用,但等效的 Objective C 代码可以

SCNGeometry 自定义形状出现黑色

SceneKit:增加用于 SCNNode 材质的纹理的清晰度?

GLSL + Scenekit:不允许函数定义

iOS开发之SceneKit框架--SCNGeometry.h

SceneKit 与 Vuforia AR 库自定义模型