在Scenekit中添加半透明图像作为纹理



当我添加半透明图像(示例)作为SCNNode的纹理时,如何为图像透明的节点指定颜色属性。由于我可以指定颜色或图像作为材质属性,因此无法指定节点的颜色值。是否有方法为材质属性指定颜色和图像,或者是否有解决此问题的方法。

如果将图像指定给transparent材质特性的contents,则可以将材质transparencyMode更改为.AOne.RGBZero

  • .AOne意味着透明度是从图像阿尔法通道导出的
  • .RGBZero意味着从图像中的亮度(红色、绿色和蓝色的总和)导出透明度

如果没有自定义着色器,就无法将任意颜色配置为透明。

但是,从示例图像的外观来看,我认为将示例图像指定给transparent材质属性contents并使用.AOne透明度模式会得到您想要的结果。

我将此作为新答案发布,因为它与其他答案不同。


根据您的评论,我理解您希望使用具有透明度的图像作为材质的漫反射内容,但在图像透明的地方使用背景色。换句话说,您不需要使用图像颜色上的合成作为漫反射内容。

使用UIImage

有几种不同的方法可以实现这种合成图像。最简单也可能是最熟悉的解决方案是创建一个新的UIImage,在颜色上绘制图像。此新图像的大小和比例与您的图像相同,但可能是不透明的,因为它具有纯色背景。

func imageByComposing(image: UIImage, over color: UIColor) -> UIImage {
    UIGraphicsBeginImageContextWithOptions(image.size, true, image.scale)
    defer {
        UIGraphicsEndImageContext()
    }
    let imageRect = CGRect(origin: .zero, size: image.size)
    // fill with background color
    color.set()
    UIRectFill(imageRect)
    // draw image on top
    image.drawInRect(imageRect)
    return UIGraphicsGetImageFromCurrentImageContext()
}

使用此图像作为漫反射材质属性的内容将为您提供所追求的效果。

使用着色器修改器

如果您发现自己必须非常频繁地更改颜色(可能为其设置动画),也可以使用自定义着色器或着色器修改器在颜色上合成图像。

在这种情况下,您希望将图像A合成到颜色B上,以便输出颜色(CO)为:

CO=CA+CB*(1-?A

通过将图像作为漫反射内容传递,并将输出分配给漫反射内容,表达式可以简化为:

C漫射=C散射+C颜色*(1-?漫反射
C漫射+=C颜色*(1-?散射

通常,输出alpha将取决于A和B的alpha,但由于B(颜色)是不透明的(1),因此输出alpha也是1。

这可以写成一个小的着色器修改器。由于这种解决方案的动机是能够更改颜色,因此颜色被创建为可以在代码中更新的统一变量。

// Define a color that can be set/changed from code
uniform vec3 backgroundColor;
#pragma body
// Composit A (the image) over B (the color):
//  output = image + color * (1-alpha_image)
float alpha = _surface.diffuse.a;
_surface.diffuse.rgb += backgroundColor * (1.0 - alpha);
// make fully opaque (since the color is fully opaque)
_surface.diffuse.a = 1.0;

然后将从文件中读取该着色器修改器,并在材质着色器修改器字典中进行设置

enum ShaderLoadingError: ErrorType {
    case FileNotFound, FailedToLoad
}
func shaderModifier(named shaderName: String, fileExtension: String = "glsl") throws -> String {
    guard let url = NSBundle.mainBundle().URLForResource(shaderName, withExtension: fileExtension) else {
        throw ShaderLoadingError.FileNotFound
    }
    do {
        return try String(contentsOfURL: url)
    } catch {
        throw ShaderLoadingError.FailedToLoad
    }
}
// later, in the code that configures the material ...
do {
    let modifier = try shaderModifier(named: "Composit") // the name of the shader modifier file (assuming 'glsl' file extension) 
    theMaterial.shaderModifiers = [SCNShaderModifierEntryPointSurface: modifier]
} catch {
    // Handle the error here
    print(error)
}

然后,您可以通过为材质的"backgroundColor"设置一个新值来更改颜色。请注意,没有初始值,因此必须设置一个初始值。

let backgroundColor = SCNVector3Make(1.0, 0.0, 0.7) // r, g, b
// Set the color components as an SCNVector3 wrapped in an NSValue
// for the same key as the name of the uniform variable in the sahder modifier
theMaterial.setValue(NSValue(SCNVector3: backgroundColor), forKey: "backgroundColor")

正如你所看到的,第一种解决方案更简单,如果它适合你的需求,我会推荐它。第二种解决方案比较复杂,但可以设置背景颜色的动画。

万一将来有人遇到这个。。。对于某些任务,ricksters解决方案可能是最简单的。在我的例子中,我想在映射到球体的图像顶部显示一个网格。我最初把这些图像合成一个并应用它们,但随着时间的推移,我变得越来越花哨,这开始变得复杂。所以我做了两个球体,一个在另一个里面。我把网格放在里面,把图像放在外面,然后。。。

    let outSphereGeometry = SCNSphere(radius: 20)
    outSphereGeometry.segmentCount = 100
    let outSphereMaterial = SCNMaterial()
    outSphereMaterial.diffuse.contents = topImage
    outSphereMaterial.isDoubleSided = true
    outSphereGeometry.materials = [outSphereMaterial]
    outSphere = SCNNode(geometry: outSphereGeometry)
    outSphere.position = SCNVector3(x: 0, y: 0, z: 0)
    let sphereGeometry = SCNSphere(radius: 10)
    sphereGeometry.segmentCount = 100
    sphereMaterial.diffuse.contents = gridImage
    sphereMaterial.isDoubleSided = true
    sphereGeometry.materials = [sphereMaterial]
    sphere = SCNNode(geometry: sphereGeometry)
    sphere.position = SCNVector3(x: 0, y: 0, z: 0)

我很惊讶,我不需要设置sphereMaterial.transparency,它似乎会自动得到这个。

最新更新