如何编写一个SceneKit着色器修改器以实现溶解效果

14

我想为Scenekit游戏构建一个溶解效果。由于它们似乎是最轻量级的,我一直在研究着shader modifier,但没有成功复制这个效果:

Dissolve shader effect

是否可以使用着色器修改器来创建这种效果?如何实现?

1个回答

51
您可以通过片段着色器修改器接近预期效果。基本方法如下:
  • 从噪声纹理中采样
  • 如果噪声采样低于某个阈值(我称之为“展示度”),则将其丢弃,使其完全透明
  • 否则,如果片段接近边缘,则用您喜欢的边缘颜色(或渐变)替换其颜色
  • 应用bloom使边缘发光
以下是执行此操作的着色器修改器代码:
#pragma arguments

float revealage;
texture2d<float, access::sample> noiseTexture;

#pragma transparent
#pragma body

const float edgeWidth = 0.02;
const float edgeBrightness = 2;
const float3 innerColor = float3(0.4, 0.8, 1);
const float3 outerColor = float3(0, 0.5, 1);
const float noiseScale = 3;

constexpr sampler noiseSampler(filter::linear, address::repeat);
float2 noiseCoords = noiseScale * _surface.ambientTexcoord;
float noiseValue = noiseTexture.sample(noiseSampler, noiseCoords).r;

if (noiseValue > revealage) {
    discard_fragment();
}

float edgeDist = revealage - noiseValue;
if (edgeDist < edgeWidth) {
    float t = edgeDist / edgeWidth;
    float3 edgeColor = edgeBrightness * mix(outerColor, innerColor, t);
    _output.color.rgb = edgeColor;
}

请注意,revealage参数作为材质参数暴露出来,因为您可能想要对其进行动画处理。还有其他内部常量,如边缘宽度和噪声比例可以微调以使内容产生所需的效果。
不同的噪声纹理会产生不同的溶解效果,因此您也可以尝试进行实验。我只是使用了这个多八度值噪声图像:

Value noise image used as dissolve texture

将图像加载为UIImageNSImage,并将其设置在作为noiseTexture公开的材质属性上:

material.setValue(SCNMaterialProperty(contents: noiseImage), forKey: "noiseTexture")

您需要将bloom作为后处理程序添加,以获得那种发光的电线效果。在SceneKit中,这只需启用HDR管道并设置一些参数即可:

let camera = SCNCamera()
camera.wantsHDR = true
camera.bloomThreshold = 0.8
camera.bloomIntensity = 2
camera.bloomBlurRadius = 16.0
camera.wantsExposureAdaptation = false

所有数字参数可能需要根据您的内容进行调整。

为了保持整洁,我喜欢将着色器修饰符保存在它们自己的文本文件中(我将其命名为“dissolve.fragment.txt”)。以下是如何加载一些修改器代码并将其附加到材质上的方法。

let modifierURL = Bundle.main.url(forResource: "dissolve.fragment", withExtension: "txt")!
let modifierString = try! String(contentsOf: modifierURL)
material.shaderModifiers = [
    SCNShaderModifierEntryPoint.fragment : modifierString
]

最后,要实现动画效果,您可以使用一个包装在SCNAnimation中的CABasicAnimation

let revealAnimation = CABasicAnimation(keyPath: "revealage")
revealAnimation.timingFunction = CAMediaTimingFunction(name: .linear)
revealAnimation.duration = 2.5
revealAnimation.fromValue = 0.0
revealAnimation.toValue = 1.0
let scnRevealAnimation = SCNAnimation(caAnimation: revealAnimation)
material.addAnimation(scnRevealAnimation, forKey: "Reveal")

Et voila!


1
Warren,这太棒了。高层次的概述以及详细的实现非常有帮助。当你分解它并使用CABasicAnimation包装SCNAnimation时,它变得非常清晰易懂。 - soCohesive
1
感谢您提供这个很棒的答案。我认为上面的动画代码中缺少一行。请确保添加 revealAnimation.autoreverses = true。它将自动反转动画,使您的动画像上面那样工作。 - ddeger
1
是的,为了创建上面的图形,我使用了一个具有无限repeatCount/repeatDuration的自动反转动画。这两个参数都不是展示效果所必需的,因此我在代码中省略了它们。 - warrenm
首先...这太棒了!真的不仅是一个非常棒的效果,而且回答也非常好!投票支持!不过问题是...你如何检测边缘?我的理解是,“你接近隐藏和显示之间的阈值,因此这是一个边缘?” - Mark A. Donohoe
当我尝试运行这段代码时,它无法找到揭示路径(revealage keyPath),即使着色器代码中有这个揭示参数。可能的问题是什么? 我是这样设置平面材质的。let material = SCNMaterial() material.setValue(SCNMaterialProperty(contents: noiseImage), forKey: "noiseTexture") plane.materials = [material]出现了以下错误: [SceneKit] Error: _C3DModelPathResolverRegistryResolvePathWithClassName unknown path ( revealage ) - Ozgur Sahin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接