SCNParticleSystem: animating "particleColor" property in code - swift

I would like to animate a certain color sequence in a SceneKit Particle System in code only. (Like one can do within the Particle System Editor in XCode.)
I was trying the following, and the App is crashing each time the Animation is attached to the particle system. The compiler does not complain, Xcode does not indicate any error within the syntax. (without the animation part, the particle system works fine)
func particleSystemTesting(shape: SCNGeometry) -> SCNParticleSystem {
let explosion = SCNParticleSystem(named: "explosion.scnp", inDirectory: nil)!
explosion.emitterShape = shape
explosion.birthLocation = .surface
explosion.birthDirection = .surfaceNormal
explosion.isAffectedByGravity = true
explosion.isLightingEnabled = false
explosion.loops = true
explosion.sortingMode = .none
explosion.isBlackPassEnabled = true
explosion.blendMode = .additive
explosion.particleColor = UIColor.white // using as default, should even not be required
// Animation Part
let animation = CABasicAnimation(keyPath: "particleColor")
animation.fromValue = UIColor.blue
animation.toValue = UIColor.red
animation.duration = 2.0
animation.isRemovedOnCompletion = true
explosion.addAnimation(animation, forKey: nil) // causing the crash
return explosion
}
This are the errors the console is printing out:
2020-08-23 18:25:53.281120+0200 MyTestApp[1684:892874] Metal GPU Frame Capture Enabled
2020-08-23 18:25:53.281349+0200 MyTestApp[1684:892874] Metal API Validation Enabled
2020-08-23 18:25:56.563208+0200 MyTestApp[1684:892874] -[SCNParticleSystem copyAnimationChannelForKeyPath:animation:]: unrecognized selector sent to instance 0x101041500
2020-08-23 18:25:56.564524+0200 MyTestApp[1684:892874] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[SCNParticleSystem copyAnimationChannelForKeyPath:animation:]: unrecognized selector sent to instance 0x101041500'
*** First throw call stack:
(0x1998d5654 0x1995f7bcc 0x1997d9dd8 0x1998d97f8 0x1998db71c 0x1ade571f8 0x1ade55f24 0x1ade59268 0x1ade5af2c 0x1ade23d84 0x1adf18cf0 0x199852f2c 0x19984de20 0x19984e29c 0x19984dba8 0x1a39bd344 0x19d9893e4 0x100566384 0x1996d58f0)
libc++abi.dylib: terminating with uncaught exception of type NSException
After some more research and consulting apples docs reference it seems that my first approach was wrong, because that would affect all spawned particles at the same time.
BUT IT STILL DOES NOT WORK AS EXPECTED - all particles are gone/invisible on scene - otherwise no errors. this should change the colours of each particle over time. What is wrong?
func particleSystemTesting(shape: SCNGeometry) -> SCNParticleSystem {
let explosion = SCNParticleSystem(named: "explosion_testing.scnp", inDirectory: nil)!
// let explosion = SCNParticleSystem() // makes no difference
explosion.emitterShape = shape
explosion.birthLocation = .surface
explosion.birthDirection = .surfaceNormal
explosion.isAffectedByGravity = true
explosion.isLightingEnabled = false
explosion.loops = true
explosion.sortingMode = .none
explosion.isBlackPassEnabled = true
explosion.blendMode = .additive
// explosion.particleColor = UIColor.black
let red = UIColor.red
let green = UIColor.green
let blue = UIColor.blue
let yellow = UIColor.yellow
let color1 = SCNVector4(red.redValue, red.greenValue, red.blueValue, 1.0)
let color2 = SCNVector4(green.redValue, green.greenValue, green.blueValue, 1.0)
let color3 = SCNVector4(blue.redValue, blue.greenValue, blue.blueValue, 1.0)
let color4 = SCNVector4(yellow.redValue, yellow.greenValue, yellow.blueValue, 1.0)
let animation = CAKeyframeAnimation()
// animation.keyPath = "color" // has like no effect (?...)
animation.values = [color1,color2,color3,color4]
animation.keyTimes = [0, 0.333, 0.666, 1]
animation.duration = 2.0
animation.isAdditive = false // should overwrite default colours
animation.isRemovedOnCompletion = true
let colorController = SCNParticlePropertyController(animation: animation)
explosion.propertyControllers = [SCNParticleSystem.ParticleProperty.color: colorController]
return explosion
}
Apple Docs says this:
Apple Discussion
This property’s value is a four-component vector (an NSValue object containing an SCNVector4 value for particle property controllers, or an array of four float values for particle event or modifier blocks).
The particle system’s particleColor and particleColorVariation properties determine the initial color for each particle.

