ピクセルシェーダ3.0でYUV420 -> RGBの変換(完成版)

A8R8G8B8のテクスチャにムリクリYUV420Pを書込んでそれをRGBに変換するシェーダです。
間違いもあったので、とりあえず現状報告も兼ねて。
まず、420は解除できるようになりました。U、Vプレーンがおかしいのも修正できました。
ですが、YUV->RGBの流れで色がちゃんと出ないぞ、版でございます。
よくあるYUV->RGB変換について書いてあるサイトを参考にしたんですが、なんだか黄緑っぽいですね…。
UとVの取り込むプレーンが逆だったみたいなのと、最後のプレーンの切出し位置が違ってました。RGBの1/2だと思ってたんですが流し込む先はARGBなのでさらに小さくなるんですね。ffmpegのdata[0]=Y、data[1]=U、data[2]=Vのようですね。とりあえず、8500GTとか、HD2400なんかでは同じ結果になりました。ちょっと気になるのは、H.264やWMVは綺麗に変換されるのですが、mpeg1/2だと、Uが縦8px毎に入替わってしまうような気がしますが、もしかすると2のべき乗サイズじゃないテクスチャだから誤差がでてるのかもなぁ。誤差については、Vのプレーンを0.5からはじめることで解消できました。なお、_offset.xyは紛らわしいですが、テクスチャのサイズ(ピクセル)を渡しています。

float4 convertPS(const float2 tex: TEXCOORD0): COLOR
{
	const float4x4 YUVMatrix = {
					1,  0,        1.402,   0,
					1, -0.34414, -0.71414, 0,
					1,  1.772,    0,       0,
					1,  1,        1,       1};
	const int dx = tex.x * _offset.x;
	const int dy = tex.y * _offset.y;
	float4 y = tex2D(TextureSampler0, float2(tex.x / 4 + dy     % 4 * 0.250f,           tex.y / 4));
	float4 u = tex2D(TextureSampler0, float2(tex.x / 8 + dy / 2 % 8 * 0.125f, 0.2500f + tex.y / 16)) - 0.5f;
	float4 v = tex2D(TextureSampler0, float2(tex.x / 8 + dy / 2 % 8 * 0.125f, 0.5000f + tex.y / 16)) - 0.5f;
	float4 yuv = 0;
	int sel = dx % 8;
	if        (sel == 0) {
		yuv = float4(y.b, u.b, v.b, 1);
	} else if (sel == 1) {
		yuv = float4(y.g, u.b, v.b, 1);
	} else if (sel == 2) {
		yuv = float4(y.r, u.g, v.g, 1);
	} else if (sel == 3) {
		yuv = float4(y.a, u.g, v.g, 1);
	} else if (sel == 4) {
		yuv = float4(y.b, u.r, v.r, 1);
	} else if (sel == 5) {
		yuv = float4(y.g, u.r, v.r, 1);
	} else if (sel == 6) {
		yuv = float4(y.r, u.a, v.a, 1);
	} else if (sel == 7) {
		yuv = float4(y.a, u.a, v.a, 1);
	}
 
	// YUV -> RGBして 16~235 -> 0~255に伸張
	return (mul(YUVMatrix, yuv) - 0.0627f) * 1.164f;
}

コメント

タイトルとURLをコピーしました