RenderManで扱えるtiffファイルを作る

# RenderManで使用できるテクスチャファイルの種類

RenderMan Pro Server 19.0で確認してます。
コマンドラインでribをガシガシ書く方、RenderManのribファイルをツールから出力したい方は役に立つかもしれません。

RenderManでは、tiffとtexという拡張子のファイルをテクスチャとして扱うことができます。envというのもありますがこれはまだ未調査。
コマンドラインで
`txmake xxx.png xxx.tex`
のようにすると、xxx.pngファイルからxxx.texというファイルを生成してくれます。
tiffの場合は、
`txmake xxx.png xxx.tiff`
とすると同様にtiff形式のファイルを出力でき、これをribで参照するとテクスチャになります。

このtiffファイル、photoShopなどで見ても問題なく見ることができるのですが、photoShopから生成したtiff画像ファイルをRenderManのテクスチャにすると、
`T02002 {ERROR}   "xxx.tiff" is not a texture file.`
のようにエラーメッセージが出て、テクスチャとして認識してくれません。
これの解決策を調べました。
とりあえずは、RenderManでの普通のテクスチャの作成方法を（環境マップ用のテクスチャ生成についてはまだ未調査）。

## txmakeでpatternをsingleにするとRISで速度低下

`txmake -pattern single xxx.png xxx.tiff`
のように指定した場合は、RISでレンダリングするとかなり速度低下しました。サンプリング数が多いほど足を引っ張ります。
http://renderman.pixar.com/resources/RPS_17/txmake.1.html
によるとsingleの指定は「very poor texturing performance」だそうです。
これは、mipmapを作成するかどうかのオプションのようです。

mipmapは、テクスチャサイズを1/2倍していき、最小2になるまでの複数枚を格納することで実現します。

ribファイル内でRISモードとしてテクスチャ指定する場合、Patternの指定を行います。

`Pattern "PxrTexture" "xxx" "string filename" ["xxx.tiff"] "int linearize" [1] "int invertT" [0] "int filter" [0] `
のように「"int filter" [0]」としたときは、テクスチャのピクセルは補間されません。
この場合は速度低下しませんが、「"int filter" [1]」（指定がない場合はこれになる）のようにした場合は、mipmap情報がtiffファイル内に存在しないとRISで速度低下してました。

# RenderManが理解できるtiff