I believe the documentation is wrong. Your animation values array should contain UIColor objects.
animation.values = [UIColor.red, UIColor.green, UIColor.blue, UIColor.yellow]

Interesting problem
This: https://developer.apple.com/documentation/scenekit/animation/animating_scenekit_content shows a similar call with an SCNTransaction, so it looks like what you are doing "should" work. However, it wasn't an SCNParticle system component. Particles can be a bit tricky to work with. I'm not great with selector stuff, so I can't really tell from just looking at the code.
particleColorVaration - vector randomizes the color specified. That doesn't sound exactly like what you want to do, but it might get you close - vector specifies ranges (hue, saturation, brightness, alpha) in that order.

Related

SceneKit – Adding vignette to SCNView has no effect

I'm trying to set up a vignette effect within a SCNView. I did this following this tutorial using more or less the same code as I'm unexperienced with the range of the values. But when I apply that to my SCNViews camera object, nothing happens.
The docs about vignette read that it is necessary to set wantsHDR = true so I did that without any noticeable difference.
// scene setup (light, models, etc.)
...
sceneView.backgroundColor = .gray
sceneView.allowsCameraControl = true
let camera = sceneView.scene?.rootNode.camera
camera?.wantsHDR = true
camera?.vignettingPower = 0.6
camera?.bloomIntensity = 1.4
camera?.bloomBlurRadius = 1.0
camera?.fStop = 20.0
camera?.fStop = 5.0
camera?.focusDistance = 1.0
I've only changed the parameters which were marked as deprecated but that wasn't the issue.
I've instanciated the SCNView with the Storyboard and I'm accessing it by having an Outlet in my ViewController and I can use lot of functions with success.
Further I experienced problems setting up MSAA4x with sceneView.antialiasingMode = .multisampling4X. No difference in the outcome. And some more methods/parameters with skybox/environment lighting not doing anything (see this post).
No errors shown in the console.
If you added a new camera to the scene, it would render a vignette.
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position.z = 10
cameraNode.camera?.wantsHDR = true
cameraNode.camera?.vignettingPower = 1
cameraNode.camera?.vignettingIntensity = 1
sceneView.scene?.rootNode.addChildNode(cameraNode)
Your approach doesn't work because you're accessing the default camera.
cameraNode.camera = sceneView.scene?.rootNode.camera

Animating CALayer [filters, compositionFilter, backFilters]

