Is there an easy way to hide objects that are under another object?
Asked Answered
J

16

0

Is there a way to hide objects under another object? I have a multimesh with a shader creating moving grass, but it pokes through my farm plot blocks. You can see the effect here

I found some code to cull things behind a plane from this youtube video

Someone suggested using a viewport texture to draw shapes where the grass would get drawn too, but I wasn't able to figure out how to do it. This plane culling thing seems like the most straight forward if I can get it to work. But I have no idea how to make it clip the grass only on the spots where I place obejcts.

shader_type spatial;

uniform vec4 portal_plane = vec4(1.0, 0.5, 0.0, 0.0);

bool portal_culls(vec3 vertex, mat4 cam) {
	vec3 pos = (cam * vec4(vertex, 1.0)).xyz;
	return !any(isnan(portal_plane))
		&& portal_plane.x * pos.x
			+ portal_plane.y * pos.y
			+ portal_plane.z * pos.z
			+ portal_plane.w < 0.0;
}

void fragment() {
	if (portal_culls(VERTEX, CAMERA_MATRIX)) {
		discard;
	}
	ALBEDO = vec3(1.0, 0.0, 0.0);
}
Joint answered 11/10, 2021 at 13:4 Comment(0)
J
0

Can't edit my question I guess, so I'll throw this here. It does work outside the viewport, but it either works for all of my grass if I apply it to the grass, or just for the box if I apply it to that. Not sure how to apply it to only a small area where a box would be placed, or multiple boxes.

Joint answered 11/10, 2021 at 13:16 Comment(0)
F
0

You can edit the OP by clicking on the little gear wheel located to the right of the title. I've edited the OP to fix the code formatting.

Fianna answered 11/10, 2021 at 14:44 Comment(0)
D
0

Plane clipping wouldn't be suitable for this if the area becomes more and more complex with time. Better to use texture approach and eliminate growth in the shader based on texture content.

Democrat answered 15/10, 2021 at 23:23 Comment(0)
K
0

You could try masking by rendering to a color mask, though it’s a bit more expensive for performance. I wrote a tutorial on making and using color masks.

Another thing you could maybe use is vertex colors and discard/skip if the vertex is a certain color. I’m not sure if you can modify vertex colors at runtime though.

Kanter answered 17/10, 2021 at 20:50 Comment(0)
D
0

@TwistedTwigleg said: Another thing you could maybe use is vertex colors and discard/skip if the vertex is a certain color. I’m not sure if you can modify vertex colors at runtime though.

Similar possible approach - if each blatt is an instance in a multimesh you can just send visibility info to the shader via per-instance custom data and let the vertex shader cull it.

Democrat answered 17/10, 2021 at 21:24 Comment(0)
S
0

Yes, I think that would work with instances. However, I believe the only way to modify vertex colors is with MeshDataTool, and that requires making a new mesh and sending it to the GPU which would be too slow for this use case.

Supersonics answered 17/10, 2021 at 21:39 Comment(0)
J
0

Similar possible approach - if each blatt is an instance in a multimesh you can just send visibility info to the shader via per-instance custom data and let the vertex shader cull it.

Would that be using the INSTANCE_CUSTOM? If yes, I see that in the documentation, but it only seems to have info for

x: Rotation angle in radians. y: Phase during lifetime (0 to 1). z: Animation frame.

according to the docs. How would I use it to hide certain blades of grass? Could you show an example?

Regarding vertex color at runtime. This tutorial shows it's possible if you use a texture.

Joint answered 17/10, 2021 at 22:37 Comment(0)
D
0

It's possible with a texture as already mentioned, but using instance custom data is far less work, so I'd suggest trying that first.

Custom instance data can mean whatever you want it to mean. You set that data from script using MultiMesh.set_instance_custom_data(). and read it from shader via INSTANCE_CUSTOM built-in, as you said. Data is sent as Color object but it's just 4 floats that your shader can use for whatever purpose it needs. In this case, it'd be visibility info.

You just need to determine instance ids of blatts that are covered, use one of the custom floats as a flag that determines visibility (say 0 is invisible, 1 is visible), check that custom value in the vertex shader and skip drawing the instance if flag is not set.

Or more elegantly just scale the blatt to the ground by the value of that flag to avoid branching in shader (assuming blatt base is at y=0)

So in your script at startup, set all instances to be visible:

for b in blatt_instances:
	multimesh.set_instance_custom_data(b, Color(1.0, 0.0, 0.0, 0.0) )

In your script when some blatts become invisible:

for b in newly_culled_blatt_instances:
	multimesh.set_instance_custom_data(b, Color(0.0, 0.0, 0.0, 0.0) )

And in your vertex shader:

	VERTEX.y *= INSTANCE_CUSTOM.r;
Democrat answered 17/10, 2021 at 23:12 Comment(0)
J
0

@xyz Thanks! I should have guess I could put whatever I want in there. Appreciate the detailed example as well. Should work perfectly for what I'm trying to do.

Joint answered 18/10, 2021 at 2:40 Comment(0)
J
0

@xyz I was all excited to try this because I thought it would solve my issue. (Sorry for the late reply, I've been really busy at work). When I finally got around to trying this out, no matter what index I used in my multimeshinstance, the entire mesh would move up and down (which is the problem I've been having for a while). I appreciate the ideas, but I think I'm going to have to just move on from this. I've wasted weeks trying to figure out how to do this.

This does work after all. I tested it on a much smaller grass count, and see the right effect. Finding the position of each blade that will be affected in a cheap enough way to be useful will be challenging. Nonetheless, appreciate the help!

Joint answered 22/10, 2021 at 5:10 Comment(0)
D
0

@CodeGuru12 said: Finding the position of each blade that will be affected in a cheap enough way to be useful will be challenging.

Why? Your code must know all the positions when setting up the grass. Just store them for future use.

If instances are positioned by some code you don't want to touch for whatever reason, you can extract positions from instances themselves using MultiMesh.get_instance_transform()

From your video it looks like each new affected area is rectangular. So it's just a matter of determining if specific position is inside a rectangle. Which is trivial.

The whole thing is actually quite simple and not expensive at all.

Democrat answered 22/10, 2021 at 12:22 Comment(0)
J
0

I thought about doing that. The issue isn't accessing the position. At least the approach I was thinking of would hold the positions and make them the keys to the index. But I still have to search the entire mesh instance for the position to see if it's within a certain distance, of a bounding box. That would be expensive for a mesh with 500,000 plus blades of grass.

Is there a better way to check the position than searching an array, or dictionary?

Joint answered 22/10, 2021 at 19:32 Comment(0)
D
0

@CodeGuru12 said: That would be expensive for a mesh with 500,000 plus blades of grass.

It doesn't matter. You're not doing it every frame. On top of that it's just an effect. It's not time critical. So if number of instances you need to check stutters your frame, just delegate the work to a thread and it will be distributed among several frames. So large sequential search may not be an issue at all.

Benchmark it in real situation with and without a thread. If by chance there's still a noticeable stutter, you can start thinking about employing a simple space partitioning scheme. Start with dividing instances into sectors. That way you'll need to check only sectors the rectangle is in. If you want even more optimization you can use more sophisticated partitioning structure like quadtree, which is not hard to implement and it'll bring linear search time down to logarithmic.

If instances are kept in a dictionary with keys directly derived from their positions, then no search is involved at all. Although you need to be a bit clever about dealing with such keys, and each instance position will be limited to a separate "cell" without overlap.

But as I said, start with brute force and see how it fares. It may surprise you.

Democrat answered 22/10, 2021 at 22:4 Comment(0)
S
0

Why are you rendering each blade of grass individually? That sounds crazy to me. Usually in games they use patches of grass, maybe like a meter square. And you can use a mask texture and a shader to hide parts of the grass if you need non-grid shapes. Even with instancing, I think you will be spending too much of your budget on the grass.

Supersonics answered 22/10, 2021 at 23:8 Comment(0)
J
0

@cybereality It's using a multimeshinstance, so not really individually per se. I followed a tutorial to do the grass though, which is why it's done the way it is. I'm still learning, so I'm open to doing it other ways as I learn about them. I've had issues with this grass performance, which is another reason I've considered discarding the idea of moving blades of grass.

Joint answered 22/10, 2021 at 23:22 Comment(0)
D
0

It all depends on your total area. If you want a very large area then you'll have to think about some optimization. For example display grass only around current player position by reusing 9 mesh tiles... But you can use the method of hiding we've been discussing even with that setup. Only difference is you'll have to additionally reset/update visibility whenever tiles are swapped. Which is also no big deal.

It'd be good to first define constraints of your system, then look for actual solutions to implement.

Democrat answered 22/10, 2021 at 23:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.