tiffファイル生成は、libtiff( http://www.libtiff.org/ )を使うようにします。
TIFFのタグとしてPIXAR向けの情報を埋め込むというのが答えになりますが、実はそれだけではダメで、以下のルールを守る必要があるようでした。
> ググってみたのですが、どうも10年くらい前の情報ではうまくいかなかったです。
> もしかしたら、RenderManのバージョンで仕様が変わっていってるのかもしれません。

## ピクセルの順番

画像フォーマットでは、よくRGBRGBRGB、、、 のように1ピクセルのRGB(RGBA)情報を順番に格納する場合が多いですが、RenderManで理解できるtiff画像はRRRRGGGGBBBBのように、Red/Green/Blueをそれぞれ別個で格納していく必要があるようです。
![tiff_img_01.png](https://qiita-image-store.s3.amazonaws.com/0/62860/597a25d1-9865-59bc-f6f0-d89c6c27e7d4.png)
Tiffのタグとしては「TIFFTAG_PLANARCONFIG」に「PLANARCONFIG_SEPARATE」を指定するようにします。

## 64x64 pixelごとにブロック格納

画像をピクセルごとに格納していく場合は、左上から右下に1ラインずつ格納していく形が多いです。
RenderManで扱えるTiffの場合は64x64 pixelのブロックごとに分けて格納していくようにします。
![tiff_img_02.png](https://qiita-image-store.s3.amazonaws.com/0/62860/cae9d8fb-f13c-9e17-a40b-32caf8798025.png)
64pixelに収まらない右端や下は、64x64 pixelがあるものとしてダミーデータを入れておきます。

Tiffのタグとしては「TIFFTAG_TILEWIDTH」と「TIFFTAG_TILELENGTH」に64を指定するようにします。

## テクスチャサイズは2の累乗にする

テクスチャサイズは2の累乗（16/32/64/128/256/512、、、）である必要があるようでした。
テクスチャサイズが適切でないと、prman実行時に以下のエラーが出ました。
> T03007 {ERROR}   Bad texture data in "TIFFReadDirectory: xxxx.tiff: Can not read TIFF directory count". 

## mipmap情報は必ず格納する (2015/05/07 追記)

tiffフォーマットは、1ファイル内に複数の画像情報を内包することができます。
RenderMan(RIS)では、テクスチャを参照する際にfilterを指定することでズームしてもテクスチャがスムーズになるように補間することができます。
ここで、補間なし（Pattern "PxrTexture"の指定でfilter を0にする）の場合は問題ないのですが、それ以外だとかなりレンダリング速度が低下します。

libtiffの場合は、TIFFSetFieldやTIFFWriteTile関数で画像情報を蓄えた後に「TIFFWriteDirectory」を呼ぶことで、複数画像を保持する指定となります。

## その他、決まりごと

一般的なtiffのタグのほかに、以下を格納します。

* TIFFTAG_PREDICTORにPREDICTOR_HORIZONTALを指定
* TIFFTAG_PIXAR_TEXTUREFORMATに"Plain Texture"を指定
* TIFFTAG_PIXAR_WRAPMODESに<del>"clamp,clamp"</del>"periodic,periodic"を指定
* TIFFTAG_PIXAR_FOVCOTに1.0を指定

### TIFFTAG_PIXAR_WRAPMODESについて

TIFFTAG_PIXAR_WRAPMODESはU,V方向のラップ情報でblack/clamp/periodicのいずれかを指定可能。

板ポリゴンにUVを(0, 0)、(0, 1)、(1, 0)、(1, 1)のように指定した場合。
TIFFTAG_PIXAR_WRAPMODESに"black,black"を指定した場合は、0～1範囲外の部分が黒になります。結果として、真っ白なテクスチャを与えた場合でも黒い枠で囲まれたように表現されてしまいます。
![tiff_blackblack.png](https://qiita-image-store.s3.amazonaws.com/0/62860/8b61f432-1ffc-1a32-5531-d94fd809bd83.png)

"clamp,clamp"を指定した場合は、意図した結果になります。
![tiff_clampclamp.png](https://qiita-image-store.s3.amazonaws.com/0/62860/c9047335-a910-0f02-3a30-5a72bd37141e.png)

UVが0.0 - 1.0の範囲を超える場合は、"periodic,periodic"を指定します。
汎用的に使用する場合はperiodicを使うほうがよさそうです。

# libtiffを使ったtiffの生成例

C言語ライクでは以下のようになります。

```lang:renderman_tiff_save.c
#include <vector>
#include "tiffio.h"   // tiff出力で必要.

/**
 * RenderMan向けのtiff画像出力例.
 * @param[in]  saveFileName  保存ファイル名.
 * @param[in]  width         画像の幅.
 * @param[in]  width         画像の高さ.
 * @param[in]  pRGBBuffer    色情報がRGBRGB... の順に格納されている(width * height * 3のバッファ).
 */
bool SavePRManImage (const std::string& saveFileName, const int width, const int height, unsigned char* pRGBBuffer)
{
	// tiffの書き込みとしてファイルオープン.
	TIFF* tiffImage = TIFFOpen(saveFileName.c_str(), "w");
	if (tiffImage == NULL) return false;

	const int tileWidth  = 64;
	const int tileHeight = 64;
	std::vector<unsigned char> tileBuffer;
	tileBuffer.resize(tileWidth * tileHeight);

    // width、heightは2の累乗の大きさとします.
    int width2  = width;
    int height2 = height;

    // 大きさを1/2倍して格納することで、mipmapとして保持される.
    while (width2 >= 2 && height2 >= 2) {
		std::vector<unsigned char> rgbBuffer2;
		rgbBuffer2.resize(width2 * height2 * 3);
		// pRGBBuffer ==> rgbBuffer2に、width2 x height2にリサイズして縮小したRGBを格納してください.

		TIFFSetField(tiffImage, TIFFTAG_IMAGEWIDTH, width2);					// 画像の幅.
		TIFFSetField(tiffImage, TIFFTAG_IMAGELENGTH, height2);					// 画像の高さ.
		TIFFSetField(tiffImage, TIFFTAG_BITSPERSAMPLE, 8);						// 1バイトでのビット数 (8固定).
		TIFFSetField(tiffImage, TIFFTAG_SAMPLESPERPIXEL, 3);					// 1pixelでの要素数(RGBの3つ).
		
		TIFFSetField(tiffImage, TIFFTAG_COMPRESSION, COMPRESSION_LZW);			// 圧縮方式.

		TIFFSetField(tiffImage, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);			// RGBカラーを持つ.
		TIFFSetField(tiffImage, TIFFTAG_XRESOLUTION, 1.0);
		TIFFSetField(tiffImage, TIFFTAG_YRESOLUTION, 1.0);
		TIFFSetField(tiffImage, TIFFTAG_RESOLUTIONUNIT, RESUNIT_NONE);
		TIFFSetField(tiffImage, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
		TIFFSetField(tiffImage, TIFFTAG_SOFTWARE, "libtiff");

		//--------------------------------------.
		// 以下、RenderMan向けの指定.
		// PLANARCONFIG_SEPARATEの場合は、Redを先に格納、Greenを次にまとめて、Blueを次にまとめて、という順番に格納する.
		TIFFSetField(tiffImage, TIFFTAG_PLANARCONFIG, PLANARCONFIG_SEPARATE);
		TIFFSetField(tiffImage, TIFFTAG_PREDICTOR, PREDICTOR_HORIZONTAL);

		// タイル状に、64x64 pixelごとに左上から右下に格納.
		TIFFSetField(tiffImage, TIFFTAG_TILEWIDTH, tileWidth);
		TIFFSetField(tiffImage, TIFFTAG_TILELENGTH, tileHeight);

		TIFFSetField(tiffImage, TIFFTAG_PIXAR_TEXTUREFORMAT, "Plain Texture");		// RenderMan向けのテクスチャとして出力.
		TIFFSetField(tiffImage, TIFFTAG_PIXAR_WRAPMODES, "periodic,periodic");			// テクスチャのWrap情報.
		TIFFSetField(tiffImage, TIFFTAG_PIXAR_FOVCOT, 1.0);							// Plain Textureでも必須.

		// 画像は、Red/Green/Blueの順番に、tileWidth x tileHeightのブロックごとに格納していく.

	    // 最適化していないため、適当に最適化してください.
		for (int colorLoop = 0; colorLoop < 3; colorLoop++) {
			for (int iy = 0; iy < height2; iy += tileHeight) {
				for (int ix = 0; ix < width2; ix += tileWidth) {

					// (ix, iy)の位置から、tileWidth x tileHeightのR/G/Bをコピー.
					int iPos = 0;
					if (colorLoop == 0) {	// Red.
						for (int y = 0; y < tileHeight; y++) {
							for (int x = 0; x < tileWidth; x++) {
								if (x + ix < width2 && y + iy < height2) {
									tileBuffer[iPos] = rgbBuffer2[((x + ix) + (y + iy) * width2) * 3 + 0];
								}
								iPos++;
							}
						}
					} else if (colorLoop == 1) {	// Green.
						for (int y = 0; y < tileHeight; y++) {
							for (int x = 0; x < tileWidth; x++) {
								if (x + ix < width2 && y + iy < height2) {
									tileBuffer[iPos] = rgbBuffer2[((x + ix) + (y + iy) * width2) * 3 + 1];
								}
								iPos++;
							}
						}
					} else {	// Blue.
						for (int y = 0; y < tileHeight; y++) {
							for (int x = 0; x < tileWidth; x++) {
								if (x + ix < width2 && y + iy < height2) {
									tileBuffer[iPos] = rgbBuffer2[((x + ix) + (y + iy) * width2) * 3 + 2];
								}
								iPos++;
							}
						}
					}

					TIFFWriteTile(tiffImage, &tileBuffer[0], ix, iy, 0, colorLoop);
				}
			}
		}
		TIFFWriteDirectory(tiffImage);

		width2  >>= 1;
		height2 >>= 1;
	}

	TIFFClose(tiffImage);

	return true;
}
```
# RGB float x 3のtiff画像を作成

上記説明では1ピクセルでunsigned char x 3のRGBでしたが、RGBそれぞれにfloatを使用することで、hdrのような1.0を超える輝度表現が可能になります。

LightSourceとしてPxrEnvMapLightを使用することで、2:1のサイズのパノラマ画像で背景のIBLとして機能させることができるようになります。

この場合に、tiff生成で変更を加える箇所がありますのでそれについて記載します。
unsigned char x 3のピクセル情報の場合と同じ感じで、Red/Green/Blue別にfloat値をブロックごとに格納していくことになります。

## TIFFTAG_BITSPERSAMPLEで32を指定
> TIFFSetField(tiffImage, TIFFTAG_BITSPERSAMPLE, 32);

float型のビット数を指定。

## TIFFTAG_SAMPLEFORMATでSAMPLEFORMAT_IEEEFPを指定
> TIFFSetField(tiffImage, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP);

のような指定が必要です。これで、float値の使用を明示化。

## TIFFTAG_PREDICTORでPREDICTOR_NONEを指定
> TIFFSetField(tiffImage, TIFFTAG_PREDICTOR, PREDICTOR_NONE);

## 32x32 pixelごとにブロック格納
> TIFFSetField(tiffImage, TIFFTAG_TILEWIDTH, 32);
> TIFFSetField(tiffImage, TIFFTAG_TILELENGTH, 32);

のように、ブロックごとの格納は64ではなくて32とする必要があるようでした。
64のままだと、コマンドラインでprmanでribを読み込んでレンダリングした時に
> T03007 {ERROR}   Bad texture data in "xxx.tiff: 320: Row
out of range, max 255".

というエラーが出ました。

# パノラマ画像を保存する場合

縦横比が2:1のパノラマ画像を作成し、それを背景光源として使用する場合(IBL)は、上記のfloat型のRGB格納に加えて以下の変更が必要になります。

> TIFFSetField(tiffImage, TIFFTAG_PIXAR_TEXTUREFORMAT, "LatLong Environment");
> TIFFSetField(tiffImage, TIFFTAG_PIXAR_WRAPMODES, "periodic,periodic");

このように指定してtiff画像を出力し、RIBで以下のように記述するとIBLとして機能します。
>  AttributeBegin
>    TransformBegin
>      Attribute "identifier" "string name" ["envLight"]
>      Translate 0 0 0
>      Rotate 0 0 0 1
>      Rotate 0 0 1 0
>      Rotate 90 1 0 0
>      Scale 1 1 -1
>      AreaLightSource "PxrEnvMapLight" "envLight" "string envmap" ["background_panorama.tiff"] "float intensity" [1.0]
>      Attribute "visibility" "int indirect" [0]
>      ReverseOrientation
>      Opacity [1 1 1]
>      Sides 1
>      Geometry "envsphere" "constant float radius" [-1] "constant int infinite" [1065353216] "constant float[2] resolution" [-1 -1]
>    TransformEnd
  AttributeEnd

# mipmapを格納したテクスチャが、reflectionに映り込みにくい問題の回避

tiffファイルでmipmap情報を格納した場合、最小で2x2 pixelのサイズになります。
これは拡大してもすごい粗いのは解像度からでも明らかですね。

RenderManのreflectionでテクスチャが映り込んだ場合、以下のようにテクスチャ模様が消えてしまう現象が起きる場合があります。
![prman_reflection_texture_00.jpg](https://qiita-image-store.s3.amazonaws.com/0/62860/2a489fa0-4a0b-1b75-b13d-1b9c5f6055b2.jpeg)

どうもサイズの小さなmipmapを採用しているような挙動をしてました。
回避策として、

* tiffに格納するmipmapの最小を32x32 pixelくらいにする
* RIBの「Pattern "PxrTexture"」の指定で「 "int filter" [7] 」を追加

とすると、reflectionの映り込みも反映され、速度低下もない状態になりました。
![prman_reflection_texture_01.jpg](https://qiita-image-store.s3.amazonaws.com/0/62860/32d03412-f67d-9329-f4e6-f73efd6cd238.jpeg)

映り込みを粗くしないために、mpimapの最小サイズをもう少し大きくした状態でもよいかもしれません。