I need to animate one of [filters, compositionFilter, backFilters] on a CALayer in video composition I tried the following code on NSView layer to test if the code works in the first place
view.wantsLayer = true
view.layerUsesCoreImageFilters = true
if let filter = CIFilter.init(name: "CIGaussianBlur", parameters: ["inputRadius": 50]) {
let animation = CABasicAnimation.init(keyPath: "filters")
animation.isRemovedOnCompletion = false
animation.fillMode = .forwards
animation.duration = 2 //for testing
animation.autoreverses = false
animation.fromValue = NSArray.init(array: [])
animation.toValue = NSArray.init(array: [filter])
view.layer.add(animation, forKey: "CIGaussianBlurAnimation")
}
but nothing happens, so I need to know what is the proper way to animate one/all of [filters, compositionFilter, backFilters] properties of CALayer, and whether or not it is supported on AVVideoComposition(on iOS 'I know it is not supported on iOS UIView CALayers')
I found 1/2 the solution
BUT the main question remains: "is there any way to enable CIFilter on CALayer in video composition on iOS"
it is indicated in the documentation, to update any attribute of the filter after is applied to the layer, you should use
layer.setValue(1, forKeyPath: "backgroundFilters.myFilter.inputRadius")
So it means to animate any filter, we should update the filter properties directly in the animation, so the following should be done
for compositingFilter property:
let animation = CABasicAnimation(keyPath: "compositingFilter.inputRadius")
and for a filter in filters property:
filter.name = "filterName"
layer.filters = [someFilters, filter, otherFilters]
let animation = CABasicAnimation(keyPath: "filters.filterName.inputRadius")
and for a filter in backgroundFilters property:
filter.name = "filterName"
layer.backgroundFilters = [someFilters, filter, otherFilters]
let animation = CABasicAnimation(keyPath: "backgroundFilters.filterName.inputRadius")
and set the animation properties, then add it to the layer layer.add(animation, forKey: "animationKey")
NOTE: animationKey could be any value, even a nil value, but reusing the value will remove the previous animation; even when is running

How to make particles in SCNParticleSystem opaque?

I created a SceneKit Scene File > Particle System and I can't figure out how to make all the particles opaque. The default particles alpha setting seems random. I change the image and a few other properties, and took a screen shot:
I've tried:
particle.particleColorVariation = SCNVector4(0, 0, 0, 0)
Which only makes the particles around 80%-90% transparent, but I cannot get it 100% opaque.
To make a particle system be fully opaque you need to set a blendMode instance property to .alpha (default value is .additive) and sortingMode instance property set to .distance (default value is .none)
var blendMode: SCNParticleBlendMode { get set }
var sortingMode: SCNParticleSortingMode { get set }
According to Apple documentation:
.blendMode is the blending mode for compositing particle images into the rendered scene.
There are six compositing blend modes for particles in SceneKit:
.additive
.alpha
.multiply
.replace
.screen
.subtract
Here's how it looks in real code:
let scnView = self.view as! SCNView
scnView.scene = scene
scnView.allowsCameraControl = true
scnView.backgroundColor = NSColor.black
let particleSystem = SCNParticleSystem()
particleSystem.birthRate = 5
particleSystem.blendMode = .alpha // 100% opaque if alpha = 1.0
particleSystem.sortingMode = .distance
particleSystem.particleSize = 1.0
particleSystem.emitterShape = SCNSphere(radius: 5)
particleSystem.particleLifeSpan = 100
particleSystem.particleColor = .red
// No Alpha variation
particleSystem.particleColorVariation = SCNVector4(1, 1, 1, 0)
let particlesNode = SCNNode()
particlesNode.addParticleSystem(particleSystem)
scnView.scene!.rootNode.addChildNode(particlesNode)

How to use multisampling with an MTKView?

