skip to Main Content

I have a scene containing SCNNode models, and a camera.

I’d like to generate a depth map render of my scene (from perspective of camera) – something like this: enter image description here

How can this be achieved? Currently looking into SCNTechnique but haven’t found many resources.

2

Answers


  1. Try using SCNRenderer, see here in this article how to use it for the camera scene you need: https://qiita.com/akira108/items/a743138fca532ee193fe

    More about pointOfView here:https://magicien.github.io/JSceneKit/class/js/SceneKit/SCNRenderer.js~SCNRenderer.html#instance-set-pointOfView

    Login or Signup to reply.
  2. This here might not be the Final Solution, but it could give you a guidance, how to achieve a Depth rendering technique.

    Note: Everything here is currently dependant on the camera Distance to the Geometry. Very simplified implementation. It uses a SCNProgram() for the Material.

    Here comes the GameViewController.swift :

    class GameViewController: UIViewController {
        
        @IBOutlet weak var sceneView            : SCNView!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            // create a new scene
            let scene = SCNScene()
            
            // create and add a camera to the scene
            let cameraNode = SCNNode()
            cameraNode.camera = SCNCamera()
            cameraNode.camera?.automaticallyAdjustsZRange = true
            scene.rootNode.addChildNode(cameraNode)
            
            // place the camera
            // cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
            cameraNode.position = SCNVector3(x: 0, y: 0, z: 100)
            
            // create and add a light to the scene
            let lightNode = SCNNode()
            lightNode.light = SCNLight()
            lightNode.light!.type = .omni
            lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
            scene.rootNode.addChildNode(lightNode)
            
            // create and add an ambient light to the scene
            let ambientLightNode = SCNNode()
            ambientLightNode.light = SCNLight()
            ambientLightNode.light!.type = .ambient
            ambientLightNode.light!.color = UIColor.darkGray
            scene.rootNode.addChildNode(ambientLightNode)
            
            // set the scene to the view
            sceneView.scene = scene
            
            // allows the user to manipulate the camera
            sceneView.allowsCameraControl = true
            
            // show statistics such as fps and timing information
            sceneView.showsStatistics = true
            
            // configure the view
            sceneView.backgroundColor = UIColor.black
            
            sceneView.scene?.lightingEnvironment.contents   = UIColor.black
            sceneView.scene?.background.contents            = UIColor.black
            
            // self.setupCube()
            self.setupRing()
            self.setupStick()
        }
        
        
        func setupCube() {
            let cube = SCNBox(width: 20.0, height: 20.0, length: 20.0, chamferRadius: 2.5)
            let cubeNode = SCNNode(geometry: cube)
            cubeNode.geometry?.firstMaterial =  depthMaterial() // chromeMaterialMetal()
            sceneView.scene?.rootNode.addChildNode(cubeNode)
        }
        
        func setupRing() {
            let ring = SCNTorus(ringRadius: 20.0, pipeRadius: 4.0)
            let ringNode = SCNNode(geometry: ring)
            ringNode.position = SCNVector3(0.0, +10.0, 20.0)
            ringNode.geometry?.firstMaterial =  depthMaterial() // chromeMaterialMetal()
            sceneView.scene?.rootNode.addChildNode(ringNode)
            
            let ring2 = SCNTorus(ringRadius: 20.0, pipeRadius: 4.0)
            let ringNode2 = SCNNode(geometry: ring2)
            ringNode2.position = SCNVector3(0.0, -10.0, 20.0)
            ringNode2.geometry?.firstMaterial =  depthMaterial() // chromeMaterialMetal()
            sceneView.scene?.rootNode.addChildNode(ringNode2)
        }
        
        func setupStick() {
            let stick = SCNCylinder(radius: 5.0, height: 70.0)
            let stickNode = SCNNode(geometry: stick)
            stickNode.position = SCNVector3(0.0, 0.0, 20.0)
            stickNode.geometry?.firstMaterial =  depthMaterial() // chromeMaterialMetal()
            sceneView.scene?.rootNode.addChildNode(stickNode)
        }
        
        
        override var prefersStatusBarHidden: Bool {
            return true
        }
        
        override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
            if UIDevice.current.userInterfaceIdiom == .phone {
                return .allButUpsideDown
            } else {
                return .all
            }
        }
        
        func depthMaterial() -> SCNMaterial {
            
            // let depthValue: Float = 100.0 // Adjust this value based on your scene's depth range
            
            let sceneProgramDepth = SCNProgram()
            sceneProgramDepth.vertexFunctionName    = "myVertexDepth"
            sceneProgramDepth.fragmentFunctionName  = "myFragmentDepth"
            // sceneProgramDepth.setValue(depthValue, forKey: "maxDepth") // to be implemented, ToDo
            
            let material = SCNMaterial()
            material.name                           = "metal"
            material.program = sceneProgramDepth // doing this will replace the entire built-in SceneKit shaders for that object.
            
            return material
        }
        
    }
    

    And this here is the Depth-Shader (and some Variations of it) shaders.metal:

    // Default Metal Header for SCNProgram
    #include <metal_stdlib>
    using namespace metal;
    #include <SceneKit/scn_metal>
    
    // Nodebuffer (you only need the enabled Matrix floats)
    struct MyNodeBuffer {
        float4x4 modelViewTransform; // required
        float4x4 normalTransform; // required
        // float4x4 inverseModelViewTransform;
        // float4x4 modelTransform;
        // float4x4 inverseModelTransform;
        // float4x4 modelViewProjectionTransform;
        // float4x4 inverseModelViewProjectionTransform;
    };
    
    // Input Struct
    typedef struct {
        float3 position [[attribute(SCNVertexSemanticPosition)]];
    } MyDepthInput;
    
    // Struct filled by the Vertex Shader
    struct SimpleVertexDepth {
        float4 position [[position]];
        float depth; // Store depth information
    };
    
    // Declare the maximum depth variable // needs to be filed by Scenekit, ToDo
    // constant float maxDepth [[function_constant(0)]];
    
    // Simple Vertex Shader
    vertex SimpleVertexDepth myVertexDepth(MyDepthInput in [[stage_in]],
                                           constant SCNSceneBuffer& scn_frame [[buffer(0)]],
                                           constant MyNodeBuffer& scn_node [[buffer(1)]])
    {
        float4 modelSpacePosition(in.position, 1.0f);
        float4 eyeSpacePosition = scn_node.modelViewTransform * modelSpacePosition;
    
        // Calculate depth from the vertex position in eye space
        float depth = length(eyeSpacePosition.xyz);
    
        SimpleVertexDepth out;
        out.position = scn_frame.projectionTransform * eyeSpacePosition;
        out.depth = depth;
        return out;
    }
    
    // Simple Depth
    // fragment half4 myFragmentDepth(SimpleVertexDepth in [[stage_in]])
    // {
    //     // Map depth to grayscale color inversely
    //     float gray = 1.0 - saturate(in.depth / 100); // Invert depth mapping
    //     return half4(gray, gray, gray, 1.0);
    // }
    
    // Another Variation (Better)
    fragment half4 myFragmentDepth(SimpleVertexDepth in [[stage_in]])
    {
        // Define a contrast factor to control the contrast
        float contrastFactor = 3.0; // Adjust this value to control contrast
        
        // Map depth to grayscale color with increased contrast
        float gray = 1.0 - pow(saturate(in.depth / 100), contrastFactor);
        
        return half4(gray, gray, gray, 1.0);
    }
    
    // Far away is brighter
    // fragment half4 myFragmentDepth(SimpleVertexDepth in [[stage_in]])
    // {
    //     // Map depth to grayscale color
    //     float gray = in.depth / 100; // maxDepth; // You can set maxDepth based on your scene
    //     return half4(gray, gray, gray, 1.0);
    // }
    
    // Better Variation
    // fragment half4 myFragmentDepth(SimpleVertexDepth in [[stage_in]])
    // {
    //     // Define a contrast factor to control the contrast level
    //     float contrastFactor = 3.0; // Adjust this value to control contrast
    //
    //     // Map depth to grayscale color with increased contrast
    //     float gray = pow(in.depth / 100, contrastFactor); // maxDepth is the maximum depth value in your scene
    //     return half4(gray, gray, gray, 1.0);
    // }
    

    My preferred one (for the moment) goes by the name "Another Variation (Better)" The Result looks like so:

    SceneKit Depth Render Shader

    Feel free to experiment with that stuff. I hope I could help in some way. (If you find any better solution, I would be very interested in seeing it.)

    PS: as a scratch-project use the SceneKit Default Template with the Space-Ship and delete the Space-Ship 🙂

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search