0
0

Blender→Unity間で8bitテクスチャ(PNG)で数値の受け渡しを行う

Posted at

はじめに

前回解説した記事では、Unityの色空間設定がLinerでなければ使えないとのことでした。
Blender→Unity間で32bitテクスチャ(EXR)で数値の受け渡しを行う

しかし、そうなるともっと汎用性が欲しくなります。
というわけでインポート時に色空間が設定できるPNGテクスチャで数値の受け渡しを行いたいと思います。
(基礎的なビット演算の内容となります)

Blender

Blenderのバージョンは4.1.1 Stableを使っていきます。

テクスチャ作成

前回までと内容が被っているため、クラス化まで飛ばします。

import bpy
import numpy as np

class TextureClass:
    def __init__(self, texture_name, width, height):
        self.image = bpy.data.images.get(texture_name)
        if not self.image:
            self.image = bpy.data.images.new(texture_name, width=width, height=height, alpha=True)
        elif self.image.size[0] != width or self.image.size[1] != height:
            self.image.scale(width, height)
        
        self.point = np.array(self.image.pixels[:])
        self.point.resize(height, width * 4)
        self.point[:] = 0

        self.point_R = self.point[::, 0::4]
        self.point_G = self.point[::, 1::4]
        self.point_B = self.point[::, 2::4]
        self.point_A = self.point[::, 3::4]
    
    def SetPixel(self, py, px, r, g, b, a):
        self.point_R[py][px] = r
        self.point_G[py][px] = g
        self.point_B[py][px] = b
        self.point_A[py][px] = a

    def Export(self):
        self.image.pixels = self.point.flatten()


image = TextureClass('CustomTexture', 1, 1)
image.SetPixel(0, 0, 0.5, 0, 0, 0.5)
image.Export()

変数を分割

まず、主な整数型のintや浮動小数型のfloatは32bitです。

そして8bitテクスチャにRGBAがあるので、各色に2進数で分割しながら情報を書きこめば、8bit×4=32bitとなって1ピクセルにデータを納めることが出来ますね!

...

といきたいところですが、1つ確認事項があります。

それはAが0の時です。

image.png

目視できる色はありませんが、そのまま値を渡せるのでしょうか?

image.png

実は大丈夫なようです。
安心しました。

それでは気にすることなく、各種関数を作成していきましょう。

import bpy
import struct
import numpy as np

class TextureClass:
    def __init__(self, texture_name, width, height):
        self.image = bpy.data.images.get(texture_name)
        if not self.image:
            self.image = bpy.data.images.new(texture_name, width=width, height=height, alpha=True)
        elif self.image.size[0] != width or self.image.size[1] != height:
            self.image.scale(width, height)
        
        self.point = np.array(self.image.pixels[:])
        self.point.resize(height, width * 4)
        self.point[:] = 0

        self.point_R = self.point[::, 0::4]
        self.point_G = self.point[::, 1::4]
        self.point_B = self.point[::, 2::4]
        self.point_A = self.point[::, 3::4]
    
    def SetPixel(self, py, px, r, g, b, a):
        self.point_R[py][px] = r
        self.point_G[py][px] = g
        self.point_B[py][px] = b
        self.point_A[py][px] = a

    def Export(self):
        self.image.pixels = self.point.flatten()


+ def SplitFloat(f):
+     return [a / 255 for a in struct.unpack('BBBB', struct.pack('f', f))]
+ 
+ def SplitInt(i):
+     return [a / 255 for a in struct.unpack('BBBB', struct.pack('I', i))]
+ 
+ def SplitInt16(i1, i2):
+     return [a / 255 for a in struct.unpack('BBBB', struct.pack('HH', i1, i2))]


image = TextureClass('CustomTexture', 2, 2)
image.SetPixel(0, 0, *SplitFloat(0.12345))
image.SetPixel(0, 1, *SplitInt(123456))
image.SetPixel(1, 0, *SplitInt16(1234, 1234))
image.Export()

内容としては、32bitを8bitに分割し、0~255を0~1の範囲にする関数です。

def SplitFloat(f):
    return [a / 255 for a in struct.unpack('BBBB', struct.pack('f', f))]

def SplitInt(i):
    return [a / 255 for a in struct.unpack('BBBB', struct.pack('I', i))]

def SplitInt16(i1, i2):
    return [a / 255 for a in struct.unpack('BBBB', struct.pack('HH', i1, i2))]

Unity

image.png

インポートするテクスチャの設定は以下の通りです。
sRGB:False
Format:RGBA 32bit

Shader

RGBA(fixed4)を元の変数型に戻す関数を作成します。
数値表示のシェーダーは、前回と同じくブタジエン氏の物を使用します。

fixed4 frag(v2f i) : SV_Target
{
    int v1, v2;
    CombineInt16(tex2D(_Texture, float2(0, 0.5)), v1, v2);

    fixed4 col = 
          numbercol(float2(i.uv), CombineFloat(tex2D(_Texture, float2(0, 0))), 5, 1, 3, 4)
        + numbercol(float2(i.uv), CombineInt(tex2D(_Texture, float2(0.5, 0))), 5, 2, 7, 1)
        + numbercol(float2(i.uv), v1, 5, 3, 7, 1)
        + numbercol(float2(i.uv), v2, 5, 4, 7, 1)
        ;

    return col;
}

関数の内容としては、0~1を0~255に戻して、順番通り並べています。

float CombineFloat(fixed4 color){
    return asfloat(uint(color.r * 255.0) | (uint(color.g * 255.0) << 8) | (uint(color.b * 255.0) << 16) | (uint(color.a * 255.0) << 24));
}

float CombineInt(fixed4 color){
    return asint(uint(color.r * 255.0) | (uint(color.g * 255.0) << 8) | (uint(color.b * 255.0) << 16) | (uint(color.a * 255.0) << 24));
}

void CombineInt16(fixed4 color, out int v1, out int v2){
    v1 = asint(uint(color.r * 255.0) | (uint(color.g * 255.0) << 8));
    v2 = asint(uint(color.b * 255.0) | (uint(color.a * 255.0) << 8));
}

結果

image.png

ちゃんと復元できていますね。

懸念点

この方法の欠点としては、テクスチャの読みこみ回数が増えることです。

exr形式のテクスチャを1回参照した時のデータ量は32×4=128なのですが、pngでは8×4=32と読みだせるデータ量が少ないです。

つまりexrと同等のことをしようとすると4倍テクスチャを参照しなければなりません。

自分が作成したVATではexrで6回参照していたため、pngに変更するとなると24回参照することになります。

そのため汎用性を持たせるために、GPU負荷を増やすことになりますね。

0
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
0
0