Python之OpenGL笔记(38):三种光照通道的合成

一、目的

1、实现镜面光照射下的棋盘球体;
2、环境光、散射光、镜面光三种光照通道的合成

二、程序运行结果

Python之OpenGL笔记(38):三种光照通道的合成_第1张图片

三、镜面光

   现实世界中,当光滑表面被照射时会有方向很集中的反射光。这就是镜面光(Specular)
   与散射光最终强度仅依赖于入射光与被照射点法向量的夹角不同,镜面光的最终强度还依赖于观察者的位置。也就是说,如果从摄像机到被照射点的向量不在反射光方向集中的范围内,观察者将不会看到镜面光,图6-12简单地说明了这个问题。
Python之OpenGL笔记(38):三种光照通道的合成_第2张图片

   镜面光的计算模型比前面的两种光都要复杂一些,具体公式如下。

镜面光照射结果=材质的反射系数×镜面光强度×max(0,(cos(半向量与法向量的夹角))^粗糙度)

   实际开发中往往分两步进行计算,此时公式被拆解为如下情况。

镜面光最终强度=镜面光强度×max(0,(cos(半向量与法向量的夹角))^粗糙度)
镜面光照射结果=材质的反射系数×镜面光最终强度

   材质的反射系数实际指的就是物体被照射处的颜色,镜面光强度指的是镜面光中RGB(红、绿、蓝)3个色彩通道的强度。

   从上述公式中可以看出,与散射光计算公式主要有两点区别。首先是计算余弦值时对应的角不再是入射角,而是半向量与法向量的夹角。半向量指的是从被照射点到光源的向量与从被照射点到观察点向量的平均向量,图6-13说明了半向量的含义。
Python之OpenGL笔记(38):三种光照通道的合成_第3张图片

   图6-13中V为从被照射点到观察点的向量,N为被照射点表面的法向量,H为半向量,L为从被照射点到光源的向量。

   从图6-13中可以看出,半向量H与V及L共面,并且其与这两个向量的夹角相等。因此,已知V和L后计算H非常简单,只要首先将V和L规格化,然后将规格化后的V与L求和并再次规格化即可。求得半向量后,再求其与法向量夹角的余弦值就非常简单了,只需将规格化后的法向量与半向量进行点积即可。

   另外一个区别就是,求得的余弦值还需要对粗糙度进行乘方运算,此运算可以达到粗糙度越小,镜面光面积越大的效果,这也是很贴近现实世界的。

   粗糙度越小,镜面光面积越大,这也符合我们观察现实世界的经验。另外,镜面光也是随光源位置的变化而变化的。

四、源代码

"""
程序名称:GL_DrawBall05.py
编程: dalong10
功能: 散射光的应用实现
参考资料: 《OpenGL ES 3.x游戏开发》(上卷)吴亚峰
"""
import myGL_Funcs    #Common OpenGL utilities,see myGL_Funcs.py
import sys, random, math
import OpenGL
from OpenGL.GL import *
from OpenGL.GL.shaders import *
import numpy 
import numpy as np
import glfw
from pyrr import Quaternion, matrix44, Vector3

