(1)简单漫反射光
3D图形应用最普遍的光线类型就是漫射光。漫反射光是一种经过平面反射的定向光,其强度和光线在表面的入射角成正比。
要确定一个指定定点上的光线的强度,我们需要两个向量。第一个向量就是光源的方向。某些光源技术只是提供一个指向光源的向量,我们称之为定向光。对于所有顶点来说指向光源的,都是同一个向量。
注意:这种方式在光源距离被照亮物体非常远的情况下是非常适用的。如果照明代码提供的是光源的位置,那么我们必须在着色器中使用经过变换的视觉坐标光源位置减去顶点位置。从而确定指向光源的向量。
表面法线
我们在漫反射光源中需要的第二个向量是表面法线。经过某个假想平面上方的顶点,并与这个平面成直角的一条线段,这条线就成为法向量。
注意,每个顶点都要制定一个法向量。但是我们并不希望每个法向量都和多边形的表面精确垂直。我们可以将这些表面近似看成是平的,但是结果会得到一个锯齿状或者多面的表面,我们可以通过“调整”表面法线使平面多边形表面平滑,从而得到平滑表面的错觉。
顶点照明
顶点上光的强度通过接收到光源的向量和表面法线的向量点乘积来计算。两个向量也需要是单位长度,而点乘积将会返回一个+-1之间的值。当表面法线和光照向量指向同一个方向的时候,将会出现一个1值的点乘积。当两个向量指向相反的方向的时候,则会返回-1,。当两个向量互相成90度的时候,返回的点乘积为0,。这个+-1之间的值实际上是这两个向量之间夹角的余弦值。
正值意味着光线落在顶点上,这个值越接近1,则光照效果越强,越接近0,那么光照效果越弱。
我们可以用点乘积的值和顶点的一个颜色值相乘,得到一个基于顶点光线强度的光照颜色值。在顶点之间对这些颜色值进行平滑的着色,有时候被称作顶点照明(vertex lighting)或者背景着色(Gouraud shading)。在GLSL中,点乘积的部分比较简单。
float intensity=dot(vSurfaceNormal, vLightDirection);
(2)点光源漫反射着色器
// Simple Diffuse lighting Shader
// Vertex Shader
// Richard S. Wright Jr.
// OpenGL SuperBible
#version 130
//首先是顶点渲染器
// 输入顶点和法向量
in vec4 vVertex;
in vec3 vNormal;
//注意:典型情况下,表面法线作为一个顶点属性提交,表面法线必须
//进行旋转从而使得他的方向在视觉空间之内。我们经常传递一个法向矩阵
//这个值只包含模型视图矩阵的旋转分量。
//可以通过GLTransformation当中的GetNormalMatrix函数返回这个值。
// 设置每个批次
uniform vec4 diffuseColor; //球体的颜色
uniform vec3 vLightPosition;//光源位置的视觉坐标
uniform mat4 mvpMatrix;//模型视图投影矩阵
uniform mat4 mvMatrix;//模型视图矩阵
uniform mat3 normalMatrix;//
// 片段程序所需要的颜色,经过平滑着色的颜色值。
smooth out vec4 vVaryingColor;
void main(void)
{
// 得到视觉坐标系的表面法线向量
vec3 vEyeNormal = normalMatrix * vNormal;
// 得到视觉坐标系的顶点坐标
vec4 vPosition4 = mvMatrix * vVertex;
vec3 vPosition3 = vPosition4.xyz / vPosition4.w;
// 得到光源的向量
vec3 vLightDir = normalize(vLightPosition - vPosition3);
// 得到点乘积所得到的漫反射强度
float diff = max(0.0, dot(vEyeNormal, vLightDir));
// 强度乘以漫反射颜色,得到漫反射光线颜色
vVaryingColor.rgb = diff * diffuseColor.rgb;
vVaryingColor.a = diffuseColor.a;
// 最后对多边形进行变换,让模型透视投影矩阵对当前
//顶点进行变换。
gl_Position = mvpMatrix * vVertex;
}
点渲染shader程序,在主程序工程文件下,以vp格式的文件存在。
#version 130
out vec4 vFragColor;
smooth in vec4 vVaryingColor;
void main(void)
{
//将点着色器当中的输出vVaryingColor直接分配给输出
//片段颜色。
vFragColor = vVaryingColor;
}
片段着色器如上,只需要对其的片段颜色进行赋值就可以了。
#pragma comment(lib,"GLTools.lib")
#include <GLTools.h> // OpenGL toolkit
#include <GLMatrixStack.h>
#include <GLFrame.h>
#include <GLFrustum.h>
#include <GLGeometryTransform.h>
#include <StopWatch.h>
#include <GL/glut.h>
GLFrame viewFrame;
GLFrustum viewFrustum;
GLTriangleBatch sphereBatch;
GLMatrixStack modelViewMatrix;
GLMatrixStack projectionMatrix;
GLGeometryTransform transformPipeline;
GLShaderManager shaderManager;
GLuint diffuseLightShader; // The diffuse light shader
GLint locColor; // The location of the diffuse color
GLint locLight; // The location of the Light in eye coordinates
GLint locMVP; // The location of the ModelViewProjection matrix uniform
GLint locMV; // The location of the ModelView matrix uniform
GLint locNM; // The location of the Normal matrix uniform
//任务:设置背景颜色
//加载渲染框架、加载渲染器程序代码、得到统一值的location
void SetupRC(void)
{
// 背景颜色的设置
glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
//遮挡剔除以及深度测试
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
shaderManager.InitializeStockShaders();
viewFrame.MoveForward(4.0f);
// 创造一个球体
gltMakeSphere(sphereBatch, 1.0f, 26, 13);
//加载渲染框架,同时加载渲染器代码,并且将两个基本点的属性传入到顶点着色器中。
diffuseLightShader = shaderManager.LoadShaderPairWithAttributes("DiffuseLight.vp", "DiffuseLight.fp", 2, GLT_ATTRIBUTE_VERTEX, "vVertex",
GLT_ATTRIBUTE_NORMAL, "vNormal");
//对统一值的取值,得到对应的统一值的位置
locColor = glGetUniformLocation(diffuseLightShader, "diffuseColor");
locLight = glGetUniformLocation(diffuseLightShader, "vLightPosition");
locMVP = glGetUniformLocation(diffuseLightShader, "mvpMatrix");
locMV = glGetUniformLocation(diffuseLightShader, "mvMatrix");
locNM = glGetUniformLocation(diffuseLightShader, "normalMatrix");
}
// Cleanup
void ShutdownRC(void)
{
}
// 任务:硬编码需要传入渲染器的两个值、激活shader程序、对对应的统一值矩阵进行赋值
void RenderScene(void)
{
static CStopWatch rotTimer;
// 清除当前颜色缓冲区以及深度缓冲区的数据
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//对模型视图矩阵压入视图帧
modelViewMatrix.PushMatrix(viewFrame);
modelViewMatrix.Rotate(rotTimer.GetElapsedSeconds() * 10.0f, 0.0f, 1.0f, 0.0f);
//硬编码需要传入渲染器程序当中的值
GLfloat vEyeLight[] = { -100.0f, 100.0f, 100.0f };
GLfloat vDiffuseColor[] = { 0.0f, 0.0f, 1.0f, 1.0f };
//激活shader程序
glUseProgram(diffuseLightShader);
//对对应的属性进行赋值
glUniform4fv(locColor, 1, vDiffuseColor);
glUniform3fv(locLight, 1, vEyeLight);
glUniformMatrix4fv(locMVP, 1, GL_FALSE, transformPipeline.GetModelViewProjectionMatrix());
glUniformMatrix4fv(locMV, 1, GL_FALSE, transformPipeline.GetModelViewMatrix());
glUniformMatrix3fv(locNM, 1, GL_FALSE, transformPipeline.GetNormalMatrix());
sphereBatch.Draw();
modelViewMatrix.PopMatrix();
glutSwapBuffers();
glutPostRedisplay();
}
void ChangeSize(int w, int h)
{
// Prevent a divide by zero
if (h == 0)
h = 1;
// Set Viewport to window dimensions
glViewport(0, 0, w, h);
//设置透视模式
viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 100.0f);
//加载透视投影矩阵
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
//渲染管线的设置矩阵堆栈,加载模型视图矩阵以及透视矩阵
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}
///////////////////////////////////////////////////////////////////////////////
// Main entry point for GLUT based programs
int main(int argc, char* argv[])
{
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
glutInitWindowSize(800, 600);
glutCreateWindow("Simple Diffuse Lighting");
glutReshapeFunc(ChangeSize);
glutDisplayFunc(RenderScene);
GLenum err = glewInit();
if (GLEW_OK != err) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}
SetupRC();
glutMainLoop();
ShutdownRC();
return 0;
}
主程序,对于漫反射渲染器的使用和操作。
注意一个经常出现的错误:在设置着色器统一值之后,和对几何图形进行渲染之前做进一步修改。注意:glUniform函数并不会将一个对这些数据的引用复制到着色器。这个函数会将实际数据复制到着色器中。
(3)ADS光照模型
ADS代表:Ambient环境光、Diffuse漫反射、Specular镜面光。它遵循一个简单的原则,物体有三种材质属性,环境光反射,漫反射,镜面反射。这些属性都是分配的颜色值,更明亮的颜色代表更高的反射量。光源也有这三种相同的属性。这些属性都是分配的颜色值,更明亮的颜色代表更高的反射量。光源也有这三种相同的属性。同样是分配的颜色值,表示光源的亮度。最终的顶点颜色就是光照和材质的这三个属性互相影响的总和。
环境光
环境光不来自任何特定的方向,来自某个光源,但是光线却是在房间或者场景当中四处反射,没有方向可言。
由于环境光所照射的物体在所有方向的表面都是均匀照亮的。我们可以把环境光看成是应用到每个光源的全局“照明”因子。这中光照分量确实非常接近环境中源自光源的散射光。
由环境光所照射的物体在所有方向的表面都是均匀照亮的,我们可以把环境光看成应用到每个光源的全局“照明”因子。这种光照分量确实非常接近环境中源自光源的散射光。
为了计算环境光源对最终顶点颜色的影响,环境光材质的性质由环境光的值来度量,这个值产生对环境颜色的影响。在GLSL着色器当中,环境光的具体实现如下:
uniform vec3 vAmbientMaterial;
uniform vec3 vAmbientLight;
vec3 vAmbientColor = vAmbientMaterial * vAmbientLight;
漫射光
漫射光是光源的定向分量,也是我们前面的示例光照着色器的主题。在着色器当中的代码如下:
uniform vec3 vDiffuseMaterial;
uniform vec3 vDiffuseLight;
float fDotProduct = max(0.0, dot(vNormal, vLightDir));
vec3 vDiffuseColor = vDiffuseMaaterial * vDiffuseLight * fDotProduct;
镜面光
镜面光具有非常强的方向性,但是他的反射角度非常锐利,仅仅沿着特定的方向反射。高强度的镜面光趋向于在它所照射的表面上形成一个亮点,成为镜面亮点。由于高度方向性本质,根据观察者的位置不同,镜面光甚至有可能看不到。聚光灯和太阳都是产生很强的镜面光的例子,不过他们当然必须是照射在一个光亮的物体。
首先我们必须找到被表面法线反射的向量和反向的光线向量。随后这两个向量的点乘积将会取“反光度”次幂。反光度越大,镜面反射高光越小。
利用GLSL所实现的功能如下:
uniform vec3 vSpecularMaterial;
uniform vec3 vSpecularLight;
float shininess=128f;
vec3 vReflection= reflect(-vLightDir, vEyeNormal);
fSpec = pow(EyeReflectionAngle, shininess);
vec3 vSpecularColor= vSpecularLight*vSpecularMaaterial*fSpec;
注意,和其他参数一样,反光度参数也可以是统一值,最高的镜面指数设置为128,大于这个数字,这个值将会逐渐减弱。
注意,顶点最终的颜色可以像下面这样进行计算:
vVertexColor=vAmbientColor+vDiffuseColor+vSpecularColor;
// ADS Point lighting Shader
// Vertex Shader
// Richard S. Wright Jr.
// OpenGL SuperBible
#version 130
// 输入的数据,仍然是点坐标以及点的法向量
in vec4 vVertex;
in vec3 vNormal;
// 设置批次
uniform vec4 ambientColor;
uniform vec4 diffuseColor;
uniform vec4 specularColor;
uniform vec3 vLightPosition;
uniform mat4 mvpMatrix;
uniform mat4 mvMatrix;
uniform mat3 normalMatrix;
// 最终传递给片元渲染器的color
smooth out vec4 vVaryingColor;
void main(void)
{
// 得到视觉坐标系的法向量
vec3 vEyeNormal = normalMatrix * vNormal;
// 得到视觉坐标系的坐标
vec4 vPosition4 = mvMatrix * vVertex;
vec3 vPosition3 = vPosition4.xyz / vPosition4.w;
// 得到光源向量
vec3 vLightDir = normalize(vLightPosition - vPosition3);
// 点乘得到一个漫反射强度
float diff = max(0.0, dot(vEyeNormal, vLightDir));
// 强度乘以漫反射颜色
vVaryingColor = diff * diffuseColor;
// 在此基础上加上环境光
vVaryingColor += ambientColor;
// 镜面光
//得到光源的反射向量
vec3 vReflection = normalize(reflect(-vLightDir, vEyeNormal));
//得到反射角的cos值
float spec = max(0.0, dot(vEyeNormal, vReflection));
if(diff != 0) {
float fSpec = pow(spec, 128.0);
vVaryingColor.rgb += vec3(fSpec, fSpec, fSpec);
}
//最终应用几何变换
gl_Position = mvpMatrix * vVertex;
}
ADS的顶点渲染器代码如上。
#version 130
out vec4 vFragColor;
smooth in vec4 vVaryingColor;
void main(void)
{
vFragColor = vVaryingColor;
}
片元着色器如上。
(4)Phong着色
三角形之间的不连续会导致上面所渲染的球体会产生亮线星光的效果。这种不连续则是由于颜色值在空间中进行的是线性插值的原因。亮线就是两个独立三角形之间的缝隙。
解决这种效果的一种方法叫做Phong着色。我们不在顶点之间进行颜色插值,而是在顶点之间进行表面法线插值,这就是所谓的Phong着色。
Phong着色会导致片元着色器中作的工作大大提高,因为片段着色器的执行次数将会比顶点程序的执行次数多很多。
// ADS Point lighting Shader
// Vertex Shader
// Richard S. Wright Jr.
// OpenGL SuperBible
#version 130
//输入每个顶点以及每个顶点的法线
in vec4 vVertex;
in vec3 vNormal;
uniform mat4 mvpMatrix;
uniform mat4 mvMatrix;
uniform mat3 normalMatrix;
uniform vec3 vLightPosition;
// 片段程序颜色
smooth out vec3 vVaryingNormal;
smooth out vec3 vVaryingLightDir;
void main(void)
{
// 获取表面法线的视觉坐标
vVaryingNormal = normalMatrix * vNormal;
// 获取顶点位置的视觉坐标
vec4 vPosition4 = mvMatrix * vVertex;
vec3 vPosition3 = vPosition4.xyz / vPosition4.w;
// 获取到光源的方向
vVaryingLightDir = normalize(vLightPosition - vPosition3);
// 最后对多边形进行变换
gl_Position = mvpMatrix * vVertex;
}
Phong顶点着色器,把具体的操作几乎都转移到片元着色器上了。
#version 130
out vec4 vFragColor;
uniform vec4 ambientColor;
uniform vec4 diffuseColor;
uniform vec4 specularColor;
smooth in vec3 vVaryingNormal;
smooth in vec3 vVaryingLightDir;
void main(void)
{
// 点乘得到漫反射的强度
float diff = max(0.0, dot(normalize(vVaryingNormal), normalize(vVaryingLightDir)));
// 用强度去乘以漫反射颜色将颜色设置为1.0
vFragColor = diff * diffuseColor;
// 增加环境光
vFragColor += ambientColor;
// 镜面光
vec3 vReflection = normalize(reflect(-normalize(vVaryingLightDir), normalize(vVaryingNormal)));
float spec = max(0.0, dot(normalize(vVaryingNormal), vReflection));
if(diff != 0) {
float fSpec = pow(spec, 128.0);
vFragColor.rgb += vec3(fSpec, fSpec, fSpec);
}
}
如上所示为片元着色器,大量的操作从顶点着色器转移到片元着色器当中,但是代码几乎完全没有改变,仅仅转移了计算位置。这样算是高质量渲染了,会影响性能。
注意:一个着色器性能优化的常规原则:将尽可能多的处理过程移出片元着色器而放入顶点着色器。