Skip to content

Commit

Permalink
Merge pull request #2 from bengtsts/master
Browse files Browse the repository at this point in the history
Added GLES2 support.
  • Loading branch information
Master-J authored Dec 21, 2020
2 parents ed9cee1 + ba0f6d6 commit 00e7c5d
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 3 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ Having a dozen decals in your camera's view frustum shouldn't be a problem but d

# Known issues and limitations

- Only work with the GLES3.0 renderer
- GLES2.0 projections update with a delay when the camera moves.
- GLES2.0 normal maps may look considerably worse than they do with GLES3.0 depending on viewing angle and light setup.
- GLES2.0 in DecalCo uses DEPTH_TEXTURE which may not work on some old hardware; especially mobile, as stated in the official documentation.
- PBR lighting not supported because of some hacks necessary for the shader to work, PBR could be done if things like the iradiance texture is exposed to the light shader.
- Specular lighting only works with a single light.
- Decal wrapping on sharp angles produce ugly results, this could be solved by cliping the decal on those spots but will require to compute the face normal using the screen texture which would make the shader even less efficient than it already is.
5 changes: 3 additions & 2 deletions decalco/src/Decal.gd
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ tool
extends MeshInstance
class_name Decal , "../icons/icon_decal.svg"

const DECAL_SHADER : Resource = preload("Decal.shader");
const BORDER_ALPHA_MASK : Texture = preload("alpha_mask.png");

