SceneKit, flip direction of SCNMaterial
Asked Answered
F

4

8

extremely new to SceneKit, so just looking for help here:

I have an SCNSphere with a camera at the center of it

I create an SCNMaterial, doubleSided, and assign it to the sphere

Since the camera is at the center, the image looks flipped vertically, which when having text inside totally messes things up.

So how can i flip the material, or the image (although later it will be frames from a video), any other suggestion is welcome.

This solution, btw, is failing on me, normalImage is applied as a material (but the image is flipped when looking from inside the sphere), but assigning flippedImage results in no material whatsoever (white screen)

    let normalImage = UIImage(named: "text2.png")
    let ciimage = CIImage(CGImage: normalImage!.CGImage!)
    let flippeCIImage = ciimage.imageByApplyingTransform(CGAffineTransformMakeScale(-1, 1))
    let flippedImage = UIImage(CIImage: flippeCIImage, scale: 1.0, orientation: .Left)

    sceneMaterial.diffuse.contents = flippedImage
    sceneMaterial.specular.contents = UIColor.whiteColor()
    sceneMaterial.doubleSided = true
    sceneMaterial.shininess = 0.5
Flagon answered 22/10, 2015 at 15:27 Comment(0)
E
18

Instead of scaling the node (which may break your lighting) you can flip the mapping using SCNMaterialProperty's contentsTransform property:

material.diffuse.contentsTransform = SCNMatrix4MakeScale(1,-1,1)
material.diffuse.wrapT = SCNWrapModeRepeat // or translate contentsTransform by (0,1,0)
Er answered 23/10, 2015 at 8:15 Comment(4)
thanks! this works fine for a image content, but I'm having issue with a SKVideoNode inside a SKScene, any ideas?Flagon
This also worked for me, but it took me a long time to figure out that the actual transform of the containing node also affects the appearance of the material. My case involved an SCNCylinder geometry I was trying to make look like a coin, make sure you get the rotation of the containing node right first before giving up on the above answer.Benioff
@Er Can you explain your comment // or translate contentsTransform by (0,1,0);. How are these equivalent?Silly
Doesn't seem to do anything on iOS 13.4Fronia
F
16

To flip the image horizontally:

material.diffuse.contentsTransform = SCNMatrix4Translate(SCNMatrix4MakeScale(-1, 1, 1), 1, 0, 0)

to flip it vertically:

material.diffuse.contentsTransform = SCNMatrix4Translate(SCNMatrix4MakeScale(1, -1, 1), 0, 1, 0)
Formularize answered 9/5, 2017 at 14:35 Comment(7)
How can I flip the image back, this works well, but I want to flip it back?Allergic
@Allergic Setting it to .identity should do it.Formularize
Yes, but I cannot figure out the syntax? Can you show an example?Allergic
@Allergic material.diffuse.contentsTransform = .identityFormularize
material.diffuse.contentsTransform = .identity doesn't exist it seems? Type 'SCNMatrix4' has no member 'identity'Allergic
@Allergic Since you've looked into the documentation yourself, you probably already discovered that it’s SCNMatrix4Identity. Strange Swift naming imo, should be SCNMatrix4.identity.Formularize
Sent you a linked in request Ortwin, would be good to connect if I may.Allergic
F
5

this worked for me, flipping the normal of the geometry by scaling the node it's attached to:

sphereNode.scale = SCNVector3Make(-1, 1, 1)

Flagon answered 22/10, 2015 at 16:22 Comment(1)
Works for me too, on an M1 iOS 15, Swift 5.5Allergic
S
3

The accepted answer will not work, guaranteed. Following is how to flip an image that is assigned as the value of the [material].diffuse.contents property; it assumes that two cubes in the scene, side-by-side:

// Define the matrices that perform the two orientation variants
SCNMatrix4 flip_horizontal;
flip_horizontal = SCNMatrix4Translate(SCNMatrix4MakeScale(-1, 1, 1), 1, 0, 0);
SCNMatrix4 flip_vertical;
flip_vertical = SCNMatrix4Translate(SCNMatrix4MakeScale(1, -1, 1), 0, 1, 0);

// Create the material objects for each cube, and assign an image as the contents
self.source_material = [SCNMaterial material];
self.source_material.diffuse.contents = [UIImage imageNamed:@"towelface.png"];
self.mirror_material = [SCNMaterial material];
self.mirror_material.diffuse.contents = self.source_material.diffuse.contents;

Pick only one of the following sections (as defined by the comments):

// PortraitOpposingDown
    [self.mirror_material.diffuse setContentsTransform:SCNMatrix4Mult(self.source_material.diffuse.contentsTransform, flip_vertical)];
        [self.source_material.diffuse setContentsTransform:SCNMatrix4Mult(self.mirror_material.diffuse.contentsTransform, flip_horizontal)]; 

// PortraitFacingDown
    [self.source_material.diffuse setContentsTransform:SCNMatrix4Mult(self.source_material.diffuse.contentsTransform, flip_vertical)];
    [self.mirror_material.diffuse setContentsTransform:SCNMatrix4Mult(self.source_material.diffuse.contentsTransform, flip_horizontal)];

// PortraitOpposingUp
    [self.source_material.diffuse setContentsTransform:SCNMatrix4Mult(self.source_material.diffuse.contentsTransform, flip_horizontal)];

// PortraitFacingUp
    [self.mirror_material.diffuse setContentsTransform:SCNMatrix4Mult(self.source_material.diffuse.contentsTransform, flip_horizontal)];

Insert the material at the desired index:

[cube[0] insertMaterial:self.source_material atIndex:0];
[cube[1] insertMaterial:self.mirror_material atIndex:0];

By the way, to insert a new image (such as for live video), simply replace the material at the index specified by the insertMaterial:atIndex method; do not reorient the contentsTransform. The following code shows you how; it assumes that your video camera is configured to output a sample buffer for each frame it captures to an AVCaptureVideoDataOutputSampleBufferDelegate, and the requisite code (CreateCGImageFromCVPixelBuffer) to create a CGImage from a CVPixelBuffer:

- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    CGImageRef cgImage;
    CreateCGImageFromCVPixelBuffer(pixelBuffer, &cgImage);
    UIImage *image = [UIImage imageWithCGImage:cgImage];
    dispatch_async(dispatch_get_main_queue(), ^{
        self.source_material.diffuse.contents = image;
        [cube[0] replaceMaterialAtIndex:0 withMaterial:self.source_material];
        self.mirror_material.diffuse.contents = self.source_material.diffuse.contents;
        [cube[1] replaceMaterialAtIndex:0 withMaterial:self.mirror_material];
        });
        CGImageRelease(cgImage);
}

If you'd like actual code instead of my assumptions of code on your end, please ask. Here's a short video showing this code in action, but with live video instead of a static image.

Studied answered 22/3, 2018 at 6:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.