shader_type spatial; uniform sampler2D u_texture_top : hint_albedo; uniform sampler2D u_texture_sides : hint_albedo; // Bitmask telling which of the 6 faces of the block are bordered by a block of lower resolution uniform int u_transition_mask; // We'll need to pass data from the vertex shader to the fragment shader varying vec3 v_world_pos; varying vec3 v_world_normal; varying vec4 v_weights; varying vec4 v_indices; // We'll use a utility function to decode components. // It returns 4 values in the range [0..255]. vec4 decode_8bit_vec4(float v) { uint i = floatBitsToUint(v); return vec4( float(i & uint(0xff)), float((i >> uint(8)) & uint(0xff)), float((i >> uint(16)) & uint(0xff)), float((i >> uint(24)) & uint(0xff))); } // A voxel mesh can have overhangs in any direction, // so we may have to use triplanar mapping functions. vec3 get_triplanar_blend(vec3 world_normal) { vec3 blending = abs(world_normal); blending = normalize(max(blending, vec3(0.00001))); // Force weights to sum to 1.0 float b = blending.x + blending.y + blending.z; return blending / vec3(b, b, b); } vec4 texture_triplanar(sampler2D tex, vec3 world_pos, vec3 blend) { vec4 xaxis = texture(tex, world_pos.yz); vec4 yaxis = texture(tex, world_pos.xz); vec4 zaxis = texture(tex, world_pos.xy); // blend the results of the 3 planar projections. return xaxis * blend.x + yaxis * blend.y + zaxis * blend.z; } float get_hash(vec2 c) { return fract(sin(dot(c.xy, vec2(12.9898,78.233))) * 43758.5453); } vec3 get_transvoxel_position(vec3 vertex_pos, vec4 vertex_col) { int border_mask = int(vertex_col.a); int cell_border_mask = border_mask & 63; // Which sides the cell is touching int vertex_border_mask = (border_mask >> 6) & 63; // Which sides the vertex is touching // If the vertex is near a side where there is a low-resolution neighbor, // move it to secondary position int m = u_transition_mask & (cell_border_mask & 63); float t = float(m != 0); // If the vertex lies on one or more sides, and at least one side has no low-resolution neighbor, // don't move the vertex. t *= float((vertex_border_mask & ~u_transition_mask) == 0); // Position to use when border mask matches vec3 secondary_position = vertex_col.rgb; return mix(vertex_pos, secondary_position, t); } void vertex() { // Indices are integer values so we can decode them as-is v_indices = decode_8bit_vec4(UV.x); // Weights must be in [0..1] so we divide them v_weights = decode_8bit_vec4(UV.y) / 255.0; //v_normal = NORMAL; vec3 world_pos = (WORLD_MATRIX * vec4(VERTEX, 1.0)).xyz; v_world_pos = world_pos; v_world_normal = NORMAL; VERTEX = get_transvoxel_position(VERTEX, COLOR); } void fragment() { vec3 normal = v_world_normal;//normalize(v_world_normal); vec3 wpos = v_world_pos * 0.2; // Sample the 4 blending textures, all with triplanar mapping. // We can re-use the same triplanar blending factors for all of them so separating that part // of the function improves performance a little. vec3 blending = get_triplanar_blend(v_world_normal); vec3 top_col = texture_triplanar(u_texture_top, wpos, blending).rgb; vec3 side_col = texture_triplanar(u_texture_sides, wpos, blending).rgb; // Get weights and make sure they are normalized. // We may add a tiny safety margin so we can afford some degree of error. vec4 weights = v_weights; weights /= (weights.x + weights.y + weights.z + weights.w + 0.00001); // Calculate albedo //vec3 col = // col0 * weights.r + // col1 * weights.g + // col2 * weights.b + // col3 * weights.a; float r = top_col.r; ALBEDO = mix(side_col, top_col, clamp(normal.y * 10.0 - 4.0 - 8.0*r, 0.0, 1.0)); }