3D空間での透明度のある画像描画とZソートなど

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
Teno

3D空間での透明度のある画像描画とZソートなど

#1

投稿記事 by Teno » 11年前

現在、DXライブラリを使用して3DのSTGを作っています。
DrawBillboard3Dを使用して弾の描画を行っているのですが、透明度がある場合や加算ブレンドを使うときは、
Zバッファと別にZソートを用いて奥から順に描画しなければならないと聞きました。
そこで以下のようなプログラムを作ってみました。

コード:

#include "DxLib.h"
//最大数
const int MAX_NUM = 10;
//弾の情報
struct SHOT{
	VECTOR pos;	//座標
	float range;		//カメラからの距離
}shot[MAX_NUM];

//ベクトル間の距離
float RangeV(VECTOR v1,VECTOR v2){
	return (v1.x - v2.x) * (v1.x - v2.x) + (v1.y - v2.y) * (v1.y - v2.y) + (v1.z - v2.z) * (v1.z - v2.z);
}

//カメラから遠い順にソートする
int comp(const void * a,const void * b){
	SHOT * c = (SHOT *)a;
	SHOT * d = (SHOT *)b;
	//qsortはint型しか扱えない?
	return (unsigned int)(d->range * 10) - (unsigned int)(c->range * 10);
}
/*__________________________________________________*/
int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int){
	ChangeWindowMode(true);
	DxLib_Init();
	SetDrawScreen(DX_SCREEN_BACK);
	SetCameraNearFar(100,10000);
	SetUseZBuffer3D(true);
	SetWriteZBuffer3D(true);
	SetDrawMode(DX_DRAWMODE_BILINEAR);
	//メインループ
	while(!ProcessMessage() && !ScreenFlip() && !ClearDrawScreen()){

	
		//カメラの座標
		const VECTOR cameraPos = VGet(0,100,-1000);
		SetCameraPositionAndTarget_UpVecY(cameraPos,VGet(0,0,0));


		//位置と距離の設定
		for(int i = 0;i < MAX_NUM;i ++){
			//位置をランダムに設定
			shot[i].pos = VGet(GetRand(500)-150.f,GetRand(500)-150.f,GetRand(500)-150.f);
			//距離を取得する
			shot[i].range = RangeV(cameraPos,shot[i].pos);
		}
		//Zソート
		qsort(shot,MAX_NUM,sizeof(SHOT),comp);

		//描画
		const int IMG = LoadGraph("../image/shot.png");
		for(int i = 0;i < MAX_NUM;i ++) DrawBillboard3D(shot[i].pos,0.5,0.5,500,0,IMG,true);


	}
	DxLib_End();
	return 0;
}
使用した画像はこちらです。画像
実行した結果、それなりには改善されてはいるのですが、画像のように少なからず背景が写ってしまいます。画像
これを改善する方法があれば教えていただけないでしょうか。
他にも、実際にゲーム化するときは1万以上の弾を同時に処理することになるので、
その時に出来るだけ軽くする方法などあれば教えて欲しいです。

アバター
みけCAT
記事: 6734
登録日時: 15年前
住所: 千葉県
連絡を取る:

Re: 3D空間での透明度のある画像描画とZソートなど

#2

投稿記事 by みけCAT » 11年前

とりあえずこれでしょうか?
乗算済みアルファのすすめ
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

Teno

Re: 3D空間での透明度のある画像描画とZソートなど

#3

投稿記事 by Teno » 11年前

乗算済みアルファについて知らなかったので勉強になりました。
しかし、実際に試してみたのですが、あまり改善されていないようで...
なんでかなぁと思いながら試行錯誤した結果、ソート自体に問題があるのかな、と思います(座標と距離を自分で設定して試した場合はこの現象が起こらなかったので)
他に方法がないか、どこか間違っていないかを模索してみますが、何か情報があれば教えて欲しいです。
それと、この問題が解決していないのに追加で質問するのもおかしな話ですが、球体以外の弾(楕円など)を描画したい時はモデルデータを使うのが一般なのかについても教えていただきたいです。

