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:
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 🙂
2
Answers
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
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
:And this here is the Depth-Shader (and some Variations of it)
shaders.metal
:My preferred one (for the moment) goes by the name "Another Variation (Better)" The Result looks like so:
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 🙂