Saturday, December 27, 2014

Python + GLSL 3D Fractal Viewer

Because it's Xmas! 

Pre-requiries: http://pyopengl.sourceforge.net
Source code: https://github.com/cudaopencl/pyRayCaster


Vertex shader

#version 120

varying vec3 normal;
attribute vec2 a_position;
varying vec2 v_position;

void main()
{
   normal = gl_NormalMatrix * gl_Normal;
   gl_Position = vec4(a_position, 0.0, 1.0);
   v_position = a_position;
}

Fragment shader:

#version 120

varying vec2 v_position;
uniform float u_fov = 0.0;
uniform float u_timer = 0.0;
uniform vec3 u_origin = vec3( 0.f, 0.f, -4.f );
uniform float u_rotation_x = 0.0;
uniform float u_rotation_y = 0.0;
uniform float u_rotation_z = 0.0;

vec3 rotate( in vec3 source, in vec3 angles )
{
    vec3 cosAngles = vec3( cos(angles.x), cos(angles.y), cos(angles.z));
    vec3 sinAngles = vec3( sin(angles.x), sin(angles.y), sin(angles.z));

    float x,y,z;
    vec3 returnValue = source;

    // Y axis
    z = source.z*cosAngles.y - source.x*sinAngles.y;
    x = source.z*sinAngles.y + source.x*cosAngles.y;
    returnValue.z = z;
    returnValue.x = x;

    // X axis
    z = returnValue.z*cosAngles.x + returnValue.y*sinAngles.x;
    y = returnValue.z*sinAngles.x - returnValue.y*cosAngles.x;
    returnValue.z = z;
    returnValue.y = y;

    return returnValue;
}

vec4 calc_mandel(vec3 o, vec3 d, float t)
{
   float X = o.x + d.x * t;
   float Y = o.y + d.y * t;
   float Z = o.z + d.z * t;

   vec2 uv = vec2(X*0.2f+0.5f,Y*0.2f+0.5f);
   float scale = 1.f; //512 / 512;
   uv=((uv-0.5)*5.5);
   uv.y*=scale;
   uv.y+=0.0;
   uv.x-=0.5;

   vec2 z = vec2(0.0, 0.0);
   vec3 c = vec3(0.0, 0.0, 0.0);
   float v;

   float I=0.f;
   for(int i=0;(i<100);i++)
   {
      if(((z.x*z.x+z.y*z.y) >= 4.0+cos(u_timer*3.f))) break;
      z = vec2(z.x*z.x - z.y*z.y, 2.0*sin(u_timer*2.6f)*z.y*z.x) + uv;
      if((z.x*z.x+z.y*z.y) >= 2.0)
      {
         c.b=float(i)/20.0;
         c.r=sin((float(i)/5.0));
      }
      I += 1.f+cos(u_timer);
   }
   if(I<Z)
      return vec4(c.b,c.r,0.f,1.0);
   else
      return vec4(0.f,0.f,c.r,0.f);
}

vec4 wave( vec3 o, vec3 d, float t )
{
   float x = o.x + d.x * t;
   float y = o.y + d.y * t;
   float z = o.z + d.z * t;
   float h = cos(x+u_timer)*sin(z);
   if(y<h) {
      float a = 0.2f*(h/(1.f+t*2));
      return vec4(a,a,a,0);
   }
   return vec4(0.f,0.f,0.f,0.f);
}

void main()
{
   vec3 angles = vec3(u_rotation_x,u_rotation_y,u_rotation_z);
    vec2 pos = v_position;
    vec3 origin = u_origin;
    origin.z = u_fov;
    origin = rotate( origin, angles );
    vec3 dir = (vec3(pos.x, pos.y, u_fov+2.f)-origin);
    dir = rotate( dir, angles );

   bool found=false;
   vec4 color = vec4(0.f,0.f,0.f,0.f);
    for( float t=1.f; t<10.f; t+=.5f)
    {
       //color += 5.f*wave(origin, dir, t);
       color += 0.3f*calc_mandel(origin, dir, t);
    }
   gl_FragColor = color;
}