strVS = """
#version 330 core
layout(location = 0) in vec3 aPosition;
in vec3 aNormal;     //顶点法向量
uniform mat4 uMVMatrix;//总变换矩阵
uniform mat4 uMMatrix; //变换矩阵(包括平移、旋转、缩放)
uniform vec3 uCamera;  //摄像机位置
uniform vec3 uLightLocation;//光源位置
out vec3 vPosition ;  //用于传递给片元着色器的顶点位置
out vec4 vAmbient;			//用于传递给片元着色器的环境光最终强度
out vec4 vDiffuse;			//用于传递给片元着色器的散射光最终强度
out vec4 vSpecular;			//用于传递给片元着色器的镜面光最终强度
void pointLight(					//定位光光照计算的方法
  in vec3 normal,				//法向量
  inout vec4 ambient,			//环境光最终强度
  inout vec4 diffuse,				//散射光最终强度
  inout vec4 specular,			//镜面光最终强度
  in vec3 lightLocation,			//光源位置
  in vec4 lightAmbient,			//环境光强度
  in vec4 lightDiffuse,			//散射光强度
  in vec4 lightSpecular			//镜面光强度
){
  ambient=lightAmbient;			//直接得出环境光的最终强度  
  vec3 normalTarget=aPosition+normal;	//计算变换后的法向量
  vec3 newNormal=(uMMatrix*vec4(normalTarget,1)).xyz-(uMMatrix*vec4(aPosition,1)).xyz;
  newNormal=normalize(newNormal); 	//对法向量规格化
  //计算从表面点到摄像机的向量
  vec3 eye= normalize(uCamera-(uMMatrix*vec4(aPosition,1)).xyz);  
  //计算从表面点到光源位置的向量vp
  vec3 vp= normalize(lightLocation-(uMMatrix*vec4(aPosition,1)).xyz);  
  vp=normalize(vp);//格式化vp
  vec3 halfVector=normalize(vp+eye);	//求视线与光线的半向量    
  float shininess=50.0;				//粗糙度,越小越光滑
  float nDotViewPosition=max(0.0,dot(newNormal,vp)); 	//求法向量与vp的点积与0的最大值
  diffuse=lightDiffuse*nDotViewPosition;				//计算散射光的最终强度
  float nDotViewHalfVector=dot(newNormal,halfVector);	//法线与半向量的点积 
  float powerFactor=max(0.0,pow(nDotViewHalfVector,shininess)); 	//镜面反射光强度因子
  specular=lightSpecular*powerFactor;    			//计算镜面光的最终强度
}

void main(){
   gl_Position = uMVMatrix * vec4(aPosition,1); //根据总变换矩阵计算此次绘制此顶点位置     
   vec4 ambientTemp,diffuseTemp,specularTemp;	  //用来接收三个通道最终强度的变量 
   pointLight(normalize(aNormal),ambientTemp,diffuseTemp,specularTemp,uLightLocation,
   vec4(0.15,0.15,0.15,1.0),vec4(0.8,0.8,0.8,1.0),vec4(0.7,0.7,0.7,1.0));   
   vAmbient=ambientTemp; 		//将环境光最终强度传给片元着色器
   vDiffuse=diffuseTemp; 		//将散射光最终强度传给片元着色器
   vSpecular=specularTemp; 		//将镜面光最终强度传给片元着色器  
   vPosition = aPosition;  //将顶点的位置传给片元着色器
	}
"""

strFS = """
#version 330 core
in vec3 vPosition;//接收从顶点着色器过来的顶点位置
in vec4 vAmbient;//接收从顶点着色器过来的环境光最终强度
in vec4 vDiffuse;//接收从顶点着色器过来的散射光最终强度
in vec4 vSpecular;//接收从顶点着色器过来的镜面反射光最终强度
out vec4 fragColor;//输出的片元颜色
void main(){
   vec3 color;
   float n = 8.0;//外接立方体每个坐标轴方向切分的份数
   float uR=0.8 ;
   float span = 2.0*uR/n;//每一份的尺寸(小方块的边长)
   
   int i = int((vPosition.x + uR)/span);//当前片元位置小方块的行数
   int j = int((vPosition.y + uR)/span);//当前片元位置小方块的层数
   int k = int((vPosition.z + uR)/span);//当前片元位置小方块的列数
   //计算当点应位于白色块还是黑色块中
   int whichColor = int(mod(float(i+j+k),2.0));
   if(whichColor == 1) {//奇数时为红色
   		color = vec3(0.678,0.231,0.129);//红色
   }
   else {//偶数时为白色
   		color = vec3(1.0,1.0,1.0);//白色
   }
   //最终颜色
   vec4 finalColor=vec4(color,0);
   //给此片元颜色值
   fragColor=finalColor*vAmbient + finalColor*vDiffuse + finalColor*vSpecular;
	}
"""

cameraPos=np.array([0.0, 0.0, 30])      # 眼睛的位置(默认z轴的正方向)
cameraFront=np.array([0.0, 0.0, 0.0])  # 瞄准方向的参考点(默认在坐标原点)
cameraUp=np.array([0.0, 1.0, 0.0])     # 定义对观察者而言的上方(默认y轴的正方向)
WIN_W, WIN_H = 640, 480  # 保存窗口宽度和高度的变量

