简体   繁体   中英

Trying to draw shapes in screen-space coordinates using an orthographic projection with WebGL (regl)

Using regl I'm trying to implement a very simple drawRect() function that will eventually be able to pass an x, y, width, height in screen space (with origin 0,0 at top left corner of the screen)

Here's what I have so far:

https://codesandbox.io/s/nn9qvxom4l

const regl = require('regl')();
const mat4 = require('gl-mat4');

var drawRect = regl({
    frag: `
      precision mediump float;
      uniform vec4 color;
      void main() {
        gl_FragColor = color;
      }`,

    vert: `
      precision mediump float;
      attribute vec2 position;
      uniform vec2 offset;
      uniform vec2 scale;
      uniform float viewportWidth;
      uniform float viewportHeight;
      uniform mat4 projection;
      void main() {
        // windows ratio scaling factor.
        float r = (viewportWidth) / (viewportHeight);
        gl_Position = projection * vec4(position.xy * scale, 0, 1);
      }`,

    attributes: {
        position: [
            [-1, -1], //tri 1 bottom left
            [1, 1],
            [-1, 1],
            [-1, -1],
            [1, -1],
            [1, 1]
        ]
    },

    uniforms: {
        offset: regl.prop('offset'),
        scale: regl.prop('scale'),
        color: regl.prop('color'),
        viewportWidth: regl.context('viewportWidth'),
        viewportHeight: regl.context('viewportHeight'),
        projection: ({ viewportWidth, viewportHeight }) => {
            //glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);
            //ortho(out:mat4, left, right, bottom, top, near, far)
            // this makes everything blank
            var m = mat4.ortho(
                [],
                0,
                viewportWidth,
                0,
                viewportHeight,
                0.1,
                100.0
            );
            console.log(m);
            return m;

        }
    },

    depth: {
        enable: false
    },

    cull: {
        enable: true
    },

    count: 6,

    viewport: {
        x: 0,
        y: 0,
        width: window.innerWidth,
        height: window.innerHeight
    }
});

regl.clear({
    color: [0, 0, 0, 255],
    depth: 1
});

// all these coordinates are in clip-space* right now (* I think)
// but I'd like to be able to pass in screen-space coordinates
drawRect([
    {
        offset: [0, 0],
        scale: [0.002, 0.002],
        color: [1, 0.2, 0.2, 1]
    },
    {
        offset: [0, -0.2],
        scale: [0.2, 0.05],
        color: [1, 0.2, 1, 1]
    },
    {
        offset: [0, 0],
        scale: [0.5, 0.5],
        color: [0.2, 0.2, 1, 1]
    }
]);

Currently it draws a blank screen (I'm guessing due the projection matrix clipping or moving everything outside of the viewport). If you remove the projection from the vertex shader or use an identity matrix in the projection attribute then it renders a blue square again.

So my questions are:

  1. What am I doing wrong here? Why does my orthographic projection not work?
  2. How would I debug this in the future so I can "see" the resultant coordinates and how they're wrong?

I have read some SO questions, OpenGL books and blogs, but I'm stuck here because it seems like this should work.

Related resources:

https://unspecified.wordpress.com/2012/06/21/calculating-the-gluperspective-matrix-and-other-opengl-matrix-maths/ https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_model_view_projection https://webglfundamentals.org/webgl/lessons/webgl-2d-matrices.html WebGL Isometric projection Want an OpenGL 2D example (VC++, draw a rectangle) http://songho.ca/opengl/gl_transform.html

Edit: fixed viewportWidth being used twice in ortho projection. This fixes a blank screen, however origin is now bottom-left instead of top-left. Switching around parameters of ortho projection cause a blank screen again.

The projection matrix describes the mapping from 3D points of a scene, to 2D points of the viewport. The projection matrix transforms from view space to the clip space. The coordinates in the clip space are transformed to the normalized device coordinates (NDC) in the range (-1, -1, -1) to (1, 1, 1) by dividing with the w component of the clip coordinates.

At Orthographic Projection the coordinates in the view space are linearly mapped to clip space coordinates and the clip space coordinates are equal to the normalized device coordinates, because the w component is 1 (for a cartesian input coordinate).

正投影

The values for left, right, bottom, top, near and far define a box. All the geometry which is inside the volume of the box is "visible" on the viewport.

Finally the normalized device coordinates are linearly mapped to the viewport.

When you set an orthographic projection like this:

var m = mat4.ortho([], 0, viewportWidth, 0, viewportHeight, 0.1, 100.0);

then the rectangle with the lower left (0, 0) and the upper right (viewportWidth, viewportHeight) is mapped to NDC from (-1, -1) to (1, 1) .

When you set a viewport like this:

viewport: {x: 0, y: 0, width: window.innerWidth, height: window.innerHeight}

then the NDC are mapped to the viewport rectangle with the lower left (0, 0) and the upper right (viewportWidth, viewportHeight) .

This means, that the view coordinates in (0, 0, window.innerWidth, window.innerHeight) are mapped to the viewport rectangle (0, 0, window.innerWidth, window.innerHeight) . So you are drawing in "window" (pixel) coordinates.

If you have a geometry defined like this:

position: [[-1, -1], [1, 1], [-1, 1], [-1, -1], [1, -1], [1, 1] ]

{ offset: [0, 0], scale: [0.5, 0.5], color: [0.2, 0.2, 1, 1] }

then the size of this geometry, is 1*1 pixel on the viewport - a rectangle from (-1, -1) to (1, 1) , with a scale of (0.5, 0.5) .

Increase the scale (eg scale: [100, 100] ) to solve your issue.


Furthermore the geometry has to be in between the near and far plane, to be inside the view volume.

In your case the z coordinate of the geometry is 0, as specified in the vertex shader:

gl_Position = projection * vec4(position.xy * scale, 0, 1);

But the near plane is 0.1 and the far plane 100.0, as specified in the orthographic projection:

var m = mat4.ortho([], 0, viewportWidth, 0, viewportHeight, 0.1, 100.0);

This causes that the geometry is clipped by the near plane, because the z coordinate is less then the near plane.

Change the near plane, to solve the issue:

var m = mat4.ortho([], 0, viewportWidth, 0, viewportHeight, -100.0, 100.0);

The Orthographic Projection Matrix looks like this:

r = right, l = left, b = bottom, t = top, n = near, f = far 

2/(r-l)         0               0               0
0               2/(t-b)         0               0
0               0               -2/(f-n)        0
-(r+l)/(r-l)    -(t+b)/(t-b)    -(f+n)/(f-n)    1

Accoridng to the comment:

for mat4.ortho, top left origin should be mat4.ortho([], 0, viewportWidth, viewportHeight, 0, -1, 1) but with that projection nothing appears on the screen again.

If top is 0 and bottom is a positive value, then the y axis becomes negative, because of 2/(top-bottom) .

To solve is you can invert the y axis manually:

var m = mat4.ortho([], 0, viewportWidth, -viewportHeight, 0, -1, 1);

In this case you have to invert the offsets, because the upper right of the view it (0, 0) and the lower left is (viewportWidth, -viewportHeight) .

eg

offset: [250, -250]

If you would use

var m = mat4.ortho([], 0, viewportWidth, viewportHeight, 0, -1, 1);

then you have to invert the y scales.

eg

scale: [100, -100]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM