0

The source is of a green dragon rendering. My question is how to get the clip distances working on it? Also, the textures are not appearing as the expected output. Any ideas what can be modified in the source code to render the program as expected?

Update: With the excellent help and superb answers of Rabbid76 the clip distance is working and texture loading is working! Thank You.

Bonus example: clipdistance_torus_package.zip a clip distance example with a torus and textures!

Expected output:

enter image description here

Files to run: clipdistance_dragon.zip

#!/usr/bin/python3

import sys
import time
import ctypes

fullscreen = True

sys.path.append("./shared")

from sbmloader import SBMObject    # location of sbm file format loader
from ktxloader import KTXObject

from sbmath import m3dDegToRad, m3dRadToDeg, m3dTranslateMatrix44, m3dRotationMatrix44, m3dMultiply, m3dOrtho, m3dPerspective, rotation_matrix, translate, m3dScaleMatrix44, \
    scale, m3dLookAt, normalize

try:
    from OpenGL.GLUT import *
    from OpenGL.GL import *
    from OpenGL.GLU import *
    from OpenGL.raw.GL.ARB.vertex_array_object import glGenVertexArrays, glBindVertexArray
except:
    print ('''
    ERROR: PyOpenGL not installed properly.
        ''')
    sys.exit()

import numpy as np 
from math import cos, sin 
identityMatrix = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]


myobject = SBMObject()

render_program = GLuint(0)
paused = False

class uniforms:
    proj_matrix = GLint(0)
    mv_matrix = GLint(0)
    clip_plane = GLint(0)
    clip_sphere = GLint(0)

uniform = uniforms()


def shader_load(filename, shader_type):
    result = GLuint(0)
    with open ( filename, "rb") as data:
        result = glCreateShader(shader_type)
        glShaderSource(result, data.read() )

    glCompileShader(result)
    if not glGetShaderiv(result, GL_COMPILE_STATUS):
        print( 'compile error:' )
        print( glGetShaderInfoLog(result) )
    return result

def link_from_shaders(shaders, shader_count, delete_shaders, check_errors=False):
    program = GLuint(0)
    program = glCreateProgram()

    for i in range(0, shader_count):
        glAttachShader(program, shaders[i])

    glLinkProgram(program)
    if not glGetProgramiv(program, GL_LINK_STATUS):
        print( 'link error:' )
        print( glGetProgramInfoLog(program) )

    if (delete_shaders):
        for i in range(0, shader_count):
            glDeleteShader(shaders[i])
    return program


def load_shaders():
    global render_program
    global uniform

    if (render_program):
        glDeleteProgram(render_program);

    shaders = [
        shader_load("render.vs.glsl", GL_VERTEX_SHADER),
        shader_load("render.fs.glsl", GL_FRAGMENT_SHADER)
    ]

    render_program = link_from_shaders(shaders, 2, True)

    uniform.proj_matrix = glGetUniformLocation(render_program, "proj_matrix");
    uniform.mv_matrix = glGetUniformLocation(render_program, "mv_matrix");
    uniform.clip_plane = glGetUniformLocation(render_program, "clip_plane");
    uniform.clip_sphere = glGetUniformLocation(render_program, "clip_sphere");

tex_dragon=None

