20190318のC#に関する記事は3件です。

RenderTexture(Texture2D)を画像保存すると暗い時

概要

D1TflLAVAAACF5T.jpg
© Unity Technologies Japan/UCL
下記のようなスクリプトで画像を保存すると全体的に暗い画像が保存される場合があります。この場合の原因と対策を書きます。

RenderTexture CamTex;

Texture2D tex = new Texture2D(CamTex.width, CamTex.height, TextureFormat.RGB24, false);
RenderTexture.active = CamTex;
tex.ReadPixels(new Rect(0, 0, CamTex.width, CamTex.height), 0, 0);
tex.Apply();

byte[] bytes = tex.EncodeToPNG();
File.WriteAllBytes("RenderTexture.png", bytes);

原因

Edit > Project Settings > PlayerのPlayer SettingsからOther Settings > RenderingよりColor SpaceをLinearに設定している場合に暗くなります。UTS2.0などのシェーダーに関するスライドを読むと、最初に設定するように書かれていることが多い内容です。仕組みが分かると「まぁ、そうか」という感じですが、「原因」として書くようなことではなく、完全に仕様です。

対策1

RenderTexture CamTex;

Texture2D tex = new Texture2D(CamTex.width, CamTex.height, TextureFormat.RGB24, false);
RenderTexture.active = CamTex;
tex.ReadPixels(new Rect(0, 0, CamTex.width, CamTex.height), 0, 0);
/////////////////////////////////////////////////////////////////////////////////////////追加ここから
if (PlayerSettings.colorSpace == ColorSpace.Linear)
{
    // ガンマ補正
    var color = tex.GetPixels();
    for (int i = 0; i < color.Length; i++)
    {
        color[i].r = Mathf.Pow(color[i].r, 1 / 2.2);
        color[i].g = Mathf.Pow(color[i].g, 1 / 2.2);
        color[i].b = Mathf.Pow(color[i].b, 1 / 2.2);
    }
    tex.SetPixels(color);
}
/////////////////////////////////////////////////////////////////////////////////////////追加ここまで
tex.Apply();

byte[] bytes = tex.EncodeToPNG();
File.WriteAllBytes("RenderTexture.png", bytes);

方法は色々ありそうですし、上記があまり良い方法とも思えませんが、とにかくガンマ補正というのをすると解決します。この対策では厳密にはColor SpaceがGammaの時と完全に同じRGB値にならないかもしれませんが、誤差だと思うので気にしないことにします。

※コメントにてご教授頂きましたので、以下加筆します。

対策2

対策1のRGB各色計算を以下のように書き換えます。恐らくですが、こちらの方が正確な値になると思われます。

color[i].r = Mathf.LinearToGammaSpace(color[i].r);
color[i].g = Mathf.LinearToGammaSpace(color[i].g);
color[i].b = Mathf.LinearToGammaSpace(color[i].b);

対策3

これが最も簡単でスマートな方法だと思われますが、以下のように作成したRenderTextureのsRGB設定をチェックありにします。すると、対策1でスクリプトを追加した部分は不要になり、この設定だけで解決します。
無題.png

結果

どの対策も目視では、ほぼ同様の結果になります。
D1UMXVrVsAA-Gpy.jpg
© Unity Technologies Japan/UCL

仕組み

恐らく散々語られてる分野だと思われますので、私が下手な事言うより、検索した方が良いと思います。キーワードは「ガンマ補正」「トーンカーブ」踏み込むと「sRGB」「比視感度」などだと思います。

参考

リニアのワークフローとガンマのワークフロー
https://docs.unity3d.com/ja/current/Manual/LinearRendering-LinearOrGammaWorkflow.html
ガンマ補正
http://w3.kcua.ac.jp/~fujiwara/infosci/gamma.html
明るさの調整(γ補正)
http://www.mis.med.akita-u.ac.jp/~kata/image/colorgamma.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#】2つの画像の差分を得る(Bitmap)

はじめに

前回の「画像生成の基本」に続いて、今回はBitmapオブジェクトを使って「2つの画像の差分」を抽出するコードを作成しました。

以下のコードではBitmapオブジェクトを直接使っているので、画像サイズが大きくなればなるほど処理時間がかかってしまいますが、以下のコードではBitmapオブジェクトを操作する回数を可能な限り少なくしています。

コード

  • 以下のコードでは「画像が同じ部分は元の色で描画し、異なる部分だけ赤で描画する」という処理になっています。
    • 「画像が同じ部分は元の色で描画する」という部分だけ除去すれば、差分となる部分だけを描画するコードになります。
  • 例えばループを以下のように書くと、ループを回す度にBitmapオブジェクトを操作してしまうので、処理が若干遅くなると思います。そこで、ループの外で画像の高さと幅をローカル変数に入れて、Bitmapオブジェクトを操作する回数を減らしています。
    • for (int i = 0; i < bmp1.Width; i++) { ... }
    • for (int j = 0; j < bmp1.Height; j++) { ... }
