Python之OpenGL筆記(37):散射光下的棋盤球體

一、目的

1、實現(xiàn)散射光照射下的棋盤球體;

二、程序運行結(jié)果

散射光照射下的棋盤球體

三、散射光

?? 上一小節(jié)中給出了僅僅使用環(huán)境光進行照射的案例,讀者可能覺得效果并不好。確實如此,僅僅有環(huán)境光的場景效果是很差的,沒有層次感。本節(jié)將介紹另外一種真實感好很多的光照效果—散射光(Diffuse),其指的是從物體表面向全方位360°均勻反射的光,如圖6-8所示。


?? 散射光具體代表的是現(xiàn)實世界中粗糙的物體表面被光照射時,反射光在各個方向基本均勻(也稱為“漫反射”)的情況。

?? 雖然反射后的散射光在各個方向是均勻的,但散射光反射的強度與入射光的強度以及入射的角度密切相關(guān)。因此,當光源的位置發(fā)生變化時,散射光的效果會發(fā)生明顯變化。主要體現(xiàn)為當光垂直地照射到物體表面時比斜照時要亮,其具體計算公式如下。

散射光照射結(jié)果=材質(zhì)的反射系數(shù)×散射光強度×max(cos(入射角),0)

?? 實際開發(fā)中往往分兩步進行計算,此時公式被拆解為如下情況。

散射光最終強度=散射光強度×max(cos(入射角),0)
散射光照射結(jié)果=材質(zhì)的反射系數(shù)×散射光最終強度

?? 材質(zhì)的反射系數(shù)實際指的就是物體被照射處的顏色,散射光強度指的是散射光中RGB(紅、綠、藍)3個色彩通道的強度。

?? 從上述公式中可以看出,與環(huán)境光計算公式唯一的區(qū)別是引入了最后一項“max(cos(入射角),0)”。其含義是入射角越大,反射強度越弱,當入射角的余弦值為負時(即入射角大于90°),反射強度為0。由于入射角為入射光向量與法向量的夾角,因此,其余弦值并不需要調(diào)用三角函數(shù)進行計算,只需要首先將兩個向量進行規(guī)格化,然后再進行點積即可,圖6-10說明了這個問題。


?? 圖6-10中的N代表被照射點表面的法向量,P為被照射點,L為從P點到光源的向量。N與L的夾角即為入射角。向量數(shù)學(xué)中,兩個向量的點積為兩個向量夾角的余弦值乘以兩個向量的模,而規(guī)格化后向量的模為1。因此,首先將兩個向量規(guī)格化,再點積就可以求得兩個向量夾角的余弦值。

四、程序說明:

?? 由于本案例中原始情況下的球心位于坐標原點,所以,每個頂點法向量的x、y、z軸分量與頂點的x、y、z坐標是一致的。這樣就不必單獨計算每個頂點的法向量了,直接將頂點坐標序列看作頂點法向量序列使用即可。

五、源代碼

"""
程序名稱:GL_DrawBall04.py
編程: dalong10
功能: 散射光的應(yīng)用實現(xiàn)
參考資料: 《OpenGL ES 3.x游戲開發(fā)》(上卷)吳亞峰
"""
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; //變換矩陣(包括平移、旋轉(zhuǎn)、縮放)
uniform vec3 uLightLocation;//光源位置
out vec3 vPosition ;  //用于傳遞給片元著色器的頂點位置
out vec4 vDiffuse;    //接收從頂點著色器過來的散射光分量

void pointLight (                               //散射光光照計算的方法
  in vec3 normal,                               //法向量
  inout vec4 diffuse,                               //散射光計算結(jié)果
  in vec3 lightLocation,                            //光源位置
  in vec4 lightDiffuse                          //散射光強度
){  
  vec3 normalTarget=aPosition+normal;                   //計算變換后的法向量
  vec3 newNormal=(uMMatrix*vec4(normalTarget,1)).xyz-(uMMatrix*vec4(aPosition,1)).xyz;
  newNormal=normalize(newNormal);                   //對法向量規(guī)格化
//計算從表面點到光源位置的向量vp
  vec3 vp= normalize(lightLocation-(uMMatrix*vec4(aPosition,1)).xyz);
  vp=normalize(vp);                                 //規(guī)格化vp
  float nDotViewPosition=max(0.0,dot(newNormal,vp));    //求法向量與vp向量的點積與0的最大值
  diffuse=lightDiffuse*nDotViewPosition;            //計算散射光的最終強度
}
void main(){
    gl_Position= uMVMatrix* vec4(aPosition, 1.0);
   vec4 diffuseTemp=vec4(0.0,0.0,0.0,0.0);   
   pointLight(normalize(aNormal), diffuseTemp, uLightLocation, vec4(0.8,0.8,0.8,1.0));  
   vDiffuse=diffuseTemp;                    //將散射光最終強度傳給片元著色器
   vPosition = aPosition;                   //將頂點的位置傳給片元著色器
    }
"""

strFS = """
#version 330 core
in vec3 vPosition;//接收從頂點著色器過來的頂點位置
in vec4 vDiffuse;//接收從頂點著色器過來的散射光最終強度
out vec4 fragColor;//輸出的片元顏色
void main(){
   vec3 color;
   float n = 8.0;//外接立方體每個坐標軸方向切分的份數(shù)
   float uR=0.8 ;
   float span = 2.0*uR/n;//每一份的尺寸(小方塊的邊長)
   
   int i = int((vPosition.x + uR)/span);//當前片元位置小方塊的行數(shù)
   int j = int((vPosition.y + uR)/span);//當前片元位置小方塊的層數(shù)
   int k = int((vPosition.z + uR)/span);//當前片元位置小方塊的列數(shù)
    //計算當前片元行數(shù)、層數(shù)、列數(shù)的和并對2取模
   int whichColor = int(mod(float(i+j+k),2.0));
   if(whichColor == 1) {//奇數(shù)時為紅色
        color = vec3(0.678,0.231,0.129);//紅色
   }
   else {//偶數(shù)時為白色
        color = vec3(1.0,1.0,1.0);//白色
   }
    //根據(jù)環(huán)境光強度計算最終片元顏色值
   fragColor=vec4(color,0)*vDiffuse;
    }
"""

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,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, '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 #  起點都是軸指向的方向。根據(jù)右手定則決定轉(zhuǎn)向,只要轉(zhuǎn)向相同,那么兩個就合適
            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_DrawBall04 ", 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)     # 設(shè)置視點
        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,Vector3([3.0, 2, 0])) #球1
        firstSphere1.render( model2,mMatrix2,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的簡書:http://www.itdecent.cn/p/49dec482a291
2、吳亞峰《OpenGL ES 3.x游戲開發(fā)》(上卷)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容