diff --git a/src/shaders/main.glsl b/src/shaders/main.glsl index 19303543d..c562a1e80 100644 --- a/src/shaders/main.glsl +++ b/src/shaders/main.glsl @@ -3,8 +3,24 @@ R"(shader_type spatial; render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx; +/* This shader is generated based upon the debug views you have selected. + * The terrain function depends on this shader. So don't change: + * - vertex positioning in vertex() + * - terrain normal calculation in fragment() + * + * Most will only want to customize the material calculation and PBR application in fragment() + * + * Uniforms that begin with _ are private and will not display in the inspector. However, + * you can set them via code. You are welcome to create more of your own hidden uniforms. + * + * This system only supports albedo, height, normal, roughness. Most textures don't need the other + * PBR channels. Height can be used as an approximation for AO. For the rare textures do need + * additional channels, you can add maps for that one texture. e.g. an emissive map for lava. + * + */ + uniform float _region_size = 1024.0; -uniform float _region_pixel_size = 0.0009765625; // 1.0 / 1024.0 +uniform float _region_texel_size = 0.0009765625; // = 1./1024. uniform int _region_map_size = 16; uniform int _region_uv_limit = 8; uniform int _region_map[256]; @@ -19,38 +35,36 @@ uniform float _texture_uv_scale_array[32]; uniform float _texture_uv_rotation_array[32]; uniform vec4 _texture_color_array[32]; -//INSERT: WORLD_NOISE1 +varying vec3 v_vertex; // World coordinate vertex location -vec3 unpack_normal(vec4 rgba) { - vec3 n = rgba.xzy * 2.0 - vec3(1.0); - n.z *= -1.0; - return n; -} +//////////////////////// +// Vertex +//////////////////////// -vec4 pack_normal(vec3 n, float a) { - n.z *= -1.0; - return vec4((n.xzy + vec3(1.0)) * 0.5, a); -} - -// Takes location in world space coordinates, returns ivec3 with: -// XY: (0-_region_size) coordinates within the region +// Takes in UV world space coordinates, returns ivec3 with: +// XY: (0 to _region_size) coordinates within a region // Z: region index used for texturearrays, -1 if not in a region -ivec3 get_region(vec2 uv) { +ivec3 get_region_uv(vec2 uv) { + uv *= _region_texel_size; ivec2 pos = ivec2(floor(uv)) + (_region_map_size / 2); - int index = _region_map[ pos.y*_region_map_size + pos.x ] - 1; + int index = _region_map[ pos.y * _region_map_size + pos.x ] - 1; return ivec3(ivec2((uv - _region_offsets[index]) * _region_size), index); } -// vec3 form of get_region. Same return values -vec3 get_regionf(vec2 uv) { +// Takes in UV2 region space coordinates, returns vec3 with: +// XY: (0 to 1) coordinates within a region +// Z: region index used for texturearrays, -1 if not in a region +vec3 get_region_uv2(vec2 uv) { ivec2 pos = ivec2(floor(uv)) + (_region_map_size / 2); - int index = _region_map[ pos.y*_region_map_size + pos.x ] - 1; + int index = _region_map[ pos.y * _region_map_size + pos.x ] - 1; return vec3(uv - _region_offsets[index], float(index)); } +//INSERT: WORLD_NOISE1 +// 1 lookup float get_height(vec2 uv) { float height = 0.0; - vec3 region = get_regionf(uv); + vec3 region = get_region_uv2(uv); if (region.z >= 0. && abs(uv.x) < float(_region_uv_limit) && abs(uv.y) < float(_region_uv_limit)) { height = texture(_height_maps, region).r; } @@ -58,6 +72,54 @@ float get_height(vec2 uv) { return height; } +void vertex() { + // Get vertex of flat plane in world coordinates and set world UV + v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; + + // UV coordinates in world space. Values are 0 to _region_size within regions + UV = v_vertex.xz; + + // UV coordinates in region space + texel offset. Values are 0 to 1 within regions + UV2 = (UV + vec2(0.5)) * _region_texel_size; + + // Get final vertex location and save it + VERTEX.y = get_height(UV2); + v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; + + // Flatten normal to be calculated in fragment() + NORMAL = vec3(0, 1, 0); +} + +//////////////////////// +// Fragment +//////////////////////// + +// 4 lookups +vec3 get_normal(vec2 uv, out vec3 tangent, out vec3 binormal) { + float left = get_height(uv + vec2(-_region_texel_size, 0)); + float right = get_height(uv + vec2(_region_texel_size, 0)); + float back = get_height(uv + vec2(0, -_region_texel_size)); + float front = get_height(uv + vec2(0, _region_texel_size)); + vec3 horizontal = vec3(2.0, right - left, 0.0); + vec3 vertical = vec3(0.0, back - front, 2.0); + vec3 normal = normalize(cross(vertical, horizontal)); + normal.z *= -1.0; + tangent = cross(normal, vec3(0, 0, 1)); + binormal = cross(normal, tangent); + return normal; +} + +vec3 unpack_normal(vec4 rgba) { + vec3 n = rgba.xzy * 2.0 - vec3(1.0); + n.z *= -1.0; + return n; +} + +vec4 pack_normal(vec3 n, float a) { + n.z *= -1.0; + return vec4((n.xzy + vec3(1.0)) * 0.5, a); +} + float random(in vec2 xy) { return fract(sin(dot(xy, vec2(12.9898, 78.233))) * 43758.5453); } @@ -68,7 +130,7 @@ float blend_weights(float weight, float detail) { return result; } -vec4 depth_blend(vec4 a_value, float a_bump, vec4 b_value, float b_bump, float t) { +vec4 height_blend(vec4 a_value, float a_bump, vec4 b_value, float b_bump, float t) { float ma = max(a_bump + (1.0 - t), b_bump + t) - 0.1; float ba = max(a_bump + (1.0 - t) - ma, 0.0); float bb = max(b_bump + t - ma, 0.0); @@ -79,29 +141,13 @@ vec2 rotate(vec2 v, float cosa, float sina) { return vec2(cosa * v.x - sina * v.y, sina * v.x + cosa * v.y); } -vec3 get_normal(vec2 uv, out vec3 tangent, out vec3 binormal) { - // Normal calc - // Control map is also sampled 4 times, so in theory we could reduce the region samples to 4 from 8, - // but control map sampling is slightly different with the mirroring and doesn't work here. - // The region map is very, very small, so maybe the performance cost isn't too high - float left = get_height(uv + vec2(-_region_pixel_size, 0)); - float right = get_height(uv + vec2(_region_pixel_size, 0)); - float back = get_height(uv + vec2(0, -_region_pixel_size)); - float fore = get_height(uv + vec2(0, _region_pixel_size)); - vec3 horizontal = vec3(2.0, right - left, 0.0); - vec3 vertical = vec3(0.0, back - fore, 2.0); - vec3 normal = normalize(cross(vertical, horizontal)); - normal.z *= -1.0; - tangent = cross(normal, vec3(0, 0, 1)); - binormal = cross(normal, tangent); - return normal; -} - +// 2-4 lookups vec4 get_material(vec2 uv, vec4 index, vec2 uv_center, float weight, inout float total_weight, inout vec4 out_normal) { float material = index.r * 255.0; float r = random(uv_center) * PI; float rand = r * _texture_uv_rotation_array[int(material)]; vec2 rot = vec2(cos(rand), sin(rand)); + uv *= .5; // Allow larger numbers on uv scale array - move to C++ vec2 matUV = rotate(uv, rot.x, rot.y) * _texture_uv_scale_array[int(material)]; vec4 albedo = texture(_texture_array_albedo, vec3(matUV, material)); @@ -121,8 +167,8 @@ vec4 get_material(vec2 uv, vec4 index, vec2 uv_center, float weight, inout float n = unpack_normal(normal2); normal2.xz = rotate(n.xz, rot2.x, -rot2.y); - albedo = depth_blend(albedo, albedo.a, albedo2, albedo2.a, index.b); - normal = depth_blend(normal, albedo.a, normal2, albedo2.a, index.b); + albedo = height_blend(albedo, albedo.a, albedo2, albedo2.a, index.b); + normal = height_blend(normal, albedo.a, normal2, albedo2.a, index.b); } normal = pack_normal(normal.xyz, normal.a); @@ -132,16 +178,8 @@ vec4 get_material(vec2 uv, vec4 index, vec2 uv_center, float weight, inout float return albedo * weight; } -void vertex() { - vec3 world_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; - UV = world_vertex.xz * 0.5; // Without this, individual material UV needs to be very small. - UV2 = ((world_vertex.xz + vec2(0.5)) / vec2(_region_size)); - VERTEX.y = get_height(UV2); - NORMAL = vec3(0, 1, 0); -} - void fragment() { - // Calculate Terrain World Normals + // Calculate Terrain Normals. 4 lookups vec3 w_tangent, w_binormal; vec3 w_normal = get_normal(UV2, w_tangent, w_binormal); NORMAL = mat3(VIEW_MATRIX) * w_normal; @@ -149,24 +187,29 @@ void fragment() { BINORMAL = mat3(VIEW_MATRIX) * w_binormal; // Calculated Weighted Material - // source : https://github.com/cdxntchou/IndexMapTerrain - // black magic which I don't understand at all. Seems simple but what and why? - vec2 pos_texel = UV2 * _region_size + 0.5; - vec2 pos_texel00 = floor(pos_texel); - vec4 mirror = vec4(fract(pos_texel00 * 0.5) * 2.0, 1.0, 1.0); + // https://github.com/cdxntchou/IndexMapTerrain/blob/master/Assets/Terrain/Shaders/IndexedTerrainShader.shader + vec2 texel_pos = UV; + vec2 texel_pos_floor = floor(UV); + + // Create a cross hatch grid of alternating 0/1 horizontal and vertical stripes 1 unit wide in XY + vec4 mirror = vec4(fract(texel_pos_floor * 0.5) * 2.0, 1.0, 1.0); + // And the opposite grid in ZW mirror.zw = vec2(1.0) - mirror.xy; - ivec3 index00UV = get_region((pos_texel00 + mirror.xy) * _region_pixel_size); - ivec3 index01UV = get_region((pos_texel00 + mirror.xw) * _region_pixel_size); - ivec3 index10UV = get_region((pos_texel00 + mirror.zy) * _region_pixel_size); - ivec3 index11UV = get_region((pos_texel00 + mirror.zw) * _region_pixel_size); + // Get the region and control map ID of four vertices surrounding this pixel + ivec3 index00UV = get_region_uv(texel_pos_floor + mirror.xy); + ivec3 index01UV = get_region_uv(texel_pos_floor + mirror.xw); + ivec3 index10UV = get_region_uv(texel_pos_floor + mirror.zy); + ivec3 index11UV = get_region_uv(texel_pos_floor + mirror.zw); - vec4 index00 = texelFetch(_control_maps, index00UV, 0); - vec4 index01 = texelFetch(_control_maps, index01UV, 0); - vec4 index10 = texelFetch(_control_maps, index10UV, 0); - vec4 index11 = texelFetch(_control_maps, index11UV, 0); + vec4 control00 = texelFetch(_control_maps, index00UV, 0); + vec4 control01 = texelFetch(_control_maps, index01UV, 0); + vec4 control10 = texelFetch(_control_maps, index10UV, 0); + vec4 control11 = texelFetch(_control_maps, index11UV, 0); - vec2 weights1 = clamp(pos_texel - pos_texel00, 0, 1); + // Calculate weight for the pixel position between the vertices + // Bilinear interpolate difference of UV and floor UV + vec2 weights1 = clamp(texel_pos - texel_pos_floor, 0, 1); weights1 = mix(weights1, vec2(1.0) - weights1, mirror.xy); vec2 weights0 = vec2(1.0) - weights1; @@ -174,16 +217,17 @@ void fragment() { vec4 normal = vec4(0.0); vec4 color = vec4(0.0); - color = get_material(UV, index00, vec2(index00UV.xy), weights0.x * weights0.y, total_weight, normal); - color += get_material(UV, index01, vec2(index01UV.xy), weights0.x * weights1.y, total_weight, normal); - color += get_material(UV, index10, vec2(index10UV.xy), weights1.x * weights0.y, total_weight, normal); - color += get_material(UV, index11, vec2(index11UV.xy), weights1.x * weights1.y, total_weight, normal); + // Accumulate material. 8-16 lookups + color = get_material(UV, control00, vec2(index00UV.xy), weights0.x * weights0.y, total_weight, normal); + color += get_material(UV, control01, vec2(index01UV.xy), weights0.x * weights1.y, total_weight, normal); + color += get_material(UV, control10, vec2(index10UV.xy), weights1.x * weights0.y, total_weight, normal); + color += get_material(UV, control11, vec2(index11UV.xy), weights1.x * weights1.y, total_weight, normal); total_weight = 1.0 / total_weight; normal *= total_weight; color *= total_weight; - // Apply Colormap - vec3 ruv = get_regionf(UV2); + // Apply Colormap. 1 lookup + vec3 ruv = get_region_uv2(UV2); vec4 color_tex = vec4(1., 1., 1., .5); if (ruv.z >= 0.) { color_tex = texture(_color_maps, ruv); diff --git a/src/shaders/world_noise.glsl b/src/shaders/world_noise.glsl index 623c8602a..ae60d4f1d 100644 --- a/src/shaders/world_noise.glsl +++ b/src/shaders/world_noise.glsl @@ -33,6 +33,7 @@ float noise2D(vec2 st) { } // World Noise end + //INSERT: WORLD_NOISE2 // World Noise float weight = texture(_region_blend_map, (uv/float(_region_map_size))+0.5).r;