Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using existing opengl texture with scenekit

According to Apple's documentation you can use NSColor, NSImage and CALayer to provide a texture to a SCNMaterialProperty. SCNMaterialProperty

I am however wondering if there is a way to provide an existing OpenGL texture, which has been created with glGenTextures and rendered into separately.

I can of course read the buffer of the texture, setup an NSImage, and provide that one to the SCNMaterialProperty; but for performance reasons that is obviously not optimal.

It would make sense to implement the above by overriding the shader program(s) of the material I guess, but the documentation for doing this seems to be non-existent.

like image 910
simonfi Avatar asked Sep 05 '13 16:09

simonfi


2 Answers

if you need to directly deal with OpenGL and bind your custom textures then SCNProgram or SCNNode's delegate are the ways to go (MountainLion or greater). If you have an Apple developer account you will find a sample code here: http://developer.apple.com/downloads/ under "WWDC 2013 Sample Code". Look for "OS_X_SceneKit_Slides_WWDC2013"

like image 37
Toyos Avatar answered Sep 29 '22 23:09

Toyos


You can override the shader programs by creating assigning a SCNProgram to the material. Then in one of the delegate methods for SCNProgramDelegate you can bind your own values for the texture (and other uniforms). You will however have do write your own shaders.

There is a little bit of setup if you are used to Objective-C but it's really not that much when you think of the corresponding OpenGL code.

The following is an example shader program that binds a texture to the surface of a geometry object. For simplicity it doesn't do any shading with normals and light sources.

Note that I don't know how you want to bind your specific texture so in the code below I read a png using GLKit and use that texture.

// Create a material
SCNMaterial *material = [SCNMaterial material];

// Create a program
SCNProgram *program = [SCNProgram program];

// Read the shader files from your bundle
NSURL *vertexShaderURL   = [[NSBundle mainBundle] URLForResource:@"yourShader" withExtension:@"vert"];
NSURL *fragmentShaderURL = [[NSBundle mainBundle] URLForResource:@"yourShader" withExtension:@"frag"];
NSString *vertexShader = [[NSString alloc] initWithContentsOfURL:vertexShaderURL
                                                        encoding:NSUTF8StringEncoding
                                                           error:NULL];
NSString *fragmentShader = [[NSString alloc] initWithContentsOfURL:fragmentShaderURL
                                                          encoding:NSUTF8StringEncoding
                                                             error:NULL];
// Assign the shades 
program.vertexShader   = vertexShader;
program.fragmentShader = fragmentShader;

// Bind the position of the geometry and the model view projection
// you would do the same for other geometry properties like normals
// and other geometry properties/transforms.
// 
// The attributes and uniforms in the shaders are defined as:
// attribute vec4 position;
// attribute vec2 textureCoordinate;
// uniform mat4 modelViewProjection;    
[program setSemantic:SCNGeometrySourceSemanticVertex
           forSymbol:@"position"
             options:nil];
[program setSemantic:SCNGeometrySourceSemanticTexcoord
           forSymbol:@"textureCoordinate"
             options:nil];
[program setSemantic:SCNModelViewProjectionTransform
           forSymbol:@"modelViewProjection"
             options:nil];


// Become the program delegate so that you get the binding callback
program.delegate = self;

// Set program on geometry
material.program = program; 
yourGeometry.materials = @[material];

In this example the shaders are written as

// yourShader.vert
attribute vec4 position;
attribute vec2 textureCoordinate;
uniform mat4 modelViewProjection;

varying vec2 texCoord;

void main(void) {
    // Pass along to the fragment shader
    texCoord = textureCoordinate;

    // output the projected position
    gl_Position = modelViewProjection * position;
}

And

// yourShader.frag
uniform sampler2D yourTexture;
varying vec2 texCoord;

void main(void) {
    gl_FragColor = texture2D(yourTexture, texCoord);
}

Finally the implementation for the ...bindValueForSymbol:... method to bind a texture to the "yourTexture" uniform.

- (BOOL)    program:(SCNProgram *)program
 bindValueForSymbol:(NSString *)symbol
         atLocation:(unsigned int)location
          programID:(unsigned int)programID
           renderer:(SCNRenderer *)renderer
{
    if ([symbol isEqualToString:@"yourTexture"]) {
        // I'm loading a png with GLKit but you can do your very own thing
        // here to bind your own texture.
        NSError *error = nil;
        NSString *imagePath = [[NSBundle mainBundle] pathForResource:@"sampleImage" ofType:@"png"];
        GLKTextureInfo *texture = [GLKTextureLoader textureWithContentsOfFile:imagePath options:nil error:&error];
        if(!texture) {
            NSLog(@"Error loading file: %@", [error localizedDescription]);
        }

        glBindTexture(GL_TEXTURE_2D, texture.name);

        return YES; // indicate that the symbol was bound successfully.
    }

    return NO; // no symbol was bound.
} 

Also, for debugging purposes it's very helpful to implement program:handleError: to print out any shader compilation errors

- (void)program:(SCNProgram*)program handleError:(NSError*)error {
    // Log the shader compilation error
    NSLog(@"%@", error);
}
like image 186
David Rönnqvist Avatar answered Sep 30 '22 00:09

David Rönnqvist