Beispiel #1
0
class TestContext(BaseContext):
    """Demonstrates use of attribute types in GLSL
    """
    def OnInit(self):
        """Initialize the context"""
        '''== Phong and Blinn Reflectance ==

        A shiny surface will tend to have a "bright spot" at the point 
        on the surface where the angle of incidence for the reflected
        light ray and the viewer's ray are (close to) equal.  
        A perfect mirror would have the brights spot solely when the 
        two vectors are exactly equal, while a perfect Lambertian
        surface would have the "bright spot" spread across the entire
        surface.
        
        The Phong rendering process models this as a setting, traditionally
        called material "shininess" in Legacy OpenGL.  This setting acts 
        as a power which raises the cosine (dot product) of the 
        angle between the reflected ray and the eye.  The calculation of 
        the cosine (dot product) of the two angles requires that we do 
        a dot product of the two angles once for each vertex/fragment 
        for which we wish to calculate the specular reflectance, we also 
        have to find the angle of reflectance before we can do the 
        calculation:'''
        """
            L_dir = (V_pos-L_pos)
            R = 2N*(dot( N, L_dir))-L_dir
            // Note: in eye-coordinate system, Eye_pos == (0,0,0)
            Spec_factor = pow( dot( R, V_pos-Eye_pos ), shininess)
        """
        '''which, as we can see, involves the vertex position in a number 
        of stages of the operation, so requires recalculation all through 
        the rendering operation.
        
        There is, however, a simplified version of Phong Lighting called 
        [http://en.wikipedia.org/wiki/Blinn%E2%80%93Phong_shading_model Blinn-Phong]
        which notes that if we were to do all of our calculations in 
        "eye space", and were to assume that (as is normal), the eye 
        and light coordinates will not change for a rendering pass,
        (note: this limits us to directional lights!) we 
        can use a pre-calculated value which is the bisecting angle
        between the light-vector and the view-vector, called the 
        "half vector" to 
        perform approximately the same calculation.  With this value:'''
        """
            // note that in Eye coordinates, Eye_EC_dir == 0,0,-1
            H = normalize( Eye_EC_dir + Light_EC_dir )
            Spec_factor = pow( dot( H, N ), shininess )
        """
        '''Note: however, that the resulting Spec_factor is not *precisely*
        the same value as the original calculation, so the "shininess"
        exponent must be slightly lower to approximate the value that
        Phong rendering would achieve.  The value is, however, considered
        close to "real world" materials, so the Blinn method is generally 
        preferred to Phong.
        
        Traditionally, n_dot_pos would be cut off at 0.0, but that would 
        create extremely hard-edged cut-offs for specular color.  Here 
        we "fudge" the result by 0.05
        '''
        phong_weightCalc = """
        vec2 phong_weightCalc( 
            in vec3 light_pos, // light position
            in vec3 half_light, // half-way vector between light and view
            in vec3 frag_normal, // geometry normal
            in float shininess
        ) {
            // returns vec2( ambientMult, diffuseMult )
            float n_dot_pos = max( 0.0, dot( 
                frag_normal, light_pos
            ));
            float n_dot_half = 0.0;
            if (n_dot_pos > -.05) {
                n_dot_half = pow(max(0.0,dot( 
                    half_light, frag_normal
                )), shininess);
            }
            return vec2( n_dot_pos, n_dot_half);
        }		
        """
        '''We are going to use per-fragment rendering.
        As a result, our vertex shader becomes very simple, just arranging
        for the Normals to be varied across the surface.
        '''
        vertex = shaders.compileShader(
            """
        attribute vec3 Vertex_position;
        attribute vec3 Vertex_normal;
        
        varying vec3 baseNormal;
        void main() {
            gl_Position = gl_ModelViewProjectionMatrix * vec4( 
                Vertex_position, 1.0
            );
            baseNormal = gl_NormalMatrix * normalize(Vertex_normal);
        }""", GL_VERTEX_SHADER)
        '''Our fragment shader looks much like our previous tutorial's 
        vertex shader.  As before, we have lots of uniform values,
        but now we also calculate the light's half-vector (in eye-space 
        coordinates).  The phong_weightCalc function does the core Blinn 
        calculation, and we simply use the resulting factor to add to 
        the colour value for the fragment.
        
        Note the use of the eye-coordinate-space to simplify the 
        half-vector calculation, the eye-space eye-vector is always 
        the same value (pointing down the negative Z axis), 
        and the eye-space eye-coordinate is always (0,0,0), so the 
        eye-to-vertex vector is always the eye-space vector position.
        '''
        fragment = shaders.compileShader(
            phong_weightCalc + """
        uniform vec4 Global_ambient;
        
        uniform vec4 Light_ambient;
        uniform vec4 Light_diffuse;
        uniform vec4 Light_specular;
        uniform vec3 Light_location;
        
        uniform float Material_shininess;
        uniform vec4 Material_specular;
        uniform vec4 Material_ambient;
        uniform vec4 Material_diffuse;
        
        varying vec3 baseNormal;
        void main() {
            // normalized eye-coordinate Light location
            vec3 EC_Light_location = normalize(
                gl_NormalMatrix * Light_location
            );
            // half-vector calculation 
            vec3 Light_half = normalize(
                EC_Light_location - vec3( 0,0,-1 )
            );
            vec2 weights = phong_weightCalc(
                EC_Light_location,
                Light_half,
                baseNormal,
                Material_shininess
            );
            
            gl_FragColor = clamp( 
            (
                (Global_ambient * Material_ambient)
                + (Light_ambient * Material_ambient)
                + (Light_diffuse * Material_diffuse * weights.x)
                // material's shininess is the only change here...
                + (Light_specular * Material_specular * weights.y)
            ), 0.0, 1.0);
        }
        """, GL_FRAGMENT_SHADER)

        self.shader = shaders.compileProgram(vertex, fragment)
        '''Here's the call that creates the two VBOs and the 
        count of records to render from them. If you're curious 
        you can read through the source code of the 
        OpenGLContext.scenegraph.quadrics module to read the 
        mechanism that generates the values.
        
        The sphere is a simple rendering mechanism, as for a 
        unit-sphere at the origin, the sphere's normals are the 
        same as the sphere's vertex coordinate.  The complexity 
        comes primarily in generating the triangle indices that 
        link the points generated.
        '''
        self.coords, self.indices, self.count = Sphere(radius=1).compile()
        '''We have a few more uniforms to control the specular 
        components.  Real-world coding would also calculate the 
        light's half-vector and provide it as a uniform (so that 
        it would only need to be calculated once), but we are going 
        to do the half-vector calculation in the shader to make 
        it obvious what is going on.  The legacy OpenGL pipeline 
        provides the value pre-calculated as part of the light structure 
        in GLSL.
        '''
        for uniform in (
                'Global_ambient',
                'Light_ambient',
                'Light_diffuse',
                'Light_location',
                'Light_specular',
                'Material_ambient',
                'Material_diffuse',
                'Material_shininess',
                'Material_specular',
        ):
            location = glGetUniformLocation(self.shader, uniform)
            if location in (None, -1):
                print 'Warning, no uniform: %s' % (uniform)
            setattr(self, uniform + '_loc', location)
        for attribute in (
                'Vertex_position',
                'Vertex_normal',
        ):
            location = glGetAttribLocation(self.shader, attribute)
            if location in (None, -1):
                print 'Warning, no attribute: %s' % (uniform)
            setattr(self, attribute + '_loc', location)

    def Render(self, mode=None):
        """Render the geometry for the scene."""
        BaseContext.Render(self, mode)
        glUseProgram(self.shader)
        try:
            '''==Indexed VBO Rendering==
            
            You'll notice here that we are binding two different VBO 
            objects.  As we mentioned above, the Sphere renderer 
            generated both VBOs, but doesn't the second binding replace 
            the first binding?  That is, why doesn't OpenGL try to read 
            the Vertex data out of the indices VBO?
            
            OpenGL defines multiple binding "targets" for VBOs, the 
            first VBO (vertices) was bound to the GL_ARRAY_BUFFER
            target (the default for the class), which is used for reading 
            per-vertex data arrays, while the indices buffer was defined
            as targetting the GL_ELEMENT_ARRAY_BUFFER, which is used
            solely for reading indices.
            
            Each target can be bound to a different VBO, and thus we can
            bind both VBOs at the same time without confusion.
            '''
            self.coords.bind()
            self.indices.bind()
            '''Here, being lazy, we use the numpy array's nbytes value 
            to specify the stride between records.  The VBO object has 
            a "data" value which is the data-set which was initially 
            passed to the VBO constructor.  The first element in this 
            array is a single vertex record.  This array happens to have 
            8 floating-point values (24 bytes), the first three being 
            the vertex position, the next two being the texture coordinate 
            and the last three being the vertex normal.  We'll ignore 
            the texture coordinate for now.
            '''
            stride = self.coords.data[0].nbytes
            try:
                glUniform4f(self.Global_ambient_loc, .05, .05, .05, .1)
                glUniform4f(self.Light_ambient_loc, .1, .1, .1, 1.0)
                glUniform4f(self.Light_diffuse_loc, .25, .25, .25, 1)
                '''We set up a yellow-ish specular component in the 
                light and move it to rest "just over our right shoulder"
                in relation to the initial camera.'''
                glUniform4f(self.Light_specular_loc, 0.0, 1.0, 0, 1)
                glUniform3f(self.Light_location_loc, 6, 2, 4)

                glUniform4f(self.Material_ambient_loc, .1, .1, .1, 1.0)
                glUniform4f(self.Material_diffuse_loc, .15, .15, .15, 1)
                '''We make the material have a bright specular white 
                colour and an extremely "shiny" surface.  The shininess 
                value has the effect of reducing the area of the
                highlight, as the cos of the angle is raised 
                to the power of the (fractional) shininess.'''
                glUniform4f(self.Material_specular_loc, 1.0, 1.0, 1.0, 1.0)
                glUniform1f(self.Material_shininess_loc, .95)
                glEnableVertexAttribArray(self.Vertex_position_loc)
                glEnableVertexAttribArray(self.Vertex_normal_loc)
                glVertexAttribPointer(self.Vertex_position_loc, 3, GL_FLOAT,
                                      False, stride, self.coords)
                glVertexAttribPointer(self.Vertex_normal_loc, 3, GL_FLOAT,
                                      False, stride, self.coords + (5 * 4))
                '''Here we introduce the OpenGL call which renders via 
                an index-array rather than just rendering vertices in 
                definition order.  The last two arguments tell OpenGL 
                what data-type we've used for the indices (the Sphere 
                renderer uses shorts).  The indices VBO is actually 
                just passing the value c_void_p( 0 ) (i.e. a null pointer),
                which causes OpenGL to use the currently bound VBO for 
                the GL_ELEMENT_ARRAY_BUFFER target.
                '''
                glDrawElements(GL_TRIANGLES, self.count, GL_UNSIGNED_SHORT,
                               self.indices)
            finally:
                '''Note the need to unbind *both* VBOs, we have to free 
                *both* VBO targets to avoid any other rendering operation 
                from trying to access the VBOs.'''
                self.coords.unbind()
                self.indices.unbind()
                glDisableVertexAttribArray(self.Vertex_position_loc)
                glDisableVertexAttribArray(self.Vertex_normal_loc)
        finally:
            glUseProgram(0)
