【问题标题】:Adding semi-transparent images as textures in Scenekit在 Scenekit 中添加半透明图像作为纹理
【发布时间】:2016-02-20 03:46:46
【问题描述】:

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

【问题讨论】:

  • 您可以在此处找到在 iOS 下使用 OpenGL 执行此操作的工作示例,同样的方法可以适用于您的 SceneKit impl。 modejong.com/blog/…

标签: ios swift scenekit


【解决方案1】:

如果您将图像分配给transparent 材质属性的contents,您可以将材质transparencyMode 更改为.AOne.RGBZero

  • .AOne 表示透明度来自图像的 Alpha 通道。
  • .RGBZero 表示透明度源自图像中的亮度(红、绿和蓝的总和)。

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

但是,从您的示例图像的外观来看,我认为将示例图像分配给 transparent 材料属性 contents 并使用 .AOne 透明度模式将为您提供您正在寻找的结果。

【讨论】:

  • 我尝试将示例图像分配给transparent 材质属性内容并使用AOne 透明度模式,但它仅使用图像中的 alpha 值而忽略了图像的颜色值。例如,我为分配了黄色的SCNBox 做了这个,我得到的是this image,但我想要的是this
  • @Krish 你找到解决方案了吗?
  • @Crashalot 不,我还没有找到解决方案。我需要尝试 MoDJ 建议的方法。
【解决方案2】:

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


根据您的评论,我了解到您希望使用具有透明度的图像作为材质的漫反射内容,但在图像透明的地方使用背景颜色。换句话说,你不会使用a composite of the image over a color 作为漫反射内容。

使用 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 上合成,以便输出颜色(C O) 是:

CO = CA + CB * (1 - ɑA)

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

Cdiffuse = Cdiffuse + Ccolor * (1 - ɑdiffuse)
C漫射 += Ccolor * (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")

如您所见,第一个解决方案更简单,如果它适合您的需求,我会推荐它。第二种解决方案更复杂,但启用了背景颜色动画。

【讨论】:

  • 另一个选项,取决于您的场景需要,可能是分层几何体——例如,将纯色放在一个球体上,将透明图像放在与另一个球体同心且稍大的另一个球体上。在WWDC14 SceneKit demo 中看到有云“滑动”的地球。
【解决方案3】:

以防万一将来有人遇到这种情况...对于某些任务,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,它似乎是自动得到的。

【讨论】:

    猜你喜欢
    • 2020-07-05
    • 2016-07-04
    • 1970-01-01
    • 1970-01-01
    • 2021-04-02
    • 2012-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多