On my SceneKit node, I want to apply a frosted glass effect like this:
https://www.shadertoy.com/view/WdSGz1
This shader takes a texture as input and outputs a "blurry" version of the texture. What I want is to create a SCNProgram() and apply it to the material of one my node so that it takes this frosted glass texture.
I tried reproducing this with a metal shader but I am not very familiar with how it works. This is what I wrote:
#include <metal_stdlib>
using namespace metal;
#include <SceneKit/scn_metal>
struct NodeBuffer {
float4x4 modelTransform;
float4x4 modelViewProjectionTransform;
float4x4 modelViewTransform;
float4x4 normalTransform;
float2x3 boundingBox;
};
struct MyNodeBuffer {
float4x4 modelTransform;
float4x4 inverseModelTransform;
float4x4 modelViewTransform;
float4x4 inverseModelViewTransform;
float4x4 normalTransform;
float4x4 modelViewProjectionTransform;
float4x4 inverseModelViewProjectionTransform;
};
struct VertexInput {
float3 position [[attribute(SCNVertexSemanticPosition)]];
float2 uv [[attribute(SCNVertexSemanticTexcoord0)]];
};
struct VertexOut {
float4 position [[position]];
float2 uv;
};
vertex VertexOut vertexFunction(VertexInput in [[ stage_in ]], constant NodeBuffer& scn_node [[buffer(1)]]) {
VertexOut out;
out.position = scn_node.modelViewProjectionTransform * float4(in.position, 1.0);
out.uv = in.uv;
return out;
};
float stepfun(float x) {
return (sign(x) + 1.0) / 2.0;
}
float square(float2 pos) {
return (stepfun(pos.x + 1.0) * stepfun(1.0 - pos.x)) *
(stepfun(pos.y + 1.0) * stepfun(1.0 - pos.y));
};
constexpr sampler textureSampler(coord::normalized, filter::linear, address::repeat);
float2 dist(float2 pos, texture2d<float,access::sample> texture [[ texture(0) ]])
{
float2 offset = pos;
return pos + square((offset - 0.5) * 3.0) * texture.sample(textureSampler, (offset - 0.5) * 5.0).xy * 0.05;
}
fragment float4 fragmentFunction(VertexOut out [[ stage_in ]],
texture2d<float, access::sample> texture [[ texture(0) ]],
device float3 *resolution [[ buffer(0) ]])
{
float2 uv = out.uv / resolution[0].xy;
float4 tex = texture.sample(textureSampler, dist(uv, texture));
tex.a = 0.5;
return tex;
}
However, I need to give a texture as input to my fragmentFunction and I don't know what this is supposed to be. I would like it to be the texture my node would have without the shader.
Well, I have probably found something similar.
Try to use a Normal Map, that is kind alike to some frost effect.
func foregroundObjectFrostNormal() {
let plane = SCNPlane(width: 3.0, height: 3.0) // SCNSphere(radius: 1.5)
plane.firstMaterial?.diffuse.contents = UIColor.lightGray
plane.firstMaterial?.normal.contents = UIImage.init(named: "art.scnassets/frost_normal/frost_long.png")
plane.firstMaterial?.roughness.contents = 0.1
plane.firstMaterial?.metalness.contents = 0.0
plane.firstMaterial?.transparency = 0.25
plane.firstMaterial?.lightingModel = .physicallyBased
let node = SCNNode(geometry: plane)
node.position = SCNVector3(0.0, 0.0, +3.0)
scene.rootNode.addChildNode(node)
}
Here is a project, you can see the effect and play around with: https://drive.google.com/file/d/1mCZ2OrxkCENO6TMeQe5HctyAdpgz5gv8/view?usp=share_link