【问题标题】:Swift SceneKit lighting and effecting emission textureSwift SceneKit 照明和影响发射纹理
【发布时间】:2018-07-30 23:40:30
【问题描述】:

我正在开发一个关于太阳系的应用程序。 我正在尝试关闭发射纹理,光线照射到行星表面。但问题是,默认情况下,发射纹理始终显示发射点,无论是否存在光。

简而言之我的要求:(我想在光线照射到表面的地方隐藏发射点)

override func viewDidLoad() {
    super.viewDidLoad()

    let scene = SCNScene()

    let earth = SCNSphere(radius: 1)
    let earthNode = SCNNode()
    let earthMaterial = SCNMaterial()
    earthMaterial.diffuse.contents = UIImage(named: "earth.jpg")
    earthMaterial.emission.contents = UIImage(named: "earthEmission.jpg")
    earth.materials = [earthMaterial]
    earthNode.geometry = earth
    scene.rootNode.addChildNode(earthNode)

    let lightNode = SCNNode()
    lightNode.light = SCNLight()
    lightNode.light?.type = .omni
    lightNode.position = SCNVector3(x: 0, y: 10, z: 5)
    scene.rootNode.addChildNode(lightNode)

    sceneView.scene = scene


}

【问题讨论】:

  • 现在面临同样的问题,发射应该只显示在夜间,即由 SCNLight 照亮的一侧。有人有什么想法吗?
  • SceneKit 是否有纹理的位图遮罩?

标签: ios swift scenekit


【解决方案1】:

SceneKit 的shader modifiers 非常适合此类任务。

您可以看到最终结果的镜头here

片段着色器修饰符

我们可以使用_lightingContribution.diffuse(RGB (vec3) 颜色表示应用于漫反射的灯光)来确定物体(在本例中为地球)被照亮的区域,然后使用它来掩盖发射fragment shader modifier 中的纹理。

您使用它的方式完全取决于您。这是我想出的最简单的解决方案(使用GLSL 语法,但如果您使用它,它会在运行时自动转换为Metal

uniform sampler2D emissionTexture;

vec3 light = _lightingContribution.diffuse;
float lum = max(0.0, 1 - (0.2126*light.r + 0.7152*light.g + 0.0722*light.b)); // 1
vec4 emission = texture2D(emissionTexture, _surface.diffuseTexcoord) * lum; // 2, 3
_output.color += emission; // 4
  1. 计算_lightingContribution.diffuse颜色的亮度(使用公式from here)(如果照明不是纯白色)
  2. 从 1 中减去它以获得“暗面”的亮度
  3. 使用漫反射 UV 坐标从自定义纹理获取发射(授权发射和漫反射纹理具有相同的坐标)并通过乘法对其应用亮度
  4. 将其添加到最终输出颜色(与应用常规发射的方式相同)

这就是着色器部分,现在让我们来看看 Swift 方面的内容。


快速设置

首先,我们不会使用材质的emission.contents 属性,而是需要创建一个自定义SCNMaterialProperty

let emissionTexture = UIImage(named: "earthEmission.jpg")!
let emission = SCNMaterialProperty(contents: emissionTexture)

并使用setValue(_:forKey:)将其设置为材质

earthMaterial.setValue(emission, forKey: "emissionTexture")

密切注意键 - 它应该与着色器修改器中的制服相同。此外,您不需要自己保留材料属性,setValue 会创建一个强引用。

剩下要做的就是将片段着色器修改器设置为材质:

let shaderModifier =
"""
uniform sampler2D emissionTexture;

vec3 light = _lightingContribution.diffuse;
float lum = max(0.0, 1 - (0.2126*light.r + 0.7152*light.g + 0.0722*light.b));
vec4 emission = texture2D(emissionTexture, _surface.diffuseTexcoord) * lum;
_output.color += emission;
"""
earthMaterial.shaderModifiers = [.fragment: shaderModifier]

这是此着色器修改器的footage 动态。

请注意,光源必须非常明亮,否则“地球”周围会出现昏暗的灯光。我必须在您的设置中将 lightNode.light?.intensity 设置为至少 2000 才能按预期工作。您可能想尝试计算亮度并将其应用于发射的方式以获得更好的结果。


如果您可能需要它,_lightingContribution 是片段着色器修改器中可用的结构,它也有 ambientspecular 成员(下面是 Metal 语法):

struct SCNShaderLightingContribution {
    float3 ambient;
    float3 diffuse;
    float3 specular;
} _lightingContribution;

【讨论】:

  • 谢谢 Lësha,这完全符合我的需要。也很高兴我现在知道如何设置着色器修改器,以前在 Unity 中做过,只是在 Swift/Scenekit 中还没有!
【解决方案2】:

我喜欢 Lësha 的回答,对着色器进行了小修改,以便它可以在较低的光照水平下工作。添加了一个阈值 (t),低于该阈值将不会显示发射值,然后在阈值和零之间,它会在漫反射和漫反射 + 发射之间插入值。改变 t 的值可以调整描绘白天和黑夜之间过渡的带的宽度。我还在发射公式上附加了一个 0.5 乘数,因为我使用的发射纹理在没有它的情况下看起来人为地明亮。

let shaderModifier =
"""
uniform sampler2D emissionTexture;

vec3 light = _lightingContribution.diffuse;
float lum = max(0.0, 1 - (0.2126*light.r + 0.7152*light.g + 0.0722*light.b));
vec4 emission = texture2D(emissionTexture, _surface.diffuseTexcoord) * lum * 0.5;
float t = 0.11; // no emission will show above this threshold
_output.color = vec4(
    light.r > t ? _output.color.r : light.r/t * _output.color.r + (1-light.r/t) * (_output.color.r + emission.r),
    light.g > t ? _output.color.g : light.g/t * _output.color.g + (1-light.g/t) * (_output.color.g + emission.g),
    light.b > t ? _output.color.b : light.b/t * _output.color.b + (1-light.b/t) * (_output.color.b + emission.b),1);
"""

【讨论】:

    猜你喜欢
    • 2015-06-29
    • 1970-01-01
    • 1970-01-01
    • 2017-09-26
    • 2016-09-05
    • 2021-08-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多