I'm trying to get multisampling working with MTKView. I have an MTKView with a delegate. I set the view's sampleCount property to 4. I create a pipeline state descriptor with the rasterSampleCount set to 4, and use that to make a render pipeline state that I use when rendering.
In the delegate's draw(in:) method, I create a render pass descriptor by getting the view's current render pass descriptor and setting the storeAction to multisampleResolve. I've also set tried storeAndMultisampleResolve to no avail.
I have created a resolve texture for the render pass descriptor, and it is the same width and height as the view and the same pixel format.
Given the above, I get a full red frame during rendering. I have used the metal debugger to look at the textures, and both the view's texture and the resolve texture have the correct rendering in them. I'm on an AMD machine where a fully red texture often indicates an uninitialized texture.
Is there anything I need to do to get the rendering to go to the screen?
Here's how I'm setting up the view, pipeline state, and resolve texture:
metalView = newMetalView
metalView.sampleCount = 4
metalView.clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0)
device = newMetalView.device!
let metalLibrary = device.makeDefaultLibrary()!
let vertexFunction = metalLibrary.makeFunction(name: "vertexShader")
let fragmentFunction = metalLibrary.makeFunction(name: "fragmentShader")
let pipelineStateDescriptor = MTLRenderPipelineDescriptor.init()
pipelineStateDescriptor.label = "Particle Renderer"
pipelineStateDescriptor.vertexFunction = vertexFunction
pipelineStateDescriptor.fragmentFunction = fragmentFunction
pipelineStateDescriptor.colorAttachments [ 0 ].pixelFormat = metalView.colorPixelFormat
pipelineStateDescriptor.rasterSampleCount = 4
do {
try pipelineState = device.makeRenderPipelineState(descriptor: pipelineStateDescriptor)
} catch {
NSLog("Unable to create pipeline state")
pipelineState = nil
}
let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: metalView.colorPixelFormat, width: Int(metalView.bounds.width), height: Int(metalView.bounds.height), mipmapped: false)
resolveTexture = device.makeTexture(descriptor: textureDescriptor)!
And here's how I'm drawing:
let commandBuffer = commandQueue.makeCommandBuffer()
commandBuffer?.label = "Partcle Command Buffer"
let renderPassDescriptor = metalView.currentRenderPassDescriptor
renderPassDescriptor?.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 0.0)
renderPassDescriptor?.colorAttachments[0].loadAction = MTLLoadAction.clear
renderPassDescriptor?.colorAttachments[0].storeAction = MTLStoreAction.multisampleResolve
renderPassDescriptor?.colorAttachments[0].resolveTexture = resolveTexture
let renderEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: renderPassDescriptor!)
renderEncoder?.label = "Particle Render Encoder"
renderEncoder?.setViewport(MTLViewport(originX: 0.0, originY: 0.0, width: Double(viewportSize.x), height: Double(viewportSize.y), znear: -1.0, zfar: 1.0))
renderEncoder?.setRenderPipelineState(pipelineState!);
Then I make my draw calls, and then finish up by calling:
renderEncoder?.endEncoding()
commandBuffer?.present(metalView.currentDrawable!)
commandBuffer?.commit()
Here's what the debugger shows is in my textures:
Oddly, while doing that debugging, I accidentally hid Xcode, and for 1 frame, the view showed the correct texture.
What's the initial configuration of renderPassDescriptor (as returned from metalView.currentRenderPassDescriptor?
I believe you want the color attachment's texture set to metalView.multisampleColorTexture and its resolveTexture set to metalView.currentDrawable.texture. That is, it should do the primary, multi-sampled rendering to the multi-sample texture and then that gets resolved to the drawable texture to actually draw it in the view.
I don't know if MTKView sets up its currentRenderPassDescriptor like that automatically when there's a sampleCount > 1. Ideally, it would.

AR with iOS: putting a light in the scene makes everything black?

