一、目的
1、顶点着色器的妙用,画一个扭动的软糖;
二、程序运行结果
三、基本原理
吴亚峰《OpenGL ES 3.x游戏开发》(下卷)内容
从图 2-4 中可以看出,软糖模型实际上是由很多层小矩形叠加而成。在同一帧中,随着 y 坐标的不断升高,此层的顶点绕中心轴扭曲的角度越大。因此,实现扭动软糖的效果只要将代表软糖的长方体中各层顶点的 x、z 坐标按照一定的规则根据顶点的 y 坐标以及当前帧的控制参数进行变换即可,具体的计算思路如图 2-5、图 2-6 与图 2-7 所示。
具体的计算步骤如下:
1、首先如图 2-5 所示,需要计算出当前顶点 y 坐标与最下层顶点 y 坐标的差值。
2、接着根据 y 坐标的差值、角度换算比例以及本帧的总扭曲角度换算出当前顶点的扭曲角度,计算公式为: currAngle =(currY-yStart)/ySpan×angleSpan。
3、最后根据当前顶点的 x、 z 坐标以及扭曲的角度计算出变换后顶点的 x、 z 坐标,此步的计算思路如图 2-6 与图 2-7 所示。
4、从图 2-6 与图 2-7 中可以看出,将顶点绕中心轴扭曲(旋转)实际上可以看成是将从中心点出发到变换前顶点的向量旋转指定的角度。旋转后得到的新向量的终点位置即所求的变换后顶点的位置,因此具体的计算公式如下。
x'=xcosα-zsinα
z'=xsinα+zcosα
上述公式中的α为需要旋转(扭曲)的角度,实际计算时采用前面步骤计算出来的变量 currAngle 的值即可。
四、源代码
"""
程序名称:GL_textures05.py
编程: dalong10
功能: 扭动的软糖的实现
参考资料: 《OpenGL ES 3.x游戏开发》(下卷)吴亚峰
"""
import myGL_Funcs #Common OpenGL utilities,see myGL_Funcs.py
import glfw
from OpenGL.GL import *
from OpenGL.GL.shaders import compileProgram, compileShader
import numpy
import numpy as np
import pyrr
from PIL import Image
SphereVS = """
#version 330 core
layout(location = 0) in vec3 aPosition;
layout(location = 1) in vec2 inTexCoord;
uniform mat4 uMVPMatrix; //总变换矩阵
uniform float angleSpan;//本帧扭曲总角度
uniform float yStart;//Y坐标起始点
uniform float ySpan;//Y坐标总跨度
out vec2 passTexCoord;
void main(){
float tempAS= angleSpan*(aPosition.y-yStart)/ySpan;//计算当前顶点扭动(绕中心点选择)的角度
vec3 tPosition=aPosition;
if(aPosition.y>yStart)
{//若不是最下面一层的顶点则计算扭动后的X、Z坐标
tPosition.x=(cos(tempAS)*aPosition.x-sin(tempAS)*aPosition.z);
tPosition.z=(sin(tempAS)*aPosition.x+cos(tempAS)*aPosition.z);
}
gl_Position = uMVPMatrix * vec4(tPosition,1); //根据总变换矩阵计算此次绘制此顶点的位置
passTexCoord = inTexCoord; //将接收的纹理坐标传递给片元着色器
}
"""
SphereFS = """
#version 330 core
precision mediump float;
uniform sampler2D tex; //纹理内容数据
in vec2 passTexCoord; //接收从顶点着色器过来的参数
out vec4 outColor; //输出到的片元颜色
void main(){
//给此片元从纹理中采样出颜色值
outColor = texture(tex, passTexCoord);
}
"""
class CuboMode:
def make_Mode(self):
if ( self.rows < 1 or self.cols < 1):
raise Exception("BoardMode can not be initialized!")
self.vertexs = np.array([], np.float32) # 位置FloatArray(numPoint * 3)
self.texcoords = np.array([], np.float32) # 纹理FloatArray(numPoint * 2)
self.indices = np.array([], np.int32) # 索引 ShortArray(numPoint * 6)
# 把矩形平铺在一个平面上
PI = np.pi
Y_MAX=1.5*0.5 #
Y_MIN=-1.5 *0.5 #
FD=6
hw=0.5 *0.5
self.vCount=FD*4*6 # 每个格子两个三角形,每个三角形3个顶点
yStart=Y_MIN
ySpan=(Y_MAX-Y_MIN)/FD
count=0
tCount=0
for i in range(FD):
x1=-hw
y1=yStart
z1=hw
x2=hw
y2=yStart
z2=hw
x3=hw
y3=yStart
z3=-hw
x4=-hw
y4=yStart
z4=-hw
x5=-hw
y5=yStart+ySpan
z5=hw
x6=hw
y6=yStart+ySpan
z6=hw
x7=hw
y7=yStart+ySpan
z7=-hw
x8=-hw
y8=yStart+ySpan
z8=-hw
self.vertexs=np.hstack((self.vertexs, np.array([x5,y5,z5], np.float32) )) #每个顶点xyz三个坐标,6个顶点//512
self.vertexs=np.hstack((self.vertexs, np.array([x1,y1,z1], np.float32) ))
self.vertexs=np.hstack((self.vertexs, np.array([x2,y2,z2], np.float32) ))
self.vertexs=np.hstack((self.vertexs, np.array([x5,y5,z5], np.float32) )) #每个顶点xyz三个坐标,6个顶点//526
self.vertexs=np.hstack((self.vertexs, np.array([x2,y2,z2], np.float32) ))
self.vertexs=np.hstack((self.vertexs, np.array([x6,y6,z6], np.float32) ))
self.vertexs=np.hstack((self.vertexs, np.array([x6,y6,z6], np.float32) )) #每个顶点xyz三个坐标,6个顶点//623
self.vertexs=np.hstack((self.vertexs, np.array([x2,y2,z2], np.float32) ))
self.vertexs=np.hstack((self.vertexs, np.array([x3,y3,z3], np.float32) ))
self.vertexs=np.hstack((self.vertexs, np.array([x6,y6,z6], np.float32) )) #每个顶点xyz三个坐标,6个顶点//637
self.vertexs=np.hstack((self.vertexs, np.array([x3,y3,z3], np.float32) ))
self.vertexs=np.hstack((self.vertexs, np.array([x7,y7,z7], np.float32) ))
self.vertexs=np.hstack((self.vertexs, np.array([x7,y7,z7], np.float32) ))
self.vertexs=np.hstack((self.vertexs, np.array([x3,y3,z3], np.float32) ))
self.vertexs=np.hstack((self.vertexs, np.array([x4,y4,z4], np.float32) ))
self.vertexs=np.hstack((self.vertexs, np.array([x7,y7,z7], np.float32) ))
self.vertexs=np.hstack((self.vertexs, np.array([x4,y4,z4], np.float32) ))
self.vertexs=np.hstack((self.vertexs, np.array([x8,y8,z8], np.float32) ))
self.vertexs=np.hstack((self.vertexs, np.array([x8,y8,z8], np.float32) ))
self.vertexs=np.hstack((self.vertexs, np.array([x4,y4,z4], np.float32) ))
self.vertexs=np.hstack((self.vertexs, np.array([x1,y1,z1], np.float32) ))
self.vertexs=np.hstack((self.vertexs, np.array([x8,y8,z8], np.float32) ))
self.vertexs=np.hstack((self.vertexs, np.array([x1,y1,z1], np.float32) ))
self.vertexs=np.hstack((self.vertexs, np.array([x5,y5,z5], np.float32) ))
yStart=yStart+ySpan
self.texcoords=np.hstack((self.texcoords, numpy.array([0,0,0,1,1,1,0,0,1,1,1,0], numpy.float32) ))
self.texcoords=np.hstack((self.texcoords, numpy.array([0,0,0,1,1,1,0,0,1,1,1,0], numpy.float32) ))
self.texcoords=np.hstack((self.texcoords, numpy.array([0,0,0,1,1,1,0,0,1,1,1,0], numpy.float32) ))
self.texcoords=np.hstack((self.texcoords, numpy.array([0,0,0,1,1,1,0,0,1,1,1,0], numpy.float32) ))
#-------------生成软糖数据库完毕-----------
def Image_Open(self):
img = Image.open(self.imgfilename)
imgData = np.array(list(img.getdata()), np.int8)
texture = glGenTextures(1)
glPixelStorei(GL_UNPACK_ALIGNMENT,1)
glBindTexture(GL_TEXTURE_2D, texture)
glPixelStorei(GL_UNPACK_ALIGNMENT,1)
glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_BORDER_COLOR, 1,0,0) # 如果用GL_CLAMP_TO_BORDER,还需要指定一个边缘颜色,超出的坐标为用户指定的边缘颜色。
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img.size[0], img.size[1],
0, GL_RGBA, GL_UNSIGNED_BYTE, imgData)
glBindTexture(GL_TEXTURE_2D, 0)
self.texid1 = texture
def __init__(self,rows,cols,imgfilename):
self.rows=rows
self.cols=cols
self.imgfilename = imgfilename
self.make_Mode()
# load shaders
self.program = myGL_Funcs.loadShaders(SphereVS, SphereFS)
#print('ok1')
glUseProgram(self.program)
self.vertIndex = glGetAttribLocation(self.program, b"aPosition")
# set up vertex array object (VAO)
self.vao = glGenVertexArrays(1)
glBindVertexArray(self.vao)
# set up VBOs
vertexData = numpy.array(self.vertexs, 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)
# 顶点纹理属性
texData = numpy.array(self.texcoords, numpy.float32)
self.texBuffer = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, self.texBuffer)
glBufferData(GL_ARRAY_BUFFER, 4*len(texData), texData, GL_STATIC_DRAW)
glEnableVertexAttribArray(1)
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0,None)
self.Image_Open()
# unbind VAO
glBindVertexArray(0)
glBindBuffer(GL_ARRAY_BUFFER, 0)
def render(self, pMatrix,angleSpan,yStart,ySpan):
# use shader
glUseProgram(self.program)
# set modelview matrix
glUniformMatrix4fv(glGetUniformLocation(self.program, 'uMVPMatrix'),
1, GL_FALSE, mvMatrix)
glUniform1f(glGetUniformLocation(self.program, "angleSpan"), angleSpan)
glUniform1f(glGetUniformLocation(self.program, "yStart"), yStart)
glUniform1f(glGetUniformLocation(self.program, "ySpan"), ySpan)
#// 启用多个纹理单元 绑定纹理对象
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, self.texid1);
glUniform1i(glGetUniformLocation(self.program, "tex"), 0); #// 设置纹理单元为0号
# bind VAO
glBindVertexArray(self.vao)
# draw
glDrawArrays(GL_TRIANGLES, 0,self.rows*self.cols*6 )
# unbind VAO
glBindVertexArray(0)
# glfw callback functions
def window_resize(window, width, height):
glViewport(0, 0, width, height)
if __name__ == '__main__':
import sys
import glfw
import OpenGL.GL as gl
cameraPos=np.array([0.0, 0.0, 4]) # 眼睛的位置(默认z轴的正方向)
cameraFront=np.array([0.0, 0.0, 0.0]) # 瞄准方向的参考点(默认在坐标原点)
cameraUp=np.array([0.0, 1.0, 0.0]) # 定义对观察者而言的上方(默认y轴的正方向)
# Initialize the library
if not glfw.init():
sys.exit()
# Create a windowed mode window and its OpenGL context
window = glfw.create_window(320, 240, "My OpenGL window", None, None)
if not window:
glfw.terminate()
sys.exit()
# set window's position
glfw.set_window_pos(window, 100, 100)
# set the callback function for window resize
glfw.set_window_size_callback(window, window_resize)
# make the context current
glfw.make_context_current(window)
glClearColor(0, 0.1, 0.1, 1)
glEnable(GL_DEPTH_TEST)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
mvMatrix=np.matrix(np.identity(4))
theta1=0
WIDTH_SPAN=6
board1=CuboMode(40,40,"wall.png",)
mvMatrix = pyrr.Matrix44.from_x_rotation(0.5)
# the main application loop
while not glfw.window_should_close(window):
width, height = glfw.get_framebuffer_size(window)
ratio = width / float(height)
currentFrame = 1.0*glfw.get_time()
glfw.poll_events()
gl.glViewport(0, 0, width, height)
gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
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.0,4.0,0.0)
theta1=currentFrame
board1.render( mvMatrix, theta1,-0.6, WIDTH_SPAN) #从y=-0.6以上开始扭
glfw.swap_buffers(window)
# terminate glfw, free up allocated resources
glfw.terminate()
五、参考资料
1、大龙10的简书://www.greatytc.com/p/49dec482a291
2、吴亚峰《OpenGL ES 3.x游戏开发》(下卷)