(1)简单漫反射光
3D图形利用最普遍的光线类型就是漫射光。漫反射光是1种经过平面反射的定向光,其强度和光线在表面的入射角成正比。
要肯定1个指定定点上的光线的强度,我们需要两个向量。第1个向量就是光源的方向。某些光源技术只是提供1个指向光源的向量,我们称之为定向光。对所有顶点来讲指向光源的,都是同1个向量。
注意:这类方式在光源距离被照亮物体非常远的情况下是非常适用的。如果照明朝码提供的是光源的位置,那末我们必须在着色器中使用经过变换的视觉坐标光源位置减去顶点位置。从而肯定指向光源的向量。
表面法线
我们在漫反射光源中需要的第2个向量是表面法线。经过某个假想平面上方的顶点,并与这个平面成直角的1条线段,这条线就成为法向量。
注意,每一个顶点都要制定1个法向量。但是我们其实不希望每一个法向量都和多边形的表面精确垂直。我们可以将这些表面近似看成是平的,但是结果会得到1个锯齿状或多面的表面,我们可以通过“调剂”表面法线使平面多边形表面平滑,从而得到平滑表面的错觉。
顶点照明
顶点上光的强度通过接收到光源的向量和表面法线的向量点乘积来计算。两个向量也需要是单位长度,而点乘积将会返回1个+⑴之间的值。当表面法线和光照向量指向同1个方向的时候,将会出现1个1值的点乘积。当两个向量指向相反的方向的时候,则会返回⑴,。当两个向量相互成90度的时候,返回的点乘积为0,。这个+⑴之间的值实际上是这两个向量之间夹角的余弦值。
正值意味着光线落在顶点上,这个值越接近1,则光照效果越强,越接近0,那末光照效果越弱。
我们可以用点乘积的值和顶点的1个色彩值相乘,得到1个基于顶点光线强度的光照色彩值。在顶点之间对这些色彩值进行平滑的着色,有时候被称作顶点照明(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;
//注意:典型情况下,表面法线作为1个顶点属性提交,表面法线必须
//进行旋转从而使得他的方向在视觉空间以内。我们常常传递1个法向矩阵
//这个值只包括模型视图矩阵的旋转份量。
//可以通过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
//任务:设置背景色彩
//加载渲染框架、加载渲染器程序代码、得到统1值的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);
// 创造1个球体
gltMakeSphere(sphereBatch, 1.0f, 26, 13);
//加载渲染框架,同时加载渲染器代码,并且将两个基本点的属性传入到顶点着色器中。
diffuseLightShader = shaderManager.LoadShaderPairWithAttributes("DiffuseLight.vp", "DiffuseLight.fp", 2, GLT_ATTRIBUTE_VERTEX, "vVertex",
GLT_ATTRIBUTE_NORMAL, "vNormal");
//对统1值的取值,得到对应的统1值的位置
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程序、对对应的统1值矩阵进行赋值
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;
}
主程序,对漫反射渲染器的使用和操作。
注意1个常常出现的毛病:在设置着色器统1值以后,和对几何图形进行渲染之前做进1步修改。注意:glUniform函数其实不会将1个对这些数据的援用复制到着色器。这个函数会将实际数据复制到着色器中。
(3)ADS光照模型
ADS代表:Ambient环境光、Diffuse漫反射、Specular镜面光。它遵守1个简单的原则,物体有3种材质属性,环境光反射,漫反射,镜面反射。这些属性都是分配的色彩值,更明亮的色彩代表更高的反射量。光源也有这3种相同的属性。这些属性都是分配的色彩值,更明亮的色彩代表更高的反射量。光源也有这3种相同的属性。一样是分配的色彩值,表示光源的亮度。终究的顶点色彩就是光照和材质的这3个属性相互影响的总和。
环境光
环境光不来自任何特定的方向,来自某个光源,但是光线却是在房间或场景当中4处反射,没有方向可言。
由于环境光所照耀的物体在所有方向的表面都是均匀照亮的。我们可以把环境光看成是利用到每一个光源的全局“照明”因子。这中光照份量确切非常接近环境中源自光源的散射光。
由环境光所照耀的物体在所有方向的表面都是均匀照亮的,我们可以把环境光看成利用到每一个光源的全局“照明”因子。这类光照份量确切非常接近环境中源自光源的散射光。
为了计算环境光源对终究顶点色彩的影响,环境光材质的性质由环境光的值来度量,这个值产生对环境色彩的影响。在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;
镜面光
镜面光具有非常强的方向性,但是他的反射角度非常锋利,仅仅沿着特定的方向反射。高强度的镜面光趋向于在它所照耀的表面上构成1个亮点,成为镜面亮点。由于高度方向性本质,根据视察者的位置不同,镜面光乃至有可能看不到。聚光灯和太阳都是产生很强的镜面光的例子,不过他们固然必须是照耀在1个光亮的物体。
首先我们必须找到被表面法线反射的向量和反向的光线向量。随后这两个向量的点乘积将会取“反光度”次幂。反光度越大,镜面反射高光越小。
利用GLSL所实现的功能以下:
uniform vec3 vSpecularMaterial;
uniform vec3 vSpecularLight;
float shininess=128f;
vec3 vReflection= reflect(-vLightDir, vEyeNormal);
fSpec = pow(EyeReflectionAngle, shininess);
vec3 vSpecularColor= vSpecularLight*vSpecularMaaterial*fSpec;
注意,和其他参数1样,反光度参数也能够是统1值,最高的镜面指数设置为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);
// 点乘得到1个漫反射强度
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着色
3角形之间的不连续会致使上面所渲染的球体会产生亮线星光的效果。这类不连续则是由于色彩值在空间中进行的是线性插值的缘由。亮线就是两个独立3角形之间的缝隙。
解决这类效果的1种方法叫做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);
}
}
如上所示为片元着色器,大量的操作从顶点着色器转移到片元着色器当中,但是代码几近完全没有改变,仅仅转移了计算位置。这样算是高质量渲染了,会影响性能。
注意:1个着色器性能优化的常规原则:将尽量多的处理进程移出片元着色器而放入顶点着色器。
上一篇 Lua函数的多个返回值
下一篇 Xcode8证书错误