enum PlaybackType {
Expand Down Expand Up @@ -40,7 +39,9 @@ func _init() -> void :
cast_shadow = false;

decal_material = ShaderMaterial.new();
decal_material.shader = DECAL_SHADER;
#Assign shader for current driver
if OS.get_current_video_driver() == OS.VIDEO_DRIVER_GLES3: decal_material.shader = preload("Decal.shader");
elif OS.get_current_video_driver() == OS.VIDEO_DRIVER_GLES2: decal_material.shader = preload("DecalGLES2.shader");

set("material/0", decal_material);
decal_material.render_priority = -1; #Needed in order to make the decal render behind transparent geometry
Expand Down
166 changes: 166 additions & 0 deletions decalco/src/DecalGLES2.shader
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
shader_type spatial;
render_mode blend_mix, depth_draw_never, cull_back, depth_test_disable;

uniform sampler2D border_mask : hint_white;

uniform sampler2D albedo : hint_albedo;
uniform vec4 albedo_tint : hint_color = vec4(1.0);

uniform sampler2D emission : hint_black;
uniform vec4 emission_tint : hint_color = vec4(vec3(0.0), 1.0);
uniform float emission_strength = 1.0;

uniform sampler2D occlusion : hint_white;
uniform float occlusion_strength = 1.0;

uniform sampler2D specular : hint_white;
uniform float specular_strength = 1.0;

uniform sampler2D metallic : hint_black;
uniform float metallic_strength = 1.0;

uniform sampler2D normal_map : hint_normal;

uniform int current_frame = 0;
uniform int flipbook_columns_count = 1;

uniform float current_frame_blend = 0.0;

uniform bool use_normal_map = false;

uniform vec3 decal_position;
uniform vec3 decal_right;
uniform vec3 decal_up;
uniform vec3 decal_forward;
uniform vec3 decal_half_scale;

//Checks if the given point is in the decal's boundaries using an oriented bounding box defined by the decal's tranform
bool is_point_in_decal_bounds(vec3 point)
{
vec3 scale = decal_half_scale * 2.0;
vec3 p = point - decal_position;
return abs(dot(p, decal_right)) <= scale.x && abs(dot(p, decal_forward)) <= scale.y && abs(dot(p, decal_up)) <= scale.z;
}


void vertex()
{
//Override the projector mesh's normals in order to render the decal with mostly correct lighting
NORMAL = (vec4(decal_up, 0.0) * WORLD_MATRIX).xyz;
TANGENT = (vec4(decal_right, 0.0) * WORLD_MATRIX).xyz;
BINORMAL = (vec4(decal_forward, 0.0) * WORLD_MATRIX).xyz;
}

void fragment ()
{
//Compute world position using the depth buffer
float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x;
vec3 ndc = vec3(SCREEN_UV, depth) * 2.0 - 1.0;
vec4 view = INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
view.xyz /= view.w;
vec4 world = CAMERA_MATRIX * INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
vec3 world_position = world.xyz / world.w;

if(is_point_in_decal_bounds(world_position))
{
//If the world position is within the decal's boundaries, we can compute it's uv coordinates
vec4 local_position = (vec4(world_position - decal_position, 0.0)) * WORLD_MATRIX;

vec2 flipbook_frame_index = vec2(float(current_frame % flipbook_columns_count), float(current_frame / flipbook_columns_count));
vec2 frame_size = vec2(1.0/float(flipbook_columns_count));
vec2 uv_coords = (vec2(local_position.x, -local_position.y) / (4.0*(decal_half_scale.xy * 2.0 * decal_half_scale.xy))) - vec2(0.5);

//This is used to fix some blending issues on the decal's edges, border mask is a white texture with a 1px transparent border on all sides
float border_alpha = texture(border_mask, uv_coords).a;

//Offset UVs to handle flipbook animation
uv_coords = uv_coords / float(flipbook_columns_count);
uv_coords -= float(flipbook_columns_count - 1) * frame_size - flipbook_frame_index * frame_size;

//Hacky stuff, to get UVs, correct lighting and normal mapping working, i need to get the fragment's local position in the light shader
//Unfortunately, we can't use varying to pass values between the fragment and light shaders
//To work around this limitation, i put the data i need in the TRANSMISSION built-in.
//Also, due to some limitation caused by how this shader works, PBR lighting isn't supported.
TRANSMISSION = vec3(1.0) - local_position.xyz / 100.0;

ALBEDO = texture(albedo, uv_coords).rgb * albedo_tint.rgb;
EMISSION = texture(emission, uv_coords).rgb * emission_tint.rgb * emission_strength;
ALPHA = texture(albedo, uv_coords).a * border_alpha;
}else{
ALPHA = 0.0;
}
}

//taken from https://stackoverflow.com/questions/22442304/glsl-es-dfdx-dfdy-analog
float dfd(vec2 p)
{
return p.x * p.x - p.y;
}

//taken from http://www.thetenthplanet.de/archives/1180 -- Modified for ES2!
mat3 cotangent_frame(vec3 N, vec3 p, vec2 uv)
{
vec2 ps = vec2(1.0 / uv.x, 1.0 / uv.y);
float c = dfd(uv);
vec3 dp1 = vec3(dfd(uv + p.x) - c);
vec3 dp2 = vec3(dfd(uv + p.y) - c);
vec2 duv1 = vec2(dfd(uv));
vec2 duv2 = vec2(dfd(uv));

vec3 dp2perp = cross( dp2, N );
vec3 dp1perp = cross( N, dp1 );

vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;
vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;

float invmax = inversesqrt( max( dot(T,T), dot(B,B) ) );
return mat3( T * invmax, B * invmax, N );
}

vec3 perturb_normal(vec3 N, vec3 V, vec2 texcoord )
{
vec3 map = texture(normal_map, texcoord ).rgb;
map = map * 255./127. - 128./127.;
map.x *= 1.0; //Normal direction is flipped in GLES2
map.y *= 1.0;
map.z *= -1.0;
//mat3 TBN = cotangent_frame(N, V, texcoord); //Makes normals look worse in GLES2
return normalize(map);
}

void light ()
{
//Get back the data from the fragment shader
vec3 data = (vec3(1.0) - TRANSMISSION) * 100.0;

//Recompute UV coordinates
vec2 uv_coords = vec2(data.x, -data.y);

vec2 flipbook_frame_index = vec2(float(current_frame % flipbook_columns_count), float(current_frame / flipbook_columns_count));
vec2 frame_size = vec2(1.0/float(flipbook_columns_count));
uv_coords = (uv_coords.xy / (4.0*(decal_half_scale.xy * 2.0 * decal_half_scale.xy))) - vec2(0.5);
uv_coords = uv_coords / float(flipbook_columns_count);
uv_coords -= float(flipbook_columns_count - 1) * frame_size - flipbook_frame_index * frame_size;

//Normal mapping
vec3 normal = NORMAL;
if(use_normal_map == true)
{
normal = perturb_normal(NORMAL, VIEW, uv_coords);
}

float n_dot_l = clamp(dot(normal, LIGHT), 0.0, 1.0);

//Specular lighting
vec3 view_dir = normalize(CAMERA_MATRIX[3].xyz - data);
vec3 reflection_dir = reflect(-LIGHT, normal);
float spec = pow(max(dot(view_dir, reflection_dir), 0.0), 32);
vec3 specular_light = specular_strength * spec * LIGHT_COLOR;

//Diffuse lighting
vec3 albedo_color = ALBEDO * n_dot_l;
albedo_color = albedo_color * mix(1.0, texture(occlusion, uv_coords).r, occlusion_strength);

DIFFUSE_LIGHT += albedo_color * ATTENUATION * LIGHT_COLOR;
SPECULAR_LIGHT = specular_light * texture(specular, uv_coords).r;
}

0 comments on commit 00e7c5d

Please sign in to comment.