How to use SCNBufferBindingBlock in SceneKit? - swift

I'm looking at SceneKit's handle binding method with the SCNBufferBindingBlock call back as described here:
https://developer.apple.com/documentation/scenekit/scnbufferbindingblock
Does anyone have an example of how this works?
let program = SCNProgram()
program.handleBinding(ofBufferNamed: "", frequency: .perFrame) { (steam, theNode, theShadable, theRenderer) in
}
To me it reads like I can use a *.metal shader on a SCNNode without having to go through the hassle of SCNTechniques....any takers?

Just posting this in case someone else came here looking for a concise example. Here's how SCNProgram's handleBinding() method can be used with Metal:
First define a data structure in your .metal shader file:
struct MyShaderUniforms {
float myFloatParam;
float2 myFloat2Param;
};
Then pass this as an argument to a shader function:
fragment half4 myFragmentFunction(MyVertex vertexIn [[stage_in]],
constant MyShaderUniforms& shaderUniforms [[buffer(0)]]) {
...
}
Next, define the same data structure in your Swift file:
struct MyShaderUniforms {
var myFloatParam: Float = 1.0
var myFloat2Param = simd_float2()
}
Now create an instance of this data structure, changes its values and define the SCNBufferBindingBlock:
var myUniforms = MyShaderUniforms()
myUniforms.myFloatParam = 3.0
...
program.handleBinding(ofBufferNamed: "shaderUniforms", frequency: .perFrame) { (bufferStream, node, shadable, renderer) in
bufferStream.writeBytes(&myUniforms, count: MemoryLayout<MyShaderUniforms>.stride)
}
Here, the string passed to ofBufferNamed: corresponds to the argument name in the fragment function. The block's bufferStream property then contains the user-defined data type MyShaderUniforms which can then be written to with updated values.

The .handleBinding(ofBufferNamed:frequency:handler:) method registers a block for SceneKit to call at render time for binding a Metal buffer to the shader program. This method can only be used with Metal or OpenGL shading language based programs. SCNProgram object helps perform this custom rendering. Program object contains a vertex shader and a fragment shader. Using a program object completely replaces SceneKit’s rendering. Your shaders take input from SceneKit and become responsible for all transform, lighting and shading effects you want to produce. Use .handleBinding() method to associate a block with a Metal shader program to handle setup of a buffer used in that shader.
Here's a link to Developer Documentation on SCNProgram class.
Also you need an instance method writeBytes(_:count:) that copies all your necessary data bytes into the underlying Metal buffer for use by a shader.
SCNTechniqueclass specifically made for post-processing SceneKit's rendering of a scene using additional drawing passes with custom Metal or OpenGL shaders. Using SCNTechnique you can create such effects as color grading or displacement, motion blur and render ambient occlusion as well as other render passes.
Here is a first code's excerpt how to properly use .handleBinding() method:
func useTheseAPIs(shadable: SCNShadable,
bufferStream: SCNBufferStream
voidPtr: UnsafeMutableRawPointer,
bindingBlock: #escaping SCNBindingBlock,
bufferFrequency: SCNBufferFrequency,
bufferBindingBlock: #escaping SCNBufferBindingBlock,
program: SCNProgram) {
bufferStream.writeBytes(voidPtr, count: 4)
shadable.handleBinding!(ofSymbol: "symbol", handler: bindingBlock)
shadable.handleUnbinding!(ofSymbol: "symbol", handler: bindingBlock)
program.handleBinding(ofBufferNamed: "pass",
frequency: bufferFrequency,
handler: bufferBindingBlock)
}
And here is a second code's excerpt:
let program = SCNProgram()
program.delegate = self as? SCNProgramDelegate
program.vertexShader = NextLevelGLContextYUVVertexShader
program.fragmentShader = NextLevelGLContextYUVFragmentShader
program.setSemantic(
SCNGeometrySource.Semantic.vertex.rawValue,
forSymbol: NextLevelGLContextAttributeVertex,
options: nil)
program.setSemantic(
SCNGeometrySource.Semantic.texcoord.rawValue,
forSymbol: NextLevelGLContextAttributeTextureCoord,
options: nil)
if let material = self._material {
material.program = program
material.handleBinding(ofSymbol: NextLevelGLContextUniformTextureSamplerY, handler: {
(programId: UInt32, location: UInt32, node: SCNNode?, renderer: SCNRenderer) in
glUniform1i(GLint(location), 0);
})
material.handleBinding(ofSymbol: NextLevelGLContextUniformTextureSamplerUV, handler: {
(programId: UInt32, location: UInt32, node: SCNNode?, renderer: SCNRenderer) in
glUniform1i(GLint(location), 1);
})
}
Also, look at Simulating refraction in SceneKit
SO post.