Beispiel #2
0
class TestContext(BaseContext):
    """Demonstrates use of attribute types in GLSL
    """
    LIGHT_COUNT = 3
    LIGHT_SIZE = 4

    def OnInit(self):
        """Initialize the context"""
        '''==Sharing Declarations=
        
        Since we are going to use these values in both the 
        vertex and fragment shaders, it is handy to separate out 
        the constants we'll use into a separate block of code that 
        we can add to both shaders.  The use of the constants also 
        makes the code far easier to read than using the bare numbers.
        
        Note that the varying baseNormal value is part of the lighting
        calculation, so we have included it in our common lighting
        declarations.
        
        We've also parameterized the LIGHT count and size, so that 
        we can use them in both Python and GLSL code.
        '''
        lightConst = """
        const int LIGHT_COUNT = %s;
        const int LIGHT_SIZE = %s;
        
        const int AMBIENT = 0;
        const int DIFFUSE = 1;
        const int SPECULAR = 2;
        const int POSITION = 3;
        
        uniform vec4 lights[ LIGHT_COUNT*LIGHT_SIZE ];
        varying vec3 EC_Light_half[LIGHT_COUNT];
        varying vec3 EC_Light_location[LIGHT_COUNT]; 
        
        varying vec3 baseNormal;
        """ % (self.LIGHT_COUNT, self.LIGHT_SIZE)
        '''As you can see, we're going to create two new varying values,
        the EC_Light_half and EC_Light_location values.  These are 
        going to hold the normalized partial calculations for the lights.
        The other declarations are the same as before, they are just 
        being shared between the shaders.
        
        Our phong_weightCalc calculation hasn't changed.
        '''
        phong_weightCalc = """
        vec2 phong_weightCalc( 
            in vec3 light_pos, // light position
            in vec3 half_light, // half-way vector between light and view
            in vec3 frag_normal, // geometry normal
            in float shininess
        ) {
            // returns vec2( ambientMult, diffuseMult )
            float n_dot_pos = max( 0.0, dot( 
                frag_normal, light_pos
            ));
            float n_dot_half = 0.0;
            if (n_dot_pos > -.05) {
                n_dot_half = pow(max(0.0,dot( 
                    half_light, frag_normal
                )), shininess);
            }
            return vec2( n_dot_pos, n_dot_half);
        }		
        """
        '''Our new vertex shader has a loop in it.  It iterates over the 
        set of lights doing the partial calculations for half-vector 
        and eye-space location.  It stores the results of these in our
        new, varying array values.
        '''
        vertex = shaders.compileShader(
            lightConst + """
        attribute vec3 Vertex_position;
        attribute vec3 Vertex_normal;
        
        void main() {
            gl_Position = gl_ModelViewProjectionMatrix * vec4( 
                Vertex_position, 1.0
            );
            baseNormal = gl_NormalMatrix * normalize(Vertex_normal);
            for (int i = 0; i< LIGHT_COUNT; i++ ) {
                EC_Light_location[i] = normalize(
                    gl_NormalMatrix * lights[(i*LIGHT_SIZE)+POSITION].xyz
                );
                // half-vector calculation 
                EC_Light_half[i] = normalize(
                    EC_Light_location[i] - vec3( 0,0,-1 )
                );
            }
        }""", GL_VERTEX_SHADER)
        '''Our fragment shader looks much the same, save that we 
        have now moved the complex half-vector and eye-space location 
        calculations out.  We've also separated out the concept of 
        which light we are processing and what array-offset we are 
        using, to make it clearer which value is being accessed.
        '''
        fragment = shaders.compileShader(
            lightConst + phong_weightCalc + """
        struct Material {
            vec4 ambient;
            vec4 diffuse;
            vec4 specular;
            float shininess;
        };
        uniform Material material;
        uniform vec4 Global_ambient;
        
        void main() {
            vec4 fragColor = Global_ambient * material.ambient;
            
            int i,j;
            for (i=0;i<LIGHT_COUNT;i++) {
                j = i* LIGHT_SIZE;
                vec2 weights = phong_weightCalc(
                    EC_Light_location[i],
                    EC_Light_half[i],
                    baseNormal,
                    material.shininess
                );
                fragColor = (
                    fragColor 
                    + (lights[j+AMBIENT] * material.ambient)
                    + (lights[j+DIFFUSE] * material.diffuse * weights.x)
                    + (lights[j+SPECULAR] * material.specular * weights.y)
                );
            }
            gl_FragColor = fragColor;
        }
        """, GL_FRAGMENT_SHADER)
        '''The rest of our code is very familiar.'''
        self.shader = shaders.compileProgram(vertex, fragment)
        self.coords, self.indices, self.count = Sphere(radius=1).compile()
        self.uniform_locations = {}
        for uniform, value in self.UNIFORM_VALUES:
            location = glGetUniformLocation(self.shader, uniform)
            if location in (None, -1):
                print 'Warning, no uniform: %s' % (uniform)
            self.uniform_locations[uniform] = location
        self.uniform_locations['lights'] = glGetUniformLocation(
            self.shader, 'lights')
        for attribute in (
                'Vertex_position',
                'Vertex_normal',
        ):
            location = glGetAttribLocation(self.shader, attribute)
            if location in (None, -1):
                print 'Warning, no attribute: %s' % (uniform)
            setattr(self, attribute + '_loc', location)

    UNIFORM_VALUES = [
        ('Global_ambient', (.05, .05, .05, 1.0)),
        ('material.ambient', (.2, .2, .2, 1.0)),
        ('material.diffuse', (.5, .5, .5, 1.0)),
        ('material.specular', (.8, .8, .8, 1.0)),
        ('material.shininess', (.995, )),
    ]
    LIGHTS = array([
        x[1] for x in [
            ('lights[0].ambient', (.05, .05, .05, 1.0)),
            ('lights[0].diffuse', (.3, .3, .3, 1.0)),
            ('lights[0].specular', (1.0, 0.0, 0.0, 1.0)),
            ('lights[0].position', (4.0, 2.0, 10.0, 0.0)),
            ('lights[1].ambient', (.05, .05, .05, 1.0)),
            ('lights[1].diffuse', (.3, .3, .3, 1.0)),
            ('lights[1].specular', (0.0, 1.0, 0.0, 1.0)),
            ('lights[1].position', (-4.0, 2.0, 10.0, 0.0)),
            ('lights[2].ambient', (.05, .05, .05, 1.0)),
            ('lights[2].diffuse', (.3, .3, .3, 1.0)),
            ('lights[2].specular', (0.0, 0.0, 1.0, 1.0)),
            ('lights[2].position', (-4.0, 2.0, -10.0, 0.0)),
        ]
    ], 'f')

    def Render(self, mode=None):
        """Render the geometry for the scene."""
        BaseContext.Render(self, mode)
        if not mode.visible:
            return
        glUseProgram(self.shader)
        try:
            self.coords.bind()
            stride = self.coords.data[0].nbytes
            try:
                '''Note the use of the parameterized values to specify 
                the size of the light-parameter array.'''
                glUniform4fv(self.uniform_locations['lights'],
                             self.LIGHT_COUNT * self.LIGHT_SIZE, self.LIGHTS)
                for uniform, value in self.UNIFORM_VALUES:
                    location = self.uniform_locations.get(uniform)
                    if location not in (None, -1):
                        if len(value) == 4:
                            glUniform4f(location, *value)
                        elif len(value) == 3:
                            glUniform3f(location, *value)
                        elif len(value) == 1:
                            glUniform1f(location, *value)
                glEnableVertexAttribArray(self.Vertex_position_loc)
                glEnableVertexAttribArray(self.Vertex_normal_loc)
                glVertexAttribPointer(self.Vertex_position_loc, 3, GL_FLOAT,
                                      False, stride, self.coords)
                glVertexAttribPointer(self.Vertex_normal_loc, 3, GL_FLOAT,
                                      False, stride, self.coords + (5 * 4))
                self.indices.bind()
                glDrawElements(GL_TRIANGLES, self.count, GL_UNSIGNED_SHORT,
                               self.indices)
            finally:
                self.coords.unbind()
                self.indices.unbind()
                glDisableVertexAttribArray(self.Vertex_position_loc)
                glDisableVertexAttribArray(self.Vertex_normal_loc)
        finally:
            glUseProgram(0)
Beispiel #3
0
class TestContext(BaseContext):
    """Demonstrates use of attribute types in GLSL
    """
    LIGHT_COUNT = 3
    '''Note that we're going to add 2 new vec4 fields to our light,
    the legacy GL has 2 floats and a direction vector, but we're 
    combining all the values into a uniform vector set.'''
    LIGHT_SIZE = 7

    def OnInit(self):
        """Initialize the context"""
        '''As you can see, we've created a new 4-element vector 
        called SPOT_PARAMS which holds 3 "meaningful" float values.
        The first element cos_spot_cutoff represents the cosign of 
        the angle beyond which the light is cut off. This angle is 
        measured from the light's spot_direction compared with the 
        light_location vector.  In effect, it is a check to see if 
        the fragment is within the cone of the light.  The use of the 
        *cosine* of the angle is to allow for very fast checks against 
        the dot-product of the normalized vectors.
        
        The second element, spot_exponent, is used to calculate the 
        amount of "spotiness" of the spotlight, that is, the amount 
        to which the spotlight focusses light on the center of the 
        beam versus the outside of the beam.  A higher spot_exponent
        will cause the spotlight to "focus" more, a lower exponent 
        will cause the spotlight to act more like a "shielded" 
        point-light (such as a lamp with blockers rather than reflectors).
        
        The last component of the SPOT_PARAMS is being used as a simple 
        flag to tell us whether to apply the spot calculation.  We could 
        have shaved a whole vec4 by packing the spot_exponent into the 
        ATTENUATION vector's unused .w component, and then packing the 
        spot_cutoff into the spot_direction's unused .w, but that becomes 
        a bit awkward looking.
        '''
        lightConst = """
        const int LIGHT_COUNT = %s;
        const int LIGHT_SIZE = %s;
        
        const int AMBIENT = 0;
        const int DIFFUSE = 1;
        const int SPECULAR = 2;
        const int POSITION = 3;
        const int ATTENUATION = 4;
        //SPOT_PARAMS [ cos_spot_cutoff, spot_exponent, ignored, is_spot ]
        const int SPOT_PARAMS = 5;
        const int SPOT_DIR = 6;
        
        uniform vec4 lights[ LIGHT_COUNT*LIGHT_SIZE ];
        varying vec3 EC_Light_half[LIGHT_COUNT];
        varying vec3 EC_Light_location[LIGHT_COUNT]; 
        varying float Light_distance[LIGHT_COUNT]; 
        
        varying vec3 baseNormal;
        """ % (self.LIGHT_COUNT, self.LIGHT_SIZE)
        '''Our phong_weightCalc function receives its final tweaks here.  We 
        provide the two vec4 spot elements for the current light.
        The spotlight operation modifies the point-light code such that 
        the "attenuation" numerator is either 1.0 (for non-directional 
        point-lights) or a calculated value for spot-lights.
        
        To calculate this value, we take the (cos of the) angle between 
        the light direction (spot_direction) and the vector between 
        the fragment and the light location (-light_pos).  If this value 
        is lower than our spot_cutoff, then we do not want to provide 
        any lighting whatsoever from this light, so we short-circuit and 
        return a null vector of weights. 
        
        If the value is higher than the cutoff, we calculate the
        "spotiness" multiplier.  Here we are *not* using the OpenGL 
        standard method, instead we calculate the fraction of total 
        cosine-space which is displayed and raise it to the power of our 
        spot_exponent value.
        
        This is our last tweak to the blinn-phong lighting model so we'll make 
        this version of the function available like so:
        
        from OpenGLContext.resources.phongweights_frag import data as phong_weightCalc
        '''
        phong_weightCalc = """
        vec3 phong_weightCalc( 
            in vec3 light_pos, // light position/direction
            in vec3 half_light, // half-way vector between light and view
            in vec3 frag_normal, // geometry normal
            in float shininess, // shininess exponent
            in float distance, // distance for attenuation calculation...
            in vec4 attenuations, // attenuation parameters...
            in vec4 spot_params, // spot control parameters...
            in vec4 spot_direction // model-space direction
        ) {
            // returns vec3( ambientMult, diffuseMult, specularMult )
            
            float n_dot_pos = max( 0.0, dot( 
                frag_normal, light_pos
            ));
            float n_dot_half = 0.0;
            float attenuation = 1.0;
            if (n_dot_pos > -.05) {
                float spot_effect = 1.0;
                if (spot_params.w != 0.0) {
                    // is a spot...
                    float spot_cos = dot(
                        gl_NormalMatrix * normalize(spot_direction.xyz),
                        normalize(-light_pos)
                    );
                    if (spot_cos <= spot_params.x) {
                        // is a spot, and is outside the cone-of-light...
                        return vec3( 0.0, 0.0, 0.0 );
                    } else {
                        if (spot_cos == 1.0) {
                            spot_effect = 1.0;
                        } else {
                            spot_effect = pow( 
                                    (1.0-spot_params.x)/(1.0-spot_cos), 
                                    spot_params.y 
                                );
                        }
                    }
                }
                n_dot_half = pow(
                    max(0.0,dot( 
                        half_light, frag_normal
                    )), 
                    shininess
                );
                if (distance != 0.0) {
                    float attenuation = 1.0/(
                        attenuations.x + 
                        (attenuations.y * distance) +
                        (attenuations.z * distance * distance)
                    );
                    n_dot_half *= spot_effect;
                    n_dot_pos *= attenuation;
                    n_dot_half *= attenuation;
                }
            }
            return vec3( attenuation, n_dot_pos, n_dot_half);
        }		
        """
        '''Nothing needs to change in our vertex shader, save that we're 
        using the functions we stored to external files in the previous 
        tutorial.'''
        from OpenGLContext.resources.phongprecalc_vert import data as phong_preCalc
        light_preCalc = open('_shader_tut_lightprecalc.vert').read()

        vertex = shaders.compileShader(
            lightConst + phong_preCalc + light_preCalc + """
        attribute vec3 Vertex_position;
        attribute vec3 Vertex_normal;
        void main() {
            gl_Position = gl_ModelViewProjectionMatrix * vec4( 
                Vertex_position, 1.0
            );
            baseNormal = gl_NormalMatrix * normalize(Vertex_normal);
            light_preCalc( Vertex_position );
        }""", GL_VERTEX_SHADER)
        '''Our only change for the fragment shader is to pass in the 
        spot components of the current light when calling phong_weightCalc.'''
        fragment = shaders.compileShader(
            lightConst + phong_weightCalc + """
        struct Material {
            vec4 ambient;
            vec4 diffuse;
            vec4 specular;
            float shininess;
        };
        uniform Material material;
        uniform vec4 Global_ambient;
        
        void main() {
            vec4 fragColor = Global_ambient * material.ambient;
            
            int i,j;
            for (i=0;i<LIGHT_COUNT;i++) {
                j = i* LIGHT_SIZE;
                vec3 weights = phong_weightCalc(
                    normalize(EC_Light_location[i]),
                    normalize(EC_Light_half[i]),
                    normalize(baseNormal),
                    material.shininess,
                    abs(Light_distance[i]), // see note tutorial 9
                    lights[j+ATTENUATION],
                    lights[j+SPOT_PARAMS],
                    lights[j+SPOT_DIR]
                );
                fragColor = (
                    fragColor 
                    + (lights[j+AMBIENT] * material.ambient * weights.x)
                    + (lights[j+DIFFUSE] * material.diffuse * weights.y)
                    + (lights[j+SPECULAR] * material.specular * weights.z)
                );
            }
            gl_FragColor = fragColor;
        }
        """, GL_FRAGMENT_SHADER)
        '''Our uniform/geometry handling code is unchanged.'''
        self.shader = shaders.compileProgram(vertex, fragment)
        self.coords, self.indices, self.count = Sphere(radius=1).compile()
        self.uniform_locations = {}
        for uniform, value in self.UNIFORM_VALUES:
            location = glGetUniformLocation(self.shader, uniform)
            if location in (None, -1):
                print 'Warning, no uniform: %s' % (uniform)
            self.uniform_locations[uniform] = location
        self.uniform_locations['lights'] = glGetUniformLocation(
            self.shader, 'lights')
        for attribute in (
                'Vertex_position',
                'Vertex_normal',
        ):
            location = glGetAttribLocation(self.shader, attribute)
            if location in (None, -1):
                print 'Warning, no attribute: %s' % (uniform)
            setattr(self, attribute + '_loc', location)

    '''We'll dial down the shininess on our material a little so that 
    it's easier to see the spotlight cones on the sphere.'''
    UNIFORM_VALUES = [
        ('Global_ambient', (.05, .05, .05, 1.0)),
        ('material.ambient', (.8, .8, .8, 1.0)),
        ('material.diffuse', (.8, .8, .8, 1.0)),
        ('material.specular', (.8, .8, .8, 1.0)),
        ('material.shininess', (.8, )),
    ]
    '''Our lights array now has more fields per light.  The spotlight 
    vectors always have to be present, even if we are not using them 
    for a particular light.  We're going to define 3 lights here with 
    fairly high "spotiness" values so that we can see the focussed 
    beam effect on the sphere.  The spot exponents in this case tend to 
    cause an area in the center of the beam to saturate completely.
    '''
    LIGHTS = array([
        x[1] for x in [
            ('lights[0].ambient', (.05, .05, .05, 1.0)),
            ('lights[0].diffuse', (.1, .8, .1, 1.0)),
            ('lights[0].specular', (0.0, .05, 0.0, 1.0)),
            ('lights[0].position', (2.5, 3.5, 2.5, 1.0)),
            ('lights[0].attenuation', (0.0, 1.0, 1.0, 1.0)),
            ('lights[0].spot_params', (cos(.25), 1.0, 0.0, 1.0)),
            ('lights[0].spot_dir', (-8, -20, -8.0, 1.0)),
            ('lights[1].ambient', (.05, .05, .05, 1.0)),
            ('lights[1].diffuse', (.8, .1, .1, 1.0)),
            ('lights[1].specular', (.25, 0.0, 0.0, 1.0)),
            ('lights[1].position', (-2.5, 2.5, 2.5, 1.0)),
            ('lights[1].attenuation', (0.0, 0.0, .125, 1.0)),
            ('lights[1].spot_params', (cos(.25), 1.25, 0.0, 1.0)),
            ('lights[1].spot_dir', (2.5, -5.5, -2.5, 1.0)),
            ('lights[2].ambient', (.05, .05, .05, 1.0)),
            ('lights[2].diffuse', (.1, .1, 1.0, 1.0)),
            ('lights[2].specular', (0.0, .25, .25, 1.0)),
            ('lights[2].position', (0.0, -3.06, 3.06, 1.0)),
            ('lights[2].attenuation', (2.0, 0.0, 0.0, 1.0)),
            ('lights[2].spot_params', (cos(.15), .75, 0.0, 1.0)),
            ('lights[2].spot_dir', (0.0, 3.06, -3.06, 1.0)),
        ]
    ], 'f')
    '''Nothing else needs to change from the previous tutorial.'''

    def Render(self, mode=None):
        """Render the geometry for the scene."""
        BaseContext.Render(self, mode)
        if not mode.visible:
            return
        glUseProgram(self.shader)
        try:
            self.coords.bind()
            self.indices.bind()
            stride = self.coords.data[0].nbytes
            try:
                glUniform4fv(self.uniform_locations['lights'],
                             self.LIGHT_COUNT * self.LIGHT_SIZE, self.LIGHTS)
                for uniform, value in self.UNIFORM_VALUES:
                    location = self.uniform_locations.get(uniform)
                    if location not in (None, -1):
                        if len(value) == 4:
                            glUniform4f(location, *value)
                        elif len(value) == 3:
                            glUniform3f(location, *value)
                        elif len(value) == 1:
                            glUniform1f(location, *value)
                glEnableVertexAttribArray(self.Vertex_position_loc)
                glEnableVertexAttribArray(self.Vertex_normal_loc)
                glVertexAttribPointer(self.Vertex_position_loc, 3, GL_FLOAT,
                                      False, stride, self.coords)
                glVertexAttribPointer(self.Vertex_normal_loc, 3, GL_FLOAT,
                                      False, stride, self.coords + (5 * 4))
                glDrawElements(GL_TRIANGLES, self.count, GL_UNSIGNED_SHORT,
                               self.indices)
            finally:
                self.coords.unbind()
                self.indices.unbind()
                glDisableVertexAttribArray(self.Vertex_position_loc)
                glDisableVertexAttribArray(self.Vertex_normal_loc)
        finally:
            glUseProgram(0)
