光照模型将采用广泛应用的phong模型,虽然这种模型在openGL的固定管线中已经实现了,但是学习该光照模型可以更加清楚的了解可编程渲染管线的流程。
首先要实现phong光照模型先要了解该模型中光照计算,关于phong模型光照计算的相关资料网上相当多,或者参考任何一本计算机图形学的相关书籍即可,这里只给出计算公式。
C = ambient + diffuse + specular
从公式中可以得出,物体顶点的最终颜色是由环境反射,漫反射和镜面反射三个成分来决定的。环境反射的计算公式为:
ambient = IaKa
其中,Ia是光的环境反射强度,Ka是物体材质的环境射系数。环境反射很简单,和光源、法线等等都没有关系,下面是只有环境反射的例子。
Fig1环境反射,Ia=(1.0, 1.0, 1.0),Ka=(0.15,0.15,0.0)
和环境反射相比,漫反射就稍微复杂一点,它和光源的位置和物体顶点的法线都有关系。漫反射的计算公式为:
diffuse = I?d?Kd (NL)
其中I?d?是光的漫反射强度,Kd是物体材质漫反射系数,NL表示法线N和入射光线L的内积。下面是只有面反射的例子。
Fig2漫反射,Id=(1.0, 1.0, 1.0),Kd=(1.0, 1.0 , 0.0)
最后是镜面反射,和漫反射相比,镜面反射又复杂了一点。它不仅和光源位置,物体顶点法线有关系,而且还和我们观看的位置有关系。镜面反射的计算公式为:
specular = IsKs(VR)?n
其中Is是光的镜面反射强度,Ks是物体材质的镜面反射系数,VR表示相机朝向向量V和反射光线R的内积,n表示该内积的n次幂。这里反射光线R可以通过公式
R = 2(LN)N-L?
来得到。这里还可以使用half vector来计算,计算half vector要比计算反射向量R方便快速的多。
H=(L+V)/2
所以,镜面反射公式现在可以写成
specular = IsKs(NH)?n
下面是只有镜面反射的例子。
Fig3镜面反射,Is=(1.0, 1.0, 1.0),Ks=(1.0, 1.0 , 1.0,), n=32
通过上面的过程,分别计算出了物体每个顶点的环境反射,面反射和镜面反射。最后简单将这三个成分相加即可得到顶点最终的颜色。
C = IaKa + I?d?Kd (NL)+ IsKs(NH)?n
图fig4显示了这个相加的过程。
|
+ |
|
+ |
|
= |
|
Fig4 phong光照
下面是vertex shader的代码。由于是基于vertex的光照,所以不需要fragment shader。
vertex脚本:
uniform float3 LightPosition; //光源位置
uniform float3 eyePosition; //相机位置
uniform float3 I; //光强度
uniform float3 Ka; //环境光反射系数
uniform float3 Kd; //漫反射系数
uniform float3 Ks; //镜面反射系数
uniform float shininess; //n幂次struct output
{float4 position : POSITION; float4 color : COLOR;
};output v_main( float4 position : POSITION,float3 normal : NORMAL,uniform float4x4 MV, // 在相机坐标系中计算,所以要用到ModelView变换矩阵uniform float4x4 MVP // ModelViewProjection变换矩阵)
{output OUT;OUT.position = mul(MVP, position);float3 N = normalize(mul(MV, float4(normal,0.0)) ).xyz; //转换法线到相机坐标系float3 P = mul(MV, position).xyz; //转换物体顶点到相机坐标系float3 L = normalize(LightPosition - P);float NdotL = max(dot(N,L),0); //判断法线和入射光线的角度是否大于90度float3 ambient = Ka * I; //环境反射float3 diffuse = Kd * I * NdotL; //漫反射float3 V = normalize(eyePosition - P);float3 H = normalize(L+V); //half vectorfloat NdotH = pow(max(dot(N,H), 0), shininess);if(NdotL<=0) NdotH = 0.0;float3 specular = Ks*I*NdotH; //镜面反射float3 color = ambient + diffuse +specular; //所有成分相加OUT.color.xyz= color;OUT.color.w = 1.0;return OUT;
}
主程序:
#include <gl/glut.h>
#include <cg/cg.h>
#include <Cg/cgGL.h>
#include <stdio.h>
#include <Windows.h>int ww = 640, hh = 480;void render();
void reshape(int w, int h);static CGcontext myCgContext;
static CGprofile myCgVertexProfile;
static CGprogram myCgVertexProgram;static const char *myProgramName = "Lighting CG";
static const char *myVertexProgramFileName = "CSDN_02v.cg";
static const char *myVertexProgramName = "v_main";CGparameter lp, ep, i, a, d, s, n;
//display FPS on the title of the window
static float lastTime = 0.0f;
void displayFPS(){static float framesPerSecond = 0.0f; // This will store our fps// This will hold the time from the last framefloat currentTime = GetTickCount() * 0.001f; if( currentTime - lastTime > 0.0f ){framesPerSecond = 1/(currentTime - lastTime);char strFrameRate[256];lastTime = currentTime;sprintf(strFrameRate, "Current Frames Per Second: %f", framesPerSecond);glutSetWindowTitle( strFrameRate);framesPerSecond = 0;}
}int main(int argc, char** argv)
{//【1】初始化部分glutInit(&argc, argv);glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);glutInitWindowSize(ww,hh);glutCreateWindow(myProgramName);//【2】构建shader运行环境;myCgContext = cgCreateContext();cgGLSetDebugMode(CG_FALSE);cgSetParameterSettingMode(myCgContext, CG_DEFERRED_PARAMETER_SETTING);myCgVertexProfile = cgGLGetLatestProfile(CG_GL_VERTEX);cgGLSetOptimalOptions(myCgVertexProfile);myCgVertexProgram =cgCreateProgramFromFile(myCgContext, /* Cg runtime context */CG_SOURCE, /* Program in human-readable form */myVertexProgramFileName, /* Name of file containing program */myCgVertexProfile, /* Profile: OpenGL ARB vertex program */myVertexProgramName, /* Entry function name */NULL); /* No extra compiler options */cgGLLoadProgram(myCgVertexProgram);//【3】将shader载入,并运行glutDisplayFunc(render);glutReshapeFunc(reshape);glEnable(GL_DEPTH_TEST);glutMainLoop();return 0;
}void reshape(int w, int h)
{glMatrixMode(GL_PROJECTION);glLoadIdentity();gluPerspective(45, (float)w/(float)h, 0.1, 100);glViewport(0,0,w,h);ww = w;hh = h;
}void render()
{displayFPS();//【3.1】gl视图变换glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);glClearColor(.0f, .0f, .2f, 1.0f);glMatrixMode(GL_MODELVIEW);glLoadIdentity();gluLookAt(.0,.0,5.0, .0,.0,.0, .0,1.0,.0);static float angle;glRotatef(angle, 0.0,1.0,0.0);//【3.2】把程序与当前API状态绑定起来;cgGLBindProgram(myCgVertexProgram);cgGLEnableProfile(myCgVertexProfile);//将ModelViewProjection矩阵传入shaderCGparameter mvp = cgGetNamedParameter(myCgVertexProgram, "MVP");cgGLSetStateMatrixParameter(mvp, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);CGparameter mv = cgGetNamedParameter(myCgVertexProgram, "MV");cgGLSetStateMatrixParameter(mv, CG_GL_MODELVIEW_MATRIX, CG_GL_MATRIX_IDENTITY);//注意传参的方法☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
lp = cgGetNamedParameter(myCgVertexProgram, "LightPosition");ep = cgGetNamedParameter(myCgVertexProgram, "eyePosition");i = cgGetNamedParameter(myCgVertexProgram, "I");a = cgGetNamedParameter(myCgVertexProgram, "Ka");d = cgGetNamedParameter(myCgVertexProgram, "Kd");s = cgGetNamedParameter(myCgVertexProgram, "Ks");n = cgGetNamedParameter(myCgVertexProgram, "shininess");cgSetParameter3f(lp,1.0,1.0,1.0);cgSetParameter3f(ep,0,0.5,0);cgSetParameter3f(i ,1.0, 1.0, 1.0);cgSetParameter3f(a ,0.15,0.15, 0.0);cgSetParameter3f(d ,1.0, 1.0 , 0.0);cgSetParameter3f(s ,1.0, 1.0 , 1.0);cgSetParameter1f(n ,32);glutSolidTorus(0.3,1.0,30,30);cgGLDisableProfile(myCgVertexProfile);angle += 0.5;if(angle >=360) angle = 0.0f;glutSwapBuffers();glutPostRedisplay();
}
由于是基于vertex的光照,虽然采用gouraud shading要比flat shading效果好的多,但是和phong shading的效果相差很大。这里大家要注意的是,phong model和phong shading的区别。上一篇教程所讲的光照模型叫phong model,而这篇教程要介绍的一种着色方法叫phong shading,必须要使用shader才能实现。下面的图中对比了采用flat shading,gouraud shading和phong shading技术渲染的一个圆环。
Flat shading |
Gouraud shading |
Phong shading |
Cg来实现基于pixel lighing的phong shading光照就容易多了。绝大部分的Cg shader代码都是一样的,主要的改变就是这次不是在vertex shader里光照,而是在fragment shader里计算光照。所以整个vertex shader的代码很简单,将要渲染的物体的顶点位置,顶点法线传入fragment shader就是vertex shader的全部工作。整个vertex shader的代码如下。
上面的代码的输出结构体中有三个成员。Position是传入的物体的顶点,该顶点将用ModelViewProjection矩阵转换成剪裁坐标系中的坐标供光栅化使用。一旦将坐标转换后,我们就无法在fragment shader中使用物体的顶点坐标了。由于要在fragmentshader中计算光照,所以我们要将物体的顶点位置,顶点法线都传入到fragment shader中。这里物体转换前的顶点和法线分别使用了语义TEXCOORD0和TEXCOORD1,代表将它们作为纹理坐标后传入fragment shader。这样GPU会把顶点和法线信息当做纹理坐标来处理,在贴图的时候,GPU会根据纹理自动插值计算每个像素对应的颜色,而现在GPU插值计算出来的就是每个像素对应的坐标和法线信息了。有了这些数据就可以进行phong shading的计算了。光照计算的公式和方法和上一个教程介绍的一模一样。
vertex脚本:
struct output
{float4 position : POSITION; float3 objectPos : TEXCOORD0; float3 normal : TEXCOORD1;
};output v_main( float4 position : POSITION,float3 normal : NORMAL,uniform float4x4 MV,uniform float4x4 MVP)
{output OUT;OUT.position = mul(MVP, position);OUT.objectPos = mul(MV, position).xyz;OUT.normal = mul(MV, float4(normal,0.0)).xyz;return OUT;
}
fragment脚本:
uniform float3 LightPosition;
uniform float3 eyePosition;
uniform float3 I;
uniform float3 Ka;
uniform float3 Kd;
uniform float3 Ks;
uniform float shininess;struct input{float3 objectPos: TEXCOORD0; float3 normal : TEXCOORD1;
};struct output{float4 color : COLOR;
};output f_main( in input IN )
{output OUT;float3 N = normalize(IN.normal);float3 P = IN.objectPos;float3 L = normalize(LightPosition - P);float NdotL = max(dot(N,L),0);float3 ambient = Ka * I;float3 diffuse = Kd * I * NdotL;float3 V = normalize(eyePosition - P);float3 H = normalize(L+V);float NdotH = pow(max(dot(N,H), 0), shininess);if(NdotL<=0)NdotH = 0.0;float3 specular = Ks*I*NdotH;float3 color = ambient + diffuse + specular;OUT.color.xyz= color;OUT.color.w = 1.0;return OUT;
}
主程序:
#include <gl/glut.h>
#include <cg/cg.h>
#include <Cg/cgGL.h>
#include <stdio.h>
#include <Windows.h>int ww = 640, hh = 480;void render();
void reshape(int w, int h);static CGcontext myCgContext;
static CGprofile myCgVertexProfile;
static CGprofile myCgFragmentProfile;
static CGprogram myCgVertexProgram;
static CGprogram myCgFragmentProgram;static const char *myProgramName = "Lighting CG2";
static const char *myVertexProgramFileName = "CSDN_03v.cg";
static const char *myVertexProgramName = "v_main";
static const char *myFragmentProgramFileName = "CSDN_03f.cg";
static const char *myFragmentProgramName = "f_main";CGparameter lp, ep, i, a, d, s, n;
//display FPS on the title of the windowvoid displayFPS(){static float lastTime = 0.0f;static float framesPerSecond = 0.0f; // This will store our fps// This will hold the time from the last framefloat currentTime = GetTickCount() * 0.001f; if( currentTime - lastTime > 0.0f ){framesPerSecond = 1/(currentTime - lastTime);char strFrameRate[256];lastTime = currentTime;sprintf(strFrameRate, "Current Frames Per Second: %f", framesPerSecond);glutSetWindowTitle( strFrameRate);framesPerSecond = 0;}
}int main(int argc, char** argv)
{//【1】初始化部分glutInit(&argc, argv);glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);glutInitWindowSize(ww,hh);glutCreateWindow(myProgramName);//【2】构建shader运行环境;myCgContext = cgCreateContext();cgGLSetDebugMode(CG_FALSE);cgSetParameterSettingMode(myCgContext, CG_DEFERRED_PARAMETER_SETTING);myCgVertexProfile = cgGLGetLatestProfile(CG_GL_VERTEX);cgGLSetOptimalOptions(myCgVertexProfile);myCgVertexProgram =cgCreateProgramFromFile(myCgContext, /* Cg runtime context */CG_SOURCE, /* Program in human-readable form */myVertexProgramFileName, /* Name of file containing program */myCgVertexProfile, /* Profile: OpenGL ARB vertex program */myVertexProgramName, /* Entry function name */NULL); /* No extra compiler options */cgGLLoadProgram(myCgVertexProgram);myCgFragmentProfile = cgGLGetLatestProfile(CG_GL_FRAGMENT);cgGLSetOptimalOptions(myCgFragmentProfile);myCgFragmentProgram =cgCreateProgramFromFile(myCgContext, /* Cg runtime context */CG_SOURCE, /* Program in human-readable form */myFragmentProgramFileName, /* Name of file containing program */myCgFragmentProfile, /* Profile: OpenGL ARB vertex program */myFragmentProgramName, /* Entry function name */NULL); /* No extra compiler options */cgGLLoadProgram(myCgFragmentProgram);//【3】将shader载入,并运行glutDisplayFunc(render);glutReshapeFunc(reshape);glEnable(GL_DEPTH_TEST);glutMainLoop();return 0;
}void reshape(int w, int h)
{glMatrixMode(GL_PROJECTION);glLoadIdentity();gluPerspective(45, (float)w/(float)h, 0.1, 100);glViewport(0,0,w,h);ww = w;hh = h;
}void render()
{displayFPS();//【3.1】gl视图变换glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);glClearColor(.0f, .0f, .2f, 1.0f);glMatrixMode(GL_MODELVIEW);glLoadIdentity();gluLookAt(.0,.0,5.0, .0,.0,.0, .0,1.0,.0);static float angle;glRotatef(angle, 0.0,1.0,0.0);//【3.2】把程序与当前API状态绑定起来;cgGLBindProgram(myCgVertexProgram);cgGLEnableProfile(myCgVertexProfile);cgGLBindProgram(myCgFragmentProgram);cgGLEnableProfile(myCgFragmentProfile);//将ModelViewProjection矩阵传入shaderCGparameter mvp = cgGetNamedParameter(myCgVertexProgram, "MVP");cgGLSetStateMatrixParameter(mvp, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);CGparameter mv = cgGetNamedParameter(myCgVertexProgram, "MV");cgGLSetStateMatrixParameter(mv, CG_GL_MODELVIEW_MATRIX, CG_GL_MATRIX_IDENTITY);//传参的方法lp = cgGetNamedParameter(myCgFragmentProgram, "LightPosition");ep = cgGetNamedParameter(myCgFragmentProgram, "eyePosition");i = cgGetNamedParameter(myCgFragmentProgram, "I");a = cgGetNamedParameter(myCgFragmentProgram, "Ka");d = cgGetNamedParameter(myCgFragmentProgram, "Kd");s = cgGetNamedParameter(myCgFragmentProgram, "Ks");n = cgGetNamedParameter(myCgFragmentProgram, "shininess");cgSetParameter3f(lp,1.0,1.0,1.0);cgSetParameter3f(ep,0,0.5,0);cgSetParameter3f(i ,1.0, 1.0, 1.0);cgSetParameter3f(a ,0.15,0.15, 0.0);cgSetParameter3f(d ,1.0, 1.0 , 0.0);cgSetParameter3f(s ,1.0, 1.0 , 1.0);cgSetParameter1f(n ,32);glutSolidTorus(0.3,1.0,30,30);cgGLDisableProfile(myCgVertexProfile);cgGLDisableProfile(myCgFragmentProfile);angle += 0.5;if(angle >=360) angle = 0.0f;glutSwapBuffers();glutPostRedisplay();
}
phong model结果1: phong shading结果2: