10

I have an AR application which uses SceneKit, and imports a video on to scene using AVPlayer and thereby adding it as a child node of an SKVideo node.

The video is visible as it is supposed to, but the transparency in the video is not achieved.

Code as follows:

let spriteKitScene = SKScene(size: CGSize(width: self.sceneView.frame.width, height: self.sceneView.frame.height))
spriteKitScene.scaleMode = .aspectFit

guard let fileURL = Bundle.main.url(forResource: "Triple_Tap_1", withExtension: "mp4") else {
    return
}

let videoPlayer = AVPlayer(url: fileURL)
videoPlayer.actionAtItemEnd = .none

let videoSpriteKitNode = SKVideoNode(avPlayer: videoPlayer)
videoSpriteKitNode.position = CGPoint(x: spriteKitScene.size.width / 2.0, y: spriteKitScene.size.height / 2.0)
videoSpriteKitNode.size = spriteKitScene.size
videoSpriteKitNode.yScale = -1.0
videoSpriteKitNode.play()
spriteKitScene.backgroundColor = .clear          
spriteKitScene.addChild(videoSpriteKitNode)

let background = SCNPlane(width: CGFloat(2), height: CGFloat(2))
background.firstMaterial?.diffuse.contents = spriteKitScene

let backgroundNode = SCNNode(geometry: background)
backgroundNode.position = position
backgroundNode.constraints = [SCNBillboardConstraint()]
backgroundNode.rotation.z = 0
self.sceneView.scene.rootNode.addChildNode(backgroundNode)

// Create a transform with a translation of 0.2 meters in front of the camera.
var translation = matrix_identity_float4x4
translation.columns.3.z = -0.2
let transform = simd_mul((self.session.currentFrame?.camera.transform)!, translation)

// Add a new anchor to the session.
let anchor = ARAnchor(transform: transform)
self.sceneView.session.add(anchor: anchor)

What could be the best way to implement the transparency of the Triple_Tap_1 video in this case. I have gone through some stack overflow questions on this topic, and found the only solution to be a KittyBoom repository that was created somewhere in 2013, using Objective C.

I'm hoping that the community can reveal a better solution for this problem. GPUImage library is not something I could get to work.

