pythonopengl,在这里更新一组顶点,在一个缓冲区中发送

vuktfyat  于 2023-08-04  发布在  Python
关注(0)|答案(1)|浏览(97)

我是图形开发的新手,我正在尝试使用Gerstner wave来显示wave。我的代码主要来自Youtube GetIntoGameDev,除了我创建飞机的部分。我有一些优化的问题和一些想法,它可以从哪里来。
我在更新顶点的位置时遇到了一些问题。首先,我发送一个三角形的顶点缓冲区,每个点至少存在于一个三角形中,最多4个,所以当我必须更新我改变4个点。由于这是指数级的网格大小,我得到了很多延迟。有没有办法只发送顶点,然后面作为一个三元组的列表,然后只更新顶点。
我的下一个问题是关于调用'update':有没有办法让它每帧调用一次?

main.py

import glfw
import glfw.GLFW as GLFW_CONSTANTS
from OpenGL.GL import *
from OpenGL.GL.shaders import compileProgram,compileShader
import numpy as np
import pyrr
from PIL import Image
from math import cos,sin
SCREEN_WIDTH = 1280
SCREEN_HEIGHT = 720
RETURN_ACTION_CONTINUE = 0
RETURN_ACTION_END = 1

def initialize_glfw():
    glfw.init()
    glfw.window_hint(GLFW_CONSTANTS.GLFW_CONTEXT_VERSION_MAJOR, 3 )
    glfw.window_hint(GLFW_CONSTANTS.GLFW_CONTEXT_VERSION_MINOR, 3 )
    glfw.window_hint(GLFW_CONSTANTS.GLFW_OPENGL_PROFILE, GLFW_CONSTANTS.GLFW_OPENGL_CORE_PROFILE ) 
    glfw.window_hint(GLFW_CONSTANTS.GLFW_OPENGL_FORWARD_COMPAT, GLFW_CONSTANTS.GLFW_TRUE ) 
    glfw.window_hint(GLFW_CONSTANTS.GLFW_DOUBLEBUFFER, GLFW_CONSTANTS.GLFW_FALSE ) 

    window = glfw.create_window(SCREEN_WIDTH, SCREEN_HEIGHT, "Water simulation", None, None)
    glfw.make_context_current(window)
    glfw.set_input_mode(window, GLFW_CONSTANTS.GLFW_CURSOR, GLFW_CONSTANTS.GLFW_CURSOR_HIDDEN)

    return window
p=0.05

ky =[1,23,   5, 8]
kx =[-1,-7, -13, -12]
A= [0.05,0.001,0.002, 0.002]
w = [200,800,500, 550]
omega= [i*0.005 for i in w]

def genVertices():
    vertices = []
    N = int(1/p)
    for i in range(N):
        for j in range(N):
            x,y,z= p*i,p*j,0
            vertices.append([x,y,z]) 

    return vertices

def genEdges(vertices):
    edges = []
    N = int(1/p)
    for i in range(len(vertices)):
        if  i%N != 0:
            edges.append([i-1,i])

    for i in range(N):
        for j in range(N-1):
            edges.append([j*N+i,(j+1)*N+i])
    return edges

def genFacesTri(vertices):
    faces = []
    N=int(1/p)
    for i in range(N-1):
        for j in range(N-1):
            faces += vertices[N*j+i]
            faces += [0,1]
            faces += vertices[i+1+N*j]
            faces += [1,0]
            faces += vertices[i+N+N*j]
            faces += [1,1]
            
    
    for i in range(1,N):
        for j in range(N-1):
            faces+= vertices[N*j+i]
            faces += [1,1]
            faces += vertices[i-1+N+N*j]
            faces += [0,1]
            faces += vertices[i+N+N*j]
            faces += [1,0]
    return faces

faces = genFacesTri(genVertices())


class Player:
    def __init__(self, position):
        self.position = np.array(position, np.float32)
        self.theta = 0
        self.phi = 0
        self.update_vectors()

    def update_vectors(self):
        self.forwards = np.array(
            [
                np.cos(np.deg2rad(self.theta)) * np.cos(np.deg2rad(self.phi)),
                np.sin(np.deg2rad(self.theta)) * np.cos(np.deg2rad(self.phi)),
                np.sin(np.deg2rad(self.phi))
            ]
        )

        globalUp = np.array([0,0,1], np.float32)

        self.right = np.cross(self.forwards, globalUp)
        self.up = np.cross(self.right, self.forwards)

