如何为溶解效果编写 sceneKit 着色器修改器

Posted

技术标签:

【中文标题】如何为溶解效果编写 sceneKit 着色器修改器【英文标题】:How to write a sceneKit shader modifier for a dissolve in effect 【发布时间】:2019-06-30 21:47:41 【问题描述】:

我想为 Scenekit 游戏构建溶解效果。我一直在研究着色器修改器,因为它们似乎是最轻的并且在复制这种效果方面没有任何运气:

是否可以使用着色器修改器来创建这种效果? 你将如何实施一个?

【问题讨论】:

【参考方案1】:

您可以使用片段着色器修改器获得非常接近预期效果。基本做法如下:

噪声纹理样本 如果噪声样本低于某个阈值(我称之为“揭示”),则将其丢弃,使其完全透明 否则,如果片段靠近边缘,请将其颜色替换为您喜欢的边缘颜色(或渐变) 应用绽放使边缘发光

这是执行此操作的着色器修改器代码:

#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 参数作为材质参数公开,因为您可能希望对其进行动画处理。还有其他内部常数,例如边缘宽度和噪声比例,可以微调以获得所需的内容效果。

不同的噪波纹理会产生不同的溶解效果,因此您也可以尝试一下。我刚刚使用了这个多倍频程值噪声图像:

将图像加载为UIImageNSImage,并将其设置为暴露为noiseTexture 的材质属性:

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

您需要添加光晕作为后期处理,以获得发光的电子线效果。在 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
]

最后,为了动画效果,您可以使用 CABasicAnimation 包裹 SCNAnimation

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")

【讨论】:

沃伦这太棒了。对正在发生的事情的高级概述以及详细的实现确实非常有帮助。当您将其分解并使用带有 SCNAnimation 的 CABasicAnimation 时,这很有意义。 感谢您的出色回答。我认为上面的动画代码缺少一行。确保添加revealAnimation.autoreverses = true。它将自动反转动画,您的动画将像上面一样工作。 是的,为了创建上图,我使用了具有无限重复计数/重复持续时间的自动反转动画。两者都不是演示效果所必需的,所以我在代码中省略了它们。 首先......这太棒了!说真的……不仅效果非常好,而且答案也非常出色!投票赞成!问题虽然......你如何检测边缘?我是否理解“你接近隐藏和揭示之间的门槛,所以这是一个边缘?” 当我尝试这段代码时,即使着色器代码有这个revealage参数,它也找不到revealage keyPath。可能是什么问题呢?我这样设置平面材质。 let material = SCNMaterial() material.setValue(SCNMaterialProperty(contents: noiseImage), forKey: "noiseTexture") plane.materials = [material] 得到这个错误:[SceneKit] Error: _C3DModelPathResolverRegistryResolvePathWithClassName unknown path (revealage)

以上是关于如何为溶解效果编写 sceneKit 着色器修改器的主要内容,如果未能解决你的问题,请参考以下文章

SceneKit - 在着色器修改器中访问目标颜色以进行混合

如何为小部件边框/阴影添加霓虹发光效果?

iOS10 + SceneKit:使用自定义着色器渲染视频

带有 SceneKit SCNProgram 的金属着色器

将 Metal 缓冲区传递给 SceneKit 着色器

为啥金属着色器渐变作为 SCNProgram 应用于 SceneKit 节点比作为 MTKView 更轻?