#Instanced Grass Rendering

1 messages · Page 1 of 1 (latest)

tight dragon
#

@soft sigil We're flooding the channel, so let's move the discussion here.

#

First time writing a compute shader?

soft sigil
#

compute no

#

random hlsh yes

tight dragon
#

Ok. We can move onto the compute shader.

soft sigil
#
#ifndef PROCEDURL_INSTANCE_INCLUDED
#define PROCEDURL_INSTANCE_INCLUDED

StructuredBuffer<float3> _GrassPositions;
StructuredBuffer<int> _GrassIndices;
 
#if UNITY_ANY_INSTANCING_ENABLED
    void vertInstancingSetup() {}
#endif
 
void GetInstancingPos_float(in float3 PositionWS, out float3 Out)
{
    Out = 0;
#if UNITY_ANY_INSTANCING_ENABLED
    int grassIndex = _GrassIndices[unity_InstanceID];
    Out = PositionWS + _GrassPositions[grassIndex];
#endif
}
#endif```
#

this is what i got

tight dragon
#

If there are no errors in Shader Graph, we can move on to the compute shader

soft sigil
#

well, nothing in console

#

so ig no errors rn

tight dragon
#

It would show it in the graph editor, next to the Custom Function node

soft sigil
#

no, no excl marks

#

feels good

#

tho material is invisible in the scene

tight dragon
#

You can replace Out = 0 with Out = PositionWS.

#

That should fix previews.

soft sigil
#

on 000 it's alr

#

on x 1 it's weird

tight dragon
#

The world position is probably getting applied twice. This won't be a problem in the instanced draw call.

soft sigil
#

alr

#

it feels like object also moves on z

#

oh no it's just the rotation shit

#

yeah it's just twiced

#

actualy it's 4x, not 2x

tight dragon
#

For the compute shader, to keep things simple and try to get something working quickly, let's ignore frustum culling for now. So it's just filling the fullRes and lod buffers based on their distance from the camera.

soft sigil
#

yup

tight dragon
#

Do you need any help with that or can you get started with that?

soft sigil
#

well, i need to refresh some stuff

#

got those three

tight dragon
#

If you want the data to be set up the way I suggested, then the append buffers should be int.

#

You can also do float3 if you want. That saves one buffer read in the grass shader, but uses 3 times more memory.

soft sigil
#

alr int

#

i don't want grass eat 10% of gpu memory xD

tight dragon
#

You may also want to add more data later, like an up direction, so the grass points up from the ground normal, so the int will save even more memory there.

soft sigil
#

done

tight dragon
#

Ok, since this is working with a one dimensional array, I would suggest changing numthreads to (16, 1, 1) and id to uint.

soft sigil
#

done

tight dragon
#

The rest should be straightforward.

soft sigil
#

now i loop through the positions

#

and get cam distance

tight dragon
#

No, the threads already take care of that for you. id is the index you want to read from.

#

CSMain will be called for each grass position.

soft sigil
#

for real

#

i swear i asked gpt bout that

#

and it returned a loop

tight dragon
#

There may be some cases where a loop is necessary, but not in this case.

soft sigil
#

imagine for every grasspos looping through the entire position set

#

which is nearly 100k

#

so it's 100k squared operations

#

wasted

#

so, it's smth like:

#
void CSMain (uint id : SV_DispatchThreadID)
{
    if (distance(positions[id], camPosition) < 25)
    {
        fullRes.Append(id);
    }
    else
    {
        lod.Append(id);
    }

}
#

i can move 25 to a variable later

#

am i right?

#

grassCompute.Dispatch(16, 1, 1, 1); also this is what i should write?

#

@tight dragon

cobalt hazel
#

NUM_THREAD_X = 16 in your case

#

Then grassCompute.Dispatch(numDispatchesX, 1, 1);

soft sigil
#

it accepts 4 args tho

#

groupZ which idk about

#
grassCompute.Dispatch(Mathf.CeilToInt(positions.count / 16), 1, 1, 1);```
tight dragon
#

The kernel index comes first

soft sigil
#

oh fr

#

so kernel index is 0

#

or 1

soft sigil
#

it's FindKernel yeah

cobalt hazel
#

Or simply count #pagma kernel count from top starting from 0

#

in your compute shader source

soft sigil
#
 grassCompute.Dispatch(grassCompute.FindKernel("CSMain"), Mathf.CeilToInt(positions.count / 16), 1, 1);```
#

bruh i wrote those things like month ago

#

and forgot everything already

#

so these 3 buffers look like this

#
fullResBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Append | GraphicsBuffer.Target.Structured, density, 4);
lodBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Append | GraphicsBuffer.Target.Structured, density, 4);```
#

first has stride 12 cuz float3

#

and other two have 4 cuz int

#

now what about this code

#
public void Update() {
    fullResBuffer.SetCounterValue(0);
    lodBuffer.SetCounterValue(0);
    grassCompute.SetFloats("camPosition", cam.transform.position.x, cam.transform.position.y, cam.transform.position.z);
    grassCompute.Dispatch(grassCompute.FindKernel("CSMain"), Mathf.CeilToInt(positions.count / 16), 1, 1);
    GraphicsBuffer.CopyCount(fullResBuffer, gBuffer, 0);
    Graphics.RenderMeshIndirect(rparams, mesh, gBuffer);
    GraphicsBuffer.CopyCount(lodBuffer, gBuffer, 0);
    Graphics.RenderMeshIndirect(rparams, lodMesh, gBuffer);
}```
#

what do i read and where

tight dragon
soft sigil
#

forgor

#

set

#
grassCompute.SetBuffer(kernelId, "positions", positions);
grassCompute.SetBuffer(kernelId, "fullRes", fullResBuffer);
grassCompute.SetBuffer(kernelId, "lod", lodBuffer);```
tight dragon
#

Ok. The draw call args buffer is still incorrectly initialized. CopyCount should be setting the 4th byte offset, not the 0th. And you're not setting the index count, which is the first argument.

soft sigil
tight dragon
#

dstOffsetBytes determines where in the buffer it gets copied to. There are 5 uints in the arguments buffer, 4 bytes each. The first one is indexCountPerInstance, the triangle count of the mesh. The second one is instanceCount.

#

The remaining three are not needed for this and can remain as zero.

soft sigil
#

so well, i go for

#

GraphicsBuffer.CopyCount(lodBuffer, gBuffer, 4);

soft sigil
#

and then i need gBuffer.SetData()?

tight dragon
#

The docs for RenderMeshIndirect has an example of that.

soft sigil
#

in case i have 4 planes

#

which means i have 8 triangles

#

indexCountPerInstance is 8

#

right?

#
void Update()
{
    if (lastDensity != density) UpdateDensity(density);

    fullResBuffer.SetCounterValue(0);
    lodBuffer.SetCounterValue(0);

    grassCompute.SetFloats("camPosition", cam.transform.position.x, cam.transform.position.y, cam.transform.position.z);

    grassCompute.Dispatch(kernelId, Mathf.CeilToInt(positions.count / 16), 1, 1);

    GraphicsBuffer.CopyCount(fullResBuffer, gBuffer, 4);
    var cmds = new GraphicsBuffer.IndirectDrawIndexedArgs[fullResBuffer.count]; 
    for (int i = 0; i < fullResBuffer.count; i++)
    {
        cmds[i].indexCountPerInstance = 8;
        cmds[0].instanceCount = 1;
    }
    gBuffer.SetData(cmds);
    Graphics.RenderMeshIndirect(rparams, mesh, gBuffer, lodBuffer.count);

    GraphicsBuffer.CopyCount(lodBuffer, gBuffer, 4);
    cmds = new GraphicsBuffer.IndirectDrawIndexedArgs[lodBuffer.count];
    for (int i = 0; i < lodBuffer.count; i++)
    {
        cmds[i].indexCountPerInstance = 8;
        cmds[0].instanceCount = 1;
    }
    gBuffer.SetData(cmds);
    Graphics.RenderMeshIndirect(rparams, lodMesh, gBuffer, lodBuffer.count);
}```
#

that's what i got but i'm unsure

#

what i don't get is where are positions written

#

are they part of a command or smth

tight dragon
tight dragon
tight dragon
#

And instance count should definitely not be 1, that's how many grass instances you want to render

#

You have to call SetData first, because it overwrites instance count as well, then write over it with the real count using CopyCount.

soft sigil
#

alr i fixed the triangle count

#

and the commands

