用于几何入口点的SceneKit着色器修改器适用于iOS,但不适用于OS X

我正在做一个SceneKit着色器修改器(用于几何入口点)的早期阶段,它根据高度贴图纹理来移动平面的几何。 计划是用它来创造地形。

在iOS中(编辑:iOS模拟器),着色器按照原样工作,但是将此警告打印到控制台:

SceneKit:错误,无代码修饰无效

它在iOS上看起来如何

但是,当为OS X构build时,着色器会产生一个致命错误,而地形几何体只会显示为粉红色的矩形。

这是几何着色器修改器:

uniform sampler2D displacementMap; const float intensity = 7.5; # pragma body vec4 displace = texture2D(displacementMap, _geometry.texcoords[0]); _geometry.position.z += displace.r * intensity; 

这就是着色器如何连接到几何。 terrainScene是高度贴图,它位于着色器修改器中的漫reflection内容和自定义displacementMap贴图采样器值中:

  //displacement shader let mat = SCNMaterial() mat.diffuse.contents = terrainScene var displacementShader: String? guard let path = NSBundle.mainBundle().pathForResource("DisplaceGeometry", ofType: "shader") else {return} do { displacementShader = try String(contentsOfFile: path, encoding: NSUTF8StringEncoding) } catch { return } mat.shaderModifiers = [SCNShaderModifierEntryPointGeometry: displacementShader!] let noiseProperty = SCNMaterialProperty(contents: terrainScene!) mat.setValue(noiseProperty, forKey: "displacementMap") //geometry let plane = SCNPlane(width: 80, height: 80) plane.widthSegmentCount = 40 plane.heightSegmentCount = 40 plane.materials = [mat] 

这是OS X错误信息的开头:

 SceneKit: error, modifier without code is invalid 2016-06-11 10:58:32.054 EndlessTerrainOSX[16923:5292387] SceneKit: error, Invalid shader modifier : no code provided 2016-06-11 10:58:32.640 EndlessTerrainOSX[16923:5292387] FATAL ERROR : failed loading compiling shader: 

而错误的结束:

 UserInfo={NSLocalizedDescription=Compilation failed: <program source>:343:8: error: global variables must have a constant address space qualifier float4 displace = displacementMap.sample(displacementMapSampler, _geometry.texcoords[0]); ^ <program source>:343:19: error: use of undeclared identifier 'displacementMap' float4 displace = displacementMap.sample(displacementMapSampler, _geometry.texcoords[0]); ^ <program source>:343:42: error: use of undeclared identifier 'displacementMapSampler' float4 displace = displacementMap.sample(displacementMapSampler, _geometry.texcoords[0]); ^ <program source>:344:1: error: unknown type name '_geometry' _geometry.position.z += displace.r * intensity; ^ <program source>:344:10: error: cannot use dot operator on a type _geometry.position.z += displace.r * intensity; ^ } 2016-06-11 10:58:32.646 EndlessTerrainOSX[16923:5292387] Shaders without a vertex function are not allowed 

有谁知道为什么它显示在iOS上,但在OS X上失败?

我build议它可能不是一个iOS与OSX的问题,但与使用OpenGL和其他金属的一个设备有关。

错误消息中的代码是Metal,表示SceneKit已将您的代码从GLSL转换为Metal。 根据我的经验,这是不百分之百的时间。 在你的情况下,最简单的解决scheme可能是通过在创build时传入选项来强制SCNView使用OpenGL。 我相信这里也有一个Interface Builder的下拉菜单。

如果你想继续使用金属,并有理由,请继续阅读。

当着色器修改器无法编译时,着色器的全部内容被转储到标准输出,这真的有助于debugging过程。 如果你向上滚动到第343行,你会发现SceneKit包含了float4 displace = displacementMap.sample(...); 方法顶点着色器之前 。 被定义在函数之外意味着它是一个全局variables,因此需要constant地址空间修饰符,但这不是你想要的……在这种情况下,从GLSL到Metal的翻译并没有像你期望的那样工作。

我注意到你错过了#pragma arguments指令,这应该包括在统一定义之上(参考“编写着色器修饰符片段” ),这可能会有所帮助。 以我的经验,我认为你将有一个艰难的时间将纹理传递给基于金属的着色器修改器。 我尝试了,失败了,写了一个SCNProgram来代替它。 build议强制OpenGL。

