OpenGL蓝宝书源码学习(十八)第六章——ADS光照模型

本文解析了ADS光照模型的原理及两种实现方式:Gouraud着色和Phong着色。通过对比两种着色器源码,揭示了它们如何在OpenGL中实现高质量的光照效果。

ADS光照着色器渲染图形(球形),ADSGoudraud和ADSPhong光照模型,这两个模型的着色程序有所不用,所以解析相同的Client程序两源码,分别解析两种不同的Server着色程序源码,并且分别贴出不同的.vp源码。

// DiffuseLight.cpp
// OpenGL SuperBible
// Demonstrates simple diffuse lighting
// Program by Richard S. Wright Jr.

#include 	// OpenGL toolkit
#include 
#include 
#include 
#include 
#include 

#include 
#ifdef __APPLE__
#include 
#else
#define FREEGLUT_STATIC
#include 
#endif


GLFrame             viewFrame;
GLFrustum           viewFrustum;
GLTriangleBatch     sphereBatch;
GLMatrixStack       modelViewMatrix;
GLMatrixStack       projectionMatrix;
GLGeometryTransform transformPipeline;
GLShaderManager     shaderManager;

GLuint	ADSLightShader;		// The diffuse light shader
GLint	locAmbient;			// The location of the ambient color
GLint   locDiffuse;			// The location of the diffuse color
GLint   locSpecular;		// The location of the specular 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


// This function does any needed initialization on the rendering
// context. 
void SetupRC(void)
	{
	// Background
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f );

	glEnable(GL_DEPTH_TEST);
	glEnable(GL_CULL_FACE);

    shaderManager.InitializeStockShaders();
    viewFrame.MoveForward(4.0f);

    // Make the sphere
    gltMakeSphere(sphereBatch, 1.0f, 26, 13);

	ADSLightShader = shaderManager.LoadShaderPairWithAttributes("ADSGouraud.vp", "ADSGouraud.fp", 2, GLT_ATTRIBUTE_VERTEX, "vVertex",
			GLT_ATTRIBUTE_NORMAL, "vNormal");

	locAmbient = glGetUniformLocation(ADSLightShader, "ambientColor");
	locDiffuse = glGetUniformLocation(ADSLightShader, "diffuseColor");
	locSpecular = glGetUniformLocation(ADSLightShader, "specularColor");
	locLight = glGetUniformLocation(ADSLightShader, "vLightPosition");
	locMVP = glGetUniformLocation(ADSLightShader, "mvpMatrix");
	locMV  = glGetUniformLocation(ADSLightShader, "mvMatrix");
	locNM  = glGetUniformLocation(ADSLightShader, "normalMatrix");
	}

// Cleanup
void ShutdownRC(void)
{

}


// Called to draw scene
void RenderScene(void)
	{
	static CStopWatch rotTimer;

	// Clear the window and the depth buffer
	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 vAmbientColor[] = { 0.1f, 0.1f, 0.1f, 1.0f };
		GLfloat vDiffuseColor[] = { 0.0f, 0.0f, 1.0f, 1.0f };
		GLfloat vSpecularColor[] = { 1.0f, 1.0f, 1.0f, 1.0f };

		glUseProgram(ADSLightShader);
		glUniform4fv(locAmbient, 1, vAmbientColor);
		glUniform4fv(locDiffuse, 1, vDiffuseColor);
		glUniform4fv(locSpecular, 1, vSpecularColor);
		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("ADS Lighting, Gouraud Shading");
    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;
    }

                                 

一、ADS光照模型基础

ADS光照模型是一种最常见的光照模型。ADS代表环境光(Ambient)、漫射光(Diffuse)、镜面光(Specular)。他遵循一个简单的原则,即物体有3种材质属性,环境光反射、漫反射和镜面反射(计算机图形学书也又讲)。这些属性都是分配的颜色值,更明亮的颜色代表更高的反射量。光源也有3种相同的属性,同样也是分配颜色值,表示光源的亮度。这样,最终的顶点颜色值就是光照和材质的这3个属性互相影响的总和。

1、环境光

环境光并不来自任何特定的方向。我们可以把环境光看成是应用到每个光源的全局“照明”因子。这种光照分量确实非常接近环境中源自光源的散射光。环境光是附着给物体的,所照射的物体在所有方向的表面都是均匀照亮的,会产生反射,如果场景里没有设置环境光,那么物体的才是看起来只有光泽。(略微难理解)


2、漫射光

漫射光是光源的定向分量,前面有学习过。在ADS光照模型下,漫反射材质和光照值相乘,光照值是由表面法线和光照向量的点乘积进行缩放。


在源码解析学习的时候在详细介绍。

3、镜面光

镜面光具有很强的方向性,它的反射角度很锐利,只沿一个特定的方向反射。高强度的镜面光趋向于在它所照射的表面上形成以个亮点,成为镜面亮点。由于它的高度方向性本质,根据观察者位置的不同,镜面光甚至有可能看不到。

计算:必须找到被表面法线反射的向量和反向的光线向量。随后这两个向量的点乘积将取“反光度”(shininess)次幂。反光度数值越大,结果得到镜面反射的高度区越小。