#
 grassCompute.Dispatch(kernelId, Mathf.CeilToInt(positions.count / 16), 1, 1);
 var cmds = new GraphicsBuffer.IndirectDrawIndexedArgs[1];
 cmds[0].indexCountPerInstance = mesh.GetIndexCount(0);
 cmds[0].instanceCount = (uint)fullResBuffer.count;
 gBuffer.SetData(cmds);
 GraphicsBuffer.CopyCount(fullResBuffer, gBuffer, 4);
 Graphics.RenderMeshIndirect(rparams, mesh, gBuffer, gBuffer.count);
 cmds[0].instanceCount = (uint)lodBuffer.count;
 gBuffer.SetData(cmds);
 GraphicsBuffer.CopyCount(lodBuffer, gBuffer, 4);
 Graphics.RenderMeshIndirect(rparams, lodMesh, gBuffer, gBuffer.count);```
#

is that right?

soft sigil
#

and what's the other buffer

tight dragon
soft sigil
#

well

#

i tested some parts and got strange error

#

one sec

tight dragon
soft sigil
#
var results = new NativeArray<RaycastHit>(density, Allocator.TempJob);
var commands = new NativeArray<RaycastCommand>(density, Allocator.TempJob);
for (int i = 0; i < density; i++)
{
    Vector3 randomPoint = new Vector3(Random.Range(-transform.localScale.x / 2, transform.localScale.x / 2), 4096, Random.Range(-transform.localScale.z / 2, transform.localScale.z / 2)) + transform.position;
    Debug.Log(randomPoint);
    commands[i] = new RaycastCommand(randomPoint, Vector3.down, QueryParameters.Default, 8192);
}
JobHandle handle = RaycastCommand.ScheduleBatch(commands, results, 1, density, default);
handle.Complete();
commands.Dispose();
List<Vector3> coords = new();
foreach (var hit in results)
{
    if (hit.collider != null && hit.collider.gameObject.TryGetComponent<GrassSupport>(out _))
    {
        coords.Add(hit.point);
    }
}
results.Dispose();
Debug.Log(coords.Count);```
#

i get a good set of positions

#

but when i schedule the thing and read the results, it's empty

#

coords.count returns 0

#

and also this warning

#

also debugged results.length and it's proper

#

but for some reason raycasts from like (2, 4096, 0) straight down with length 8192 return a null, but it's just impossible

#

since plane has collider

tight dragon
soft sigil
#

oh

#
var commands = new NativeArray<RaycastCommand>(density, Allocator.TempJob);
for (int i = 0; i < density; i++)
{
    commands[i] = new RaycastCommand(RandomPoint(), Vector3.down, QueryParameters.Default, 8192);
}
JobHandle handle = RaycastCommand.ScheduleBatch(commands, results, 1, 1, default);```
#

it workz

soft sigil
#
 var cmds = new GraphicsBuffer.IndirectDrawIndexedArgs[1];
 cmds[0].indexCountPerInstance = mesh.GetIndexCount(0);
 cmds[0].instanceCount = (uint)fullResBuffer.count;
 gBuffer.SetData(cmds);
 GraphicsBuffer.CopyCount(fullResBuffer, gBuffer, 4);
 Graphics.RenderMeshIndirect(rparams, mesh, gBuffer, gBuffer.count);
 cmds[0].indexCountPerInstance = mesh.GetIndexCount(0);
 cmds[0].instanceCount = (uint)lodBuffer.count;
 gBuffer.SetData(cmds);
 GraphicsBuffer.CopyCount(lodBuffer, gBuffer, 4);
 Graphics.RenderMeshIndirect(rparams, lodMesh, gBuffer, gBuffer.count);```
#

is this correct

#

like no more errors in this part

#

firstly drawing fullres and then lod

tight dragon
#

No, you removed the CopyCount

#

.count is the max size. Only the GPU knows the actual count after the compute shader dispatch. CopyCount copies the count from the internal count in the buffer into the args buffer, all in GPU memory. The CPU never finds out the actual count, that's the point of Indirect draw calls.

#

Sorry, I missed it in the code.

#

But there's no reason to set instanceCount to anything there, because it gets overwritten immediately by CopyCount.

#

I don't like gBuffer.count being passed as the command count in RenderMeshIndirect. It should always be 1 here.

#

You're using the full mesh index count for the lod indexCountPerInstance

soft sigil
#
fullResBuffer.SetCounterValue(0);
lodBuffer.SetCounterValue(0);

grassCompute.SetFloats("camPosition", cam.transform.position.x, cam.transform.position.y, cam.transform.position.z);
grassCompute.Dispatch(kernelId, Mathf.CeilToInt(positions.count / 16), 1, 1);

var cmds = new GraphicsBuffer.IndirectDrawIndexedArgs[1];

cmds[0].indexCountPerInstance = mesh.GetIndexCount(0);
gBuffer.SetData(cmds);
GraphicsBuffer.CopyCount(fullResBuffer, gBuffer, 4);
Graphics.RenderMeshIndirect(rparams, mesh, gBuffer, 1);

cmds[0].indexCountPerInstance = lodMesh.GetIndexCount(0);
gBuffer.SetData(cmds);
GraphicsBuffer.CopyCount(lodBuffer, gBuffer, 4);
Graphics.RenderMeshIndirect(rparams, lodMesh, gBuffer, 1);```
#

is it proper now?

#

the entire code takes like 4ms to complete

#

is there a way to get how many ms Dispatch takes each frame

tight dragon
soft sigil
#

Alr, so the last part is drawing each grass on its own place

#

Ig that goes after the dispatch

#

by increasing number of X threads i get some boost, right?

#

like if there are 64 threads instead of 16/8

tight dragon
soft sigil
#

alr

#

how do i send the data tho

#

rp.matProps?

tight dragon
#

What data to where

soft sigil
#

position data to all 100k grasses

#

rparams.SetMatrixArray("_ObjectToWorld", data); or smth

tight dragon
#

You have to assign a new MaterialPropertyBlock, which you should only create once.

soft sigil
#

matPropBlock = new MaterialPropertyBlock();

#

in start

tight dragon
#

Then you can do rparams.SetBuffer("_GrassPosition", positions) and rparams.SetBuffer("_GrassIndices", fullResBuffer/lodBuffer)

soft sigil
#

you mean |

#

or /

tight dragon
#

You will have to set the _GrassIndices to different buffers depending on the draw call.

soft sigil
#

oh

#

do i do rparams.matProps = matPropBlock;?

tight dragon
#

Yes, and it would be rparams.matProps.SetBuffer, my mistake.

#

Or matPropBlock.SetBuffer

soft sigil
#

i do the first buffer set only in beginning

#

and second/third each frame?

tight dragon
#

That's fine

soft sigil
#

it's a question

#

xD

#

sry

tight dragon
#

Yes, that's fine

soft sigil
#

so

        rparams = new RenderParams(material);
        matPropBlock = new MaterialPropertyBlock();
        rparams.matProps = matPropBlock;
        matPropBlock.SetBuffer("_GrassPosition", positions);```
tight dragon
#

Make sure the buffer names match the ones used in the hlsl file.

soft sigil
#

yeah did it

soft sigil
# soft sigil so ```csharp rparams = new RenderParams(material); matPropBlock ...

and then in Update

grassCompute.Dispatch(kernelId, Mathf.CeilToInt(positions.count / 16), 1, 1);
var cmds = new GraphicsBuffer.IndirectDrawIndexedArgs[1];
cmds[0].indexCountPerInstance = mesh.GetIndexCount(0);
gBuffer.SetData(cmds);
GraphicsBuffer.CopyCount(fullResBuffer, gBuffer, 4);
rparams.matProps.SetBuffer("_GrassIndices", fullResBuffer);
Graphics.RenderMeshIndirect(rparams, mesh, gBuffer, 1);
cmds[0].indexCountPerInstance = lodMesh.GetIndexCount(0);
gBuffer.SetData(cmds);
GraphicsBuffer.CopyCount(lodBuffer, gBuffer, 4);
rparams.matProps.SetBuffer("_GrassIndices", lodBuffer);
Graphics.RenderMeshIndirect(rparams, lodMesh, gBuffer, 1);```
tight dragon
#

Looks good

soft sigil
#

there is only one grass spawning for some reason

#

and fps is 5

#

i swear we missed smth

#

what those ints mean

#

like the position id

tight dragon
#

They are probably all being drawn in the same position.

#

The int is the index into the positions buffer.

soft sigil
#

so well, the pos id

#

this feels alr

tight dragon
#

Screenshot the Start method as well

soft sigil
tight dragon
soft sigil
#

it's useless here ig

tight dragon
#

But it's probably defaulting to zero so it's probably not making any difference.

soft sigil
#

one sec

#

nah ig i just cut the assigning part

#

that's the updatedensity

#

in case i switch from 100k to just 100

tight dragon
#

Just to make sure the right part of the .hlsl file is working, try changing the Out = PositionWS; back to Out = 0;. If it disappears, then it means something is wrong.

soft sigil
#

position buffer gets 100k points correctly

#

still there

tight dragon
#

Ok, and then to test if unity_InstanceID is correct, lets try just placing them in a line on the X axis based on their instance id:

Out = PositionWS + float3(unity_InstanceID, 0, 0);
#

If it works, you'll see all of the grass in a long line.

soft sigil
#

nah still in place

#

looks like id is always 0

tight dragon
#

Can you screenshot the Shader Graph nodes again?

soft sigil
#

sec

#

wait

#

it's not the unityid not working

#

it's just the hlsl

#

i just replaced id with 50

#

so all grasses should be drawn on 50/0/0

#

but grass stays in place

tight dragon
#

That's why I wanted to see the shader, to see how it uses the output

soft sigil
#

it might be GetInstancingPos_float

#

or it's just for the type

tight dragon
#

That's a bit weird. You're taking the world position, then trying to transform it from object to world space, then converting the output (which is in world space) from object space to world space.

#

You're supposed to omit the _float. It's fine, you would get an error if it was wrong.

soft sigil
#

that's what the guide stated

#

copypasted the thing

tight dragon
#

See the difference here?

soft sigil
#

first node ig

tight dragon
#

And the one after the custom function

soft sigil
#

xD

#

changed the thing

#

replaced thing back

#

still the same result xD

tight dragon
#

If you replace it with:

Out = PositionWS + float3(10, 0, 0);

Does it move the grass?

soft sigil
#

still stays

#

no errors

tight dragon
#

Can you try resaving/recompiling the shader graph?

#

Are you definitely using the right shader on the material you're using in the draw call?

soft sigil
#

wait

#

waaaait

#

LOL

#

LOL

#

LOOOOL

#

Somewhere deep in my project files

#

there is a freaking old grass material

#

which is attached to the script

#

it's like a month old

tight dragon
#

Happens

soft sigil
#

i hate myself

#

alr

tight dragon
#

It will keep happening, just part of being a game developer. Over time you'll learn to assume nothing and check everything.

soft sigil
#

300x300 plane 100k meshes

#

LEZZZZZZZZZ GOOOOOOOOOOOOOOO

#

wait

tight dragon
#

And you're CPU bound there, since the app time and CPU main time are the same. Your GPU can probably do it even faster if the CPU wasn't taking so long.

#

Btw, that stats window will not count these draw calls. It's not aware of them. That's why Tris and Verts are so low.

soft sigil
#

when i rotate 30 degrees it disappears

#

even in scene view

tight dragon
#

You have to assign the correct bounds in rparams.

soft sigil
#

are those bounds of entire grass zone

tight dragon
#

Yes

soft sigil
#

or each individual grass object

tight dragon
#

Everything combined

soft sigil
#

can i just use the bounds of grasszone

#

like transform scale

tight dragon
#

Sure

soft sigil
#

how do i transform vector3 to bounds

#

is there some built in stuff

#

or i need mafs

tight dragon
#

rparams.worldBounds = new Bounds(transform.position, transform.localScale);

soft sigil
#

oh scale is always positive sure

#

just a point

#

i got some kind of bug ig

#

far grass is ok, it's just a cross, but close grass is weird

#

it should look this way

#

feels like missing vertices

tight dragon
#

Looks like the wrong indexCountPerInstance

soft sigil
#

it's imported from blender but still

#

it's 4 planes which means 16 verts

#

and 8 tris

tight dragon
#

Ok, so then it's probably because the data in the buffer is not being copied after the first RenderMeshIndirect, so it will end up using the same args as the low res one, with fewer triangles.

#

A quick solution to that is to make the args buffer 2 count instead of 1.

soft sigil
#

i refill it with new data

#

don't i?

tight dragon
#

Yes, but it's my mistake. The GPU won't read that args buffer until later, at which point you've already overwritten it with the lod data.

soft sigil
#

alr

tight dragon
#

It's not a big change.

#

Just change the count of gBuffer to 2 when you create it.

#

Then cmds to 2 count.

#

And assign cmds[0] and cmds[1] in each step instead of overwriting the same one.

soft sigil
#

like dis

tight dragon
#

In this case, you will only need to call gBuffer.SetData once. Just assign both cmds first and call SetData once

soft sigil
#

lez go

#

it works

tight dragon
#

You have one more missing thing

#

You have to tell the lod RenderMeshIndirect to use the second command, by adding a fifth parameter 1, which is the startCommand it starts reading from.

soft sigil
#

alr lets hope i get more than 10fps

#

wait

#

it doesn't work now

tight dragon
#

Oh and you have to fix the second CopyCount, because it's still copying only to the first command.

soft sigil
#

second mesh is just not getting rendered

tight dragon
#

GraphicsBuffer.CopyCount(lodBuffer, gBuffer, GraphicsBuffer.IndirectDrawIndexedArgs.size + 4)

soft sigil
#

shouldn't i also change command count to 2 in second render mesh ?

#

... ,gBuffer, 2, 1);

tight dragon
#

No, then it will try to read two commands from the buffer, starting at index 1

soft sigil
#

alr

#

buffers seem so unlogical

#

but as you experiment more you actually get why they are structured this way

tight dragon
#

Byte offsets are annoying and often a cause of bugs.

soft sigil
#

well

#

the thing caused a glitch

#

those grasses in the background flash and move around

#

not only in the background

#

just everywhere outside of fullRes mesh zone

#

can't i just use two different GraphicsBuffer.IndirectDrawIndexedArgs[1]?

tight dragon
#

Screenshot of the current code please

soft sigil
tight dragon
#

You're copying from the fullResBuffer twice

soft sigil
#

OH

#

my bad

#

copypaste goes bad

tight dragon
#

How many is that?

soft sigil
#

100k meshes

#

over 300x300

#

with 60+ fps

#

lez goooo

tight dragon
#

Nice. You're probably still CPU bound, so the FPS you're at now is not because of the grass. You can probably get more FPS if you fix whatever is making the CPU slow down.

soft sigil
#

well, buy a new cpu

#

r3 2200g feels bad

tight dragon
#

Check the Profiler and see what's taking too long.

#

Might be something easy to fix.

soft sigil
#

others and scripts

#

take time

tight dragon
#

Hmm, maybe not. Not sure why the Stats window is saying CPU main is taking 16.8 ms when it's clearly much lower than that.

#

Maybe I'm wrong, maybe it's just waiting for the GPU.

#

In which case, yeah, that's the FPS of the grass.

soft sigil
#

editor loop

tight dragon
#

But you'll get a bit more FPS once you add frustum culling.

soft sigil
#

when this code runs it's 60 but also 100k meshes bruh

#

it's giant

#

i had like 15 before

#

with RenderMeshInstanced

#

how do i cull this

tight dragon
#

You can find examples of this by searching "unity compute shader frustum culling"

#

Since all your grass meshes are the same size and shape, its simplest to consider them as spheres for culling. Then you just need to check if the distance is greater than the radius of the sphere, and if so, don't append it to any buffer.

#

Other implementations you find online might be more complicated, comparing the AABB/Bounds of the mesh against the frustum planes, which is unnecessary here.

soft sigil
tight dragon
soft sigil
#

Il try adapting it tomorrow

soft sigil
#

lez go

#

@tight dragon

#

I got no fps boost for some reason xD

#

Ig its hard even for gpu to go through 100k grasses and calculate dot product with planes for each

soft sigil
#

without calling rendermeshindirect i get like 200fps

#

but those 2 calls eat 140 of it and i get 60

tight dragon
soft sigil
#

i got some weird issue

#

if i move grass zone from 0/0/0 to some other place, everything works like shit

#

generated positions set is proper, i checked several times

#

also since i used height, i can definitely see positions copying the geometry

#

but all grass is moved from where it should be to some other coords on a diagonal

#

idk what's it doing here

#

when it should be all around the terrain

tight dragon
soft sigil
#

problem is here

#

PositionWS is alr

#

but _GrassPositions[] are dumb

tight dragon
#

Because they are in world space, not relative to the grass zone.

#

Try removing PositionWS and just use GrassPositions

soft sigil
#

so well, how do i update the shader to render grass on the correct place

#
void GetInstancingPos_float(in float3 PositionWS, out float3 Out)
{
    Out = 0;
#if UNITY_ANY_INSTANCING_ENABLED
    int grassIndex = _GrassIndices[unity_InstanceID];
    Out = _GrassPositions[grassIndex];
#endif
}```
tight dragon
#

Yes

soft sigil
#

it's empty

tight dragon
#

Oh sorry, PositionWS is where the vertex positions are

soft sigil
#

turned terrain and plane on for orientation

#

nothing on both

tight dragon
#

Try feeding in just the object position into the Custom Function and revert the code.

soft sigil
#

u mean this?

tight dragon
#

So, remove the Transform node before the Custom Function

soft sigil
#

done

#

no i can get back to WS + _Grass...?

tight dragon
#

Yes

soft sigil
#

lez gooo

#

waaaaait

#

is this grass occlusion culled automatically?

#

fps skyrockets when there's no grass in view

tight dragon
#

No, there's just vastly less overdraw when the terrain is in the way.

soft sigil
#

so well, i still need to code occlusion culling for this grass?

#

or it's alr this way

tight dragon
#

Only if you feel you need more performance. But if frustum culling didn't give you much, occlusion culling won't be any better. More complicated to run.

soft sigil
#

is there a way to fix this

#

like one side is more lit that another

#

it's really noticable

#

grasses look like painted shields

tight dragon
#

One side is facing the sun more than the other. You can overwrite the normals in the shader to all face in the same direction, for example up.

soft sigil
#

wherehow

#

is that part of shadergraph

#

or hlsl

tight dragon
#

In the Shader Graph

soft sigil
#

this part?

tight dragon
#

Easier to change the Normal in the vertex stage

soft sigil
#

lez go

#

perfect

#

is that a common thing for every game tho

#

gpu instancing grass meshes

tight dragon
#

Yes, it's common for grass normals to always face up

soft sigil
#

but what about the geometry

#

are like guys in every game instantiate crossed planes

#

or some higher-poly stuff can also be instantiated

#

im asking because...

tight dragon
#

I'm not sure. I think there's a variety.

soft sigil
#

without adding additional planes

tight dragon
#

I think there are games that have individually modeled blades of grass.

soft sigil
#

yeah but it feels too AAA for me

#

i'll probably just rotate these 4 planes a bit

#

so they look normal from up

soft sigil
#

@tight dragon what about trees XD

#

you ever worked with them?

#

dk if tree should be a single mesh or one mesh for tree and tons of them for leaves

tight dragon
soft sigil
#

i decided to allow several different grass textures to be drawn in each grass zone

#

does that mean i create an array of buffers with length of textures amount

tight dragon
#

I would change the positions buffer into a buffer of a struct that contains a position and a texture index.

#

You can also use a texture array if you're fine with each texture being the same resolution.

soft sigil
#

is there a way to randomly select it in the grass lit shader

tight dragon
#

Sure, you can generate a pseudo random index based on the unity_InstanceID.

tight dragon
#

Or, since your grass is already in a random order due to the random raycasts, you can just modulo the instance ID with the number of textures in the array

int textureIndex = unity_InstanceID % textureCount;
#

Well, actually, that might not be possible since you're adding everything into an AppendBuffer. The order of that is not guaranteed to be the same every frame.

#

So then you might need to generate a pseudo random number from the position instead.

soft sigil
#

switched to unity 6 to check out features

#

texture is gone xD

soft sigil
#

BRUUH THIS FPS GAIN

#

300k meshes+-

soft sigil
#

yo @tight dragon , should this thing work for HDRP as well?

#

i might have messed up the shader graph or hdrp just doesn't support the same thing

soft sigil
#

strange thing -> when you don't look at the grass mesh itself, it's texture is not rendered

#

so if i use the Vetrex -> Position to set grass position, it will ofc change place, but will only be visible while mesh is in view itself

#

straaaaaaaange

#

this part

 GraphicsBuffer.CopyCount(fullResBuffer, gBuffer, 4);
 rparams.matProps.SetBuffer("_GrassIndices", fullResBuffer);
 Graphics.RenderMeshIndirect(rparams, mesh, gBuffer, 1);
 GraphicsBuffer.CopyCount(lodBuffer, gBuffer, GraphicsBuffer.IndirectDrawIndexedArgs.size + 4);
 rparams.matProps.SetBuffer("_GrassIndices", lodBuffer);
 Graphics.RenderMeshIndirect(rparams, lodMesh, gBuffer, 1, 1);```
is now useless
tight dragon
#

I don't know. HDRP is a complicated beast that doesn't really like to be poked.

soft sigil
#

yeah, but Unity 6.0 added GPU Runtime Occlusion Culling

tight dragon
#

That won't work with this.

soft sigil
#

which only works on HDRP version

soft sigil
#

i need it to occlude everything else

#

since i can't bake occlusion culling

tight dragon
soft sigil
#

oh wait

#

URP supports gpu based occlusion

#

but it's a quest to find the setting

#

nvm