Related

Bevy - Have multiple Sprites Sheets per Entity

I have a Entity containing a Srite Sheet and class instance
let texture_handle = asset_server.load("turret_idle.png");
let texture_atlas: TextureAtlas = TextureAtlas::from_grid(texture_handle, ...);
let texture_atlas_handle = texture_atlases.add(texture_atlas);
let mut turret = Turret::create(...);
commands.spawn_bundle(SpriteSheetBundle {
texture_atlas: texture_atlas_handle,
transform: Transform::from_xyz(pos),
..default()
})
.insert(turret)
.insert(AnimationTimer(Timer::from_seconds(0.04, true)));
The AnimationTimer will then be used in a query together with the Texture Atlas Handle to render the next sprite
fn animate_turret(
time: Res<Time>,
texture_atlases: Res<Assets<TextureAtlas>>,
mut query: Query<(
&mut AnimationTimer,
&mut TextureAtlasSprite,
&Handle<TextureAtlas>,
)>,
) {
for (mut timer, mut sprite, texture_atlas_handle) in &mut query {
timer.tick(time.delta());
if timer.just_finished() {
let texture_atlas = texture_atlases.get(texture_atlas_handle).unwrap();
sprite.index = (sprite.index + 1) % texture_atlas.textures.len();
}
}
}
This works perfectly fine as long as the tower is idle thus plays the idle animation. As soon as a target is found and attacked, I want to display another sprite sheet instead.
let texture_handle_attack = asset_server.load("turret_attack.png");
Unfortunately, It seems that I cannot add multiple TextureAtlas Handles to a Sprite Sheet Bundle and decide later which one to render. How do I solve this? I thought about merging all animations into one Sprite Sheet already but this is very messy as they have different frames.
Maybe create a struct with all the different handles you need and add it as a resource? Then you need a component for the enum states "idle", "attacked" etc.. and a system that handles setting the correct handle in texture_atlas from your resource handles.

How can I get rid of blurry textures in Metal?

I just created a Metal template and changed up the code a little. I switched out the default colormap with the Minecraft 16x16 lapis ore texture, but for some reason they are blurred out when they are low resolution. I am trying to achieve that pixelated, Minecraft look and so would like to know how to disable this blurring/filtering.
Is there a way to load/present assets without this blurring? Here is my load asset function:
class func loadTexture(device: MTLDevice, textureName: String) throws -> MTLTexture {
/// Load texture data with optimal parameters for sampling
return try MTKTextureLoader(device: device).newTexture(name: textureName, scaleFactor: 1.0, bundle: nil, options: [
MTKTextureLoader.Option.textureUsage: NSNumber(value: MTLTextureUsage.shaderRead.rawValue),
MTKTextureLoader.Option.textureStorageMode: NSNumber(value: MTLStorageMode.`private`.rawValue)
])
}
Here is a screenshot of the blurry cube I'm getting:
In your texture sample call (in the shader), you need to set your magnification filter to 'nearest', instead of 'linear', like so (assuming your sampler is declared inline inside your shader):
constexpr sampler textureSampler (mag_filter::nearest, // <-- Set this to 'nearest' if you don't want any filtering on magnification
min_filter::nearest);
// Sample the texture to obtain a color
const half4 colorSample = colorTexture.sample(textureSampler, in.textureCoordinate);

Connection between [[stage_in]], MTLVertexDescriptor and MTKMesh

