Quantcast
Channel: FlexMonkey
Viewing all articles
Browse latest Browse all 257

A Simple SceneKit Material Editor in Swift

$
0
0
One of the features I'm considering adding to Nodality, my node based image editor for iPads, is a function to create materials for SceneKit geometries. My first step is to create a user interface control for Nodality nodes that can preview the user generated material. I've worked on this component in a little test harness project that I thought I'd share.

My MaterialPreviewWidget is a component that initialises itself with a default SCNMaterial,  accepts a new material and renders a sphere using that material. My view controller adds an instance of MaterialPreviewWidget to its view, along with sliders to control the fresnel exponent, shininess and transparency and segmented controls to select normal, specular, diffuse and reflective maps.

Creating the sphere preview using SceneKit is pretty simple. MaterialPreviewWidget extends the SCNView class and in its didMoveToSuperview() method I start building the scene. First of all, I define an SCNSceneset that  at the scene view's scene:


        let thisScene = SCNScene()
        

        scene = thisScene

...next I create a camera, define its position and field of view and add it as a child node to the scene:


        let camera = SCNCamera()
        
        camera.xFov = 45
        camera.yFov = 45
        
        let cameraNode = SCNNode()
        
        cameraNode.camera = camera
        cameraNode.position = SCNVector3(x: 0, y: 0, z: 20)
        

        thisScene.rootNode.addChildNode(cameraNode)

...at the top of the class, I create a constant named sphere that's an instance of SCNSphere and defines the sphere's geometry. I now need to create an SCNNode to act as the sphere's parent and manage its position in space. I also apply the default material to the geometry:


        let sphereNode = SCNNode(geometry: sphere)
        sphereNode.position = SCNVector3(x: 0, y: 0, z: 0)
        thisScene.rootNode.addChildNode(sphereNode)
        

        sphere.materials = [material]

...next I create an ambient and an omni light. As with the sphere geometry, these need an SCNNode to handle their positions in space:


        let ambientLight = SCNLight()
        ambientLight.type = SCNLightTypeAmbient
        ambientLight.color = UIColor(white: 0.25, alpha: 1.0)
        let ambientLightNode = SCNNode()
        ambientLightNode.light = ambientLight
        
        thisScene.rootNode.addChildNode(ambientLightNode)
        

        let omniLight = SCNLight()
        omniLight.type = SCNLightTypeOmni
        omniLight.color = UIColor(white: 1.0, alpha: 1.0)
        let omniLightNode = SCNNode()
        omniLightNode.light = omniLight
        omniLightNode.position = SCNVector3(x: -5, y: 8, z: 10)
        

        thisScene.rootNode.addChildNode(omniLightNode)

Finally, I need a SCNFloor instance which is a reflective infinite plane. Again, this needs an SCNNode to manage its position:


        let floor = SCNFloor()
        let floorNode = SCNNode(geometry: floor)
        floorNode.position = SCNVector3(x: 0, y: -6.2, z: 0)
        

        thisScene.rootNode.addChildNode(floorNode)

Back up in the view controller, when any of the sliders or segmented controls dispatch a UIControlEvents.ValueChanged control event, I simply update the widget's material's properties:


    func propertySliderChange()
    {
        materialPreviewWidget.material.fresnelExponent = fresnelExponentSlider.value
        materialPreviewWidget.material.shininess = shininessSlider.value
        materialPreviewWidget.material.transparency = transparencySlider.value

        materialPreviewWidget.material.specular.contents = specularSegmentedControl.value
        materialPreviewWidget.material.diffuse.contents = diffuseSegmentedControl.value
        materialPreviewWidget.material.reflective.contents = reflectiveSegmentedControl.value
        materialPreviewWidget.material.normal.contents = normalSegmentedControl.value

    }

...and the sphere updates with the updated material.

One thing to note: when I've worked with bump maps in the past, they were always monochrome images where the brightness affected the height. Normal maps are slightly different - they use the red, green and blue values of an image to change the normal in the x, y and z directions. That's why to affect just the 'height' of a bump, my bump map is blue only:




All the source code for this project is available at my GitHub repository here.

Thanks to Swift Development with Cocoa by O'Reilly for guiding me through this!

Finally, Nodality version 2.3 is beginning to take shape and here's a screen shot of the material preview widget inside a node and accepting inputs:





Viewing all articles
Browse latest Browse all 257

Trending Articles