class Scene:
    def __init__(self):
        #mis cube 7:40
        self.player = Player(position=[0,0,0])
        self.plane = Plane(
            position = [0,0,0],
            eulers = [0,0,0]
        )
        self.entities =  [
            self.plane 
        ]
    def update(self,t):
        pass

    def move_player(self,dPos):
        dPos = np.array(dPos, np.float32)
        self.player.position += dPos

    def spin_player(self, dTheta, dPhi):
        self.player.theta += dTheta
        if self.player.theta >360:
            self.player.theta -= 360
        if self.player.theta < 0:
            self.player.theta += 360

        self.player.phi = min(89, max(self.player.phi + dPhi, -89))
        self.player.update_vectors()

class GraphicsEngine:
    def __init__(self):
        self.mesh = PlaneMesh(vertices=faces)
        self.water_texture = Material("gfx/water_texture.jpg")

        glClearColor(0.1, 0.2, 0.2, 1)

        self.shader = self.create_shader(vertex_filepath = "shaders/vertex.txt", fragment_filepath = "shaders/fragment.txt")
        
        glUseProgram(self.shader)
        glUniform1i(glGetUniformLocation(self.shader, "imageTexture"), 0)
        glEnable(GL_DEPTH_TEST)
        projection_transform = pyrr.matrix44.create_perspective_projection(
            fovy = 45, aspect = 1280/720, 
            near = 0.001, far = 10, dtype=np.float32
        )
        glUniformMatrix4fv(
            glGetUniformLocation(self.shader,"projection"),
            1, GL_FALSE, projection_transform
        )

        self.modelMatrixLocation = glGetUniformLocation(self.shader,"model")
        self.viewMatrixLocation = glGetUniformLocation(self.shader,"view")
    def create_shader(self,vertex_filepath: str, fragment_filepath: str) -> int:
        with open(vertex_filepath,'r') as f:
            vertex_src = f.readlines()

        with open(fragment_filepath,'r') as f:
            fragment_src = f.readlines()
        
        shader = compileProgram(compileShader(vertex_src, GL_VERTEX_SHADER),
                                compileShader(fragment_src, GL_FRAGMENT_SHADER))
        
        return shader
    def render(self,scene):
            

                #refresh screen
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
            glUseProgram(self.shader)

            

            view_transform = pyrr.matrix44.create_look_at(
                eye = scene.player.position,
                target = scene.player.position+scene.player.forwards, 
                up = scene.player.up,
                dtype=np.float32)
            
            glUniformMatrix4fv(self.viewMatrixLocation, 1, GL_FALSE, view_transform)

            self.water_texture.use()
            glBindVertexArray(self.mesh.vao)

            for entity in scene.entities:
                model_transform = pyrr.matrix44.create_identity(np.float32)

                model_transform = pyrr.matrix44.multiply(
                    m1=model_transform, 
                    m2=pyrr.matrix44.create_from_eulers(
                        eulers = entity.eulers,
                        dtype = np.float32
                    )
                )
                model_transform = pyrr.matrix44.multiply(
                    m1=model_transform, 
                    m2=pyrr.matrix44.create_from_translation(
                        vec=np.array(entity.position),dtype=np.float32
                    )
                )
            
                glUniformMatrix4fv(
                    self.modelMatrixLocation, 1, GL_FALSE, 
                    model_transform)
                
                glDrawArrays(GL_TRIANGLES, 0, self.mesh.vertex_count)

            glFlush()
    def quit(self):
        self.mesh.destroy()
        self.water_texture.destroy()
        glDeleteProgram(self.shader)

class Plane:

    def __init__(self, position: list[float], eulers: list[float]):

        self.position = np.array(position, dtype=np.float32)
        self.eulers = np.array(eulers, dtype=np.float32)

