问题
I'd like to build a dissolve in effect for a Scenekit game. I've been looking into shader modifiers since they seem to be the most light weight and haven't had any luck in replicating this effect:
Is it possible to use shader modifiers to create this effect? How would you go about implementing one?
回答1:
You can get pretty close to the intended effect with a fragment shader modifier. The basic approach is as follows:
- Sample from a noise texture
- If the noise sample is below a certain threshold (which I call "revealage"), discard it, making it fully transparent
- Otherwise, if the fragment is close to the edge, replace its color with your preferred edge color (or gradient)
- Apply bloom to make the edges glow
Here's the shader modifier code for doing this:
#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;
}
Notice that the revealage parameter is exposed as a material parameter, since you might want to animate it. There are other internal constants, such as edge width and noise scale that can be fine-tuned to get the desired effect with your content.
Different noise textures produce different dissolve effects, so you can experiment with that as well. I just used this multioctave value noise image:
Load the image as a UIImage
or NSImage
and set it on the material property that gets exposed as noiseTexture
:
material.setValue(SCNMaterialProperty(contents: noiseImage), forKey: "noiseTexture")
You'll need to add bloom as a post-process to get that glowy, e-wire effect. In SceneKit, this is as simple as enabling the HDR pipeline and setting some parameters:
let camera = SCNCamera()
camera.wantsHDR = true
camera.bloomThreshold = 0.8
camera.bloomIntensity = 2
camera.bloomBlurRadius = 16.0
camera.wantsExposureAdaptation = false
All of the numeric parameters will potentially need to be tuned to your content.
To keep things tidy, I prefer to keep shader modifiers in their own text files (I named mine "dissolve.fragment.txt"). Here's how to load some modifier code and attach it to a material.
let modifierURL = Bundle.main.url(forResource: "dissolve.fragment", withExtension: "txt")!
let modifierString = try! String(contentsOf: modifierURL)
material.shaderModifiers = [
SCNShaderModifierEntryPoint.fragment : modifierString
]
And finally, to animate the effect, you can use a CABasicAnimation
wrapped with a 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")
来源:https://stackoverflow.com/questions/54562128/how-to-write-a-scenekit-shader-modifier-for-a-dissolve-in-effect