opengl 教程(17) 环境光

http://www.cnblogs.com/mikewolf2002/archive/2013/01/20/2868859.html


原帖地址:http://ogldev.atspace.co.uk/www/tutorial17/tutorial17.html

      在3D真实感图形学中,光照是很重要的技术。从物理上讲,一束光是由很多细小的粒子“光子”组成,这些光子在空气中传输,在物体的表面折射,反射,最终进入我的视觉系统,形成了我们眼中看到的真实世界。在编程中,我们不可能模拟所有光子的行为,所以如何对光照进行建模,模拟出它的真实效果,是计算机图形学中一个永恒的话题

      在各种paper中,人们已经提出了许多种光照模型算法,随着图形学的发展以及GPU计算能力提高,还会出现更多的光照算法。在本篇教程中,我们只讨论最基本的光照模型,它编程相对容易,它的效果作用于场景中的所有物体。

      最基本的光照模型称作 环境光/漫反射光/高光(Ambient/Diffuse/Specular)光照模型:

      环境光就是在没有太阳的情况下,你周围的光照情况,它模拟的是太阳光照射地面后,在不同的位置被反射,折射,最后混合在一起的一种光线,环境光没有方向,没有起点,它是一种均匀的光照效果,即使在阴影中,也有环境光存在。

     漫反射光是光照在物体表面被反射,最终进入我们眼睛的效果,漫反射光和光线的方向,以及物体表面的法线有关,比如一个物体有2面,当漫反射光只照在一个面时,这个面是亮的,而另一个面是暗的。太阳光看似没有方向,其实是有的,比如一个大楼,太阳照在它的上面,它的投影方向就是太阳光的方向。

     高光是物体本身的一种属性,当光作用于物体时,物体上某个部分可能特别亮,这就是高光,高光会随着我们的视点移动而不断改变位置。金属物体一般都有这种高光属性,比如金属球,汽车表面等等。计算高光必须考虑光线方向,光照物体的法向,以及视线的方向。

    在3D应用程序中,我们并不直接设定环境光,漫反射光和高光,而是使用光源的概念,比如太阳,电灯,蜡烛,火把等等,这些光源都是环境光,漫反射光,高光不同强度的绑定组合。

在下面的教程中,我们实现几种不同光源,通过这些光源来研究基本光照模型。

      首先我们来看看“方向光”,方向光是一束没有起点的光,这意味着所有的光线都是平行的。方向光的方向可以用一个向量来表示,这个向量用来计算场景中所有物体的光照效果。现实生活中,太阳光就可以认为是方向光,并不是说太阳光发出的光线都是平行的,只是因为太阳离我们太远了,它的光线可以近似认为是平行光。

      方向光另外一个重要的属性就是不管它离物体的距离多远,它的亮度都是恒定的,在下面的教程,我们将会学习到点光源,而点光源的亮度是会随着距离衰减的。

下面的图很好的解释了方向光:

directional_light

      我们已经知道太阳光包括环境光和漫反射光2部分,本篇教程中,我们先编程实现环境光的效果,下一篇教程再来学习漫反射光。

      前面的教程中,我们学习了如何在一个纹理上采样像素颜色,像素颜色有3个通道,红绿蓝(RGB),每个通道都是一个单字节,也就是说每个通道的颜色值范围是[0,255],不同的RGB值代表不同的颜色,(0,0,0)表示黑色,(255,255,255)表示白色。我们可以对一个颜色RGB值按比例缩放,可以看到颜色不变,但亮度越来越低。er or darker (depending on the scaling factor)。

      白色光照到物体表面时候,反射的光仅仅是物体本身的颜色,可能会更暗或者更亮,这依赖于光源的强度。如果光源是纯红色(255,0,0),那么反射光就仅仅是物体表面的颜色的红色通道部分,因为没有蓝色和绿色部分被反射,如果物体是纯蓝的,那么此时物体看起来就是黑色的,因为没有蓝色光被反射。

      我们将指定光源颜色的RGB值范围是[0-1],然后把光源颜色和物体表面颜色相乘,得到光反射的颜色,即我们看到的物体颜色。下面的方程用来计算环境光:

ambient_light

      在这篇教程程序中,我们可以通过a和s键来增加和减少环境光的强度,当然,这只是环境光的部分,方向光本身还没有被计算进来,在下篇教程中,我们将来实现漫反射光,现在我们渲染的物体金字塔,它的四个面光照都是一样的。

      环境光真实感并不强,是人为添加的光照因素,后面我们考虑了漫反射和高光后,会把它们组合在一起,实现更真实的三维物体渲染。

代码如下:

     本教程中,我们会对程序架构进行一些改变,主要变化如下:

  1. 在  Technique  类中包装了shader管理,比如shader编译,链接等等。我们自己的特效(effect)类都是从 Technique  中派生出来的。
  2. 把 GLUT初始化以及回调函数管理放在GLUTBackend模块中处理,这个模块会注册自己以便接受GLUT的回调函数,然后通过C++接口类  ICallbacks 转发给应用程序。 
  3. 把主cpp中的全局函数和变量移动到一个类中,这个类能够被看作应用程序类,在后面的教程中,我们将会扩展这个类,提供更多的通用功能,这中模式在很多的引擎和框架中都很流行。

     下面看看重构后的代码:

glut_backend.h

