Edit Nov 12, 2013
It seems that not only did angular change a little in 1.2, but that there is an even better method. I've created two filters. I tried to combine them into one but got digest errors. Here are the two filters:
.filter("mySecondFilter", function(){
    return function(input, row, numColumns){
        var returnArray = [];
        for(var x = row * numColumns; x < row * numColumns + numColumns; x++){
            if(x < input.length){
                returnArray.push(input[x]);                    
            }
            else{
                returnArray.push(""); //this is used for the empty cells
            }
        }
        return returnArray;   
    }
})
.filter("myFilter", function(){
    return function(input, numColumns){
        var filtered = [];
        for(var x = 0; x < input.length; x++){
            if(x % numColumns === 0){
                filtered.push(filtered.length);
            }
        }
        return filtered;
    }
});
And now the html will look like this:
<table border="1">
     <tr data-ng-repeat="rows in (objects | myFilter:numColumns)">
          <td data-ng-repeat="column in (objects | mySecondFilter:rows:numColumns)">{{ column.entry }}</td>
     </tr>  
</table>
jsFiddle: http://jsfiddle.net/W39Q2/
Edit Sept 20, 2013
While working with lots of data that needed dynamic columns I've come up with a better method.
HTML: 
<table border="1">
    <tr data-ng-repeat="object in (objects | myFilter:numColumns.length)">
        <td data-ng-repeat="column in numColumns">{{ objects[$parent.$index * numColumns.length + $index].entry }}</td>
    </tr>  
</table>
Javascript:
$scope.objects = [ ];
for(var x = 65; x < 91; x++){
    $scope.objects.push({
        entry: String.fromCharCode(x)
    });
}
$scope.numColumns = [];
$scope.numColumns.length = 3;
New Filter:
.filter("myFilter", function(){
    return function(input, columns){
        var filtered = [];
        for(var x = 0; x < input.length; x+= columns){
             filtered.push(input[x]);   
        }
        return filtered;
    }
});
This allows it to be dynamic. To change the columns just change the numColumns.length. In the js fiddle you can see I've wired it up to a dropdown.
jsFiddle: http://jsfiddle.net/j4MPK/
Your html markup would look like this:
<div data-ng-repeat="row in rows">
    <div data-ng-repeat="col in row.col">{{col}}</div>
</div>
And then you could make a variable in your controller like so:
$scope.rows = [
    {col: [ 1,2,3,4 ]},
    {col: [ 5,6,7 ]},
    {col: [ 9,10,11,12 ]}
]; 
This way, you can have any number of columns you want.
jsfiddle http://jsfiddle.net/rtCP3/39/
Edit I've modified the fiddle to now support having a flat array of objects:
jsfiddle: http://jsfiddle.net/rtCP3/41/
The html now looks like this:
<div class="row" data-ng-repeat="row in rows">
    <div class="col" data-ng-repeat="col in cols">
        {{objects[$parent.$index * numColumns + $index].entry}}
    </div>
</div>  
And then in the controller i have: 
$scope.objects = [
    {entry: 'a'},
    {entry: 'b'},
    {entry: 'c'},
    {entry: 'd'},
    {entry: 'e'},
    {entry: 'f'},
    {entry: 'g'},
    {entry: 'h'}    
];
$scope.numColumns = 3;
$scope.rows = [];
$scope.rows.length = Math.ceil($scope.objects.length / $scope.numColumns);
$scope.cols = [];
$scope.cols.length = $scope.numColumns;
The $scope.numColumns variable is used to specify how many columns you want in each row.
To handle dynamic array size changes, put a watch on the length of the array (not the whole array, that would be redundent)
$scope.numColumns = 3;  
$scope.rows = [];    
$scope.cols = [];    
$scope.$watch("objects.length", function(){
    $scope.rows.length = Math.ceil($scope.objects.length / $scope.numColumns);
    $scope.cols.length = $scope.numColumns;        
});
jsfiddle: http://jsfiddle.net/rtCP3/45/