pax_global_header 0000666 0000000 0000000 00000000064 12602344560 0014514 g ustar 00root root 0000000 0000000 52 comment=7a101aa0e3f481802393d064eca50ba8391d3308
virtual_reality_viewport-7a101aa0e3f481802393d064eca50ba8391d3308/ 0000775 0000000 0000000 00000000000 12602344560 0023421 5 ustar 00root root 0000000 0000000 virtual_reality_viewport-7a101aa0e3f481802393d064eca50ba8391d3308/.gitignore 0000664 0000000 0000000 00000000006 12602344560 0025405 0 ustar 00root root 0000000 0000000 *.swp
virtual_reality_viewport-7a101aa0e3f481802393d064eca50ba8391d3308/.gitmodules 0000664 0000000 0000000 00000001013 12602344560 0025571 0 ustar 00root root 0000000 0000000 [submodule "space_view3d_virtual_reality/lib/python-ovrsdk"]
path = space_view3d_virtual_reality/lib/python-ovrsdk
url = https://github.com/dfelinto/python-ovrsdk.git
[submodule "space_view3d_virtual_reality/oculus_sdk_bridge"]
path = space_view3d_virtual_reality/oculus_sdk_bridge
url = http://git.impa.br/dfelinto/oculus_sdk_bridge.git
[submodule "space_view3d_virtual_reality/lib/oculus_sdk_bridge"]
path = space_view3d_virtual_reality/lib/oculus_sdk_bridge
url = http://git.impa.br/dfelinto/oculus_sdk_bridge.git
virtual_reality_viewport-7a101aa0e3f481802393d064eca50ba8391d3308/README.md 0000664 0000000 0000000 00000004624 12602344560 0024706 0 ustar 00root root 0000000 0000000 # Virtual Reality Viewport
Addon to bring virtual reality devices to the Blender viewport.
This is work in progress/pre-alpha state, use at your own risk.
Prerequisite
============
Currently working on Blender Test Build 2.75: http://wiki.blender.org/index.php/Dev:Ref/Release_Notes/2.75
How to Use
==========
In the viewport press ``Space`` + ``Virtual Reality Viewport``.
And then press:
* ``Alt + F11`` (Window Fullscreen)
* ``Alt + F10`` (Fullscreen Area and Hide Panels)
Current State
=============
Video of plugin working:
[![Video of plugin in action](http://img.youtube.com/vi/saSn2qvW0aE/0.jpg)](https://www.youtube.com/watch?v=saSn2qvW0aE)
Installation
============
In a terminal paste the following commands:
```
$ git clone https://github.com/dfelinto/virtual_reality_viewport.git
$ cd virtual_reality_viewport
$ git submodule update --init --recursive --remote
$ zip -x __pycache__ -x */.git* -r9 space_view3d_virtual_reality.zip space_view3d_virtual_reality
```
Now install the space_view3d_virtual_reality.zip in Blender as an addon.
Update
======
In a terminal paste the following commands:
```
$ git pull origin
$ git submodule update --recursive --remote
```
Followed by the rsync command for your OS:
Mac:
```
$ rsync -rv --exclude=.DS_Store --exclude=.git --exclude=*.blend1 --exclude=*.blend2 --exclude=*.swp --exclude=*.swo space_view3d_virtual_reality ~/Library/Application\ Support/Blender/2.75/scripts/addons/
```
Linux:
```
$ rsync -rv --exclude=.DS_Store --exclude=.git --exclude=*.blend1 --exclude=*.blend2 --exclude=*.swp --exclude=*.swo space_view3d_virtual_reality ~/.config/blender/2.75/scripts/addons/
```
Optionally, instead of rsync you can generate a new ``.zip``, remove the previous version of the addon and re-install it.
Roadmap
=======
Oculus DK2 is currently working. The next step is to support view navigation (`Shift + F`) within the addon.
Later we can make it flexible enough to support other HMD devices.
There are a few things I still need to implement:
* Automatically go to clean fullscreen (Alt+F10) and fullwindow (Alt+F11)
Feel free to send pull requests to any of the above.
Credits
=======
Oculus DK2 Shader by Martins Upitis (which I guess based his work from elsewhere)
OculusVR wrapper by https://github.com/jherico/python-ovrsdk
Blender Addon - Dalai Felinto - http://www.dalaifelinto.com
virtual_reality_viewport-7a101aa0e3f481802393d064eca50ba8391d3308/space_view3d_virtual_reality/ 0000775 0000000 0000000 00000000000 12602344560 0031274 5 ustar 00root root 0000000 0000000 __init__.py 0000664 0000000 0000000 00000004455 12602344560 0033336 0 ustar 00root root 0000000 0000000 virtual_reality_viewport-7a101aa0e3f481802393d064eca50ba8391d3308/space_view3d_virtual_reality #====================== 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 ========================
#
bl_info = {
"name": "Virtual Reality Viewport",
"author": "Dalai Felinto, Visgraf/IMPA",
"version": (0, 9),
"blender": (2, 7, 7),
"location": "View 3D Tools",
"description": "",
"warning": "",
"wiki_url": "",
"tracker_url": "",
"category": "3D View"}
import bpy
from . import ui
from . import operator
# ############################################################
# User Preferences
# ############################################################
# Preferences
class VirtualRealityPreferences(bpy.types.AddonPreferences):
bl_idname = __name__
display_backend = bpy.props.EnumProperty(
name="Display Backend",
description="Library to use for the display",
items=(("OCULUS", "Oculus", "Oculus - oculus.com"),
("DEBUG", "Debug", "Debug backend - no real HMD"),
),
default="OCULUS",
)
def draw(self, context):
layout = self.layout
row = layout.row()
row.prop(self, "display_backend")
# ############################################################
# Un/Registration
# ############################################################
def register():
bpy.utils.register_class(VirtualRealityPreferences)
operator.register()
ui.register()
def unregister():
bpy.utils.unregister_class(VirtualRealityPreferences)
operator.unregister()
ui.unregister()
if __name__ == '__main__':
register()
virtual_reality_viewport-7a101aa0e3f481802393d064eca50ba8391d3308/space_view3d_virtual_reality/hmd/ 0000775 0000000 0000000 00000000000 12602344560 0032044 5 ustar 00root root 0000000 0000000 __init__.py 0000664 0000000 0000000 00000007250 12602344560 0034102 0 ustar 00root root 0000000 0000000 virtual_reality_viewport-7a101aa0e3f481802393d064eca50ba8391d3308/space_view3d_virtual_reality/hmd
TODO = True
from mathutils import (
Matrix,
Vector,
)
import gpu
# ############################################################
# Data structs
# ############################################################
def HMD(display_backend):
"""
return the head mounted display device class
(defined in another file)
:param display_backend: asdasd
:type display_backend: str
"""
from .oculus import Oculus
from .debug import Debug
displays = {
'OCULUS':Oculus,
'DEBUG':Debug,
}
if display_backend not in displays:
assert False, "Display Backend \"{0}\" not implemented".format(display_backend)
return displays[display_backend]()
# ############################################################
# Data structs
# ############################################################
class HMD_Data:
status = None
projection_matrix = Matrix()
modelview_matrix = Matrix()
interpupillary_distance = Vector((0.0, 0.0))
width = 0
height = 0
fbo_id = 0
texture_id = 0
# ############################################################
# Base class inherited by HMD devices
# ############################################################
class HMD_Base:
__slots__ = {
"_fbo",
"_height",
"_interpupillary_distance",
"_modelview_matrix",
"_name",
"_offscreen_object",
"_projection_matrix",
"_texture",
"_width",
}
def __init__(self, name):
self._name = name
self._projection_matrix = Matrix()
self._modelview_matrix = Matrix()
self._interpupillary_distance = Vector((0.0, 0.0))
self._width = 0
self._height = 0
self._fbo_id = 0
self._texture_id = 0
self._offscreen_object = None
@property
def offscreen_object(self):
return self._offscreen_object
@property
def fbo(self):
return self._fbo_id
@property
def texture(self):
return self._texture_id
@property
def width(self):
return self._width
@property
def height(self):
return self._height
@property
def projection_matrix(self):
return self._projection_matrix
@property
def modelview_matrix(self):
TODO # calculate
return self._modelview_matrix
def isConnected(self):
"""
Check if device is connected
:return: return True if the device is connected
:rtype: bool
"""
assert False, "isConnected() not implemented for the \"{0}\" device".format(self._name)
def init(self):
"""
Initialize device
:return: return True if the device was properly initialized
:rtype: bool
"""
try:
self._offscreen_object = gpu.offscreen_object_create(self._width, self._height)
self._fbo_id = self._offscreen_object.framebuffer_object
self._texture_id = self._offscreen_object.color_object
except Exception as E:
print(E)
return False
else:
return True
def loop(self):
"""
Get fresh tracking data
"""
assert False, "loop() not implemented for the \"{0}\" device".format(self._name)
def frameReady(self):
"""
The frame is ready to be send to the device
"""
assert False, "frameReady() not implemented for the \"{0}\" device".format(self._name)
def quit(self):
"""
Garbage collection
"""
try:
gpu.offscreen_object_free(self._offscreen_object)
except Exception as E:
print(E)
debug.py 0000664 0000000 0000000 00000007331 12602344560 0033431 0 ustar 00root root 0000000 0000000 virtual_reality_viewport-7a101aa0e3f481802393d064eca50ba8391d3308/space_view3d_virtual_reality/hmd """
Debug
=====
Debug device for testing
"""
from . import HMD_Base, HMD_Data
VERBOSE = False
def print_debug(*args):
if VERBOSE:
print("Debug: {0}".format(*args))
class Debug(HMD_Base):
def __init__(self):
super(Debug, self).__init__('Debug')
def isConnected(self):
"""
Check if device is connected
:return: return True if the device is connected
:rtype: bool
"""
print_debug('isConnected()')
return True
def init(self):
"""
Initialize device
:return: return True if the device was properly initialized
:rtype: bool
"""
print_debug('init()')
self._width = 1024
self._height = 512
return super(Debug, self).init()
def loop(self):
"""
Get fresh tracking data
"""
print_debug('loop()')
debug_draw(self._offscreen_object, self._width, self._height)
def frameReady(self):
"""
The frame is ready to be send to the device
"""
print_debug('frameReady()')
def quit(self):
"""
Garbage collection
"""
print_debug('quit()')
return super(Debug, self).quit()
# ##################
# Debug Debug Debug
# ##################
from bgl import *
global _time
_time = 0
# ##################
# OpenGL generic 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():
# Get texture info
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_TEXTURE)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
# ##################
# Draw an animated cube on the offscreen object
# ##################
def debug_draw(offscreen_object, width, height):
"""
draw in the FBO
"""
import time
import math
import gpu
global _time
# setup
viewport = Buffer(GL_INT, 4)
glGetIntegerv(GL_VIEWPORT, viewport)
glViewport(0, 0, width, height)
gpu.offscreen_object_bind(offscreen_object, True)
glClearColor(1.0, 1.0, 1.0, 1.0)
glClearDepth(1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glDisable(GL_DEPTH_TEST)
glDepthFunc(GL_LESS)
view_setup()
# actual drawing
speed = 0.01
one = 1.0
zer = 0.0
_time, _int = math.modf(_time + speed)
factor = _time * 2.0
if factor > 1.0:
factor = 2.0 - factor;
one = one - factor;
zer = factor - zer;
glMatrixMode(GL_PROJECTION)
glPushMatrix()
glLoadIdentity()
glOrtho(-1.0, 1.0, -1.0, 1.0, 1.0, 20.0)
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glLoadIdentity()
gluLookAt(0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)
current_color = Buffer(GL_FLOAT, 4)
glGetFloatv(GL_CURRENT_COLOR, current_color);
glEnable(GL_COLOR_MATERIAL)
glBegin(GL_QUADS)
glColor3f(one, zer, zer)
glVertex3f(-0.75, -0.75, 0.0)
glColor3f(zer, one, zer)
glVertex3f( 0.75, -0.75, 0.0)
glColor3f(zer, zer, one)
glVertex3f( 0.75, 0.75, 0.0)
glColor3f(one, one, zer)
glVertex3f(-0.75, 0.75, 0.0)
glEnd()
glColor4fv(current_color)
glDisable(GL_COLOR_MATERIAL)
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
glDisable(GL_DEPTH_TEST)
view_reset()
glViewport(viewport[0], viewport[1], viewport[2], viewport[3])
# unbinding
gpu.offscreen_object_unbind(offscreen_object, True)
oculus.py 0000664 0000000 0000000 00000003223 12602344560 0033651 0 ustar 00root root 0000000 0000000 virtual_reality_viewport-7a101aa0e3f481802393d064eca50ba8391d3308/space_view3d_virtual_reality/hmd """
Oculus
======
Oculus (oculus.com) head mounted display
It uses a C app to connect with the SDK
The bridge code is hosted at Visgraf:
http://git.impa.br/dfelinto/oculus_sdk_bridge
"""
TODO = False
from . import HMD_Base, HMD_Data
from ..lib import (
checkModule,
)
class Oculus(HMD_Base):
def __init__(self):
super(Oculus, self).__init__('Oculus')
checkModule('oculus_sdk_bridge')
def isConnected(self):
"""
Check if device is connected
:return: return True if the device is connected
:rtype: bool
"""
from bridge import OculusBridge
try:
return OculusBridge.isConnected()
except Exception as E:
print(E)
return False
def init(self):
"""
Initialize device
:return: return True if the device was properly initialized
:rtype: bool
"""
return TODO
"""
Oculus SDK bridge
return: status, projection matrix, eye separation, width, height
"""
return super(Oculus, self).init()
def loop(self):
"""
Get fresh tracking data
"""
TODO
"""
Oculus SDK bridge
return:head position, head orientation
"""
def frameReady(self):
"""
The frame is ready to be send to the device
"""
TODO
"""
Oculus SDK bridge
"""
def quit(self):
"""
Garbage collection
"""
TODO
"""
Oculus SDK bridge
delete fbo, rbo, tex_id
"""
return super(Oculus, self).quit()
lib.py 0000664 0000000 0000000 00000001152 12602344560 0032334 0 ustar 00root root 0000000 0000000 virtual_reality_viewport-7a101aa0e3f481802393d064eca50ba8391d3308/space_view3d_virtual_reality """
Library
=======
Sorted util functions
"""
def getAddonName():
return __name__.split('.')[0]
def getDisplayBackend(context):
"""preference set in the addon"""
addon = getAddonName()
preferences = context.user_preferences.addons[addon].preferences
return preferences.display_backend
def checkModule(path):
"""
If library exists append it to sys.path
"""
import sys
import os
addon_path = os.path.dirname(os.path.abspath(__file__))
library_path = os.path.join(addon_path, "lib", path)
if library_path not in sys.path:
sys.path.append(library_path)
virtual_reality_viewport-7a101aa0e3f481802393d064eca50ba8391d3308/space_view3d_virtual_reality/lib/ 0000775 0000000 0000000 00000000000 12602344560 0032042 5 ustar 00root root 0000000 0000000 oculus_sdk_bridge/ 0000775 0000000 0000000 00000000000 12602344560 0035452 5 ustar 00root root 0000000 0000000 virtual_reality_viewport-7a101aa0e3f481802393d064eca50ba8391d3308/space_view3d_virtual_reality/lib python-ovrsdk/ 0000775 0000000 0000000 00000000000 12602344560 0034612 5 ustar 00root root 0000000 0000000 virtual_reality_viewport-7a101aa0e3f481802393d064eca50ba8391d3308/space_view3d_virtual_reality/lib opengl_helper.py 0000664 0000000 0000000 00000027164 12602344560 0034424 0 ustar 00root root 0000000 0000000 virtual_reality_viewport-7a101aa0e3f481802393d064eca50ba8391d3308/space_view3d_virtual_reality 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)
glBindTexture(GL_TEXTURE_2D, 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)
# ##################
# Framebuffer Routines
# ##################
def check_framebuffer_status(target):
status = glCheckFramebufferStatus(target)
if status == GL_FRAMEBUFFER_COMPLETE:
return True
elif status == GL_FRAMEBUFFER_UNDEFINED:
print("framebuffer not complete: GL_FRAMEBUFFER_UNDEFINED - returned if the specified framebuffer is the default read or draw framebuffer, but the default framebuffer does not exist.")
elif status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
print("framebuffer not complete: GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT - returned if any of the framebuffer attachment points are framebuffer incomplete.")
elif status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
print("framebuffer not complete: GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT - returned if the framebuffer does not have at least one image attached to it.")
elif status == GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
print("framebuffer not complete: GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER - returned if the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE for any color attachment point named by GL_DRAW_BUFFERi.")
elif status == GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
print("framebuffer not complete: GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER - returned if GL_READ_BUFFER is not GL_NONE and the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE for the color attachment point named by GL_READ_BUFFER.")
elif status == GL_FRAMEBUFFER_UNSUPPORTED:
print("framebuffer not complete: GL_FRAMEBUFFER_UNSUPPORTED - returned if the combination of internal formats of the attached images violates an implementation-dependent set of restrictions.")
elif status == GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
print("framebuffer not complete: GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE - returned if the value of GL_RENDERBUFFER_SAMPLES is not the same for all attached renderbuffers; if the value of GL_TEXTURE_SAMPLES is the not same for all attached textures; or, if the attached images are a mix of renderbuffers and textures, the value of GL_RENDERBUFFER_SAMPLES does not match the value of GL_TEXTURE_SAMPLES. also returned if the value of GL_TEXTURE_FIXED_SAMPLE_LOCATIONS i s not the same for all attached textures; or, if the attached images are a mix of renderbuffers and textures, the value of GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not GL_TRUE for all attached textures.")
elif status == GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
print("framebuffer not complete: GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS - returned if any framebuffer attachment is layered, and any populated attachment is not layered, or if all populated color attachments are not from textures of the same target.")
else:
print("framebuffer not complete: status 0x%x (unknown)" % (status,))
return False
def create_framebuffer(width, height, target=GL_RGBA):
"""create an empty framebuffer"""
id_buf = Buffer(GL_INT, 1)
glGenFramebuffers(1, id_buf)
fbo_id = id_buf.to_list()[0]
if fbo_id == 0:
print("Framebuffer error on creation")
return -1
tex_id = create_image(width, height)
glBindFramebuffer(GL_FRAMEBUFFER, fbo_id)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex_id, 0)
glGenRenderbuffers(1, id_buf)
depth_id = id_buf.to_list()[0]
glBindRenderbuffer(GL_RENDERBUFFER, depth_id)
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32, width, height)
# attach the depth buffer
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth_id)
#glDrawBuffers(fbo_id, GL_COLOR_ATTACHMENT0)
if not check_framebuffer_status(GL_DRAW_FRAMEBUFFER):
delete_framebuffer(fbo_id)
glBindFramebuffer(GL_FRAMEBUFFER, 0)
return -1
glBindFramebuffer(GL_FRAMEBUFFER, 0)
return fbo_id
def delete_framebuffer(fbo_id):
"""clear created framebuffer"""
id_buf = Buffer(GL_INT, 1)
id_buf.to_list()[0] = fbo_id
if glIsFramebuffer(fbo_id):
glDeleteFramebuffers(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, is_left):
""""""
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)
uniform = glGetUniformLocation(program, "bgl_RenderedStereoEye")
if uniform != -1: glUniform1i(uniform, 0 if is_left else 1)
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():
# Get texture info
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_TEXTURE)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
def draw_rectangle_rainbow(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)]
colors = [(1.0, 0.0, 0.0, 0.0), (0.0, 1.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (1.0, 1.0, 0.0, 0.0)]
glPolygonMode(GL_FRONT_AND_BACK , GL_FILL)
glBegin(GL_QUADS)
for i in range(4):
color = colors[i]
glColor4f(color[0], color[1], color[2], color[3])
glTexCoord3f(texco[i][0], texco[i][1], zed)
glVertex2f(verco[i][0], verco[i][1])
glEnd()
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)
glColor4f(1.0, 1.0, 1.0, 0.0)
glBegin(GL_QUADS)
for i in range(4):
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
is_left = self.is_stereo_left(context)
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)
# (2) 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()
# update shader
glUseProgram(self.program_shader)
setup_uniforms(self.program_shader, self.color_id, self.width, self.height, is_left)
draw_rectangle()
# (3) restore opengl defaults
glUseProgram(0)
glActiveTexture(act_tex[0])
glBindTexture(GL_TEXTURE_2D, 0)
view_reset()
glViewport(self.viewport[0], self.viewport[1], self.viewport[2], self.viewport[3])
glMatrixMode(GL_MODELVIEW)
glTranslatef(-cam_pos[0], -cam_pos[1], -cam_pos[2])
operator.py 0000664 0000000 0000000 00000015062 12602344560 0033426 0 ustar 00root root 0000000 0000000 virtual_reality_viewport-7a101aa0e3f481802393d064eca50ba8391d3308/space_view3d_virtual_reality import bpy
from bpy.app.handlers import persistent
from .hmd import HMD
from .preview import Preview
from .lib import (
getDisplayBackend,
)
TODO = True
# ############################################################
# Main Operator
# ############################################################
class VirtualRealityDisplayOperator(bpy.types.Operator):
""""""
bl_idname = "view3d.virtual_reality_display"
bl_label = "Toggle Virtual Reality Display"
bl_description = ""
_gl_data = None
_timer = None
_handle = None
_width = 1920
_height = 1080
_area_hash = -1
action = bpy.props.EnumProperty(
description="",
items=(("ENABLE", "Enable", "Enable"),
("DISABLE", "Disable", "Disable"),
),
default="DISABLE",
options={'SKIP_SAVE'},
)
@classmethod
def poll(cls, context):
return context.area.type == 'VIEW_3D'
def modal(self, context, event):
wm = context.window_manager
vr = wm.virtual_reality
area = context.area
if not area:
self.quit(context)
self._quit(context)
return {'FINISHED'}
if not vr.is_enabled:
self._quit(context)
area.tag_redraw()
return {'FINISHED'}
if event.type == 'TIMER':
self.loop()
if vr.preview_scale and context.area:
area.tag_redraw()
return {'PASS_THROUGH'}
def invoke(self, context, event):
wm = context.window_manager
vr = wm.virtual_reality
is_enabled = vr.is_enabled
if self.action == 'DISABLE':
if vr.is_enabled:
self.quit(context)
return {'FINISHED'}
else:
self.report({'ERROR'}, "Virtual Reality Display is not enabled")
return {'CANCELLED'}
else: # ENABLE
if vr.is_enabled:
self.report({'ERROR'}, "Virtual Reality Display is already enabled")
return {'CANCELLED'}
if self.init(context):
return {'RUNNING_MODAL'}
else:
# quit right away
wm.virtual_reality.is_enabled = False
self._quit(context)
self.report({'ERROR'}, "Error initializing device")
return {'CANCELLED'}
def quit(self, context):
"""garbage collect"""
# change it so the original modal operator will clean things up
wm = context.window_manager
wm.virtual_reality.is_enabled = False
def _quit(self, context):
"""actual quit"""
wm = context.window_manager
if self._timer:
wm.event_timer_remove(self._timer)
del self._timer
if self._handle:
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
del self._handle
self._hmd.quit()
self._preview.quit()
# cleanup viewport
if context.area:
context.area.tag_redraw()
def init(self, context):
"""
Initialize the callbacks and the external devices
"""
wm = context.window_manager
wm.virtual_reality.is_enabled = True
display_backend = getDisplayBackend(context)
self._hmd = HMD(display_backend)
self._preview = Preview()
if not self._hmd.isConnected():
return False
if not self._hmd.init():
return False
# get the data from device
width = self._hmd.width
height = self._hmd.height
texture = self._hmd.texture
self._preview.init(texture, width, height)
self._area_hash = hash(context.area)
# setup modal
self._timer = wm.event_timer_add(1.0 / 75.0, context.window) # 75 Hz
self._handle = bpy.types.SpaceView3D.draw_handler_add(self._draw_callback_px, (context,), 'WINDOW', 'POST_PIXEL')
wm.modal_handler_add(self)
return True
def loop(self):
"""
Get fresh tracking data and render into the FBO
"""
self._hmd.loop()
offscreen_object = self._hmd.offscreen_object
modelview_matrix = self._hmd.modelview_matrix
projection_matrix = self._hmd.projection_matrix
TODO # need to run this twice, once for each eye
# drawing
# bpy.ops.view3d.offscreen(offscreen_object=offscreen_object, projection_matrix=projection_matrix, modelview_matrix=modelview_matrix)
self._hmd.frameReady()
def _draw_callback_px(self, context):
"""callback function, run every time the viewport is refreshed"""
if self._area_hash == hash(context.area):
wm = context.window_manager
vr = wm.virtual_reality
self._preview.loop(vr.preview_scale)
# ############################################################
# Global Properties
# ############################################################
class VirtualRealityInfo(bpy.types.PropertyGroup):
is_enabled = bpy.props.BoolProperty(
name="Enabled",
default=False,
)
preview_scale = bpy.props.IntProperty(
name="Preview Scale",
min=0,
max=100,
default=100,
subtype='PERCENTAGE',
)
# ############################################################
# Callbacks
# ############################################################
@persistent
def virtual_reality_load_pre(dummy):
wm = bpy.context.window_manager
wm.virtual_reality.is_enabled = False
@persistent
def virtual_reality_load_post(dummy):
wm = bpy.context.window_manager
wm.virtual_reality.is_enabled = False
# ############################################################
# Un/Registration
# ############################################################
def register():
bpy.app.handlers.load_pre.append(virtual_reality_load_pre)
bpy.app.handlers.load_pre.append(virtual_reality_load_post)
bpy.utils.register_class(VirtualRealityDisplayOperator)
bpy.utils.register_class(VirtualRealityInfo)
bpy.types.WindowManager.virtual_reality = bpy.props.PointerProperty(
name="virtual_reality",
type=VirtualRealityInfo,
options={'HIDDEN'},
)
def unregister():
bpy.app.handlers.load_pre.remove(virtual_reality_load_pre)
bpy.app.handlers.load_pre.remove(virtual_reality_load_post)
bpy.utils.unregister_class(VirtualRealityDisplayOperator)
del bpy.types.WindowManager.virtual_reality
bpy.utils.unregister_class(VirtualRealityInfo)
preview.py 0000664 0000000 0000000 00000005002 12602344560 0033245 0 ustar 00root root 0000000 0000000 virtual_reality_viewport-7a101aa0e3f481802393d064eca50ba8391d3308/space_view3d_virtual_reality """
Viewport Preview Drawing
************************
Routines to draw in the viewport the result
that is projected in the HMD
"""
from .opengl_helper import (
draw_rectangle,
view_reset,
view_setup,
)
from bgl import *
TODO = True
class Preview:
__slots__ = {
"_texture",
"_width",
"_height",
}
def init(self, texture, width, height):
"""
Initialize preview window
:param texture: 2D Texture binding ID (bind to the Framebuffer Object)
:type texture: bgl.GLint
:param width: Horizontal dimension of preview window
:type width: int
:param height: Vertical dimension of preview window
:type height: int
"""
self._texture = texture
self.update(texture, width, height)
def quit(self):
"""
Destroy preview window
"""
pass
def update(self, texture, width, height):
"""
Resize preview window
:param texture: 2D Texture binding ID (bind to the Framebuffer Object)
:type texture: bgl.GLint
:param width: Horizontal dimension of preview window
:type width: int
:param height: Vertical dimension of preview window
:type height: int
"""
self._texture = texture
self._width = width
self._height = height
def loop(self, scale):
"""
Draw in the preview window
"""
if not scale:
return
texture = self._texture
act_tex = Buffer(GL_INT, 1)
glGetIntegerv(GL_ACTIVE_TEXTURE, act_tex)
if scale != 100:
viewport = Buffer(GL_INT, 4)
glGetIntegerv(GL_VIEWPORT, viewport)
width = int(scale * 0.01 * viewport[2])
height = int(scale * 0.01 * viewport[3])
glViewport(viewport[0], viewport[1], width, height)
glScissor(viewport[0], viewport[1], width, height)
glEnable(GL_DEPTH_TEST)
glDepthFunc(GL_LESS)
view_setup()
glEnable(GL_TEXTURE_2D)
glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_2D, texture)
draw_rectangle()
glBindTexture(GL_TEXTURE_2D, act_tex[0])
glDisable(GL_TEXTURE_2D)
glDisable(GL_DEPTH_TEST)
view_reset()
if scale != 100:
glViewport(viewport[0], viewport[1], viewport[2], viewport[3])
glScissor(viewport[0], viewport[1], viewport[2], viewport[3])
shaders/ 0000775 0000000 0000000 00000000000 12602344560 0032646 5 ustar 00root root 0000000 0000000 virtual_reality_viewport-7a101aa0e3f481802393d064eca50ba8391d3308/space_view3d_virtual_reality oculus_dk1.glsl 0000664 0000000 0000000 00000004457 12602344560 0035614 0 ustar 00root root 0000000 0000000 virtual_reality_viewport-7a101aa0e3f481802393d064eca50ba8391d3308/space_view3d_virtual_reality/shaders // Oculus DK1 lens distortion shader for multiple eyes
// shader is adapted from Oculus DK1 distortion shader by
// Lubosz Sarnecki(lubosz.wordpress.com/)
uniform sampler2D bgl_RenderedTexture;
uniform float bgl_RenderedTextureWidth;
uniform float bgl_RenderedTextureHeight;
const vec4 kappa = vec4(1.0,1.7,0.7,15.0);
float screen_width = bgl_RenderedTextureWidth;
float screen_height = bgl_RenderedTextureHeight;
const float scaleFactor = 0.8;
const vec2 leftCenter = vec2(0.25, 0.5);
const vec2 rightCenter = vec2(0.75, 0.5);
const float separation = 0.01;
// Scales input texture coordinates for distortion.
vec2 hmdWarp(vec2 LensCenter, vec2 texCoord, vec2 Scale, vec2 ScaleIn) {
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 * 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);
float as = float(screen.x / 2.0) / float(screen.y);
vec2 Scale = vec2(0.5, as);
vec2 ScaleIn = vec2(2.0 * scaleFactor, 1.0 / as * scaleFactor);
vec2 texCoord = gl_TexCoord[0].st;
vec2 texCoordSeparated = texCoord;
vec2 tc = vec2(0);
vec4 color = vec4(0);
// ad hoc enhanced separation to allow proper viewing factor
float ad_hoc_enhance_stereo = 4.0;
if (texCoord.x < 0.5) {
texCoordSeparated.x -= ad_hoc_enhance_stereo *separation;
tc = hmdWarp(leftCenter, texCoordSeparated, Scale, ScaleIn );
color = texture2D(bgl_RenderedTexture, tc);
if (!validate(tc, 1))
color = vec4(0);
} else {
texCoordSeparated.x += ad_hoc_enhance_stereo *separation;
tc = hmdWarp(rightCenter, texCoordSeparated, Scale, ScaleIn);
color = texture2D(bgl_RenderedTexture, tc);
if (!validate(tc, 0))
color = vec4(0);
}
gl_FragColor = color;
}
oculus_dk2.glsl 0000664 0000000 0000000 00000003606 12602344560 0035610 0 ustar 00root root 0000000 0000000 virtual_reality_viewport-7a101aa0e3f481802393d064eca50ba8391d3308/space_view3d_virtual_reality/shaders // Oculus DK2 lens distortion shader for single eye
// shader is adapted from Oculus DK1 distortion shader by
// Lubosz Sarnecki(lubosz.wordpress.com/)
uniform sampler2D bgl_RenderedTexture;
uniform float bgl_RenderedTextureWidth;
uniform float bgl_RenderedTextureHeight;
const vec4 kappa = vec4(1.0,0.9,1.0,2.0);
float screen_width = bgl_RenderedTextureWidth;
float screen_height = bgl_RenderedTextureHeight;
const float scaleFactor = 0.83;
const vec2 lensCenter = vec2(0.5, 0.5);
// Scales input texture coordinates for distortion.
vec2 hmdWarp(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;
}
float edges(vec2 tc)
{
float vertL = smoothstep(0.0,0.05,tc.x);
float vertR = smoothstep(1.0,0.95,tc.x);
float horizL = smoothstep(0.0,0.05,tc.y);
float horizR = smoothstep(1.0,0.95,tc.y);
return vertL*vertR*horizL*horizR;
}
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) / float(screen.y);
vec2 Scale = vec2(1.0, 1.0);
vec2 ScaleIn = vec2(scaleFactor, scaleFactor);
vec2 texCoord = gl_TexCoord[0].st;
vec2 tcR = vec2(0.0);
vec2 tcG = vec2(0.0);
vec2 tcB = vec2(0.0);
vec4 color = vec4(0.0);
tcR = hmdWarp(texCoord, Scale, ScaleIn, eta.r );
tcG = hmdWarp(texCoord, Scale, ScaleIn, eta.g );
tcB = hmdWarp(texCoord, Scale, ScaleIn, eta.b );
color.r = texture2D(bgl_RenderedTexture, tcR*vec2(0.5,1.0)+vec2(0.25,0.0)).r;
color.g = texture2D(bgl_RenderedTexture, tcG*vec2(0.5,1.0)+vec2(0.25,0.0)).g;
color.b = texture2D(bgl_RenderedTexture, tcB*vec2(0.5,1.0)+vec2(0.25,0.0)).b;
color = color * edges(tcR);
gl_FragColor = color;
}
virtual_reality_viewport-7a101aa0e3f481802393d064eca50ba8391d3308/space_view3d_virtual_reality/ui.py0000664 0000000 0000000 00000002176 12602344560 0032271 0 ustar 00root root 0000000 0000000 import bpy
# ############################################################
# User Interface
# ############################################################
class VirtualRealityPanel(bpy.types.Panel):
bl_label = "Head Mounted Display"
bl_space_type = 'VIEW_3D'
bl_region_type = 'TOOLS'
bl_category = 'Virtual Reality'
@staticmethod
def draw(self, context):
layout = self.layout
wm = context.window_manager
vr = wm.virtual_reality
col = layout.column()
if not vr.is_enabled:
col.operator("view3d.virtual_reality_display", text="Virtual Reality", icon="PLAY").action='ENABLE'
else:
col.operator("view3d.virtual_reality_display", text="Virtual Reality", icon="X").action='DISABLE'
col.separator()
col.prop(vr, "preview_scale", text="Preview")
# ############################################################
# Un/Registration
# ############################################################
def register():
bpy.utils.register_class(VirtualRealityPanel)
def unregister():
bpy.utils.unregister_class(VirtualRealityPanel)