Quad Rendering Code

Technical discussion for those interested in Supermodel development and Model 3 reverse engineering. Prospective contributors welcome.
Forum rules
Keep it classy!

  • No ROM requests or links.
  • Do not ask to be a play tester.
  • Do not ask about release dates.
  • No drama!

Quad Rendering Code

Postby Ian » Thu Oct 12, 2017 10:57 am

Been working on this for some time. I've tried various solutions, tesselation, various quad shader demos.
Anyway I ported this code over from an nvidia CG shader demo to glsl
http://vcg.isti.cnr.it/publications/pap ... dering.pdf
Video of the code in action
https://www.youtube.com/watch?v=qAASBiVig7I

Some screenshots

Conventional rendering (split quad into 2 triangles)
Image

Proper quadratic emulation
Image

Source code

Vertex shader (yes test vertices being generated in the vertex shader)
Code: Select all
#version 410 core

uniform mat4 mvp;
uniform mat4 modelMat;
uniform int vid_offset = 0;

out VS_OUT
{
    vec4 color;
} vs_out;

void main(void)
{
    const vec4 vertices[] = vec4[](vec4(-0.0, -0.5, 0.0, 1.0),
                                   vec4( 0.5, -0.5, 0.0, 1.0),
                                   vec4( 0.5,  0.5, 0.0, 1.0),
                                   vec4(-0.5,  0.5, 0.0, 1.0));

    const vec4 colors[] = vec4[](vec4(1.0, 0.0, 0.0, 1.0),
                                 vec4(0.0, 1.0, 0.0, 1.0),
                                 vec4(0.0, 0.0, 0.0, 1.0),
                                 vec4(1.0, 1.0, 1.0, 1.0));

    gl_Position      = mvp * vertices[(gl_VertexID + vid_offset) % 4];
    vs_out.color   = colors[(gl_VertexID + vid_offset) % 4];
}


Geometry shader
Code: Select all
#version 410 core

layout (lines_adjacency) in;
layout (triangle_strip, max_vertices = 4) out;

in VS_OUT
{
    vec4 color;
} gs_in[4];

out GS_OUT
{
   vec2 v[4];
   float area[4];
   float oneOverW[4];
   vec4 color[4];
} gs_out;

float area(vec2 a, vec2 b)
{
   return a.x*b.y - a.y*b.x;
}

void main(void)
{
   int i, j, j_next;

   vec2 v[4];

   for (i=0; i<4; i++) {
      float oneOverW      = 1.0 / gl_in[i].gl_Position.w;
      gs_out.color[i]      = gs_in[i].color * oneOverW;
      gs_out.oneOverW[i]   = oneOverW;
      v[i]            = gl_in[i].gl_Position.xy * oneOverW;
   }

   for (i=0; i<4; i++) {
      // Mapping of polygon vertex order to triangle strip vertex order.
      //
      // Quad (lines adjacency)    Triangle strip
      // vertex order:             vertex order:
      //
      //        1----2                 1----3
      //        |    |      ===>       | \  |
      //        |    |                 |  \ |
      //        0----3                 0----2
      //
      int reorder[4] = int[]( 0, 1, 3, 2 );
      int ii = reorder[i];

      for (j=0; j<4; j++) {
         gs_out.v[j]   = v[j] - v[ii];
      }
      for (j=0; j<4; j++) {
         j_next = (j+1) % 4;
         gs_out.area[j] = area(gs_out.v[j], gs_out.v[j_next]);
      }

      gl_Position = gl_in[ii].gl_Position / gl_in[ii].gl_Position.w;

      EmitVertex();
   }
}


Fragment shader
Code: Select all
#version 410 core

in GS_OUT
{
   vec2 v[4];
   float area[4];
   float oneOverW[4];
   vec4 color[4];
} fs_in;

out vec4 color;

