How to Draw a Scnnode Always in Front of Others

Is there a way to draw a SCNNode always in front of others?

As explained in my previous answer, the accepted answer isn't optimal and works properly only for billboards, huds and other generally flat objects (not necessarily entirely 2D). When using 3D objects and disabling reading from the depth buffer and objects like the one in the image in above, it won't render correctly from every angle. I.e a 3D object needs to read from the depth buffer to detect its own pixels and depth. That all said, I present the correct answer:

SCNTechnique

In short, render 2 additional passes. One for the control gizmo (DRAW_NODE), and one to mix it together with the scene by another pass (DRAW_QUAD, with a shader that uses the previous passes as inputs).

Following is the techique's scntec.plist contents:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>passes</key>
<dict>
<key>gizmoonly</key>
<dict>
<key>colorStates</key>
<dict>
<key>clear</key>
<true/>
<key>clearColor</key>
<string>0.5 0.5 0.5 0.0</string>
</dict>
<key>depthStates</key>
<dict>
<key>clear</key>
<true/>
</dict>
<key>inputs</key>
<dict>
<key>colorSampler</key>
<string>COLOR</string>
</dict>
<key>outputs</key>
<dict>
<key>color</key>
<string>gizmonode</string>
</dict>
<key>draw</key>
<string>DRAW_NODE</string>
<key>node</key>
<string>movegizmo</string>
</dict>
<key>quadscene</key>
<dict>
<key>colorStates</key>
<dict>
<key>clear</key>
<true/>
<key>clearColor</key>
<string>sceneBackground</string>
</dict>
<key>depthStates</key>
<dict>
<key>clear</key>
<true/>
</dict>
<key>inputs</key>
<dict>
<key>totalSceneO</key>
<string>COLOR</string>
<key>a_texcoord</key>
<string>a_texcoord-symbol</string>
<key>gizmoNodeO</key>
<string>gizmonode</string>
</dict>
<key>outputs</key>
<dict>
<key>color</key>
<string>COLOR</string>
</dict>
<key>draw</key>
<string>DRAW_QUAD</string>
<key>program</key>
<string>gizmo</string>
</dict>
</dict>
<key>sequence</key>
<array>
<string>gizmoonly</string>
<string>quadscene</string>
</array>
<key>targets</key>
<dict>
<key>totalscene</key>
<dict>
<key>type</key>
<string>color</string>
</dict>
<key>gizmonode</key>
<dict>
<key>type</key>
<string>color</string>
</dict>
</dict>
<key>symbols</key>
<dict>
<key>a_texcoord-symbol</key>
<dict>
<key>semantic</key>
<string>texcoord</string>
</dict>
<key>vertexSymbol</key>
<dict>
<key>semantic</key>
<string>vertex</string>
</dict>
</dict>
</dict>
</plist>

Following is the vertex shader for the second pass:

attribute vec4 a_position;
varying vec2 uv;

void main() {
gl_Position = a_position;
uv = (a_position.xy + 1.0) * 0.5;
}

The fragment shader for the second pass:

uniform sampler2D totalSceneO;
uniform sampler2D gizmoNodeO;

varying vec2 uv;

void main() {
vec4 t0 = texture2D(totalSceneO, uv);
vec4 t1 = texture2D(gizmoNodeO, uv);
gl_FragColor = (1.0 - t1.a) * t0 + t1.a * t1;
}

Swift code:

if let path = NSBundle.mainBundle().pathForResource("scntec", ofType: "plist") {
if let dico1 = NSDictionary(contentsOfFile: path) {
let dico = dico1 as! [String : AnyObject]

let technique = SCNTechnique(dictionary:dico)
scnView.technique = technique
}
}

Objective-C code:

NSURL *url = [[NSBundle mainBundle] URLForResource:@"scntec" withExtension:@"plist"];
SCNTechnique *technique = [SCNTechnique techniqueWithDictionary:[NSDictionary dictionaryWithContentsOfURL:url]];
self.myView.technique = technique;

Set the name for the gizmo node:

theGizmo.name = @"movegizmo";

SCNNode Look At Current Position and Always Orient Relative Up

func carouselLookAtMe() {
//Here we use tangent lines on a sphere, relating to the POSITIVE Y Axis, to find the point at which the up direction is.
let currentPos = self.sceneView.pointOfView!.position
let D:Float = pow(currentPos.x, 2) + pow(currentPos.y, 2) + pow(currentPos.z, 2)

let newPos = SCNVector3(0, abs(Float(D/Float(currentPos.y))), 0)
let xDiff = currentPos.x-self.carousel.position.x
let yDiff = currentPos.y-self.carousel.position.y
let zDiff = currentPos.z-self.carousel.position.z

let upVectorPos = SCNVector3(newPos.x-xDiff, newPos.y-yDiff, newPos.z-zDiff)
self.carousel.look(at: self.sceneView.pointOfView!.position, up: upVectorPos, localFront: SCNVector3(0, 0, 1))
}

Notes: self.carousel is the node that I want to look at me. It stays looking at me while orienting upwards, relatively speaking.

I call this inside func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor)

Position a SceneKit object in front of SCNCamera's current orientation

(Swift 4)

Hey, you can use this simpler function if you want to put the object a certain position relative to another node (e.g. the camera node) and also in the same orientation as the reference node:

func updatePositionAndOrientationOf(_ node: SCNNode, withPosition position: SCNVector3, relativeTo referenceNode: SCNNode) {
let referenceNodeTransform = matrix_float4x4(referenceNode.transform)

// Setup a translation matrix with the desired position
var translationMatrix = matrix_identity_float4x4
translationMatrix.columns.3.x = position.x
translationMatrix.columns.3.y = position.y
translationMatrix.columns.3.z = position.z

// Combine the configured translation matrix with the referenceNode's transform to get the desired position AND orientation
let updatedTransform = matrix_multiply(referenceNodeTransform, translationMatrix)
node.transform = SCNMatrix4(updatedTransform)
}

If you'd like to put 'node' 2m right in front of a certain 'cameraNode', you'd call it like:

let position = SCNVector3(x: 0, y: 0, z: -2)
updatePositionAndOrientationOf(node, withPosition: position, relativeTo: cameraNode)

Edit: Getting the camera node

To get the camera node, it depends if you're using SceneKit, ARKit, or other framework. Below are examples for ARKit and SceneKit.

With ARKit, you have ARSCNView to render the 3D objects of an SCNScene overlapping the camera content. You can get the camera node from ARSCNView's pointOfView property:

let cameraNode = sceneView.pointOfView

For SceneKit, you have an SCNView that renders the 3D objects of an SCNScene. You can create camera nodes and position them wherever you want, so you'd do something like:

let scnScene = SCNScene()
// (Configure scnScene here if necessary)
scnView.scene = scnScene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 5, 10) // For example
scnScene.rootNode.addChildNode(cameraNode)

Once a camera node has been setup, you can access the current camera in the same way as ARKit:

let cameraNode = scnView.pointOfView

How can I point an SCNNode at another SCNNode?

How about an SCNBillboardConstraint? That restricts you to iOS 9/El Capitan/tvOS. Add the constraint to each of your flat plane (billboard) nodes.

From the SceneKit Framework Reference: https://developer.apple.com/library/ios/documentation/SceneKit/Reference/SCNBillboardConstraint_Class/index.html

An SCNBillboardConstraint object automatically adjusts a node’s orientation so that it always points toward the pointOfView node currently being used to render the scene.

In the more general case, SCNLookAtConstraint will keep any node's minus-Z axis pointed toward any other node.

How to position SceneNode always at the center of the sceneView while using ARFaceTrackingConfiguration

So here is the answer to my own question.

firstly to place an object in the middle of the sceneView statically, which means you don't want the object to move when camera is moving, you need to set its position to the current facing position of the camera.
To Get the current location of the camera we need to access pointOfView value.
pointOfView contains the current location and orientation of the cameraView
One use-case is when we need to add nodes in front of the camera.
The location and orientation are encoded inside of the pointOfView into a transformMatrices SCNMatrix4
.
From Apple documentation:

Image below shows the matrix configurations for some of the more common
transformations you can make. Multiplying any coordinate by the
identity transform returns the exact same coordinate. For other
transformations, how the coordinate is modified depends entirely on
which matrix components you change. For example, to translate along
the x-axis only, you would supply a nonzero value for the tx component
of the translation matrix and leave the ty and tz values to 0. For
rotations, you would provide the appropriate sine and cosine values of
the target rotation angle. Matrix configurations for

Sample Image

Pheeeew...!
Now grab the pointOfView in your class body not in ARSCNViewDelegate methods if you do it in renderer(_nodeFor:) method then your object will follow the face geometry:

guard let pointOfView = self.sceneView.pointOfView else { return }

and then you can position your object in the middle of the screen by:

DispatchQueue.main.async {
pointOfView.addChildNode(yourNode)
}

So now the object is centered in front of the camera and it will not move accordingly with camera movement.

then for moving the object by tilting the head up/down we need to implement ARSCNViewDelegate and call the:

func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode?

this method asks the delegate to provide a SceneKit node corresponding to a newly added anchor which in my scenario i'm interested in FaceAnchor.
i've assigned a face geometry to the faceAnchor with 0 transparency because i don't need that being visible its just for tracking the face position via that node, when the face is moved the didUpdate method will be called and then we can update the nodes or what so ever in didUpdate method because it's getting called whenever the targeted node is moved.

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
guard let faceAnchor = anchor as? ARFaceAnchor else { return }

let faceTransform = faceAnchor.transform
let positionY = faceLocation.columns.2.y
let positionX = faceLocation.columns.2.x
let horizentalLine = Int(positionY * 100)

self.horizentalNode.eulerAngles = SCNVector3(horizentalLine.degreeToRadians, 0, 0)

So as i said before inside each node/anchor there is a property called transform which is a matrix encoding the position, orientation, and scale of the anchor relative to the world coordinate space of the AR session the anchor is placed in.
in short: This has something to do with 3 dimensional metrics.
if found out that y and x value in column number 2 is the horizontal and vertical movement of the head, the little math there + degreeToRadians extension is just to convert the value for eulerAngles rotation matrices.

How to draw different images on each side of SCNPlane Geometry

Since you want different images, you need to use different materials. SceneKit allows specifying material per geometry element. SCNPlane has only one element, that's why isDoubleSided just mirrors image on the back side. You have two options here:

  • Create two SCNPlane geometries, orient them back to back and assign different images to each geometry.firstMaterial.diffuse.contents
  • Create custom SCNGeometry from SCNGeometrySource (4 vertices of plane) and two SCNGeometryElements (one for each side: 2 triangles, 6 indices), and assign array of two materials (different images) to geometry.

The first option is easier, but looks more like a workaround.



Related Topics



Leave a reply



Submit