What is the connection between:
Using [[stage_in]] in a Metal Shader
Using MTLVertexDescriptor
Using MTKMesh
For example
Is it possible to use [[stage_in]] without using MTLVertexDescriptor?
Is it possible to use MTLVertexDescriptor without using MTKMesh, but an array of a custom struct based data structure? Such as struct Vertex {...}, Array<Vertex>?
Is it possible to use MTKMesh without using MTLVertexDescriptor? For example using the same struct based data structure?
I didn't find this information on the internet, and the Metal Shading Language Specification doesn't even include the words "descriptor" or "mesh".
No. If you try to create a render pipeline state from a pipeline descriptor without a vertex descriptor, and the corresponding vertex function has a [[stage_in]] parameter, the pipeline state creation call will fail.
Yes. After all, when you draw an MTKMesh, you're still obligated to call setVertexBuffer(...) with the buffers wrapped by the mesh's constituent MTKMeshBuffers. You could just as readily create an MTLBuffer yourself and copy your custom vertex structs into it.
Yes. Instead of having a [[stage_in]] parameter, you'd have a parameter attributed with [[buffer(0)]] (assuming all of the vertex data is interleaved in a single vertex buffer) of type MyVertexType *, as well as a [[vertex_id]] parameter that tells you where to index into that buffer.
Here's an example of setting the vertex buffers from an MTKMesh on a render command encoder:
for (index, vertexBuffer) in mesh.vertexBuffers.enumerated() {
commandEncoder.setVertexBuffer(vertexBuffer.buffer, offset: vertexBuffer.offset, index: index)
}
vertexBuffer is of type MTKMeshBuffer, while its buffer property is of type MTLBuffer; I mention this because it can be confusing.
Here is one way in which you might create a vertex descriptor to tell Model I/O and MetalKit to lay out the mesh data you're loading:
let mdlVertexDescriptor = MDLVertexDescriptor()
mdlVertexDescriptor.attributes[0] = MDLVertexAttribute(name: MDLVertexAttributePosition, format: MDLVertexFormat.float3, offset: 0, bufferIndex: 0)
mdlVertexDescriptor.attributes[1] = MDLVertexAttribute(name: MDLVertexAttributeNormal, format: MDLVertexFormat.float3, offset: 12, bufferIndex: 0)
mdlVertexDescriptor.attributes[2] = MDLVertexAttribute(name: MDLVertexAttributeTextureCoordinate, format: MDLVertexFormat.float2, offset: 24, bufferIndex: 0)
mdlVertexDescriptor.layouts[0] = MDLVertexBufferLayout(stride: 32)
You can create a corresponding MTLVertexDescriptor in order to create a render pipeline state suitable for rendering such a mesh:
let vertexDescriptor = MTKMetalVertexDescriptorFromModelIO(mdlVertexDescriptor)!
Here's a vertex struct that matches that layout:
struct VertexIn {
float3 position [[attribute(0)]];
float3 normal [[attribute(1)]];
float2 texCoords [[attribute(2)]];
};
Here's a stub vertex function that consumes one of these vertices:
vertex VertexOut vertex_main(VertexIn in [[stage_in]])
{
}
And finally, here's a vertex struct and vertex function you could use to render the exact same mesh data without a vertex descriptor:
struct VertexIn {
packed_float3 position;
packed_float3 normal;
packed_float2 texCoords;
};
vertex VertexOut vertex_main(device VertexIn *vertices [[buffer(0)]],
uint vid [[vertex_id]])
{
VertexIn in = vertices[vid];
}
Note that in this last case, I need to mark the struct members as packed since by default, Metal's simd types are padded for alignment purposes (specifically, the stride of a float3 is 16 bytes, not 12 as we requested in our vertex descriptor).

SpriteKit Particle Emitter Multi Image

I'm trying to use SKSprite Particle Emitter with Swift.
But I want use a number of different textures in my emitter.
Is it possible to: have many images, and, have the emitter use the images randomly, instead of using only one image?
Thanks
Suppose you designed your emitter with one texture and saved it as "original.sks", and you have an array with the textures called textures:
var emitters:[SKEmitterNode] = []
for t in textures {
let emitter = SKEmitterNode(fileNamed: "original.sks")!
emitter.particleTexture = t
emitter.numParticlesToEmit /= CGFloat(emitters.count)
emitter.particleBirthRate /= CGFloat(emitters.count)
emitters.append(emitter)
}
Now you have an array of emitters instead of a single one. Whatever you'd do with your emitter, just do it with the array:
// What you'd do with a single emitter:
addChild(someNormalEmitter)
someNormalEmitter.run(someAction)
...
// How to do the same with the array:
emitters.forEach {
self.addChild($0)
$0.run(someAction)
...
}
Of course, you can also subclass SKEmitterNode so that it contains other SKEmitterNode children and propagates all the usual emitter methods and actions and properties to the children… depending on your needs.

Adding and transitioning animations in SceneKit