Beispiel #4
0
class TestContext( BaseContext ):
    """Demonstrates use of attribute types in GLSL
    """
    LIGHT_COUNT = 3
    LIGHT_SIZE = 5
    def OnInit( self ):
        """Initialize the context"""
        '''Our common light-model declarations are getting slightly 
        more involved.  We're adding a single field to the light 
        "structure", the attenuation field.  This is a 4-item vector 
        where the first item is a constant attenuation factor, the 
        second is a linear attenuation factor, and the third a quadratic 
        attenuation factor.  The fourth item is ignored, but we are 
        using an array of vec4s for the light parameters, so it is 
        easiest to just ignore the w value.
        '''
        lightConst = """
        const int LIGHT_COUNT = %s;
        const int LIGHT_SIZE = %s;
        
        const int AMBIENT = 0;
        const int DIFFUSE = 1;
        const int SPECULAR = 2;
        const int POSITION = 3;
        const int ATTENUATION = 4;
        
        uniform vec4 lights[ LIGHT_COUNT*LIGHT_SIZE ];
        varying vec3 EC_Light_half[LIGHT_COUNT];
        varying vec3 EC_Light_location[LIGHT_COUNT]; 
        varying float Light_distance[LIGHT_COUNT]; 
        
        varying vec3 baseNormal;
        """%( self.LIGHT_COUNT, self.LIGHT_SIZE )
        '''==Lighting Attenuation=
        
        For the first time in many tutorials we're altering out 
        lighting calculation.  We're adding 2 inputs to the function,
        the first is the distance from the fragment to the light,
        the second is the attenuation vector for the in-process light.
        We are also going to return one extra value, the ambient-light 
        multiplier for this light.  For our directional lights this 
        was always 1.0, but now our light's ambient contribution can 
        be controlled by attenuation.
        
        The core calculation for attenuation looks like this:
        '''
        """attenuation = clamp(
            0.0,
            1.0,
            1.0 / (
                attenuations.x + 
                (attenuations.y * distance) +
                (attenuations.z * distance * distance)
            )
        );"""
        '''The default attenuation for legacy OpenGL was
        (1.0, 0.0, 0.0), which is to say, no attenuation at all.
        The attenuation values are not particularly "human friendly",
        but they give you some control over the distance at which 
        lights cause effects.  Keep in mind when using attenuation
        coefficients that smaller values mean the light goes farther,
        so a coefficient of .5 is "brighter" than a coefficient of 1.0.
        '''
        phong_weightCalc = """
        vec3 phong_weightCalc( 
            in vec3 light_pos, // light position/direction
            in vec3 half_light, // half-way vector between light and view
            in vec3 frag_normal, // geometry normal
            in float shininess, // shininess exponent
            in float distance, // distance for attenuation calculation...
            in vec4 attenuations // attenuation parameters...
        ) {
            // returns vec3( ambientMult, diffuseMult, specularMult )
            float n_dot_pos = max( 0.0, dot( 
                frag_normal, light_pos
            ));
            float n_dot_half = 0.0;
            float attenuation = 1.0;
            if (n_dot_pos > -.05) {
                n_dot_half = pow(
                    max(0.0,dot( 
                        half_light, frag_normal
                    )), 
                    shininess
                );
                if (distance != 0.0) {
                    attenuation = clamp(
                        0.0,
                        1.0,
                        1.0 / (
                            attenuations.x + 
                            (attenuations.y * distance) +
                            (attenuations.z * distance * distance)
                        )
                    );
                    n_dot_pos *= attenuation;
                    n_dot_half *= attenuation;
                }
            }
            return vec3( attenuation, n_dot_pos, n_dot_half);
        }
        """
        '''==Calculating Distance and Direction==
        
        Our new lights are "point sources", that is, they have a 
        model-space location which is not at "infinite distance".
        Because of this, unlike "directional lights", we have to 
        recalculate the light position/location/direction vector 
        for each fragment.  We also need to know the distance of 
        the light from each fragment.
        
        While we could perform those calculations in the fragment 
        shader, the vectors and distances we need vary smoothly 
        across the triangles involved, so we'll calculate them at 
        each vertex and allow the hardware to interpolate them.
        We'll have to normalize the interpolated values, but this 
        is less processor intensive than doing the calculations 
        for each fragment.
        
        We are doing our vector calculations for the light location
        and distance in model-space.  You could do them in view-space 
        as well.
        
        Our vertex calculations are getting complex enough that we're 
        going to split them into a separate function for readability and 
        (eventual) reusability.  We'll create a function which encodes the 
        algorithmic operation (phong_preCalc) and another which takes our 
        particular attributes/uniforms and accumulates the results from 
        that function.
        
        We're defining phong_preCalc inline here, but we'll also store it 
        as a resource we can use from anywhere:
        
            from OpenGLContext.resources.phongprecalc_vert import data as phong_preCalc
        '''
        phong_preCalc = """
        // Vertex-shader pre-calculation for lighting...
        void phong_preCalc( 
            in vec3 vertex_position,
            in vec4 light_position,
            out float light_distance,
            out vec3 ec_light_location,
            out vec3 ec_light_half
        ) {
            // This is the core setup for a phong lighting pass 
            // as a reusable fragment of code.
            
            // vertex_position -- un-transformed vertex position (world-space)
            // light_position -- un-transformed light location (direction)
            // light_distance -- output giving world-space distance-to-light 
            // ec_light_location -- output giving location of light in eye coords 
            // ec_light_half -- output giving the half-vector optimization
            
            if (light_position.w == 0.0) {
                // directional rather than positional light...
                ec_light_location = normalize(
                    gl_NormalMatrix *
                    light_position.xyz
                );
                light_distance = 0.0;
            } else {
                // positional light, we calculate distance in 
                // model-view space here, so we take a partial 
                // solution...
                vec3 ms_vec = (
                    light_position.xyz -
                    vertex_position
                );
                vec3 light_direction = gl_NormalMatrix * ms_vec;
                ec_light_location = normalize( light_direction );
                light_distance = abs(length( ms_vec ));
            }
            // half-vector calculation 
            ec_light_half = normalize(
                ec_light_location + vec3( 0,0,1 )
            );
        }"""
        '''This function is not as generally reusable, so we'll store it 
        to a separate file named '_shader_tut_lightprecalc.vert'.'''
        light_preCalc = """
        void light_preCalc( in vec3 vertex_position ) {
            // This function is dependent on the uniforms and 
            // varying values we've been using, it basically 
            // just iterates over the phong_lightCalc passing in 
            // the appropriate pointers...
            vec3 light_direction;
            for (int i = 0; i< LIGHT_COUNT; i++ ) {
                int j = i * LIGHT_SIZE;
                phong_preCalc(
                    vertex_position,
                    lights[j+POSITION],
                    // following are the values to fill in...
                    Light_distance[i],
                    EC_Light_location[i],
                    EC_Light_half[i]
                );
            }
        }
        """
        vertex = shaders.compileShader( 
            lightConst + phong_preCalc + light_preCalc +
        """
        attribute vec3 Vertex_position;
        attribute vec3 Vertex_normal;
        void main() {
            gl_Position = gl_ModelViewProjectionMatrix * vec4( 
                Vertex_position, 1.0
            );
            baseNormal = gl_NormalMatrix * normalize(Vertex_normal);
            light_preCalc(Vertex_position);
        }""", GL_VERTEX_SHADER)
        '''Our fragment shader is only slightly modified to use our 
        new phong_weightCalc function.  We need a larger "weights" variable and 
        need to pass in more information.  We also need to multiply 
        the per-light ambient value by the new weight we've added.
        
        You will also notice that since we are using the 'i' variable 
        to directly index the varying arrays, we've introduced a 'j' 
        variable that tracks the offset into the light array which 
        begins the current light.
        '''
        fragment = shaders.compileShader( 
            lightConst + phong_weightCalc + """
        struct Material {
            vec4 ambient;
            vec4 diffuse;
            vec4 specular;
            float shininess;
        };
        uniform Material material;
        uniform vec4 Global_ambient;
        
        void main() {
            vec4 fragColor = Global_ambient * material.ambient;
            int i,j;
            for (i=0;i<LIGHT_COUNT;i++) {
                j = i* LIGHT_SIZE;
                vec3 weights = phong_weightCalc(
                    normalize(EC_Light_location[i]),
                    normalize(EC_Light_half[i]),
                    baseNormal,
                    material.shininess,
                    // some implementations will produce negative values interpolating positive float-arrays!
                    // so we have to do an extra abs call for distance
                    abs(Light_distance[i]),
                    lights[j+ATTENUATION]
                );
                fragColor = (
                    fragColor 
                    + (lights[j+AMBIENT] * material.ambient * weights.x)
                    + (lights[j+DIFFUSE] * material.diffuse * weights.y)
                    + (lights[j+SPECULAR] * material.specular * weights.z)
                );
            }
            //fragColor = vec4(Light_distance[0],Light_distance[1],Light_distance[2],1.0);
            gl_FragColor = fragColor;
        }
        """, GL_FRAGMENT_SHADER)
        '''Our general uniform setup should look familiar by now.'''
        self.shader = shaders.compileProgram(vertex,fragment)
        self.coords,self.indices,self.count = Sphere( 
            radius = 1 
        ).compile()
        self.uniform_locations = {}
        for uniform,value in self.UNIFORM_VALUES:
            location = glGetUniformLocation( self.shader, uniform )
            if location in (None,-1):
                print 'Warning, no uniform: %s'%( uniform )
            self.uniform_locations[uniform] = location
        self.uniform_locations['lights'] = glGetUniformLocation( 
            self.shader, 'lights' 
        )
        for attribute in (
            'Vertex_position','Vertex_normal',
        ):
            location = glGetAttribLocation( self.shader, attribute )
            if location in (None,-1):
                print 'Warning, no attribute: %s'%( uniform )
            setattr( self, attribute+ '_loc', location )
    UNIFORM_VALUES = [
        ('Global_ambient',(.05,.05,.05,1.0)),
        ('material.ambient',(.2,.2,.2,1.0)),
        ('material.diffuse',(.5,.5,.5,1.0)),
        ('material.specular',(.8,.8,.8,1.0)),
        ('material.shininess',(2.0,)),
    ]
    '''We've created 3 equal-distance lights here, in red, green and 
    blue.  The green light uses linear attenuation, the red quadratic
    and the blue constant.
    '''
    LIGHTS = array([
        x[1] for x in [
            ('lights[0].ambient',(.05,.05,.05,1.0)),
            ('lights[0].diffuse',(.1,.8,.1,1.0)),
            ('lights[0].specular',(0.0,1.0,0.0,1.0)),
            ('lights[0].position',(2.5,2.5,2.5,1.0)),
            ('lights[0].attenuation',(0.0,.15,0.0,1.0)),
            
            ('lights[1].ambient',(.05,.05,.05,1.0)),
            ('lights[1].diffuse',(.8,.1,.1,1.0)),
            ('lights[1].specular',(1.0,0.0,0.0,1.0)),
            ('lights[1].position',(-2.5,2.5,2.5,1.0)),
            ('lights[1].attenuation',(0.0,0.0,.15,1.0)),
            
            ('lights[2].ambient',(.05,.05,.05,1.0)),
            ('lights[2].diffuse',(.1,.1,.8,1.0)),
            ('lights[2].specular',(0.0,0.0,1.0,1.0)),
            ('lights[2].position',(0.0,-3.06,3.06,1.0)),
            ('lights[2].attenuation',(.15,0.0,0.0,1.0)),
        ]
    ], 'f')
    def Render( self, mode = None):
        """Render the geometry for the scene."""
        BaseContext.Render( self, mode )
        if not mode.visible:
            return
        glUseProgram(self.shader)
        try:
            self.coords.bind()
            self.indices.bind()
            stride = self.coords.data[0].nbytes
            try:
                '''Again, we're using the parameterized light size/count 
                to pass in the array.'''
                glUniform4fv( 
                    self.uniform_locations['lights'],
                    self.LIGHT_COUNT * self.LIGHT_SIZE,
                    self.LIGHTS
                )
                for uniform,value in self.UNIFORM_VALUES:
                    location = self.uniform_locations.get( uniform )
                    if location not in (None,-1):
                        if len(value) == 4:
                            glUniform4f( location, *value )
                        elif len(value) == 3:
                            glUniform3f( location, *value )
                        elif len(value) == 1:
                            glUniform1f( location, *value )
                glEnableVertexAttribArray( self.Vertex_position_loc )
                glEnableVertexAttribArray( self.Vertex_normal_loc )
                glVertexAttribPointer( 
                    self.Vertex_position_loc, 
                    3, GL_FLOAT,False, stride, self.coords
                )
                glVertexAttribPointer( 
                    self.Vertex_normal_loc, 
                    3, GL_FLOAT,False, stride, self.coords+(5*4)
                )
                glDrawElements(
                    GL_TRIANGLES, self.count,
                    GL_UNSIGNED_SHORT, self.indices
                )
            finally:
                self.coords.unbind()
                self.indices.unbind()
                glDisableVertexAttribArray( self.Vertex_position_loc )
                glDisableVertexAttribArray( self.Vertex_normal_loc )
        finally:
            glUseProgram( 0 )
