imagetexture-mappingrealitykitvisionos

How to apply a texture image to a RealityKit plane?


I am trying to understand visionOS/RealityKit.
As a test, I created a box and wanted to apply a texture image only to the top of the box. To achieve this, I have apparently to construct the box of 6 faces, e.g. planes (code see below).

As a first test, I assigned a green material to the top face, and a brown material to all other faces. This is the result:

enter image description here

Then, I wanted to replace the top face material by a texture image. I read this tutorial how to do this, implemented the tutorial code, and the texture image is displayed correctly:

enter image description here

Eventually, I wanted to apply the same texture image to the top face of my box. I used the following code:

let texture = try! TextureResource.load(named: "tomatoes")
var material = UnlitMaterial()
material.color = UnlitMaterial.BaseColor(texture: .init(texture))

Here is the result:

enter image description here

Apparently, only part of the bottom row of the image is copied to all depth values of the top face.
I don't have experience in computer graphics, and thus don't know how mapping a texture to a plane works.
Obviously, something is wrong with my code, but what?

Here is the code that creates the anchor entity of the box:

let halfWidth     = 1.0
let halfHeight    = 1.0
let halfThickness = 0.04

// Define the 8 corners of a box
let frontBottomLeft     = SIMD3(-halfWidth, -halfHeight,  halfThickness) // 0
let frontBottomRight    = SIMD3( halfWidth, -halfHeight,  halfThickness) // 1
let frontTopRight       = SIMD3( halfWidth,  halfHeight,  halfThickness) // 2
let frontTopLeft        = SIMD3(-halfWidth,  halfHeight,  halfThickness) // 3
let backBottomLeft      = SIMD3(-halfWidth, -halfHeight, -halfThickness) // 4
let backBottomRight     = SIMD3( halfWidth, -halfHeight, -halfThickness) // 5
let backTopRight        = SIMD3( halfWidth,  halfHeight, -halfThickness) // 6
let backTopLeft         = SIMD3(-halfWidth,  halfHeight, -halfThickness) // 7

// Define the 8 vertices of the box
let vertices: [SIMD3<Float>] = [
    frontBottomLeft,
    frontBottomRight,
    frontTopRight,
    frontTopLeft,
    backBottomLeft,
    backBottomRight,
    backTopRight,
    backTopLeft
]

// Define a polygon for every face. THere are 6 faces, and every face has 4 polygons.
let polygons: ([UInt8], [UInt32]) = ([4, 4, 4, 4, 4, 4], // The nr of vertices in each polygon
                                     [0, 1, 2, 3,   // Front face
                                      4, 5, 6, 7,   // Back face
                                      4, 0, 3, 7,   // Left face
                                      1, 5, 6, 2,   // Right face
                                      3, 2, 6, 7,   // Top face
                                      4, 5, 1, 0])  // Bottom face

// Define materials
var defaultMaterial = UnlitMaterial(applyPostProcessToneMap: false)
defaultMaterial.color.tint = .brown
var topMaterial = material

// Loop over the polygons. Here, primitives are polygons. They are processed sequentially.
let boardEntity = Entity()
var indexPointer = 0 // Point to the 1st index of the next polygon
for i in 0 ..< polygons.0.count { // There are 6 polygons for a box
    var meshDescriptor = MeshDescriptor()
    meshDescriptor.name = "Board mesh face \(i)"
    let nrVerticesOfPolygon = Int(polygons.0[i]) // Here, all 6 polygons have 4 vertices
    var indices: [UInt32] = [] // The indices say which vertex is connected which other one for the current polygon
    for j in indexPointer ..< indexPointer + nrVerticesOfPolygon { // Use the next 4 indexes
        indices.append(polygons.1[j]) // Append next vertex index
    }
    meshDescriptor.positions = MeshBuffer(vertices) 
    meshDescriptor.primitives = .polygons([polygons.0[i]], indices)
    let mesh = try! MeshResource.generate(from: [meshDescriptor])
    let material = i == 4 ? topMaterial : defaultMaterial
    let modelEntity = ModelEntity(mesh: mesh, materials: [material])
    boardEntity.addChild(modelEntity)
    indexPointer += nrVerticesOfPolygon
}

let anchorEntity = AnchorEntity(world: position)
anchorEntity.addChild(boardEntity)

Solution

  • My code was much too complicated. I found the solution to my problem here.
    It is possible by a built-in function to generate a box and to assign different materials to its faces, see the docs.
    Using this, the solution is trivial:

    var defaultMaterial = UnlitMaterial(applyPostProcessToneMap: false)
    defaultMaterial.color.tint = .brown
    let materials = [defaultMaterial, topMaterial, defaultMaterial, defaultMaterial, defaultMaterial, defaultMaterial]
    
    let mesh = MeshResource.generateBox(width: 2, height: 0.04, depth: 2, splitFaces: true)
    
    let boardEntity = ModelEntity(mesh: mesh, materials: materials)
    let anchorEntity = AnchorEntity(world: position)
    anchorEntity.addChild(boardEntity)
    

    The result it
    enter image description here