Source code for Elements.pyGLV.GL.Shader

"""
Shader classes
    
The Shader Compoment class is the dedicated to a specific type of data container in the Elements.pyECSS Component class
that of assembling, using and destroying OpenGL API shader programs

Based on the Composite and Iterator design patterns:

* https://refactoring.guru/design-patterns/composite
* https://github.com/faif/python-patterns/blob/master/patterns/structural/composite.py
* https://refactoring.guru/design-patterns/iterator
* https://github.com/faif/python-patterns/blob/master/patterns/behavioral/iterator.py

"""

from __future__         import annotations
from abc                import ABC, abstractmethod
import sys
from typing             import List
import os  

import OpenGL.GL as gl


from Elements.pyECSS.System import System
from Elements.pyECSS.Component import Component, ComponentDecorator, RenderMesh, CompNullIterator
from Elements.pyGLV.GL.VertexArray import VertexArray
from Elements.pyGLV.GL.Textures import Texture, Texture3D

[docs]class Shader(Component): """ A concrete OpenGL-GLSL Shader container Component class """ # --------------------------------------------------- # basic pass-through Vertex-Fragment Shader examples # other custom shaders can be imported from files # --------------------------------------------------- COLOR_VERT = """#version 410 layout (location=0) in vec4 vPosition; layout (location=1) in vec4 vColor; out vec4 color; void main() { gl_Position = vPosition; color = vColor; } """ COLOR_FRAG = """ #version 410 in vec4 color; out vec4 outputColor; void main() { outputColor = color; //outputColor = vec4(0.1, 0.1, 0.1, 1); } """ COLOR_VERT_MVP = """ #version 410 layout (location=0) in vec4 vPosition; layout (location=1) in vec4 vColor; out vec4 color; uniform mat4 modelViewProj; void main() { gl_Position = modelViewProj * vPosition; color = vColor; } """ COLOR_VERT_MVP_MANOS = """ #version 410 layout (location=0) in vec4 vPosition; layout (location=1) in vec4 vColor; out vec4 color; uniform mat4 modelViewProj; uniform vec3 my_color; void main() { gl_Position = modelViewProj * vPosition; # color = vec4(my_color,1); color = vec4(0.1, 0.1, 0.1, 1) } """ VERT_PHONG_MVP = """ #version 410 layout (location=0) in vec4 vPosition; layout (location=1) in vec4 vColor; layout (location=2) in vec4 vNormal; out vec4 pos; out vec4 color; out vec3 normal; uniform mat4 modelViewProj; uniform mat4 model; void main() { gl_Position = modelViewProj * vPosition; pos = model * vPosition; color = vColor; normal = mat3(transpose(inverse(model))) * vNormal.xyz; } """ FRAG_PHONG_MATERIAL = """ #version 410 in vec4 pos; in vec4 color; in vec3 normal; out vec4 outputColor; // Phong products uniform vec3 ambientColor; uniform float ambientStr; // Lighting uniform vec3 viewPos; uniform vec3 lightPos; uniform vec3 lightColor; uniform float lightIntensity; // Material uniform float shininess; uniform vec3 matColor; void main() { vec3 norm = normalize(normal); vec3 lightDir = normalize(lightPos - pos.xyz); vec3 viewDir = normalize(viewPos - pos.xyz); vec3 reflectDir = reflect(-lightDir, norm); // Ambient vec3 ambientProduct = ambientStr * ambientColor; // Diffuse float diffuseStr = max(dot(norm, lightDir), 0.0); vec3 diffuseProduct = diffuseStr * lightColor; // Specular float specularStr = pow(max(dot(viewDir, reflectDir), 0.0), 32); vec3 specularProduct = shininess * specularStr * color.xyz; vec3 result = (ambientProduct + (diffuseProduct + specularProduct) * lightIntensity) * matColor; outputColor = vec4(result, 1); } """ FRAG_PHONG = """ #version 410 in vec4 pos; in vec4 color; in vec3 normal; out vec4 outputColor; // Phong products uniform vec3 ambientColor; uniform float ambientStr; // Lighting uniform vec3 viewPos; uniform vec3 lightPos; uniform vec3 lightColor; uniform float lightIntensity; // Material uniform float shininess; uniform vec3 matColor; void main() { vec3 norm = normalize(normal); vec3 lightDir = normalize(lightPos - pos.xyz); vec3 viewDir = normalize(viewPos - pos.xyz); vec3 reflectDir = reflect(-lightDir, norm); // Ambient vec3 ambientProduct = ambientStr * ambientColor; // Diffuse float diffuseStr = max(dot(norm, lightDir), 0.0); vec3 diffuseProduct = diffuseStr * lightColor; // Specular float specularStr = pow(max(dot(viewDir, reflectDir), 0.0), 32); vec3 specularProduct = shininess * specularStr * color.xyz; vec3 result = (ambientProduct + (diffuseProduct + specularProduct) * lightIntensity) * color.xyz; outputColor = vec4(result, 1); } """ VERT_PHONG_MVP_ARMATURE = """ #version 410 layout (location=0) in vec4 vPosition; layout (location=1) in vec4 vColor; layout (location=2) in vec4 vNormal; layout (location=3) in vec3 vWeight1; layout (location=4) in vec3 vWeight2; layout (location=5) in vec3 vWeight3; out vec4 pos; out vec4 color; out vec3 normal; uniform mat4 bonePos1; uniform mat4 bonePos2; uniform mat4 bonePos3; uniform mat4 model; uniform mat4 view; uniform mat4 project; void main() { mat4 weightedModel = (vWeight1.x*bonePos1 + vWeight2.x*bonePos2 + vWeight3.x*bonePos3); gl_Position = project * view * weightedModel * vPosition; pos = weightedModel * vPosition; color = vColor; normal = mat3(transpose(inverse(weightedModel))) * vNormal.xyz; } """ SIMPLE_TEXTURE_VERT = """ #version 410 layout (location=0) in vec4 vPos; layout (location=1) in vec2 vTexCoord; out vec2 fragmentTexCoord; uniform mat4 model; uniform mat4 View; uniform mat4 Proj; void main() { gl_Position = Proj * View * model * vPos; fragmentTexCoord = vTexCoord; } """ SIMPLE_TEXTURE_VERT_MVP = """ #version 410 layout (location=0) in vec4 vPos; layout (location=1) in vec2 vTexCoord; out vec2 fragmentTexCoord; uniform mat4 modelViewProj; void main() { gl_Position = modelViewProj * vPos; fragmentTexCoord = vTexCoord; } """ SIMPLE_TEXTURE_FRAG = """ #version 410 in vec2 fragmentTexCoord; out vec4 color; uniform sampler2D ImageTexture; void main() { //vec2 flipped_texcoord = vec2(fragmentTexCoord.x, 1.0 - fragmentTexCoord.y); //color = texture(ImageTexture,flipped_texcoord); color = texture(ImageTexture,fragmentTexCoord); } """ SIMPLE_TEXTURE_PHONG_VERT = """ #version 410 layout (location=0) in vec4 vPos; layout (location=1) in vec4 vNormal; layout (location=2) in vec2 vTexCoord; out vec2 fragmentTexCoord; out vec4 pos; out vec3 normal; uniform mat4 model; uniform mat4 View; uniform mat4 Proj; void main() { gl_Position = Proj * View * model * vPos; pos = model * vPos; fragmentTexCoord = vTexCoord; normal = mat3(transpose(inverse(model))) * vNormal.xyz; } """ SIMPLE_TEXTURE_PHONG_FRAG = """ #version 410 in vec2 fragmentTexCoord; in vec4 pos; in vec3 normal; out vec4 outputColor; // Phong products uniform vec3 ambientColor; uniform float ambientStr; // Lighting uniform vec3 viewPos; uniform vec3 lightPos; uniform vec3 lightColor; uniform float lightIntensity; // Material uniform float shininess; //uniform vec3 matColor; uniform sampler2D ImageTexture; void main() { vec3 norm = normalize(normal); vec3 lightDir = normalize(lightPos - pos.xyz); vec3 viewDir = normalize(viewPos - pos.xyz); vec3 reflectDir = reflect(-lightDir, norm); // Ambient vec3 ambientProduct = ambientStr * ambientColor; // Diffuse float diffuseStr = max(dot(norm, lightDir), 0.0); vec3 diffuseProduct = diffuseStr * lightColor; // Specular float specularStr = pow(max(dot(viewDir, reflectDir), 0.0), 32); vec4 tex = texture(ImageTexture,fragmentTexCoord); vec3 specularProduct = shininess * specularStr * tex.xyz; vec3 result = (ambientProduct + (diffuseProduct + specularProduct) * lightIntensity) * tex.xyz; outputColor = vec4(result, 1); } """ TEXTURE_3D_VERT = """ #version 410 layout (location=0) in vec4 vPos; out vec3 TexCoords; uniform mat4 model; uniform mat4 View; uniform mat4 Proj; void main() { gl_Position = Proj * View * model * vPos; TexCoords = vPos.xyz; } """ TEXTURE_3D_FRAG = """ #version 410 in vec3 TexCoords; out vec4 FragColor; uniform samplerCube cubemap; void main() { FragColor = texture(cubemap, TexCoords); } """ TEXTURE_3D_PHONG_VERT = """ #version 410 """ TEXTURE_3D_PHONG_FRAG = """ #version 410 """ STATIC_SKYBOX_VERT = """ #version 410 layout (location = 0) in vec4 vPos; out vec3 TexCoords; uniform mat4 Proj; uniform mat4 View; void main() { mat4 viewPos = mat4(mat3(View)); //removes Translation gl_Position = Proj * viewPos * vPos; //gl_Position = Proj * View * vPos; // with Translation TexCoords = vPos.xyz; } """ STATIC_SKYBOX_FRAG = """ #version 410 out vec4 FragColor; in vec3 TexCoords; uniform samplerCube cubemap; void main() { FragColor = texture(cubemap, TexCoords); } """
[docs] def __init__(self, name=None, type=None, id=None, vertex_source=None, fragment_source=None, vertex_import_file=None, fragment_import_file=None ): super().__init__(name, type, id) self._parent = self self._texture = None self._texture3D = None self._glid = None self._mat4fDict = {} self._mat3fDict = {} self._float1fDict = {} self._float3fDict = {} self._float4fDict = {} self._aaaDict = {} self._textureDict = {} self._texture3DDict ={} # Prioritize import from file, and then from shader name if vertex_import_file is not None: try: f = open(vertex_import_file, 'r') except OSError: print ("Could not open/read vertex shader file:", vertex_import_file) sys.exit() with f: self._vertex_source = f.read() else: if not vertex_source: self._vertex_source = Shader.COLOR_VERT else: self._vertex_source = vertex_source if fragment_import_file is not None: try: f = open(fragment_import_file, 'r') except OSError: print ("Could not open/read fragment shader file:", fragment_import_file) sys.exit() with f: self._fragment_source = f.read() else: if not fragment_source: self._fragment_source = Shader.COLOR_FRAG else: self._fragment_source = fragment_source
#self.init(vertex_source, fragment_source) #init Shader under a valid GL context @property def glid(self): return self._glid @property def vertex_source(self): return self._vertex_source @vertex_source.setter def vertex_source(self, value): self._vertex_source = value @property def fragment_source(self): return self._fragment_source @fragment_source.setter def fragment_source(self, value): self._fragment_source = value @property def mat4fDict(self): return self._mat4fDict @mat4fDict.setter def mat4fDict(self, value): self._mat4fDict = value @property def mat3fDict(self): return self._mat3fDict @mat3fDict.setter def mat3fDict(self, value): self._mat3fDict = value @property def float1fDict(self): return self._float1fDict @float1fDict.setter def float1fDict(self, value): self._float1fDict = value @property def float3fDict(self): return self._float3fDict @float3fDict.setter def float3fDict(self, value): self._float3fDict = value @property def float4fDict(self): return self._float4fDict @float4fDict.setter def float4fDict(self, value): self._float4fDict = value @property def textureDict(self): return self._textureDict @textureDict.setter def textureDict(self, value): self._textureDict = value @property def texture3DDict(self): return self._texture3DDict @texture3DDict.setter def texture3DDict(self, value): self._texture3DDict = value def __del__(self): gl.glUseProgram(0) if self._glid: gl.glDeleteProgram(self._glid) def disableShader(self): gl.glUseProgram(0) def enableShader(self): gl.glUseProgram(self._glid) if self._mat4fDict is not None: for key, value in self._mat4fDict.items(): loc = gl.glGetUniformLocation(self._glid, key) gl.glUniformMatrix4fv(loc, 1, True, value) if self._mat3fDict is not None: for key, value in self._mat3fDict.items(): loc = gl.glGetUniformLocation(self._glid, key) gl.glUniformMatrix3fv(loc, 1, True, value) if self._float1fDict is not None: for key, value in self._float1fDict.items(): loc = gl.glGetUniformLocation(self._glid, key) # gl.glUniform1fv(loc, 1, True, value) Bad call gl.glUniform1fv(loc, 1, value) if self._float3fDict is not None: for key, value in self._float3fDict.items(): loc = gl.glGetUniformLocation(self._glid, key) # gl.glUniform3fv(loc, 1, True, value) Bad call gl.glUniform3fv(loc, 1, value) if self._float4fDict is not None: for key, value in self._float4fDict.items(): loc = gl.glGetUniformLocation(self._glid, key) # gl.glUniform4fv(loc, 1, True, value) Bad call gl.glUniform4fv(loc, 1, value) if self._textureDict is not None: for key,value in self._textureDict.items(): if self._texture is None: loc = gl.glGetUniformLocation(self._glid,key) gl.glUniform1i(loc, value._texure_channel) value.bind() if self._texture3DDict is not None: for key,value in self._texture3DDict.items(): if self._texture3D is None: loc = gl.glGetUniformLocation(self._glid,key) gl.glUniform1i(loc,0) value.bind() @staticmethod def _compile_shader(src, shader_type): src = open(src, 'r').read() if os.path.exists(src) else src #src = src.decode('ascii') if isinstance(src, bytes) else src.decode shader = gl.glCreateShader(shader_type) gl.glShaderSource(shader, src) gl.glCompileShader(shader) status = gl.glGetShaderiv(shader, gl.GL_COMPILE_STATUS) src = ('%3d: %s' % (i+1, l) for i,l in enumerate(src.splitlines()) ) # print('Compile shader success for %s\n%s\n%s' % (shader_type, status, src)) if not status: log = gl.glGetShaderInfoLog(shader).decode('ascii') gl.glDeleteShader(shader) src = '\n'.join(src) print('Compile failed for %s\n%s\n%s' % (shader_type, log, src)) return None return shader def update(self): # print(self.getClassName(), ": update() called") pass def accept(self, system: System): """ Accepts a class object to operate on the Component, based on the Visitor pattern. :param system: [a System object] :type system: [System] """ system.apply2Shader(self) def init(self): """ shader extra initialisation from raw strings or source file names """ vert = self._compile_shader(self._vertex_source, gl.GL_VERTEX_SHADER) frag = self._compile_shader(self._fragment_source, gl.GL_FRAGMENT_SHADER) if vert and frag: self._glid = gl.glCreateProgram() gl.glAttachShader(self._glid, vert) gl.glAttachShader(self._glid, frag) gl.glLinkProgram(self._glid) gl.glDeleteShader(vert) gl.glDeleteShader(frag) status = gl.glGetProgramiv(self._glid, gl.GL_LINK_STATUS) if not status: print(gl.glGetProgramInfoLog(self._glid).decode('ascii')) gl.glDeleteProgram(self._glid) self._glid = None def __iter__(self) ->CompNullIterator: """ A component does not have children to iterate, thus a NULL iterator """ return CompNullIterator(self)
[docs]class ShaderGLDecorator(ComponentDecorator): """A decorator of the Shader Compoment to decorate it with custom standard pass-through shader attributes :param ComponentDecorator: [description] :type ComponentDecorator: [type] """ def init(self): self.component.init() def update(self): self.component.update() # add here custom shader draw calls, e.g. glGetUniformLocation(), glUniformMatrix4fv() etc.add() # e.g. loc = GL.glGetUniformLocation(shid, 'projection') # GL.glUniformMatrix4fv(loc, 1, True, projection) def setUniformVariable(self,key, value, mat4=False, mat3=False, float1=False, float3=False, float4=False,texture=False,texture3D=False): if mat4: self.component.mat4fDict[key]=value if mat3: self.component.mat3fDict[key]=value if float1: self.component.float1fDict[key]=value if float3: self.component.float3fDict[key]=value if float4: self.component.float4fDict[key]=value if texture: self.component.textureDict[key]= value #self.component.textureDict[key]=Texture(value) if texture3D: self.component.texture3DDict[key]=Texture3D(value) def enableShader(self): self.component.enableShader() def disableShader(self): self.component.disableShader() def get_glid(self): return self.component.glid def accept(self, system: System): """ Accepts a class object to operate on the Component, based on the Visitor pattern. :param system: [a System object] :type system: [System] """ system.apply2ShaderGLDecorator(self) def __iter__(self) ->CompNullIterator: """ A concrete component Decorator does not have children to iterate, thus a NULL iterator """ return CompNullIterator(self)
[docs]class InitGLShaderSystem(System): """Initialise outside of the rendering loop RenderMesh, Shader, VertexArray, ShaderGLDecorator classes """ def init(self): pass def update(self): """ """ #add here custom Shader render calls def apply2RenderMesh(self, renderMesh:RenderMesh): """ method to be subclassed for behavioral or logic computation when visits Components. """ # print(f'\n{renderMesh} accessed within {self.getClassName()}::apply2RenderMesh() \n') self.update() def apply2VertexArray(self, vertexArray:VertexArray): """ method to be subclassed for behavioral or logic computation when visits Components. """ # print(f'\n{vertexArray} accessed within {self.getClassName()}::apply2RenderMesh() \n') # Access parent Entity's RenderMesh parentEntity = vertexArray.parent parentRenderMesh = parentEntity.getChildByType(RenderMesh.getClassName()) if parentRenderMesh: # Copy RenderMesh::vertex_attributes and vertex_indices to vertexArray vertexArray.attributes = parentRenderMesh.vertex_attributes vertexArray.index = parentRenderMesh.vertex_index vertexArray.init() else: print("\n no RenderMesh to copy vertex attributes from! \n") # Init vertexArray def apply2Shader(self, shader:Shader): """ method to be subclassed for behavioral or logic computation when visits Components. """ # if there is no ShaderGLDecorator, init Shader # for the moment assume that the user will not be directly adding both a shader and shaderDecorator at scenegraph level # we can prevent this at ECSSManager level, but not at scenegraph direct access level shader.init() # print(f'\n{shader} accessed within {self.getClassName()}::apply2Shader() \n') def apply2ShaderGLDecorator(self, shaderGLDecorator:ShaderGLDecorator): """ method to be subclassed for behavioral or logic computation when visits Components. """ #init ShaderGLDecorator if there is such a node shaderGLDecorator.init()
# print(f'\n{shaderGLDecorator} accessed within {self.getClassName()}::apply2ShaderGLDecorator() \n')
[docs]class RenderGLShaderSystem(System): """A RenderSystem specifically for GL vertex and fragment Shaders and associated VertexArray components attached to a specific Entity """ def init(self): pass def apply2VertexArray(self, vertexArray:VertexArray): """ Main GPU rendering is initiated when a vertexArray node is encountered and if a Shader/Shaderdecorator and RenderMesh components are present method to be subclassed for behavioral or logic computation when visits RenderMesh Components of the parent EntityNode. Separate SystemDecorator is needed for each case, e.g. for rendering with GL vertex and fragment Shaders: RenderShaderSystem Actuall RenderShaderSystem rendering is initiated in this update call, according to following pseudocode: """ parentEntity = vertexArray.parent compRenderMesh = parentEntity.getChildByType(RenderMesh.getClassName()) compShader = parentEntity.getChildByType(Shader.getClassName()) if not compShader: compShader = parentEntity.getChildByType(ShaderGLDecorator.getClassName()) if (vertexArray and compRenderMesh and compShader): self.render(vertexArray, compRenderMesh, compShader) def render(self, vertexArray:VertexArray = None, compRenderMesh:RenderMesh = None, compShader=None): """ - Shader-based main draw(): - retrive ShaderDecorator glid - useShaderProgram(ShaderDecorator.glid) - call ShaderDecorator::update to pass on uniform shader parameters to GPU - renderMeshVertexArray.execute(gl.GL_TRIANGLES) - userShaderProgram(0) #clean GL state """ # retrieve L2C matrix here to pass it as uniform to shader #add here custom Shader render calls compShader.enableShader() #call main draw from VertexArray vertexArray.update() compShader.disableShader()
# print (f'\nMain shader GL render within {self.getClassName()}::render() \n')