Commit 689afa9d authored by Dalai Felinto's avatar Dalai Felinto
Browse files

Core functionality

parents
Loading
Loading
Loading
Loading
+193 −0
Changes for space_view3d_virtual_reality/__init__.py: 193 added lines, 0 removed lines.
Original line number Diff line number Diff line
#====================== BEGIN GPL LICENSE BLOCK ======================
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#======================= END GPL LICENSE BLOCK ========================

# <pep8 compliant>
bl_info = {
    "name": "Virtual Reality Viewport",
    "author": "Dalai Felinto",
    "version": (0, 9),
    "blender": (2, 7, 5),
    "location": "Window Menu",
    "description": "",
    "warning": "",
    "wiki_url": "",
    "tracker_url": "",
    "category": "3D View"}


import bpy

from bgl import (
        Buffer,
        GL_INT,
        GL_RGBA,
        )

from .opengl_helper import (
        calculate_image_size,
        create_image,
        create_shader,
        delete_image,
        draw_callback_px,
        resize,
        )


def get_context_3dview (context):
    """returns area and space"""
    screen = context.screen

    for area in screen.areas:
        if area.type == 'VIEW_3D':
            for region in area.regions:
                if region.type == 'WINDOW':
                    return area, region

    return None, None


def get_space_3dview(context):
    area = context.area
    for space in area.spaces:
        if space.type == 'VIEW_3D':
            return space
    return None


def get_glsl_shader():
    import os
    folderpath = os.path.dirname(os.path.abspath(__file__))
    filepath = os.path.join(folderpath, 'oculus_dk2.glsl')
    f = open(filepath, 'r')
    data = f.read()
    f.close()
    return data


class VirtualRealityViewportOperator(bpy.types.Operator):
    """"""
    bl_idname = "view3d.virtual_reality_toggle"
    bl_label = "Toggle Virtual Reality Mode"
    bl_description = ""

    _enabled = True
    _timer = None

    def modal(self, context, event):
        if event.type == 'ESC':
            return self.cancel(context)

        if event.type == 'TIMER':

            # bug, waiting for fix: "[#31026] context.region broken after QuadView on + off"
            # http://projects.blender.org/tracker/index.php?func=detail&aid=31026&group_id=9&atid=498
            if not context.region or \
                not context.space_data or \
                context.space_data.type != 'VIEW_3D':
                return {'PASS_THROUGH'}

            viewport_shade = context.space_data.viewport_shade
            self._enabled = (viewport_shade != 'RENDERED')

            if (self.width != context.region.width) or (self.height != context.region.height):
                resize(self, context)


        return {'PASS_THROUGH'}

    def invoke(self, context, event):
        """
        * Create a fullscreen window with the editor in fullscreen with clean UI.
        * Views should be on, and this window should have stereo 3d mode set to side-by-side
        * Also you should lock a camera to the viewport to make sure you always look nicely
        * Sync the Oculus rotation + translation to the Blender camera
        * Now the opengl fun ... create a GLSL screen shader to run the warping distortions.
        """

        if context.area.type == 'VIEW_3D':
            #if bpy.ops.wm.window_fullscreen_toggle.poll():
            #    bpy.ops.wm.window_fullscreen_toggle()

            context.scene.render.use_multiview = True
            context.window.stereo_3d_display.display_mode = 'SIDEBYSIDE'

            #if bpy.ops.screen.screen_full_area.poll():
            #    bpy.ops.screen.screen_full_area(use_hide_panels=True)

            if bpy.ops.view3d.view_all.poll():
                bpy.ops.view3d.view_all()

            space = get_space_3dview(context)
            space.show_only_render = True
            space.stereo_3d_camera = 'S3D'

            #bpy.v3d = get_context_3dview(context)
            #bpy.space = space
            bpy.ops.view3d.viewnumpad(type='CAMERA')

            self._timer = context.window_manager.event_timer_add(0.1, context.window)
            self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, (self, context), 'WINDOW', 'POST_VIEW')
            context.window_manager.modal_handler_add(self)

            self.viewport = Buffer(GL_INT, 4)
            self.width = context.region.width
            self.height = context.region.height

            # power of two dimensions
            self.buffer_width, self.buffer_height = calculate_image_size(self.width, self.height)

            # images to dump the screen buffers
            self.color_id = create_image(self.buffer_width, self.buffer_height, GL_RGBA)

            # glsl shaders
            fragment_shader = get_glsl_shader()
            self.program_shader = create_shader(fragment_shader)

            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "View3D not found, cannot run operator")
            return {'CANCELLED'}

    def cancel(self, context):
        if self._handle:
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            del self._handle

        if self._timer:
            context.window_manager.event_timer_remove(self._timer)
            del self._timer

        self.quit()
        return {'CANCELLED'}

    def quit(self):
        """garbage colect"""
        if self.color_id:
            delete_image(self.color_id)