class Scene:

    def __init__(self, width, height):
        global myobject, tex_dragon

        myobject.load("dragon.sbm");

        load_shaders()

        ktxobj = KTXObject()
        tex_dragon = ktxobj.ktx_load("pattern1.ktx")

    def display(self):
        global paused

        currentTime = time.time()

        black = [ 0.0, 0.0, 0.0, 0.0 ]
        one = 1.0

        last_time = 0.0
        total_time = 0.0

        if (not paused):
            total_time += (currentTime - last_time)
        last_time = currentTime

        f = total_time

        glClearBufferfv(GL_COLOR, 0, black)
        glClearBufferfv(GL_DEPTH, 0, one)

        glUseProgram(render_program)

        proj_matrix = (GLfloat * 16)(*identityMatrix)
        proj_matrix = m3dPerspective(m3dDegToRad(50.0), float(self.width) / float(self.height), 0.1, 1000.0)

        T1 = (GLfloat * 16)(*identityMatrix)
        m3dTranslateMatrix44(T1, 0.0, 0.0, -15.0)

        RY = (GLfloat * 16)(*identityMatrix)
        m3dRotationMatrix44(RY, f * 0.34, 0.0, 1.0, 0.0)

        T2 = (GLfloat * 16)(*identityMatrix)
        m3dTranslateMatrix44(T2, 0.0, -4.0, 0.0)

        mv_matrix = (GLfloat * 16)(*identityMatrix)
        mv_matrix = m3dMultiply(T1, m3dMultiply(RY, T2))


        RX = (GLfloat * 16)(*identityMatrix)
        m3dRotationMatrix44(RX, f * 6.0, 1.0, 0.0, 0.0)

        RY = (GLfloat * 16)(*identityMatrix)
        m3dRotationMatrix44(RY, f * 7.3, 0.0, 1.0, 0.0)

        plane_matrix = (GLfloat * 16)(*identityMatrix)
        plane_matrix = m3dMultiply(RX , RY )

        plane = plane_matrix[0:4]
        plane[3] = 0
        plane = normalize(plane)

        clip_sphere = [sin(f * 0.7) * 3.0, cos(f * 1.9) * 3.0, sin(f * 0.1) * 3.0, cos(f * 1.7) + 2.5]

        glUniformMatrix4fv(uniform.proj_matrix, 1, GL_FALSE, proj_matrix)
        glUniformMatrix4fv(uniform.mv_matrix, 1, GL_FALSE, mv_matrix)
        glUniform4fv(uniform.clip_plane, 1, plane)
        glUniform4fv(uniform.clip_sphere, 1, clip_sphere)

        glEnable(GL_DEPTH_TEST)
        glEnable(GL_CLIP_DISTANCE0)
        glEnable(GL_CLIP_DISTANCE1)

        glActiveTexture(GL_TEXTURE0)
        glBindTexture(GL_TEXTURE_2D, tex_dragon)
        myobject.render()

        glutSwapBuffers()

    def reshape(self, width, height):
        self.width = width
        self.height = height

    def keyboard(self, key, x, y ):
        global fullscreen
        global paused

        print ('key:' , key)
        if key == b'\x1b': # ESC
            sys.exit()

        elif key == b'f' or key == b'F': #fullscreen toggle

            if (fullscreen == True):
                glutReshapeWindow(512, 512)
                glutPositionWindow(int((1360/2)-(512/2)), int((768/2)-(512/2)))
                fullscreen = False
            else:
                glutFullScreen()
                fullscreen = True

        elif key == b'p' or key == b'P':
            paused = not paused

        elif key == b'r' or key == b'R':
            pass 

        print('done')

    def init(self):
        pass

    def timer(self, blah):

        glutPostRedisplay()
        glutTimerFunc( int(1/60), self.timer, 0)
        time.sleep(1/60.0)


if __name__ == '__main__':
    start = time.time()

    glutInit()


    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH)

    glutInitWindowSize(512, 512)

    w1 = glutCreateWindow('OpenGL SuperBible - Clip Distance')
    glutInitWindowPosition(int((1360/2)-(512/2)), int((768/2)-(512/2)))

    fullscreen = False
    #glutFullScreen()

    scene = Scene(512,512)
    glutReshapeFunc(scene.reshape)
    glutDisplayFunc(scene.display)
    glutKeyboardFunc(scene.keyboard)

    glutIdleFunc(scene.display)
    #glutTimerFunc( int(1/60), scene.timer, 0)

    scene.init()

    glutMainLoop()

ported into python from clipdistance.cpp , source of sbmobject.cpp just in case sbmloader.py is in question for the texture issue.

