In this scene
- the column of spheres on the left are created using
SCNSphere()
- the column of 'circles' on the right are created using
SCNGeometry()
using.point
primitives - there is only an ambient light source
- all geometries are using a
.constant
lighting model - each pair of spheres uses the same RGB values to define the colour.
Why are the shades of black for the last two pairs lighter for the circles than their equivalient sphere?
Full playground code to reproduce this images:
Creating the scene
import UIKit
import SceneKit
import PlaygroundSupport
// Constants I'm using for the darkest grey colour
let B_RED: CGFloat = 0.05
let B_GREEN: CGFloat = 0.05
let B_BLUE: CGFloat = 0.05
let B_ALPHA: CGFloat = 1.0
let BLACK_COLOUR = UIColor(red: B_RED, green: B_GREEN, blue: B_BLUE, alpha: B_ALPHA)
let scene: SCNScene = {
let s = SCNScene()
return s
}()
let sceneView: SCNView = {
let v = SCNView(frame: CGRect(x: 0, y: 0, width: 600, height: 800))
v.scene = scene
v.backgroundColor = UIColor(white: 0.66, alpha: 1.0)
v.allowsCameraControl = true
v.debugOptions = [SCNDebugOptions.showLightInfluences]
v.backgroundColor
return v
}()
let ambientLigntNode: SCNNode = {
let n = SCNNode()
n.light = SCNLight()
n.light!.type = SCNLight.LightType.ambient
n.light!.color = UIColor(white: 1, alpha: 1.0)
return n
}()
PlaygroundPage.current.liveView = sceneView
// a camera
var cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.simdPosition = simd_float3(0,0,8)
scene.rootNode.addChildNode(cameraNode)
scene.rootNode.addChildNode(ambientLigntNode)
The column of spheres
// ----------------------------------------------------
// White Sphere
let whiteSphere = SCNSphere(radius: 0.3)
let whiteMaterial = SCNMaterial()
whiteMaterial.diffuse.contents = simd_float3(1,1,1)
whiteMaterial.lightingModel = .constant
whiteSphere.materials = [whiteMaterial]
let whiteSphereNode = SCNNode(geometry: whiteSphere)
whiteSphereNode.simdPosition = simd_float3(-1,2,0)
scene.rootNode.addChildNode(whiteSphereNode)
// ----------------------------------------------------
// Black Sphere
let blackSphere = SCNSphere(radius: 0.3)
let blackMaterial = SCNMaterial()
blackMaterial.diffuse.contents = BLACK_COLOUR
blackMaterial.lightingModel = .constant
blackSphere.materials = [blackMaterial]
let blackSphereNode = SCNNode(geometry: blackSphere)
blackSphereNode.simdPosition = simd_float3(-1,-2,0)
scene.rootNode.addChildNode(blackSphereNode)
// ----------------------------------------------------
// Red Sphere
let redSphere = SCNSphere(radius: 0.3)
let redMaterial = SCNMaterial()
redMaterial.diffuse.contents = UIColor(
red: 1.0,
green: 0.0,
blue: 0.0,
alpha: 1.0
)
redMaterial.lightingModel = .constant
redSphere.materials = [redMaterial]
let redSphereNode = SCNNode(geometry: redSphere)
redSphereNode.simdPosition = simd_float3(-1, 1, 0)
scene.rootNode.addChildNode(redSphereNode)
// ----------------------------------------------------
// Green Sphere
let greenSphere = SCNSphere(radius: 0.3)
let greenMaterial = SCNMaterial()
greenMaterial.diffuse.contents = UIColor(
red: 0.0,
green: 1.0,
blue: 0.0,
alpha: 1.0
)
greenMaterial.lightingModel = .constant
greenSphere.materials = [greenMaterial]
let greenSphereNode = SCNNode(geometry: greenSphere)
greenSphereNode.simdPosition = simd_float3(-1, 0, 0)
scene.rootNode.addChildNode(greenSphereNode)
// ----------------------------------------------------
// Blue Sphere
let blueSphere = SCNSphere(radius: 0.3)
let blueMaterial = SCNMaterial()
blueMaterial.diffuse.contents = UIColor(
red: 0.0,
green: 0.0,
blue: 1.0,
alpha: 1.0
)
blueMaterial.lightingModel = .constant
blueSphere.materials = [blueMaterial]
let blueSphereNode = SCNNode(geometry: blueSphere)
blueSphereNode.simdPosition = simd_float3(-1, -1, 0)
scene.rootNode.addChildNode(blueSphereNode)
// ----------------------------------------------------
// Grey Sphere
let greySphere = SCNSphere(radius: 0.3)
let greyMaterial = SCNMaterial()
greyMaterial.diffuse.contents = UIColor(
red: 0.5,
green: 0.5,
blue: 0.5,
alpha: 1.0
)
greyMaterial.lightingModel = .constant
greySphere.materials = [greyMaterial]
let greySphereNode = SCNNode(geometry: greySphere)
greySphereNode.simdPosition = simd_float3(-1, -3, 0)
scene.rootNode.addChildNode(greySphereNode)
Column of circles
// ----------------------------------------------------
// Custom SCNGeometry using vertex data
struct Vertex {
let x: Float
let y: Float
let z: Float
let r: Float
let g: Float
let b: Float
let a: Float
}
let vertices: [Vertex] = [
Vertex(x: 0.0, y: 2.0, z: 0.0, r: 1.0, g: 1.0, b: 1.0, a: 1.0), // white
Vertex(x: 0.0, y: 1.0, z: 0.0, r: 1.0, g: 0.0, b: 0.0, a: 1.0), // red
Vertex(x: 0.0, y: 0.0, z: 0.0, r: 0.0, g: 1.0, b: 0.0, a: 1.0), // green
Vertex(x: 0.0, y: -1.0, z: 0.0, r: 0.0, g: 0.0, b: 1.0, a: 1.0), // blue
Vertex(x: 0.0, y: -3.0, z: 0.0, r: 0.5, g: 0.5, b: 0.5, a: 1.0), // rgb
Vertex(
x: 0.0, y: -2.0, z: 0.0,
r: Float(B_RED), g: Float(B_GREEN), b: Float(B_BLUE), a: Float(B_ALPHA)
)
]
let vertexData = Data(
bytes: vertices,
count: MemoryLayout<Vertex>.size * vertices.count
)
let positionSource = SCNGeometrySource(
data: vertexData,
semantic: SCNGeometrySource.Semantic.vertex,
vectorCount: vertices.count,
usesFloatComponents: true,
componentsPerVector: 3,
bytesPerComponent: MemoryLayout<Float>.size,
dataOffset: 0,
dataStride: MemoryLayout<Vertex>.size
)
let colourSource = SCNGeometrySource(
data: vertexData,
semantic: SCNGeometrySource.Semantic.color,
vectorCount: vertices.count,
usesFloatComponents: true,
componentsPerVector: 4,
bytesPerComponent: MemoryLayout<Float>.size,
dataOffset: MemoryLayout<Float>.size * 3,
dataStride: MemoryLayout<Vertex>.size
)
let elements = SCNGeometryElement(
data: nil,
primitiveType: .point,
primitiveCount: vertices.count,
bytesPerIndex: MemoryLayout<Int>.size
)
elements.pointSize = 100
elements.minimumPointScreenSpaceRadius = 100
elements.maximumPointScreenSpaceRadius = 100
let spheres = SCNGeometry(
sources: [positionSource, colourSource],
elements: [elements]
)
let sphereNode = SCNNode(geometry: spheres)
let sphereMaterial = SCNMaterial()
sphereMaterial.lightingModel = .constant
spheres.materials = [sphereMaterial]
sphereNode.simdPosition = simd_float3(0,0,0)
scene.rootNode.addChildNode(sphereNode)
Updated based on accepted answer. Converting from sRGB to LinearRGB gives the same result
func srgbToLinear(x: Float) -> Float {
if x <= 0.04045 {
return x / 12.92;
}
return powf((x + 0.055) / 1.055, 2.4)
}
let vertices: [Vertex] = [
Vertex(x: 0.0, y: 2.0, z: 0.0, r: 1.0, g: 1.0, b: 1.0, a: 1.0), // white
Vertex(x: 0.0, y: 1.0, z: 0.0, r: 1.0, g: 0.0, b: 0.0, a: 1.0), // red
Vertex(x: 0.0, y: 0.0, z: 0.0, r: 0.0, g: 1.0, b: 0.0, a: 1.0), // green
Vertex(x: 0.0, y: -1.0, z: 0.0, r: 0.0, g: 0.0, b: 1.0, a: 1.0), // blue
Vertex(x: 0.0, y: -3.0, z: 0.0, r: srgbToLinear(x: 0.5), g: srgbToLinear(x: 0.5), b: srgbToLinear(x: 0.5), a: 1.0), // rgb
Vertex(
x: 0.0, y: -2.0, z: 0.0,
r: srgbToLinear(x: Float(B_RED)),
g: srgbToLinear(x: Float(B_GREEN)),
b: srgbToLinear(x: Float(B_GREEN)),
a: 1.0
)
]
This would be the case if the two types of graphic are generated in different color spaces. For example, if gray for the spheres is interpreted as being in the sRGB as the color space, and the circle are interpreted as generic RGB space then you would see a different in color values.
Consider the following playground code:
It produces an effect almost identical to the one you are seeing. I suspect that the circles are drawn assuming a generic color space and the spheres are drawn assuming sRGB.