Swift SceneKit照明和影响发射纹理

时间:2017-12-13 22:38:02

标签: ios swift scenekit

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

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

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


}

2 个答案:

答案 0 :(得分:7)

SceneKit shader modifiers非常适合这类任务。

您可以看到最终结果here的视频。

片段着色器修饰符

visual representation of _lightingContribution.diffuse and the final result of the shader modifier

我们可以使用_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. 从一个中减去它以获得"暗侧"
  3. 的亮度
  4. 使用漫反射UV坐标从自定义纹理中获取发射(授予发射和漫反射纹理具有相同的纹理)并通过乘法将亮度应用于它
  5. 将其添加到最终输出颜色(与应用常规发射的方式相同)
  6. 对于着色器部分而言,现在让我们通过Swift方面。

    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在运动中。

    请注意,光源必须非常明亮,否则会在" globe"周围看到昏暗的灯光。我必须在您的设置中将lightNode.light?.intensity设置为至少2000以使其按预期工作。您可能希望尝试计算光度并将其应用于发射以获得更好的结果。

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

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

答案 1 :(得分:1)

我喜欢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);
"""