Stan S.
  • 237
  • 7
  • 22
  • 1
    glm a very nice library when I'm not trying to implement it all by creating a function for it. Thank you for the links, they are very informative. – Stan S. Jun 30 '19 at 09:27
  • The fragment shader code in the *zip* doesn't wrap any texture to the model. – Rabbid76 Jun 30 '19 at 10:14
  • I'm sorry for all the questions, if I may ask, how do I go about applying the fragment shader code to wrap the model with a texture? – Stan S. Jun 30 '19 at 10:36
  • In the example there is no texture applied to the model. The different look is caused because the normal vector attributes haven't been specified. I fixed the issue (see the answer - middle part). Additionally I added a code to apply a texture. Note the model has to provide proper texture coordinates to make this work. Sadly this dragon model doesn't provide texture coordinates (the coordinates array is empty). – Rabbid76 Jun 30 '19 at 11:51

1 Answers1

1

The C++ code from the example

vmath::mat4 plane_matrix = vmath::rotate(f * 6.0f, 1.0f, 0.0f, 0.0f) *
                           vmath::rotate(f * 7.3f, 0.0f, 1.0f, 0.0f);

corresponds to the following python code

RX = (GLfloat * 16)(*identityMatrix)
m3dRotationMatrix44(RX, f * 6.0, 1.0, 0.0, 0.0)

RY = (GLfloat * 16)(*identityMatrix)
m3dRotationMatrix44(RY, f * 7.3, 0.0, 1.0, 0.0)

plane_matrix = (GLfloat * 16)(*identityMatrix)
plane_matrix = m3dMultiply(RX , RY)

Note, you've to swap RX and RY in the matrix multiplication.


Your functions length and normalize can only deal with vectors which have 3 components (x, y, z). In compare the C++ function vmath::normalize from the example can handle vectors with 4 components (x, y, z, w), too.
Further the "division by zero" handling is missing in normalize.

Adapt the functions normalize and length, to deal with any vector size.
If the length of a vector is 0, then all its components are 0. There is no correct solution for that, so just return a copy of the vector itself.

def length(v):
    sum_sq = sum([s*s for s in v])
    return math.sqrt(sum_sq)
def normalize(v):
    l = length(v)
    if l == 0.0:
        return v[:]
    return [s/l for s in v]

Now you can port the C++ code from the example

vmath::vec4 plane = plane_matrix[0];
plane[3] = 0.0f;
plane = vmath::normalize(plane);

very straight to python:

plane = plane_matrix[0:4]
plane[3] = 0
plane = normalize(plane)

Further, there is an issue in the sbmloader module.
Only the array of vertex coordinates and texture coordinates is specified. The normal vectors are skipped.

Just skip the line

if attrib.name=='position' or attrib.name=='map1':

to fix the issue:

for attrib_i, attrib in enumerate(vertex_attrib_chunk.attrib_data):

    #if attrib.name=='position' or attrib.name=='map1': 

    glVertexAttribPointer(attrib_i,
        attrib.size, attrib.type,
        GL_TRUE if (attrib.flags & SB6M_VERTEX_ATTRIB_FLAG_NORMALIZED) != 0 else GL_FALSE,
        attrib.stride, ctypes.c_void_p(int(attrib.data_offset)))
    glEnableVertexAttribArray(attrib_i)


If you additionally want to wrap a texture to the model, then you've to add the texture coordinate attribute to the vertex shader:

layout (location = 2) in vec2 tc;

And to pass it by an output to the next shader stage

out VS_OUT
{
    vec3 N;
    vec3 L;
    vec3 V;
    vec2 T;
} vs_out;
void main()
{
   // ...

   vs_out.T = tc;

   // ...  
}    

In the fragment shader you've to add the texture sampler uniform

layout (binding = 0) uniform sampler2D tex;

Read the color form the texture

vec4 texColor = texture(tex, fs_in.T); 

Multiply the output color by the texture color

color = vec4(diffuse + specular + rim, 1.0) * texColor;

Fragment shader (note, I changed diffuse_albedo):

#version 420 core

// Output
layout (location = 0) out vec4 color;

// Input from vertex shader
in VS_OUT
{
    vec3 N;
    vec3 L;
    vec3 V;
    vec2 T;
} fs_in;

// Material properties
uniform vec3 diffuse_albedo = vec3(0.5);
uniform vec3 specular_albedo = vec3(0.7);
uniform float specular_power = 128.0;
uniform vec3 rim_color = vec3(0.1, 0.2, 0.2);
uniform float rim_power = 5.0;
layout (binding = 0) uniform sampler2D tex;

vec3 calculate_rim(vec3 N, vec3 V)
{
    float f = 1.0 - dot(N, V);

    f = smoothstep(0.0, 1.0, f);
    f = pow(f, rim_power);

    return f * rim_color;
}

void main(void)
{
    // Normalize the incoming N, L and V vectors
    vec3 N = normalize(fs_in.N);
    vec3 L = normalize(fs_in.L);
    vec3 V = normalize(fs_in.V);

    // Calculate R locally
    vec3 R = reflect(-L, N);

    // Compute the diffuse and specular components for each fragment
    vec3 diffuse = max(dot(N, L), 0.0) * diffuse_albedo;
    vec3 specular = pow(max(dot(R, V), 0.0), specular_power) * specular_albedo;
    vec3 rim = calculate_rim(N, V);

    // read color from the texture
    vec4 texColor = texture(tex, fs_in.T);

    // Write final color to the framebuffer
    color = vec4(diffuse + specular + rim, 1.0) * texColor;
}

I recommend to add shader compile and link error logging:

glCompileShader(result)
if not glGetShaderiv(result, GL_COMPILE_STATUS):
    print( 'compile error:' )
    print( glGetShaderInfoLog(result) )
glLinkProgram(program)
if not glGetProgramiv(program, GL_LINK_STATUS):
    print( 'link error:' )
    print( glGetProgramInfoLog(program) )

Read a texture at the initialization of the application

class Scene:

    def __init__(self, width, height):
        global myobject, tex_dragon

        myobject.load("dragon.sbm")

        load_shaders()

        ktxobj = KTXObject()
        tex_dragon = ktxobj.ktx_load("texture_file_name.ktx")

Bind the texture before drawing the model

glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_2D, tex_dragon)
myobject.render()
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • Wow, with the one line fix the dragon looks amazing and vivid. Thank you very much! I'll put a bounty on this to celebrate the fix, as it is better to give than to receive as it will come back fourfold! It is quite an excellent answer to have the textures working from the `sbmloader` fix and deserving of many upvotes! I am checking out the fragment texture wrapping code now. Thank you. – Stan S. Jun 30 '19 at 16:00
  • @StanS. Thank you, but note that the texture (last part of the answer) won't work with the dragon model, because the array of texture coordinates in "dragon.sbm" is empty. What I actually fixed was the light model (middle part of the answer). – Rabbid76 Jun 30 '19 at 16:07
  • I'm trying to start a bounty on this fine answer. I'm not getting the option just yet. Does it need a minimum number of votes to proceed with `start a bounty` how does that work? – Stan S. Jun 30 '19 at 16:12
  • Yes, I agree the texture coordinates in "dragon.sbm" is empty. Perhaps there's texture coordinates in another *.smb that we can work on. In the meantime. I have a question about text rendering if that may draw your interest done with the help of shaders, I'll have that question post ready in a little while. – Stan S. Jun 30 '19 at 16:23
  • Adding `layout (binding = 0) uniform sampler2D tex;` line to fragment shader results in 1282 invalid operation. Though the grey dragon does look good! – Stan S. Jun 30 '19 at 16:59
  • 1
    I added pattern1.ktx to zip file to get an example going. Spotted pattern dragon not showing, just grey. Code is perfect. Thank you very much! – Stan S. Jun 30 '19 at 18:05
  • @StanS. You can test the code by using "torus_nrms_tc.sbm", but you've to change the location of the texture coordinates in the vertex shader from 2 to 4: `layout (location = 4) in vec2 tc;` – Rabbid76 Jun 30 '19 at 18:21