【问题标题】:Use of SCNMorpher produces undesired flat polygon rendering in SceneKit在 SceneKit 中使用 SCNMorpher 会产生不需要的平面多边形渲染
【发布时间】:2015-10-02 20:52:22
【问题描述】:

我在 iOS SceneKit 中使用 SCNMorpher 在从 Blender 导出为 DAE 文件的 3D 面部模型上的不同面部表情之间变形。变形本身工作正常。

在我第一次在变形器上调用setWeight:forTargetAtIndex: 之前,模型会根据需要平滑渲染。

但是一旦我打了那个电话,所有的多边形边缘都变得可见,这很不吸引人。这与在 Blender 本身中从“平滑”渲染切换到“平面”渲染的区别相同。

图像如下:首先是平滑渲染,pre-morph,然后是平面渲染,post-morph。

我正在使用 Lambert 光照模型(尽管其他模型受到的影响相同),并且 litPerPixel 对每个目标几何体的每种材质都是正确的。

我不清楚这是 SCNMorpher 的已知/故意限制、错误还是我做错了什么。我想知道变形是否以某种方式破坏了通常用于平滑渲染的顶点法线数据。

任何人都可以散发的光芒将不胜感激。 (我想一个可能的解决方法可能是通过插值所有顶点和法线向量来手动进行变形以形成一个新的几何体,但我想这会非常慢)。

相关部分代码如下:

faceNode.geometry = faces.rootNode.childNodeWithName("neutral", recursively: true)!.geometry
scene.rootNode.addChildNode(faceNode)

var morphs: [SCNGeometry] = []

let moods: [String] = "mood1 mood2".componentsSeparatedByString(" ")
for mood in moods {
  let moodFace = faces.rootNode.childNodeWithName(mood, recursively: true)!.geometry!
  morphs.append(moodFace)
}

let morpher = SCNMorpher()
morpher.targets = morphs

faceNode.morpher = morpher
morpher.setWeight(0.5, forTargetAtIndex: 0)

【问题讨论】:

  • Blender 中如何将法线构建到网格中可能有问题,但这有点超出我的职责范围。 (尝试使用 Blender 为您提供的任何导出选项,或使用其他工具来使用顶点缓冲区布局。iOS 9 / OS X 10.11 中的Model I/O 可能会在这方面有所帮助。)或者它可能是一个 Scenekit 错误 - 你可以总是file it 并找出答案。
  • 谢谢,我已经提交了。

标签: scenekit


【解决方案1】:

刚刚也遇到了这个问题。 您需要在变形器上将 unifiesNormals 设置为 true。

let morpher = SCNMorpher()
morpher.targets = morphs
morpher.unifiesNormals = true
faceNode.morpher = morpher

【讨论】:

  • 嗯——这看起来很有可能解决问题。谢谢。但是该属性仅在 iOS 11 之后才可用,所以当我问这个问题时,它不是一个答案。我当时对此提交了一份错误报告,Apple 声称无法重现该问题,即使我提供了一个最小的测试用例作为 Xcode 项目。但很高兴看到他们最终解决了这个问题!
【解决方案2】:

我上面提到的可能解决方法的更新:通过插入所有顶点和法线向量来手动变形以形成新的几何体。

这实际上工作得非常好,使用 Accelerate/vDSP 进行插值。

我的用例不如 SCNMorpher 提供的通用——我只需要随时在两个模型之间变形。下面的代码实现了这一点(在我的 iPhone 6 上,对于大约 40,000 个顶点的几何图形,速度约为 30fps):

// .h

@property (nonatomic) size_t morphBufferSize;
@property (nonatomic) float* morphBuffer;

// .m (note: we assume floats not doubles by using vDSP_vintb rather than vDSP_vintbD)