反光强度(shininess)也可以是统一值。传统上,最高的镜面指数被设置为128,大于这个数字的值其效果将逐渐减弱。

二、ADS源码解析

源程序仅仅为环境光、漫射光和镜面材料传递了一个单独的颜色值,而并没有传递独立的材质和光照颜色/强度。我们可以将它看作将材质左乘光照颜色。因为逐个顶点地计算了光照值,然后为了进行着色而在顶点之间使用了颜色空间插值。


1、Client程序源码解析

1.全局变量

GLFrame             viewFrame;     //场景角色对象     
GLFrustum           viewFrustum;//视锥体(平截头体)
GLTriangleBatch     sphereBatch;//球形图元批次
GLMatrixStack       modelViewMatrix;//模型视图矩阵堆栈
GLMatrixStack       projectionMatrix;//投影矩阵堆栈
GLGeometryTransform transformPipeline;//管线管理对象

GLShaderManager     shaderManager;//着色器管理对象


GLuintADSLightShader;// ADS光照模型的shader
GLintlocAmbient;// 环境光矩阵坐标
GLint   locDiffuse;// 漫射光矩阵坐标
GLint   locSpecular;// 镜面光矩阵坐标
GLintlocLight;// 视觉坐标系中的光源位置坐标
GLintlocMVP;// 模型视图投影矩阵的坐标
GLintlocMV;// 模型视图矩阵的坐标
GLintlocNM;// 法线坐标

2.函数解析

1)void SetupRC(void)

.......

//加载着色器

ADSLightShader = shaderManager.LoadShaderPairWithAttributes("ADSGouraud.vp", "ADSGouraud.fp", 2, GLT_ATTRIBUTE_VERTEX, "vVertex",GLT_ATTRIBUTE_NORMAL, "vNormal");

//获取到着色器程序中的统一值

locAmbient = glGetUniformLocation(ADSLightShader, "ambientColor");
locDiffuse = glGetUniformLocation(ADSLightShader, "diffuseColor");
locSpecular = glGetUniformLocation(ADSLightShader, "specularColor");
locLight = glGetUniformLocation(ADSLightShader, "vLightPosition");
locMVP = glGetUniformLocation(ADSLightShader, "mvpMatrix");
locMV  = glGetUniformLocation(ADSLightShader, "mvMatrix");
locNM  = glGetUniformLocation(ADSLightShader, "normalMatrix");

2)void RenderScene(void)

.........

//定义需要赋值的向量值

GLfloat vEyeLight[] = { -100.0f, 100.0f, 100.0f };
GLfloat vAmbientColor[] = { 0.1f, 0.1f, 0.1f, 1.0f };
GLfloat vDiffuseColor[] = { 0.0f, 0.0f, 1.0f, 1.0f };
GLfloat vSpecularColor[] = { 1.0f, 1.0f, 1.0f, 1.0f };


glUseProgram(ADSLightShader); //使用加载的着色器
glUniform4fv(locAmbient, 1, vAmbientColor);//设置着色器中的环境光统一值
glUniform4fv(locDiffuse, 1, vDiffuseColor);//设置着色器中的漫射光统一值
glUniform4fv(locSpecular, 1, vSpecularColor);//设置着色器中的镜面光统一值
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());//设置着色器中的法向量统一值

.....

注意:这里略去了前面学习过的重复的代码的解析,只是解析了所用到的ADS光照模型所用到的统一值。ADSGouraud和ADSPhong光照模型的Client程序是一样的。

2、Server程序源码解析

// ADS Point lighting Shader
// Vertex Shader
// Richard S. Wright Jr.
// OpenGL SuperBible
#version 130

// Incoming per vertex... position and normal
in vec4 vVertex;
in vec3 vNormal;

// Set per batch
uniform vec4    ambientColor;
uniform vec4    diffuseColor;	
uniform vec4    specularColor;

uniform vec3	vLightPosition;
uniform mat4	mvpMatrix;
uniform mat4	mvMatrix;
uniform mat3	normalMatrix;

// Color to fragment program
smooth out vec4 vVaryingColor;

void main(void) 
    { 
    // Get surface normal in eye coordinates
    vec3 vEyeNormal = normalMatrix * vNormal;

    // Get vertex position in eye coordinates
    vec4 vPosition4 = mvMatrix * vVertex;
    vec3 vPosition3 = vPosition4.xyz / vPosition4.w;

    // Get vector to light source
    vec3 vLightDir = normalize(vLightPosition - vPosition3);

    // Dot product gives us diffuse intensity
    float diff = max(0.0, dot(vEyeNormal, vLightDir));

    // Multiply intensity by diffuse color, force alpha to 1.0
    vVaryingColor = diff * diffuseColor;

    // Add in ambient light
    vVaryingColor += ambientColor;


    // Specular Light
    vec3 vReflection = normalize(reflect(-vLightDir, vEyeNormal));
    float spec = max(0.0, dot(vEyeNormal, vReflection));
    if(diff != 0) {
        float fSpec = pow(spec, 128.0);
        vVaryingColor.rgb += vec3(fSpec, fSpec, fSpec);
	}


    // Don't forget to transform the geometry!
    gl_Position = mvpMatrix * vVertex;
    }// ADS Point lighting Shader
