SCNMaterialProperty.contents
的文档指出它是一个可设置动画的属性,实际上我可以在两种颜色之间执行交叉淡入淡出。但是我无法在两个图像之间交叉淡入淡出。
所以我开始想知道这是否可能,或者我是否需要为此创建一个自定义着色器?
我尝试过隐式动画,在这种情况下它会立即显示“之后”图像:
node.geometry.firstMaterial.diffuse.contents = [UIImage imageNamed:@"before"];
[SCNTransaction begin];
[SCNTransaction setAnimationDuration:5];
node.geometry.firstMaterial.diffuse.contents = [UIImage imageNamed:@"after"];
[SCNTransaction commit];
显式动画,不执行任何操作:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"contents"];
animation.fromValue = (__bridge id)[UIImage imageNamed:@"before"].CGImage;
animation.toValue = (__bridge id)[UIImage imageNamed:@"after"].CGImage;
animation.duration = 5;
[node.geometry.firstMaterial.diffuse addAnimation:animation forKey:nil];
以及通过
CALayer
,它什么也不做:
CALayer *textureLayer = [CALayer layer];
textureLayer.frame = CGRectMake(0, 0, 793, 1006);
textureLayer.contents = (__bridge id)[UIImage imageNamed:@"before"].CGImage;
node.geometry.firstMaterial.diffuse.contents = textureLayer;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"contents"];
animation.fromValue = (__bridge id)[UIImage imageNamed:@"before"].CGImage;
animation.toValue = (__bridge id)[UIImage imageNamed:@"after"].CGImage;
animation.duration = 5;
[textureLayer addAnimation:animation forKey:nil];
根据我自己的测试,当涉及纹理值(而不是纯色值)时,该属性实际上并不可设置动画。这要么是 SceneKit 中的一个错误(即它本来是可动画的,但实际上不起作用),要么是 Apple 文档中的错误(即它本来不是可动画的,但他们说是)。无论哪种方式,您都应该提交该错误,以便当 Apple 修复该错误时您会收到通知。
(看起来这也不是 tvOS 特定的问题——我也在 OS X 上看到了它。)
我可以理解为什么动画纹理过渡可能不存在...从 GL/Metal 的角度来看,这需要绑定一个额外的纹理单元并在过渡期间每个像素进行两次纹理查找(而不是一个)。
我可以想到一些不错的潜在解决方法:
使用着色器修改器。编写一个 GLSL(ish) 片段,如下所示:
uniform sampler2D otherTexture;
uniform float fadeFactor;
#pragma body
vec4 otherTexel = texture2D(otherTexture, _surface.diffuseTexcoord);
_surface.diffuse = mix(_surface.diffuse, otherTexel, fadeFactor);
使用
SCNShaderModifierEntryPointSurface
入口点将其设置在要设置动画的材质上。然后使用 setValue:forKey:
将 SCNMaterialProperty
与 otherTexture
关联,并使用 CABasicAnimation
将 fadeFactor
从 0 动画到 1。使用更具动画效果的内容(例如 SpriteKit 场景)作为材质属性内容,并为其设置动画以执行过渡。 (作为奖励,当您这样做时,您可以使用其他过渡样式。)
您的动画没有发生,因为“内容”属性仅在设置为非图像颜色时才可设置动画。您可以在苹果关于内容属性的文档中阅读它。
我找到了一种更简单的方法来满足我的特殊需求。这不是真正的交叉淡入淡出,因为两个纹理依次淡出而不是“同时”淡出。但已经很接近了。 虽然
contents
属性不能通过通常的方式设置动画,但
intensity
属性却可以。
用 intensity
CAKeyframeAnimation
。当 contents
intensity
设置为新纹理。将 intensity
let animation = CAKeyframeAnimation(keyPath: "multiply.intensity")
animation.values = [1.0, 0.0, 0.0, 1.0]
animation.keyTimes = [0.0, 0.4, 0.6, 1.0]
animation.autoreverses = false
animation.duration = 1.0
material.addAnimation(animation, forKey: nil)
//I'm using DispatchQueue, but you could use SCNAction instead:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.45) {
//The animation has reached a value of zero. Set the new texture:
material.multiply.contents = newTexture
}