【PyOpenGL教程-介绍着色器】漫反射、环境光、平行光
发表于2018-07-11
漫反射、环境光、方向光
原文地址:http://pyopengl.sourceforge.net/context/tutorials/shader_5.html
本教程在以往教程的基础上添加了:
- 环境光
- 漫射光
- 方向光(如,太阳)
- 法线、法线矩阵
照明是渲染流程中最复杂的部分之一。目前还没有人提出一种“完美”的实时图像渲染模拟方案(即使是非实时图像也没有真正解决所有材料的问题)。
所以每个OpenGL渲染器都是一个特定的材料在特定的光照环境下应该是什么样的近似效果。传统的(legacy)OpenGL有一个特定的照明模型,它经常“用”于几何体的简单图形化。本教程将向您展示如何开始创建类似的照明效果(称为Phong着色,高光部分将在下一篇教程中介绍)。
Ambient Light(环境光)
环境光是用来模拟光照的“辐射”效应的,也就是说,光强在光源的周围中“逐步减弱”,这就是环境光的光照模型。
在Legacy OpenGL中,环境光通过设置每个材质表面的漫反射系数(颜色)来实现,它可以有一组可以反射的两个光源。
- 全局环境光
- Per-light环境光的影响
全局光可以被认为是“一直存在的光”,即使没有特意启动任何光源,(环境)光也会在环境中作用出来。每个光源对环境光的作用只在光源是启用状态时才计算进去,否则光线值就是全局环境光的值。你可以把它考虑为“打开的光源越多,对环境光强度的增益越大”。
这里每一种材质的环境光作用都很简单:light ambient * Material_ambient
在环境光的计算中没有其他信息。光与材料的关系,或者光的入射角度,或者你看材料的角度,都不重要。
我们的着色器会假设只有一个有效的非全局环境光。Legacy OpenGL允许至少8个活动的光,所有这些都将涉及到环境光的计算(在启用时)。
材质的环境光可以认为是“材质接受光照后又重新发射出的光的多少”(而不是吸收)。注意,这里所有的环境光都是4个分量的颜色值,所以材料的环境值实际上可能改变了环境反射光的颜色。类似地,强烈的彩色环境光会让所有的材料都有一种强烈的“undercast”颜色。
from OpenGLContext import testingcontext BaseContext = testingcontext.getInteractive() from OpenGL.GL import * from OpenGL.arrays import vbo from OpenGLContext.arrays import * from OpenGL.GL import shaders from OpenGLContext.events.timer import Timer class TestContext( BaseContext ): """Demonstrates use of attribute types in GLSL """ def OnInit( self ): """Initialize the context"""
Diffuse Light(漫射光)
环境光是模拟物体接受到周围的光线并没有全部吸收而是重新发出光的现象,物体表面上重新发出光并不是“有序”的(也就是说,重新发射的光被分散了)
一个不“光滑”的表面会重新发出所有照射过来的光线(比如雪,或者一个粗糙的木板),会有一个非常高的漫反射值。当被光线照射时,漫射表面会向四面八法发出光,但它发出的光量值是由光线照射到表面的角度来决定的。
(用术语来讲这叫做兰伯特反射)
为了计算漫反射光的值,我们需要一些信息:
- 物体表面与光线的夹角
- 光的漫反射强度值
- 材质的漫反射系数
为了计算表面与光线之间的夹角,我们需要某种方法来确定一个表面的特定部分指向哪个方向。在OpenGL中,传统的实现方法是为每个顶点添加法线信息,然后在整个表面将这些法线信息进行插值。
不像你在线性代数与几何计算的法线,一个顶点的法线不一定非要是两个相临边的叉乘。相反,它是一个我们自己赋予的一个让整个顶点看起正确的法线值(看起来是对的,它就是对的)。它通常是两个相邻面的“自然”法线的混合值,这样一来会让两个面看起来是一个连续的表面,产生一个平滑的外观。
我们已经有了法线值,现在我们还需要光的方向来计算法线与光线方向的夹角。在本教程中,我们将使用最简单的光,一个无限远的“方向”光,它可以粗略地模拟地球表面的阳光行为。
这束光有一个方向,所有光线都被认为是平行于这个方向的。因此,光的相对位置(即“无限远”,这意味着所有的相对位置都是相同的)对光线照射到表面的角度没有影响。从本质上说,定向光是一个规格化的矢量,它从光的“位置”指向原点。
通过我们的法线和方向光,我们可以应用兰伯特定律来计算任意给定顶点的漫射分量倍率。兰伯特定律寻找两个向量的余弦(法线和光位置向量),这是通过取两个(标准化)向量的点积来计算的。
GLSL的phong_WeightCalc函数(如下)将计算出决定物体对于单个方向光的漫射系数。传递的两个值都必须是标准化的向量。
phong_weightCalc = """ float phong_weightCalc( in vec3 light_pos, // light position in vec3 frag_normal // geometry normal ) { // returns vec2( ambientMult, diffuseMult ) float n_dot_pos = max( 0.0, dot( frag_normal, light_pos )); return n_dot_pos; } """
我们的顶点着色器将为我们做所有的工作,它定义了大量的uniform变量来存储各种各样的光和材料参数。我们还定义了两个顶点属性来存储顶点位置和用户分配的法线值。
vertex = shaders.compileShader( phong_weightCalc + """ uniform vec4 Global_ambient; uniform vec4 Light_ambient; uniform vec4 Light_diffuse; uniform vec3 Light_location; uniform vec4 Material_ambient; uniform vec4 Material_diffuse; attribute vec3 Vertex_position; attribute vec3 Vertex_normal; varying vec4 baseColor; void main() { gl_Position = gl_ModelViewProjectionMatrix * vec4( Vertex_position, 1.0 ); vec3 EC_Light_location = gl_NormalMatrix * Light_location; float diffuse_weight = phong_weightCalc( normalize(EC_Light_location), normalize(gl_NormalMatrix * Vertex_normal) ); baseColor = clamp( ( // global component (Global_ambient * Material_ambient) // material's interaction with light's contribution // to the ambient lighting... + (Light_ambient * Material_ambient) // material's interaction with the direct light from // the light. + (Light_diffuse * Material_diffuse * diffuse_weight) ), 0.0, 1.0); }""", GL_VERTEX_SHADER)
实际上,光照计算只是将不同的因素值加在一起,得到最终的颜色,然后将结果clamp(限制)在[0.0,1.0]范围内。我们完全可以让OpenGL来做这个clamp(限制),这里的调用只是为了说明效果。
是否选用视图空间?
在我们的顶点着色器中,我们实际上是在“视图空间”下进行角度计算。我们可以使用“模型空间”下的顶点坐标轻易地计算出来Lambertian反射值。
""" vec2 weights = phong_weightCalc( normalize(Light_location), normalize(Vertex_normal) );"""
在大多数的文档中,为了简化更多涉及到照明的计算,更倾向于在“视图空间”中进行光照计算。
本次的片元着色器非常简单。我们逐片元进行光照计算即可。但是使用简单的漫反射着色器不能够很好的改善我们的光照效果。
fragment = shaders.compileShader(""" varying vec4 baseColor; void main() { gl_FragColor = baseColor; } """, GL_FRAGMENT_SHADER) self.shader = shaders.compileProgram(vertex,fragment)
我们将为这节课创建一个稍微不那么“平坦”的几何图形,我们将在“弓形窗”的排列中创建一组6个面,这样就可以很容易地看到直接照明的效果。
self.vbo = vbo.VBO( array( [ [ -1, 0, 0, -1,0,1], [ 0, 0, 1, -1,0,2], [ 0, 1, 1, -1,0,2], [ -1, 0, 0, -1,0,1], [ 0, 1, 1, -1,0,2], [ -1, 1, 0, -1,0,1], [ 0, 0, 1, -1,0,2], [ 1, 0, 1, 1,0,2], [ 1, 1, 1, 1,0,2], [ 0, 0, 1, -1,0,2], [ 1, 1, 1, 1,0,2], [ 0, 1, 1, -1,0,2], [ 1, 0, 1, 1,0,2], [ 2, 0, 0, 1,0,1], [ 2, 1, 0, 1,0,1], [ 1, 0, 1, 1,0,2], [ 2, 1, 0, 1,0,1], [ 1, 1, 1, 1,0,2], ],'f') )
由于我们有大量的uniform和attributes,所以我们将使用一些迭代来为我们自己设置这些值。
for uniform in ( 'Global_ambient', 'Light_ambient','Light_diffuse','Light_location', 'Material_ambient','Material_diffuse', ): location = glGetUniformLocation( self.shader, uniform ) if location in (None,-1): print 'Warning, no uniform: %s'%( uniform ) setattr( self, uniform+ '_loc', location ) for attribute in ( 'Vertex_position','Vertex_normal', ): location = glGetAttribLocation( self.shader, attribute ) if location in (None,-1): print 'Warning, no attribute: %s'%( uniform ) setattr( self, attribute+ '_loc', location ) def Render( self, mode = None): """Render the geometry for the scene.""" BaseContext.Render( self, mode ) glUseProgram(self.shader) try: self.vbo.bind() try:
我们将环境光设为了比较引人注目的淡红色,这样你就能明显地观察到全局环境光的作用。
glUniform4f( self.Global_ambient_loc, .3,.05,.05,.1 )
在传统的OpenGL中,我们会调用不同的专用函数来设置这些变量。
glUniform4f( self.Light_ambient_loc, .2,.2,.2, 1.0 ) glUniform4f( self.Light_diffuse_loc, 1,1,1,1 ) glUniform3f( self.Light_location_loc, 2,2,10 ) glUniform4f( self.Material_ambient_loc, .2,.2,.2, 1.0 ) glUniform4f( self.Material_diffuse_loc, 1,1,1, 1 )
我们只为每个顶点定义了两个attribute变量
glEnableVertexAttribArray( self.Vertex_position_loc ) glEnableVertexAttribArray( self.Vertex_normal_loc ) stride = 6*4 glVertexAttribPointer( self.Vertex_position_loc, 3, GL_FLOAT,False, stride, self.vbo ) glVertexAttribPointer( self.Vertex_normal_loc, 3, GL_FLOAT,False, stride, self.vbo+12 ) glDrawArrays(GL_TRIANGLES, 0, 18) finally: self.vbo.unbind()
跟往常一样,需要清理环境。
glDisableVertexAttribArray( self.Vertex_position_loc ) glDisableVertexAttribArray( self.Vertex_normal_loc ) finally: glUseProgram( 0 ) if __name__ == "__main__": TestContext.ContextMainLoop()
我们的下一个教程将介绍余下的Phong渲染算法——为物体表面添加“高光”(光泽)。
译者附:
完整代码
from OpenGLContext import testingcontext BaseContext = testingcontext.getInteractive() from OpenGL.GL import * from OpenGL.arrays import vbo from OpenGLContext.arrays import * from OpenGL.GL import shaders from OpenGLContext.events.timer import Timer class TestContext( BaseContext ): """Demonstrates use of attribute types in GLSL """ def OnInit( self ): """Initialize the context""" phong_weightCalc = """ float phong_weightCalc( in vec3 light_pos, // light position in vec3 frag_normal // geometry normal ) { // returns vec2( ambientMult, diffuseMult ) float n_dot_pos = max( 0.0, dot( frag_normal, light_pos )); return n_dot_pos; } """ vertex = shaders.compileShader( phong_weightCalc + """ uniform vec4 Global_ambient; uniform vec4 Light_ambient; uniform vec4 Light_diffuse; uniform vec3 Light_location; uniform vec4 Material_ambient; uniform vec4 Material_diffuse; attribute vec3 Vertex_position; attribute vec3 Vertex_normal; varying vec4 baseColor; void main() { gl_Position = gl_ModelViewProjectionMatrix * vec4( Vertex_position, 1.0 ); vec3 EC_Light_location = gl_NormalMatrix * Light_location; float diffuse_weight = phong_weightCalc( normalize(EC_Light_location), normalize(gl_NormalMatrix * Vertex_normal) ); baseColor = clamp( ( // global component (Global_ambient * Material_ambient) // material's interaction with light's contribution // to the ambient lighting... + (Light_ambient * Material_ambient) // material's interaction with the direct light from // the light. + (Light_diffuse * Material_diffuse * diffuse_weight) ), 0.0, 1.0); }""", GL_VERTEX_SHADER) """ vec2 weights = phong_weightCalc( normalize(Light_location), normalize(Vertex_normal) );""" fragment = shaders.compileShader(""" varying vec4 baseColor; void main() { gl_FragColor = baseColor; } """, GL_FRAGMENT_SHADER) self.shader = shaders.compileProgram(vertex,fragment) self.vbo = vbo.VBO( array( [ [ -1, 0, 0, -1,0,1], [ 0, 0, 1, -1,0,2], [ 0, 1, 1, -1,0,2], [ -1, 0, 0, -1,0,1], [ 0, 1, 1, -1,0,2], [ -1, 1, 0, -1,0,1], [ 0, 0, 1, -1,0,2], [ 1, 0, 1, 1,0,2], [ 1, 1, 1, 1,0,2], [ 0, 0, 1, -1,0,2], [ 1, 1, 1, 1,0,2], [ 0, 1, 1, -1,0,2], [ 1, 0, 1, 1,0,2], [ 2, 0, 0, 1,0,1], [ 2, 1, 0, 1,0,1], [ 1, 0, 1, 1,0,2], [ 2, 1, 0, 1,0,1], [ 1, 1, 1, 1,0,2], ],'f') ) for uniform in ( 'Global_ambient', 'Light_ambient','Light_diffuse','Light_location', 'Material_ambient','Material_diffuse', ): location = glGetUniformLocation( self.shader, uniform ) if location in (None,-1): print 'Warning, no uniform: %s'%( uniform ) setattr( self, uniform+ '_loc', location ) for attribute in ( 'Vertex_position','Vertex_normal', ): location = glGetAttribLocation( self.shader, attribute ) if location in (None,-1): print 'Warning, no attribute: %s'%( uniform ) setattr( self, attribute+ '_loc', location ) def Render( self, mode = None): """Render the geometry for the scene.""" BaseContext.Render( self, mode ) glUseProgram(self.shader) try: self.vbo.bind() try: glUniform4f( self.Global_ambient_loc, .3,.05,.05,.1 ) glUniform4f( self.Light_ambient_loc, .2,.2,.2, 1.0 ) glUniform4f( self.Light_diffuse_loc, 1,1,1,1 ) glUniform3f( self.Light_location_loc, 2,2,10 ) glUniform4f( self.Material_ambient_loc, .2,.2,.2, 1.0 ) glUniform4f( self.Material_diffuse_loc, 1,1,1, 1 ) glEnableVertexAttribArray( self.Vertex_position_loc ) glEnableVertexAttribArray( self.Vertex_normal_loc ) stride = 6*4 glVertexAttribPointer( self.Vertex_position_loc, 3, GL_FLOAT,False, stride, self.vbo ) glVertexAttribPointer( self.Vertex_normal_loc, 3, GL_FLOAT,False, stride, self.vbo+12 ) glDrawArrays(GL_TRIANGLES, 0, 18) finally: self.vbo.unbind() glDisableVertexAttribArray( self.Vertex_position_loc ) glDisableVertexAttribArray( self.Vertex_normal_loc ) finally: glUseProgram( 0 ) if __name__ == "__main__": TestContext.ContextMainLoop()
来自:https://blog.csdn.net/v_xchen_v/article/details/80338172