Creating a vertical gradient texture programmatically in SceneKit

So, I have this scene in SceneKit, where I programmatically create a bunch of objects with each height. Most of them are simple boxes, but some of them are pressed tracks.

I need to add a simple material color for them so that the color goes from a darker value to a lighter one from the bottom.

I tried to achieve this by creating material with the texture of a gradient image programmatically created using CAGradientLayer . It almost works. The problem with this approach is that ALL the faces of the object will get the same effect, which is not what I want. The upper and lower surfaces of objects should be solid colors - light color on the upper surface and light below.

I also played with shaders, but the result is a gradient that is relative to the screen instead of my object, thereby turning my 3D object into 2D.

Here I create a simple color :

 func createBox(with bounds: CGRect, and height: CGFloat) { let material = SCNMaterial() material.diffuse.contents = UIColor.white if #available(iOS 10.0, *) { material.lightingModel = .physicallyBased material.metalness.contents = 0.2 material.roughness.contents = 0.5 } let geometry = SCNBox(width: bounds.size.width, height: height, length: bounds.size.height, chamferRadius: 0.0) geometry.materials = [material] geometry.chamferRadius = 0.02 let node = NKNode(geometry: geometry) } 

Now, since the box has 6 different geometry elements by default, I can choose to create a material that has its contents configured for the gradient image, and then add this material 6 times to the materials geometry array. This allows me to set the material for each person individually, for example:

 let material = SCNMaterial() material.diffuse.contents = UIImage(named: "gradient-test") geometry.materials = [material, material, material, material, material, material] 

With this I can have 3 different materials (sides, top, bottom) and by trial and error add them in the correct order in the materials array. This will mean the following:

 let sideMaterial = SCNMaterial() sideMaterial.diffuse.contents = UIImage(named: "gradient") let topMaterial = SCNMaterial() topMaterial.diffuse.contents = UIColor.white let bottomMaterial = SCNMaterial() bottomMaterial.diffuse.contents = UIColor.black // The order is side, side, side, side, top, bottom geometry.materials = [sideMaterial, sideMaterial, sideMaterial, sideMaterial, topMaterial, bottomMaterial] 

But my problem appears when my object is not an easy but extruded path. Here's how they are created:

 func createFromPath(_ path: UIBezierPath, with height: CGFloat) { let material = SCNMaterial() material.diffuse.contents = UIColor.white if #available(iOS 10.0, *) { material.lightingModel = .physicallyBased material.metalness.contents = 0.2 material.roughness.contents = 0.5 } material.isDoubleSided = true let geometry = SCNShape(path: path, extrusionDepth: height) geometry.materials = [material] geometry.chamferRadius = 0.02 } 

As you can see, a simple solid color is simple. But applying the same logic to add a gradient texture to the side no longer works, since my path may have an undefined number of faces. And, frankly, I even think that the geometry created in this way does not break into several "elements", like a simple box.

So any idea how I can achieve what I want?

Update


Here is a really basic example of what I would like to achieve. As you can see, the 3D β€œbuildings” on this map are thin from bottom to top gradient.

enter image description here

+5
source share

Source: https://habr.com/ru/post/1262723/


All Articles