使用SCNMorpher会在SceneKit

时间:2015-07-14 09:33:23

标签: scenekit

我在iOS SceneKit中使用SCNMorpher在从Blender导出为DAE文件的3D人脸模型上的不同面部表情之间进行变换。变形本身很好。

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

但是一旦我进行了这个调用,所有的多边形边缘都变得可见,这是非常不吸引人的。这与Blender本身从“平滑”渲染到“平面”渲染的区别相同。

图像如下:首先是平滑渲染,预变形,然后是平面渲染,后变形。

Smooth rendering, pre-morph Flat rendering, 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)

3 个答案:

答案 0 :(得分:5)

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

goodPeople.forEach(function(element){
 element.family = true;
});

答案 1 :(得分:0)

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

事实上,使用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);
}

答案 2 :(得分:0)

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

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

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

重新计算法线是可扩展的,因此为了保存性能,您可能希望导出具有平滑法线的模型。

如果使用ModelI / O向平方目标添加平滑法线,则promblem应该消失。

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;
}