vec4 GetQuadColour()
{
   uint i, i_next, i_prev;

   vec2 s[4];
   float A[4];

   for (i=0; i<4; i++) {
      s[i] = fs_in.v[i];
      A[i] = fs_in.area[i];
   }

   float D[4];
   float r[4];

   for (i=0; i<4; i++) {
      i_next = (i+1)%4;
      D[i] = dot(s[i], s[i_next]);
      r[i] = length(s[i]);
      if (fs_in.oneOverW[i] < 0) {  // is w[i] negative?
         r[i] = -r[i];
      }
   }

   float t[4];

   for (i=0; i<4; i++) {
      i_next = (i+1)%4;
      t[i] = (r[i]*r[i_next] - D[i]) / A[i];
   }

   float uSum = 0;
   float u[4];

   for (i=0; i<4; i++) {
      i_prev = (i-1)%4;
      u[i] = (t[i_prev] + t[i]) / r[i];
      uSum += u[i];
   }


   float lambda[4];

   for (i=0; i<4; i++) {
      lambda[i] = u[i] / uSum;
   }

   /* Discard fragments when all the weights are neither all negative nor all positive. */

   int lambdaSignCount = 0;

   for (i=0; i<4; i++) {
      if (lambda[i] < 0) {
         lambdaSignCount--;
      } else {
         lambdaSignCount++;
      }
   }
   if (abs(lambdaSignCount) != 4) {
      discard;
   }

   vec4 interp_colorOverW = vec4(0.0);
   float interp_oneOverW = 0;

   for (i=0; i<4; i++) {
      interp_colorOverW += lambda[i] * fs_in.color[i];
      interp_oneOverW   += lambda[i] * fs_in.oneOverW[i];
   }

   return interp_colorOverW / interp_oneOverW;
}

void main(void)
{
    color = GetQuadColour();
}


The only thing it won't render is triangles. But we could easily emulate this being creating an imaginary 4th point out of an average of vertices 1 and 3. Then we could render everything with the same render path. The example shaders only work with colour interpolation. But it should be trivial enough to add texture coordinates and other vertex attributes.
Ian
 
Posts: 1175
Joined: Tue Feb 23, 2016 9:23 am

Re: Quad Rendering Code

Postby Bart » Thu Oct 12, 2017 3:48 pm

I'm not opposed to having geometry shaders but I think this would break OpenGL 2/GLSL 2.1 compatibility. Would it be a huge hassle to make this feature configurable? It sounds like there are a substantial number of users using relatively dated hardware.
User avatar
Bart
Site Admin
 
Posts: 2038
Joined: Thu Sep 01, 2011 2:13 pm
Location: New York City

Re: Quad Rendering Code

Postby Ian » Thu Oct 12, 2017 4:13 pm

Well ..
Opengl 2.0 came out in 2004.
Opengl 3.3 came out in 2010, so a good 7 years ago now. Pretty much all pcs will be capable of 3.3 or above. If your GPU is so old it can't run 3.3 .. the cpu will probably be too slow to emulate supermodel anyway.

For windows side, simply me switching the main shaders to 3.3 will cause no issues at all. Things will just carry on as normal. On Mac OS X things will get more complicated. In order to use 3.3 shaders we will have to explicitly create a 3.3 context. But OS X doesn't support compatibility profiles (like windows), so all the depreciated stuff will I assume simply stop working. Fixing all that will create a fair amount of additional work.