アバター
みけCAT
記事: 6734
登録日時: 15年前
住所: 千葉県
連絡を取る:

Re: 3D空間での透明度のある画像描画とZソートなど

#4

投稿記事 by みけCAT » 11年前

Teno さんが書きました://qsortはint型しか扱えない?
これは間違いです。任意の型を扱うために比較関数の引数の型がconst void*になっています。
例えば、今回の場合は比較関数をこのようにするといいかもしれません。

コード:

int comp(const void* a,const void* b) {
	float c = ((const SHOT*)a)->range;
	float d = ((const SHOT*)b)->range;
	if(d>c)return 1;
	if(d<c)return -1;
	return 0;
}
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
みけCAT
記事: 6734
登録日時: 15年前
住所: 千葉県
連絡を取る:

Re: 3D空間での透明度のある画像描画とZソートなど

#5

投稿記事 by みけCAT » 11年前

RangeV関数はベクトルv1とv2のユークリッド距離の2乗を返しているようですね。
類似度と距離 - CatTail Wiki*
この値を足し算したり引き算したりせず、大きさを比較するだけならそれ自体は問題にならないと思いますが、
例えば図のような状況を考えると、赤い弾までの距離が緑の弾までの距離より長いと判定されます。
camera_kyori.png
撃墜例
camera_kyori.png (5.23 KiB) 閲覧数: 4091 回
従って、今の方法では不都合が生じると思います。

カメラの位置から弾のposの距離に、
カメラの正面が向いている方向のベクトルとカメラから弾のposに向かう方向のベクトルのなす角の余弦(cosine)を掛けた値を用いて
順番を決めれば、うまくいくかもしれません。
camera_kyori2.png
提案手法
camera_kyori2.png (7.91 KiB) 閲覧数: 4091 回
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

Teno

Re: 3D空間での透明度のある画像描画とZソートなど

#6

投稿記事 by Teno » 11年前

とても分かりやすい解説と図まで用意していただいてありがとうございます!
qsortは戻り値がint型だから...と勘違いしていました。const void*についても勉強になりました。
解説と図をみて、なるほど、それが原因だったのか...と思いながら自分なりに考えて以下のプログラムを組んでみました。

コード:

#include "DxLib.h"
#include "math.h"
const int MAX_NUM = 100;
struct SHOT{
	VECTOR pos;
	float range;
	float cosine;
}shot[MAX_NUM];
//ベクトル間の距離(平方根)
float RangeV(VECTOR v1,VECTOR v2){
	return sqrt((v1.x - v2.x) * (v1.x - v2.x) + (v1.y - v2.y) * (v1.y - v2.y) + (v1.z - v2.z) * (v1.z - v2.z));
}
//カメラから遠い順にソートする(余弦も考慮)
int comp(const void* a,const void* b) {
	SHOT * c = (SHOT *)a;
    SHOT * d = (SHOT *)b;
	float e = c->range * c->cosine;
	float f = d->range * d->cosine;
	if(f > e) return 1;
	if(f < e) return -1;
	return 0;
}
int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int){
	ChangeWindowMode(true);
	DxLib_Init();
	SetDrawScreen(DX_SCREEN_BACK);
	SetCameraNearFar(100,10000);
	SetUseZBuffer3D(true);
	SetWriteZBuffer3D(true);
	SetDrawMode(DX_DRAWMODE_BILINEAR);
	int IMG = LoadGraph("../image/shot.png");
	//メインループ
	while(!ProcessMessage() && !ScreenFlip() && !ClearDrawScreen()){
		DrawBox(0,0,640,480,GetColor(255,0,0),true);
		//カメラの座標、目線の座標
		const VECTOR cameraPos = VGet(0,0,-1000);
		const VECTOR targetPos = VGet(0,0,0);
		SetCameraPositionAndTarget_UpVecY(cameraPos,targetPos);
		//位置、距離、余弦の設定
		for(int i = 0;i < MAX_NUM;i ++){
			shot[i].pos = VGet(GetRand(500)-250.f,GetRand(500)-250.f,GetRand(500)-250.f);
			shot[i].range = RangeV(cameraPos,shot[i].pos);
			//カメラの向き
			VECTOR cameraAngle;
			float angle = atan2(cameraPos.x - targetPos.x,cameraPos.z - targetPos.z);
			cameraAngle.x = sin(angle);
			cameraAngle.z = -cos(angle);
			angle = atan2(cameraPos.z - targetPos.z,cameraPos.y - targetPos.y);
			cameraAngle.y = cos(angle);
			//カメラから弾への向き
			VECTOR shotAngle;
			angle = atan2(cameraPos.x - shot[i].pos.x,cameraPos.z - shot[i].pos.z);
			shotAngle.x = sin(angle);
			shotAngle.z = -cos(angle);
			angle = atan2(cameraPos.z - shot[i].pos.z,cameraPos.y - shot[i].pos.y);
			shotAngle.y = cos(angle);
			//長さを求める(平方根)
			float cameraRange = VSize(cameraAngle);
			float shotRange = VSize(shotAngle);
			//余弦を求める
			shot[i].cosine = VDot(cameraAngle,shotAngle) / (cameraRange * shotRange);
		}
		//Zソート
		qsort(shot,MAX_NUM,sizeof(SHOT),comp);
		//描画
		for(int i = 0;i < MAX_NUM;i ++) DrawBillboard3D(shot[i].pos,0.5,0.5,500,0,IMG,true);
	}
	DxLib_End();
	return 0;
}
その結果、ほぼ完璧な精度でソートできていることを確認しました。
最後に、次の2点について確認させていただきたいです。
  • このプログラム自体に間違いがないか
    (希にソートできていない場合がある、当方が三角関数を習っていない年齢でatan2あたりの計算があっているか不安、などの理由から)
  • sqrt(RangeV関数とVSize関数で使用)を使わないで計算できないか(どうしても重くなってしまうので)

アバター
みけCAT
記事: 6734
登録日時: 15年前
住所: 千葉県
連絡を取る:

Re: 3D空間での透明度のある画像描画とZソートなど

#7

投稿記事 by みけCAT » 11年前

カメラの向きとカメラ→弾の向きの余弦は、atan2、sin、cosを使用しなくても、以下の式で計算できるはずです。
(擬似コード:実装するときは成分ごとの計算か、ライブラリの関数を使用してください)

コード:

vector cameraPos; // カメラの位置(代入済み)
vector targetPos; // カメラが見ている位置(代入済み)
vector shotPos; // 弾の位置(代入済み、shot[i].posに相当する)

vector c2t=targetPos-cameraPos; // カメラの向きのベクトル
vector c2s=shotPos-cameraPos; // カメラ→弾の向きのベクトル
// 余弦を内積の公式から求める
// dot(a,b) : aとbの内積
// len(a) : aの長さ
float cosine=dot(c2t,c2s)/(len(c2t)*len(c2s));

// 求める余弦の値
return cosine;
質問の内容については、これから検討します。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

Teno

Re: 3D空間での透明度のある画像描画とZソートなど

#8

投稿記事 by Teno » 11年前

なるほど、遠回りな計算をしていたようです...
教えていただいた方法で組みなおした結果、更に精度が上がったように思えます。
平方根を使わない計算は自分の知識では難しかったので、こちらのサイト様に載っていた高速sqrtを試してみました。
http://www.riken.jp/brict/Ijiri/study/fastsqrt.html
これを用いた場合、計算だけで5fps程度軽くなったので、平方根処理は必ず必要という場合はこれで妥協しようと思います。

閉鎖

“C言語何でも質問掲示板” へ戻る