void GLUTBackendInit(int argc, char** argv); 
bool GLUTBackendCreateWindow(unsigned int Width, unsigned int Height, unsigned int bpp, bool isFullScreen, const char* pTitle);
 

GLUT相关的代码都被移到 "GLUT backend" 模块中,这使得初始化GLUT和创建窗口变得更容易。

void GLUTBackendRun(ICallbacks* pCallbacks);

      GLUT初始化和创建窗口后,我们就要执行GLUT主循环,GLUTBackendRun函数就是用来执行主循环的。新增的Callbacks接口用来注册GLUT回调函数。和前面教程中每个应用程序注册自己的回调函数不同,GLUT backend模块把自己注册为虚函数函数,然后通过继承类,把事件发送到这个函数中掉用的对象。

technique.h

class Technique 

public: 
Technique(); 
~Technique(); 
virtual bool Init(); 
void Enable(); 
protected: 
bool AddShader(GLenum ShaderType, const char* pShaderText); 
bool Finalize(); 
GLint GetUniformLocation(const char* pUniformName); 
private: 
GLuint m_shaderProg; 
typedef std::list<GLuint> ShaderObjList; 
ShaderObjList m_shaderObjList; 
};

      前面教程中,我们在主程序cpp中实现shader的编译,链接等功能,本教程代码中,我们包装了这些公用的功能,建立Technique类,它的继承类将主要专注于shader特效。

      每个technique首先要调用Init函数进行实例化操作,techniqu的派生类必须首先调用基类的Init化函数(该函数中创建OpenGL程序对象),也可以增加自己的私有函数。

      一个Technique对象被创建和实例化后,然后派生类要调用函数  AddShader() 去增加shader,最后调用Finalize()函数链接对象。在切换  technique和调用draw之前,我们必须调用Enable函数,因为该函数包装了 glUseProgram() 。

该类会跟踪中间编译的对象,并在它们使用完毕后,用glDeleteShader()删除它们,这有助于减少程序的资源消耗。程序对象本身在析构函数通过glDeleteProgram()删除。

tutorial17.cpp

class Tutorial17 : public ICallbacks 

public: 
Tutorial17() 

... 

~Tutorial17() 

... 

bool Init() 

... 

void Run() 

GLUTBackendRun(this); 

virtual void RenderSceneCB() 

... 

virtual void IdleCB() 

... 

virtual void SpecialKeyboardCB(int Key, int x, int y) 

... 

virtual void KeyboardCB(unsigned char Key, int x, int y) 

... 

virtual void PassiveMouseCB(int x, int y) 

... 

private: 
void CreateVertexBuffer() 

... 

void CreateIndexBuffer() 

... 

GLuint m_VBO; 
GLuint m_IBO; 
LightingTechnique* m_pEffect; 
Texture* m_pTexture; 
Camera* m_pGameCamera; 
float m_scale; 
DirectionalLight m_directionalLight; 
};

      上面的代码是主应用程序类的代码,它封装了其余的一些代码。Init() 用于创建特效类,装入纹理和创建顶点和索引缓冲,Run函数调用 GLUTBackendRun()并把对象自己作为参数传入

lighting_technique.h

struct DirectionalLight 

Vector3f Color; 
float AmbientIntensity; 
};

我们定义了一个方向光的结构,注意现在成员只有光的颜色和环境光,后面的教程中,我们会继续增加漫反射和高光部分。

第一个参数是光源的RGA颜色,第二参数是环境光系数,为1.0时候,是很亮的环境光,为0.1时候是很暗的环境光。

class LightingTechnique : public Technique 

public: 
LightingTechnique(); 
virtual bool Init(); 
void SetWVP(const Matrix4f& WVP); 
void SetTextureUnit(unsigned int TextureUnit); 
void SetDirectionalLight(const DirectionalLight& Light); 
private: 
GLuint m_WVPLocation; 
GLuint m_samplerLocation; 
GLuint m_dirLightColorLocation; 
GLuint m_dirLightAmbientIntensityLocation; 
};

我们将从 Technique类中派生出光照类,该类创建后,首先要调用Init函数。

in vec2 TexCoord0; 
out vec4 FragColor; 
struct DirectionalLight 

vec3 Color; 
float AmbientIntensity; 
}; 
uniform DirectionalLight gDirectionalLight; 
uniform sampler2D gSampler; 
void main() 

FragColor = texture2D(gSampler, TexCoord0.xy) * 
vec4(gDirectionalLight.Color, 1.0f) * 
gDirectionalLight.AmbientIntensity; 
}

      顶点shader代码没有变化,片元shader中,增加了一个方向光的uniform变量,该变量用来从应用程序传入光照参数。最后的返回颜色,我们用光照和纹理采样的结果相乘,得到最终的像素颜色。

m_WVPLocation = GetUniformLocation("gWVP"); 
m_samplerLocation = GetUniformLocation("gSampler"); 
m_dirLightColorLocation = GetUniformLocation("gDirectionalLight.Color"); 
m_dirLightAmbientIntensityLocation = GetUniformLocation("gDirectionalLight.AmbientIntensity");
 

      为了访问uniform变量DirectionalLight,我们增加了上述代码,注意,对于结构中成员变量,我们需要分别指定。

程序运行后,界面如下,我们可以用a和s键来调节环境光亮度。

image


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值