Grass shell Texturing

Demo shell texturing for grass using shader and MultiMeshInstance.
main
Peery 8 months ago
parent 16778381cb
commit 2b4746b77a
Signed by: pandro
SSH Key Fingerprint: SHA256:iBUZSuDxqYr4hYpe9U3BA9NJmXKpbGt4H0S8hUwIbrA

@ -0,0 +1,46 @@
extends Node3D
class_name RotateCamera
@export var camera_rotate_sensitivity: float = 0.005
@export var camera_zoom_sensititivy: float = 0.01
@export var zoom_max: float = 10.
@export var zoom_min: float = 0.1
@onready var camera: Camera3D = $Camera3D
var camera_rotate_diff: Vector3 = Vector3()
var camera_zoom_weight: float = .15
var enable_rotate: bool = false
func _process(delta):
self._update_rotation()
self._update_zoom()
func _unhandled_input(event):
if self.enable_rotate and event is InputEventMouseMotion:
self.camera_rotate_diff.y -= event.relative.x
self.camera_rotate_diff.x -= event.relative.y
elif event is InputEventMouseButton:
if event.is_action_pressed("camera_rotate_button"):
self.enable_rotate = true
elif event.is_action_released("camera_rotate_button"):
self.enable_rotate = false
elif event.is_action_pressed("camera_zoom_in"):
self.camera_zoom_weight = clampf(self.camera_zoom_weight - camera_zoom_sensititivy, 0., 1.)
elif event.is_action_pressed("camera_zoom_out"):
self.camera_zoom_weight = clampf(self.camera_zoom_weight + camera_zoom_sensititivy, 0., 1.)
func _update_rotation():
if self.enable_rotate:
self.rotation += self.camera_rotate_diff * camera_rotate_sensitivity
self.camera_rotate_diff = Vector3.ZERO
func _update_zoom():
var from = self.global_position
var to = self.global_position + self.camera.get_global_transform().basis.z * zoom_max
var new_cam_pos = lerp(from, to, camera_zoom_weight)
self.camera.global_position = new_cam_pos

@ -0,0 +1,10 @@
[gd_scene load_steps=2 format=3 uid="uid://bkavbjcx1vhaf"]
[ext_resource type="Script" path="res://camera.gd" id="1_bmlh2"]
[node name="camera_root" type="Node3D"]
transform = Transform3D(1, 0, 0, 0, 0.869029, 0.494761, 0, -0.494761, 0.869029, 0, 0, 0)
script = ExtResource("1_bmlh2")
[node name="Camera3D" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.306079, 2.53232)

@ -0,0 +1,61 @@
shader_type spatial;
render_mode cull_disabled;
//uniform int _ShellIndex; // index of shell being worked on (0 -> _ShellCount)
uniform float _NoiseMin; // minimum strand length
uniform float _NoiseMax; // maxmimum strand length
uniform float _ShellLength; // amount of distance that the shells cover, 1 means shells will span across 1 world space unit
uniform int _ShellCount; // total number of shells (for normalizing the shell index)
uniform float _ShellDistanceAttenuation; // exponent how far to push the shell outwards
uniform float _Thickness; // how thick a strand shall be
uniform float _Attenuation; // AO strength factor
uniform float _OcclusionBias; // additive bias for AO
uniform vec3 _ShellColor;
uniform float _Density; // amout of strands to generate
varying flat int _ShellIndex;
float hash(uint n) {
n = (n << 13U) ^ n;
n = n * (n * n * 15731U + 1239221U) + 123376312589U;
return float(n & uint(0x7fffffffU)) / float(0x7fffffff);
}
void vertex() {
_ShellIndex = int(INSTANCE_CUSTOM.x);
float shell_height = float(_ShellIndex) / float(_ShellCount);
shell_height = pow(shell_height, _ShellDistanceAttenuation);
VERTEX.xyz += NORMAL.xyz * _ShellLength * shell_height;
//NORMAL = normalize(NORMAL);
UV = UV;
}
void fragment() {
vec2 newUV = UV * _Density;
vec2 localUV = fract(newUV) * 2. - 1.;
float localDistanceFromCenter = length(localUV);
float h = float(_ShellIndex) / float(_ShellCount); // normalized height
uint seed = uint(int(newUV.x) + 100 * int(newUV.y) + 100 * 10);
float rand = mix(_NoiseMin, _NoiseMax, hash(seed)); // getting random value for strand
if (localDistanceFromCenter > _Thickness * (rand - h) && _ShellIndex > 0) discard; // discarding pixels outside of thickness
//float ndotl = dot(NORMAL, WORLD) // TODO do light later?
float ambientOcclusion = pow(h, _Attenuation); // fake Ambient Occlusion
ambientOcclusion += _OcclusionBias;
// Called for every pixel the material is visible on.
ALBEDO.rgb = _ShellColor * ambientOcclusion;
//ALPHA = 1.0;
}
void light() {
float ndotl = clamp(dot(NORMAL, LIGHT), 0., 1.) * .5 + .5;
DIFFUSE_LIGHT += ndotl * ndotl;
}

