1. 程式人生 > 其它 >[計算機圖形學]Blinn-Phong光照模型

[計算機圖形學]Blinn-Phong光照模型

技術標籤:# 計算機圖形學

文章目錄

一、前言

Blinn-Phong光照模型以Phong模型為基礎的,提供比Phong更柔和、更平滑的高光,而且由於Blinn-Phong的光照模型省去了計算反射光線的過程,運算速度上更快,因此成為很多CG軟體中預設的光照渲染方法,同時也被整合到大多數的圖形晶片中,在OpenGL和Direct3D渲染管線中,Blinn-Phong就是預設的渲染模型。

簡言之,Blinn-Phong光照模型提供的是非真實的光照,但這並不影響其在計算機圖形學中的地位。

二、原理

Blinn-Phong光照模型公式:

I = I a K a + I p K d ( l ⋅ n ) + I p K s ( h ⋅ n ) p I = I_aK_a + I_pK_d(l \cdot n) + I_pK_s(h \cdot n)^p

I=IaKa+IpKd(ln)+IpKs(hn)p

模型很精簡,就是三項之和:環境光、漫反射、鏡面反射

環境光, I a K a I_aK_a IaKa,是個定值
漫反射, I p K d ( l ⋅ n ) I_pK_d(l \cdot n) IpKd(ln),漫反射分量均勻的向四周傳播,因此與視線 v v v無關,決定物體上點的明暗( K d K_d Kd為點的實際顏色值的歸一化結果)
鏡面反射, I p K s ( h ⋅ n ) p I_pK_s(h \cdot n)^p IpKs(hn)p,決定點的高光程度,p為反射指數,越大越光滑

特別的,光源(只考慮點光源) L p L_p

Lp是衰減後的,處理為 L l ⋅ l \frac{L}{l \cdot l} llL。距離不是要開根號麼?其實無所謂,本來就不是真實的光照公式。

在這裡插入圖片描述
Blinn-Phong光照模型的優化在於引入了半形向量,使得在計算高光時避免了向量分解運算,而是採用向量合成,得到 h = l + v ∣ l + v ∣ h=\frac{l+v}{|l+v|} h=l+vl+v,這樣就不用通過 l 、 n l、n ln來分解求上圖的初設方向 r r r了。

由於 l 、 n 、 h l、n、h lnh都是單位向量,所以公式中的 ( l ⋅ n ) (l \cdot n) (ln) ( h ⋅ n ) (h \cdot n)

(hn)本質上就是 c o s θ cos \theta cosθ,反應出來的就是一個向量在另一個向量上的投影大小

鏡面反射表示當視線 v v v恰好沿著光的出射方向 r r r看時才最“刺眼”, p p p決定著鏡面反射分量作用程度,還記得註明的努力公式麼?

0.9 9 365 = ? 1.0 1 365 = ? 0.99^{365} = ? \\ 1.01^{365} = ? 0.99365=?1.01365=?

這裡也是一樣的,當 p p p很大時,視線 v v v只要偏離出射方向 r r r一丁丁點,這一項就沒了。

三、程式碼

程式碼網上很多,這裡提供一個C語言版本的

Blinn-Phong光照模型工程示例

//點光源位置(世界座標系下)
_internal_rw float _s_Light_Position[][3] = {
	{100, 20, -40},
	{-40, 100, 0},
};

//點光源強度
_internal_rw float _s_Light_Intensity[][3] = {
	{3000, 3000, 3000},
	{3000, 3000, 3000},
};

//環境光強度
_internal_rw float _s_Amb_Light_Intensity[3] = {
	100, 100, 100,
};

//鏡面反射指數因子的冪
_internal_ro float _k_SpecularFactorPower = 150.0f;

//-----------------------------------------------
//功能描述:判斷點是否在三角形內
//
//參    數:x,y[in],		    螢幕上要判斷的點的座標(計算畫素點的中點,畫素點差值為1)
//	       *ppointp[in],	三角形頂點座標,[x1, y1, z1], [x2, y2, z2], [x3, y3, z3]
//
//返 回 值:1, 在內部
//		   0, 在外部
//
//備註內容:無
//-----------------------------------------------
static int _IsInsideTriangle( float x, float y, float *ptpoint )
{
	float AB[2];
	float BC[2];
	float CA[2];
	
	float AP[2];
	float BP[2];
	float CP[2];
	
	int is_inside[2];
	
	AB[0] = ptpoint[3*1+0] - ptpoint[3*0+0];
	AB[1] = ptpoint[3*1+1] - ptpoint[3*0+1];
	
	BC[0] = ptpoint[3*2+0] - ptpoint[3*1+0];
	BC[1] = ptpoint[3*2+1] - ptpoint[3*1+1];

	CA[0] = ptpoint[3*0+0] - ptpoint[3*2+0];
	CA[1] = ptpoint[3*0+1] - ptpoint[3*2+1];

	AP[0] = x - ptpoint[3*0+0];
	AP[1] = y - ptpoint[3*0+1];
	
	BP[0] = x - ptpoint[3*1+0];
	BP[1] = y - ptpoint[3*1+1];

	CP[0] = x - ptpoint[3*2+0];
	CP[1] = y - ptpoint[3*2+1];
	
	is_inside[0] = ((AB[0]*AP[1] - AB[1]*AP[0]) > 0) 
			    && ((BC[0]*BP[1] - BC[1]*BP[0]) > 0)
			    && ((CA[0]*CP[1] - CA[1]*CP[0]) > 0);
	is_inside[1] = ((AB[0]*AP[1] - AB[1]*AP[0]) < 0) 
			    && ((BC[0]*BP[1] - BC[1]*BP[0]) < 0)
			    && ((CA[0]*CP[1] - CA[1]*CP[0]) < 0);

	return (is_inside[0] || is_inside[1]);
}

