LoginSignup
1
0

RealityKitのCustomMaterialでアウトラインを描くShader

Posted at

RealityKitのCustomMaterialでアウトラインを描くShader

RealityKitのCustomMaterialでアウトラインを描くShaderを作ってみました
オマケでゲーミングアウトラインモードもあります

リポジトリはこちら

アウトラインを描く仕組み

今回は最もシンプルな?法線方向へモデルを拡大して塗りつぶした画像を合成することでアウトラインを描く仕組みを実装しました

Frame 294.png

こちらのブログを大変参考にさせていただきました
他にもPost Effectを利用する方法もあるようです

アウトラインを付ける実装

こちらの記事で取り扱ったRealityKitのnonARモードで試してます

CustomMaterialの設定

アウトラインをつけたいEntityのクローンを作成し、CustomMaterialを設定し、子Entityとして追加します
surfaceShadergeometryShader はShaderで、実装は後述します
GeometryModifierでモデルを拡大し、SurfaceShaderで任意の色で塗りつぶします
カリングで前方を指定し、被っている範囲は描画しないようにします

let plane: Entity // アウトラインをつけたいEntity
let clone = plane.clone(recursive: true)

let device = MTLCreateSystemDefaultDevice()!
let library = device.makeDefaultLibrary()!
let surfaceShader = CustomMaterial.SurfaceShader(named: "surfaceShader", in: library)
let geometryShader = CustomMaterial.GeometryModifier(named: "geometryShader", in: library)
clone.modifyMaterials { original in
    var mat = try! CustomMaterial(from: original, surfaceShader: surfaceShader, geometryModifier: geometryShader)
    mat.faceCulling = .front
    customMaterials.append(mat)
    return mat
}
plane.addChild(clone)

// https://developer.apple.com/documentation/realitykit/altering_realitykit_rendering_with_shader_functions
extension Entity {
    func modifyMaterials(_ closure: (RealityKit.Material) throws -> RealityKit.Material) rethrows {
        try children.forEach { try $0.modifyMaterials(closure) }

        guard var comp = components[ModelComponent.self] as? ModelComponent else { return }
        comp.materials = try comp.materials.map { try closure($0) }
        components[ModelComponent.self] = comp
    }
}

Shaderの実装

まずはシンプルにアウトラインを描くShaderです
metalファイルに記述します
geometryShaderでモデルの各頂点を法線方向へ押し出し、
surfaceShaderでモデルを真っ黒に塗りつぶします

#include <metal_stdlib>
#include <RealityKit/RealityKit.h>
using namespace metal;

[[visible]]
void geometryShader(realitykit::geometry_parameters params) {
    float3 norm = params.geometry().normal();

    params.geometry().set_model_position_offset(norm * 0.4);
    params.geometry().set_normal(-norm);
}

[[visible]]
void surfaceShader(realitykit::surface_parameters params) {
    params.surface().set_base_color(half3());
    params.surface().set_ambient_occlusion(0);
}

結果

シンプルな黒縁のアウトラインを描くことができました

Shaderに動的に値を渡す

UI上のスライダーでアウトラインの太さやゲーミングモードをコントロールするためには、Shaderに対して動的に値を渡す必要があります
動的に値を渡すためには、CustomMaterial.customを利用します

customに値を渡す

SIMD4を渡せるので、xにゲーミングの有効フラグ、yにゲーミングの色を流すタイミング、wにアウトラインの太さを入れます

var offset = 0
let width: Float = 0.2
// ---
offset %= 30
clone.setCustomVector(vector: .init(x: gaming ? 1 : 0, y: Float(offset) / 30, z: 0, w: width))
            
// https://developer.apple.com/documentation/realitykit/altering_realitykit_rendering_with_shader_functions
extension Entity {
    func setCustomVector(vector: SIMD4<Float>) {
        children.forEach { $0.setCustomVector(vector: vector) }

        guard var comp = components[ModelComponent.self] as? ModelComponent else { return }
        comp.materials = comp.materials.map { (material) -> Material in
            if var customMaterial = material as? CustomMaterial {
                customMaterial.custom.value = vector
                return customMaterial
            }
            return material
        }
        components[ModelComponent.self] = comp
    }
}

customの値を読み取る

Shaderで設定された値をparams.uniforms().custom_parameter()で読み取ります
geometryShaderではw、surfaceShaderではxとyを使用します
スクリーン上の位置とタイミングとyのタイミングを足して、色相に変換することで、流れるゲーミングを再現してます

#include <metal_stdlib>
#include <RealityKit/RealityKit.h>
using namespace metal;

[[visible]]
void geometryShader(realitykit::geometry_parameters params) {
    float3 norm = params.geometry().normal();

    params.geometry().set_model_position_offset(norm * params.uniforms().custom_parameter().w);
    params.geometry().set_normal(-norm);
}

half3 hue2Rgba(half h) {
    half hueDeg = h * 360.0;
    half x = (1 - abs(fmod(hueDeg / 60.0, 2) - 1));
    half3 rgba;
    if (hueDeg < 60) rgba = half3(1, x, 0);
    else if (hueDeg < 120) rgba = half3(x, 1, 0);
    else if (hueDeg < 180) rgba = half3(0, 1, x);
    else if (hueDeg < 240) rgba = half3(0, x, 1);
    else if ( hueDeg < 300) rgba = half3(x, 0, 1);
    else rgba = half3(1, 0, x);
    return rgba;
}

[[visible]]
void surfaceShader(realitykit::surface_parameters params) {
    auto cp = params.uniforms().custom_parameter();
    float4 position = params.geometry().screen_position();
    params.surface().set_base_color(cp.x > 0.5 ? hue2Rgba(position.x / 2000 + cp.y) : half3());
    params.surface().set_ambient_occlusion(cp.x > 0.5 ? 1 : 0);
}

結果

アウトラインの太さを変更や、ゲーミングモードを実装できました

output-palette.gif

まとめ

RealityKitのCustomMaterialでアウトラインを描くShaderを作ってみました
情報が少なすぎたり、カメラを起動しないといけなかったりで、開発のハードルは高いですが、
RealityKitは思っていたより、様々な機能があり、柔軟性もありそうです
また、nonARモードとSimulatorを使用することで、ARのためのカメラを起動せずに開発できたので良かったです

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0