sgrove
sgrove

Reputation: 1189

Drawing a grid in a WebGL fragment shader

I'm working on porting a ZUI from SVG over to WebGL for a few reasons, and I'd like to render a grid using a fragment shader.

Here's the basic effect I'm going for https://dl.dropboxusercontent.com/u/412963/steel/restel_2.mp4

I'd like to have a triangle that has thin, 1px lines every 10 units, and a thicker 2px line every 100 units (the units here being arbitrary but consistent with world-space, not screen-space).

Here's what I have so far, without the secondary thicker lines like in the video (note that this is literally a copy from my open buffer, and obviously isn't right):

Vertex Shader:

attribute vec3 aVertexPosition;

uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;

varying float vX;
varying float vY;

void main(void) {
  vX = aVertexPosition.x;
  vY = aVertexPosition.y;
  gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
}

Fragment Shader:

precision mediump float;

uniform vec2 resolution;
uniform float uZoomFactor;

varying float vX;
varying float vY;

void main(void) {
  float distance = gl_FragCoord.z / gl_FragCoord.w;
  float fuzz = 1.0 / distance;

  float minorLineFreq;

  if (distance > 10.0) {
    minorLineFreq = 1.0;
  } else if (distance > 5.0) {
    minorLineFreq = 1.0;
  } else {
    minorLineFreq = 0.10;
  }

  float xd = mod(vX, minorLineFreq) * 88.1;
  float yd = mod(vY, minorLineFreq) * 88.1;

  if (xd < fuzz) {
    gl_FragColor = vec4(0.0,0.0,0.0,1.0);
  } else if (yd < fuzz) {
    gl_FragColor = vec4(0.0,0.0,0.0,1.0);
  } else {
    gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
  }
}

It produces approximately the right image at a certain distance (but notice the banding effect where there's 2px lines instead of 1px):

Grid with banding Grid with banding Zoomed in grid with unwanted thicker lines Zoomed in grid with unwanted thicker lines

So, how can I get a consistent grid, with 1px thick lines at every distance, all inside of a WebGL fragment shader?

Upvotes: 6

Views: 15295

Answers (2)

Alec Thilenius
Alec Thilenius

Reputation: 553

gl_FragCoord is already scaled to the render target resolution. So you can simply:

precision mediump float;

vec4 color = vec4(1.);
vec2 pitch  = vec2(50., 50.);

void main() {    
    if (mod(gl_FragCoord.x, pitch[0]) < 1. ||
        mod(gl_FragCoord.y, pitch[1]) < 1.) {
        gl_FragColor = color;
    } else {
        gl_FragColor = vec4(0.);
    }
}

https://glslsandbox.com/e#74754.0

Upvotes: 5

sgrove
sgrove

Reputation: 1189

I believe I've found an acceptable solution.

Using the following vertices (drawn in a triangle strip):

[ 1.0  1.0  0.0
 -1.0  1.0  0.0
  1.0 -1.0  0.0
 -1.0 -1.0  0.0]

Vertex shader:

attribute vec4 aVertexPosition;

void main(void) {
  gl_Position = aVertexPosition;
}

Fragment Shader:

precision mediump float;

uniform float vpw; // Width, in pixels
uniform float vph; // Height, in pixels

uniform vec2 offset; // e.g. [-0.023500000000000434 0.9794000000000017], currently the same as the x/y offset in the mvMatrix
uniform vec2 pitch;  // e.g. [50 50]

void main() {
  float lX = gl_FragCoord.x / vpw;
  float lY = gl_FragCoord.y / vph;

  float scaleFactor = 10000.0;

  float offX = (scaleFactor * offset[0]) + gl_FragCoord.x;
  float offY = (scaleFactor * offset[1]) + (1.0 - gl_FragCoord.y);

  if (int(mod(offX, pitch[0])) == 0 ||
      int(mod(offY, pitch[1])) == 0) {
    gl_FragColor = vec4(0.0, 0.0, 0.0, 0.5);
  } else {
    gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
  }
}

Gives results (depending on the pitch and offset) like:

Example Grid output

Upvotes: 11

Related Questions