我正在开发一个关于太阳系的应用程序。 我试图关闭发射纹理,光线照射到行星表面。但问题是,默认情况下,发射纹理总是显示发射点,无论光线是否存在。
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
}
答案 0 :(得分:7)
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
_lightingContribution.diffuse
颜色的亮度(使用公式from here)(如果灯光不是纯白色)对于着色器部分而言,现在让我们通过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
是片段着色器修饰符中可用的结构,该结构也包含ambient
和specular
成员(下面是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);
"""