How to write a sceneKit shader modifier for a dissolve in effect

后端 未结 1 951
借酒劲吻你
借酒劲吻你 2020-12-23 23:56

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 i

1条回答
  •  时光说笑
    2020-12-24 00:22

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

    Et voila!

    0 讨论(0)
提交回复
热议问题