a call for opinions - drawing grass

Started by
26 comments, last by Norman Barrows 7 years, 3 months ago

D3D9 supports "real" instancing:

i think that may be the "dx9 instancing" i was referring to in my other thread. the stuff with buffers looks familiar, and its shader based, which means i would not have tried it before when i first learned of it long ago.

but that means i can write some (dx9) shader code to do the instancing, right? and use that alongside the fixed function pipeline? IE my draw savanna grass routine calls a shader instead of DIP ? and everything else is still fixed function, and they work together? or is it an one or the other situation, shader or fixed function code in one scene, but not both?

can you mix (or switch between) dx9 fixed function and dx9 shaders in a single scene?

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

Advertisement

IIRC DX9 instancing is implemented using a stream divisor frequency. Nothing to do with shaders AFAIR.

edit - also that is alot of draw calls.... and you were getting 60fps? Was that on your new machine with the 1080 or the old machine?

edit2- now that I think about it I really don't remember the interplay between the FF pipeline and instancing... so I might be wrong.

-potential energy is easily made kinetic-

You can mix shaders with non-shader draw calls. They both go through DrawIndexedPrimitive(). It's the setup you do before the draw call that's different (i.e. you call SetVertexShader() and SetPixelShader() to activate shaders, which you can set back to NULL again to go back to non-shader mode).

edit - also that is alot of draw calls.... and you were getting 60fps? Was that on your new machine with the 1080 or the old machine?

60fps? oh, heavans no! <g>

here's the breakdown in brief:

terrain chunks are 300 feet across (~ 100m)

i draw the player's chunk, and the 8 surrounding chunks.

chunks are skipped if off the edge of the world map, or are 100% behind the player, based on player yr. i found it interesting that N-S-E-W chunks only have a 90 degree arc where they are not visible, but diagonal chunks have a 180 degree arc. this is because the player can be anywhere in their chunk, looking in any direction.

a chunk is a list of renderables (Zdrawinfo structs with params for a draw call), with a database styte index that lists them in order by texture. the structs are in an un-ordered arrray. the index is an ordered array of ints (struct indices) sorted on texture.

chunks are drawn one at a time, with each chunk's renderables drawn in order by texture alone. to date this has been sufficiently fast that i don't even bother sorting on mesh or near to far.

for each texture in the chunk, it sets the texture, then draws all the meshes that use it. it sets the mesh, cull, clamp, material, etc, and then calls DIP.

my wrapper API for dx9 includes state managers for things like texture, mesh, cull, clamp, alphatest, etc.

the far plane is set to 1000 feet for the pass that draws grass.

trivial range rejection cliprange is also set to 1000 feet. the player can control clip range for about a dozen different things, including grass.

clipping consists of a trivial range rejection based on bbox distance to the camera, and frustum cull (b-spheres vs planes), for each mesh drawn.

these are the kind of numbers i'm getting on the i7-6700K + gtx1080

one plant every 3 feet, 10,000 plants per chunk, 90,000 plants total before clipping, 60 fps rock solid all day long. but up to a meter between adjacent plants when drawn to scale or even a bit oversized. don't recall the number visible offhand, maybe 3000.

one plant every 2 feet, 22,500 plants per chunk, 202,500 plants total before clipping, about 54-56 fps. again, i don't recall the number visible offhand, perhaps 10k or so. if you really want to know the numbers , i can always kick them back up and tell you what they are. it only takes 15 seconds to make a code change on this new PC. just 20 seconds + edit time from quit game to running game again with recompiled changes. beats the hell out of 3-4 minutes on the old dual core 1.3Ghz PC..

one plant every foot, 90,000 plants per chunk, 810,000 pants total before clipping, about 15 fps. ~98,000 plants visible. i hit 101K+ draw calls total, with about 3000 of them going towards everything except the grass.

as the number of plants increases, the max number of meshes per chunk must increase (from 15K to 95K), and the max number of chunks in the chunk cache must decrease (from 90 to 30) to keep from running out of ram.

each chunk uses 4 different textures for grass. total textures for a chunk is on the order of one to two dozen. 4 ground, 2 tree trunk, 2 tree leaf, a couple rocks, maybe a berry bush or fruit tree, or water, and the grass plants, that's about it.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

You can mix shaders with non-shader draw calls.

cool. that's the one thing i wan't sure about at first.

looks like this won't turn out to be too much of a big deal to implement.

OTOH, i still have yet to get my hands on any usable shader code examples. i assume the ones in the dx9 samples will get me started, and the shaders used by the skinned mesh code work just fine, so i can always just use them as a guide.

i plant to keep it as simple as possible: set stream, set shaders, set constants, DIP, set fvf when done. single purpose specialty shaders just for special effects like animated grass. no effects files. pre-compiled at dev time, and loaded once a program start.

thanks everyone for all for the help!

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

right now i've been doing this with one plant per draw call, so i can rotate them around x and z to make them sway in the wind.

I just realized by plant do you mean a model or a billboard?

Also how are you spawning grass? Are they placed manually or spawned with some sort of coverage map?

edit - also if a model how many triangles? And if billboard how many billboard per plant?

-potential energy is easily made kinetic-

Nothing grass-related to add here but I would say to Norman that I put off moving to using Shaders in DX 9 for a long time myself, but when I finally bit the bullet, I never looked back.

It is actually pretty trivial to replicate the basic FFP with a shader. I now don't use the FFP at all, even if I could do what I want with it.

And the sudden rush of possibilities (even with DX 9 and it's limited shader types which I am also still using) is very exhilarating.

Do it, man!
I just realized by plant do you mean a model or a billboard?

3d models, anything from 3 quads up to 20 or so. most are twisted or subdivided and bent planes so no faces are coplanar. so "non-coplanar cards" yield pretty good results without resorting to true 3d models with a stem with volume, etc.

i tried billboards as impostors very early on (like 2-3 tears ago), but was unimpressed by the results. multiple billbards at differnt angles are required for asymmetric rock outcroppings. once i went to chunk based rendering on texture order, there was no need for impostoring until now perhaps. as one of my recent tests i tried 6 plants on a 10 foot wide 1 foot tall 2d billboard, at random locations and rotations. looks great at long distance and not from above - definitely a LOD possibilty there, but can only really rotate around x for swaying in the wind. shaders would allow a bit more deformation of the upper verts.

Also how are you spawning grass? Are they placed manually or spawned with some sort of coverage map?

when a terrain chunk is generated, it generates the grass. it uses a pair of loops that start with a plant every 1, 2 or 3 feet, then uses that x,z location to lookup jitter, rotation, mesh, texture, and scale, based on a generic random pattern map. so for example, get_savanna_grass_mesh(x,z) mods x and z by 100 to map them into the range 0-99, then uses them as the indices into a 100x100 array of random ints with values from 0 thru 100 (the generic random pattern map). the result is then sent thru an if then else statement, if < 26, use mesh #1, else if < 51 use mesh #2, etc. the same idea for rotation: yr = result*0.0628 radians. jitter is (result-50) / 25. IE +/- 2 feet. scale is also based on the random pattern map. this means one generic random pattern map can be used to determine the jitter, rotation, mesh, texture, and scale of all savanna plants in the game. the same generic pattern map is used for drawing tall grass terrain as well. things like trees, fruit trees, berry bushes, and rocks each have their own dedicated "plant maps" to determine location, scale, texture, mesh, etc. plant maps was the original way of doing things (800x800 sparse matrix with 1300 plants). the generic random pattern map is the new way. i even use it as the basis for a heightmap function to generate canyon ground meshes.

also if a model how many triangles? And if billboard how many billboard per plant?

i'vre tested it with models with just 3 twisted quads, and models with up to 20 non-coplanar quads. triangle count (numverts) is really not an issue. a bit more of a hit than tri size (rasterization costs), but still nothing compared to number of draw calls, or figuring out what to draw (iterating over massive numbers of possibly renderable objects). with billboard tests its always just one quad per plant, or 6 plant images on one quad in my latest test. iteraing can even be more expensive than draw calls actually made, if the number of possibly renderable objects is very high. IE if i try to draw close to a million plants, and only end up drawing 90,000 its almost as slow / even slower than just trying to draw fewer plants but ending up drawing about the same number.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

And the sudden rush of possibilities (even with DX 9 and it's limited shader types which I am also still using) is very exhilarating. Do it, man!

LOL!

yeah, i know, i've been putting it off for too long. but i'm trying to build games, not graphics engines. and as you say, the power of shaders is intoxicating at the very least - especially for someone like me who's done stuff like hand tuned assembly code blitters and my own software renderer. you can do a lot of cool stuff when you can code down to the metal. <g>. i may go effects crazy. good thing caveman isn't a sci-fi game or i might never finish the final graphics! <g>.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

i'vre tested it with models with just 3 twisted quads, and models with up to 20 non-coplanar quads. triangle count (numverts) is really not an issue. a bit more of a hit than tri size (rasterization costs), but still nothing compared to number of draw calls, or figuring out what to draw (iterating over massive numbers of possibly renderable objects). with billboard tests its always just one quad per plant, or 6 plant images on one quad in my latest test. iteraing can even be more expensive than draw calls actually made, if the number of possibly renderable objects is very high. IE if i try to draw close to a million plants, and only end up drawing 90,000 its almost as slow / even slower than just trying to draw fewer plants but ending up drawing about the same number.

Actually modern GPU's work on either 32(nvidia) or 64(amd) vertices at a time. And AMD GPU's for some reason like a minimum call size of 256 vertices. So if you are drawing with less than that per call or per instance you are underutilizing the GPU.

when a terrain chunk is generated, it generates the grass. it uses a pair of loops that start with a plant every 1, 2 or 3 feet, then uses that x,z location to lookup jitter, rotation, mesh, texture, and scale, based on a generic random pattern map. so for example, get_savanna_grass_mesh(x,z) mods x and z by 100 to map them into the range 0-99, then uses them as the indices into a 100x100 array of random ints with values from 0 thru 100 (the generic random pattern map). the result is then sent thru an if then else statement, if < 26, use mesh #1, else if < 51 use mesh #2, etc. the same idea for rotation: yr = result*0.0628 radians. jitter is (result-50) / 25. IE +/- 2 feet. scale is also based on the random pattern map. this means one generic random pattern map can be used to determine the jitter, rotation, mesh, texture, and scale of all savanna plants in the game. the same generic pattern map is used for drawing tall grass terrain as well. things like trees, fruit trees, berry bushes, and rocks each have their own dedicated "plant maps" to determine location, scale, texture, mesh, etc. plant maps was the original way of doing things (800x800 sparse matrix with 1300 plants). the generic random pattern map is the new way. i even use it as the basis for a heightmap function to generate canyon ground meshes.

As you said in your original post if you could merge some of the plants together you'd be better off for the aforementioned reason.

You already know you have to decrease your drawcall count... the only thing I'd look into is billboards for further away grass.

-potential energy is easily made kinetic-

This topic is closed to new replies.

Advertisement