//-----------------------------------------------
//功能描述:計算三角形重心座標
//
//參    數:x,y[in],		    螢幕上要判斷的點的座標(計算畫素點的中點,畫素點差值為1)
//		   *ppointp[in],	三角形頂點座標,[x1, y1, z1], [x2, y2, z2], [x3, y3, z3]
//		   *pOut[out],		重心座標,[α,β,γ]
//
//返 回 值:void
//
//備註內容:無
//-----------------------------------------------
static void _ComputeBarycentric2D( float x, float y, float *ptpoint,
								   float *pOut )
{
    pOut[0] = (x*(ptpoint[3*1+1] - ptpoint[3*2+1]) + y*(ptpoint[3*2+0] - ptpoint[3*1+0]) + ptpoint[3*1+0]*ptpoint[3*2+1] - ptpoint[3*2+0]*ptpoint[3*1+1]) 
			/ (ptpoint[3*0+0]*(ptpoint[3*1+1] - ptpoint[3*2+1]) + (ptpoint[3*2+0] - ptpoint[3*1+0])*ptpoint[3*0+1] + ptpoint[3*1+0]*ptpoint[3*2+1] - ptpoint[3*2+0]*ptpoint[3*1+1]);
   
	pOut[1] = (x*(ptpoint[3*2+1] - ptpoint[3*0+1]) + y*(ptpoint[3*0+0] - ptpoint[3*2+0]) + ptpoint[3*2+0]*ptpoint[3*0+1] - ptpoint[3*0+0]*ptpoint[3*2+1]) 
			/ (ptpoint[3*1+0]*(ptpoint[3*2+1] - ptpoint[3*0+1]) + (ptpoint[3*0+0] - ptpoint[3*2+0])*ptpoint[3*1+1] + ptpoint[3*2+0]*ptpoint[3*0+1] - ptpoint[3*0+0]*ptpoint[3*2+1]);
    
	pOut[2] = (x*(ptpoint[3*0+1] - ptpoint[3*1+1]) + y*(ptpoint[3*1+0] - ptpoint[3*0+0]) + ptpoint[3*0+0]*ptpoint[3*1+1] - ptpoint[3*1+0]*ptpoint[3*0+1]) 
			/ (ptpoint[3*2+0]*(ptpoint[3*0+1] - ptpoint[3*1+1]) + (ptpoint[3*1+0] - ptpoint[3*0+0])*ptpoint[3*2+1] + ptpoint[3*0+0]*ptpoint[3*1+1] - ptpoint[3*1+0]*ptpoint[3*0+1]);
}

//-----------------------------------------------
//功能描述:根據重心座標對三角形內插值
//
//參    數:*pbarycentric[in],	重心座標(SIZE=3)
//		   *pIn[in],			輸入資訊(SIZE=3×SIZE),[x1, y1, z1], [x2, y2, z2], ...
//		   *pOut[out],			輸出資訊(SIZE=3)
//		   weight[in],			權值
//
//返 回 值:void
//
//備註內容:無
//-----------------------------------------------
static void _interpolate( float *pbarycentric, float *pIn, float *pOut, float weight )
{
	for (int i=0; i<3; i++) {
		pOut[i] = 0.0f;
	}

	for (int i=0; i<3; i++)
	{
		for (int j=0; j<3; j++)
		{
			pOut[j] += (pbarycentric[i] * pIn[3*i+j]);
		}
	}
	
	for (int i=0; i<3; i++) {
		pOut[i] /= weight;
	}
}

//-----------------------------------------------
//功能描述:對單個三角形進行Phong著色
//
//參    數:*ppoint[in],	    頂點座標資訊,[x1, y1, z1], [x2, y2, z2], ...
//		   *pvn[in],		法線資訊,[vnx, vny, vnz],
//		   *pvt[in],		紋理座標,[vnt, vty]
//		   num[in],		    ppoint大小(目前num必須為9!)
//
//返 回 值:void
//
//備註內容:無
//-----------------------------------------------
static void _phong_fragment_shader( float *ppoint, float *pvn, float *pvt, uint16_t num )
{
	float min_x = MIN(ppoint[3*0+0], MIN(ppoint[3*1+0], ppoint[3*2+0]));
	float max_x = MAX(ppoint[3*0+0], MAX(ppoint[3*1+0], ppoint[3*2+0]));
	float min_y = MIN(ppoint[3*0+1], MIN(ppoint[3*1+1], ppoint[3*2+1]));
	float max_y = MAX(ppoint[3*0+1], MAX(ppoint[3*1+1], ppoint[3*2+1]));

	if ((!ppoint) || (9 != num) ) 
		return;
	
	min_x = (int)floor(min_x);
	max_x = (int)ceil(max_x);
	min_y = (int)floor(min_y);
	max_y = (int)ceil(max_y);
	
	min_x = MAX(0, min_x);
	max_x = MIN(1000, max_x);
	min_y = MAX(0, min_y);
	max_y = MIN(1000, max_y);
	
	for (int x=min_x; x<=max_x; x++) 
    {	
		for (int y=min_y; y<=max_y; y++) 
        {	
			if (_IsInsideTriangle(x+0.5f, y+0.5f, ppoint))
			{
				float barycentric[3];
				_ComputeBarycentric2D(x+0.5f, y+0.5f, ppoint, barycentric); //1.59us

				float interpolate_point[3], interpolate_texture[3];
				_interpolate(barycentric, ppoint, interpolate_point, 1.0f);
				_interpolate(barycentric, pvt, interpolate_texture, 1.0f);
								
				uint16_t index = y*g_TransferMatrixInfo.width + x;
				if ( (index < GET_ARRAY_NUM(_s_DeepBuffer)) 
				  && (_s_DeepBuffer[index] > interpolate_point[2]) )
				{
					_s_DeepBuffer[index] = interpolate_point[2];
				
					float color24[9];

					if (pvt)
						_g_TriangleInfo.face_color = G3D_GetBMPPointColor(interpolate_texture[0], interpolate_texture[1], &_g_BMPHeadInfo);					

					for (int i=0; i<3; i++) 
					{
						color24[3*i+0] = (_g_TriangleInfo.face_color & 0xF800) >> 8;
						color24[3*i+1] = (_g_TriangleInfo.face_color & 0x07E0) >> 3;
						color24[3*i+2] = (_g_TriangleInfo.face_color & 0x001F) << 3;
					}	
					
					float vn_inpolated[3],v_inpolated[3],c_inpolated[3];
					
					_interpolate(barycentric, color24, c_inpolated, 1.0f);
					_interpolate(barycentric, pvn, vn_inpolated, 1.0f);
					_interpolate(barycentric, ppoint, v_inpolated, 1.0f);
					
					float Ka[3] = {0.000f, 0.000f, 0.000f};
					float Kd[3] = {1.0f, 1.0f, 1.0f};
					calc_vector_nummulti(3, c_inpolated, 1.0f/255.0f, Kd);
					float Ks[3] = {0.7937f, 0.7937f, 0.7937f};
													
					float result_color[3] = {0.0f, 0.0f, 0.0f};
					for (int i=0; i<GET_ARRAY_NUM(_s_Light_Position); i++)
					{
						float dummy_data[3];

						float v[3],l[3],h[3];
						float nn[3],vn[3],ln[3],hn[3];
						
						calc_vector_minus(3, g_TransferMatrixInfo.eye_point, v_inpolated, v); 
						calc_vector_minus(3, _s_Light_Position[i], v_inpolated, l);
									
						calc_vector_normalized(3, vn_inpolated, nn);
						calc_vector_normalized(3, v, vn);
						calc_vector_normalized(3, l, ln);

						calc_vector_plus(3, ln, vn, h);
						calc_vector_normalized(3, h, hn);
						
						float r = 1.0f / calc_vector_multi(3, l, l);
						float decay[3];
						calc_vector_nummulti(3, _s_Light_Intensity[i], r, decay);
																	
						float La[3],Ld[3],Ls[3];
						float dump;
									
						//環境光分量
						calc_vector_cwisemulti(3, Ka, _s_Amb_Light_Intensity, La); 
						
						//漫反射分量
						calc_vector_cwisemulti(3, Kd, decay, Ld); 
						dump = MAX(0.0f, calc_vector_multi(3, nn, ln));
						calc_vector_nummulti(3, Ld, dump, Ld);
						
						//鏡面反射分量
						calc_vector_cwisemulti(3, Ks, decay, Ls); 
						dump = pow(MAX(0.0f, calc_vector_multi(3, nn, hn)), _k_SpecularFactorPower);
						calc_vector_nummulti(3, Ls, dump, Ls);
												
						//求和
						calc_vector_plus(3, La, Ld, dummy_data);
						calc_vector_plus(3, Ls, dummy_data, dummy_data);
						
						calc_vector_plus(3, dummy_data, result_color, result_color);
					}

					calc_vector_nummulti(3, result_color, 255.0f/GET_ARRAY_NUM(_s_Light_Position), result_color);
					calc_vector_minlimit(3, result_color, 255.0f, result_color);
					
					uint16_t color = TFT_MakeColor( ((uint8_t)result_color[0])>>3, ((uint8_t)result_color[1])>>2, ((uint8_t)result_color[2])>>3);
					TFT_DrawPoint( SCREEN_ORIGIN_X_OFFSET+g_TransferMatrixInfo.width-x, 
								   SCREEN_ORIGIN_Y_OFFSET+y, 
								   color );
				}
			}
		}
	}
}