【Unity2018】 ShaderGraphで遊ぼう!その1

Unity2018で、ついにShaderGraphが追加されました。 ↓のような感じで、ノードを繋げることでデザイナーでもシェーダーを簡単に開発することができる機能です。 f:id:notargs:20180508064607g:plain f:id:notargs:20180508065031g:plain

元々Unreal Engine 4などのHD向けのゲームエンジンではあった機能で、これと同じような機能を持ったUnityAssetなども販売されていましたが、ついに公式でのサポートが入りました。

同じくUnity2018から正式版になったScriptableRenderPipelineという機能がありますが、プロジェクトで採用したRenderPipelineごとに大きくシェーダーコードが変わるため、アーティストのレイヤーではそこの差異を吸収したい!という目的もあり、優先的に実装されたっぽい雰囲気を感じています。

今回は、このShaderGraphを使い、適当なサイバーっぽいシェーダーを作ってみようと思います。

続きを読む

【Unity】UGUIのRectTransformを100倍快適に扱うためのユーティリティを作った

概要

この記事はCluster,Inc. Advent Calendar 2017の14日目の記事です。

f:id:notargs:20171214105828g:plain

こんな感じのWindowのようなものを作っていたのですが、 RectTransformの具体的なサイズが知りたい! 親Transformの左端、右端から、相対的に位置を指定したい! など、RectTransformに思うところが多かったので、RectTransformを便利に扱うためのユーティリティを作ってみました。

コード

/**
MIT Lisence

Copyright (c) 2017 Cluster,Inc.

This software is released under the MIT License.
http://opensource.org/licenses/mit-license.php
*/

public enum Corner
{
    LeftTop,
    RightTop,
    LeftBottom,
    RightBottom,
}
/**
MIT Lisence

Copyright (c) 2017 Cluster,Inc.

This software is released under the MIT License.
http://opensource.org/licenses/mit-license.php
*/

using System;
using System.Linq;
using UnityEngine;

public static class RectTransformUtil
{
    public static Bounds GetWorldBounds(this RectTransform rectTransform)
    {
        var corners = new Vector3[4];
        rectTransform.GetWorldCorners(corners);

        var center = corners.Aggregate(Vector3.zero, (current, corner) => current + corner) / corners.Length;
        var size = new Vector3(
            corners.Max(corner => corner.x) - corners.Min(corner => corner.x),
            corners.Max(corner => corner.y) - corners.Min(corner => corner.y),
            1);
        return new Bounds(center, size);
    }

    public static Vector2 GetRelativePosition(this RectTransform rectTransform, Corner corner)
    {
        var parentBounds = rectTransform.parent.GetComponent<RectTransform>().GetWorldBounds();
        var bounds = rectTransform.GetWorldBounds();

        var pos = Vector2.zero;
            
        switch (corner)
        {
            case Corner.LeftBottom:
            case Corner.RightBottom:
                pos.y = bounds.min.y - parentBounds.min.y;
                break;
            case Corner.LeftTop:
            case Corner.RightTop:
                pos.y = parentBounds.max.y - bounds.max.y;
                break;
            default:
                throw new ArgumentOutOfRangeException("corner", corner, null);
        }

        switch (corner)
        {
            case Corner.LeftTop:
            case Corner.LeftBottom:
                pos.x = bounds.min.x - parentBounds.min.x;
                break;
            case Corner.RightTop:
            case Corner.RightBottom:
                pos.x = parentBounds.max.x - bounds.max.x;
                break;
            default:
                throw new ArgumentOutOfRangeException("corner", corner, null);
        }

        return pos;
    }

    public static void SetRelativePosition(this RectTransform rectTransform, Vector2 pos, Corner corner)
    {
        var parentBounds = rectTransform.parent.GetComponent<RectTransform>().GetWorldBounds();
        var bounds = rectTransform.GetWorldBounds();
        var anchoredPosition = rectTransform.position;

        switch (corner)
        {
            case Corner.LeftBottom:
            case Corner.RightBottom:
                anchoredPosition.y += parentBounds.min.y - bounds.min.y + pos.y;
                break;
            case Corner.LeftTop:
            case Corner.RightTop:
                anchoredPosition.y += parentBounds.max.y - bounds.max.y - pos.y;
                break;
            default:
                throw new ArgumentOutOfRangeException("corner", corner, null);
        }
            
        switch (corner)
        {
            case Corner.LeftTop:
            case Corner.LeftBottom:
                anchoredPosition.x += parentBounds.min.x - bounds.min.x + pos.x;
                break;
            case Corner.RightTop:
            case Corner.RightBottom:
                anchoredPosition.x += parentBounds.max.x - bounds.max.x - pos.x;
                break;
            default:
                throw new ArgumentOutOfRangeException("corner", corner, null);
        }
            
        rectTransform.position = anchoredPosition;
    }
}