Beispiel #5
0
class TestContext(BaseContext):
    LIGHT_COUNT = 3
    LIGHT_SIZE = 4

    def OnInit(self):

        lightConst = '''
        const int LIGHT_COUNT = %s;
        const int LIGHT_SIZE = %s;
        const int AMBIENT = 0;
        const int DIFFUSE = 1;
        const int SPECULAR = 2;
        const int POSITION = 3;
        uniform vec4 lights[LIGHT_COUNT*LIGHT_SIZE];
        varying vec3 EC_Light_half[LIGHT_COUNT];
        varying vec3 EC_Light_location[LIGHT_COUNT];
        varying vec3 baseNormal;
        ''' % (self.LIGHT_COUNT, self.LIGHT_SIZE)

        phong_weightCalc = '''
        vec2 phong_weightCalc(
            in vec3 light_pos,
            in vec3 half_light,
            in vec3 frag_normal,
            in float shininess
        ){
            float n_dot_pos = max(0.0, dot(frag_normal, light_pos));
            float n_dot_half = 0.0;
            if (n_dot_pos > -0.05){
                n_dot_half = pow(max(0.0, dot(half_light, frag_normal)), shininess);
                return vec2(n_dot_pos, n_dot_half);
            }
        }
        '''

        vs = lightConst + '''
        attribute vec3 Vertex_position;
        attribute vec3 Vertex_normal;
        void main(){
            gl_Position = gl_ModelViewProjectionMatrix * vec4(Vertex_position, 1.0);
            baseNormal = gl_NormalMatrix * normalize(Vertex_normal);
            for (int i=0; i<LIGHT_COUNT; i++){
                EC_Light_location[i] = normalize(gl_NormalMatrix * lights[(i*LIGHT_SIZE)+POSITION].xyz);
                EC_Light_half[i] = normalize(EC_Light_location[i] - vec3(0,0,-1));
            }
        }
        '''
        vertex = shaders.compileShader(vs, GL_VERTEX_SHADER)

        fs = lightConst + phong_weightCalc + '''
        struct Material {
            vec4 ambient;
            vec4 diffuse;
            vec4 specular;
            float shininess;
        };
        uniform Material material;
        uniform vec4 Global_ambient;
        void main(){
            vec4 fragColor = Global_ambient * material.ambient;
            int i,j;
            for (i=0; i<LIGHT_COUNT;i++){
                j = i * LIGHT_SIZE;
                vec2 weights = phong_weightCalc(
                    EC_Light_location[i],
                    EC_Light_half[i],
                    baseNormal,
                    material.shininess
                );
                fragColor = (
                    fragColor
                    + (lights[i+AMBIENT] * material.ambient)
                    + (lights[i+DIFFUSE] * material.diffuse * weights.x)
                    + (lights[i+SPECULAR] * material.specular * weights.y)
                );
            }
            gl_FragColor = fragColor;
        }
        '''
        fragment = shaders.compileShader(fs, GL_FRAGMENT_SHADER)
        self.shader = shaders.compileProgram(vertex, fragment)
        self.coords, self.indices, self.count = Sphere(radius=1).compile()

        self.UNIFORM_VALUES = [
            ('Global_ambient', (.05, .05, .05, 1.0)),
            ('material.ambient', (.2, .2, .2, 1.0)),
            ('material.diffuse', (.5, .5, .5, 1.0)),
            ('material.specular', (.8, .8, .8, 1.0)),
            ('material.shininess', (.995, )),
        ]

        self.uniform_locations = {}
        for uniform, value in self.UNIFORM_VALUES:
            location = glGetUniformLocation(self.shader, uniform)
            if location in (None, -1):
                print('Warning, no uniform: {}'.format(uniform))
            self.uniform_locations[uniform] = location
        self.uniform_locations['lights'] = glGetUniformLocation(
            self.shader, 'lights')

        attribute_vals = ('Vertex_position', 'Vertex_normal')

        for attribute in attribute_vals:
            location = glGetAttribLocation(self.shader, attribute)
            if location in (None, -1):
                print('Warning no attribute: {}'.format(attribute))
            setattr(self, attribute + '_loc', location)

        self.LIGHTS = array(
            [[
                (.05, .05, .05, 1.0),  #'lights[0].ambient'
                (.3, .3, .3, 1.0),  #'lights[0].diffuse'
                (1.0, 0.0, 0.0, 1.0),  #'lights[0].specular'
                (4.0, 2.0, 10.0, 0.0),  #'lights[0].position
                (.05, .05, .05, 1.0),  #'lights[1].ambient'
                (.3, .3, .3, 1.0),  #'lights[1].diffuse'
                (0.0, 1.0, 0.0, 1.0),  #'lights[1].specular'
                (-4.0, 2.0, 10.0, 0.0),  # 'lights[1].position
                (.05, .05, .05, 1.0),  #('lights[2].ambient',
                (.3, .3, .3, 1.0),  #lights[2].diffuse'
                (0.0, 0.0, 1.0, 1.0),  #'lights[2].specular'
                (-4.0, 2.0, -10.0, 0.0)  #'lights[2].position
            ]],
            'f')

    def Render(self, mode=None):
        BaseContext.Render(self, mode)
        if not mode.visible:
            return
        glUseProgram(self.shader)
        try:
            self.coords.bind()
            self.indices.bind()
            stride = self.coords.data[0].nbytes
            try:
                glUniform4fv(self.uniform_locations['lights'],
                             self.LIGHT_COUNT * self.LIGHT_SIZE, self.LIGHTS)
                for uniform, value in self.UNIFORM_VALUES:
                    location = self.uniform_locations.get(uniform)
                    if location not in (None, -1):
                        if len(value) == 4:
                            glUniform4f(location, *value)
                        elif len(value) == 3:
                            glUniform3f(location, *value)
                        elif len(value) == 1:
                            glUniform1f(location, *value)
                glEnableVertexAttribArray(self.Vertex_position_loc)
                glEnableVertexAttribArray(self.Vertex_normal_loc)
                glVertexAttribPointer(self.Vertex_position_loc, 3, GL_FLOAT,
                                      False, stride, self.coords)
                glVertexAttribPointer(self.Vertex_normal_loc, 3, GL_FLOAT,
                                      False, stride, self.coords + (5 * 4))
                glDrawElements(GL_TRIANGLES, self.count, GL_UNSIGNED_SHORT,
                               self.indices)
            finally:
                self.coords.unbind()
                self.indices.unbind()
                glDisableVertexAttribArray(self.Vertex_position_loc)
                glDisableVertexAttribArray(self.Vertex_normal_loc)
        finally:
            glUseProgram(0)
Beispiel #6
0
class TestContext(BaseContext):
    """Demonstrates use of attribute types in GLSL
    """
    LIGHT_COUNT = 3
    LIGHT_SIZE = 5

    def OnInit(self):
        """Initialize the context"""
        '''Our common light-model declarations are getting slightly 
        more involved.  We're adding a single field to the light 
        "structure", the attenuation field.  This is a 4-item vector 
        where the first item is a constant attenuation factor, the 
        second is a linear attenuation factor, and the third a quadratic 
        attenuation factor.  The fourth item is ignored, but we are 
        using an array of vec4s for the light parameters, so it is 
        easiest to just ignore the w value.
        '''
        lightConst = """
        const int LIGHT_COUNT = %s;
        const int LIGHT_SIZE = %s;
        
        const int AMBIENT = 0;
        const int DIFFUSE = 1;
        const int SPECULAR = 2;
        const int POSITION = 3;
        const int ATTENUATION = 4;
        
        uniform vec4 lights[ LIGHT_COUNT*LIGHT_SIZE ];
        varying vec3 EC_Light_half[LIGHT_COUNT];
        varying vec3 EC_Light_location[LIGHT_COUNT]; 
        varying float Light_distance[LIGHT_COUNT]; 
        
        varying vec3 baseNormal;
        """ % (self.LIGHT_COUNT, self.LIGHT_SIZE)
        '''==Lighting Attenuation=
        
        For the first time in many tutorials we're altering out 
        lighting calculation.  We're adding 2 inputs to the function,
        the first is the distance from the fragment to the light,
        the second is the attenuation vector for the in-process light.
        We are also going to return one extra value, the ambient-light 
        multiplier for this light.  For our directional lights this 
        was always 1.0, but now our light's ambient contribution can 
        be controlled by attenuation.
        
        The core calculation for attenuation looks like this:
        '''
        """attenuation = clamp(
            0.0,
            1.0,
            1.0 / (
                attenuations.x + 
                (attenuations.y * distance) +
                (attenuations.z * distance * distance)
            )
        );"""
        '''The default attenuation for legacy OpenGL was
        (1.0, 0.0, 0.0), which is to say, no attenuation at all.
        The attenuation values are not particularly "human friendly",
        but they give you some control over the distance at which 
        lights cause effects.  Keep in mind when using attenuation
        coefficients that smaller values mean the light goes farther,
        so a coefficient of .5 is "brighter" than a coefficient of 1.0.
        '''
        phong_weightCalc = """
        vec3 phong_weightCalc( 
            in vec3 light_pos, // light position/direction
            in vec3 half_light, // half-way vector between light and view
            in vec3 frag_normal, // geometry normal
            in float shininess, // shininess exponent
            in float distance, // distance for attenuation calculation...
            in vec4 attenuations // attenuation parameters...
        ) {
            // returns vec3( ambientMult, diffuseMult, specularMult )
            float n_dot_pos = max( 0.0, dot( 
                frag_normal, light_pos
            ));
            float n_dot_half = 0.0;
            float attenuation = 1.0;
            if (n_dot_pos > -.05) {
                n_dot_half = pow(
                    max(0.0,dot( 
                        half_light, frag_normal
                    )), 
                    shininess
                );
                if (distance != 0.0) {
                    attenuation = clamp(
                        0.0,
                        1.0,
                        1.0 / (
                            attenuations.x + 
                            (attenuations.y * distance) +
                            (attenuations.z * distance * distance)
                        )
                    );
                    n_dot_pos *= attenuation;
                    n_dot_half *= attenuation;
                }
            }
            return vec3( attenuation, n_dot_pos, n_dot_half);
        }
        """
        '''==Calculating Distance and Direction==
        
        Our new lights are "point sources", that is, they have a 
        model-space location which is not at "infinite distance".
        Because of this, unlike "directional lights", we have to 
        recalculate the light position/location/direction vector 
        for each fragment.  We also need to know the distance of 
        the light from each fragment.
        
        While we could perform those calculations in the fragment 
        shader, the vectors and distances we need vary smoothly 
        across the triangles involved, so we'll calculate them at 
        each vertex and allow the hardware to interpolate them.
        We'll have to normalize the interpolated values, but this 
        is less processor intensive than doing the calculations 
        for each fragment.
        
        We are doing our vector calculations for the light location
        and distance in model-space.  You could do them in view-space 
        as well.
        
        Our vertex calculations are getting complex enough that we're 
        going to split them into a separate function for readability and 
        (eventual) reusability.  We'll create a function which encodes the 
        algorithmic operation (phong_preCalc) and another which takes our 
        particular attributes/uniforms and accumulates the results from 
        that function.
        
        We're defining phong_preCalc inline here, but we'll also store it 
        as a resource we can use from anywhere:
        
            from OpenGLContext.resources.phongprecalc_vert import data as phong_preCalc
        '''
        phong_preCalc = """
        // Vertex-shader pre-calculation for lighting...
        void phong_preCalc( 
            in vec3 vertex_position,
            in vec4 light_position,
            out float light_distance,
            out vec3 ec_light_location,
            out vec3 ec_light_half
        ) {
            // This is the core setup for a phong lighting pass 
            // as a reusable fragment of code.
            
            // vertex_position -- un-transformed vertex position (world-space)
            // light_position -- un-transformed light location (direction)
            // light_distance -- output giving world-space distance-to-light 
            // ec_light_location -- output giving location of light in eye coords 
            // ec_light_half -- output giving the half-vector optimization
            
            if (light_position.w == 0.0) {
                // directional rather than positional light...
                ec_light_location = normalize(
                    gl_NormalMatrix *
                    light_position.xyz
                );
                light_distance = 0.0;
            } else {
                // positional light, we calculate distance in 
                // model-view space here, so we take a partial 
                // solution...
                vec3 ms_vec = (
                    light_position.xyz -
                    vertex_position
                );
                vec3 light_direction = gl_NormalMatrix * ms_vec;
                ec_light_location = normalize( light_direction );
                light_distance = abs(length( ms_vec ));
            }
            // half-vector calculation 
            ec_light_half = normalize(
                ec_light_location + vec3( 0,0,1 )
            );
        }"""
        '''This function is not as generally reusable, so we'll store it 
        to a separate file named '_shader_tut_lightprecalc.vert'.'''
        light_preCalc = """
        void light_preCalc( in vec3 vertex_position ) {
            // This function is dependent on the uniforms and 
            // varying values we've been using, it basically 
            // just iterates over the phong_lightCalc passing in 
            // the appropriate pointers...
            vec3 light_direction;
            for (int i = 0; i< LIGHT_COUNT; i++ ) {
                int j = i * LIGHT_SIZE;
                phong_preCalc(
                    vertex_position,
                    lights[j+POSITION],
                    // following are the values to fill in...
                    Light_distance[i],
                    EC_Light_location[i],
                    EC_Light_half[i]
                );
            }
        }
        """
        vertex = shaders.compileShader(
            lightConst + phong_preCalc + light_preCalc + """
        attribute vec3 Vertex_position;
        attribute vec3 Vertex_normal;
        void main() {
            gl_Position = gl_ModelViewProjectionMatrix * vec4( 
                Vertex_position, 1.0
            );
            baseNormal = gl_NormalMatrix * normalize(Vertex_normal);
            light_preCalc(Vertex_position);
        }""", GL_VERTEX_SHADER)
        '''Our fragment shader is only slightly modified to use our 
        new phong_weightCalc function.  We need a larger "weights" variable and 
        need to pass in more information.  We also need to multiply 
        the per-light ambient value by the new weight we've added.
        
        You will also notice that since we are using the 'i' variable 
        to directly index the varying arrays, we've introduced a 'j' 
        variable that tracks the offset into the light array which 
        begins the current light.
        '''
        fragment = shaders.compileShader(
            lightConst + phong_weightCalc + """
        struct Material {
            vec4 ambient;
            vec4 diffuse;
            vec4 specular;
            float shininess;
        };
        uniform Material material;
        uniform vec4 Global_ambient;
        
        void main() {
            vec4 fragColor = Global_ambient * material.ambient;
            int i,j;
            for (i=0;i<LIGHT_COUNT;i++) {
                j = i* LIGHT_SIZE;
                vec3 weights = phong_weightCalc(
                    normalize(EC_Light_location[i]),
                    normalize(EC_Light_half[i]),
                    baseNormal,
                    material.shininess,
                    // some implementations will produce negative values interpolating positive float-arrays!
                    // so we have to do an extra abs call for distance
                    abs(Light_distance[i]),
                    lights[j+ATTENUATION]
                );
                fragColor = (
                    fragColor 
                    + (lights[j+AMBIENT] * material.ambient * weights.x)
                    + (lights[j+DIFFUSE] * material.diffuse * weights.y)
                    + (lights[j+SPECULAR] * material.specular * weights.z)
                );
            }
            //fragColor = vec4(Light_distance[0],Light_distance[1],Light_distance[2],1.0);
            gl_FragColor = fragColor;
        }
        """, GL_FRAGMENT_SHADER)
        '''Our general uniform setup should look familiar by now.'''
        self.shader = shaders.compileProgram(vertex, fragment)
        self.coords, self.indices, self.count = Sphere(radius=1).compile()
        self.uniform_locations = {}
        for uniform, value in self.UNIFORM_VALUES:
            location = glGetUniformLocation(self.shader, uniform)
            if location in (None, -1):
                print('Warning, no uniform: %s' % (uniform))
            self.uniform_locations[uniform] = location
        self.uniform_locations['lights'] = glGetUniformLocation(
            self.shader, 'lights')
        for attribute in (
                'Vertex_position',
                'Vertex_normal',
        ):
            location = glGetAttribLocation(self.shader, attribute)
            if location in (None, -1):
                print('Warning, no attribute: %s' % (uniform))
            setattr(self, attribute + '_loc', location)

    UNIFORM_VALUES = [
        ('Global_ambient', (.05, .05, .05, 1.0)),
        ('material.ambient', (.2, .2, .2, 1.0)),
        ('material.diffuse', (.5, .5, .5, 1.0)),
        ('material.specular', (.8, .8, .8, 1.0)),
        ('material.shininess', (2.0, )),
    ]
    '''We've created 3 equal-distance lights here, in red, green and 
    blue.  The green light uses linear attenuation, the red quadratic
    and the blue constant.
    '''
    LIGHTS = array([
        x[1] for x in [
            ('lights[0].ambient', (.05, .05, .05, 1.0)),
            ('lights[0].diffuse', (.1, .8, .1, 1.0)),
            ('lights[0].specular', (0.0, 1.0, 0.0, 1.0)),
            ('lights[0].position', (2.5, 2.5, 2.5, 1.0)),
            ('lights[0].attenuation', (0.0, .15, 0.0, 1.0)),
            ('lights[1].ambient', (.05, .05, .05, 1.0)),
            ('lights[1].diffuse', (.8, .1, .1, 1.0)),
            ('lights[1].specular', (1.0, 0.0, 0.0, 1.0)),
            ('lights[1].position', (-2.5, 2.5, 2.5, 1.0)),
            ('lights[1].attenuation', (0.0, 0.0, .15, 1.0)),
            ('lights[2].ambient', (.05, .05, .05, 1.0)),
            ('lights[2].diffuse', (.1, .1, .8, 1.0)),
            ('lights[2].specular', (0.0, 0.0, 1.0, 1.0)),
            ('lights[2].position', (0.0, -3.06, 3.06, 1.0)),
            ('lights[2].attenuation', (.15, 0.0, 0.0, 1.0)),
        ]
    ], 'f')

    def Render(self, mode=None):
        """Render the geometry for the scene."""
        BaseContext.Render(self, mode)
        if not mode.visible:
            return
        glUseProgram(self.shader)
        try:
            self.coords.bind()
            self.indices.bind()
            stride = self.coords.data[0].nbytes
            try:
                '''Again, we're using the parameterized light size/count 
                to pass in the array.'''
                glUniform4fv(self.uniform_locations['lights'],
                             self.LIGHT_COUNT * self.LIGHT_SIZE, self.LIGHTS)
                for uniform, value in self.UNIFORM_VALUES:
                    location = self.uniform_locations.get(uniform)
                    if location not in (None, -1):
                        if len(value) == 4:
                            glUniform4f(location, *value)
                        elif len(value) == 3:
                            glUniform3f(location, *value)
                        elif len(value) == 1:
                            glUniform1f(location, *value)
                glEnableVertexAttribArray(self.Vertex_position_loc)
                glEnableVertexAttribArray(self.Vertex_normal_loc)
                glVertexAttribPointer(self.Vertex_position_loc, 3, GL_FLOAT,
                                      False, stride, self.coords)
                glVertexAttribPointer(self.Vertex_normal_loc, 3, GL_FLOAT,
                                      False, stride, self.coords + (5 * 4))
                glDrawElements(GL_TRIANGLES, self.count, GL_UNSIGNED_SHORT,
                               self.indices)
            finally:
                self.coords.unbind()
                self.indices.unbind()
                glDisableVertexAttribArray(self.Vertex_position_loc)
                glDisableVertexAttribArray(self.Vertex_normal_loc)
        finally:
            glUseProgram(0)
