摘要:用一个顶点光照的程序来分析Cg的程序如何写,说明上次封装的一个CGShader如何使用,并简单阐述Phong光照模型的原理。
1. 顶点程序运行的效果:
终于到了学编程最激动人心的时刻了,自己动手实现,我们要“以只看不练为耻,以勤于实践为荣”。第一个练习的效果用《The Cg Tutorial》中的第五章中的Fragment Lighting例子。程序中的模型用美丽女神Venus的雕像,渲染为青铜色,该模型是个3DS文件,网上读3DS文件类多如牛毛,随便下载一个导入就行。效果如下图:

2. 简单的Phong光照模型
这里的光照模型对经典的Phong光模型进行了修改和扩展,在这个模型里,将一个物体表面最终的颜色分解为以下几项:放射(Emissive)、环境反射(Ambient)、漫反射(Diffuse)和镜面反射(Specular),物体最终的光照效果由这几个分项光照效果叠加而来。
放射项: Emissive = Ke (Ke代表模型材质的放射光颜色)
环境反射项: Ambient = Ka X globalAmbient (其中Ka代表材质的环境反射系数,globalAmbient表示灯光的环境光的颜色)
漫反射项: Diffuse = Kd X lightColor X max( dot(N, L), 0 ) (其中 Kd为材质的漫反射系数,lightColor是灯光的漫反射光的颜色,N为物体表面的规范化的法向量,L为模型上的点指向灯光的规范化向量。max( dot(N, L), 0 ) 表示取N,L的点积值和零中较大的一个值。
镜面反射项:Specular = Ks X lightColor X facing X (max(N, H), 0)shininess (其中Ks 为材质的漫反射系数,lightColor同上,facing 取值当N, H的点积大于零在facing = 1, 否则facing = 0,max( dot(N, H)的意义同上,shininess 是其指数,H为L和V的中间向量的归一量,V是视点指向物体上一点的向量)
总体光照效果: finalColor = Emissive + Ambient + Diffuse + Specular
3. CGShader的用法
这个光照效果的实现用到了上次封装的CGShader类,这个练习用到了顶点Shader和片元Shader,其使用步骤如下代码所示:
(1) 首先定义两个Shader对象:
CGShader vertShader ( " vertShader " ); //顶点Shader
CGShader fragShader ( " fragShader " ); //片元Shader
(2) 初始化及获得两个Shader中的Uniform 变量:
vertShader.createShader(
true
,
"
vertlighting.cg
"
);
fragShader.createShader(
false
,
"
fraglighting.cg
"
);

void
AddCgParams()

...
{
vertShader.addParam("modelViewProj");
fragShader.addParam("globalAmbient");
fragShader.addParam("lights[0].color");
fragShader.addParam("lights[0].position");
fragShader.addParam("lights[1].color");
fragShader.addParam("lights[1].position");
fragShader.addParam("eyePosition");
fragShader.addParam("material.Ke");
fragShader.addParam("material.Ka");
fragShader.addParam("material.Ks");
fragShader.addParam("material.Kd");
fragShader.addParam("material.shininess");
}
前面两行是从文件载入Cg程序并编译,后面的函数是获得两个Cg程序里面的Uniform变量的,vertShader比较简单,只有一个变量,光照效果用了两个灯光。
(3) 接着给参数赋值:
void
SetParameters()

...
{
fragShader.setParamArrayf("globalAmbient", globalAmbient);
fragShader.setParamArrayf("lights[0].color", lightColor1);
fragShader.setParamArrayf("lights[1].color", lightColor2);

float brassEmissive[3] = ...{0.0f, 0.0f, 0.0f},

brassAmbient[3] = ...{0.25f, 0.148f, 0.06475f},

brassDiffuse[3] = ...{0.4f, 0.2368f, 0.1036f},

brassSpecular[3] = ...{0.7746f, 0.4586f, 0.2006f},
brassShininess = 76.8f;
fragShader.setParamArrayf("material.Ke", brassEmissive);
fragShader.setParamArrayf("material.Ka", brassAmbient);
fragShader.setParamArrayf("material.Ks", brassSpecular);
fragShader.setParamArrayf("material.Kd", brassDiffuse);
fragShader.setParam1("material.shininess", brassShininess);
}
(4) 在绘制函数里面启用Shader并绘制模型
Vector4f lightpos1
=
Vector4f(
100
*
cos(lightAngle
*
DEG2RAD),
160
,
100
*
sin(lightAngle
*
DEG2RAD),
1
);
Vector4f lightpos2
=
Vector4f(
-
60
,
350
,
550
,
1
);
Vector4f eyepos
=
Vector4f(
0
,
0
,
400
,
1
);
Vector4f lightPosition, eyePosition;

glClear(GL_COLOR_BUFFER_BIT
|
GL_DEPTH_BUFFER_BIT);
//
清除屏幕和深度缓存

//
draw model and send it to CG for lighting
Matrix4f revModelMat, scaleMat, rotateMat, modelMat, viewMat, modleViewMat;
viewMat.lookAt(eyepos.x,eyepos.y,eyepos.z,
0
,
0
,
0
,
0
,
1
,
0
);

vertShader.activate();
//
启用Shader
fragShader.activate();
modelMat.rotateX(xrot);
rotateMat.rotateY(yrot);
modelMat
*=
rotateMat;
//
获得模型的模型变换矩阵
revModelMat
=
modelMat; revModelMat.setInverse();
lightPosition
=
revModelMat
*
lightpos1;
//
把灯光的位置变换到物体局部空间
eyePosition
=
revModelMat
*
eyepos;
//
把眼睛的位置变换到物体的局部空间

float
lightp[
3
]
=
...
{lightPosition.x, lightPosition.y, lightPosition.z}
;

float
eyep[
3
]
=
...
{eyePosition.x, eyePosition.y, eyePosition.z}
;
fragShader.setParamArrayf(
"
lights[0].position
"
, lightp);
fragShader.setParamArrayf(
"
eyePosition
"
, eyep);
lightPosition
=
revModelMat
*
lightpos2;

float
lightp2[
3
]
=
...
{lightPosition.x, lightPosition.y, lightPosition.z}
;
fragShader.setParamArrayf(
"
lights[1].position
"
, lightp2);

modelViewProj
=
projMatrix
*
viewMat;
modelViewProj
*=
modelMat;
vertShader.setColPriorMatrixf(
"
modelViewProj
"
, (
float
*
)modelViewProj);
//
设置模型、视图及投影联合变换矩阵
SetParameters();
//
在这里设置参数
if
(model3ds) model3ds
->
render(ALL_EFFECTS);
//
画3DS模型
vertShader.deactivate();
//
绘制后停掉Shader
fragShader.deactivate();
由于光照处理是在物体空间里进行的,所以要把World Space里的灯光位置和眼睛位置变换到物体的局部空间,这里面涉及到了模型变换矩阵计算,视矩阵计算及一系列的向量及矩阵的运算,其中用到的Matrix4f及Vector4f 及Vector3f 都是自封装的类,实现了向量及矩阵的直观的基本运算。
(5) 程序结束要释放Cg占用的资源
CGcontext ctx
=
vertShader.getCGContext();
vertShader.destroyShader();
fragShader.destroyShader();
cgDestroyContext(ctx);
//
由于同一个程序里只有一个CGContext ,多个Shader公用,只删一次
4. 结束语:
作为初学者的第一个练习,这个东西显得略微有点庞大,不过写这么个东西,挺有收获,一下可以了解很多东西,这里面用到的CGShader的封装还很初级,只有一般参数的设置,贴图部分及其他没接触的部分的参数设置还没考虑进去,以后会陆续完善的。终于完成了第一个比较完善的Cg光照程序。