def register():
    bpy.utils.register_module(__name__)


def unregister():
    bpy.utils.unregister_module(__name__)


if __name__ == '__main__':
    register()
+78 −0
Changes for space_view3d_virtual_reality/oculus_dk2.glsl: 78 added lines, 0 removed lines.
Original line number Diff line number Diff line
uniform sampler2D bgl_RenderedTexture;
uniform float bgl_RenderedTextureWidth;
uniform float bgl_RenderedTextureHeight;

const vec4 kappa = vec4(1.0,0.7,1.0,1.0);

float screen_width = bgl_RenderedTextureWidth;
float screen_height = bgl_RenderedTextureHeight;

const float scaleFactor = 0.85;

const vec2 leftCenter = vec2(0.25, 0.5);
const vec2 rightCenter = vec2(0.75, 0.5);

const float separation = -0.00;

// Scales input texture coordinates for distortion.
vec2 hmdWarp(vec2 LensCenter, vec2 texCoord, vec2 Scale, vec2 ScaleIn, float eta) {
    vec2 theta = (texCoord - LensCenter) * ScaleIn;
    float rSq = theta.x * theta.x + theta.y * theta.y;
    vec2 rvector = theta * (kappa.x + kappa.y * rSq + kappa.z * rSq * rSq + kappa.w * rSq * rSq * rSq);
    vec2 tc = LensCenter + Scale * eta * rvector;
    return tc;
}

bool validate(vec2 tc, int left_eye)
{
    //keep within bounds of texture
    if ((left_eye == 1 && (tc.x < 0.0 || tc.x > 0.5)) ||
        (left_eye == 0 && (tc.x < 0.5 || tc.x > 1.0)) ||
        tc.y < 0.0 || tc.y > 1.0) {
        return false;
    }
    return true;
}

void main()
{
    vec2 screen = vec2(screen_width, screen_height);
    vec3 eta = vec3(1.00,1.018,1.042); //refraction indices

    float as = float(screen.x / 2.0) / float(screen.y);
    vec2 Scale = vec2(0.47, as);
    vec2 ScaleIn = vec2(2.0 * scaleFactor, 1.0 / as * scaleFactor);

    vec2 texCoord = (gl_TexCoord[0].st);
    vec2 texCoordSeparated = texCoord;

    vec2 tcR = vec2(0.0);
    vec2 tcG = vec2(0.0);
    vec2 tcB = vec2(0.0);

    vec4 color = vec4(0.0);

    if (texCoord.x < 0.5) {
        texCoordSeparated.x += separation;
        tcR = hmdWarp(leftCenter, texCoordSeparated, Scale, ScaleIn, eta.r );
        tcG = hmdWarp(leftCenter, texCoordSeparated, Scale, ScaleIn, eta.g );
        tcB = hmdWarp(leftCenter, texCoordSeparated, Scale, ScaleIn, eta.b );
        color.r = texture2D(bgl_RenderedTexture, tcR).r;
        color.g = texture2D(bgl_RenderedTexture, tcG).g;
        color.b = texture2D(bgl_RenderedTexture, tcB).b;

        if (!validate(tcR, 1))
            color = vec4(0.0);
    } else {
        texCoordSeparated.x -= separation;
        tcR = hmdWarp(rightCenter, texCoordSeparated, Scale, ScaleIn, eta.r );
        tcG = hmdWarp(rightCenter, texCoordSeparated, Scale, ScaleIn, eta.g );
        tcB = hmdWarp(rightCenter, texCoordSeparated, Scale, ScaleIn, eta.b );
        color.r = texture2D(bgl_RenderedTexture, tcR).r;
        color.g = texture2D(bgl_RenderedTexture, tcG).g;
        color.b = texture2D(bgl_RenderedTexture, tcB).b;
        if (!validate(tcR, 0))
            color = vec4(0.0);
    }
    gl_FragColor = color;
}
+286 −0
Changes for space_view3d_virtual_reality/opengl_helper.py: 286 added lines, 0 removed lines.
Original line number Diff line number Diff line
import bpy
from bgl import *
from mathutils import Matrix, Euler

