8
votes

I'm trying to draw a large mesh in Matlab using the trimesh function, with the z coordinate of the vertices controlling the color. Unfortunately, Matlab stops interpolating colors correctly when the size of the mesh exceeds 120 triangles. Here's a picture demonstrating the problem, with 120 triangles on the left, and 121 triangles on the right.

A picture demonstrating the problem

As you can see, for large meshes, Matlab interpolates directly from the color of one vertex to the color of the other vertex. This was probably done for performance reasons, but I'm trying to generate nice pictures for my thesis, and I don't care how long it takes to compute them. Is there a way to disable this approximation?

Here's the code to generate the picture:

function test(n)
    %%% Generate a mesh with n triangles.

    oneTriVerts = [0 0 0;
                   1 0 0;
                   1 0 1];

    offset = [0 (1/n) 0;
              0 (1/n) 0;
              0 (1/n) 0];

    verts = zeros(0,3);
    tris  = zeros(0,3);
    for i = 0:(n-1)
        verts = [verts; (oneTriVerts + i * offset)];
        tris = [tris; i*3+1, i*3+2, i*3+3];
    end

    %%% Draw the mesh, with color corresponding to the z coordinate.

    trimesh(tris, verts(:,1), verts(:,2), verts(:,3), verts(:,3));
    title(sprintf('n = %d', n))
    shading interp
    axis equal
1
I can confirm the effect you observe on my computer. I have no good ideas, but it seems that under the hood, trimesh calls patch, which is a built-in function. As a workaround, could you plot triangles in batches of 100, and then do a hold on after the first plot?Bas Swinckels
It looks like the shading mode is an option to patch so this is probably an artifact of trimesh changing the call to patch: mathworks.com/help/matlab/visualize/…Ben Jackson
@BasSwinckels : Good idea, but as soon as I plot the second batch of triangles, they all become incorrectly shaded. It seems like it has to do with the total number of triangles in the figure, rather than the number processed by any individual plotting command.Neil Forrester
@BenJackson : I printed out the properties of the resulting patch objects for n = 100 and n = 300, and the only differences were the amount of data, and the handle of the parent axes. All the options for vertex shading were the same.Neil Forrester
@BenJackson: Pending a real solution to the problem, I've actually taken the opposite approach as a workaround. I'm recursively dividing each triangle into 4 (kind of like a Sierpinski triangle) until there are enough vertices along the edges to hit all the colors in the colormap.Neil Forrester

1 Answers

5
votes

I think that after a certain threshold, MATLAB switched to the OpenGL renderer for better performance (hardware acceleration). Unfortunately, it is not without bugs.

I haven't closely looked at how you are building the triangular faces (there might a problem with how they are ordererd), but an easy solution is to explicitly set the rendering method. Simply add the following call at the end of your function:

set(gcf, 'Renderer','zbuffer')

EDIT

The workaround above should do just fine. Now the real problem is not a buggy OpenGL, but rather a documented limitation:

OpenGL does not do colormap interpolation. If you create a surface or patch using indexed color and interpolated face or edge coloring, OpenGL interpolates the colors through the RGB color cube instead of through the colormap.

Note that the TRIMESH call is equivalent to the following:

patch('Faces',tris, 'Vertices',verts, 'FaceVertexCData',verts(:,3), ...
    'EdgeColor','none', 'FaceColor','interp', 'CDataMapping','scaled')

So for each vertex you specify a color equal to its z-coordinate (you only have two unique values, either 0 or 1). This is interpreted as an indexed color into the current figure's colormap using scaled mapping (the default is the jet colormap). So the two colors end up being:

clr = jet(64);    % default colormap
clr(1,:)          % blueish color [0 0 0.5625] mapped from 0
clr(end,:)        % reddish color [0.5 0 0] mapped from 1

Unfortunately as the quote above explains, OpenGL renderer will not do the interpolation using the colors of colormap palette, rather perform the interpolation in the RGB colorspace between the two colors above. Thus we get the blue-red gradient you saw.

So your only option is to use one of the two other renderers, zbuffer being the best method here.


Here is the code to see the difference between the two rendering methods:

% plot patch
clf
patch('Faces',tris, 'Vertices',verts, 'FaceVertexCData',verts(:,3), ...
    'EdgeColor','none', 'FaceColor','interp', 'CDataMapping','scaled')
view(3)
axis vis3d
colorbar

% choose one of the two
set(gcf, 'Renderer','opengl')
set(gcf, 'Renderer','zbuffer')

OpenGL

opengl renderer

Z-Buffer

zbuffer renderer