Python code:

 
from OpenGL.GLUT import *
from OpenGL.GLU import *
from OpenGL.GL import shaders

import sys

SHADER_NAME = 'raycaster'WINDOW_NAME = 'RayCaster'  
 
 
class Application:
    def __init__(self):
        self.geometryRotation = [0.0, 0.0, 0.0]
        self.timer = 0        self.fov = -2.0        self.old = [0.0, 0.0, 0.0]
        self.shader = None        self.origin = [0.0, 0.0, -1.0]

    @staticmethod    def read_shader_from_file(filename):
        file_handle = open(filename, 'r')
        file_content = file_handle.read()
        file_handle.close()
        return file_content

    def mouse(self, button, status, x, y):
        self.old[0] = x
        self.old[1] = y

    def motion(self, x, y):
        #self.fov += (self.old[1] - y) / 500.0        self.geometryRotation[1] -= (self.old[0] - x) / 100.0        self.geometryRotation[0] -= (self.old[1] - y) / 100.0        self.old[0] = x
        self.old[1] = y

    def keyboard(self, key, x, y):
        print key + " pressed"        if key == 'q':
            sys.exit()
        if key == 'r':
            self.compile_shaders()

    def compile_shaders(self):
        print "Initializing shaders"        vertex_shader = shaders.compileShader(
            self.read_shader_from_file(
                'shaders/' + SHADER_NAME + '.vert'), GL_VERTEX_SHADER)
        fragment_shader = shaders.compileShader(
            self.read_shader_from_file(
                'shaders/' + SHADER_NAME + '.frag'), GL_FRAGMENT_SHADER)
        self.shader = shaders.compileProgram(vertex_shader, fragment_shader)
        glUseProgram(self.shader)

    def run(self):
        # self.shader = shaders.compileProgram(VERTEX_SHADER,FRAGMENT_SHADER)        width = 512        height = 512        glutInit(sys.argv)
        glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
        glutInitWindowSize(width, height)
        glutCreateWindow(WINDOW_NAME)

        glClearColor(0.2, 0.2, 0.2, 1.0)
        glShadeModel(GL_SMOOTH)
        glEnable(GL_CULL_FACE)
        glEnable(GL_DEPTH_TEST)

        glutIdleFunc(self.display)
        glutDisplayFunc(self.display)
        glutMouseFunc(self.mouse)
        glutMotionFunc(self.motion)
        glutKeyboardFunc(self.keyboard)

        glMatrixMode(GL_PROJECTION)
        gluPerspective(45.0, float(width) / float(height), 0.1, 100.0)
        glMatrixMode(GL_MODELVIEW)
        gluLookAt(0, 0, 10, 0, 0, 0, 0, 1, 0)
        glPushMatrix()

        self.compile_shaders()

        print "Entering main loop..."        glutMainLoop()

    def display(self):

        self.timer += 0.01        loc = glGetUniformLocation(self.shader, 'u_timer')
        glUniform1f(loc, self.timer)
        loc = glGetUniformLocation(self.shader, 'u_rotation_x')
        glUniform1f(loc, self.geometryRotation[0])
        loc = glGetUniformLocation(self.shader, 'u_rotation_y')
        glUniform1f(loc, self.geometryRotation[1])
        loc = glGetUniformLocation(self.shader, 'u_rotation_z')
        glUniform1f(loc, self.geometryRotation[2])

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        color = [1.0, 1.0, 1.0, 1.0]
        glMaterialfv(GL_FRONT, GL_DIFFUSE, color)

        # Draw scene        glPushMatrix()
        size = 1        depth = 0.0        glBegin(GL_QUADS)
        glVertex3f(-size, -size, depth)
        glNormal3f(0.0, 0.0, -1.0)
        glVertex3f(size, -size, depth)
        glNormal3f(0.0, 0.0, -1.0)
        glVertex3f(size, size, depth)
        glNormal3f(0.0, 0.0, -1.0)
        glVertex3f(-size, size, depth)
        glNormal3f(0.0, 0.0, -1.0)
        glEnd()

        glPopMatrix()
        glutSwapBuffers()

if __name__ == '__main__':
    app = Application()
    app.run()