If i push the quad code I guess it will break OS X for a while. I really dont want to write a 2nd render path for older hardware :(
Ian
 
Posts: 1175
Joined: Tue Feb 23, 2016 9:23 am

Re: Quad Rendering Code

Postby Bart » Thu Oct 12, 2017 4:27 pm

Understood and it's ultimately your call. Mac OS X users will have the legacy fallback I guess. However, it seems that a second render path should not necessarily be difficult? If quad rendering is enabled, you would load up a different set of shaders (done in one place) and then I think somewhere you just have a single place where you decode Real3D models into a vertex buffer. Wouldn't this be as simple as wrapping it with an if-statement and in the old rendering path, emit triangles but in the new one emit quads?
User avatar
Bart
Site Admin
 
Posts: 2038
Joined: Thu Sep 01, 2011 2:13 pm
Location: New York City

Re: Quad Rendering Code

Postby Ian » Thu Oct 12, 2017 4:35 pm

It might be possible to get the shaders to compile in both version with a few
#ifdef statements
I've seen code do that before.
redefine inout and varying depending on shader version.

Other stuff might be more complicated
I think I'm getting ahead of myself a bit because I haven't even integrated it yet. There might be some unwelcome surprises with an-isotripic filter or something using this method.
Ian
 
Posts: 1175
Joined: Tue Feb 23, 2016 9:23 am

Re: Quad Rendering Code

Postby Ian » Sat Oct 14, 2017 4:37 am

Updated the code a little.
I found dividing by W in the vertex or geometry shader breaks clipping for vertices that are in-front of the camera (z > 0). They appear to wrap around or do something funky. But we need linear interpolation in window space. Luckily can fix it like this

gl_Position = gl_in[ii].gl_Position/ gl_in[ii].gl_Position.w; ----> gl_Position = gl_in[ii].gl_Position;


And for our vertex attributes

out GS_OUT
{
noperspective vec2 v[4];
noperspective float area[4];
flat float oneOverW[4];
flat vec4 color[4];
} gs_out;


Using the noperspective keyword.

Unfortunately this seems to break pixel discarding for polys that overlap z=0, so bow tie and concave quads don't render 100% correctly. But not sure that's even an issue anyway because I doubt the hardware supports concave quads, so I've simply commented it out.

Anyway the full shader code

Code: Select all
#version 410 core

layout (lines_adjacency) in;
layout (triangle_strip, max_vertices = 4) out;

in VS_OUT
{
    vec4 color;
} gs_in[4];

out GS_OUT
{
   noperspective vec2 v[4];
   noperspective float area[4];
   flat float oneOverW[4];
   flat vec4 color[4];
} gs_out;

float area(vec2 a, vec2 b)
{
   return a.x*b.y - a.y*b.x;
}

void main(void)
{
   int i, j, j_next;

   vec2 v[4];

   for (i=0; i<4; i++) {
      float oneOverW      = 1.0 / gl_in[i].gl_Position.w;
      gs_out.color[i]      = gs_in[i].color * oneOverW;
      gs_out.oneOverW[i]   = oneOverW;
      v[i]            = gl_in[i].gl_Position.xy * oneOverW;
   }

   for (i=0; i<4; i++) {
      // Mapping of polygon vertex order to triangle strip vertex order.
      //
      // Quad (lines adjacency)    Triangle strip
      // vertex order:             vertex order:
      //
      //        1----2                 1----3
      //        |    |      ===>       | \  |
      //        |    |                 |  \ |
      //        0----3                 0----2
      //
      int reorder[4] = int[]( 0, 1, 3, 2 );
      int ii = reorder[i];

      for (j=0; j<4; j++) {
         gs_out.v[j]   = v[j] - v[ii];
      }
      for (j=0; j<4; j++) {
         j_next = (j+1) % 4;
         gs_out.area[j] = area(gs_out.v[j], gs_out.v[j_next]);
      }

      gl_Position = gl_in[ii].gl_Position;

      EmitVertex();
   }
}


Code: Select all
#version 410 core

in GS_OUT
{
   noperspective vec2 v[4];
   noperspective float area[4];
   noperspective float oneOverW[4];
   noperspective vec4 color[4];
} fs_in;

out vec4 color;

vec4 GetQuadColour()
{
   uint i, i_next, i_prev;

   vec2 s[4];
   float A[4];

   for (i=0; i<4; i++) {
      s[i] = fs_in.v[i];
      A[i] = fs_in.area[i];
   }

   float D[4];
   float r[4];

   for (i=0; i<4; i++) {
      i_next = (i+1)%4;
      D[i] = dot(s[i], s[i_next]);
      r[i] = length(s[i]);
      if (fs_in.oneOverW[i] < 0) {  // is w[i] negative?
         r[i] = -r[i];
      }
   }

   float t[4];

   for (i=0; i<4; i++) {
      i_next = (i+1)%4;
      t[i] = (r[i]*r[i_next] - D[i]) / A[i];
   }

   float uSum = 0;
   float u[4];

   for (i=0; i<4; i++) {
      i_prev = (i-1)%4;
      u[i] = (t[i_prev] + t[i]) / r[i];
      uSum += u[i];
   }


   float lambda[4];

   for (i=0; i<4; i++) {
      lambda[i] = u[i] / uSum;
   }

   /* Discard fragments when all the weights are neither all negative nor all positive. */

   int lambdaSignCount = 0;

   for (i=0; i<4; i++) {
      if (lambda[i] < 0) {
         lambdaSignCount--;
      } else {
         lambdaSignCount++;
      }
   }
   if (abs(lambdaSignCount) != 4) {
      //discard;      // need to revisit this
   }

   vec4 interp_colorOverW = vec4(0.0);
   float interp_oneOverW = 0;

   for (i=0; i<4; i++) {
      interp_colorOverW += lambda[i] * fs_in.color[i];
      interp_oneOverW   += lambda[i] * fs_in.oneOverW[i];
   }

   return interp_colorOverW / interp_oneOverW;
}

void main(void)
{
    color = GetQuadColour();
}
Ian
 
Posts: 1175
Joined: Tue Feb 23, 2016 9:23 am

Re: Quad Rendering Code

Postby Ian » Mon Oct 16, 2017 9:58 am

Well this is the original comparison between the h/w and quad split 1 vs 2
Image

Old version
Quad split 1
Image

Quad split 2
Image

This is what my quad emulation code looks like
Image

The texture looks almost flawless compared to the actual arcade. No more funky bend in the middle of it. But if you look at the top right a strange anomolie appears, the same problem which showed up simply changed the quad split with 2 triangles. Need to dig into the model to find out exactly what that poly is doing. Could be the 4th point is a dummy or something.

The good news is, otherwise the code works almost flawlessly, at 60fps too. The bad news is needs high end hardware. I tried it on a 5000 series AMD HD which supports opengl 4+. It first failed because it didn't have enough output space for the geometry shader. Then when I crippled the shader to remove half the outputs it then failed because it didn't support the noperspective keyword in the shader. It should support it .. but it seemed to have no effect. I couldn't get to the shaders to work either on gl 3.3 because the fragment shader is relying on unsigned integer maths which apparently gl 3.3 doesn't support. Could find a work around for that .. but doesn't seem much point.

Harry you need a new graphics card :p
Ian
 
Posts: 1175
Joined: Tue Feb 23, 2016 9:23 am

Re: Quad Rendering Code

Postby HarryTuttle » Mon Oct 16, 2017 12:04 pm

Ian, that's an amazing work! :) Another (much needed) step toward perfect emulation!

Can't wait to see how specular behaves with it, in particular I'm curious too see this case (Left = Model 3):
h1.jpeg
h1.jpeg (252.45 KiB) Viewed 130 times


Ian wrote:Harry you need a new graphics card :p

Hey, not so fast... :D If there's room for experimentation in order to find a solution for older GPUs, I'll be glad to dedicate my time on it. Many others could benefit too.
User avatar
HarryTuttle
 
Posts: 520
Joined: Thu Mar 09, 2017 8:57 am

Re: Quad Rendering Code

Postby Ian » Mon Oct 16, 2017 12:12 pm

Well,
we could shift a lot of the maths in the fragment shader to the vertex shader. That would solve the output limits i hit with the 5000 series card with regards to how much you can output from the geo shader. But without the noperspective keyword working, textures look severely broken :p I don't know anyway around it.

It's possible I did something wrong when testing the shader on that hardware, I didn't extensively test it. But it should be pretty trivial to write a test case for that hardware to see if that keyword works .. I guess it's pretty damn rare to use it, so maybe it just never made it into older hw/drivers.

It's this which relies on unsigned maths
Code: Select all
      i_prev = (i-1)%4;

Originally I used signed maths and the wrap around was wrong using it. Could put something ugly like if (i==0) i = 3; That would fix wrap around with signed values
Ian
 
Posts: 1175
Joined: Tue Feb 23, 2016 9:23 am

Re: Quad Rendering Code

Postby HarryTuttle » Mon Oct 16, 2017 12:25 pm

Ian wrote:But without the noperspective keyword working, textures look severely broken :p I don't know anyway around it.


Do you mean that using that keyword throws out an error or just ignores that? When learning GLSL I used in the past each of three interpolation qualifiers in shaders to experiment and they did work as expected. That could even be a driver bug.

In my current build, for example, I use the flat qualifier on some vertex outputs where I don't need interpolation (such as polygon bool attributes and other viewport level attributes).
User avatar
HarryTuttle
 
Posts: 520
Joined: Thu Mar 09, 2017 8:57 am

Next

Return to The Dark Room

Who is online

Users browsing this forum: No registered users and 3 guests