恒定着色是在多边形由一种材质构成。多边形的每个点的面法线都相同,因此,只需对一个顶点的像素的光照情况对整个多边形进行着色。
对于平面组成的物体,是可行的,但是如果对曲面组成的物体,会导致物体看起来由多边形组成。
在16位着色下,光照步骤为
1, 计算面法线
2, 对于光源列表中的每个光源,计算其光照强度
3, 将所有光照强度相加
4, 将结果写入到多边形颜色变量的前16位中
这里暂不考虑材质,只考虑光照。
按照代码逐步进行。
先定义几种光照的索引
#define AMBIENT_LIGHT_INDEX 0
#define INFINITE_LIGHT_INDEX 1
#define POINT_LIGHT_INDEX 2
#define SPOT_LIGHT_INDEX 3
在GAME_INIT()中,重置光源
首先,要加个RGBA的结构体
typedef struct RGBAV1_TYPE
{
union
{
int rgba; //压缩格式
UCHAR rgba_M[4]; //数组格式
struct
{
UCHAR a, b, g, r; //显式名称格式
};
};
}RGBAV1, * RGBAV1_PTR;
接下来,加上光照结构体
typedef struct LIGHTV1_TYP
{
int state; //光照状态
int id; //光源ID
int attr; //光源类型及其他属性
RGBAV1 c_ambient; //环境光强度
RGBAV1 c_diffuse; //散射光强度
RGBAV1 c_specular; //镜面反射光强度
POINT4D pos; //光源位置
VECTOR4D dir; //光源方向
float kc,kl,kq; //衰减因子
float spot_inner; //聚光灯内锥角
float sport_outer; //聚光灯外锥角
float pf; //聚光灯指数因子
}LIGHTV1, * LIGHTV1_PTR;
下面重置光源
先设定最多灯数为8
#define MAX_LIGHTS 8
int ddraw_liushuixian::Reset_Lights_LIGHTV1( LIGHTV1_PTR lights, int num_lights )
{
static int first_time = 1;
memset( lights, 0, MAX_LIGHTS * sizeof( LIGHTV1));
//重置光源数
num_lights = 0;
first_time = 0;
return ( 1 );
}
再设置两个全局变量
LIGHTV1 lights[MAX_LIGHTS];
int num_lights = 0;
就可以在Game_Init()中调用了
liushuixian.Reset_Lights_LIGHTV1( lights, num_lights );
继续往下走,
定义个8.8.8.a(alpha占8位)格式的32位颜色值
#define _RGBA32BIT(r,g,b,a) ((a) + ((b) << 8) + ((g) << 16) + ((r) << 24))
设置几个颜色
RGBAV1 white, gray, black, red, green, blue;
white.rgba = _RGBA32BIT( 255, 255, 255, 0 );
gray.rgba = _RGBA32BIT( 100, 100, 100, 0 );
black.rgba = _RGBA32BIT( 0, 0, 0, 0 );
red.rgba = _RGBA32BIT( 255, 0, 0, 0 );
green.rgba = _RGBA32BIT( 0, 255, 0, 0 );
blue.rgba = _RGBA32BIT( 0, 0, 255, 0 );
接下来设置环境光的初始化(通过传递参数)
int ddraw_liushuixian::Init_Light_LIGHTV1( ddraw_math * math2, LIGHTV1_PTR lights, int index, int _state, int _attr, RGBAV1 _c_ambient, RGBAV1 _c_diffuse, RGBAV1 _c_specular, POINT4D_PTR _pos, VECTOR4D_PTR _dir, float kc, float kl, float kq, float _spot_inner, float _spot_outer, float _pf )
{
if ( index < 0 || index >= MAX_LIGHTS )
{
return ( 0 );
}
//初始化光源
lights[index].state = _state;
lights[index].id = index;
lights[index].attr = _attr;
lights[index].c_ambient = _c_ambient;
lights[index].c_diffuse = _c_diffuse;
lights[index].c_specular = _c_specular;
lights[index].kc = kc;
lights[index].kl = kl;
lights[index].kq = kq;
if ( _pos )
{
math2->VECTOR4D_COPY(_& lights[index].pos, _pos ); //光源位置
}
if ( _dir )
{
math2->VECTOR4D_COPY( & lights[index].dir, _dir ); //光源方向
//归一化
math2->VECTOR4D_Normalize( & lights[index].dir );
}
lights[index].spot_inner = _spot_inner;
lights[index].sport_outer = _spot_outer;
lights[index].pf = _pf;
//返回光源索引
return index;
}
下面定义了光源的几个常量
#define LIGHTV1_ATTR_AMBIENT 0x0001 //环境光源
#define LIGHTV1_ATTR_INFINITE 0x0002 //无穷远光源
#define LIGHTV1_ATTR_POINT 0x0004 //点光源
#define LIGHTV1_ATTR_SPOTLIGHT1 0x0008 //1类(简单)聚光灯
#define LIGHTV1_ATTR_SPOTLIGHT2 0x0010 //2类(复杂)聚光灯
#define LIGHTV1_STATE_ON 1 //光源打开
#define LIGHTV1_STATE_OFF 0 //光源关闭
突然感觉应该加个ddraw_light类,把灯光加上去。
调整后,头文件改为
#pragma once
#include "common.h"
#include "ddraw_math.h"
#define LIGHTV1_ATTR_AMBIENT 0x0001 //环境光源
#define LIGHTV1_ATTR_INFINITE 0x0002 //无穷远光源
#define LIGHTV1_ATTR_POINT 0x0004 //点光源
#define LIGHTV1_ATTR_SPOTLIGHT1 0x0008 //1类(简单)聚光灯
#define LIGHTV1_ATTR_SPOTLIGHT2 0x0010 //2类(复杂)聚光灯
#define LIGHTV1_STATE_ON 1 //光源打开
#define LIGHTV1_STATE_OFF 0 //光源关闭
#define MAX_LIGHTS 8
typedef struct RGBAV1_TYPE
{
union
{
int rgba; //压缩格式
UCHAR rgba_M[4]; //数组格式
struct
{
UCHAR a, b, g, r; //显式名称格式
};
};
}RGBAV1, * RGBAV1_PTR;
typedef struct LIGHTV1_TYP
{
int state; //光照状态
int id; //光源ID
int attr; //光源类型及其他属性
RGBAV1 c_ambient; //环境光强度
RGBAV1 c_diffuse; //散射光强度
RGBAV1 c_specular; //镜面反射光强度
POINT4D pos; //光源位置
VECTOR4D dir; //光源方向
float kc,kl,kq; //衰减因子
float spot_inner; //聚光灯内锥角
float sport_outer; //聚光灯外锥角
float pf; //聚光灯指数因子
}LIGHTV1, * LIGHTV1_PTR;
class DDRAW_LIGHT
{
public:
DDRAW_LIGHT(void);
~DDRAW_LIGHT(void);
public:
int Init_Light_LIGHTV1(
ddraw_math * math2,
LIGHTV1_PTR lights,
int index, //要创建的光源的索引(到MAX_LIGHTS-1)
int _state, //光源状态
int _attr, //光源类型及其他属性
RGBAV1 _c_ambient, //环境光强度
RGBAV1 _c_diffuse, //散射光强度
RGBAV1 _c_specular, //镜面反射光强度
POINT4D_PTR _pos, //光源位置
VECTOR4D_PTR _dir, //光源方向
float kc,
float kl,
float kq, //衰减因子
float _spot_inner, //聚光灯内锥角
float _spot_outer, //聚光灯外锥角
float _pf //聚光灯指数因子
);
int Reset_Lights_LIGHTV1( LIGHTV1_PTR lights, int num_lights );
};
然后,初始化几个灯,并归入数组中,
环境光
light.Init_Light_LIGHTV1( math, lights, AMBIENT_LIGHT_INDEX, LIGHTV1_STATE_ON, LIGHTV1_ATTR_AMBIENT, gray, black, black, NULL, NULL, 0, 0, 0, 0, 0, 0 );
VECTOR4D dlight_dir = { -1, 0, -1, 0 };
//方向光
light.Init_Light_LIGHTV1( math, lights, INFINITE_LIGHT_INDEX, LIGHTV1_STATE_ON, LIGHTV1_ATTR_INFINITE, gray, black, black, NULL, &dlight_dir, 0, 0, 0, 0, 0, 0 );
//点光源
VECTOR4D plight_pos = { 0, 200, 0, 0 };
light.Init_Light_LIGHTV1( math, lights, POINT_LIGHT_INDEX, LIGHTV1_STATE_ON, LIGHTV1_ATTR_POINT, black,green, black, &plight_pos, NULL, 0, 0.001, 0, 0, 0, 1 );
//聚光灯
VECTOR4D slight_pos = { 0, 200, 0, 0 };
VECTOR4D slight_dir = { -1, 0, -1, 0 };
light.Init_Light_LIGHTV1( math, lights, SPOT_LIGHT_INDEX, LIGHTV1_STATE_ON, LIGHTV1_ATTR_SPOTLIGHT2, black,red, black, &slight_pos, &slight_dir, 0, 0.001, 0, 0, 0, 1 );
下面是RGB共24位如何进行映射到8位模式下的着色方法,昨天看得云里雾里,今天终于看明白了。
其实就是说,2的24次方,相当于4G空间,8位着色是256种颜色,这样每种颜色用最小平方法查找距离最相近的,将会很大。那怎么办呢?先改为16位模式,再查找相近的,2的16次方是64KB,16位有5.5.5和5.6.5两种格式,转换方法就是,5.6.5就是R>>3,G>>2,B>>3;5.5.5就是都>>3
先设定一个查找表和调色板,
先设置一个返回
LPPALETTEENTRY DDRAW_Interface::getPalette()
{
return palette;
}
一个全局查找表
UCHAR rgblookup = ( UCHAR * ) malloc(65536);
int DDRAW_Interface::RGB_16_8_IndexedRGB_Table_Builder( int rgb_format, UCHAR * rgblookup )
{
if ( ! palette || ! rgblookup )
{
return -1;
}
if ( rgb_format == DD_PIXEL_FORMAT565)
{
for ( int rgbindex = 0; rgbindex < 65536; rgbindex ++)
{
int curr_index = -1;
long curr_error = INT_MAX;
for( int color_index = 0; color_index < 256; color_index++)
{
int r = ( rgbindex >> 11 ) << 3;
int g = ( ( rgbindex >> 5 ) & 0x3f ) << 2;
int b = ( rgbindex & 0x1f ) << 3;
long delta_red = abs( palette[color_index].peRed - r );
long delta_green = abs( palette[color_index].peGreen - g );
long delta_blue = abs( palette[color_index].peBlue - b );
long error = ( delta_red * delta_red ) + ( delta_green * delta_green ) + ( delta_blue * delta_blue );
//是否更近?
if ( error < curr_error)
{
curr_index = color_index;
curr_error = error;
}
}
//找到最近的索引后,将其存储到存储表中
rgblookup[rgbindex] = curr_index;
}
}
else
if ( rgb_format == DD_PIXEL_FORMAT555)
{
for ( int rgbindex = 0; rgbindex < 32768; rgbindex ++)
{
int curr_index = -1;
long curr_error = INT_MAX;
for( int color_index = 0; color_index < 256; color_index++)
{
int r = ( rgbindex >> 10 ) << 3;
int g = ( ( rgbindex >> 5 ) & 0x3f ) << 2;
int b = ( rgbindex & 0x1f ) << 3;
long delta_red = abs( palette[color_index].peRed - r );
long delta_green = abs( palette[color_index].peGreen - g );
long delta_blue = abs( palette[color_index].peBlue - b );
long error = ( delta_red * delta_red ) + ( delta_green * delta_green ) + ( delta_blue * delta_blue );
//是否更近?
if ( error < curr_error)
{
curr_index = color_index;
curr_error = error;
}
}
//找到最近的索引后,将其存储到存储表中
rgblookup[rgbindex] = curr_index;
}
}
}
在GAME_INIT()建立索引表
ddraw->RGB_16_8_IndexedRGB_Table_Builder( DD_PIXEL_FORMAT565, rgblookup );
在GAME_main()中,每帧设定几个模式
static int wireframe_mode = -1;
static int backface_mode = 1;
static int light_mode = 1;
static int help_mode = 1;
设定光的运动
点光源和聚光灯的运动
static float plight_ang = 0, slight_ang = 0;
lights[POINT_LIGHT_INDEX].pos.x = 4000 * cosf( plight_ang );
lights[POINT_LIGHT_INDEX].pos.y = 200;
lights[POINT_LIGHT_INDEX].pos.z = 4000 * sinf( plight_ang );
if ( ( plight_ang += 3) > 360 )
{
plight_ang = 0;
}
lights[SPOT_LIGHT_INDEX].pos.x = 2000 * cosf( slight_ang );
lights[SPOT_LIGHT_INDEX].pos.y = 200;
lights[SPOT_LIGHT_INDEX].pos.z = 2000 * sinf( slight_ang );
if ( ( slight_ang -= 5) > 0 )
{
slight_ang = 0;
}
下面进行本DEMO的核心,处理光照了。
函数代码虽然长,但是很好理解,固定着色就直接赋值;恒定着色按照各种光照公式
int ddraw_liushuixian::Light_OBJECT4DV1_World16( ddraw_math math, int rgb_format, OBJECT4DV1_PTR obj, CAM4DV1_PTR cam, LIGHTV1_PTR lights, int max_lights )
{
unsigned int r_base, g_base, b_base, //原来的颜色值
r_sum, g_sum, b_sum, //全部光源的总体光照效果
shaded_color; //最后的颜色
float dp, //点积
dist, //表面和光源之间的距离
i, //强度
nl, //法线长度
atten; //衰减计算结果
if ( ! ( obj->state & OBJECT4DV1_STATE_ACTIVE ) ||
( obj-> state & OBJECT4DV1_STATE_CULLED ) ||
! ( obj->state & OBJECT4DV1_STATE_VISIBLE))
{
return ( 0 );
}
for ( int poly = 0; poly < obj->num_polys; poly++)
{
POLY4DV1_PTR curr_poly = & obj->plist[poly];
if ( ! ( curr_poly->state & POLY4DV1_STATE_ACTIVE) ||
( curr_poly->state & POLY4DV1_STATE_CLIPPED) ||
( curr_poly->state & POLY4DV1_STATE_BACKFACE ))
{
continue;
}
//提取指向主列表的顶点索引
int vindex_0 = curr_poly->vert[0];
int vindex_1 = curr_poly->vert[1];
int vindex_2 = curr_poly->vert[2];
//检查多边形的着色模式
if ( curr_poly->attr & POLY4DV1_ATTR_SHADE_MODE_FLAT ||
curr_poly->attr & POLY4DV1_ATTR_SHADE_MODE_GOURAUD )
{
//提取多边形颜色的RGB值
if ( rgb_format == DD_PIXEL_FORMAT565 )
{
_RGB565FROM16BIT( curr_poly->color, & r_base, & g_base, & b_base );
//转换成.8.8格式
r_base <<= 3;
g_base <<= 2;
b_base <<= 3;
}
else
{
_RGB555FROM16BIT( curr_poly->color, & r_base, & g_base, & b_base );
//转换成.8.8格式
r_base <<= 3;
g_base <<= 3;
b_base <<= 3;
}
//初始化总体光照颜色
r_sum = 0;
g_sum = 0;
b_sum = 0;
//遍历光照
for ( int curr_light = 0; curr_light < max_lights; curr_light ++)
{
//光源是否被打开
if ( lights[curr_light].state)
{
continue;
}
//判断光源类型
if ( lights[curr_light].attr & LIGHTV1_ATTR_AMBIENT )
{
r_sum += ( ( lights[curr_light].c_ambient.r * r_base) / 256 );
g_sum += ( ( lights[curr_light].c_ambient.g * g_base) / 256 );
b_sum += ( ( lights[curr_light].c_ambient.b * b_base) / 256 );
}
else
if ( lights[curr_light].attr & LIGHTV1_ATTR_INFINITE )
{
VECTOR4D u, v, n;
math.VECTOR4D_Build( & obj->vlist_trans[vindex_0], & obj->vlist_trans[vindex_1], & u );
math.VECTOR4D_Build( & obj->vlist_trans[vindex_0], & obj->vlist_trans[vindex_2], & v );
math.VECTOR4D_CROSS( & u, &v, &n );
nl = math.VECTOR4D_length(&n);
dp = math.VECTOR4D_DOT( &n, & lights[curr_light].dir );
if ( dp > 0 )
{
i = 128 * dp / nl;
r_sum += ( lights[curr_light].c_diffuse.r * r_base * i ) / ( 256 * 128);
g_sum += ( lights[curr_light].c_diffuse.g * g_base * i ) / ( 256 * 128);
b_sum += ( lights[curr_light].c_diffuse.b * b_base * i ) / ( 256 * 128);
}
}
else
if ( lights[curr_light].attr & LIGHTV1_ATTR_POINT )
{
VECTOR4D u, v, n, l;
math.VECTOR4D_Build( & obj->vlist_trans[vindex_0], & obj->vlist_trans[vindex_1], & u );
math.VECTOR4D_Build( & obj->vlist_trans[vindex_0], & obj->vlist_trans[vindex_2], & v );
math.VECTOR4D_CROSS( & u, &v, &n );
nl = math.VECTOR4D_length(&n);
//计算从表面到光源的向量
math.VECTOR4D_Build( & obj->vlist_trans[vindex_0], & lights[curr_light].pos, &l );
//计算距离和衰减
dist = math.VECTOR4D_length( &l);
dp = math.VECTOR4D_DOT( &n, & l);
if ( dp > 0 )
{
atten = ( lights[curr_light].kc + lights[curr_light].kl * dist + lights[curr_light].kq * dist * dist );
i = 128 * dp / ( nl * dist * atten );
r_sum += ( lights[curr_light].c_diffuse.r * r_base * i ) / ( 256 * 128);
g_sum += ( lights[curr_light].c_diffuse.g * g_base * i ) / ( 256 * 128);
b_sum += ( lights[curr_light].c_diffuse.b * b_base * i ) / ( 256 * 128);
}
}
else
if ( lights[curr_light].attr & LIGHTV1_ATTR_SPOTLIGHT1 )
{
VECTOR4D u, v, n, l;
math.VECTOR4D_Build( & obj->vlist_trans[vindex_0], & obj->vlist_trans[vindex_1], & u );
math.VECTOR4D_Build( & obj->vlist_trans[vindex_0], & obj->vlist_trans[vindex_2], & v );
math.VECTOR4D_CROSS( & v, &u, &n );
nl = math.VECTOR4D_length(&n);
//计算从表面到光源的向量
math.VECTOR4D_Build( & obj->vlist_trans[vindex_0], & lights[curr_light].pos, &l );
//计算距离和衰减
dist = math.VECTOR4D_length( &l);
dp = math.VECTOR4D_DOT( &n, & lights[curr_light].dir );
if ( dp > 0 )
{
atten = ( lights[curr_light].kc + lights[curr_light].kl * dist + lights[curr_light].kq * dist * dist );
i = 128 * dp / ( nl * atten );
r_sum += ( lights[curr_light].c_diffuse.r * r_base * i ) / ( 256 * 128);
g_sum += ( lights[curr_light].c_diffuse.g * g_base * i ) / ( 256 * 128);
b_sum += ( lights[curr_light].c_diffuse.b * b_base * i ) / ( 256 * 128);
}
}
else
if ( lights[curr_light].attr & LIGHTV1_ATTR_SPOTLIGHT2 )
{
VECTOR4D u, v, n, d, s;
math.VECTOR4D_Build( & obj->vlist_trans[vindex_0], & obj->vlist_trans[vindex_1], & u );
math.VECTOR4D_Build( & obj->vlist_trans[vindex_0], & obj->vlist_trans[vindex_2], & v );
math.VECTOR4D_CROSS( & v, &u, &n );
nl = math.VECTOR4D_length(&n);
dp = math.VECTOR4D_DOT( &n, & lights[curr_light].dir );
if ( dp > 0 )
{
//计算从表面到光源的向量
math.VECTOR4D_Build( & lights[curr_light].pos, & obj->vlist_trans[vindex_0], &s );
dist = math.VECTOR4D_length( &s);
float dpsl = math.VECTOR4D_DOT( & s, & lights[curr_light].dir ) / dist;
if ( dpsl > 0)
{
atten = ( lights[curr_light].kc + lights[curr_light].kl * dist + lights[curr_light].kq * dist * dist );
float dpsl_exp = dpsl;
for ( int e_index = 1; e_index < ( int ) lights[curr_light].pf; e_index++ )
{
dpsl *= dpsl;
}
i = 128 * dp * dpsl_exp / ( nl * atten );
r_sum += ( lights[curr_light].c_diffuse.r * r_base * i ) / ( 256 * 128);
g_sum += ( lights[curr_light].c_diffuse.g * g_base * i ) / ( 256 * 128);
b_sum += ( lights[curr_light].c_diffuse.b * b_base * i ) / ( 256 * 128);
}
}
}
}
//确保颜色分量不溢出
if ( r_sum > 255 )
{
r_sum = 255;
}
if ( g_sum > 255 )
{
g_sum = 255;
}
if ( b_sum > 255 )
{
b_sum = 255;
}
//写入颜色
if ( rgb_format == DD_PIXEL_FORMAT565 )
{
shaded_color = _RGB16BIT565( r_sum, g_sum, b_sum );
}
else
if ( rgb_format == DD_PIXEL_FORMAT555 )
{
shaded_color = _RGB16BIT555( r_sum, g_sum, b_sum );
}
curr_poly->color = ( int ) ( ( shaded_color << 16 ) | curr_poly->color );
}
else
{
curr_poly->color = ( int ) ( ( curr_poly->color << 16 ) | curr_poly->color );
}
}
}
再插入列表前,判断是否光照处理
if ( light_mode == 1)
{
liushuixian.Light_OBJECT4DV1_World16( *math, ddraw->getPixelFormat(), &obj_player, &cam, lights, 4 );
}
同样各坦克也是
if ( light_mode == 1)
{
liushuixian.Light_OBJECT4DV1_World16(* math, ddraw->getPixelFormat(), &obj_tank, &cam, lights, 4 );
}
塔:
if ( light_mode == 1)
{
liushuixian.Light_OBJECT4DV1_World16(* math, ddraw->getPixelFormat(), &obj_tower, &cam, lights, 4 );
}
同样地面也一样
if ( light_mode == 1)
{
liushuixian.Light_OBJECT4DV1_World16(* math, ddraw->getPixelFormat(), &obj_marker, &cam, lights, 4 );
}
发现一片漆黑。
弄个文本ambient.txt,发现正确
后来发现开关相反,改正后OK了