blog: a tidy room is a happy room
This commit is contained in:
parent
74f8158196
commit
683db46f39
|
@ -0,0 +1,103 @@
|
|||
# A Tidy Room is a Happy Room
|
||||
|
||||
[room-grass.jpg](./room-grass.jpg)
|
||||
|
||||
In mid-December I attended a hackathan on Meta's campus in central London.
|
||||
It was something of a novel experience, as I'm much more used to the kind ofevents put on by university student bodies.
|
||||
I made some great new friends and enjoyed working with the Quest 3, but more importantly I put some cool wavy grass on the floor of a real room.
|
||||
This post is a technical breakdown of the graphical components that went into the effect.
|
||||
|
||||
---
|
||||
|
||||
To begin with, I'll be upfront and say that I did not make the original implementation of the grass - that credit goes to [Acerola](https://www.youtube.com/watch?v=jw00MbIJcrk).
|
||||
Thanks, Acerola!
|
||||
It generates chunks of grass positions using a compute shader, which are then used to draw a large number of meshes with [`Graphics.DrawMeshInstancedIndirect()`](https://docs.unity3d.com/ScriptReference/Graphics.DrawMeshInstancedIndirect.html).
|
||||
The grass mesh is drawn with a vertex shader which lets it move in the wind, and a gradient is calculated along the blades' length to give an impression of 3D lighting.
|
||||
Chunks which are outside the field of view are culled, saving performance only for those chunks which are visible.
|
||||
|
||||
Our application has the user interacting with the grass, so we first needed to fit the grass to the physical room, regardless of its size or shape.
|
||||
For simplicity, I first reduced the grass' footprint to 10x10m, which should be just bigger most reasonable rooms, and is significantly smaller than the terrain it was covering originally.
|
||||
My approach would then be to scale and translate the generated grass positions to get them all inside the limits of the room.
|
||||
|
||||
The Quest 3 provides access to the generated mesh of the room at runtime, of which we can get an axis-aligned bounding box with [`Mesh.bounds`](https://docs.unity3d.com/ScriptReference/Mesh-bounds.html).
|
||||
This gives the actual size of the room, and so this information needs to be passed into the compute shader responsible for generating grass positions.
|
||||
By using the maximum and minimum limits on the X and Z axes, the required information can all be passed into the shader with a single `Vector4`.
|
||||
|
||||
```
|
||||
// GrassChunkPoint.compute
|
||||
|
||||
// Original implementation
|
||||
pos.x = (id.x - (chunkDimension * 0.5f * _NumChunks)) + chunkDimension * _XOffset;
|
||||
pos.z = (id.y - (chunkDimension * 0.5f * _NumChunks)) + chunkDimension * _YOffset;
|
||||
|
||||
// Scale to fit the aspect of the room
|
||||
float width = _Right - _Left;
|
||||
float length = _Front - _Back;
|
||||
pos.x *= width / dimension;
|
||||
pos.z *= length / dimension;
|
||||
|
||||
pos.xz *= (1.0f / scale);
|
||||
```
|
||||
|
||||
A world UV for each blade of grass is generated at this time, too.
|
||||
In the original implementation this is used for sampling the wind texture and a heightmap.
|
||||
We don't need a heightmap, and the resulting scale artifacts in the wind texture aren't perceptible.
|
||||
However, we would need to use the UV to interact with the grass later, so I needed to transform the generated UV appropriately too.
|
||||
|
||||
```
|
||||
float uvX = pos.x;
|
||||
float uvY = pos.z;
|
||||
|
||||
// Scale UV to room
|
||||
uvX *= dimension / width;
|
||||
uvY *= dimension / length;
|
||||
|
||||
// Apply translation after scaling
|
||||
float offset = dimension * 0.5f * _NumChunks * (1.0f / _NumChunks);
|
||||
uvX += offset;
|
||||
uvY += offset;
|
||||
|
||||
float2 uv = float2(uvX, uvY) / dimension;
|
||||
```
|
||||
|
||||
With this I was able to fit the grass to the bounds of the room.
|
||||
It is not an ideal solution, since it is always the same amount of grass scaled to fit into the room.
|
||||
As a result, smaller rooms have denser grass.
|
||||
However, most rooms are about the same order of magnitude in terms of area, so this was functional for our prototype.
|
||||
|
||||
The next problem to solve was interaction.
|
||||
Our mechanic involved cutting the grass.
|
||||
Since the grass was not GameObjects, there was no object to destroy or transform to compare, so we needed another way to relate a position within the room to something the grass could understand.
|
||||
Moritz had created a an array of points covering the floor, taking into account raised parts of the scene mesh to determine where grass ought to be.
|
||||
|
||||
[free-spots.png](./free-spots.png)
|
||||
|
||||
We opted to use a render texture to communicate this information to the grass' vertex shader.
|
||||
This approach meant we could write in information about the pre-existing furniture at startup, and use the same technique to update the grass at runtime.
|
||||
UVs are generated for points and used to write into an initially black render texture.
|
||||
Green points should have grass on startup, and so they write a white pixel into the render texture.
|
||||
Everywhere else is initially black, which means the grass should be cut at that location.
|
||||
When points are collected, they write black into the texture, cutting the grass at that location.
|
||||
|
||||
[room-rt.png](./room-rt.png)
|
||||
[grass-rt.png](./grass-rt.png)
|
||||
|
||||
The last step was to sample the render texture and use it to remove grass at a particular location.
|
||||
We can sample using the UV which was scaled to the room during position generation.
|
||||
Then we clip the grass based on the read value.
|
||||
|
||||
```
|
||||
// Sample the texture in the vertex stage to reduce texture lookups
|
||||
o.grassMap = tex2Dlod(_GrassMap, worldUV);
|
||||
|
||||
// ...
|
||||
|
||||
// Clip pixels in the fragment stage if the read value is less than white
|
||||
clip(i.grassMap.x - .99);
|
||||
```
|
||||
|
||||
That's it for the main moving parts we implemented on top of Acerola's grass for the hackathon.
|
||||
The final experience is available on [AppLab](https://www.oculus.com/experiences/7147643091959624/release-channels/386799520513932/?token=Ns1Z92Rx).
|
||||
Thanks for reading, and please [get in touch](mailto:me@ktyl.dev) if you have any questions!
|
||||
|
||||
[team.jpg](./team.jpg)
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 162 KiB |
Binary file not shown.
After Width: | Height: | Size: 220 KiB |
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 136 KiB |
Loading…
Reference in New Issue