Navigation Menu

Skip to content

Commit

Permalink
Added Plug-in for Generating Ambient-Occlusion via OpenGL.
Browse files Browse the repository at this point in the history
Uses hardware-accelerated OpenGL calls, instead of ray-tracing, to
calculate and set a per-vertex ambient-occlusion factor. A HemiCube is
used to sample the environment's visibility. The results are stored in
the vertex-colors and can also be baked to a texture through autouv.

The Hemires variable is hard-coded at 64. Values greater than 64
slowed down the calculation, and made no difference in the result.
Values below 64 offered slightly increased speed at the cost of a
much worse-looking solution. 64 seems to be the magic number here.
  • Loading branch information
Anthony D'Agostino authored and bjorng committed May 30, 2009
1 parent 031e00c commit c34833f
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 0 deletions.
5 changes: 5 additions & 0 deletions NOTES-1.1
Expand Up @@ -40,6 +40,11 @@

The command Tools|Memory Info has been removed. [bjorng]

- New in Tools menu: Ambient-Occlusion Generator/Baker. It works on a per-
vertex basis and uses a standard inside-out approach. Mesh subdivision
yields more accuracy. The results are stored in the vertex-colors and can
also be baked to a texture through AutoUV. [scorpius]

--- 1.1.1 -------------------------------------------------------------------
- The data structure for the vertex tables has been changed.
(There is one vertex table to keep track of the vertex positions,
Expand Down
1 change: 1 addition & 0 deletions plugins_src/commands/Makefile
Expand Up @@ -51,6 +51,7 @@ MODULES= \
wpc_turnedge \
wpc_tweak \
wpc_untriangulate \
wpc_ambocc \
wpc_weld

TARGET_FILES= $(MODULES:%=$(EBIN)/%.beam)
Expand Down
205 changes: 205 additions & 0 deletions plugins_src/commands/wpc_ambocc.erl
@@ -0,0 +1,205 @@
%%
%% wpc_ambocc.erl --
%%
%% Plug-in for Generating Ambient Occlusion via OpenGL
%%
%% Uses hardware-accelerated OpenGL calls, instead of ray-tracing, to
%% calculate and set a per-vertex ambient-occlusion factor. A HemiCube is
%% used to sample the environment's visibility. The results are stored in
%% the vertex-colors and can also be baked to a texture through autouv.
%%
%% CopyRight (c) 2008-2009 Anthony D'Agostino
%%
%% See the file "license.terms" for information on usage and redistribution
%% of this file, and for a DISCLAIMER OF ALL WARRANTIES.
%%

-module(wpc_ambocc).

-export([init/0,menu/2,command/2]).

-define(NEED_OPENGL, 1).
-include_lib("wings.hrl").

init() ->
true.

menu({tools}, Menu) ->
Menu ++ [separator,{"Ambient Occlusion",ambient_occlusion,
"Add Ambient-Occlusion vertex colors via OpenGL"}];
menu(_, Menu) -> Menu.

command({tools,ambient_occlusion}, St) ->
ambient_occlusion(St);
command(_Cmd, _) -> next.

ambient_occlusion(St) ->
StartTime = now(),
gl:pushAttrib(?GL_ALL_ATTRIB_BITS),
setup_gl(),
DispList = make_disp_list(St),
#st{shapes=Shapes} = St,
ProcessObject = fun(_,We) -> process_obj(We,DispList) end,
Shapes2 = ?SLOW(gb_trees:map(ProcessObject, Shapes)),
St2 = St#st{shapes=Shapes2},
gl:deleteLists(DispList,1),
gl:popAttrib(),
EndTime = now(),
Seconds = timer:now_diff(EndTime,StartTime)/1.0e6,
VidCard = gl:getString(?GL_RENDERER),
io:fwrite("OpenGL AmbOcc time: ~.1fs (~s)\n", [Seconds,VidCard]),
St3 = create_ambient_light(St2),
St3.

process_obj(We, _) when ?IS_ANY_LIGHT(We) ->
case We#we.name =/= "Ambient" of
true -> We#we{perm=[]};
false -> We
end;
process_obj(We, DispList) ->
#we{es=Etab,vp=Vtab,name=Name} = We,
io:fwrite("Processing: ~s\n", [Name]),
GetColor = fun(Key,_Val) ->
Eye = wings_vertex:pos(Key,We) ,
LookAt = wings_vertex:normal(Key,We),
get_ao_color(Eye,LookAt,DispList)
end,
VertexColors = array:sparse_map(GetColor, Vtab),
SetColor = fun(_Key,Val) ->
#edge{vs=Va,ve=Vb} = Val,
Color1 = array:get(Va,VertexColors),
Color2 = array:get(Vb,VertexColors),
Val#edge{a=Color1,b=Color2}
end,
Etab1 = array:sparse_map(SetColor, Etab),
We#we{mode=vertex,es=Etab1}.

make_disp_list(St) ->
#st{shapes=Shapes} = St,
GetAllFaces = fun(_Key,Val) ->
case ?IS_ANY_LIGHT(Val) of
true ->
[];
false ->
Fs = gb_trees:to_list(Val#we.fs),
[wings_face:vertex_positions(Face, Val) || {Face,_} <- Fs]
end
end,
AddPolygons = fun(RawFs2) ->
ProcessVert = fun(Vert) ->
{X,Y,Z} = Vert,
gl:vertex3f(X,Y,Z)
end,
ProcessFace = fun(Face) ->
gl:'begin'(?GL_POLYGON),
lists:foreach(ProcessVert, Face),
gl:'end'()
end,
lists:foreach(ProcessFace, RawFs2)
end,
RawFs = gb_trees:map(GetAllFaces, Shapes),
AllRawfs = lists:append(gb_trees:values(RawFs)),
DispList = gl:genLists(1),
gl:newList(DispList, ?GL_COMPILE),
AddPolygons(AllRawfs),
gl:endList(),
DispList.

get_ao_factor(Buffer) ->
Data = binary_to_list(Buffer),
NumWhitePixels = length([Val || Val <- Data, Val==255]),
Samples = 0.75*length(Data),
Misses = -0.25*length(Data) + NumWhitePixels,
Factor = Misses/Samples,
Factor.

read_frame() ->
Hemirez = 64, % Must be even and/or power-of-two
W = H = Hemirez,
Buffer = sdl_util:alloc(W*H, ?GL_UNSIGNED_BYTE),
gl:readPixels(0,0, W,H, ?GL_LUMINANCE, ?GL_UNSIGNED_BYTE, Buffer),
ImageBin = sdl_util:getBin(Buffer),
get_ao_factor(ImageBin).

setup_gl() ->
gl:clearColor(1,1,1,0), % Sky Color
gl:color4f(0,0,0,1), % Obj Color
gl:shadeModel(?GL_FLAT),
gl:disable(?GL_LIGHTING).

get_ao_color(Eye, Lookat, DispList) ->
gl:drawBuffer(?GL_AUX0),
gl:clear(?GL_COLOR_BUFFER_BIT),
render_hemicube(Eye, Lookat, DispList),
gl:readBuffer(?GL_AUX0),
Factor = read_frame(),
{Factor,Factor,Factor}.

get_up_right(Lookat) ->
Up0 = {0.0,0.0,1.0},
UpN = e3d_vec:neg(Up0),
case (Lookat == Up0) or (Lookat == UpN) of
true ->
{_LookatX,_LookatY,LookatZ} = Lookat,
case LookatZ of
1.0 ->
Up = {0.0,+1.0,0.0};
_ ->
Up = {0.0,-1.0,0.0}
end,
Right = {1.0,0.0,0.0};
false ->
Up1 = {0.0,0.0,1.0},
Right = e3d_vec:cross(Up1, Lookat),
Up = e3d_vec:cross(Lookat, Right)
end,
{Up,Right}.

render_hemicube(Eye, Lookat, DispList) ->
{Up,Right} = get_up_right(Lookat),
Hemirez = 64, % Must be even and/or power-of-two
P1 = trunc(Hemirez * 0.00),
P2 = trunc(Hemirez * 0.25),
P3 = trunc(Hemirez * 0.50),
P4 = trunc(Hemirez * 0.75),
W = P3, H = P2,
EpL = e3d_vec:add(Eye, Lookat),
EpR = e3d_vec:add(Eye, Right),
EmR = e3d_vec:sub(Eye, Right),
EmU = e3d_vec:sub(Eye, Up),
EpU = e3d_vec:add(Eye, Up),
LookatN = e3d_vec:neg(Lookat),
render_view(Eye, EpL, Up, [-1,1,-1,1], [P2,P2,W,W], DispList), % Center
render_view(Eye, EpR, Up, [ 0,1,-1,1], [P1,P2,H,W], DispList), % Right
render_view(Eye, EmR, Up, [-1,0,-1,1], [P4,P2,H,W], DispList), % Left
render_view(Eye, EmU, Lookat, [-1,1, 0,1], [P2,P1,W,H], DispList), % Down
render_view(Eye, EpU, LookatN, [-1,1,-1,0], [P2,P4,W,H], DispList). % Up

render_view(Eye, Lookat, Up, Frustum, Viewport, DispList) ->
Near = 0.01,
Far = 100.0,
{Ex,Ey,Ez} = Eye,
{Dx,Dy,Dz} = Lookat,
{Ux,Uy,Uz} = Up,
[L,R,B,T] = [I*Near || I <- Frustum],
[X,Y,W,H] = Viewport,
gl:matrixMode(?GL_PROJECTION),
gl:loadIdentity(),
gl:frustum(L,R, B,T, Near,Far),
gl:matrixMode(?GL_MODELVIEW),
gl:loadIdentity(),
glu:lookAt(Ex,Ey,Ez, Dx,Dy,Dz, Ux,Uy,Uz),
gl:viewport(X,Y, W,H),
gl:callList(DispList).

create_ambient_light(St) ->
wings_pref:set_value(scene_lights, true),
SceneLights = wings_light:export(St),
case proplists:is_defined("Ambient", SceneLights) of
true ->
St;
false ->
White = {1.0,1.0,1.0,1.0},
Lights = [{"Ambient",[{opengl,[{type,ambient},{ambient,White}]}]}],
wings_light:import(Lights,St)
end.

0 comments on commit c34833f

Please sign in to comment.