Beispiel #7
0
class TestContext(BaseContext):
    def OnInit(self):
        phong_weightCalc = '''
        vec2 phong_weightCalc(
            in vec3 light_pos,
            in vec3 half_light,
            in vec3 frag_normal,
            in float shininess
        ){
            // get the dot product of the normal and the light ray
            float n_dot_pos = max(0.0, dot(frag_normal, light_pos));

            // set the specular reflection to 0
            float n_dot_half = 0.0;
            if (n_dot_pos >= -0.05){
                // get the transformed dot product of the 'half light'
                // this is our new speccular reflection weight
                n_dot_half = pow(max(0.0, dot(half_light, frag_normal)), shininess);
            }
            // return both the diffuse and specular reflection, n_dot_pos is the
            // weight of the diffuse light, n_dot_half is the weight for the
            // specular light
            return vec2(n_dot_pos, n_dot_half);
        }
        '''

        vs = '''
        attribute vec3 Vertex_position;
        attribute vec3 Vertex_normal;
        varying vec3 baseNormal;
        void main(){
            // store the vertex position
            gl_Position = gl_ModelViewProjectionMatrix * vec4(Vertex_position, 1.0);
            // store the vertex normal
            baseNormal = gl_NormalMatrix * normalize(Vertex_normal);
        }
        '''
        vertex = shaders.compileShader(vs, GL_VERTEX_SHADER)

        fs = '''
        // define a bunch of stuff
        uniform vec4 Global_ambient;
        uniform vec4 Light_ambient;
        uniform vec4 Light_diffuse;
        uniform vec4 Light_specular;
        uniform vec3 Light_location;
        uniform float Material_shininess;
        uniform vec4 Material_specular;
        uniform vec4 Material_ambient;
        uniform vec4 Material_diffuse;
        varying vec3 baseNormal;
        void main(){
            // get the light location in eye space
            vec3 EC_Light_location = normalize(gl_NormalMatrix * Light_location);

            // get the half light between the light ray and the viewpoint
            vec3 Light_half = normalize(EC_Light_location - vec3(0,0,-1));

            //get the weights for specular and diffuse light from the current view
            vec2 weights = phong_weightCalc(
                EC_Light_location,
                Light_half,
                baseNormal,
                Material_shininess
            );

            // return the fragment color with various lighting added in
            gl_FragColor = clamp(
                // global ambient light
                (Global_ambient * Material_ambient)
                // ambient light from light source
                + (Light_ambient * Material_ambient)
                // the diffuse light calcualted by blinn-phong function
                + (Light_diffuse * Material_diffuse * weights.x)
                // the specular light (spotlight) calculated by blinn-phong function
                + (Light_specular * Material_specular * weights.y)
                , 0.0, 1.0);
        }
        '''
        fragment = shaders.compileShader(phong_weightCalc + fs,
                                         GL_FRAGMENT_SHADER)
        self.shader = shaders.compileProgram(vertex, fragment)
        # create two vbos to represent a sphere, coordinates and indicies
        # and return a count to tell us how many things to display
        self.coords, self.indices, self.count = Sphere(radius=1).compile()

        # setup uniform values,
        # basically map each memory
        # location in our script with
        # an attribute on our python class
        uniform_vals = ('Global_ambient', 'Light_ambient', 'Light_diffuse',
                        'Light_location', 'Light_specular', 'Material_ambient',
                        'Material_diffuse', 'Material_shininess',
                        'Material_specular')

        for uniform in uniform_vals:
            location = glGetUniformLocation(self.shader, uniform)
            if location in (None, -1):
                print('Warning, no uniform: {}'.format(uniform))
            setattr(self, uniform + '_loc', location)

        # do what we did before for shader attribute values
        attribute_vals = ('Vertex_position', 'Vertex_normal')

        for attribute in attribute_vals:
            location = glGetAttribLocation(self.shader, attribute)
            if location in (None, -1):
                print('Warning no attribute: {}'.format(attribute))
            setattr(self, attribute + '_loc', location)

    def Render(self, mode=None):
        BaseContext.Render(self, mode)
        glUseProgram(self.shader)
        try:
            self.coords.bind()
            self.indices.bind()
            # set our stride value to be the number of bytes per coordinate
            stride = self.coords.data[0].nbytes
            try:
                # set our uniform values to their values
                glUniform4f(self.Global_ambient_loc, .05, .05, .05, .1)
                glUniform4f(self.Light_ambient_loc, .1, .1, .1, 1.0)
                glUniform4f(self.Light_diffuse_loc, .25, .25, .25, 1)
                glUniform4f(self.Light_specular_loc, 0.0, 1.0, 0, 1)
                glUniform3f(self.Light_location_loc, 6, 2, 4)
                glUniform4f(self.Material_ambient_loc, .1, .1, .1, 1.0)
                glUniform4f(self.Material_diffuse_loc, .15, .15, .15, 1)
                glUniform4f(self.Material_specular_loc, 1.0, 1.0, 1.0, 1.0)
                glUniform1f(self.Material_shininess_loc, .95)

                # enable and draw all our data points
                glEnableVertexAttribArray(self.Vertex_position_loc)
                glEnableVertexAttribArray(self.Vertex_normal_loc)
                glVertexAttribPointer(self.Vertex_position_loc, 3, GL_FLOAT,
                                      False, stride, self.coords)
                glVertexAttribPointer(self.Vertex_normal_loc, 3, GL_FLOAT,
                                      False, stride, self.coords + (5 * 4))
                # draw 'self.count' number of things
                # (this was stored when we made the sphere)
                # notice that glDrawElements is different
                # from glDrawArrays, this is the indexed
                # vbo scheme where we reuse verts and render
                # based on indices
                glDrawElements(GL_TRIANGLES, self.count, GL_UNSIGNED_SHORT,
                               self.indices)
            finally:
                self.coords.unbind()
                self.indices.unbind()
                glDisableVertexAttribArray(self.Vertex_position_loc)
                glDisableVertexAttribArray(self.Vertex_normal_loc)
        finally:
            glUseProgram(0)
Beispiel #8
0
class TestContext(BaseContext):
    def OnInit(self):
        # new structure that contains
        # material properties
        materialStruct = '''
        struct Material {
            vec4 ambient;
            vec4 diffuse;
            vec4 specular;
            float shininess;
        };
        '''

        phong_weightCalc = '''
        vec2 phong_weightCalc(
            in vec3 light_pos,
            in vec3 half_light,
            in vec3 frag_normal,
            in float shininess
        ){
            float n_dot_pos = max(0.0, dot(frag_normal, light_pos));
            float n_dot_half = 0.0;
            // changed from tutorial 6 phong weight calc after my inblog
            // investigation
            if (n_dot_pos >= 0.0){
                n_dot_half = pow(max(0.0, dot(half_light, frag_normal)), shininess);
            }
            return vec2(n_dot_pos, n_dot_half);
        }
        '''

        vs = '''
        attribute vec3 Vertex_position;
        attribute vec3 Vertex_normal;
        varying vec3 baseNormal;
        void main(){
            gl_Position = gl_ModelViewProjectionMatrix * vec4(Vertex_position, 1.0);
            baseNormal = gl_NormalMatrix * normalize(Vertex_normal);
        }
        '''
        vertex = shaders.compileShader(vs, GL_VERTEX_SHADER)

        fs = '''
        // define 3 lights containing
        // ambient, diffuse, and specular, and position (4 things)
        // 4(vectors per light)*3(lights) = 12
        uniform vec4 lights [12];
        uniform Material material;
        uniform vec4 Global_ambient;
        varying vec3 baseNormal;
        void main(){
            // setup the baseline color for the fragment
            vec4 fragColor = Global_ambient * material.ambient;

            // setup some constant indices for referencing parts
            // of lights
            int AMBIENT = 0;
            int DIFFUSE = 1;
            int SPECULAR = 2;
            int POSITION = 3;

            // for every light
            int i;
            for (i=0; i<12;i=i+4){
                //normalize light location, eye coordinates
                vec3 EC_Light_location = normalize(gl_NormalMatrix * lights[i+POSITION].xyz);

                // half light vector calculation
                vec3 Light_half = normalize(EC_Light_location - vec3(0,0,-1));

                // get phong weights
                vec2 weights = phong_weightCalc(
                    EC_Light_location,
                    Light_half,
                    baseNormal,
                    material.shininess
                );

                // calculate/update the fragment color with new information
                fragColor = (
                    fragColor
                    + (lights[i+AMBIENT] * material.ambient)
                    + (lights[i+DIFFUSE] * material.diffuse * weights.x)
                    + (lights[i+SPECULAR] * material.specular * weights.y)
                );
            }
            gl_FragColor = fragColor;
        }
        '''
        fragment = shaders.compileShader(
            phong_weightCalc + materialStruct + fs, GL_FRAGMENT_SHADER)
        self.shader = shaders.compileProgram(vertex, fragment)
        # create two vbos to represent a sphere, coordinates and indicies
        # and return a count to tell us how many things to display
        self.coords, self.indices, self.count = Sphere(radius=1).compile()

        # get uniform variable locations ready to be populated
        self.UNIFORM_VALUES = [
            ('Global_ambient', (.05, .05, .05, 1.0)),
            ('material.ambient', (.2, .2, .2, 1.0)),
            ('material.diffuse', (.5, .5, .5, 1.0)),
            ('material.specular', (.8, .8, .8, 1.0)),
            ('material.shininess', (.995, )),
        ]

        self.uniform_locations = {}
        for uniform, _ in self.UNIFORM_VALUES:
            location = glGetUniformLocation(self.shader, uniform)
            if location in (None, -1):
                print('Warning, no uniform: {}'.format(uniform))
            self.uniform_locations[uniform] = location
        self.uniform_locations['lights'] = glGetUniformLocation(
            self.shader, 'lights')

        # get attribute values ready to be populated
        attribute_vals = ('Vertex_position', 'Vertex_normal')

        for attribute in attribute_vals:
            location = glGetAttribLocation(self.shader, attribute)
            if location in (None, -1):
                print('Warning no attribute: {}'.format(attribute))
            setattr(self, attribute + '_loc', location)

        # define our actual lights, ambient, diffuse, specular
        # contributions as well as position
        self.LIGHTS = array(
            [[
                (.05, .05, .05, 1.0),  #'lights[0].ambient'
                (.3, .3, .3, 1.0),  #'lights[0].diffuse'
                (1.0, 0.0, 0.0, 1.0),  #'lights[0].specular'
                (4.0, 2.0, 10.0, 0.0),  #'lights[0].position
                (.05, .05, .05, 1.0),  #'lights[1].ambient'
                (.3, .3, .3, 1.0),  #'lights[1].diffuse'
                (0.0, 1.0, 0.0, 1.0),  #'lights[1].specular'
                (-4.0, 2.0, 10.0, 0.0),  # 'lights[1].position
                (.05, .05, .05, 1.0),  #('lights[2].ambient',
                (.3, .3, .3, 1.0),  #lights[2].diffuse'
                (0.0, 0.0, 1.0, 1.0),  #'lights[2].specular'
                (-4.0, 2.0, -10.0, 0.0)  #'lights[2].position
            ]],
            'f')

    def Render(self, mode=None):
        BaseContext.Render(self, mode)
        glUseProgram(self.shader)
        try:
            # bind in our coordinates and indices
            # into gpu ram
            self.coords.bind()
            self.indices.bind()
            stride = self.coords.data[0].nbytes
            try:
                # set our lights with the values defined above
                # this is a different function '4fv', instead of '4f'
                glUniform4fv(self.uniform_locations['lights'], 12, self.LIGHTS)

                for uniform, value in self.UNIFORM_VALUES:
                    location = self.uniform_locations.get(uniform)
                    if location not in (None, -1):
                        if len(value) == 4:
                            glUniform4f(location, *value)
                        elif len(value) == 3:
                            glUniform3f(location, *value)
                        elif len(value) == 1:
                            glUniform1f(location, *value)

                # setup all our vertices / normals and draw them
                glEnableVertexAttribArray(self.Vertex_position_loc)
                glEnableVertexAttribArray(self.Vertex_normal_loc)

                glVertexAttribPointer(self.Vertex_position_loc, 3, GL_FLOAT,
                                      False, stride, self.coords)
                glVertexAttribPointer(self.Vertex_normal_loc, 3, GL_FLOAT,
                                      False, stride, self.coords + (5 * 4))

                glDrawElements(GL_TRIANGLES, self.count, GL_UNSIGNED_SHORT,
                               self.indices)
            finally:
                self.coords.unbind()
                self.indices.unbind()
                glDisableVertexAttribArray(self.Vertex_position_loc)
                glDisableVertexAttribArray(self.Vertex_normal_loc)
        finally:
            glUseProgram(0)