// Fragment Shader
// Richard S. Wright Jr.
// OpenGL SuperBible
#version 130

out vec4 vFragColor;
smooth in vec4 vVaryingColor;

void main(void)
   { 
   vFragColor = vVaryingColor;
   }

ADSGoudraud.vp

1)变量声明

in vec4 vVertex; //顶点位置属性值
in vec3 vNormal;//顶点的法向量坐标值


uniform vec4    ambientColor;//环境光颜色统一值
uniform vec4    diffuseColor; //漫射光颜色统一值
uniform vec4    specularColor;  //镜面光颜色统一值

uniform vec3vLightPosition;  //光源的位置统一值
uniform mat4mvpMatrix; //模型视图投影矩阵统一值
uniform mat4mvMatrix; //模型视图矩阵统一值

uniform mat3normalMatrix; //法向量统一值


smooth out vec4 vVaryingColor;  //将要输出给片段着色器的颜色输出值

2)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));

//用强度乘以漫反射颜色,将alpha值设为1.0

vVaryingColor = diff * diffuseColor;

//添加漫射光

vVaryingColor += ambientColor;

//镜面光

//把朝向光源坐标的光源方向作为入射向量,即-vLightDir。再求入射向量的反射向量

 vec3 vReflection = normalize(reflect(-vLightDir, vEyeNormal));

//求镜面光强,用反射向量与表面方向向量点乘积,再去“反光度(128)”次幂。
    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;  //坐标转换到视觉坐标系

ADSGoudraud.fp

out vec4 vFragColor;
smooth in vec4 vVaryingColor;


void main(void)
   { 
   vFragColor = vVaryingColor;  
//将顶点着色器的输出颜色输入到片段着色器中
   }

ADSGouraud光照模型的源码解析很简单。它有一个缺点,在活动的示例程序中,旋转这个球体时显示一种特有的闪烁有些烦人,并且一般来说是不可取的。这些亮线实际上是相互独立的不连续造成的。这种不连续则是由于颜色值在空间中进行的线性插值而导致的。


在Phong着色时,并不在顶点之间进行颜色插值,而是在顶点之间进行表面法线插值。在着色器程序中再进行颜色的赋值。

empty// ADS Point lighting Shader
// Fragment Shader
// Richard S. Wright Jr.
// OpenGL SuperBible
#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)
    { 
    // Dot product gives us diffuse intensity
    float diff = max(0.0, dot(normalize(vVaryingNormal), normalize(vVaryingLightDir)));

    // Multiply intensity by diffuse color, force alpha to 1.0
    vFragColor = diff * diffuseColor;

    // Add in ambient light
    vFragColor += ambientColor;


    // Specular Light
    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);
        }
    }
    


ADSPhong.vp

1)变量声明

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;//将要输出给片段着色器的光源的方向输出值

2)void main()

{

vVaryingNormal = normalMatrix * vNormal; //得到视坐标系中的法线的向量

//得到视坐标系中顶点的位置坐标

 vec4 vPosition4 = mvMatrix * vVertex;

    vec3 vPosition3 = vPosition4.xyz / vPosition4.w;

//获取光源的向量。即光源的位置坐标减去顶点位置坐标在进行归一化

vVaryingLightDir = normalize(vLightPosition - vPosition3);

//转换几何图形的坐标系

gl_Position = mvpMatrix * vVertex;

}

ADSPhong.fp

1)变量声明

out vec4 vFragColor;

uniform vec4    ambientColor;//环境光颜色统一值
uniform vec4    diffuseColor;   //漫射光颜色统一值
uniform vec4    specularColor;//镜面光颜色统一值

smooth in vec3 vVaryingNormal;//将顶点着色器程序的法向量输出值复制给片段着色器的法向量输入值
smooth in vec3 vVaryingLightDir;//将顶点着色器程序的光源向量输出值复制给片段着色器的光源向量输入值

2)void main()

{

//从点乘积得到漫反射的强度。用法线向量点乘光源向量

float diff = max(0.0, dot(normalize(vVaryingNormal), normalize(vVaryingLightDir)));

//用强度乘以漫反射颜色,将alpha值设为1.0

vFragColor = diff * diffuseColor;

//输出颜色添加漫射光

vFragColor += ambientColor;

//求镜面光强,用反射向量与表面方向向量点乘积,再去“反光度(128)”次幂。

 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);//把输出颜色加上镜面光强
        }
    }

}

三、小结

这次的源码解析,简单地学习了解了模拟光线进行的光照模型渲染,分别学习了两种光照模型,其中会经常使用类似Phong着色这样的高质量渲染选项。这些渲染的视觉质量非常高,而性能则常常会受到一些影响。在性能较低的硬件上,或者在已经选择了很多其他开销很大选项的场景中,Gouraud着色仍然是最好的选择。

一个着色器性能优化的常规原则是,将尽可能多的处理过程移除片段着色器而放入顶点着色器。


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值