Posts Tagged ‘ambient occlusion’

Paper-worthy rendering in MATLAB

Thursday, July 20th, 2017

MATLAB is not a great tool for creating 3D renderings. However, the learning curves for most commercial rendering tools are quite steep. Other tools like Mitsuba can create beautiful pictures, but can feel quite cumbersome for rendering pure geometry rather than the physical scenes their designed for.

Over the years, I’ve developed a way of creating plots of 3D shapes in MATLAB using a few extra functions in gptoolbox. This started as a way to just make images from research prototypes more palatable, but eventually became the usual way that I render images for papers. If the code for my research is already written in MATLAB then one huge advantage is that every image in my paper can have a *.m script that deterministically generates the result and the corresponding image with user intervention. This helps with reproducibility, editing and sharing between collaborators.

Here’s a “VFX Breakdown” of rendering a 3D shape in MATLAB.

t = tsurf(F,V);
set(gcf,'COlor',0.94*[1 1 1]);
teal = [144 216 196]/255;
pink = [254 194 194]/255;
bg_color = pink;
fg_color = teal;
for pass = 1:10
  switch pass
  case 1
    % blank run
    axis([-209.4       119.38      -181.24       262.67      -247.28 247.38]);
  case 2
    axis equal;
    axis([-209.4       119.38      -181.24       262.67      -247.28 247.38]);
    axis vis3d;
  case 3
    t.EdgeColor = 'none';
  case 4
    set(t,fphong,'FaceVertexCData',repmat(fg_color,size(V,1),1));
  case 5
    set(t,fsoft);
  case 6
    l = light('Position',[0.2 -0.2 1]);
  case 7
    set(gca,'Visible','off');
  case 8
    set(gcf,'Color',bg_color);
  case 9
    s = add_shadow(t,l,'Color',bg_color*0.8,'BackgroundColor',bg_color,'Fade','infinite');
  case 10
    apply_ambient_occlusion(t,'AddLights',false,'SoftLighting',false);
  end

  vidObj = VideoWriter(sprintf('nefertiti-%02d.mp4',pass),'MPEG-4');
  vidObj.Quality = 100;
  vidObj.open;
  thetas = linspace(30,-30,450);
  for theta = thetas(1:end-1)
    view(theta,30);
    drawnow;
    vidObj.writeVideo(getframe(gcf));
  end
  vidObj.close;

end

CAD-style rendering in matlab

Wednesday, September 9th, 2015

Here’s a little script demonstrating some of the fancy 3d-model rendering features you can squeeze out of MATLAB with a little effort. Stroking sharp edges and silhouettes and giving just the hint of transparency, you can achieve a CAD-style effect.

cad-style rendering in matlab

[V,F] = load_mesh('~/Dropbox/models/fandisk.off');
V = V*axisangle2matrix([1 0 0],pi);
N = normals(V,F);
BC = barycenter(V,F);

% sharp edges
[A,C] = adjacency_dihedral_angle_matrix(V,F);
A(1&A) = abs(A(1&A)-pi)>pi*0.11;
[CI,~,CV] = find(C.*A);
E = F([CI+mod(CV,3)*size(F,1) CI+mod(CV+1,3)*size(F,1)]);

% cut mesh at sharp edges to get crisp normals
[G,I] = cut_edges(F,E);
W = V(I,:);

% floor board
BB = bounding_box(V(:,1:2));
BB = bsxfun(@plus,bsxfun(@minus,BB,mean(BB))*2,mean(BB));
BB(:,3) = min(V(:,3))-4e-3;
BB = reshape(permute(BB,[1 3 2]),[2 2 3]);
% checkboard texture
ch = repmat(1-0.2*xor((mod(repmat(0:128-1,128,1),8*2)>7), ...
  (mod(repmat((0:128-1)',1,128),8*2)>7)),[1 1 3])*0.5 + 0.5;

clf;
hold on;
blue = [0.2 0.3 0.8];
tf = tsurf(G,W, ...
  'FaceVertexCData',repmat(blue,size(W,1),1), ...
  'SpecularStrength',0, ...
  'DiffuseStrength',0.1, ...
  'AmbientStrength',1.0, ...
  'EdgeColor','none','FaceAlpha',0.9,fphong);
te = tsurf(E(:,[1 2 2]),V,'EdgeColor',blue*0.75);
to = tsurf([1 1 1],V,'LineWidth',2,'EdgeColor',blue*0.5);
sc = surf(BB(:,:,1),BB(:,:,2),BB(:,:,3), ...
  'CData',ch,'FaceColor','texturemap', ...
  'SpecularStrength',0, 'DiffuseStrength',0, 'AmbientStrength',1);
view(130,38);
axis equal;
l = light('Position',[1 4 5.0],'Style','infinite');
[h,~,~,g] = add_shadow(tf,l,'Nudge',2e-3,'Fade','local','Color',[0.8 0.8 0.8]);
% faint amient occlusion
AO = ambient_occlusion(W,G,W,per_vertex_normals(W,G),1000);
AO = AO*0.17;
tf.FaceVertexCData = bsxfun(@times,tf.FaceVertexCData,1-AO);
hold off;
axis vis3d;
camproj('persp');
set(gca,'Visible','off');
T = get(gca,'tightinset');
set(gca,'position',[T(1) T(2) 1-T(1)-T(3) 1-T(2)-T(4)]); 

% Set up rotatation callbacks to hide view-dependent effects during drag
up = @() ...
  set(to,'Faces', ...
    outline(F((sum(N.*bsxfun(@minus,BC,campos),2)<=0),:))*[1 0 0;0 1 1]) | ...
  set(h,'FaceAlpha',0.5*(g*[campos 1]'<0)) | ...
  set(sc,'FaceAlpha',1.0*(g*[campos 1]'<0));
up();
down = @() set(to,'Faces',[]);
set(rotate3d,'ActionPostCallback',@(src,obj) up());
set(rotate3d,'ActionPreCallback',@(src,obj) down());

for t = linspace(0,-360,60)
  view(64+t,20);
  up();
  drawnow;
  figgif('fan-disk-matlab-cad-rendering.gif');
end

Update: It even looks reasonable for less artificial shapes, though perhaps the hard edges just accidentally look fuzzy like fur:

bunny cad style matlab

Ambient occlusion + anti-aliasing in MATLAB

Wednesday, October 15th, 2014

I’m enjoying the new anti-aliased graphics in matlab 2014b. Here’s the xyzrgb dragon rendered with soft lighting, ambient occlusion and a simple colormap:

xyz dragon matlab ambient occlusion

Here’s the code to reproduce it:

[V,F] = load_mesh('/Users/ajx/Documents/AutoDOF/Code/skinning/skinning/xyzrgb_dragon/xyzrgb_dragon.obj');
AO = ambient_occlusion(V,F,V,per_vertex_normals(V,F),1000);
colormap(parula(9));
t = tsurf(F,V,'EdgeColor','none');
C = squeeze(ind2rgb(floor(matrixnormalize(t.FaceVertexCData)*size(colormap,1))+1,colormap));
t.FaceVertexCData = bsxfun(@times,C,1-AO);
t.SpecularStrength = 0.1;
t.DiffuseStrength = 0.1;
t.AmbientStrength = 0.8;
l = light('Position',[1 1 100],'Style','infinite');
l2 = light('Position',[1 -100 1],'Style','infinite');
set(gca,'XTickLabel',[],'YTickLabel',[],'ZTickLabel',[],'Color',[0.94 0.94 0.94]);
set(gcf,'Color','w');
axis equal
camproj('persp');
t.Vertices = V*axisangle2matrix([0 0 1],pi)*axisangle2matrix([1 0 0],pi/2);
view(-43,6);
axis tight;
drawnow;

The ambient_occlusion call takes quite some time on my little laptop. But I think the result looks nice.

MATLAB2014b features anti-aliasing

Monday, October 13th, 2014

Finally. I’m pretty happy about the results:

cheburashka with matlab anti-aliasing

[V,F] = load_mesh('/usr/local/igl/libigl/examples/shared/cheburashka.off');
AO = ambient_occlusion(V,F,V,per_vertex_normals(V,F),1000);
t = tsurf(F,V,fphong,'EdgeColor','none');
C = squeeze(ind2rgb(floor(matrixnormalize(t.FaceVertexCData)*size(colormap,1))+1,colormap));
t.FaceVertexCData = bsxfun(@times,C,1-AO);
t.SpecularStrength = 0.1;
t.DiffuseStrength = 0.1;
t.AmbientStrength = 0.8;
l = light('Position',[1 1 100],'Style','infinite');
l2 = light('Position',[1 -100 1],'Style','infinite');
set(gca,'XTickLabel',[],'YTickLabel',[],'ZTickLabel',[],'Color',[0.94 0.94 0.94]);
set(gcf,'Color','w');

And to spin the camera around:

axis equal
axis vis3d;
camproj('persp');
for f = 1:numel(T)
  t = T(f);
  view(-cos(t*2*pi)*45,sin(t*2*pi)*45+45);
  drawnow;
  frame = getframe(gcf);
  [SIf,cm] = rgb2ind(frame.cdata,256);
  if f == 1
    imwrite(SIf,cm,filename,'Loop',Inf,'Delay',0);
  else
    imwrite(SIf,cm, filename,'WriteMode','append','Delay',0);
  end
end

With the awesome but now obsolete myaa.m hacked anti-aliasing, creating this gif would have taken many minutes. This runs in real time.

Ambient occlusion for matlab pseudo-color plots

Sunday, October 13th, 2013

I’m often plotting scalar functions on surfaces using the trisurf routine of matlab. This has basic per-vertex, per-face coloring. And with some hassle you could even set up lights. Instead I’ve settled on a better way to convey the shape of the surface and the function by multiply the colors with the inverse of the mesh’s ambient occlusion. I wrote a matlab wrapper mex function for our libigl function. Find it in libigl/examples/ambient-occlusion-mex (version ≥0.3.4). Once compiled you can call from matlab it like this:

S = ambient_occlusion(V,F,P,N,num_samples);

Where (V,F) is your triangle mesh, P are the evaluation points and N are the normals (defining a hemisphere).

Here’s an example of multiplying it against some pseudo-color values:

ambient occlusion over pseudo color plot matlab

For computing ambient occlusion per-vertex and then applying it to per-vertex colors, use:

AO = ambient_occlusion(V,F,V,per_vertex_normals(V,F),1000);
tsurf(F,V,'FaceVertexCData',bsxfun(@times,(1-AO),C),fphong,'EdgeColor','none');

Update: If you have a scalar field and you’d like to visualize it using ambient occlusion on top of a pseudo-color, try:

tsurf(F,V,'FaceVertexCData',bsxfun(@times,1-AO,squeeze(ind2rgb(floor(matrixnormalize(Wbh(:,1))*256),jet(256)))),fphong,'EdgeColor','none');

Ambient occlusion proof of concept demo in libigl

Tuesday, October 8th, 2013

The libigl “extra” for the Embree ray tracing library made it super easy to whip up an ambient occlusion demo. Check out the example in the libigl source under libigl/examples/ambient-occlusion (version ≥0.3.3).

beast ambient occlusion in libigl

The demo just shoots rays in random directions in the hemisphere of each mesh vertex and aggregates a hit ratio. Then it colors the mesh with these values per-vertex using GL_COLOR_MATERIAL. The program shoots a few more rays per point every draw frame (except when the users dragging the camera around).

There are plenty of ways to be fancier about ambient occlusion, but this demonstrates the basic idea.