Skip to main content
Up and Coming
March 30, 2013

OpenGL full example and shader...

Hello,

Great job with Rift and SDK. It seems awesome!. Waiting for my SDK...

A question:

- It's planned to release an new version of SDK with a minimal example (OculusRoomTiny) for OpenGL (with lens correction shader)?
- When? Soon? (to do this work myself or wait for new release)

Thanks!

Fernando

    36 replies

    Inspiring
    January 25, 2014
    "rupy" wrote:
    Or how do I render the left and right part of the double barrel independently? (= move the rendering half screen left with clipping to left screen and then right, I'm not talking stereoscopic eye translation here)


    I find it's easiest to do a 4-step process per frame.

    • Render the left eye scene to a texture

    • Render the left eye texture to the left half of the screen with a distortion mechanism.

    • Render the right eye scene to a texture

    • Render the right eye texture to the right half of the screen with a distortion mechanism.


    From your image I get the impression you're trying to implement the 3-step process used by the example code in the SDK

    • Render the left eye scene to the left half of a texture

    • Render the right eye scene to a right half of a texture

    • Render the texture to the screen, distorting both halves at once


    Except you're not actually doing the first two steps. You're just rendering the whole scene to the whole texture, so when you do the next step, each eye is actually getting a distorted view of half of the full image.

    You can get part of the way towards proper rendering by just taking the code that's doing all the scene rendering (not the distortion) and putting it into a for loop that executes twice, once for each eye, and using the GL viewport to restrict the rendering to that half of the window. Bear in mind one important point... glClear() does not obey viewport constraints, so you need to make sure you're calling it before you enter the for loop, not inside it.

    However, doing this will only get you part of the way as I said. In order for the images to merge when you view them on the Rift you also need to be sure you're using the right projection matrix for each eye. This is what ensures that there is more field of view to the left of the left eye and to the right of the right eye, and also results in pushing the center of the scene to the spot on the display that's directly under the lens axis (this is NOT the physical center of the viewport). You can read a little more about this here.

    "rupy" wrote:
    Or can someone post a fully working stereoscopic example in LWJGL?


    Yeah, I'm working on it.
    Brad Davis - Developer for High Fidelity Co-author of Oculus Rift in Action
    Up and Coming
    January 25, 2014
    "jherico" wrote:

    I find it's easiest to do a 4-step process per frame.


    Yes that's what I want to do too! But I need the shader, I don't understand the shaders, I wish there was some OpenGL way of distorting the render without textures and all that like this: http://github.prideout.net/barrel-distortion/; basically I just want glDoBarrel(blabla...), it should be as simple as that!

    "jherico" wrote:

    Except you're not actually doing the first two steps.


    Yeah, I know, but when I use the viewport with the LWJGL code I get binoculars in both viewports = 4 barrels! I need a single barrel shader for that. I understand that for 100% correct skew and everything you need to do separate left and right transforms, but my viewport code above doesen't and it works great while using C-cups (A- and B-cups get the borders and it's bad was kinda bad) = no sickness (which is the only important thing here because all other games/demos make me sick!!!)



    Let's not understate the importance of this: if the rift makes you the least sick the whole concept goes out the window = AAA are not an option with today's hardware because the bloated rendering software (engines) go well above the 50ms movement-to-photon latency!

    "jherico" wrote:

    Yeah, I'm working on it.


    Great!
    "It's like Homeworld in first person." Disable Aero and vSync for a completely simulator sickness free experience with 2xHz FPS. Keep the config utility open for tracking to work.
    Up and Coming
    January 25, 2014
    I solved it by rendering a larger area, this is how it looks on windows with the code below (might not work on OSX, friend had some issues with left eye disappearing when using SCISSOR on Mac):




    glEnable(GL_SCISSOR_TEST);
    GLU.gluPerspective(120, 1, 1, scale * 2); // The FOV has to be different for each A, B and C cups...

    ...

    Vector3f eye = Quaternion.right(rift);

    glScissor(0, 40, 720, 720);
    glViewport(0, 40, 720, 720);

    glTranslatef(-eye.x, -eye.y, -eye.z);
    // draw game
    glTranslatef(eye.x, eye.y, eye.z);

    glClear(GL_DEPTH_BUFFER_BIT);

    glScissor(640, 40, 720, 720);
    glViewport(560, 40, 720, 720);

    glTranslatef(eye.x, eye.y, eye.z);
    // draw game
    glTranslatef(-eye.x, -eye.y, -eye.z);
    "It's like Homeworld in first person." Disable Aero and vSync for a completely simulator sickness free experience with 2xHz FPS. Keep the config utility open for tracking to work.
    Inspiring
    January 26, 2014
    "rupy" wrote:
    "jherico" wrote:

    Yeah, I'm working on it.


    Great!


    OK, so I've pushed a simple version of this to my public repository. I'm going to do another thread about this with more detail.
    Brad Davis - Developer for High Fidelity Co-author of Oculus Rift in Action
    Up and Coming
    April 28, 2014
    So I empirically improved this and I think it should be:


    glScissor(0, 30, 740, 740);
    glViewport(0, 30, 740, 740);

    ...

    glScissor(640, 30, 740, 740);
    glViewport(530, 30, 740, 740);


    Edit:


    glScissor(0, top, square, square);
    glViewport(-ipd, top, square, square);

    glTranslatef(-eye.x + shake.x, -eye.y + shake.y, -eye.z + shake.z);
    draw(me, eye, true, time, delta);
    glTranslatef(eye.x - shake.x, eye.y - shake.y, eye.z - shake.z);

    glClear(GL_DEPTH_BUFFER_BIT);

    glScissor(640, top, square, square);
    glViewport(border + ipd, top, square, square);

    glTranslatef(eye.x + shake.x, eye.y + shake.y, eye.z + shake.z);
    draw(me, eye, false, time, delta);
    glTranslatef(-eye.x - shake.x, -eye.y - shake.y, -eye.z - shake.z);


    Where square, border and top are calculated like this:


    int vert = ovr.getVResolution();
    float vscr = ovr.getVScreenSize();
    float sep = ovr.getLensSeparationDistance();
    float width = ovr.getInterpupillaryDistance();

    float ratio = sep / vscr;
    float pixels = ratio * (vert / 2);

    square = (int) (640 - pixels) * 2;
    border = (int) (640 + (640 - square));
    top = (800 - square) / 2;


    BTW: This is why glScissor doesent work on OSX:

    http://lists.apple.com/archives/mac-opengl/2014/Jan/msg00008.html
    "It's like Homeworld in first person." Disable Aero and vSync for a completely simulator sickness free experience with 2xHz FPS. Keep the config utility open for tracking to work.
    New Participant
    October 15, 2014
    "38leinaD" wrote:
    You where faster densohax :-)

    Anywhere, here is my selfcontained example for the Barrel Distortion Shader. Sorry, I am little quicker with Java. Uses OpenGL via LWJGL.

    import static org.lwjgl.opengl.GL11.*;
    import static org.lwjgl.opengl.GL20.*;
    import static org.lwjgl.opengl.GL30.*;

    import java.nio.ByteBuffer;
    import java.nio.IntBuffer;

    import org.lwjgl.BufferUtils;
    import org.lwjgl.LWJGLException;
    import org.lwjgl.input.Keyboard;
    import org.lwjgl.opengl.*;

    public class DistortionCorrection {

    public enum Eye {
    Left, Right
    };

    protected int shader=0;
    protected int vertShader=0;
    protected int fragShader=0;

    protected int colorTextureID;
    protected int framebufferID;
    protected int depthRenderBufferID;

    private int LensCenterLocation;
    private int ScreenCenterLocation;
    private int ScaleLocation;
    private int ScaleInLocation;
    private int HmdWarpParamLocation;

    private final static String VERTEX_SHADER_SOURCE =
    "void main() {\n" +
    " gl_TexCoord[0] = gl_MultiTexCoord0;\n" +
    " gl_Position = gl_Vertex;\n" +
    "}";

    private final static String FRAGMENT_SHADER_SOURCE =
    "uniform sampler2D tex;\n" +
    "uniform vec2 LensCenter;\n" +
    "uniform vec2 ScreenCenter;\n" +
    "uniform vec2 Scale;\n" +
    "uniform vec2 ScaleIn;\n" +
    "uniform vec4 HmdWarpParam;\n" +
    "\n" +
    "vec2 HmdWarp(vec2 texIn)\n" +
    "{\n" +
    " vec2 theta = (texIn - LensCenter) * ScaleIn;\n" +
    " float rSq= theta.x * theta.x + theta.y * theta.y;\n" +
    " vec2 theta1 = theta * (HmdWarpParam.x + HmdWarpParam.y * rSq + " +
    " HmdWarpParam.z * rSq * rSq + HmdWarpParam.w * rSq * rSq * rSq);\n" +
    " return LensCenter + Scale * theta1;\n" +
    "}\n" +
    "\n" +
    "\n" +
    "\n" +
    "void main()\n" +
    "{\n" +
    " vec2 tc = HmdWarp(gl_TexCoord[0]);\n" +
    " if (any(notEqual(clamp(tc, ScreenCenter-vec2(0.25,0.5), ScreenCenter+vec2(0.25, 0.5)) - tc, vec2(0.0, 0.0))))\n" +
    " gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);\n" +
    " else\n" +
    " gl_FragColor = texture2D(tex, tc);\n" +
    "}";

    public DistortionCorrection(int screenWidth, int screenHeight) {
    initShaders(VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE);
    initFBO(screenWidth, screenHeight);
    Util.checkGLError();

    LensCenterLocation = glGetUniformLocation(shader, "LensCenter");
    ScreenCenterLocation = glGetUniformLocation(shader, "ScreenCenter");
    ScaleLocation = glGetUniformLocation(shader, "Scale");
    ScaleInLocation = glGetUniformLocation(shader, "ScaleIn");
    HmdWarpParamLocation = glGetUniformLocation(shader, "HmdWarpParam");
    System.out.println(FRAGMENT_SHADER_SOURCE);
    Util.checkGLError();
    }

    private void initFBO(int screenWidth, int screenHeight) {
    framebufferID = glGenFramebuffers();
    colorTextureID = glGenTextures();
    depthRenderBufferID = glGenRenderbuffers();

    glBindFramebuffer(GL_FRAMEBUFFER, framebufferID);

    // initialize color texture
    glBindTexture(GL_TEXTURE_2D, colorTextureID);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, screenWidth, screenHeight, 0,GL_RGBA, GL_INT, (java.nio.ByteBuffer) null);
    //glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    //glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D, colorTextureID, 0);

    // initialize depth renderbuffer
    glBindRenderbuffer(GL_RENDERBUFFER, depthRenderBufferID);
    glRenderbufferStorage(GL_RENDERBUFFER, GL14.GL_DEPTH_COMPONENT24, screenWidth, screenHeight);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_RENDERBUFFER, depthRenderBufferID);

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }

    public void beginOffScreenRenderPass() {

    glBindTexture(GL_TEXTURE_2D, 0);
    Util.checkGLError();
    glBindFramebuffer(GL_FRAMEBUFFER, framebufferID);
    Util.checkGLError();
    }

    public void endOffScreenRenderPass() {

    }

    public void renderToScreen() {
    Util.checkGLError();
    glUseProgram(shader);
    Util.checkGLError();


    glEnable(GL_TEXTURE_2D);
    glDisable(GL_DEPTH_TEST);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    glClearColor (1.0f, 0.0f, 0.0f, 0.5f);
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glBindTexture(GL_TEXTURE_2D, colorTextureID);

    renderDistortedEye(Eye.Left, 0.0f, 0.0f, 0.5f, 1.0f);
    renderDistortedEye(Eye.Right, 0.5f, 0.0f, 0.5f, 1.0f);

    glUseProgram(0);
    glEnable(GL_DEPTH_TEST);

    }

    public static float K0 = 1.0f;
    public static float K1 = 0.22f;
    public static float K2 = 0.24f;
    public static float K3 = 0.0f;

    public void renderDistortedEye(Eye eye, float x, float y, float w, float h) {
    float as = w/h;

    float scaleFactor = 1.0f;

    this.validate();
    Util.checkGLError();

    float DistortionXCenterOffset;
    if (eye == Eye.Left) {
    DistortionXCenterOffset = 0.25f;
    }
    else {
    DistortionXCenterOffset = -0.25f;
    }

    glUniform2f(LensCenterLocation, x + (w + DistortionXCenterOffset * 0.5f)*0.5f, y + h*0.5f);
    glUniform2f(ScreenCenterLocation, x + w*0.5f, y + h*0.5f);
    glUniform2f(ScaleLocation, (w/2.0f) * scaleFactor, (h/2.0f) * scaleFactor * as);;
    glUniform2f(ScaleInLocation, (2.0f/w), (2.0f/h) / as);

    glUniform4f(HmdWarpParamLocation, K0, K1, K2, K3);

    if (eye == Eye.Left) {
    glBegin(GL_TRIANGLE_STRIP);
    glTexCoord2f(0.0f, 0.0f); glVertex2f(-1.0f, -1.0f);
    glTexCoord2f(0.5f, 0.0f); glVertex2f(0.0f, -1.0f);
    glTexCoord2f(0.0f, 1.0f); glVertex2f(-1.0f, 1.0f);
    glTexCoord2f(0.5f, 1.0f); glVertex2f(0.0f, 1.0f);
    glEnd();
    }
    else {
    glBegin(GL_TRIANGLE_STRIP);
    glTexCoord2f(0.5f, 0.0f); glVertex2f(0.0f, -1.0f);
    glTexCoord2f(1.0f, 0.0f); glVertex2f(1.0f, -1.0f);
    glTexCoord2f(0.5f, 1.0f); glVertex2f(0.0f, 1.0f);
    glTexCoord2f(1.0f, 1.0f); glVertex2f(1.0f, 1.0f);
    glEnd();
    }
    }

    protected void initShaders(String vertexShader, String fragmentShader) {
    shader=glCreateProgram();

    vertShader=createVertShader(vertexShader);
    fragShader=createFragShader(fragmentShader);
    Util.checkGLError();

    if (vertShader != 0 && fragShader != 0) {
    glAttachShader(shader, vertShader);
    glAttachShader(shader, fragShader);

    glLinkProgram(shader);
    if (glGetProgram(shader, GL_LINK_STATUS) == GL_FALSE) {
    System.out.println("Linkage error");
    printLogInfo(shader);
    System.exit(0);
    }

    glValidateProgram(shader);
    if (glGetProgram(shader, GL_VALIDATE_STATUS) == GL_FALSE) {
    printLogInfo(shader);
    System.exit(0);
    }
    } else {
    System.out.println("No shaders");
    System.exit(0);
    }
    Util.checkGLError();
    }

    public void validate() {
    glValidateProgram(shader);
    if (glGetProgram(shader, GL_VALIDATE_STATUS) == GL_FALSE) {
    printLogInfo(shader);
    }
    }

    private int createVertShader(String vertexCode){
    vertShader=glCreateShader(GL_VERTEX_SHADER);

    if (vertShader==0) {
    return 0;
    }

    glShaderSource(vertShader, vertexCode);
    glCompileShader(vertShader);

    if (glGetShader(vertShader, GL_COMPILE_STATUS) == GL_FALSE) {
    printLogInfo(vertShader);
    vertShader=0;
    }
    return vertShader;
    }

    private int createFragShader(String fragCode){

    fragShader = glCreateShader(GL_FRAGMENT_SHADER);
    if (fragShader==0) {
    return 0;
    }
    glShaderSource(fragShader, fragCode);
    glCompileShader(fragShader);
    if (glGetShader(fragShader, GL_COMPILE_STATUS) == GL_FALSE) {
    printLogInfo(fragShader);
    fragShader=0;
    }
    return fragShader;
    }

    protected static boolean printLogInfo(int obj){
    IntBuffer iVal = BufferUtils.createIntBuffer(1);
    glGetShader(obj,GL_INFO_LOG_LENGTH, iVal);

    int length = iVal.get();
    if (length > 1) {
    ByteBuffer infoLog = BufferUtils.createByteBuffer(length);
    iVal.flip();
    glGetShaderInfoLog(obj, iVal, infoLog);
    byte[] infoBytes = new byte[length];
    infoLog.get(infoBytes);
    String out = new String(infoBytes);
    System.out.println("Info log:\n"+out);
    return false;
    }
    else {
    return true;
    }
    }

    public static void main(String[] args) {
    try {
    DisplayMode displayMode = new DisplayMode(640, 400);
    Display.setDisplayMode(displayMode);
    Display.setTitle("Barrel Distorion Shader");
    //Display.setLocation(-1000, 200);
    PixelFormat pixelFormat = new PixelFormat(8, 8, 8);
    Display.create(pixelFormat);
    } catch (LWJGLException e) {
    e.printStackTrace();
    }

    DistortionCorrection shader = new DistortionCorrection(640, 400);

    while (!Display.isCloseRequested()) {
    if (Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {
    System.exit(0);
    }
    shader.beginOffScreenRenderPass();

    glDisable(GL_DEPTH_TEST);
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();


    glBegin(GL_LINES);
    glColor3f(1.0f, 0.0f, 0.0f);
    for (int i=0; i<20; i++) {
    glVertex2f(-1.0f, -1.0f + 0.1f * i);
    glVertex2f(1.0f, -1.0f + 0.1f * i);
    }
    for (int i=0; i<20; i++) {
    glVertex2f(-1.0f + 0.1f * i, -1.0f);
    glVertex2f(-1.0f + 0.1f * i, 1.0f);
    }
    glEnd();

    shader.endOffScreenRenderPass();
    shader.renderToScreen();

    Display.sync(60);
    Display.update();
    }
    }
    }


    First of all, thanks a lot for sharing this snippet. I'm using it for my project. However, I just found something wrong...

    I found out that the fragment shader code only workes on some computers. Don't ask my why it works on some systems, but the function HmdWarp expects a vec2, but gl_TexCoord[0] is a vec4 and this causes the following problem (but only on some systems):
    New DisortionShader...
    Info log:
    ERROR: 0:20: 'HmdWarp' : no matching overloaded function found (using implicit conversion)
    ERROR: 0:20: '=' : cannot convert from 'const float' to '2-component vector of float'


    So I changed the line
    + "   vec2 tc = HmdWarp(gl_TexCoord[0]);\n"

    to
    + "   vec2 tc = HmdWarp(gl_TexCoord[0].xy);\n"

    and now it workes on all my test computers.

    jonek had the some error on Sat May 04, 2013 6:22 am, this seems to be the fix...