[計算機圖形學]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
a
K
a
I_aK_a
IaKa,是個定值
漫反射,
I
p
K
d
(
l
⋅
n
)
I_pK_d(l \cdot n)
IpKd(l⋅n),漫反射分量均勻的向四周傳播,因此與視線
v
v
v無關,決定物體上點的明暗(
K
d
K_d
Kd為點的實際顏色值的歸一化結果)
鏡面反射,
I
p
K
s
(
h
⋅
n
)
p
I_pK_s(h \cdot n)^p
IpKs(h⋅n)p,決定點的高光程度,p為反射指數,越大越光滑
特別的,光源(只考慮點光源)
L
p
L_p
Blinn-Phong光照模型的優化在於引入了半形向量,使得在計算高光時避免了向量分解運算,而是採用向量合成,得到
h
=
l
+
v
∣
l
+
v
∣
h=\frac{l+v}{|l+v|}
h=∣l+v∣l+v,這樣就不用通過
l
、
n
l、n
l、n來分解求上圖的初設方向
r
r
r了。
由於
l
、
n
、
h
l、n、h
l、n、h都是單位向量,所以公式中的
(
l
⋅
n
)
(l \cdot n)
(l⋅n)和
(
h
⋅
n
)
(h \cdot n)
鏡面反射表示當視線 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語言版本的
//點光源位置(世界座標系下)
_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 );
}
}
}
}
}