Ok, I am trying desperately to achieve this sort of warm lighting on my objects when added to my ARScene in Swift/Xcode - warm lighting and little glowing lights around:
To be clear, I do NOT want the objects I add to my scene to look like they belong in the surrounding room. I want them to stand out/ look warm and glow.All the tutorials on ARKit teach you how to mimic the lighting of the actual room.
Xcode has several lighting options, pulling from the surroundings gathered by the camera because with:
if let lightEstimate = session.currentFrame?.lightEstimate
I can print out the warmth, intensity, etc. And I also have these properties currently set to match the light of room:
sceneView.automaticallyUpdatesLighting = true
extension ARSCNView {
func setup() { //SCENE SETUP
antialiasingMode = .multisampling4X
autoenablesDefaultLighting = true
preferredFramesPerSecond = 60
contentScaleFactor = 1.3
if let camera = pointOfView?.camera {
camera.wantsHDR = true
camera.wantsExposureAdaptation = true
camera.exposureOffset = -1
camera.minimumExposure = -1
camera.maximumExposure = 3
}
}
}
I have tried upping the emission on my object's textures and everything but nothing achieves the effect. Adding a light just turns the objects black/no color.
What is wrong here?
To create this type of glowing red neon light result in ARKit. You can do the following.
You need to create a reactor.scnp (scenekit particle System File) and make the following changes to create the glowing red halo. This should be place in your Resources directory of the playground along with the file spark.png
These are the settings to change from the default reactor type. Leave all the other settings alone.
Change the Image animate color to red/orange/red/black
speed factor = 0.1
enable lighting checked
Emitter Shape = Sphere
Image Size = 0.5
Image Intensity = 0.1
Simulation Speed Factor = 0.1
Note: The code below is playground app I use for testing purposes. You just tap anywhere to add the Neon light into the scene. You can place as many neon lights as you like.
import ARKit
import SceneKit
import PlaygroundSupport
import SceneKit.ModelIO
class ViewController: NSObject {
var sceneView: ARSCNView
init(sceneView: ARSCNView) {
self.sceneView = sceneView
super.init()
self.setupWorldTracking()
self.sceneView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(ViewController.handleTap(_:))))
}
private func setupWorldTracking() {
if ARWorldTrackingConfiguration.isSupported {
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = .horizontal
configuration.isLightEstimationEnabled = true
self.sceneView.session.run(configuration, options: [])
}
}
#objc func handleTap(_ gesture: UITapGestureRecognizer) {
let results = self.sceneView.hitTest(gesture.location(in: gesture.view), types: ARHitTestResult.ResultType.featurePoint)
guard let result: ARHitTestResult = results.first else {
return
}
let cylinder = SCNCylinder(radius: 0.05, height: 1)
cylinder.firstMaterial?.emission.contents = UIColor.red
cylinder.firstMaterial?.emission.intensity = 1
let spotLight = SCNNode()
spotLight.light = SCNLight()
spotLight.scale = SCNVector3(1,1,1)
spotLight.light?.intensity = 1000
spotLight.castsShadow = true
spotLight.position = SCNVector3Zero
spotLight.light?.type = SCNLight.LightType.directional
spotLight.light?.color = UIColor.white
let particleSystem = SCNParticleSystem(named: "reactor", inDirectory: nil)
let systemNode = SCNNode()
systemNode.addParticleSystem(particleSystem!)
let node = SCNNode(geometry: cylinder)
let position = SCNVector3Make(result.worldTransform.columns.3.x, result.worldTransform.columns.3.y, result.worldTransform.columns.3.z)
systemNode.position = position
node.position = position
self.sceneView.scene.rootNode.addChildNode(spotLight)
self.sceneView.scene.rootNode.addChildNode(node)
self.sceneView.scene.rootNode.addChildNode(systemNode)
}
}
let sceneView = ARSCNView()
let viewController = ViewController(sceneView: sceneView)
sceneView.autoenablesDefaultLighting = false
PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = viewController.sceneView
If your looking for a neon/glowing effect in your scene... these previous answers to a similar question asked about glowing/neon lighting should give you some guidance.
As you will see from the answers provided sceneKit does not have built-in support for volumetric lighting, all the approaches are more hacks to achieve a similar effect to a glowing light.
iOS SceneKit Neon Glow
To add a "red" directional light effect to your scene... which is an alternative to using sceneView.autoenablesDefaultLighting = true
let myLight = SCNNode()
myLight.light = SCNLight()
myLight.scale = SCNVector3(1,1,1)
myLight.intensity = 1000
myLight.position = SCNVector3Zero
myLight.light?.type = SCNLight.LightType.directional
myLight.light?.color = UIColor.red
// add the light to the scene
sceneView.scene.rootNode.addChildNode(myLight)
note: This effect makes all the objects in the scene more reddish.