Beispiel #9
0
class TestContext( BaseContext ):
    """Demonstrates use of attribute types in GLSL
    """
    def OnInit( self ):
        """Initialize the context"""
        '''== Phong and Blinn Reflectance ==

        A shiny surface will tend to have a "bright spot" at the point 
        on the surface where the angle of incidence for the reflected
        light ray and the viewer's ray are (close to) equal.  
        A perfect mirror would have the brights spot solely when the 
        two vectors are exactly equal, while a perfect Lambertian
        surface would have the "bright spot" spread across the entire
        surface.
        
        The Phong rendering process models this as a setting, traditionally
        called material "shininess" in Legacy OpenGL.  This setting acts 
        as a power which raises the cosine (dot product) of the 
        angle between the reflected ray and the eye.  The calculation of 
        the cosine (dot product) of the two angles requires that we do 
        a dot product of the two angles once for each vertex/fragment 
        for which we wish to calculate the specular reflectance, we also 
        have to find the angle of reflectance before we can do the 
        calculation:'''
        """
            L_dir = (V_pos-L_pos)
            R = 2N*(dot( N, L_dir))-L_dir
            // Note: in eye-coordinate system, Eye_pos == (0,0,0)
            Spec_factor = pow( dot( R, V_pos-Eye_pos ), shininess)
        """
        '''which, as we can see, involves the vertex position in a number 
        of stages of the operation, so requires recalculation all through 
        the rendering operation.
        
        There is, however, a simplified version of Phong Lighting called 
        [http://en.wikipedia.org/wiki/Blinn%E2%80%93Phong_shading_model Blinn-Phong]
        which notes that if we were to do all of our calculations in 
        "eye space", and were to assume that (as is normal), the eye 
        and light coordinates will not change for a rendering pass,
        (note: this limits us to directional lights!) we 
        can use a pre-calculated value which is the bisecting angle
        between the light-vector and the view-vector, called the 
        "half vector" to 
        perform approximately the same calculation.  With this value:'''
        """
            // note that in Eye coordinates, Eye_EC_dir == 0,0,-1
            H = normalize( Eye_EC_dir + Light_EC_dir )
            Spec_factor = pow( dot( H, N ), shininess )
        """
        '''Note: however, that the resulting Spec_factor is not *precisely*
        the same value as the original calculation, so the "shininess"
        exponent must be slightly lower to approximate the value that
        Phong rendering would achieve.  The value is, however, considered
        close to "real world" materials, so the Blinn method is generally 
        preferred to Phong.
        
        Traditionally, n_dot_pos would be cut off at 0.0, but that would 
        create extremely hard-edged cut-offs for specular color.  Here 
        we "fudge" the result by 0.05
        '''
        phong_weightCalc = """
        vec2 phong_weightCalc( 
            in vec3 light_pos, // light position
            in vec3 half_light, // half-way vector between light and view
            in vec3 frag_normal, // geometry normal
            in float shininess
        ) {
            // returns vec2( ambientMult, diffuseMult )
            float n_dot_pos = max( 0.0, dot( 
                frag_normal, light_pos
            ));
            float n_dot_half = 0.0;
            if (n_dot_pos > -.05) {
                n_dot_half = pow(max(0.0,dot( 
                    half_light, frag_normal
                )), shininess);
            }
            return vec2( n_dot_pos, n_dot_half);
        }		
        """
        '''We are going to use per-fragment rendering.
        As a result, our vertex shader becomes very simple, just arranging
        for the Normals to be varied across the surface.
        '''
        vertex = shaders.compileShader( 
        """
        attribute vec3 Vertex_position;
        attribute vec3 Vertex_normal;
        
        varying vec3 baseNormal;
        void main() {
            gl_Position = gl_ModelViewProjectionMatrix * vec4( 
                Vertex_position, 1.0
            );
            baseNormal = gl_NormalMatrix * normalize(Vertex_normal);
        }""", GL_VERTEX_SHADER)
        '''Our fragment shader looks much like our previous tutorial's 
        vertex shader.  As before, we have lots of uniform values,
        but now we also calculate the light's half-vector (in eye-space 
        coordinates).  The phong_weightCalc function does the core Blinn 
        calculation, and we simply use the resulting factor to add to 
        the colour value for the fragment.
        
        Note the use of the eye-coordinate-space to simplify the 
        half-vector calculation, the eye-space eye-vector is always 
        the same value (pointing down the negative Z axis), 
        and the eye-space eye-coordinate is always (0,0,0), so the 
        eye-to-vertex vector is always the eye-space vector position.
        '''
        fragment = shaders.compileShader( phong_weightCalc + """
        uniform vec4 Global_ambient;
        
        uniform vec4 Light_ambient;
        uniform vec4 Light_diffuse;
        uniform vec4 Light_specular;
        uniform vec3 Light_location;
        
        uniform float Material_shininess;
        uniform vec4 Material_specular;
        uniform vec4 Material_ambient;
        uniform vec4 Material_diffuse;
        
        varying vec3 baseNormal;
        void main() {
            // normalized eye-coordinate Light location
            vec3 EC_Light_location = normalize(
                gl_NormalMatrix * Light_location
            );
            // half-vector calculation 
            vec3 Light_half = normalize(
                EC_Light_location - vec3( 0,0,-1 )
            );
            vec2 weights = phong_weightCalc(
                EC_Light_location,
                Light_half,
                baseNormal,
                Material_shininess
            );
            
            gl_FragColor = clamp( 
            (
                (Global_ambient * Material_ambient)
                + (Light_ambient * Material_ambient)
                + (Light_diffuse * Material_diffuse * weights.x)
                // material's shininess is the only change here...
                + (Light_specular * Material_specular * weights.y)
            ), 0.0, 1.0);
        }
        """, GL_FRAGMENT_SHADER)
        
        self.shader = shaders.compileProgram(vertex,fragment)
        '''Here's the call that creates the two VBOs and the 
        count of records to render from them. If you're curious 
        you can read through the source code of the 
        OpenGLContext.scenegraph.quadrics module to read the 
        mechanism that generates the values.
        
        The sphere is a simple rendering mechanism, as for a 
        unit-sphere at the origin, the sphere's normals are the 
        same as the sphere's vertex coordinate.  The complexity 
        comes primarily in generating the triangle indices that 
        link the points generated.
        '''
        self.coords,self.indices,self.count = Sphere( 
            radius = 1 
        ).compile()
        '''We have a few more uniforms to control the specular 
        components.  Real-world coding would also calculate the 
        light's half-vector and provide it as a uniform (so that 
        it would only need to be calculated once), but we are going 
        to do the half-vector calculation in the shader to make 
        it obvious what is going on.  The legacy OpenGL pipeline 
        provides the value pre-calculated as part of the light structure 
        in GLSL.
        '''
        for uniform in (
            'Global_ambient',
            'Light_ambient','Light_diffuse','Light_location',
            'Light_specular',
            'Material_ambient','Material_diffuse',
            'Material_shininess','Material_specular',
        ):
            location = glGetUniformLocation( self.shader, uniform )
            if location in (None,-1):
                print 'Warning, no uniform: %s'%( uniform )
            setattr( self, uniform+ '_loc', location )
        for attribute in (
            'Vertex_position','Vertex_normal',
        ):
            location = glGetAttribLocation( self.shader, attribute )
            if location in (None,-1):
                print 'Warning, no attribute: %s'%( uniform )
            setattr( self, attribute+ '_loc', location )
    
    def Render( self, mode = None):
        """Render the geometry for the scene."""
        BaseContext.Render( self, mode )
        glUseProgram(self.shader)
        try:
            '''==Indexed VBO Rendering==
            
            You'll notice here that we are binding two different VBO 
            objects.  As we mentioned above, the Sphere renderer 
            generated both VBOs, but doesn't the second binding replace 
            the first binding?  That is, why doesn't OpenGL try to read 
            the Vertex data out of the indices VBO?
            
            OpenGL defines multiple binding "targets" for VBOs, the 
            first VBO (vertices) was bound to the GL_ARRAY_BUFFER
            target (the default for the class), which is used for reading 
            per-vertex data arrays, while the indices buffer was defined
            as targetting the GL_ELEMENT_ARRAY_BUFFER, which is used
            solely for reading indices.
            
            Each target can be bound to a different VBO, and thus we can
            bind both VBOs at the same time without confusion.
            '''
            self.coords.bind()
            self.indices.bind()
            '''Here, being lazy, we use the numpy array's nbytes value 
            to specify the stride between records.  The VBO object has 
            a "data" value which is the data-set which was initially 
            passed to the VBO constructor.  The first element in this 
            array is a single vertex record.  This array happens to have 
            8 floating-point values (24 bytes), the first three being 
            the vertex position, the next two being the texture coordinate 
            and the last three being the vertex normal.  We'll ignore 
            the texture coordinate for now.
            '''
            stride = self.coords.data[0].nbytes
            try:
                glUniform4f( self.Global_ambient_loc, .05,.05,.05,.1 )
                glUniform4f( self.Light_ambient_loc, .1,.1,.1, 1.0 )
                glUniform4f( self.Light_diffuse_loc, .25,.25,.25,1 )
                '''We set up a yellow-ish specular component in the 
                light and move it to rest "just over our right shoulder"
                in relation to the initial camera.'''
                glUniform4f( self.Light_specular_loc, 0.0,1.0,0,1 )
                glUniform3f( self.Light_location_loc, 6,2,4 )
                
                glUniform4f( self.Material_ambient_loc, .1,.1,.1, 1.0 )
                glUniform4f( self.Material_diffuse_loc, .15,.15,.15, 1 )
                '''We make the material have a bright specular white 
                colour and an extremely "shiny" surface.  The shininess 
                value has the effect of reducing the area of the
                highlight, as the cos of the angle is raised 
                to the power of the (fractional) shininess.'''
                glUniform4f( self.Material_specular_loc, 1.0,1.0,1.0, 1.0 )
                glUniform1f( self.Material_shininess_loc, .95)
                glEnableVertexAttribArray( self.Vertex_position_loc )
                glEnableVertexAttribArray( self.Vertex_normal_loc )
                glVertexAttribPointer( 
                    self.Vertex_position_loc, 
                    3, GL_FLOAT,False, stride, self.coords
                )
                glVertexAttribPointer( 
                    self.Vertex_normal_loc, 
                    3, GL_FLOAT,False, stride, self.coords+(5*4)
                )
                '''Here we introduce the OpenGL call which renders via 
                an index-array rather than just rendering vertices in 
                definition order.  The last two arguments tell OpenGL 
                what data-type we've used for the indices (the Sphere 
                renderer uses shorts).  The indices VBO is actually 
                just passing the value c_void_p( 0 ) (i.e. a null pointer),
                which causes OpenGL to use the currently bound VBO for 
                the GL_ELEMENT_ARRAY_BUFFER target.
                '''
                glDrawElements(
                    GL_TRIANGLES, self.count,
                    GL_UNSIGNED_SHORT, self.indices
                )
            finally:
                '''Note the need to unbind *both* VBOs, we have to free 
                *both* VBO targets to avoid any other rendering operation 
                from trying to access the VBOs.'''
                self.coords.unbind()
                self.indices.unbind()
                glDisableVertexAttribArray( self.Vertex_position_loc )
                glDisableVertexAttribArray( self.Vertex_normal_loc )
        finally:
            glUseProgram( 0 )