我已经弄清楚为什么它不在Metal中工作

为什么@lock非常有帮助地指出,尽pipe行# pragma body下面, float4 displace = displacementMap.sample(...)被放置在顶点着色器之外的全局空间中。

这是因为有一个额外的空间。

它应该是#pragma body (哈希后没有空格)。

如果我删除那个空间,那么它在金属和GLES中都没有错误地编译。 自定义纹理也起作用(有一件事我没有提到,纹理实际上是一个SpriteKit场景,因为它需要dynamic改变)。

我不敢相信我花了24个小时的时间盯着这个应该可以工作的代码,所有这些都是因为我写了# pragma而不是#pragma 。 感谢@lock发现来自主体的行实际上是在全局范围内声明的。

编辑:

由于这个问题的解决scheme围绕着SceneKit将我的GLES着色器片段翻译成Metal的能力,下面是我注意到的其他一些事情:

  • 将自定义纹理传递给着色器片段可将罚款转换为金属。 我使用的Swift代码:

     let terrainGenomes = SCNMaterialProperty(contents: "art.scnassets/TerrainGenomes.jpg") mat.setValue(terrainGenomes, forKey: "terrainGenomes") 

(其中mat是SCNMaterial)

  • 使用#pragma arguments会导致GLES着色器失败。 这似乎是可选的,所以我只是删除它。

  • 我无法使Metal翻译识别自定义全局函数

尽pipeSCNShadable类引用在其示例代码片段中具有自定义全局函数,但是在尝试执行此操作时,Metal翻译总是失败,并且no previous prototype for function 。 所以为了保持兼容性,我必须“放松”片段中的function。 例如,下面是一个表面着色器修改器,用于计algorithm线(点法线看起来不太好,所以我转换到了表面着色器修改器中的像素法线)。 这在GLES中有效,但是没有转化为Metal:

 uniform sampler2D displacementMap; uniform float intensity; vec3 getNewPos(vec2 tc) { vec4 displace = texture2D(displacementMap, tc); return vec3((tc.x - 0.5) * 80., (tc.y - 0.5) * -80., displace.r * intensity); } #pragma body vec3 newPos = getNewPos(_surface.diffuseTexcoord); vec3 neighbour1 = getNewPos(_surface.diffuseTexcoord + vec2(0.015, 0.)); vec3 neighbour2 = getNewPos(_surface.diffuseTexcoord + vec2(0., 0.015)); vec4 norm = u_modelViewTransform * vec4(normalize(cross(neighbour2 - newPos, neighbour1 - newPos)), 0.); _surface.normal = norm.xyz; 

在金属翻译中,这失败了:

 <program source>:344:8: warning: no previous prototype for function 'getNewPos' float3 getNewPos(float2 tc) { ^ 

这是解开的版本。 删除函数使得代码更less干,更不漂亮,但它可以在GLES和Metal中使用:

 uniform sampler2D displacementMap; uniform float intensity; #pragma body vec4 pixel = texture2D(displacementMap, _surface.diffuseTexcoord); vec3 newPos = vec3((_surface.diffuseTexcoord.x - 0.5) * 80., (_surface.diffuseTexcoord.y - 0.5) * -80., pixel.r * intensity); vec2 tc1 = _surface.diffuseTexcoord + vec2(0.015, 0.); vec2 tc2 = _surface.diffuseTexcoord + vec2(0., 0.015); vec3 neighbour1 = vec3((tc1.x - 0.5) * 80., (tc1.y - 0.5) * -80., texture2D(displacementMap, tc1).r * intensity); vec3 neighbour2 = vec3((tc2.x - 0.5) * 80., (tc2.y - 0.5) * -80., texture2D(displacementMap, tc2).r * intensity); vec4 norm = u_modelViewTransform * vec4(normalize(cross(neighbour2 - newPos, neighbour1 - newPos)), 0.); _surface.normal = norm.xyz; 
  • SceneKit提供的一些制服,例如u_diffuseTexture ,在着色器片段的Metal翻译中不可访问

我想最终,GLES的着色器修改器自动翻译到金属只会让你到目前为止,并正确地做自定义着色器修改多目标开发,你应该提供一个完整的金属着色器片段除了一个GLES一个(并使用预编译器#if块在它们之间切换?)。 我知道GLSL相当不错,我还没有去学习金属SL。