ImageComparator.cs
/// <summary>
/// 「画像比較機」クラス。
/// </summary>
public class ImageComparator
{
    /// <summary>
    /// 1ピクセルずつ画像を比較して、差分の画像を返す。
    /// </summary>
    /// <param name="bmp1Path">比較する画像1のファイルパス。</param>
    /// <param name="bmp2Path">比較する画像2のファイルパス。</param>
    /// <param name="path">差分画像の保存先となるファイルパス。</param>
    /// <returns>2つの画像が同じであればtrue、そうでなければfalseを返す。</returns>
    public static bool Compare(string bmp1Path, string bmp2Path, string path=@".\diff_image.png")
    {
        bool isSame = true;

        // 画像を比較する際に「大きい方の画像」のサイズに合わせて比較する。
        Bitmap bmp1 = new Bitmap(bmp1Path);
        Bitmap bmp2 = new Bitmap(bmp2Path);
        int width = Math.Max(bmp1.Width, bmp2.Width);
        int height = Math.Max(bmp1.Height, bmp2.Height);

        Bitmap diffBmp = new Bitmap(width, height);         // 返却する差分の画像。
        Color diffColor = Color.Red;                        // 画像の差分に付ける色。

        // 全ピクセルを総当りで比較し、違う部分があればfalseを返す。
        for (int i = 0; i < width; i++)
        {
            for (int j = 0; j < height; j++)
            {
                try
                {
                    Color color1 = bmp1.GetPixel(i, j);
                    if (color1 == bmp2.GetPixel(i, j))
                    {
                        diffBmp.SetPixel(i, j, color1);
                    }
                    else
                    {
                        diffBmp.SetPixel(i, j, diffColor);
                        isSame = false;
                    }
                }
                catch
                {
                    // 画像のサイズが違う時は、ピクセルを取得できずにエラーとなるが、ここでは「差分」として扱う。
                    diffBmp.SetPixel(i, j, diffColor);
                    isSame = false;
                }
            }
        }
        diffBmp.Save(path, ImageFormat.Png);
        return isSame;
    }
}

比較する画像

  • 以下の2つの画像を比較します。
    • Inkscapeで自作した雑な画像ですみません。
cat1.png cat2.png
cat1.png cat2.png

処理結果

  • 2つの画像の異なる部分が赤くなっているのが分かります。
    • 何だか「怪我をした血だらけの猫」みたいになってしまいました...

diff_image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityEditor拡張(入門)自分が最初にしたこと

■最初にやったこと

とりあえず参考になりそうなサイトを読みまくって自分なりにまとめたテキストを作成した。

※一部抜粋

〇標準で使えるエディタ拡張機能
  1.インスペクターの見た目を変える
     ・Range
       int,float,long,doubleなどの数値をスライダーで変更できるようにする機能
      例)
      [Range(1, 10)]
      public int num1;

~以下略~

のような感じで自分なりに解釈した単語帳みたいなものを作成すると覚えも早いと思います。

■実際に作ってみる

まとめたら、まとめたものを中心に一度unityを起動して作ってみる。

〇まずはウィンドウを出してみた

using UnityEngine;
using UnityEditor;//エディタ拡張するときは必ず必要

//EditorWindowクラスを継承
public class StageEditorWindow : EditorWindow 
{
    private const float WINDOWSIZE_W = 500.0f;          //ウィンドウサイズ横幅
    private const float WINDOWSIZE_H = 200.0f;          //ウィンドウサイズ縦幅

    /// <summary>
    /// ウィンドウ表示
    /// </summary>
    [MenuItem("Window/StageEditor")]
    static void Open()
    {       
        var window = GetWindow<StageEditorWindow>();
        //ウィンドウサイズ設定(minとmaxを=しているのはウィンドウサイズを固定するため)
        window.maxSize = window.minSize = new Vector2(WINDOWSIZE_W,WINDOWSIZE_H);
    }
}

エディタ拡張をする際は、継承もとをMonoBehaviourではなくEditorWindowにする。

0318Qiita.png
図1)

[MenuItem("Window/StageEditor")]はメソッドの上に記述することでUnityのエディタ上からこのメソッドを呼び出すことができる。

〇初めて作ったエディタ拡張
いま作っているゲーム用のステージエディタです。(イメージしやすいように…)
0318Qiita.gif
図2)
ステージの名前(保存ファイル名)とステージの横幅を指定して、ステージ上に置くオブジェクトと床のテクスチャをクリックで配置するステージエディタです。

■終わり

今回は、初めてなのでウィンドウ出すだけでしたが次回は実際にボタンとかのレイアウトについて書こうと思います。

お疲れさまでしたm(__)m

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む