卒論ひと段落

スヌーピーのマルチボックス

2月に入り、ようやく卒論がひと段落しました。三日前の予定ではもう少しか作る予定だったのですが、論文が一通り書き終わると完成した気分になってしまい、追加は諦めてしまいました。「とりあえず、卒業できればいいや」というのが今の正直な気持ちです。
とはいえ、文章の修正や図の修正などの細かい作業は残っています。なにより、担当教官にまだ見せていないため、この後に大量のダメ出しをもらう可能性もあります。気を緩めすぎずにもう少しがんばりたいと思います。
今日の写真はスヌーピーマルチボックスです。セブンイレブンの商品についているシールを集めると交換できます。今日が最終日だったので、慌てて交換してきました。

スムージング角度(2)

後でやろう後でやろうと思いつつ、早4ヶ月。「このままでは見た目が悪すぎる!」ということで、スムージング角度を反映させた法線を作るようにしました。

特に下のブロックのように角ばった図だと明らかに見た目が変わります。青い線が面法線で、赤い線が頂点法線を表しているのですが、スムージング角度を反映させる前は頂点法線が明らかにおかしな方向を向いているのがわかります。平らな面のはずなのに陰影が…

適用前 適用後

また、下の図は前回述べていた矢印のエッジです。スムージング角度適用前は直角な角も滑らかになっていますが、適用後は鋭利な角になっています。ちなみに、スムージング角度はMetasequoiaのデフォルト値である59.5度に設定しました。

適用前 適用後


スムージング角度の適用方法は意外と簡単です。
<ある面Aの頂点Pの頂点法線Nを求めるとする>

  1. 頂点法線Nには面Aの面法線をセットしておく。
  2. 次に、頂点Pを共有する面B1,B2,B3,...Bnを見つける。
  3. 面Aの面法線と面B1の面法線の成す角度がスムージング角度以下なら、頂点法線Nに面B1の面法線を加算する。
  4. 同様に、面Aの面法線と面B2の面法線について、面B3の面法線について……と繰り返す。
  5. Bnまで終わったら、頂点法線Nを正規化して修了。

これを全ての頂点に適用するだけです。
ただしここで注意すべきなのは、頂点法線は「面の数×3」個必要だということです。トライアングルストリップやインデックスバッファを使って頂点を共有している場合はデータ構造の変更を考えなくてはいけません。

プログラムコードは次のようになりました。かなり依存したコードなので、後で書き直す予定です。

//=================================================================
// メインチャンク(EDIT3DS, VER3DS, KEYF3DSの階層)のチャンクを読む
//=================================================================
bool CLoader3ds::ReadMainChunk( SLoaderTmpMesh *pMesh )
{
	S3dsChunk parent_chunk;
	// MAIN3DSチャンクヘッダの読み取り
	if ( !ReadChunk( &parent_chunk ) )
		return false;
	if ( parent_chunk.id != MAIN3DS )
		return false;

	// メインチャンク層(Level1)のチャンクを読む
	while ( IsValidPosition( &parent_chunk ) )
	{
		S3dsChunk chunk;
		// チャンク読み取り
		if ( !ReadChunk( &chunk ) )
			return false;
		// チャンクの判別
		switch ( chunk.id )
		{
			// バージョンの読み取り===================================
			case MY3DS_VER:
				if ( !ReadLong( &m_version ) )
					return false;
				break;
			// エディット部の読み取り===================================
			case MY3DS_EDIT:
				if ( !ReadEditpart( &chunk, pMesh ) )
					return false;
				break;
			// キーフレームの読み取り===================================
			case MY3DS_KEYFRAME:
				if ( !ReadKeyframe( &chunk ) )
					return false;
				break;
			// それ以外のチャンクは読みとばす===========================
			default:
				if ( !ReadSkipChunk( &chunk ) )
					return false;
				break;
		}
	}
	return true;
}

void CLoader3ds::CalcNormals( STempObjectData *pData )
{
	MYERR_ASSERT( pData != NULL );

	// 各面の法線ベクトルを計算
	Vector3d *faceNormals = new Vector3d[pData->num_faces];
	int i;
	for ( i=0; i<pData->num_faces; ++i )
	{
		Vector3d vec1, vec2;
		vec1 = pData->verts[ pData->faces[i].poly[1] ] - pData->verts[ pData->faces[i].poly[0] ];
		vec2 = pData->verts[ pData->faces[i].poly[2] ] - pData->verts[ pData->faces[i].poly[1] ];
		faceNormals[i].cross( vec1, vec2 );
		faceNormals[i].normalize();
	}

	float smoothTh = cos( nag32::DegToRad(m_smoothAgl) );
	// 頂点法線を計算
	// ただしここでの頂点法線とは、i番目の面のj個目の頂点法線のこと.
	// つまり、面による頂点法線の共有はない
	if ( pData->normals != NULL )
		delete [] pData->normals;
	pData->normals = new Vector3d[ pData->num_faces*3 ];
	for ( i=0; i < pData->num_faces*3; ++i )
		pData->normals[i].set( 0.0f, 0.0f, 0.0f );
	for ( i=0; i < pData->num_faces; ++i )
	{
		for ( int j=0; j<3; ++j )
		{
			int idx = pData->faces[i].poly[j];
			
			pData->normals[i*3+j] += faceNormals[i];
			// 面iのj個目の頂点を共有する面kを検索
			for ( int k=i+1; k < pData->num_faces; ++k )
			{
				for ( int l=0; l<3; ++l )
				{
					if ( pData->faces[k].poly[l] == idx )
					{
						// 面iと面kの面法線の成す角度がm_smoothAgl以下ならばスムージング処理を行う
						if ( Dot( faceNormals[i], faceNormals[k] ) >= smoothTh )
						{
							pData->normals[i*3+j] += faceNormals[k];
							pData->normals[k*3+l] += faceNormals[i];
						}
					}
				}
			}
			// 法線ベクトルを正規化
			pData->normals[i*3+j].normalize();
		}
	}

	delete [] faceNormals;
}