PDA

View Full Version : Bitmap rendering is not always better



Jerryscript
November 12th, 2007, 11:12 AM
I'm still exploring 3d rendering in flash, and I've discovered that bitmap rendering is not always better. I was hoping using triangles to render textures would be faster than using the drawing API, but at least in the following example, it is almost twice as slow, and has more glitches.

Perhaps someone can point out an error I've made in the methods. Attached is an example, and here is the code used to render it, sorry for the hard coded values, they are mostly multiples of the Stage width/height :
MovieClip.prototype.createTerrain = function(){
prevTimer = getTimer(); // for rendering rates

// clear the previous rendering
fieldMap.fillRect(new flashGeomRect(0, 0, Stage.width, Stage.height), 0x00000000);
field.clear();

ty = ca * _3dData.yoffset - sa * (_3dData.startz + _3dData.stepAmount); // adjust camera view angle
tz = sa * _3dData.yoffset + ca * (_3dData.startz + _3dData.stepAmount); // adjust camera view angle
scaleRatioB = _3dData.focalLength / (_3dData.focalLength + tz); // set initial scale ratio for perspective rendering

for(z = _3dData.startz; z>=_3dData.startz - 650; z -= _3dData.stepAmount){ // loop through the rows, back to front rendering

ty = ca * _3dData.yoffset - sa * z; // adjust camera view angle
tz = sa * _3dData.yoffset + ca * z; // adjust camera view angle
scaleRatioA = _3dData.focalLength / (_3dData.focalLength + tz); // set the new scale ratio for perspective rendering

if(z == _3dData.startz){ // only need to grab these values at the very begining, subsequent segments can use previous values (point1)
displace = -(parseInt(displaceMap.getPixel((750 - _3dData.stepAmount + _3dData.xoffset) / 8, (z + _3dData.zoffset) / 8), 16))*_3dData.hoffset;
if(displace == undefined){ displace = 0; }
pixelArray[0].x = origin.x + (-250) * scaleRatioA;
pixelArray[0].y = origin.y + (ty + displace) * scaleRatioA;
}
startx = pixelArray[0].x; // store for use on next row
starty = pixelArray[0].y; // store for use on next row

// grab these values at the begining of each row, subsequent segments can use previous values (point4)
displace = -(parseInt(displaceMap.getPixel((750 - _3dData.stepAmount + _3dData.xoffset) / 8, (z + _3dData.zoffset + _3dData.stepAmount) / 8), 16))*_3dData.hoffset;
if(displace==undefined){ displace = 0; }
BX = origin.x + (-250) * scaleRatioB;
BY = origin.y + (ty + displace) * scaleRatioB;

pixelCount = 0;

for(x = -400; x<=400; x += _3dData.stepAmount){

if(z == _3dData.startz){ // only need to grab these values on the first row, subsequent rows can use previous values (point2)
displace = -(parseInt(displaceMap.getPixel((x + 1000 + _3dData.stepAmount + _3dData.xoffset) / 8, (z + _3dData.zoffset) / 8), 16))*_3dData.hoffset;
if(displace==undefined){ displace = 0; }
pixelArray[pixelCount + _3dData.stepAmount].x = origin.x + (x + _3dData.stepAmount) * scaleRatioA;
pixelArray[pixelCount + _3dData.stepAmount].y = origin.y + (ty + displace) * scaleRatioA;
}
nextX = pixelArray[pixelCount + _3dData.stepAmount].x; // store for use on next row
nextY = pixelArray[pixelCount + _3dData.stepAmount].y; // store for use on next row

// this is the only point that needs to be calculated for each segment every time (point3)
displace = -(parseInt(displaceMap.getPixel((x + 1000 + _3dData.stepAmount + _3dData.xoffset) / 8, (z + _3dData.zoffset + _3dData.stepAmount) / 8), 16))*_3dData.hoffset;
if(displace==undefined){ displace = 0; }
nextBx = origin.x + (x + _3dData.stepAmount) * scaleRatioB;
nextBy = origin.y + (ty + displace) * scaleRatioB;

// triangle transform: theMatrix = new flashGeomMatrix( (pointB._x - pointA._x) / 100 , (pointB._y - pointA._y) / 100 , (pointC._x - pointA._x) / 100 , (pointC._y - pointA._y) / 100 , pointA._x , pointA._y );

if(z < _3dData.startz){ // my first row is currently not using proper values, not sure why so I'll skip it till I figure it out
if(useBitmaps){
// grab the texture map color value, currently it's the same perlinNoise map as the displacemap, only green channel
gcolor = greenMap.getPixel((x + 1000 + _3dData.xoffset + _3dData.stepAmount / 2) / 8, (z + _3dData.zoffset + _3dData.stepAmount / 2) / 8) >> 8 & 0xff;

// draw the two triangles to make the segment
theMatrix = new flashGeomMatrix( (nextX - startx) / 100 , (nextY - starty) / 100 , (BX - startx) / 100 , (BY - starty) / 100 , startx , starty );
fieldMap.draw(triangles[gcolor], theMatrix);

theMatrix = new flashGeomMatrix( (nextX - nextBx) / 100 , (nextY - nextBy) / 100 , (BX - nextBx) / 100 , (BY - nextBy) / 100 , nextBx , nextBy );
fieldMap.draw(triangles[gcolor], theMatrix);
}else{
// use the drawing API to render the quad (same number of points as the two triangles above)
with(field){
gcolor = greenMap.getPixel((x + 1000 + _3dData.xoffset + _3dData.stepAmount / 2) / 8, (z + _3dData.zoffset + _3dData.stepAmount / 2) / 8);
lineStyle(1, 0x000000, 0);
beginFill(gcolor);
moveTo(startx, starty);
lineTo(nextX, nextY);
lineTo(nextBx, nextBy);
lineTo(BX, BY);
lineTo(startx, starty);
endFill();
}
}
}

// store/transfer point data for next segment
startx = nextX;
starty = nextY;
pixelArray[pixelCount].x = BX;
BX = nextBx;
pixelArray[pixelCount].y = BY;
BY = nextBy;
pixelCount += _3dData.stepAmount;

} // end x loop

// store remaining data for next row
pixelArray[pixelCount].x = nextBx;
pixelArray[pixelCount].y = nextBy;
scaleRatioB = scaleRatioA;
} // end z loop

// position cutout indicator on top down view
viewMap.mapBox._x = (750 + _3dData.xoffset) / 8;
viewMap.mapBox._y = (z + _3dData.zoffset) / 8;

renderRate.text = 'Rendered in ' + (getTimer() - prevTimer) + ' milliseconds';
updateAfterEvent();

}; // end rendering function

Attached is an example that that lets you move a viewing box around the color map by holding the mouse over the arrows, or by clicking on the map. I'm curious to see the rendering rates different systems get for each method, please post your results.

Note to Adobe -- Please give us a copyPixel routine using polygon shapes rather than rectangles!!!

senocular
November 12th, 2007, 11:34 AM
Did you really expect bitmaps to be faster than flat shaded shapes? Or am I not understanding the problem?

Templarian
November 12th, 2007, 11:46 AM
Note to Adobe -- Please give us a copyPixel routine using polygon shapes rather than rectangles!!!
You know how many times Sirisian has said this, its almost un countable

senocular
November 12th, 2007, 11:58 AM
You know how many times Sirisian has said this, its almost un countable

The reason its as fast as it is in the first place is because it doesn't have to account for that or other factors like scaling etc.

Jerryscript
November 12th, 2007, 12:12 PM
Did you really expect bitmaps to be faster than flat shaded shapes? Or am I not understanding the problem?
After reading various threads here and at flashkit about using bitmaps instead of movieclips for rendering, I was hoping bitmaps with transforms would be faster than using the drawing API. Re-reading the docs, I realized the transform is using the same rendering engine as the drawing API, so I understand the bottleneck. Just wishing there were a better solution!

Still curious as to the speeds people experience with both techniques on various systems?

senocular
November 12th, 2007, 12:21 PM
After reading various threads here and at flashkit about using bitmaps instead of movieclips for rendering, I was hoping bitmaps with transforms would be faster than using the drawing API.

Ah, that makes sense. That's actually a different situation. There you are replacing complicated vector graphics with a simpler bitmap. This prevents the need to redraw vectors for static imagery where instead a single bitmap "cache" of the graphics can be used instead. This doesn't apply to any imagery that animates or distorts (thereby changing the bitmap representation used to cache the visuals) or, in your case dynamically drawn graphics since you're still drawing the graphics every frame now also adding additional bitmap data.

In terms of the drawing API, including the bitmap will definitely present a hit. This not only because you are adding a bitmap fill to what would be a single color, but also because of the additional matrix math needed to fit that bitmap into your shapes.

That being said, there are definitely rooms for improvement (in terms of speed) for your code. For example, the more you can minimize dot references, the better. Also, try not to duplicate operations like (_3dData.startz + _3dData.stepAmount). Save that once to a variable and use that varibale in your calculations. The Flasm site has a bunch of additional tips that can help you optimize - or they did, I'm sure there's still a lot there ... http://flasm.sourceforge.net/


Still curious as to the speeds people experience with both techniques on various systems?

I was getting 19 on average with the flat, and 47 on average with the bitmap

Templarian
November 12th, 2007, 12:26 PM
The reason its as fast as it is in the first place is because it doesn't have to account for that or other factors like scaling etc.
I was referring to what I quoted.

Jerryscript
November 12th, 2007, 12:54 PM
Thanks for the info Senocular, I thought referencing variables from an object was faster. I will read and re-read various optimization threads and see if I can determine what bad assumptions I've made!

senocular
November 12th, 2007, 01:11 PM
Thanks for the info Senocular, I thought referencing variables from an object was faster. I will read and re-read various optimization threads and see if I can determine what bad assumptions I've made!

Local variables are a lot faster :) This especially with compiler optimizations that will reduce local variable names to 2-characters further helping performance without sacraficing code legibility (so don't try to speed up local variables with smaller names; it won't really work and your code will be harder to read, though it can still be useful for object properties). Instance naming aside, they also perform better.

Sirisian
November 12th, 2007, 01:50 PM
Note to Adobe -- Please give us a copyPixel routine using polygon shapes rather than rectangles!!!
You know how many times Sirisian has said this, its almost un countable
Just use shapes. Also I'm not sure what jerryscript is saying. Rendering bitmaps would only be slightly faster for non-solid colored bitmaps I'd imagine. I mean even using gradients and such. Meh, I haven't researched it much.

Jerryscript
November 12th, 2007, 10:26 PM
Just use shapes. Also I'm not sure what jerryscript is saying. Rendering bitmaps would only be slightly faster for non-solid colored bitmaps I'd imagine. I mean even using gradients and such. Meh, I haven't researched it much.
In this example, when referring to bitmap rendering, I'm using a bitmap triangle and using a transform matrix to map the vertices to the desired points. I create a separate triangle bitmap for each color to avoid having to use the colorTransform as well.

When using the drawing API, I'm using lineTo and beginFill (will upgrade it to using gradient fills for interpolation soon).

The results are that the drawing API is much faster than transforming bitmaps.

Sirisian
November 12th, 2007, 10:49 PM
Try doing a double pass. Render using bitmapFill using quads. and draw a terrain texture down. Then using beginFill over it, render the gradients (like shadows, raycasting ftw).

Rendering triangles seems pointless for something like this. If you take a rectangle and skew and rotate it correctly it will take on it's 3D shape which is much more efficient than triangles.

http://drawk.wordpress.com/2007/07/15/3d-flash-perlin-noise-terrains-with-as3-and-sandy/

Jerryscript
November 13th, 2007, 12:43 AM
If you take a rectangle and skew and rotate it correctly it will take on it's 3D shape which is much more efficient than triangles.

http://drawk.wordpress.com/2007/07/15/3d-flash-perlin-noise-terrains-with-as3-and-sandy/

You cannot skew a rectangle into a 3d shape. If you notice in the examples you posted, they are cutting the rectangle into two triangles, just like I do in my example above. The only difference is they are rendering wireframe, while I'm rendering a colorMap created from the same perlinNoise as the heightMap.