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

简而言之,我的要求:(我想隐藏发光点,在光线照射到表面的地方)ios - Swift SceneKit 照明和影响发射纹理-LMLPHP

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


}

最佳答案

SceneKit 的 shader modifiers 非常适合这种任务。

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

片段着色器修改器

ios - Swift SceneKit 照明和影响发射纹理-LMLPHP

我们可以使用 _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
  • 计算 _lightingContribution.diffuse 颜色的亮度(使用公式 from here )(如果照明不是纯白色)
  • 从 1 中减去它以获得“暗面”的亮度
  • 使用漫反射 UV 坐标从自定义纹理获取发射(授予的发射和漫反射纹理具有相同的坐标)并通过乘法对其应用亮度
  • 将其添加到最终输出颜色(与应用常规发射的方式相同)

  • 着色器部分就是这样,现在让我们来看看 Swift 的一面。

    快速设置

    首先,我们 不是 要使用 Material 的 emission.contents 属性,而是需要创建自定义 SCNMaterialProperty
    let emissionTexture = UIImage(named: "earthEmission.jpg")!
    let emission = SCNMaterialProperty(contents: emissionTexture)
    

    并使用 setValue(_:forKey:) 将其设置为 Material
    earthMaterial.setValue(emission, forKey: "emissionTexture")
    

    密切注意 键——它应该与着色器修改器中的统一体相同。此外,您不需要自己保留 Material 属性,setValue 创建了一个强引用。

    剩下要做的就是将片段着色器修改器设置为 Material :
    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;
    

    10-08 13:54