本文主要介绍了ARKit如何将照相机捕获到的数据传入SceneKit自定义着色器进行渲染,本文内容假定读者有一定的ARKit、SceneKit及Metal的基础。
ARKit
今年的 WWDC 开发者大会上,苹果为我们带来了 ARKit。它做到了将优秀的 AR 体验带给无数的消费级设备,其重要性我们这段时间来早就了解了。不过苹果一个巴掌毕竟拍不响,这些还都需要开发者们来支持。更多关于ARKit的介绍可以访问苹果的开发者网站。
SceneKit
在 WWDC 2012 (那时 OS X 系统还在用喵系命名),Apple 向 OS X 开发者们介绍了 SceneKit,这个 Cocoa 下的 3D 渲染框架。在第一版通用 3D 渲染器发布后,一年内又陆续增加了像 shader (着色器) 修改器、节点约束、骨骼动画等几个强大的特性 (随 Mavericks 发布)。今年,SceneKit 变的更加强大,支持了粒子效果、物理引擎、脚本事件以及多通道分层渲染等多种技术,而且,对于很多人来说更关键的是,它终于可以在 iOS 中使用了。
一上手,我发现 SceneKit 最强大和脱颖而出的地方,就是可以与 CoreImage,CoreAnimation,SpriteKit 等已有的图形框架相互整合及协作,这在其他游戏引擎中可不常见,但如果你本身就是个 Cocoa 或 Cocoa Touch 框架下的的开发者的话,就会感到相当亲切了。 关于SceneKit介绍可以到这里了解。
创建ARKit的三个模板之一就是SceneKit,并且SceneKit在开发3D游戏优势十分明显。
开始
由于使用了SceneKit的ARKit,不支持OpenGLESrenderAPI,所以以下内容的介绍为Metal。
效果如上图,在渲染自定义物体时我们可能需要传入背景去渲染。那么可以通过下面的方法去从CVPixelBufferRef获取YUV数据。
获取YUV数据
- (id<MTLTexture>)_createTextureFromPixelBuffer:(CVPixelBufferRef)pixelBuffer pixelFormat:(MTLPixelFormat)pixelFormat planeIndex:(NSInteger)planeIndex { id<MTLTexture> mtlTexture = nil; const size_t width = CVPixelBufferGetWidthOfPlane(pixelBuffer, planeIndex); const size_t height = CVPixelBufferGetHeightOfPlane(pixelBuffer, planeIndex); CVMetalTextureRef texture = NULL; CVReturn status = CVMetalTextureCacheCreateTextureFromImage(NULL, _capturedImageTextureCache, pixelBuffer, NULL, pixelFormat, width, height, planeIndex, &texture); if(status == kCVReturnSuccess) { mtlTexture = CVMetalTextureGetTexture(texture); CFRelease(texture); } return mtlTexture; }
调用并且传递数据
SCNMaterial *material; /* material = 某个SCNGeometry的material */ // 获取YUV数据 id<MTLTexture> capturedImageTextureY = [self _createTextureFromPixelBuffer:pixelBuffer pixelFormat:MTLPixelFormatR8Unorm planeIndex:0]; id<MTLTexture> capturedImageTextureCbCr = [self _createTextureFromPixelBuffer:pixelBuffer pixelFormat:MTLPixelFormatRG8Unorm planeIndex:1]; // 使用SCNMaterialProperty来传递数据,不支持直接设置给SCNMaterial SCNMaterialProperty *capturedImageTextureYMaterial = [SCNMaterialProperty materialPropertyWithContents:capturedImageTextureY]; SCNMaterialProperty *capturedImageTextureCbCrMaterial = [SCNMaterialProperty materialPropertyWithContents:capturedImageTextureCbCr]; // SCNMaterial设置KVC [material setValue:capturedImageTextureYMaterial forKey:@"capturedImageTextureY"]; [material setValue:capturedImageTextureCbCrMaterial forKey:@"capturedImageTextureCbCr"]; SCNProgram *program; /* 初始化着色器…… */ // 为SCNMaterial设置着色器 material.program = program;
VSH和FSH
VSH #include <metal_stdlib> using namespace metal; #include <SceneKit/scn_metal> #include "ShaderDatas.h" struct MyNodeBuffer { float4x4 modelTransform; float4x4 modelViewTransform; float4x4 normalTransform; float4x4 modelViewProjectionTransform; }; typedef struct { float4 position [[ attribute(SCNVertexSemanticPosition) ]]; float2 texCoord [[ attribute(SCNVertexSemanticTexcoord0) ]]; float4 normal [[ attribute(SCNVertexSemanticNormal) ]]; } MyVertexInput; struct SimpleVertex { float4 position [[position]]; float2 texCoord; float3 normal; float4 ambient; float3 light; float3 eye; float sinTime; float cosTime; float random01; }; vertex SimpleVertex myVertex(MyVertexInput in [[ stage_in ]], constant SCNSceneBuffer& scn_frame [[buffer(0)]], constant MyNodeBuffer& scn_node [[buffer(1)]]) { /* 顶点处理 */ } FSH fragment float4 glassFragment(SimpleVertex in [[stage_in]], texture2d<float, access::sample> capturedImageTextureY [[texture(0)]], texture2d<float, access::sample> capturedImageTextureCbCr [[texture(1)]]) { /* 处理图片效果 */ }
接下去就是编写脚本,与正常的Metal脚本没有什么大的区别。只是需要传入SceneKit的一些诸如灯光位置、颜色、顶点、纹理坐标等等数据。更详细的请查阅SCNProgram文档。
小结
在一些需要结合背景的渲染功能中(例如:放大镜、彩色玻璃),我们可以使用SCNProgram进行单个几何体的渲染。