Beispiel #10
0
class TestContext(BaseContext):
    """Demonstrates use of attribute types in GLSL
    """
    def OnInit(self):
        """Initialize the context"""
        '''==GLSL Structures==

        We have previously been using values named Material_ambient,
        Material_diffuse, etceteras to specify our Material's properties.
        GLSL allows us to bind these kinds of values together into a
        structure.  The structure doesn't provide many benefits other
        than keeping the namespaces of your code clean and allowing for
        declaring multiple uniforms of the same type, such as a "front"
        and "back" material.

        We are going to define a very simple Material struct which is
        a subset of the built-in gl_MaterialParameters structure
        (which also has an "emission" parameter).  GLSL defines two
        built-in Material uniforms gl_FrontMaterial and gl_BackMaterial.
        It is possible (though seldom done) to fill in these uniform
        values with glUniform calls rather than the legacy glMaterial
        calls.
        '''
        materialStruct = """
        struct Material {
            vec4 ambient;
            vec4 diffuse;
            vec4 specular;
            float shininess;
        };
        """
        '''Note that each sub-element must be terminated with a semi-colon
        ';' character, and that qualifiers (in, out, uniform, etceteras)
        are not allowed within the structure definition.  This statement
        has to occur *before* any use of the structure to declare a
        variable as being of this type.

        Our light-weighting code has not changed from the previous
        tutorial.  It is still a Blinn-Phong calculation based on the
        half-vector of light and view vector.
        '''
        phong_weightCalc = """
        vec2 phong_weightCalc(
            in vec3 light_pos, // light position
            in vec3 half_light, // half-way vector between light and view
            in vec3 frag_normal, // geometry normal
            in float shininess
        ) {
            // returns vec2( ambientMult, diffuseMult )
            float n_dot_pos = max( 0.0, dot(
                frag_normal, light_pos
            ));
            float n_dot_half = 0.0;
            if (n_dot_pos > -.05) {
                n_dot_half = pow(max(0.0,dot(
                    half_light, frag_normal
                )), shininess);
            }
            return vec2( n_dot_pos, n_dot_half);
        }
        """
        '''Our vertex shader is also unchanged.  We could move many
        of the operations currently done in our fragment shader here
        to reduce the processing load for our shader.
        '''
        vertex = shaders.compileShader(
            """
        attribute vec3 Vertex_position;
        attribute vec3 Vertex_normal;

        varying vec3 baseNormal;
        void main() {
            gl_Position = gl_ModelViewProjectionMatrix * vec4(
                Vertex_position, 1.0
            );
            baseNormal = gl_NormalMatrix * normalize(Vertex_normal);
        }""", GL_VERTEX_SHADER)
        '''To create a uniform with a structure type, we simply use
        the structure as the data-type declaration for the uniform.
        As opposed to using e.g. vec4 or vec3, we use Material (our
        structure name defined above) and give the uniform a name.
        '''
        """uniform Material material;"""
        '''==GLSL Arrays==

        Each light we have defined (so far) is composed to 4 4-component
        vectors, ambient, diffuse and specular colour, along with the
        "position" (direction) vector.  If we wanted to provide, for
        instance, 3 lights of this type, we *could* create 12 different
        uniform values, and set each of these uniforms individually.

        GLSL, however, provides for array data-types.  The array types
        must be "sized" (have a specific, final size) in order to be
        usable, so no "pointer" types are available, but we don't need
        them for this type of operation.  We can define a "lights"
        uniform which is declared as a sized array of 12 vec4 elements:'''
        """uniform vec4 lights[ 12 ];"""
        '''And we can loop over "lights" using 0-indexed [i] indices,
        where i must be an integer.  The for loop will be familiar to
        those who have used C looping:'''
        """for (i=0;i<12;i=i+4) { blah; }"""
        '''Note that you must declare the iterator variable ("i" here)

        We iterate over each light in our array of lights accumulating
        the results into the fragColor variable we've defined.  The
        global component is used to initialize the variable, with the
        contribution of each light added to the result.
        '''
        fragment = shaders.compileShader(
            phong_weightCalc + materialStruct + """
        uniform Material material;
        uniform vec4 Global_ambient;
        uniform vec4 lights[ 12 ]; // 3 possible lights 4 vec4's each

        varying vec3 baseNormal;
        void main() {
            vec4 fragColor = Global_ambient * material.ambient;

            int AMBIENT = 0;
            int DIFFUSE = 1;
            int SPECULAR = 2;
            int POSITION = 3;

            int i;
            for (i=0;i<12;i=i+4) {
                // normalized eye-coordinate Light location
                vec3 EC_Light_location = normalize(
                    gl_NormalMatrix * lights[i+POSITION].xyz
                );
                // half-vector calculation
                vec3 Light_half = normalize(
                    EC_Light_location - vec3( 0,0,-1 )
                );
                vec2 weights = phong_weightCalc(
                    EC_Light_location,
                    Light_half,
                    baseNormal,
                    material.shininess
                );
                fragColor = (
                    fragColor
                    + (lights[i+AMBIENT] * material.ambient)
                    + (lights[i+DIFFUSE] * material.diffuse * weights.x)
                    + (lights[i+SPECULAR] * material.specular * weights.y)
                );
            }
            gl_FragColor = fragColor;
        }
        """, GL_FRAGMENT_SHADER)
        '''===Why not an Array of Structures?===

        Originally this tutorial was going to use an array of LightSource
        structures as a Uniform, with the components of the structures
        specified with separate calls to glUniform4f.  Problem is, that
        doesn't actually *work*.  While glUniform *should* be able to
        handle array-of-structure indexing, it doesn't actually support
        this type of operation in the real world. The built-in
        gl_LightSourceParameters are an array-of-structures, but
        apparently the GL implementations consider this a special case,
        rather than a generic type of functionality to be supported.

        An array-of-structures value looks like this when declared in GLSL:
        '''
        lightStruct = """
        // NOTE: this does not work, it compiles, but you will
        // not be able to fill in the individual members...
        struct LightSource {
            vec4 ambient;
            vec4 diffuse;
            vec4 specular;
            vec4 position;
        };
        uniform LightSource lights[3];
        """
        '''When you attempt to retrieve the location for the Uniform
        via:

            glGetUniformLocation( shader, 'lights[0].ambient' )

        you will always get a -1 (invalid) location.

        OpenGL 3.1 introduced the concept of Uniform Buffers, which allow
        for packing Uniform data into VBO storage, but it's not yet clear
        whether they will support array-of-structure specification.
        '''
        self.shader = shaders.compileProgram(vertex, fragment)
        self.coords, self.indices, self.count = Sphere(radius=1).compile()
        self.uniform_locations = {}
        for uniform, value in self.UNIFORM_VALUES:
            location = glGetUniformLocation(self.shader, uniform)
            if location in (None, -1):
                print('Warning, no uniform: %s' % (uniform))
            self.uniform_locations[uniform] = location
        '''There's no real reason to treat the "lights" uniform specially,
        other than that we want to call attention to it.  We get the
        uniform as normal.  Note that we *could* also retrieve a
        sub-element of the array by specifying 'lights[3]' or the like.
        '''
        self.uniform_locations['lights'] = glGetUniformLocation(
            self.shader, 'lights')
        for attribute in (
                'Vertex_position',
                'Vertex_normal',
        ):
            location = glGetAttribLocation(self.shader, attribute)
            if location in (None, -1):
                print('Warning, no attribute: %s' % (uniform))
            setattr(self, attribute + '_loc', location)

    '''Our individually-specified uniform values'''
    UNIFORM_VALUES = [
        ('Global_ambient', (.05, .05, .05, 1.0)),
        ('material.ambient', (.2, .2, .2, 1.0)),
        ('material.diffuse', (.5, .5, .5, 1.0)),
        ('material.specular', (.8, .8, .8, 1.0)),
        ('material.shininess', (.995, )),
    ]
    '''The parameters we use to specify our lights, note that
    the first item in the tuples is dropped, it is the value
    that *should* work in glGetUniformLocation, but does not.
    What actually gets passed in is a single float array with
    12 4-float values representing all of the data-values for
    all of the enabled lights.

    You'll notice that we're using 0.0 as the 'w' coordinate
    for the light positions.  We're using this to flag that the
    position is actually a *direction*.  This will become useful
    in later tutorials where we have multiple light-types.
    '''
    LIGHTS = array([
        x[1] for x in [
            ('lights[0].ambient', (.05, .05, .05, 1.0)),
            ('lights[0].diffuse', (.3, .3, .3, 1.0)),
            ('lights[0].specular', (1.0, 0.0, 0.0, 1.0)),
            ('lights[0].position', (4.0, 2.0, 10.0, 0.0)),
            ('lights[1].ambient', (.05, .05, .05, 1.0)),
            ('lights[1].diffuse', (.3, .3, .3, 1.0)),
            ('lights[1].specular', (0.0, 1.0, 0.0, 1.0)),
            ('lights[1].position', (-4.0, 2.0, 10.0, 0.0)),
            ('lights[2].ambient', (.05, .05, .05, 1.0)),
            ('lights[2].diffuse', (.3, .3, .3, 1.0)),
            ('lights[2].specular', (0.0, 0.0, 1.0, 1.0)),
            ('lights[2].position', (-4.0, 2.0, -10.0, 0.0)),
        ]
    ], 'f')

    def Render(self, mode=None):
        """Render the geometry for the scene."""
        BaseContext.Render(self, mode)
        glUseProgram(self.shader)
        try:
            self.coords.bind()
            self.indices.bind()
            stride = self.coords.data[0].nbytes
            try:
                '''Here's our only change to the rendering process,
                we pass in the entire array of light-related data with
                a single call to glUniform4fv.  The 'v' forms of
                glUniform all allow for passing arrays of values,
                and all require that you specify the number of elements
                being passed (here 12).

                Aside: Incidentally, Uniforms are actually stored with
                the shader until the shader is re-linked, so specifying
                the uniforms on each rendering pass (as we do here) is
                not necessary.  The shader merely needs to be "in use"
                during the glUniform call, and this is a convenient,
                if inefficient, way to ensure it is in use at the time
                we are calling glUniform.
                '''
                glUniform4fv(self.uniform_locations['lights'], 12, self.LIGHTS)
                test_lights = (GLfloat * 12)()
                glGetUniformfv(self.shader, self.uniform_locations['lights'],
                               test_lights)
                print('Lights', list(test_lights))
                for uniform, value in self.UNIFORM_VALUES:
                    location = self.uniform_locations.get(uniform)
                    if location not in (None, -1):
                        if len(value) == 4:
                            glUniform4f(location, *value)
                        elif len(value) == 3:
                            glUniform3f(location, *value)
                        elif len(value) == 1:
                            glUniform1f(location, *value)
                glEnableVertexAttribArray(self.Vertex_position_loc)
                glEnableVertexAttribArray(self.Vertex_normal_loc)
                glVertexAttribPointer(self.Vertex_position_loc, 3, GL_FLOAT,
                                      False, stride, self.coords)
                glVertexAttribPointer(self.Vertex_normal_loc, 3, GL_FLOAT,
                                      False, stride, self.coords + (5 * 4))
                glDrawElements(GL_TRIANGLES, self.count, GL_UNSIGNED_SHORT,
                               self.indices)
            finally:
                self.coords.unbind()
                self.indices.unbind()
                glDisableVertexAttribArray(self.Vertex_position_loc)
                glDisableVertexAttribArray(self.Vertex_normal_loc)
        finally:
            glUseProgram(0)