class FirstSphere:
    def __init__(self, cube_verticeside ):
        # load shaders
        self.program = myGL_Funcs.loadShaders(strVS, strFS)
        glUseProgram(self.program)
        self.vertIndex = glGetAttribLocation(self.program, b"aPosition")
        self.normIndex = glGetAttribLocation(self.program, b"aNormal")
                
        self.cube_vertices = cube_verticeside    
        # set up vertex array object (VAO)
        self.vao = glGenVertexArrays(1)
        glBindVertexArray(self.vao)            
        # set up VBOs
        vertexData = numpy.array(self.cube_vertices, numpy.float32)
        self.vertexBuffer = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer)
        glBufferData(GL_ARRAY_BUFFER, 4*len(vertexData), vertexData, GL_STATIC_DRAW)       
        # enable arrays
        glEnableVertexAttribArray(self.vertIndex)       
        # Position attribute
        glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer)
        glVertexAttribPointer(self.vertIndex, 3, GL_FLOAT, GL_FALSE, 0,None)   
        # aNormal attribute
        normData = numpy.array(self.cube_vertices, numpy.float32)
        self.normBuffer = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, self.normBuffer)
        glBufferData(GL_ARRAY_BUFFER, 4*len(normData), normData, GL_STATIC_DRAW)       

        glEnableVertexAttribArray(self.normIndex)       
        glBindBuffer(GL_ARRAY_BUFFER, self.normBuffer)
        glVertexAttribPointer(self.normIndex, 3, GL_FLOAT, GL_FALSE, 0,None)   
                    
        # unbind VAO
        glBindVertexArray(0)
        glBindBuffer(GL_ARRAY_BUFFER, 0)    
             
    def render(self,  mvMatrix, mMatrix,ucamera,LightLocation):       
        # use shader
        glUseProgram(self.program)
   
        # set modelview matrix
        glUniformMatrix4fv(glGetUniformLocation(self.program, 'uMVMatrix'), 
                          1, GL_FALSE, mvMatrix)
        glUniformMatrix4fv(glGetUniformLocation(self.program, 'uMMatrix'), 
                          1, GL_FALSE, mMatrix)
        glUniform3fv(glGetUniformLocation(self.program, 'uCamera'), 
                          1, GL_FALSE, ucamera)
        glUniform3fv(glGetUniformLocation(self.program, 'uLightLocation'), 
                          1, GL_FALSE, LightLocation)
        # bind VAO
        glBindVertexArray(self.vao)
        # draw
        
        glDrawArrays(GL_TRIANGLES,0,len(self.cube_vertices) )
        # unbind VAO
        glBindVertexArray(0)

def drawglobeVBO():
    PI = 3.14159265358979323846264
    statcky = 30 # 横向向切成多少片
    stlicex = 30 # 纵向切多少片
    R = 0.8      # 半径
    angleHy =  (2*PI)/statcky  # 横向每份的角度		算出弧度值
    angleZx =  (2*PI)/stlicex; # 纵向每份的角度		算出弧度值
    NumAngleHy = 0.0 # 当前横向角度
    NumAngleZx = 0.0 # 当前纵向角度

    c=numpy.array([], numpy.float32)
    for j in range(statcky):
        for i in range(stlicex):
            NumAngleHy = angleHy*i # 
            NumAngleZx = angleZx*j #  起点都是轴指向的方向。根据右手定则决定转向,只要转向相同,那么两个就合适
            x0 = R*np.cos(NumAngleHy)*np.cos(NumAngleZx)  
            y0 = R*np.cos(NumAngleHy)*np.sin(NumAngleZx) 
            z0 = R*np.sin(NumAngleHy) 
            x1 = R*np.cos(NumAngleHy)*np.cos(NumAngleZx+angleZx)  
            y1 = R*np.cos(NumAngleHy)*np.sin(NumAngleZx+angleZx) 
            z1 = R*np.sin(NumAngleHy) 
            x2 = R*np.cos(NumAngleHy+angleHy)*np.cos(NumAngleZx+angleZx)  
            y2 = R*np.cos(NumAngleHy+angleHy)*np.sin(NumAngleZx+angleZx) 
            z2 = R*np.sin(NumAngleHy+angleHy) 
            x3 = R*np.cos(NumAngleHy+angleHy)*np.cos(NumAngleZx)  
            y3 = R*np.cos(NumAngleHy+angleHy)*np.sin(NumAngleZx) 
            z3 = R*np.sin(NumAngleHy+angleHy) 
            c=np.hstack((c,numpy.array([x1,y1,z1], numpy.float32) ))
            c=np.hstack((c,numpy.array([x3,y3,z3], numpy.float32) ))
            c=np.hstack((c,numpy.array([x0,y0,z0], numpy.float32) ))
            c=np.hstack((c,numpy.array([x1,y1,z1], numpy.float32) ))
            c=np.hstack((c,numpy.array([x2,y2,z2], numpy.float32) ))
            c=np.hstack((c,numpy.array([x3,y3,z3], numpy.float32) ))
    return c
 