- (SCNGeometry*)morphGeometry:(SCNGeometry*)g1 toGeometry:(SCNGeometry*)g2 withWeight:(float)weight {
  SCNGeometrySource* v1 = [g1 geometrySourcesForSemantic:SCNGeometrySourceSemanticVertex].firstObject;
  SCNGeometrySource* n1 = [g1 geometrySourcesForSemantic:SCNGeometrySourceSemanticNormal].firstObject;
  SCNGeometrySource* v2 = [g2 geometrySourcesForSemantic:SCNGeometrySourceSemanticVertex].firstObject;
  SCNGeometrySource* n2 = [g2 geometrySourcesForSemantic:SCNGeometrySourceSemanticNormal].firstObject;

  vDSP_Length len = v1.data.length;
  vDSP_Length numFloats = len / sizeof(float);

  if (_morphBufferSize < len) {
    _morphBuffer = realloc(_morphBuffer, len);
    _morphBufferSize = len;
  }

  vDSP_vintb(v1.data.bytes, 1,
             v2.data.bytes, 1,
             &weight,
             _morphBuffer, 1,
             numFloats);

  SCNGeometrySource* v3 = [SCNGeometrySource geometrySourceWithData:[NSData dataWithBytesNoCopy:_morphBuffer length:len freeWhenDone:NO]
                                                           semantic:SCNGeometrySourceSemanticVertex
                                                        vectorCount:v1.vectorCount
                                                    floatComponents:v1.floatComponents
                                                componentsPerVector:v1.componentsPerVector
                                                  bytesPerComponent:v1.bytesPerComponent
                                                         dataOffset:v1.dataOffset
                                                         dataStride:v1.dataStride];

  vDSP_vintb(n1.data.bytes, 1,
             n2.data.bytes, 1,
             &weight,
             _morphBuffer, 1,
             numFloats);

  SCNGeometrySource* n3  = [SCNGeometrySource geometrySourceWithData:[NSData dataWithBytesNoCopy:_morphBuffer length:len freeWhenDone:NO]
                                                            semantic:SCNGeometrySourceSemanticNormal
                                                         vectorCount:n1.vectorCount
                                                     floatComponents:n1.floatComponents
                                                 componentsPerVector:n1.componentsPerVector
                                                   bytesPerComponent:n1.bytesPerComponent
                                                          dataOffset:n1.dataOffset
                                                          dataStride:n1.dataStride];

  NSMutableArray* elements = [NSMutableArray arrayWithCapacity:g1.geometryElementCount];
  for (NSInteger i = 0, len = g1.geometryElementCount; i < len; i ++) [elements addObject:[g1 geometryElementAtIndex:i]];

  SCNGeometry* g3 = [SCNGeometry geometryWithSources:@[v3, n3] elements:elements];
  return g3;
}

- (void)dealloc {
    free(_morphBuffer);
}

【讨论】:

  • 这对我来说在第二个 vDSP_vintb(n1.data.bytes, 1,
【解决方案3】:

正如 Ray McClure 所说,unifiesNormals 可以正常工作,因为变形器会在应用目标时重新计算顶点的法线。

当对所有相邻面的顶点法线进行平均时,会产生平滑的阴影。在这种情况下,渲染引擎通过为相邻面插入颜色来渲染边缘,看起来面相遇变得更加“平滑”。

为什么你的人脸模型看起来像块状是因为人脸模型的变形目标在网格​​上的顶点法线不平滑(或者没有顶点的法线信息)。

重新计算法线的范围很广,因此为了节省性能,您可能需要导出具有平滑法线的模型。

如果您使用 ModelI/O 向变形器目标添加平滑法线,问题应该会消失。

SCNGeometry* soomthGeoUsingModelIO(SCNGeometry *geo){
    MDLMesh *mesh = [MDLMesh meshWithSCNGeometry:geo];
    MDLMesh *newMesh = [MDLMesh newSubdividedMesh:mesh submeshIndex:0 subdivisionLevels:0];
    //creaseThreshold is the cos value of MAX crease angle you can tolerate
    [newMesh addNormalsWithAttributeNamed:@"normals" creaseThreshold:0];
    SCNGeometry *flatGeo = [SCNGeometry geometryWithMDLMesh:newMesh];
    return flatGeo;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-02-05
    • 1970-01-01
    • 1970-01-01
    • 2019-04-08
    • 1970-01-01
    • 2013-01-21
    • 1970-01-01
    相关资源
    最近更新 更多