Beispiel #11
0
class TestContext( BaseContext ):
    """Demonstrates use of attribute types in GLSL
    """
    def OnInit( self ):
        """Initialize the context"""
        '''==GLSL Structures==

        We have previously been using values named Material_ambient,
        Material_diffuse, etceteras to specify our Material's properties.
        GLSL allows us to bind these kinds of values together into a
        structure.  The structure doesn't provide many benefits other
        than keeping the namespaces of your code clean and allowing for
        declaring multiple uniforms of the same type, such as a "front"
        and "back" material.

        We are going to define a very simple Material struct which is
        a subset of the built-in gl_MaterialParameters structure
        (which also has an "emission" parameter).  GLSL defines two
        built-in Material uniforms gl_FrontMaterial and gl_BackMaterial.
        It is possible (though seldom done) to fill in these uniform
        values with glUniform calls rather than the legacy glMaterial
        calls.
        '''
        materialStruct = """
        struct Material {
            vec4 ambient;
            vec4 diffuse;
            vec4 specular;
            float shininess;
        };
        """
        '''Note that each sub-element must be terminated with a semi-colon
        ';' character, and that qualifiers (in, out, uniform, etceteras)
        are not allowed within the structure definition.  This statement
        has to occur *before* any use of the structure to declare a
        variable as being of this type.

        Our light-weighting code has not changed from the previous
        tutorial.  It is still a Blinn-Phong calculation based on the
        half-vector of light and view vector.
        '''
        phong_weightCalc = """
        vec2 phong_weightCalc(
            in vec3 light_pos, // light position
            in vec3 half_light, // half-way vector between light and view
            in vec3 frag_normal, // geometry normal
            in float shininess
        ) {
            // returns vec2( ambientMult, diffuseMult )
            float n_dot_pos = max( 0.0, dot(
                frag_normal, light_pos
            ));
            float n_dot_half = 0.0;
            if (n_dot_pos > -.05) {
                n_dot_half = pow(max(0.0,dot(
                    half_light, frag_normal
                )), shininess);
            }
            return vec2( n_dot_pos, n_dot_half);
        }
        """
        '''Our vertex shader is also unchanged.  We could move many
        of the operations currently done in our fragment shader here
        to reduce the processing load for our shader.
        '''
        vertex = shaders.compileShader(
        """
        attribute vec3 Vertex_position;
        attribute vec3 Vertex_normal;

        varying vec3 baseNormal;
        void main() {
            gl_Position = gl_ModelViewProjectionMatrix * vec4(
                Vertex_position, 1.0
            );
            baseNormal = gl_NormalMatrix * normalize(Vertex_normal);
        }""", GL_VERTEX_SHADER)
        '''To create a uniform with a structure type, we simply use
        the structure as the data-type declaration for the uniform.
        As opposed to using e.g. vec4 or vec3, we use Material (our
        structure name defined above) and give the uniform a name.
        '''
        """uniform Material material;"""
        '''==GLSL Arrays==

        Each light we have defined (so far) is composed to 4 4-component
        vectors, ambient, diffuse and specular colour, along with the
        "position" (direction) vector.  If we wanted to provide, for
        instance, 3 lights of this type, we *could* create 12 different
        uniform values, and set each of these uniforms individually.

        GLSL, however, provides for array data-types.  The array types
        must be "sized" (have a specific, final size) in order to be
        usable, so no "pointer" types are available, but we don't need
        them for this type of operation.  We can define a "lights"
        uniform which is declared as a sized array of 12 vec4 elements:'''
        """uniform vec4 lights[ 12 ];"""
        '''And we can loop over "lights" using 0-indexed [i] indices,
        where i must be an integer.  The for loop will be familiar to
        those who have used C looping:'''
        """for (i=0;i<12;i=i+4) { blah; }"""
        '''Note that you must declare the iterator variable ("i" here)

        We iterate over each light in our array of lights accumulating
        the results into the fragColor variable we've defined.  The
        global component is used to initialize the variable, with the
        contribution of each light added to the result.
        '''
        fragment = shaders.compileShader(
            phong_weightCalc + materialStruct + """
        uniform Material material;
        uniform vec4 Global_ambient;
        uniform vec4 lights[ 12 ]; // 3 possible lights 4 vec4's each

        varying vec3 baseNormal;
        void main() {
            vec4 fragColor = Global_ambient * material.ambient;

            int AMBIENT = 0;
            int DIFFUSE = 1;
            int SPECULAR = 2;
            int POSITION = 3;

            int i;
            for (i=0;i<12;i=i+4) {
                // normalized eye-coordinate Light location
                vec3 EC_Light_location = normalize(
                    gl_NormalMatrix * lights[i+POSITION].xyz
                );
                // half-vector calculation
                vec3 Light_half = normalize(
                    EC_Light_location - vec3( 0,0,-1 )
                );
                vec2 weights = phong_weightCalc(
                    EC_Light_location,
                    Light_half,
                    baseNormal,
                    material.shininess
                );
                fragColor = (
                    fragColor
                    + (lights[i+AMBIENT] * material.ambient)
                    + (lights[i+DIFFUSE] * material.diffuse * weights.x)
                    + (lights[i+SPECULAR] * material.specular * weights.y)
                );
            }
            gl_FragColor = fragColor;
        }
        """, GL_FRAGMENT_SHADER)
        '''===Why not an Array of Structures?===

        Originally this tutorial was going to use an array of LightSource
        structures as a Uniform, with the components of the structures
        specified with separate calls to glUniform4f.  Problem is, that
        doesn't actually *work*.  While glUniform *should* be able to
        handle array-of-structure indexing, it doesn't actually support
        this type of operation in the real world. The built-in
        gl_LightSourceParameters are an array-of-structures, but
        apparently the GL implementations consider this a special case,
        rather than a generic type of functionality to be supported.

        An array-of-structures value looks like this when declared in GLSL:
        '''
        lightStruct = """
        // NOTE: this does not work, it compiles, but you will
        // not be able to fill in the individual members...
        struct LightSource {
            vec4 ambient;
            vec4 diffuse;
            vec4 specular;
            vec4 position;
        };
        uniform LightSource lights[3];
        """
        '''When you attempt to retrieve the location for the Uniform
        via:

            glGetUniformLocation( shader, 'lights[0].ambient' )

        you will always get a -1 (invalid) location.

        OpenGL 3.1 introduced the concept of Uniform Buffers, which allow
        for packing Uniform data into VBO storage, but it's not yet clear
        whether they will support array-of-structure specification.
        '''
        self.shader = shaders.compileProgram(vertex,fragment)
        self.coords,self.indices,self.count = Sphere(
            radius = 1
        ).compile()
        self.uniform_locations = {}
        for uniform,value in self.UNIFORM_VALUES:
            location = glGetUniformLocation( self.shader, uniform )
            if location in (None,-1):
                print 'Warning, no uniform: %s'%( uniform )
            self.uniform_locations[uniform] = location
        '''There's no real reason to treat the "lights" uniform specially,
        other than that we want to call attention to it.  We get the
        uniform as normal.  Note that we *could* also retrieve a
        sub-element of the array by specifying 'lights[3]' or the like.
        '''
        self.uniform_locations['lights'] = glGetUniformLocation(
            self.shader, 'lights'
        )
        for attribute in (
            'Vertex_position','Vertex_normal',
        ):
            location = glGetAttribLocation( self.shader, attribute )
            if location in (None,-1):
                print 'Warning, no attribute: %s'%( uniform )
            setattr( self, attribute+ '_loc', location )
    '''Our individually-specified uniform values'''
    UNIFORM_VALUES = [
        ('Global_ambient',(.05,.05,.05,1.0)),
        ('material.ambient',(.2,.2,.2,1.0)),
        ('material.diffuse',(.5,.5,.5,1.0)),
        ('material.specular',(.8,.8,.8,1.0)),
        ('material.shininess',(.995,)),
    ]
    '''The parameters we use to specify our lights, note that
    the first item in the tuples is dropped, it is the value
    that *should* work in glGetUniformLocation, but does not.
    What actually gets passed in is a single float array with
    12 4-float values representing all of the data-values for
    all of the enabled lights.

    You'll notice that we're using 0.0 as the 'w' coordinate
    for the light positions.  We're using this to flag that the
    position is actually a *direction*.  This will become useful
    in later tutorials where we have multiple light-types.
    '''
    LIGHTS = array([
        x[1] for x in [
            ('lights[0].ambient',(.05,.05,.05,1.0)),
            ('lights[0].diffuse',(.3,.3,.3,1.0)),
            ('lights[0].specular',(1.0,0.0,0.0,1.0)),
            ('lights[0].position',(4.0,2.0,10.0,0.0)),
            ('lights[1].ambient',(.05,.05,.05,1.0)),
            ('lights[1].diffuse',(.3,.3,.3,1.0)),
            ('lights[1].specular',(0.0,1.0,0.0,1.0)),
            ('lights[1].position',(-4.0,2.0,10.0,0.0)),
            ('lights[2].ambient',(.05,.05,.05,1.0)),
            ('lights[2].diffuse',(.3,.3,.3,1.0)),
            ('lights[2].specular',(0.0,0.0,1.0,1.0)),
            ('lights[2].position',(-4.0,2.0,-10.0,0.0)),
        ]
    ], 'f')
    def Render( self, mode = None):
        """Render the geometry for the scene."""
        BaseContext.Render( self, mode )
        glUseProgram(self.shader)
        try:
            self.coords.bind()
            self.indices.bind()
            stride = self.coords.data[0].nbytes
            try:
                '''Here's our only change to the rendering process,
                we pass in the entire array of light-related data with
                a single call to glUniform4fv.  The 'v' forms of
                glUniform all allow for passing arrays of values,
                and all require that you specify the number of elements
                being passed (here 12).

                Aside: Incidentally, Uniforms are actually stored with
                the shader until the shader is re-linked, so specifying
                the uniforms on each rendering pass (as we do here) is
                not necessary.  The shader merely needs to be "in use"
                during the glUniform call, and this is a convenient,
                if inefficient, way to ensure it is in use at the time
                we are calling glUniform.
                '''
                glUniform4fv(
                    self.uniform_locations['lights'],
                    12,
                    self.LIGHTS
                )
                test_lights = (GLfloat * 12)()
                glGetUniformfv( self.shader, self.uniform_locations['lights'], test_lights )
                print 'Lights', list(test_lights)
                for uniform,value in self.UNIFORM_VALUES:
                    location = self.uniform_locations.get( uniform )
                    if location not in (None,-1):
                        if len(value) == 4:
                            glUniform4f( location, *value )
                        elif len(value) == 3:
                            glUniform3f( location, *value )
                        elif len(value) == 1:
                            glUniform1f( location, *value )
                glEnableVertexAttribArray( self.Vertex_position_loc )
                glEnableVertexAttribArray( self.Vertex_normal_loc )
                glVertexAttribPointer(
                    self.Vertex_position_loc,
                    3, GL_FLOAT,False, stride, self.coords
                )
                glVertexAttribPointer(
                    self.Vertex_normal_loc,
                    3, GL_FLOAT,False, stride, self.coords+(5*4)
                )
                glDrawElements(
                    GL_TRIANGLES, self.count,
                    GL_UNSIGNED_SHORT, self.indices
                )
            finally:
                self.coords.unbind()
                self.indices.unbind()
                glDisableVertexAttribArray( self.Vertex_position_loc )
                glDisableVertexAttribArray( self.Vertex_normal_loc )
        finally:
            glUseProgram( 0 )
Beispiel #12
0
class TestContext( BaseContext ):
    """Demonstrates use of attribute types in GLSL
    """
    LIGHT_COUNT = 3
    LIGHT_SIZE = 4
    def OnInit( self ):
        """Initialize the context"""
        '''==Sharing Declarations=
        
        Since we are going to use these values in both the 
        vertex and fragment shaders, it is handy to separate out 
        the constants we'll use into a separate block of code that 
        we can add to both shaders.  The use of the constants also 
        makes the code far easier to read than using the bare numbers.
        
        Note that the varying baseNormal value is part of the lighting
        calculation, so we have included it in our common lighting
        declarations.
        
        We've also parameterized the LIGHT count and size, so that 
        we can use them in both Python and GLSL code.
        '''
        lightConst = """
        const int LIGHT_COUNT = %s;
        const int LIGHT_SIZE = %s;
        
        const int AMBIENT = 0;
        const int DIFFUSE = 1;
        const int SPECULAR = 2;
        const int POSITION = 3;
        
        uniform vec4 lights[ LIGHT_COUNT*LIGHT_SIZE ];
        varying vec3 EC_Light_half[LIGHT_COUNT];
        varying vec3 EC_Light_location[LIGHT_COUNT]; 
        
        varying vec3 baseNormal;
        """%( self.LIGHT_COUNT, self.LIGHT_SIZE )
        '''As you can see, we're going to create two new varying values,
        the EC_Light_half and EC_Light_location values.  These are 
        going to hold the normalized partial calculations for the lights.
        The other declarations are the same as before, they are just 
        being shared between the shaders.
        
        Our phong_weightCalc calculation hasn't changed.
        '''
        phong_weightCalc = """
        vec2 phong_weightCalc( 
            in vec3 light_pos, // light position
            in vec3 half_light, // half-way vector between light and view
            in vec3 frag_normal, // geometry normal
            in float shininess
        ) {
            // returns vec2( ambientMult, diffuseMult )
            float n_dot_pos = max( 0.0, dot( 
                frag_normal, light_pos
            ));
            float n_dot_half = 0.0;
            if (n_dot_pos > -.05) {
                n_dot_half = pow(max(0.0,dot( 
                    half_light, frag_normal
                )), shininess);
            }
            return vec2( n_dot_pos, n_dot_half);
        }		
        """
        '''Our new vertex shader has a loop in it.  It iterates over the 
        set of lights doing the partial calculations for half-vector 
        and eye-space location.  It stores the results of these in our
        new, varying array values.
        '''
        vertex = shaders.compileShader( 
            lightConst + 
        """
        attribute vec3 Vertex_position;
        attribute vec3 Vertex_normal;
        
        void main() {
            gl_Position = gl_ModelViewProjectionMatrix * vec4( 
                Vertex_position, 1.0
            );
            baseNormal = gl_NormalMatrix * normalize(Vertex_normal);
            for (int i = 0; i< LIGHT_COUNT; i++ ) {
                EC_Light_location[i] = normalize(
                    gl_NormalMatrix * lights[(i*LIGHT_SIZE)+POSITION].xyz
                );
                // half-vector calculation 
                EC_Light_half[i] = normalize(
                    EC_Light_location[i] - vec3( 0,0,-1 )
                );
            }
        }""", GL_VERTEX_SHADER)
        '''Our fragment shader looks much the same, save that we 
        have now moved the complex half-vector and eye-space location 
        calculations out.  We've also separated out the concept of 
        which light we are processing and what array-offset we are 
        using, to make it clearer which value is being accessed.
        '''
        fragment = shaders.compileShader( 
            lightConst + phong_weightCalc + """
        struct Material {
            vec4 ambient;
            vec4 diffuse;
            vec4 specular;
            float shininess;
        };
        uniform Material material;
        uniform vec4 Global_ambient;
        
        void main() {
            vec4 fragColor = Global_ambient * material.ambient;
            
            int i,j;
            for (i=0;i<LIGHT_COUNT;i++) {
                j = i* LIGHT_SIZE;
                vec2 weights = phong_weightCalc(
                    EC_Light_location[i],
                    EC_Light_half[i],
                    baseNormal,
                    material.shininess
                );
                fragColor = (
                    fragColor 
                    + (lights[j+AMBIENT] * material.ambient)
                    + (lights[j+DIFFUSE] * material.diffuse * weights.x)
                    + (lights[j+SPECULAR] * material.specular * weights.y)
                );
            }
            gl_FragColor = fragColor;
        }
        """, GL_FRAGMENT_SHADER)
        
        '''The rest of our code is very familiar.'''
        self.shader = shaders.compileProgram(vertex,fragment)
        self.coords,self.indices,self.count = Sphere( 
            radius = 1 
        ).compile()
        self.uniform_locations = {}
        for uniform,value in self.UNIFORM_VALUES:
            location = glGetUniformLocation( self.shader, uniform )
            if location in (None,-1):
                print 'Warning, no uniform: %s'%( uniform )
            self.uniform_locations[uniform] = location
        self.uniform_locations['lights'] = glGetUniformLocation( 
            self.shader, 'lights' 
        )
        for attribute in (
            'Vertex_position','Vertex_normal',
        ):
            location = glGetAttribLocation( self.shader, attribute )
            if location in (None,-1):
                print 'Warning, no attribute: %s'%( uniform )
            setattr( self, attribute+ '_loc', location )
    UNIFORM_VALUES = [
        ('Global_ambient',(.05,.05,.05,1.0)),
        ('material.ambient',(.2,.2,.2,1.0)),
        ('material.diffuse',(.5,.5,.5,1.0)),
        ('material.specular',(.8,.8,.8,1.0)),
        ('material.shininess',(.995,)),
    ]
    LIGHTS = array([
        x[1] for x in [
            ('lights[0].ambient',(.05,.05,.05,1.0)),
            ('lights[0].diffuse',(.3,.3,.3,1.0)),
            ('lights[0].specular',(1.0,0.0,0.0,1.0)),
            ('lights[0].position',(4.0,2.0,10.0,0.0)),
            ('lights[1].ambient',(.05,.05,.05,1.0)),
            ('lights[1].diffuse',(.3,.3,.3,1.0)),
            ('lights[1].specular',(0.0,1.0,0.0,1.0)),
            ('lights[1].position',(-4.0,2.0,10.0,0.0)),
            ('lights[2].ambient',(.05,.05,.05,1.0)),
            ('lights[2].diffuse',(.3,.3,.3,1.0)),
            ('lights[2].specular',(0.0,0.0,1.0,1.0)),
            ('lights[2].position',(-4.0,2.0,-10.0,0.0)),
        ]
    ], 'f')
    def Render( self, mode = None):
        """Render the geometry for the scene."""
        BaseContext.Render( self, mode )
        if not mode.visible:
            return 
        glUseProgram(self.shader)
        try:
            self.coords.bind()
            stride = self.coords.data[0].nbytes
            try:
                '''Note the use of the parameterized values to specify 
                the size of the light-parameter array.'''
                glUniform4fv( 
                    self.uniform_locations['lights'],
                    self.LIGHT_COUNT * self.LIGHT_SIZE,
                    self.LIGHTS
                )
                for uniform,value in self.UNIFORM_VALUES:
                    location = self.uniform_locations.get( uniform )
                    if location not in (None,-1):
                        if len(value) == 4:
                            glUniform4f( location, *value )
                        elif len(value) == 3:
                            glUniform3f( location, *value )
                        elif len(value) == 1:
                            glUniform1f( location, *value )
                glEnableVertexAttribArray( self.Vertex_position_loc )
                glEnableVertexAttribArray( self.Vertex_normal_loc )
                glVertexAttribPointer( 
                    self.Vertex_position_loc, 
                    3, GL_FLOAT,False, stride, self.coords
                )
                glVertexAttribPointer( 
                    self.Vertex_normal_loc, 
                    3, GL_FLOAT,False, stride, self.coords+(5*4)
                )
                self.indices.bind()
                glDrawElements(
                    GL_TRIANGLES, self.count,
                    GL_UNSIGNED_SHORT, self.indices
                )
            finally:
                self.coords.unbind()
                self.indices.unbind()
                glDisableVertexAttribArray( self.Vertex_position_loc )
                glDisableVertexAttribArray( self.Vertex_normal_loc )
        finally:
            glUseProgram( 0 )