class App:

    def __init__(self, window):
            self.window = window
            self.renderer = GraphicsEngine()
            self.scene = Scene()

            self.lastTime = glfw.get_time()
            self.currentTime = 0
            self.numFrames = 0
            self.frameTime = 0 

            
            self.walk_offset_lookup = {
                1: 0,
                2: 90,
                3: 45,
                4: 180,
                6: 135,
                7: 9,
                8: 270,
                9: 315,
                11: 0,
                12: 225,
                13: 270,
                25: 180,
            }

            self.run()     
    
    def run(self) -> None:
        """ Run the app """

        running = True
        while (running):
            
            if glfw.window_should_close(self.window) or glfw.get_key(self.window, GLFW_CONSTANTS.GLFW_KEY_ESCAPE) == GLFW_CONSTANTS.GLFW_PRESS:
                running = False

            self.handleKeys()
            self.handleMouse()
            glfw.poll_events()

            self.scene.update(self.frameTime / 16.7)
            self.renderer.render(self.scene)
           
            #timing
            self.calculateFramerate()

        self.quit()

    
    def handleKeys(self):


        """
            z : 1 -> 0 degrees
            q : 2 -> 90 degrees
            z & q : 3 -> 45 degrees
            s : 4 -> 180 degrees
            z & s :  5 -> x
            q & s : 6 -> 135 degrees
            z & q & s : 7 -> 90 degrees
            d : 8 -> 270 degrees
            z & d : 9 -> 315 degrees
            q & d : 10 -> x
            z & q & d : 11 -> 0 degrees
            s & d : 12 -> 225 degrees
            z & s & d : 13 -> 270 degrees
            q & s & d : 14 -> 180 degrees
            w & a & s & d: 15 -> x
        """

        combo = 0
        directionModifier = 0 

        if glfw.get_key(self.window, GLFW_CONSTANTS.GLFW_KEY_W) == GLFW_CONSTANTS.GLFW_PRESS:
            combo += 1 
        if glfw.get_key(self.window, GLFW_CONSTANTS.GLFW_KEY_A) == GLFW_CONSTANTS.GLFW_PRESS:
            combo += 2
        if glfw.get_key(self.window, GLFW_CONSTANTS.GLFW_KEY_S) == GLFW_CONSTANTS.GLFW_PRESS:
            combo += 4
        if glfw.get_key(self.window, GLFW_CONSTANTS.GLFW_KEY_D) == GLFW_CONSTANTS.GLFW_PRESS:
            combo += 8
        if glfw.get_key(self.window, GLFW_CONSTANTS.GLFW_KEY_SPACE) == GLFW_CONSTANTS.GLFW_PRESS:
            self.scene.player.position[2] += 0.01
        if glfw.get_key(self.window, GLFW_CONSTANTS.GLFW_KEY_LEFT_CONTROL) == GLFW_CONSTANTS.GLFW_PRESS:
            self.scene.player.position[2] -= 0.01
        

        if combo in self.walk_offset_lookup:
            directionModifier = self.walk_offset_lookup[combo]
            self.scene.player.update_vectors()
            dPos = [
                0.05* self.frameTime / 16.7  * np.cos(np.deg2rad(self.scene.player.theta + directionModifier)),
                0.05 *self.frameTime / 16.7  * np.sin(np.deg2rad(self.scene.player.theta + directionModifier)),
                0
            ]
            self.scene.move_player(dPos)
    def handleMouse(self):
        (x,y) = glfw.get_cursor_pos(self.window)
        rate = 0.2* self.frameTime / 16.7 
        theta_increment = rate * ((SCREEN_WIDTH/2) - x)
        phi_increment = rate * ((SCREEN_HEIGHT/2) - y)
        self.scene.spin_player(theta_increment,phi_increment)
        glfw.set_cursor_pos(self.window, SCREEN_WIDTH/2, SCREEN_HEIGHT/2)

    def calculateFramerate(self):
        self.currentTime = glfw.get_time()
        delta = self.currentTime - self.lastTime
        if (delta >= 1):
            framerate = max(1, int( self.numFrames / delta))
            glfw.set_window_title(self.window, f"Ocean : {framerate} fps")

            self.lastTime = self.currentTime
            self.numFrames = - 1
            self.frameTime = float(1000/max(1,framerate))
        self.numFrames+=1 

        #Problem
        if (delta >= .1):
             #update plane
            self.renderer.mesh.update(glfw.get_time())

    def quit(self):
        self.renderer.quit()
        
class PlaneMesh:

    def __init__(self, vertices):

        # x, y, z, s, t
    
        self.vertex_count = len(vertices)//5
        self.vertices = np.array(vertices, dtype=np.float32)
        self.initialVertices = self.vertices.copy()

        self.vao = glGenVertexArrays(1)
        glBindVertexArray(self.vao)
        self.vbo = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
        glBufferData(GL_ARRAY_BUFFER, self.vertices.nbytes, self.vertices, GL_STATIC_DRAW)

        glEnableVertexAttribArray(0)
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 20, ctypes.c_void_p(0))

        glEnableVertexAttribArray(1)
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 20, ctypes.c_void_p(12))


    def update(self,t):
        for i in range(0,len(self.vertices),5):
            x0,y0,z0 = self.initialVertices[i],self.initialVertices[i+1],self.initialVertices[i+2]
            x,y,z = x0,y0,z0
            for j in range(len(kx)):
                xi = kx[j]*x0+ky[j]*y0-omega[j]*t
                x +=  kx[j]*A[j]*cos(xi)
                z += A[j]*sin(xi)
                y +=  ky[j]*A[j]*cos(xi)
            self.vertices[i], self.vertices[i+1], self.vertices[i+2] = x,y,z

        glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
        glBufferSubData(GL_ARRAY_BUFFER, 0, self.vertices.nbytes, self.vertices)



    def destroy(self) -> None:
        glDeleteVertexArrays(1,(self.vao,))
        glDeleteBuffers(1,(self.vbo,))