@ -0,0 +1,22 @@
[gd_resource type="QuadMesh" load_steps=3 format=3 uid="uid://obd7wi7g27sb"]
[ext_resource type="Shader" path="res://grass.gdshader" id="1_5707v"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_hgeuk"]
render_priority = 0
shader = ExtResource("1_5707v")
shader_parameter/_NoiseMin = 0.4
shader_parameter/_NoiseMax = 1.0
shader_parameter/_ShellLength = 0.03
shader_parameter/_ShellCount = 16
shader_parameter/_ShellDistanceAttenuation = 1.0
shader_parameter/_Thickness = 3.4
shader_parameter/_Attenuation = null
shader_parameter/_OcclusionBias = null
shader_parameter/_ShellColor = Vector3(0.2, 0.7, 0.2)
shader_parameter/_Density = 10.0
[resource]
material = SubResource("ShaderMaterial_hgeuk")
subdivide_width = 32
subdivide_depth = 32

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://yafv1eyajxv0"
path="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.png"
dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

@ -0,0 +1,23 @@
[gd_scene load_steps=5 format=3 uid="uid://b45p5o8obc4rb"]
[ext_resource type="PackedScene" uid="uid://bkavbjcx1vhaf" path="res://camera.tscn" id="1_fql2y"]
[ext_resource type="PackedScene" uid="uid://gbmju3x1gp7y" path="res://multi_grass.tscn" id="3_lkk6e"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_sivjy"]
albedo_color = Color(0.2, 0.137255, 0.0431373, 1)
[sub_resource type="PlaneMesh" id="PlaneMesh_vpqsi"]
material = SubResource("StandardMaterial3D_sivjy")
[node name="main" type="Node3D"]
[node name="camera" parent="." instance=ExtResource("1_fql2y")]
[node name="light" type="DirectionalLight3D" parent="."]
transform = Transform3D(0.758372, 0.524229, 0.38737, -0.457532, 0.00484201, 0.88918, 0.464258, -0.851564, 0.243524, 1.21403, 2.38572, 0.933982)
[node name="multi_grass" parent="." instance=ExtResource("3_lkk6e")]
[node name="ground" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.149795, 0)
mesh = SubResource("PlaneMesh_vpqsi")

@ -0,0 +1,72 @@
extends MultiMeshInstance3D
@export_range(1, 126) var shell_count = 64
@export var noise_min: float = 0.4
@export var noise_max: float = 1.0
@export var shell_length: float = .1
@export var shell_distance_attenuation: float = 1.
@export var thickness: float = 3.4
@export var attenuation: float = 1.98
@export var occlusion_bias: float = .04
@export var shell_color: Vector3 = Vector3(0.2, 0.7, 0.2)
@export var density: int = 150
func _generate_mmi(layers: int, mmi: MultiMeshInstance3D, mesh: Mesh, material: Material, cast_shadow: bool):
var mdt = MeshDataTool.new()
if mmi.multimesh == null:
mmi.multimesh = MultiMesh.new()
mmi.multimesh.transform_format = MultiMesh.TRANSFORM_3D
var new_mesh: Mesh = mesh.duplicate(true) as Mesh
#new_mesh = _normals_to_vertex_color(new_mesh, material) # saves normal data as vertex color to be used by the shader (in the orig code)
mmi.multimesh.mesh = new_mesh
mmi.multimesh.instance_count = layers
mmi.multimesh.visible_instance_count = layers
for surface in new_mesh.get_surface_count():
material = ShaderMaterial.new()
material.set_shader(preload("res://grass.gdshader"))
mmi.multimesh.mesh.surface_set_material(surface, material.duplicate(true))
for i in range(layers):
#mmi.multimesh.set_instance_transform(i, Transform3D(Basis(), Vector3(0., .1 * i, 0.)))
mmi.multimesh.set_instance_transform(i, Transform3D(Basis(), Vector3()))
var grey = float(i) / float(layers)
#mmi.multimesh.set_instance_color(i, Color(1., 1., 1., 1.))
var mat = mmi.multimesh.mesh.surface_get_material(0)
if mat is ShaderMaterial:
#print("Setting shader params (", i, ")")
#mat.set_shader_parameter("_ShellIndex", i)
mmi.multimesh.set_instance_custom_data(i, Color(i, 0., 0., 0.)) # passes shell index for each instance
mat.set_shader_parameter("_NoiseMin", noise_min)
mat.set_shader_parameter("_NoiseMax", noise_max)
mat.set_shader_parameter("_ShellLength", shell_length)
mat.set_shader_parameter("_ShellCount", layers)
mat.set_shader_parameter("_ShellDistanceAttenuation", shell_distance_attenuation)
mat.set_shader_parameter("_Thickness", thickness)
mat.set_shader_parameter("_Attenuation", attenuation)
mat.set_shader_parameter("_OcclusionBias", occlusion_bias)
mat.set_shader_parameter("_ShellColor", shell_color)
mat.set_shader_parameter("_Density", density)
mmi.cast_shadow = 1 if cast_shadow else 0
func _ready():
var mat = ShaderMaterial.new()
#var mat2 = StandardMaterial3D.new()
mat.set_shader(preload("res://grass.gdshader"))
self._generate_mmi(self.shell_count, self, self.get_multimesh().mesh, mat, false)
print("===== Shell Texturing =====")
print("Layers: ", self.shell_count)
print("Noise (min): ", self.noise_min)
print("Noise (max): ", self.noise_max)
print("Shell length: ", self.shell_length)
print("Shell count: ", self.shell_count)
print("Shell distance attenuation: ", self.shell_distance_attenuation)
print("Thickness: ", self.thickness)
print("Attenuation: ", self.attenuation)
print("Occlusion bias: ", self.occlusion_bias)
print("Shell color: ", self.shell_color)
print("Density: ", self.density)

@ -0,0 +1,15 @@
[gd_scene load_steps=4 format=3 uid="uid://gbmju3x1gp7y"]
[ext_resource type="Script" path="res://multi_grass.gd" id="1_f7cbg"]
[sub_resource type="QuadMesh" id="QuadMesh_h8afx"]
orientation = 1
[sub_resource type="MultiMesh" id="MultiMesh_j6hjp"]
transform_format = 1
use_custom_data = true
mesh = SubResource("QuadMesh_h8afx")
[node name="multi_grass" type="MultiMeshInstance3D"]
multimesh = SubResource("MultiMesh_j6hjp")
script = ExtResource("1_f7cbg")

@ -0,0 +1,34 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="Shell Texturing"
run/main_scene="res://main.tscn"
config/features=PackedStringArray("4.2", "Forward Plus")
config/icon="res://icon.png"
[input]
camera_rotate_button={
"deadzone": 0.5,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":3,"canceled":false,"pressed":false,"double_click":false,"script":null)
]
}
camera_zoom_in={
"deadzone": 0.5,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":4,"canceled":false,"pressed":false,"double_click":false,"script":null)
]
}
camera_zoom_out={
"deadzone": 0.5,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":5,"canceled":false,"pressed":false,"double_click":false,"script":null)
]
}
Loading…
Cancel
Save