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.
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"
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With