【问题标题】:SceneKit shader modifier for geometry entry points works in iOS but not OS X几何入口点的 SceneKit 着色器修改器适用于 iOS,但不适用于 OS X
【发布时间】:2016-10-12 06:22:16
【问题描述】:

我正处于制作 SceneKit 着色器修改器(用于几何入口点)的早期阶段,该修改器根据高度贴图纹理置换平面的几何图形。计划是用它来创建地形。

在 iOS(编辑:iOS 模拟器)中,着色器可以正常工作,但会将此警告打印到控制台:

SceneKit:错误,没有代码的修饰符无效

但是,在为 OS X 构建时,着色器出现致命错误,并且地形几何体仅显示为粉红色矩形。

这是几何着色器修改器:

uniform sampler2D displacementMap;
const float intensity = 7.5;

# pragma body

vec4 displace = texture2D(displacementMap, _geometry.texcoords[0]);

_geometry.position.z += displace.r * intensity;

这就是着色器与几何体的连接方式。 terrainScene 是高度图,它被放置在漫反射内容和着色器修改器中自定义的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 上失败?

【问题讨论】:

    标签: ios macos shader scenekit heightmap


    【解决方案1】:

    我认为这可能不是 iOS 与 OSX 的问题,而是与使用 OpenGL 的一个设备和另一个 Metal 的设备有关。

    错误信息中的代码是 Metal,表示 SceneKit 已将您的代码从 GLSL 转换为 Metal。以我的经验,这不是 100% 的。在您的情况下,最简单的解决方案可能是通过passing in options when it's created 强制SCNView 使用OpenGL。我相信 Interface Builder 中也有一个下拉菜单。

    如果您希望继续使用 Metal,并且有理由继续使用,请继续阅读。

    当着色器修改器编译失败时,着色器的全部内容被转储到标准输出,这确实有助于调试过程。如果向上滚动到第 343 行,您会发现 SceneKit 在顶点着色器 之前 包含了 float4 displace = displacementMap.sample(...); 方法。在函数外部定义意味着它是一个全局变量,因此需要 constant 地址空间修饰符,但这根本不是您想要的......在这种情况下,从 GLSL 到 Metal 的转换没有起到你期望的。

    我注意到您缺少#pragma arguments 指令,这应该包含在统一定义之上(请参阅"Writing a Shader Modifier Snippet"),它可能会有所帮助。根据我的经验,我认为您将很难将纹理传递到基于金属的着色器修改器中。我尝试过,失败了,然后写了一个SCNProgram 来代替它。建议强制 OpenGL。

    【讨论】:

    • 谢谢。你是对的,这是一个金属/ OpenGL 问题。我应该提到我正在 iOS 模拟器中对其进行测试。直到现在我才意识到,虽然 El Capitan 运行 Metal,iPhone 5S 运行 Metal,但 iOS 模拟器(在 El Capitan 上运行并模拟 5S)显然仍然只是 OpenGL。正如您所建议的,强制 OpenGL 可以正确显示地形。它仍然打印错误“SceneKit:错误,没有代码的修饰符无效”。有什么想法吗?
    • 也感谢有关将纹理传递到金属着色器修改器的警告。我在想,在某些时候我必须从 sn-p 切换到完整的 SCNProgram 着色器,我想是时候这样做了。
    • 我能看到代码中出现“SceneKit:错误,没有代码的修饰符无效”错误的唯一方法是文件为空,但它根本不起作用。金属计算着色器是其他需要考虑的东西,您可以使用它来变形 SceneKit 几何体,甚至更新其法线,并且仍然使用默认的 SceneKit 着色器。
    • 是的,这是一个奇怪的错误。感谢有关计算/内核着色器的提示,我会研究这些。我对法线的想法是查找另外两个高度图纹理样本并在顶点着色器中计算叉积。
    • 关于金属计算/内核着色器,您能否指出使用计算/内核着色器处理网格几何体的代码示例?我只发现了它们用于图像处理或处理任意数组的示例。我真的很想探索这个,但可以在正确的方向上使用一些指针。再次感谢您迄今为止的帮助。
    【解决方案2】:

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

    以及为什么,正如@lock 非常有帮助地指出的那样,float4 displace = displacementMap.sample(...) 行被放置在顶点着色器之外的全局空间中,即使它位于# pragma body 行下方。

    这是因为额外的空间。

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

    如果我删除了那个空间,那么它在 metal 和 GLES 中编译都不会出错。并且自定义纹理也可以工作(我没有提到的一件事是,纹理实际上是一个 SpriteKit 场景,因为它需要动态更改)。

    我不敢相信我花了 24 小时盯着这个应该可以工作的代码,这一切都是因为我写了 # pragma 而不是 #pragma。感谢@lock 发现正文中的行实际上是在全局范围内声明的。

    编辑:

    由于这个问题的解决方案围绕着 SceneKit 将我的 GLES 着色器 sn-p 转换为 Metal 的能力,所以我注意到了其他一些事情:

    • 将自定义纹理传递给着色器 sn-ps 可以很好地转换为金属。我使用的 Swift 代码:

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

    (其中mat 是 SCNMaterial)

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

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

    当我尝试执行此操作时,Metal 翻译总是以 no previous prototype for function 失败,即使 SCNShadable class reference 在其示例 sn-p 中具有自定义全局函数。所以为了保持兼容性,我不得不“展开”sn-p 中的函数。例如,这是一个计算法线的表面着色器修改器(点法线看起来不太好,所以我在表面着色器修改器中切换到像素法线)。这在 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) {
           ^
    

    这是展开的版本。删除该函数会使代码变得不那么 DRY 并且不那么漂亮,但它在 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 在着色器 sn-p 的 Metal 翻译中无法访问

    我想最终,将 GLES 着色器修改器自动转换为 Metal 只会让您走这么远,并且要正确使用自定义着色器修改器进行多目标开发,您应该提供完整的金属着色器 sn-p除了 GLES 之外(并使用预编译器 #if 块在它们之间切换?)。我对 GLSL 很了解,我还没有开始学习 Metal SL。

    【讨论】:

    • 哎呀,那会很痛 ;-) .
    • 关于您的更新,这一切似乎都非常符合我的经验。希望在这次 WWDC 上听到一些 Metal/SceneKit 的改进。
    • 我遇到了同样的问题,无法识别功能。这是一个错误/功能还是做错了什么?作为一个实验,我尝试从示例中解析完全相同的 sincos 函数,但它没有工作。
    • 你们有没有找到其他解决方案?
    • @s1ddok Metal / GLES 跨平台支持变得不那么重要了,因为无法运行 Metal 的设备比例下降了。实际上只有模拟器需要 GLES。您可以不使用模拟器,而是让您的 SceneKit / Metal 应用程序以 iOS 和 macOS 为目标,并使用 macOS 进行快速开发构建。 Xcode 9 具有跨平台 SceneKit 和 Metal 项目的模板。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多