SpaceView3D = bpy.types.SpaceView3D
callback_handle = []


fragment_shader ="""
#version 120
uniform sampler2D color_buffer;

void main(void)
{
    vec2 coords = gl_TexCoord[0].st;
    vec4 foreground = texture2D(color_buffer, coords);
    gl_FragColor = mix(foreground, vec4(1.0, 0.0, 0.0, 1.0), 0.5);
}
"""

# ##################
# GLSL Debug
# ##################

def print_shader_errors(shader):
    """"""
    log = Buffer(GL_BYTE, len(fragment_shader))
    length = Buffer(GL_INT, 1)

    print('Shader Code:')
    glGetShaderSource(shader, len(log), length, log)

    line = 1
    msg = "  1 "

    for i in range(length[0]):
        if chr(log[i-1]) == '\n':
            line += 1
            msg += "%3d %s" %(line, chr(log[i]))
        else:
            msg += chr(log[i])

    print(msg)

    glGetShaderInfoLog(shader, len(log), length, log)
    print("Error in GLSL Shader:\n")
    msg = ""
    for i in range(length[0]):
        msg += chr(log[i])

    print (msg)


def print_program_errors(program):
    """"""
    log = Buffer(GL_BYTE, 1024)
    length = Buffer(GL_INT, 1)

    glGetProgramInfoLog(program, len(log), length, log)

    print("Error in GLSL Program:\n")

    msg = ""
    for i in range(length[0]):
        msg += chr(log[i])

    print (msg)

# ######################
# OpenGL Image Routines
# ######################

def resize(self, context):
    """we can run every frame or only when width/height change"""
    # remove old textures
    self.quit()

    self.width = context.region.width
    self.height = context.region.height

    self.buffer_width, self.buffer_height = calculate_image_size(self.width, self.height)

    # image to dump screen
    self.color_id = create_image(self.buffer_width, self.buffer_height, GL_RGBA)


def calculate_image_size(width, height):
    """get a power of 2 size"""
    buffer_width, buffer_height = 0,0

    i = 0
    while (1 << i) <= width:i+= 1
    buffer_width = 1 << i

    i = 0
    while (1 << i) <= height:i+= 1
    buffer_height = 1 << i

    return buffer_width, buffer_height


def update_image(tex_id, viewport, target=GL_RGBA, texture=GL_TEXTURE0):
    """copy the current buffer to the image"""
    glActiveTexture(texture)
    glBindTexture(GL_TEXTURE_2D, tex_id)
    glCopyTexImage2D(GL_TEXTURE_2D, 0, target, viewport[0], viewport[1], viewport[2], viewport[3], 0)


def create_image(width, height, target=GL_RGBA):
    """create an empty image, dimensions pow2"""
    if target == GL_RGBA:
        target, internal_format, dimension  = GL_RGBA, GL_RGB, 3
    else:
        target, internal_format, dimension = GL_DEPTH_COMPONENT32, GL_DEPTH_COMPONENT, 1

    null_buffer = Buffer(GL_BYTE, [(width + 1) * (height + 1) * dimension])

    id_buf = Buffer(GL_INT, 1)
    glGenTextures(1, id_buf)

    tex_id = id_buf.to_list()[0]
    glBindTexture(GL_TEXTURE_2D, tex_id)

    glTexImage2D(GL_TEXTURE_2D, 0, target, width, height, 0, internal_format, GL_UNSIGNED_BYTE, null_buffer)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)

    if target == GL_DEPTH_COMPONENT32:
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE)

    glCopyTexImage2D(GL_TEXTURE_2D, 0, target, 0, 0, width, height, 0)

    glBindTexture(GL_TEXTURE_2D, 0)

    del null_buffer

    return tex_id


def delete_image(tex_id):
    """clear created image"""
    id_buf = Buffer(GL_INT, 1)
    id_buf.to_list()[0] = tex_id

    if glIsTexture(tex_id):
        glDeleteTextures(1, id_buf)


# ##################
# GLSL Screen Shader
# ##################
def create_shader(source, program=None, type=GL_FRAGMENT_SHADER):
    """"""
    if program == None:
        program = glCreateProgram()

    shader = glCreateShader(type)
    glShaderSource(shader, source)
    glCompileShader(shader)

    success = Buffer(GL_INT, 1)
    glGetShaderiv(shader, GL_COMPILE_STATUS, success)

    if not success[0]:
        print_shader_errors(shader)
    glAttachShader(program, shader)
    glLinkProgram(program)

    return program


