Hello fellow developers
here is something that I don t fully grasp :
1/ I have a fake SceneKit with two nodes both having light
2/ I have a small widget to explore those lights and tweak some param
-> in the small widget I can t update a toggle item when a new light is selected while other params are updated !
here is a short sample that illustrate what I am trying to resolve
import SwiftUI
import SceneKit
class ShortScene {
var scene = SCNScene()
var lightNodes : [SCNNode] {
get {scene.rootNode.childNodes(passingTest: { current, stop in current.light != nil} ) }
}
init() {
let light1 = SCNLight()
light1.castsShadow = false
light1.type = .omni
light1.intensity = 100
let nodelight1 = SCNNode()
nodelight1.light = light1
nodelight1.name = "nodeLight1"
scene.rootNode.addChildNode(nodelight1)
let light2 = SCNLight()
light2.castsShadow = false
light2.type = .ambient
light2.intensity = 300
let nodelight2 = SCNNode()
nodelight2.light = light2
nodelight2.name = "nodeLight2"
scene.rootNode.addChildNode(nodelight2)
}
}
extension SCNLight : ObservableObject {}
extension SCNNode : ObservableObject {}
struct LightViewEx : View {
@ObservedObject var lightParam : SCNLight
@ObservedObject var lightNode : SCNNode
var bindCol : Binding<Color>
@State var castShadows : Bool
init( _ _lightNode : SCNNode) {
if let _light = _lightNode.light {
lightParam = _light
lightNode = _lightNode
bindCol = Binding<Color>( get: { if let _lightcol = _lightNode.light!.color as! NSColor? { return Color(_lightcol)} else { return Color.red } },
set: { newCol in _lightNode.light!.color = NSColor(newCol) } )
castShadows = _lightNode.light!.castsShadow
print( "For \(lightNode.name!) : CShadows \(castShadows)")
} else {
fatalError("No Light attached to Node")
}
}
var body : some View {
VStack(alignment: .leading) {
Text("Light Params")
Picker("Type",selection : $lightParam.type) {
Text("IES").tag(SCNLight.LightType.IES)
Text("Ambient").tag(SCNLight.LightType.ambient)
Text("Directionnal").tag(SCNLight.LightType.directional)
Text("Directionnal").tag(SCNLight.LightType.directional)
Text("Omni").tag(SCNLight.LightType.omni)
Text("Probe").tag(SCNLight.LightType.probe)
Text("Spot").tag(SCNLight.LightType.spot)
Text("Area").tag(SCNLight.LightType.area)
}
ColorPicker("Light Color", selection: bindCol)
Text("Intensity")
TextField("Intensity", value: $lightParam.intensity, formatter: NumberFormatter())
Divider()
// Toggle("shadows", isOn: $lightParam.castsShadow ).onChange(of: lightParam.castsShadow, { lightParam.castsShadow.toggle() })
Toggle("CastShadows", isOn: $castShadows )
.onChange(of: castShadows) { lightParam.castsShadow = castShadows;print("castsShadows changed to \(castShadows)") }
}
}
}
struct sceneView : View {
@State var _lightIdx : Int = 0
@State var shortScene = ShortScene()
var body : some View {
VStack(alignment: .leading) {
if shortScene.lightNodes.isEmpty == false {
Picker("Lights",
selection: $_lightIdx) {
ForEach(0..<shortScene.lightNodes.count, id: \.self) { index in
Text(shortScene.lightNodes[index].name ?? "NoName" ).tag(index)
}
}
GridRow(alignment: .top) {
LightViewEx(shortScene.lightNodes[_lightIdx])
}
}
}
}
}
struct testUIView: View {
var body: some View {
sceneView()
}
}
#Preview {
testUIView()
}
Something is obviously not right ! Anyone has some idea ?
Post
Replies
Boosts
Views
Activity
Hello
I am a bit stuck with a silly challenge I set myself : I want to have a node with a simple geometry ( let's say a triangle ) and I want to render those triangles N time with passing in some information per triangle. Let s say an offset to apply at shader time.
I understand that may be I should create a sourcegeo and create multiple nodes to reflect that but here the point is to implement some Metal stuff in the renderNode override ( SCNode.rendererDelegate / SCNRenderNodeDelegate ).
so I set up some vertex shader like this :
vertex VertexOut brush_vertex_main(const VertexIn vertexIn [[stage_in]],
constant BrushNodeBuffer& scn_node [[buffer(BufferNode)]],
constant BrushInstances *instances [[buffer(BufferBrush)]],
uint instanceID [[instance_id]]) {
float4 vertexOffset = float4(instances[instanceID].offset.xyz,1.0) + float4(vertexIn.position.xyz,1.0);
// float4 vertexOffset = float4(vertexIn.position.xyz,1.0);
VertexOut out = {
.position = scn_node.modelViewProjectionTransform * vertexOffset,
.color = vertexIn.color };
return out;
}
Did some binding as well to declare a pipelinerenderState like for eg
let defLib = wd.device!.makeDefaultLibrary()
let vertFunc = defLib?.makeFunction(name: vertexFunctionName)
let fragFunc = defLib?.makeFunction(name: fragmentFunctionName)
// add geo desc ( geometries should be doing that underhood anyway
let vertexDescriptor = MTLVertexDescriptor()
// pos (SCNVertexSemanticPosition)
vertexDescriptor.attributes[0].format = .float3
vertexDescriptor.attributes[0].bufferIndex = 0
vertexDescriptor.attributes[0].offset = 0
// color ( SCNVertexSemanticColor)
vertexDescriptor.attributes[3].format = .float3
vertexDescriptor.attributes[3].bufferIndex = 0
vertexDescriptor.attributes[3].offset = MemoryLayout<simd_float3>.stride
vertexDescriptor.layouts[0].stride = MemoryLayout<simd_float3>.stride * 2
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.vertexFunction = vertFunc
pipelineDescriptor.fragmentFunction = fragFunc
pipelineDescriptor.vertexDescriptor = vertexDescriptor
did some buffers creation, setting them properly in the rendering loop
rendererCmdEnc.setRenderPipelineState(brushRenderPipelineState!)
rendererCmdEnc.setVertexBuffer(vertBuff!, offset: 0, index: 0)
// node info
rendererCmdEnc.setVertexBuffer(nodeBuff! , offset: 0, index: Int(BufferNode.rawValue))
// per instance info
rendererCmdEnc.setVertexBuffer(primBuff! , offset: 0, index: Int(BufferBrush.rawValue))
rendererCmdEnc.drawIndexedPrimitives(type: .triangle,
indexCount: primitiveIdx.count,
indexType: .uint16,
indexBuffer: indexBuff!,
indexBufferOffset: 0,
instanceCount: 6)
and I keep banging my head when this executes : I have a miss match between my renderpipelineState vs the RenderPassDescriptor. Either it s the colorAttachment or the sample count/rastersamplecount that is invalid.
-[MTLDebugRenderCommandEncoder setRenderPipelineState:]:1604: failed assertion `Set Render Pipeline State Validation
For depth attachment, the texture sample count (1) does not match the renderPipelineState rasterSampleCount (4).
The color sample count (1) does not match the renderPipelineState's color sample count (4)
The raster sample count (1) does not match the renderPipelineState's raster sample count (4)
I used the passdescriptor collorattachment to be a close as possible when describing the renderpipelinestate. changed the rastersamplecount. Tried without any specific for collorattachment etc... Alas, either the API validation will tell me I have the wrong colorAttachment info when I set up the renderpipelinestate in the renderloop and if I fixed the colorattach info at renderStatePipeline creation, some invalid sample count.
In a nutshell : is there any way to do this kind of geo instancing in a single node using SceneKit ?
thanks in advance for any support you would find interesting to provide!