Model 3 texturing

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!

Model 3 texturing

Postby Ian » Fri Oct 12, 2018 5:49 pm

This one is for Harry, since it was his suggestion quite some time ago that we add this code. But I never added it previously because it didn't make sense to add it so early on in the renderer development.

To start off :)
What is this ugly line in the sky in daytona?

Image

To understand this problem we first need to understand how textures are repeated over a polygon. In OpenGL texture coordinates go from 0 -> 1
0 is the start of a texture, and 1 is the end. If values exceed 1, they can be wrapped, mirrored, or clamped. Something like this

Image

Most of the time people use GL_REPEAT or GL_CLAMP_TO_EDGE. GL_CLAMP_TO_EDGE stops the textures wrapping around and 'bleeding' on the edges.

Here is an early version of supermodel, where you can see this edge bleeding all over the place.

Image

If textures start at 0, and end at 1, and the values are always in between these, why do we get this edge bleeding at all? To understand this problem, let's have a closer look at how textures are sampled with gl_repeat.

Image

A bilinear filter always samples 2 adjacent pixels, and does a linear interpolation between them. At the edge, or texture coordinate 0, the first pixel will actually be sampled from the other side of the texture.

Image

Okay, so what is the issue here? The problem is the model3 has a unique texture mode, which is a combination of GL_CLAMP_TO_EDGE and GL_REPEAT. This texture mode doesn't exist on modern hardware. So this means textures repeat over a polygon, but on the edges there is no texture bleeding.

Previously I solved the issue by stretching the texture coordinates half a pixel inwards, to prevent it sampling the edges. Okay then why did the seem still exist in Daytona? The answer is because half a pixel shift, is only half a pixel in the base texture. Each mipmap is the same texture but half the resolution. Half a texel shift of the base texture, is not half a texel shift in the mipmap, so the seem appears again, and gets worse with each mipmap level.

In order to solve this problem I had to re-implement bilinear and trilinear filtering from scratch inside the shader, with our custom wrap modes :) This is the result, perfect texturing.

Image

Nice, no more seem!

Harry also noticed some quite fundamental differences in how alpha (or contour textures as they are known on the model3) work. Alpha textures are textures with 1 bit alpha. So they are either fully opaque, or 100% transparent.

Image

To sample a texture with bilinear filtering, it's actually an average of 4 pixels. And the weight of each pixel depends how far into the pixel it is. This means at the edge of a opaque section, the edge pixels will actually average together with say the background colour in the texture. Quite a common issue with alpha texturing. An example here

Image

Suddenly white edges appear. If the rest of the transparent texture was black, black edges would appear. The real3d guys actually had a solution in hardware, that modified how bilinear filtering worked.

The theory behind it is here
https://blog.ostermiller.org/dilate-and-erode

This was Harry's description
What is happening here, instead, is that the edge texels "bleed" toward the "outer", transparent, texture area copying their RGB values there, while leaving the alpha channel intact. Infact the texture edge is cut in respect of the original alpha value, while its color is replicated from the "inner" texels.

This fixes, with the appropriate contour threshold, all the ugly white/black borders (but even some colored ones) in contour textures.


So what is happening is they are copying the colour values to the adjacent pixels, to stop the colour bleed from the non transparent sections. Quite a clever solution. I've never actually seen this before. Again modern hardware doesn't do this, or at least not in the fixed function hardware. We can emulate this in our shaders though.

The algorithm modifies the bilinear filter to do something like this

Image

Anyway with this algorithm textures look perfect as they do on the model 3. There is no colour bleed from the adjacent pixels.

Image
Last edited by Ian on Sat Oct 13, 2018 6:45 am, edited 1 time in total.
Ian
 
Posts: 1535
Joined: Tue Feb 23, 2016 9:23 am

Re: Model 3 texturing

Postby Ian » Sat Oct 13, 2018 2:25 am

This is the code to make it happen :)

Code: Select all
   
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);


Turn off OpenGL texture filtering. Our algorithm can actually work with it turned on, but then opengl is needlessly sampling extra pixels.

This is the shader code

Code: Select all
float mip_map_level(in vec2 texture_coordinate) // in texel units
{
    vec2  dx_vtc        = dFdx(texture_coordinate);
    vec2  dy_vtc        = dFdy(texture_coordinate);
    float delta_max_sqr = max(dot(dx_vtc, dx_vtc), dot(dy_vtc, dy_vtc));
    float mml = 0.5 * log2(delta_max_sqr);
    return max( 0, mml );
}

float LinearTexLocations(int wrapMode, float size, float u, out float u0, out float u1)
{
   float texelSize      = 1.0 / size;
   float halfTexelSize   = 0.5 / size;

   if(wrapMode==0) {                     // repeat
      u   = (u * size) - 0.5;
      u0   = (floor(u) + 0.5) / size;         // + 0.5 offset added to push us into the centre of a pixel, without we'll get rounding errors
      u0   = fract(u0);
      u1   = u0 + texelSize;
      u1   = fract(u1);

      return fract(u);                  // return weight
   }
   else if(wrapMode==1) {                  // repeat + clamp
      u   = fract(u);                     // must force into 0-1 to start
      u   = (u * size) - 0.5;
      u0   = (floor(u) + 0.5) / size;         // + 0.5 offset added to push us into the centre of a pixel, without we'll get rounding errors
      u1   = u0 + texelSize;

      if(u0 <  0.0)   u0 = 0.0;
      if(u1 >= 1.0)   u1 = 1.0 - halfTexelSize;
      
      return fract(u);                  // return weight
   }
   else {                              // mirror + mirror clamp - both are the same since the edge pixels are repeated anyway

      float odd = floor(mod(u, 2.0));         // odd values are mirrored

      if(odd > 0.0) {
         u = 1.0 - fract(u);
      }
      else {
         u = fract(u);
      }

      u   = (u * size) - 0.5;
      u0   = (floor(u) + 0.5) / size;         // + 0.5 offset added to push us into the centre of a pixel, without we'll get rounding errors
      u1   = u0 + texelSize;

      if(u0 <  0.0)   u0 = 0.0;
      if(u1 >= 1.0)   u1 = 1.0 - halfTexelSize;
      
      return fract(u);                  // return weight
   }
}

vec4 texBiLinear(sampler2D texSampler, float level, ivec2 wrapMode, vec2 texSize, vec2 texCoord)
{
   float tx[2], ty[2];
   float a = LinearTexLocations(wrapMode.s, texSize.x, texCoord.x, tx[0], tx[1]);
   float b = LinearTexLocations(wrapMode.t, texSize.y, texCoord.y, ty[0], ty[1]);
   
   vec4 p0q0 = texture2DLod(texSampler, vec2(tx[0],ty[0]), level);
    vec4 p1q0 = texture2DLod(texSampler, vec2(tx[1],ty[0]), level);
    vec4 p0q1 = texture2DLod(texSampler, vec2(tx[0],ty[1]), level);
    vec4 p1q1 = texture2DLod(texSampler, vec2(tx[1],ty[1]), level);

   if(alphaTest) {
      if(p0q0.a > p1q0.a)      { p1q0.rgb = p0q0.rgb; }
      if(p0q0.a > p0q1.a)      { p0q1.rgb = p0q0.rgb; }

      if(p1q0.a > p0q0.a)      { p0q0.rgb = p1q0.rgb; }
      if(p1q0.a > p1q1.a)      { p1q1.rgb = p1q0.rgb; }

      if(p0q1.a > p0q0.a)      { p0q0.rgb = p0q1.rgb; }
      if(p0q1.a > p1q1.a)      { p1q1.rgb = p0q1.rgb; }

      if(p1q1.a > p0q1.a)      { p0q1.rgb = p1q1.rgb; }
      if(p1q1.a > p1q0.a)      { p1q0.rgb = p1q1.rgb; }
   }

   // Interpolation in X direction.
    vec4 pInterp_q0 = mix( p0q0, p1q0, a ); // Interpolates top row in X direction.
    vec4 pInterp_q1 = mix( p0q1, p1q1, a ); // Interpolates bottom row in X direction.

    return mix( pInterp_q0, pInterp_q1, b ); // Interpolate in Y direction.
}

vec4 textureR3D(sampler2D texSampler, ivec2 wrapMode, vec2 texSize, vec2 texCoord)
{
   float numLevels = floor(log2(min(texSize.x, texSize.y)));            // r3d only generates down to 1:1 for square textures, otherwise its the min dimension
   float fLevel   = min(mip_map_level(texCoord * texSize), numLevels);

   if(alphaTest) fLevel *= 0.5;
   else fLevel *= 0.8;

   float iLevel = floor(fLevel);                  // value as an 'int'

   vec2 texSize0 = texSize / pow(2, iLevel);
   vec2 texSize1 = texSize / pow(2, iLevel+1.0);

   vec4 texLevel0 = texBiLinear(texSampler, iLevel, wrapMode, texSize0, texCoord);
   vec4 texLevel1 = texBiLinear(texSampler, iLevel+1.0, wrapMode, texSize1, texCoord);

   return mix(texLevel0, texLevel1, fract(fLevel));   // linear blend between our mipmap levels
}
Ian
 
Posts: 1535
Joined: Tue Feb 23, 2016 9:23 am

Re: Model 3 texturing

Postby theboy181 » Sat Oct 13, 2018 12:31 pm

Where can I get the build that supports these changes? Also, GREAT WORK!
theboy181
 
Posts: 6
Joined: Mon Jun 20, 2016 8:17 pm

Re: Model 3 texturing

Postby doteater » Sun Oct 14, 2018 8:41 am

some people use emufrance but that site seems to have a dodgy reputation ? not sure why,never downloaded anything from there

i personally wait for jiterdomer to do his magic,first post here
viewtopic.php?f=3&t=1206

you probably need r749 or 750 to get the latest improvements

and good shit as always,ian
User avatar
doteater
 
Posts: 15
Joined: Fri Nov 18, 2016 9:24 am

Re: Model 3 texturing

Postby theboy181 » Thu Oct 18, 2018 7:52 am

ian,

Thanks to this post, things for the N64 scene are about to improve. THANK YOU!

https://github.com/gonetz/GLideN64/issu ... -430875824

I was hoping I could get your opinion on another issue that plagues most N64 GFX plugins. The output of each pixel is not perfectly square.
https://github.com/gonetz/GLideN64/issues/1933

I was wondering if you have ever had this challenge, or if you have any idea as to what might be going on?
theboy181
 
Posts: 6
Joined: Mon Jun 20, 2016 8:17 pm

Re: Model 3 texturing

Postby Ian » Fri Oct 19, 2018 4:04 am

I don't really know enough about the n64 to answer that question
But I know the n64 has some totally unique 3 point bilinear filter, instead of the usual 4 point which leads to some interesting filtering. I guess that was an extreme measure to save performance on that old hw.
Ian
 
Posts: 1535
Joined: Tue Feb 23, 2016 9:23 am


Return to The Dark Room

Who is online

Users browsing this forum: No registered users and 6 guests