class Material:

    
    def __init__(self, filepath: str):

        self.texture = glGenTextures(1)
        glBindTexture(GL_TEXTURE_2D, self.texture)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)

        with Image.open(filepath, mode = 'r') as image:
            image_width,image_height = image.size
            image = image.convert("RGBA")
            img_data = bytes(image.tobytes())
            glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,image_width,image_height,0,GL_RGBA,GL_UNSIGNED_BYTE,img_data)
        glGenerateMipmap(GL_TEXTURE_2D)

    def use(self) -> None:
        """
            Arm the texture for drawing.
        """

        glActiveTexture(GL_TEXTURE0)
        glBindTexture(GL_TEXTURE_2D,self.texture)

    def destroy(self) -> None:
        """
            Free the texture.
        """

        glDeleteTextures(1, (self.texture,))

window = initialize_glfw()
my_app = App(window)

字符串

vertex.txt

#version 330 core

layout (location=0) in vec3 vertexPos;
layout (location=1) in vec2 vertexTexCoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

out vec2 fragmentTexCoord;

void main()
{
    gl_Position = projection * model * view * vec4(vertexPos, 1.0);
    fragmentTexCoord = vertexTexCoord;
}

fragment.txt

#version 330 core

in vec2 fragmentTexCoord;

uniform sampler2D imageTexture;

out vec4 color;

void main()
{
    color = texture(imageTexture, fragmentTexCoord);
    color = color * vec4(1,1,1,0.5);
}


我这样做的方式适用于小网格p = 0.1p = 0.04,但它开始滞后,我认为有优化的空间。
谢谢你,我很好

vktxenjb

vktxenjb1#

简答:

通常,当人们希望网格以可预测的方式更新每一帧时,他们使用GPU,即着色器来修改它们。然而,大多数时候,您只需要传递一个矩阵,并将其与顶点着色器中的顶点相乘。

详情:

在这里,你可以指定t和你的其他变量作为一个统一变量,类似于你指定MVP矩阵的方式,并将整个update函数移动到你的顶点着色器:

#version 330 core

layout (location=0) in vec3 vertexPos;
layout (location=1) in vec2 vertexTexCoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

uniform float t;
uniform vec4 kx;
uniform vec4 ky;
uniform vec4 A;
uniform vec4 omega;

out vec2 fragmentTexCoord;

vec3 update(vec3 vpos) {
    vec3 updatedPos = vpos;
    for (int i = 0; i < 4; i++) {
        float xi = kx[i] * vpos.x + ky[i] * vpos.y - omega[j] * t
        updatedPos += vec3(kx[i]*A[i]*cos(xi), A[i]*sin(xi), ky[i]*A[i]*cos(xi))
    }
    return updatedPos;
}

void main()
{
    vec3 updatedPos = update(vertexPos);
    gl_Position = projection * model * view * vec4(updatedPos, 1.0);
    fragmentTexCoord = vertexTexCoord;
}

字符串
但在这种情况下,就像在许多其他情况下一样,你不需要执行创建一个单独的函数的麻烦,实际上可以将变换表示为一个矩阵,这样你就可以做这样的事情,这就简单得多:

#version 330 core

layout (location=0) in vec3 vertexPos;
layout (location=1) in vec2 vertexTexCoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform mat4 updateMatrix; # the matrix that performs the 'update' transform

out vec2 fragmentTexCoord;

void main()
{
    gl_Position = projection * model * view * updateMatrix * vec4(update, 1.0); # multiply it like this
    fragmentTexCoord = vertexTexCoord;
}


然后你只需要在CPU代码中指定矩阵。如果你的效率更高,你可以将你的变换矩阵乘以model,这样你就不必处理额外的统一,但在几乎所有的情况下,它在着色器代码中并不是什么麻烦。
当然,还有更精确和更高级的方法来更新网格,比如Geometry ShadersGeometry Shaders是顶点和片段着色器之间的中间阶段,控制primitives的创建。

相关问题