つかいかた

rectTransform.GetWorldBounds() のように、拡張メソッドとして使えるようになっています。

GetWorldBounds

Boundsとして、ワールド空間でのRectTransformの位置やサイズ、各点の位置などが取得できます。

GetRelativePosition / SetRelativePosition

右側/左側/上側/下側が、親の端からどのくらい離れているかを相対位置で取得/設定できます。 親のオブジェクトをはみ出さないように位置を制限する、といった挙動の実装に便利です。

微妙なところ

パフォーマンス

メソッドが呼ばれるたびにGetComponent<RectTransform>()を2回も呼んでいるので、それなりにパフォーマンスが悪いです。 適宜、必要に応じてキャッシュする仕組みなどを作ると良いと思います。

親がRectTransformじゃない場合の挙動は?

わからん 😱

親にスケールがかかっていた場合の挙動は?

わからん😇😇😇 なんかもっといいアプローチがあったら教えてください

SurfaceShaderで主線を描画する

概要

主線を表示し、エッジを強調するシェーダーをSurface Shaderで作りました。

f:id:notargs:20171012191320g:plain

主線に影は乗っていませんが、ライトの色を変更すると主線の色も変わるのがキモです。

f:id:notargs:20171012191354g:plain

仕組み

1パス目

普通にモデルを描画しています

f:id:notargs:20171012191935p:plain

2パス目

頂点シェーダーで少しだけ法線方向に拡大したものを描画します。 自前のライティング関数を指定することで、影を描画しないように変更しています。

f:id:notargs:20171012191932p:plain

コード

/*
StandardEdge

Copyright(c) 2017 Yutaka Sato

This software is released under the MIT License.
http://opensource.org/licenses/mit-license.php
*/

Shader "Custom/StandardEdge" 
{
    Properties 
    {
        _Color("Color", Color) = (1,1,1,1)
        _EdgeColor("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
        _EdgeWidth("EdgeWidth", Range(0, 0.1)) = 0.02
    }
    SubShader 
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGINCLUDE
       #include "UnityPBSLighting.cginc"
        
        half _Glossiness;
        half _Metallic;
        float4 _Color;
        float3 _EdgeColor;
        float _EdgeWidth;

        sampler2D _MainTex;

        struct Input 
        {
            float2 uv_MainTex;
        };

        void surf(Input IN, inout SurfaceOutputStandard o)
        {
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }

        void edgeVert(inout appdata_full v)
        {
            v.vertex.xyz += v.normal * _EdgeWidth;
        }

        void edgeSurf(Input IN, inout SurfaceOutputStandard o)
        {
            o.Albedo = _EdgeColor;
        }

        half4 LightingEdge(SurfaceOutputStandard s, half3 lightDir, half atten)
        {
            half4 c;
            c.rgb = s.Albedo * _LightColor0.rgb;
            c.a = s.Alpha;
            return c;
        }
        ENDCG

        Cull Back
        CGPROGRAM
       #pragma surface surf Standard fullforwardshadows
       #pragma target 3.0
        ENDCG

        Cull Front
        CGPROGRAM
       #pragma surface edgeSurf Edge fullforwardshadows vertex:edgeVert
       #pragma target 3.0

        ENDCG
    }
    FallBack "Diffuse"
}

【Unity】"Error building Player because scripts had compiler errors"とだけ出た時のエラー箇所の探し方

Editorビルドのみが失敗しているときなど、極稀にError building Player because scripts had compiler errorsとだけログに出力されることがあります。

それしかエラーが出ないため該当箇所の特定が難しかったのですが、対処方法がわかったためメモっておきます。

続きを読む