使用现有的OpenGL纹理与SceneKit

6
根据苹果文档,您可以使用 NSColor、NSImage 和 CALayer 来为 SCNMaterialProperty 提供纹理。SCNMaterialProperty 但是,我想知道是否有一种方法可以提供现有的 OpenGL 纹理,该纹理已使用 glGenTextures 创建并单独渲染。
当然,我可以读取纹理的缓冲区,设置 NSImage,并将其提供给 SCNMaterialProperty;但出于性能原因,这显然不是最佳选择。
实现上述功能似乎应通过重写材料的着色器程序来完成,但对于如何执行此操作的文档似乎不存在。
2个回答

9
您可以通过为材质创建并分配一个SCNProgram来覆盖着色程序。然后在SCNProgramDelegate的一个委托方法中,您可以绑定自己的纹理值(和其他uniform)。但是,您必须编写自己的着色器。
如果您习惯于Objective-C,则需要进行一些设置,但是当您考虑到相应的OpenGL代码时,这些设置其实并不多。
以下是一个示例着色器程序,它将纹理绑定到几何对象的表面。为简单起见,它不使用法线和光源进行着色。
请注意,我不知道您要如何绑定特定的纹理,因此在下面的代码中,我使用GLKit读取png文件并使用该纹理。
// 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];

在这个例子中,着色器被编写为:
// 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);
}

最后实现了...bindValueForSymbol:...方法,将纹理绑定到“yourTexture”统一变量。

- (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.
} 

此外,为了调试目的,实现program:handleError:以打印出任何着色器编译错误非常有帮助。
- (void)program:(SCNProgram*)program handleError:(NSError*)error {
    // Log the shader compilation error
    NSLog(@"%@", error);
}

2
终于有时间来试玩一下了,再次感谢你!除了一个小错别字(在顶点着色器中的a_textureCoordinate),这个代码完美地运行了,并且比我之前一直使用新的NSImage替换纹理的实现要快得多。只有一个小问题:你是怎么找出这个方法的?到目前为止,SceneKit 的文档似乎非常有限。 - simonfi

1
如果你需要直接处理OpenGL并绑定自定义纹理,那么SCNProgram或SCNNode的代理是可行的途径(MountainLion或更高版本)。如果你有苹果开发者账户,你可以在这里找到一个示例代码: http://developer.apple.com/downloads/ 在“WWDC 2013 Sample Code”下面。寻找“OS_X_SceneKit_Slides_WWDC2013”。

不幸的是,您将不得不提供自己的着色器。此外,SceneKit本身是Mountain Lion及更高版本的系统才支持的 ;) - David Rönnqvist
@Toyos,你的电子邮件地址是什么?如果您不介意,我想通过电子邮件与您联系关于一个项目。 - Crashalot

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接