def setup_uniforms(program, color_id, width, height):
    """"""
    uniform = glGetUniformLocation(program, "bgl_RenderedTexture")
    glActiveTexture(GL_TEXTURE0)
    glBindTexture(GL_TEXTURE_2D, color_id)
    if uniform != -1: glUniform1i(uniform, 0)

    uniform = glGetUniformLocation(program, "bgl_RenderedTextureWidth")
    if uniform != -1: glUniform1f(uniform, width)

    uniform = glGetUniformLocation(program, "bgl_RenderedTextureHeight")
    if uniform != -1: glUniform1f(uniform, height)

def bindcode(image):
    """load the image in the graphic card if necessary"""
    image.gl_touch(GL_NEAREST)
    return image.bindcode


# ##################
# Drawing Routines
# ##################
def view_setup():
    glMatrixMode(GL_PROJECTION)
    glPushMatrix()
    glLoadIdentity()

    glMatrixMode(GL_TEXTURE)
    glPushMatrix()
    glLoadIdentity()

    glMatrixMode(GL_MODELVIEW)
    glPushMatrix()
    glLoadIdentity()

    glOrtho(-1, 1, -1, 1, -20, 20)
    gluLookAt(0.0, 0.0, 1.0, 0.0,0.0,0.0, 0.0,1.0,0.0)


def view_reset(viewport):
    # Get texture info
    glMatrixMode(GL_PROJECTION)
    glPopMatrix()

    glMatrixMode(GL_TEXTURE)
    glPopMatrix()

    glMatrixMode(GL_MODELVIEW)
    glPopMatrix()

    glViewport(viewport[0], viewport[1], viewport[2], viewport[3])


def draw_rectangle(zed=0.0):
    texco = [(1, 1), (0, 1), (0, 0), (1,0)]
    verco = [(1.0, 1.0), (-1.0, 1.0), (-1.0, -1.0), ( 1.0, -1.0)]

    glPolygonMode(GL_FRONT_AND_BACK , GL_FILL)

    glBegin(GL_QUADS)
    for i in range(4):
        glColor4f(1.0, 1.0, 1.0, 0.0)
        glTexCoord3f(texco[i][0], texco[i][1], zed)
        glVertex2f(verco[i][0], verco[i][1])
    glEnd()


def draw_callback_px(self, context):
    """core function"""
    if not self._enabled: return

    act_tex = Buffer(GL_INT, 1)
    glGetIntegerv(GL_ACTIVE_TEXTURE, act_tex)

    glGetIntegerv(GL_VIEWPORT, self.viewport)

    # (1) dump buffer in texture
    update_image(self.color_id, self.viewport, GL_RGBA, GL_TEXTURE0)

    # (3) run screenshader
    glEnable(GL_DEPTH_TEST)
    glDepthFunc(GL_LESS)

    pjm = Buffer(GL_FLOAT, 16)
    mvm = Buffer(GL_FLOAT, 16)

    cam_pos = context.scene.camera.location.copy()
    glMatrixMode(GL_MODELVIEW)
    glTranslatef(cam_pos[0], cam_pos[1], cam_pos[2])

    glGetFloatv(GL_PROJECTION_MATRIX, pjm)
    glGetFloatv(GL_MODELVIEW_MATRIX, mvm)

    # set identity matrices
    view_setup()

    # calculate matrixes
    projection_matrix = Matrix((pjm[0:4], pjm[4:8], pjm[8:12], pjm[12:16])).transposed()
    modelview_matrix = Matrix((mvm[0:4], mvm[4:8], mvm[8:12], mvm[12:16])).transposed()

    modelviewprojinv_matrix = (projection_matrix * modelview_matrix).inverted()
    modelviewprojinv_mat = Buffer(GL_FLOAT, (4,4), modelviewprojinv_matrix.transposed())

    glUseProgram(self.program_shader)
    setup_uniforms(self.program_shader, self.color_id, self.width, self.height)
    draw_rectangle()

    # (4) restore opengl defaults
    glUseProgram(0)
    glActiveTexture(act_tex[0])
    glBindTexture(GL_TEXTURE_2D, 0)
    view_reset(self.viewport)

    glMatrixMode(GL_MODELVIEW)
    glTranslatef(-cam_pos[0], -cam_pos[1], -cam_pos[2])