I looked at Banana game from WWDC which is written in Objective-C was trying to convert the code to Swift for importing animation and transitioning between them but I am having problems running the animations in swift from DAE files.
I have exporter DAE files in both AutoDesk format from 3dsMax and in openCollada format. The Autodesk format the animation is for each bone so I am unable to call an animation by name so I just import the scene and do the following for the animation to start as soon as the file loads.
scene = SCNScene(named: "monster.scnassets/monsterScene.DAE")
scene2 = SCNScene(named:"monster.scnassets/monster.DAE")
var heroNode = SCNNode()
heroNode = scene.rootNode.childNodeWithName("heroNode", recursively: false)!
var nodeArray = scene2.rootNode.childNodes
for childNode in nodeArray {
heroNode.addChildNode(childNode as SCNNode)
}
Although the animation plays as soon as the scene starts I done know how to store the animation.
If I export the collada file using openCollada. I can use the following to run the get and run the animation as there is just one animation for the whole object instead of each bone in case of AutoDesk collada format. By this way I can store the animation also using CAAnimation.
var anim = scene2.rootNode.animationForKey("monster-1")
childNode.addAnimation(anim, forKey: "monster-1")
But then the character runs at an angle and also runs back and forth instead of running at the same spot.
Also the lighting is better using openCollada. I just would like to use openCollada instead of autodesk collada export. Right now I am using openCollada format for exporting the scene and autodesk for exporting character.
How do store animations in SceneKit/Swift and transition between them? Thanks.
If you don't change the default options when loading a scene from a file, all animations in the scene immediately and automatically attach to their target nodes and play. (See the Animation Import Options in the SceneKit API reference.)
If you want to load the animations from a scene file and hold on to them for attaching to nodes (that is, playing) later, you're better off loading them with the SCNSceneSource class. In addition, you can (but don't have to) store your base model in one file and animations in other files.
Just look at this Bananas animation loading code. Just look at it.*
// In AAPLGameLevel.m:
SCNNode *monkeyNode = [AAPLGameSimulation loadNodeWithName:nil fromSceneNamed:#"art.scnassets/characters/monkey/monkey_skinned.dae"];
AAPLMonkeyCharacter *monkey = [[AAPLMonkeyCharacter alloc] initWithNode:monkeyNode];
[monkey createAnimations];
// In AAPLSkinnedCharacter.m (parent class of AAPLMonkeyCharacter):
+ (CAAnimation *)loadAnimationNamed:(NSString *)animationName fromSceneNamed:(NSString *)sceneName
{
NSURL *url = [[NSBundle mainBundle] URLForResource:sceneName withExtension:#"dae"];
SCNSceneSource *sceneSource = [SCNSceneSource sceneSourceWithURL:url options:nil ];
CAAnimation *animation = [sceneSource entryWithIdentifier:animationName withClass:[CAAnimation class]];
//...
}
// In AAPLMonkeyCharacter.m:
- (void)update:(NSTimeInterval)deltaTime
{
// bunch of stuff to decide whether/when to play animation, then...
[self.mainSkeleton addAnimation:[self cachedAnimationForKey:#"monkey_get_coconut-1"] forKey:nil];
//...
}
What's going on here:
There's a custom class managing the animated character. It owns a SCNNode containing the character model, as well as a bunch of CAAnimations for all of the things the model can do (idle/jump/throw/etc).
That class is initialized by passing the character node loaded from one DAE file. (AAPLGameSimulation loadNodeWithName:fromSceneNamed: is a convenience wrapper around loading a SCNScene from a file and grabbing a named node out of it.) That DAE file contains only the character model, with no animations.
Then, AAPLMonkeyCharacter loads (and stores references to) the animations it needs from the separate DAE files containing each animation. This is where SCNSceneSource comes in — it lets you grab animations out of the file without playing them.
When it's time to play, the monkey class calls addAnimation:forKey: to run the animation on its main node.
Translating to Swift and applying to your problem — where you seem to have all animations in the same file — I'd do something like this (vague outline of a hypothetical class):
class Monster {
let node: SCNNode
let attackAnimation: CAAnimation
init() {
let url = NSBundle.mainBundle().URLForResource(/* dae file */)
let sceneSource = SCNSceneSource(URL: url, options: [
SCNSceneSourceAnimationImportPolicyKey : SCNSceneSourceAnimationImportPolicyDoNotPlay
])
node = sceneSource.entryWithIdentifier("monster", withClass: SCNNode.self)
attackAnimation = sceneSource.entryWithIdentifier("monsterIdle", withClass: CAAnimation.self)
}
func playAttackAnimation() {
node.addAnimation(attackAnimation, forKey: "attack")
}
}
The key bits:
SCNSceneSourceAnimationImportPolicyDoNotPlay makes sure that nodes loaded from the scene source don't start with animations attached/playing.
You have to load the animations separately with entryWithIdentifier:withClass:. Be sure to configure them how you like (repeating, fade duration etc) before attaching to the nodes.
2022 code fragment.
In terms of the swift sample in #rickster 's answer.
It seems the code is now more like:
let p = Bundle.main.url(forResource: "File Name", withExtension: "dae")!
source = SCNSceneSource(url: p, options: nil)!
let geom = source.entryWithIdentifier("geometry316",
withClass: SCNGeometry.self)!
as SCNGeometry
yourDragonNode = SCNNode(geometry: geom)
yourAnime = source.entryWithIdentifier("unnamed_animation__0",
withClass: CAAnimation.self)!
In terms of how to get the "mystery strings":
"geometry316" and "unnamed_animation__0" in the example.
notice: https://stackoverflow.com/a/75088130/294884
and: https://stackoverflow.com/a/56787980/294884