webgl 3D texture from 2D image, working but with rendering issue

543 Views Asked by At

I am trying to use the 3D textures in WebGL, but since it's not currently nativelly supported, I have to use to tricks with a 2D image. It is composed of 64 images, each are 64px by 64px (for a total of 64x4096). I wanted to post a link but I dont have enough xp on SO (seriously?), still you can find the file url in the fiddle lower.

I used the solution by gman as well as my own (nearest neighbor). In both cases, I have a strange rendering issue with grey stripe artifacts when I use an oblique projection (capture).

The stripes/dashes seem to appear in between the slices when the projection is oblique, and depending on the camera position, they do not appear at the same frequency. This is why I am thinking about a rendering issue rather than a sampling issue.

Does anyone know how to get rid of these artifacts? Is there some built-in OpenGL ES thing to enable/disable?

Here is the shader code:

<!-- FRAGMENT SHADER -->
<script id="fragment_shader1_custom3" type="x-shader/x-fragment">

uniform sampler2D texture;
uniform float imageIndex;
varying vec4 worldCoord;
varying vec2 vUv;

// tex is a texture with each slice of the cube placed in grid in a texture.
// texCoord is a 3d texture coord
// size is the size if the cube in pixels.
// slicesPerRow is how many slices there are across the texture
// numRows is the number of rows of slices

vec2 computeSliceOffset(float slice, float slicesPerRow, vec2 sliceSize) {
  return sliceSize * vec2(mod(slice, slicesPerRow),
  floor(slice / slicesPerRow));
}

vec4 sampleAs3DTexture(sampler2D tex, vec3 texCoord, float size, float numRows, float slicesPerRow){

  float slice   = texCoord.z * size;
  float sliceZ  = floor(slice);                         // slice we need
  float zOffset = fract(slice);                         // dist between slices

  vec2 sliceSize = vec2(1.0 / slicesPerRow,             // u space of 1 slice
    1.0 / numRows);                 // v space of 1 slice

  vec2 slice0Offset = computeSliceOffset(sliceZ, slicesPerRow, sliceSize);
  vec2 slice1Offset = computeSliceOffset(sliceZ + 1.0, slicesPerRow, sliceSize);

  vec2 slicePixelSize = sliceSize / size;               // space of 1 pixel
  vec2 sliceInnerSize = slicePixelSize * (size - 1.0);  // space of size pixels

  vec2 uv = slicePixelSize * 0.5 + texCoord.xy * sliceInnerSize;
  vec4 slice0Color = texture2D(tex, slice0Offset + uv);
  vec4 slice1Color = texture2D(tex, slice1Offset + uv);
  return mix(slice0Color, slice1Color, zOffset);
}


void main( void ) {
  // the position within the shader
  vec2 shaderPos = vUv;

  // if outside the texture cube, use a yellow color
  if(worldCoord.x < 0.0 || worldCoord.x >= 1.0 || worldCoord.y < 0.0 || worldCoord.y >= 1.0 || worldCoord.z < 0.0 || worldCoord.z >= 1.0){
    gl_FragColor = vec4(1.0, 1.0 , 0.0, 1.0);
  }else{

    vec3 chunkPosition = vec3(worldCoord.x, worldCoord.y, worldCoord.z);

    // 63.0 instead of 64.0 to avoid a edge situation
    gl_FragColor = sampleAs3DTexture(texture, chunkPosition, 63.0, 64.0, 1.0);

  }
}
</script> <!-- END FRAGMENT SHADER -->



<!-- VERTEX SHADER -->
<script id="vertexShader_custom1" type="x-shader/x-vertex">
varying vec2 vUv;
varying vec4 worldCoord;

void main()
{
  vUv = uv;
  vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
  gl_Position = projectionMatrix * mvPosition;
  worldCoord = modelMatrix * vec4( position, 1.0 );
}
</script> <!-- END VERTEX SHADER -->

Here is the fiddle.

Note that I use ThreeJS. Thanks for your help.

1

There are 1 best solutions below

0
On

As you found by yourself, the solution was to set texture.minFilter = THREE.NearestFilter after loading the texture with THREE.TextureLoader() in your js code (for this reason, please append the js code in your question, this may help a future user that would step on this question on stackoverflow).

Now an explanation of the stripes: when your pixel is close to one of the planes where you change slice, the variable sliceZ jumps. You get in the situation where the pixel covers more than one texel. Note that part of the pixel is at some v coordinate, and part of the pixel is at a v coordinate that is bigger by a quantity that corresponds to 64 pixels of the texture (to get to the image below)/ My guess is that the hidden machinery believes that your pixel covers a v-span of 64 texture pixels and thus takes the average value of those pixels (that has been pre-computed by the default mip-map setting in .minFilter).