EmiOnGit / warbler_grass

A bevy plugin for creating 3d grass in your game
Apache License 2.0
120 stars 11 forks source link

Feature Request: Ability to set rotation of each grass blade #60

Closed LiamGallagher737 closed 5 months ago

LiamGallagher737 commented 1 year ago

What problem does this solve or what need does it fill?

I would like to be able to put grass on a spherical planet like the following image. image

Describe the solution you'd like

One possible solution would be to have the Grass struct also take a list of rotations for use in the WarblersExplicitBundle.

EmiOnGit commented 1 year ago

Seems like a good solution. Should behave like the rotation field of the Transform component.

I'm travelling for the next days. If someone wants to implement this, feel free to do so, else I'll do it once I'm back

EmiOnGit commented 1 year ago

I've fiddled with the implementation a bit and recognized that it is to much work for me as I'm focusing on my thesis.

Again, if someone wants to give it a try, I'll gladly assist but currently this is out of scope

duncesparce commented 9 months ago

How would the rotation be set? can it be set using a normal map texture with the individual channels sent to the vertex shader? I'm not very good at shaders but I could give this a try.

EmiOnGit commented 9 months ago

Hey @duncesparce , feel free to implement it :)

My thoughts: Sending a normal map should be a solid solution for the problem described. You don't have to send the channels one by one, you can send the texture completely. You can take a look at the YMap to see how this works.

For extra information's, I removed the WarblersExplicitBundle on main. Therefore only WarblersBundle has to be supported :) You can ask for any questions you might still have

fazil47 commented 9 months ago

I've also tried to implement this with a normal map, but the grass blades are becoming really thin when I rotate them.

thin_blades

Without rotation:

without_rotation

With rotation 1

I made this normal map in Blender by making a displacement mesh using grass_y_map.png by following this tutorial. Normal map: ![grass_normal_map](https://github.com/EmiOnGit/warbler_grass/assets/18116695/2169e11e-77d9-41b9-ab6a-7eadf3a0eb74) Bake settings: ![bake_settings](https://github.com/EmiOnGit/warbler_grass/assets/18116695/0c36623c-c3d1-4369-94e7-3f8276ba7a3b) Result: ![result_1](https://github.com/EmiOnGit/warbler_grass/assets/18116695/d3060251-c0b8-479d-a2ca-661682473a42) Normals: ![normals_1](https://github.com/EmiOnGit/warbler_grass/assets/18116695/cfe0aa39-dbd2-4513-a612-c6e3990c3ef7)

With rotation 2

Normal map: ![grass_normal_map_alt](https://github.com/EmiOnGit/warbler_grass/assets/18116695/3155b283-d7f2-4f70-bb81-6a33a90e9175) Result: ![result_2](https://github.com/EmiOnGit/warbler_grass/assets/18116695/a7e2a0f6-2fb0-44e8-89b7-d6b205eb7a18) Normals: ![normals_2](https://github.com/EmiOnGit/warbler_grass/assets/18116695/c6cf8afd-0490-43ea-abbc-f6ae4ba4a201)

grass_shader.wgsl ```wgsl #import bevy_pbr::mesh_functions mesh_position_local_to_clip #import bevy_pbr::mesh_types Mesh #import bevy_pbr::mesh_view_bindings globals struct ShaderRegionConfiguration { wind: vec2, _wasm_padding: vec2, }; struct Vertex { @location(0) vertex_position: vec3, @location(3) xz_position: vec2, } struct Color { main_color: vec4, bottom_color: vec4, } @group(1) @binding(0) var mesh: Mesh; @group(2) @binding(0) var config: ShaderRegionConfiguration; @group(2) @binding(1) var noise_texture: texture_2d; @group(3) @binding(0) var color: Color; @group(4) @binding(0) var y_texture: texture_2d; struct ShaderAabb { vect: vec3, _wasm_padding: f32, } @group(4) @binding(1) var aabb: ShaderAabb; #ifdef HEIGHT_TEXTURE @group(5) @binding(0) var height_texture: texture_2d; #else struct ShaderHeightUniform { height: f32, _wasm_padding: vec2, } @group(5) @binding(0) var height_uniform: ShaderHeightUniform; #endif @group(6) @binding(0) var t_normal: texture_2d; @group(6) @binding(1) var s_normal: sampler; struct VertexOutput { @builtin(position) clip_position: vec4, @location(0) color: vec4, }; const NOISE_TEXTURE_SPEED: f32 = 50.; const NOISE_TEXTURE_ZOOM: f32 = 35.; fn wind_offset(vertex_position: vec2) -> vec2 { var texture_offset = config.wind.xy * globals.time * NOISE_TEXTURE_SPEED; var texture_position = vec2(vertex_position.x ,vertex_position.y) * NOISE_TEXTURE_ZOOM + texture_offset; // dimensions of noise texture in vec2 let dim = textureDimensions(noise_texture, 0); // read just position in case of a over/under flow of tex. coords texture_position = abs(texture_position % vec2(dim)); var texture_pixel = textureLoad(noise_texture, vec2(i32(texture_position.x),i32(texture_position.y)), 0); return texture_pixel.xy * config.wind; } const BIG_PRIME: f32 = 1302151.; fn density_map_offset(vertex_position: vec2) -> vec2 { var texture_position = vec2(vertex_position.x ,vertex_position.y) * BIG_PRIME ; // dimensions of noise texture in vec2 let dim = textureDimensions(noise_texture, 0); // read just position in case of a over/under flow of tex. coords texture_position = abs(texture_position % vec2(dim)); var texture_pixel = textureLoad(noise_texture, vec2(i32(texture_position.x),i32(texture_position.y)), 0); return texture_pixel.xz - vec2(0.5,0.5) ; } fn texture2d_offset(texture: texture_2d, vertex_position: vec2) -> vec3 { let dim = textureDimensions(texture, 0); let texture_position = abs((vertex_position.xy / aabb.vect.xz ) * vec2(dim)) ; var texture_rgb = textureLoad(texture, vec2(i32(texture_position.x),i32(texture_position.y)), 0).rgb; return texture_rgb * aabb.vect.y; } // Source: https://gist.github.com/kevinmoran/b45980723e53edeb8a5a43c49f134724 fn rotate_align(v1: vec3, v2: vec3) -> mat3x3 { let axis = cross(v1, v2); let cos_a = dot(v1, v2); let k = 1.0 / (1.0 + cos_a); let result = mat3x3( (axis.x * axis.x * k) + cos_a, (axis.y * axis.x * k) - axis.z, (axis.z * axis.x * k) + axis.y, (axis.x * axis.y * k) + axis.z, (axis.y * axis.y * k) + cos_a, (axis.z * axis.y * k) - axis.x, (axis.x * axis.z * k) - axis.y, (axis.y * axis.z * k) + axis.x, (axis.z * axis.z * k) + cos_a ); return result; } @vertex fn vertex(vertex: Vertex, @builtin(instance_index) instance_index: u32) -> VertexOutput { var out: VertexOutput; var position_field_offset = vec3(vertex.xz_position.x, 0.,vertex.xz_position.y); let density_offset = density_map_offset(position_field_offset.xz) / 1.; position_field_offset += vec3(density_offset.x, 0.,density_offset.y); // ---Y_POSITIONS--- position_field_offset.y = texture2d_offset(y_texture, position_field_offset.xz).r; // ---NORMAL--- var normal = texture2d_offset(t_normal, vertex.xz_position.xy).xyz; // Get normal scaled over grass field normal = normalize(normal); let rotation_matrix = rotate_align(vertex.vertex_position, normal); // Calculate rotation matrix to align grass with normal // ---HEIGHT--- var height = 0.; #ifdef HEIGHT_TEXTURE height = (texture2d_offset(height_texture, position_field_offset.xz).r + 4.) / 3.; #else height = height_uniform.height; #endif var position = rotation_matrix * vertex.vertex_position; position = position * vec3(1., height, 1.) + position_field_offset; // ---WIND--- // only applies wind if the vertex is not on the bottom of the grass (or very small) let offset = wind_offset(position_field_offset.xz); let strength = max(0.,log(vertex.vertex_position.y + 1.)); position.x += offset.x * strength; position.z += offset.y * strength; // ---CLIP_POSITION--- out.clip_position = mesh_position_local_to_clip(mesh.model, vec4(position, 1.0)); // ---COLOR--- let lambda = clamp(vertex.vertex_position.y, 0., 1.); out.color = mix(color.bottom_color, color.main_color, lambda); return out; } @fragment fn fragment(in: VertexOutput) -> @location(0) vec4 { return in.color; } ```

I could open a draft PR if you want.

EmiOnGit commented 9 months ago

That's some great research you did @fazil47!

Opening a draft PR is a good idea. In the first picture it seems like the face which was pointing to the camera was not rendered, which is a bit weird.

I'm also not sure why the grass blades get so thin but I think if we have access to your code, we can figure that out :)

LiamGallagher737 commented 5 months ago

Added in #70