SWAT
  • 1,107
  • 8
  • 19
  • Possible duplicate of [iPhone SDK - How to play a video with transparency?](https://stackoverflow.com/questions/1401517/iphone-sdk-how-to-play-a-video-with-transparency) – Tamás Sengel Sep 14 '17 at 20:40
  • You could try doing the following: create a video that has a transparency mask of the footage; add it to `material.transparent.contents`; use a surface shader modifier that will be "cutting out the alpha" from `_surface.diffuse` using `_surface.transparent`. If that sounds like a somewhat reasonable solution to you, I will elaborate. – Lësha Turkowski Sep 20 '17 at 12:38
  • Yes please. Would help a lot. – SWAT Sep 21 '17 at 06:45
  • Note that in recent versions of SceneKit using a `SKScene` and a `SKVideoNode` is not necessary. You can directly set the `AVPlayer` as the contents of a `SCNMaterialProperty` instance. – mnuages Apr 22 '18 at 22:34

1 Answers1

12

I've came up with two ways of making this possible. Both utilize surface shader modifiers. Detailed information on shader modifiers can be found in Apple Developer Documentation.

Here's an example project I've created.


1. Masking

  1. You would need to create another video that represents a transparency mask. In that video black = fully opaque, white = fully transparent (or any other way you would like to represent transparency, you would just need to tinker the surface shader).

  2. Create a SKScene with this video just like you do in the code you provided and put it into material.transparent.contents (the same material that you put diffuse video contents into)

    let spriteKitOpaqueScene = SKScene(...)
    let spriteKitMaskScene = SKScene(...)
    ... // creating SKVideoNodes and AVPlayers for each video etc
    
    let material = SCNMaterial()
    material.diffuse.contents = spriteKitOpaqueScene
    material.transparent.contents = spriteKitMaskScene
    
    let background = SCNPlane(...)
    background.materials = [material]
    
  3. Add a surface shader modifier to the material. It is going to "convert" black color from the mask video (well, actually red color, since we only need one color component) into alpha.

    let surfaceShader = "_surface.transparent.a = 1 - _surface.transparent.r;"
    material.shaderModifiers = [ .surface: surfaceShader ]
    

That's it! Now the white color on the masking video is going to be transparent on the plane.

However you would have to take extra care of syncronizing these two videos since AVPlayers will probably get out of sync. Sadly I didn't have time to address that in my example project (yet, I will get back to it when I have time). Look into this question for a possible solution.

Pros:

  • No artifacts (if syncronized)
  • Precise

Cons:

  • Requires two videos instead of one
  • Requires synchronisation of the AVPlayers

2. Chroma keying

  1. You would need a video that has a vibrant color as a background that would represent parts that should be transparent. Usually green or magenta are used.

  2. Create a SKScene for this video like you normally would and put it into material.diffuse.contents.

  3. Add a chroma key surface shader modifier which will cut out the color of your choice and make these areas transparent. I've lent this shader from GPUImage and I don't really know how it actually works. But it seems to be explained in this answer.

     let surfaceShader =
    """
    uniform vec3 c_colorToReplace = vec3(0, 1, 0);
    uniform float c_thresholdSensitivity = 0.05;
    uniform float c_smoothing = 0.0;
    
    #pragma transparent
    #pragma body
    
    vec3 textureColor = _surface.diffuse.rgb;
    
    float maskY = 0.2989 * c_colorToReplace.r + 0.5866 * c_colorToReplace.g + 0.1145 * c_colorToReplace.b;
    float maskCr = 0.7132 * (c_colorToReplace.r - maskY);
    float maskCb = 0.5647 * (c_colorToReplace.b - maskY);
    
    float Y = 0.2989 * textureColor.r + 0.5866 * textureColor.g + 0.1145 * textureColor.b;
    float Cr = 0.7132 * (textureColor.r - Y);
    float Cb = 0.5647 * (textureColor.b - Y);
    
    float blendValue = smoothstep(c_thresholdSensitivity, c_thresholdSensitivity + c_smoothing, distance(vec2(Cr, Cb), vec2(maskCr, maskCb)));
    
    float a = blendValue;
    _surface.transparent.a = a;
    """
    
    shaderModifiers = [ .surface: surfaceShader ]
    

    To set uniforms use setValue(:forKey:) method.

    let vector = SCNVector3(x: 0, y: 1, z: 0) // represents float RGB components
    setValue(vector, forKey: "c_colorToReplace")
    setValue(0.3 as Float, forKey: "c_smoothing")
    setValue(0.1 as Float, forKey: "c_thresholdSensitivity")
    

    The as Float part is important, otherwise Swift is going to cast the value as Double and shader will not be able to use it.

    But to get a precise masking from this you would have to really tinker with the c_smoothing and c_thresholdSensitivity uniforms. In my example project I ended up having a little green rim around the shape, but maybe I just didn't use the right values.

Pros:

  • only one video required
  • simple setup

Cons:

  • possible artifacts (green rim around the border)
Lësha Turkowski
  • 1,361
  • 1
  • 10
  • 21
  • I will try this out. – SWAT Sep 23 '17 at 11:12
  • You can read more about problems with the ChromaKey approach here: https://stackoverflow.com/a/17307035/763355 – MoDJ Sep 25 '17 at 00:32
  • Hey Lësha, I downloaded your example project and used your video and the ChromaKeyMaterial and VideoContent classes in my own project and that works fine. But when I use my own video the video is totally invisible. Can you help me fix this problem? I have the same green background you are using in the chromakeyfootage.mp4 video. – KNV May 30 '18 at 10:15
  • @KNV does the video work without using ChromaKeyMaterial (just plain SCNMaterial with VideoContent as diffuse contents)? – Lësha Turkowski May 30 '18 at 11:08
  • @LëshaTurkowski No, your video does also not work without the ChromaKeyMaterial. – KNV May 30 '18 at 11:47
  • just checking, did you have any idea how to use chroma with RealityKit? I am using this library to remove the chroma key color from the video and it works well. But maybe there are easier ways? https://stackoverflow.com/a/70110548/9497800 – multitudes Nov 25 '21 at 12:39