#Is called whenever a key is pressed/released via GLFW
def on_key(window, key, scancode, action, mods):
    if key == glfw.KEY_ESCAPE and action == glfw.PRESS:
        glfw.set_window_should_close(window,1)

if __name__ == '__main__':
    import sys
    import glfw
    import OpenGL.GL as gl
    
    keys=numpy.zeros(1024)
    deltaTime = 0.0
    lastFrame = 0.0   # Time of last frame
    # Initialize the library
    if not glfw.init():
        sys.exit()

    # Create a windowed mode window and its OpenGL context
    window = glfw.create_window(640, 480, "GL_DrawBall05 ", None, None)
    if not window:
        glfw.terminate()
        sys.exit()

    # Make the window's context current
    glfw.make_context_current(window)
    # Install a key handler
    glfw.set_key_callback(window, on_key)
    PI = 3.14159265358979323846264
     
    # 画球面 
    vert = drawglobeVBO()               
    mMatrix1 = matrix44.create_from_translation(Vector3([-3, 0, 3]))
    mMatrix2 = matrix44.create_from_translation(Vector3([2, -2, 4]))
    # Loop until the user closes the window
    a=0          
    firstSphere1 = FirstSphere(vert)
    while not glfw.window_should_close(window):
        currentFrame = glfw.get_time()
        deltaTime = currentFrame - lastFrame       
        lastFrame = currentFrame
        # Render here
        width, height = glfw.get_framebuffer_size(window)
        WIN_W, WIN_H =width, height
        ratio = width / float(height)
        glfw.poll_events()

        gl.glViewport(0, 0, width, height)
        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
        
        #glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);  #用于控制多边形的显示方式
        gl.glMatrixMode(gl.GL_PROJECTION)
        gl.glLoadIdentity()
        gl.glOrtho(-ratio, ratio, -1, 1, 1, -1)
        gl.glMatrixMode(gl.GL_MODELVIEW)
        gl.glLoadIdentity()
        gl.glClearColor(0.0,0.1,0.1,1.0)
        
        # modelview matrix
        mvMatrix = matrix44.create_look_at(cameraPos, cameraFront, cameraUp,None)     # 设置视点
        pMatrix = matrix44.create_perspective_projection_from_bounds(-ratio*1.0, ratio*1.0,  -1, 1,20,100,None)  
        model0 = matrix44.multiply(mvMatrix,pMatrix)       
        trans1 = matrix44.create_from_translation(Vector3([-0.6, 0, 0]))
        trans2 = matrix44.create_from_translation(Vector3([0.6, 0, 0]))
        model1 = matrix44.multiply(model0,trans1)       
        model2 = matrix44.multiply(model0,trans2)       

        firstSphere1.render( model1,mMatrix1,cameraPos,Vector3([3.0, 2, 0])) #球1
        firstSphere1.render( model2,mMatrix2,cameraPos,Vector3([0, 1, 0]))   #球2
      
        # Swap front and back buffers
        glfw.swap_buffers(window)       
        # Poll for and process events
        glfw.poll_events()

    glfw.terminate()

五、参考资料

1、大龙10的简书:https://www.jianshu.com/p/49dec482a291
2、吴亚峰《OpenGL ES 3.x游戏开发》(上卷)

你可能感兴趣的:(Python,OpenGL)