diff --git a/Effects/ws30.eff b/Effects/ws30.eff index 0333ba89f..474cf5247 100644 --- a/Effects/ws30.eff +++ b/Effects/ws30.eff @@ -292,6 +292,7 @@ Shaders/filters-ALS.vert Shaders/shadows-include.vert Shaders/ws30-ALS-ultra.frag + Shaders/ws30-ALS-landclass-search-functions.frag Shaders/noise.frag Shaders/cloud-shadowfunc.frag Shaders/hazes.frag diff --git a/Shaders/ws30-ALS-landclass-search-functions.frag b/Shaders/ws30-ALS-landclass-search-functions.frag new file mode 100644 index 000000000..c9a6de062 --- /dev/null +++ b/Shaders/ws30-ALS-landclass-search-functions.frag @@ -0,0 +1,1002 @@ +// WS30 FRAGMENT SHADER +// -*-C++-*- +#version 130 +// WS30 terrain - Landclass search functions used by fragment shaders +// Split off from from ws30-ALS-ultra.frag + + +////////////////////////////////////////////////////////////////// +// TEST PHASE TOGGLES AND CONTROLS FOR PROFILING ON DIFFERENT GPUS +// + +// Instructions for power users: +// Change the numbers for values and controls, save the file, and +// reload shaders to compare the difference. +// In-sim menu > debug > configure development extensions > reload shaders. +// It's safe to tinker with things in this section. +// At worst the terrain will look odd. If there's an error or a stray +// character, the shader will just not compile - the terrain changes +// appearance to black. +// Simply try again and hit reload shaders button. You can also save a backup +// of this file. +// "//" means everything on that line is a comment. +// Lines assigning numbers to variables end in a ";" + +// Testing performance: +// Use the UFO or video assistant. Turn all scenery layers off (vegetation, +// buildings, random scenery objects etc.). +// The terrain quality shader level determines the shaders used. Set it to ultra. +// The view should be 100% terrain. No sky. Try to minimise parts of terrain +// or scenery rendered by other shaders, as the results will be inaccurate. +// Avoid clouds in view. Minimise water in view. +// WS3 OSM roads must be turned off to get accurate results: +// in-sim menu > view > adjust LoD ranges > set all "Minimum Line Feature +// Width" sliders to 50m (move sliders slightly if initial value = 9999). +// Going closer or reducing FoV works, but these also can change performance. +// Looking towards the horizon at high altitude may cause perfomance to be CPU +// bound due to OSG scene traveral. +// Remember to mention factors that change performance: +// resolution, the rough area (regional definitions at work), altitude, +// WS3 scenery package, AA settings, driver control panel settings and overrides +// - (you can set these to application controlled) + + +// Comparing performance: +// GPU bound: To use FPS to compare performance you must be bottlenecked by +// the GPU (GPU bound). +// When GPU bound (fragment bound), changing the window size slightly should +// result in a change in FPS. +// You can also tell if you are GPU bound, as GPU utilisation will be 100%. +// Change in performance = FPS2/FPS1. +// e.g. increase of 15 FPS to 30 FPS = 30/15 = 2x or 200% increase. +// Not GPU bound: If your bottleneck is not the GPU, you need to measure +// GPU utilisation. +// GPU utilisation is the GPU load - it will be less than 100%. +// Change in performance = utilisation1/utilisation2. +// e.g. Drop from 40% to 20% = 40/20 = 2x increase in performance or +// doubling of FPS. e.g 40% to 30% = 40/30 = 1.33x increase. +// CPU bound FPS limit: You can usually find your CPU bound FPS limit by +// reducing window size until FPS stops increasing. This depends on what's +// in view. +// To Compare performace with WS2: untick the WS3 tick box in render settings, +// and make sure both WS2 and WS3 are GPU bound, and not CPU bound. + + +// +// Note: +// Ensure in-sim menu > view > rendering options > throttle FPS is off. +// Ensure vsync is off. +// Ensure WS3 OSM roads are off (see testing performance section). +// Make sure your power plan is set to maximum or balanced in Windows, or +// results could be inaccurate - laptops may be on a power saving plan +// by default. + +// To test: All transitions are off by default. Set both remove squareness and +// enable large scale transitions to 1 to get a quality with most features +// turned on. + +// Maximum number of neighbor landclass textures to lookup if neighbors are found. +// The more landclass textures are looked up the more pressure on VRAM. +// Performance hit varies by altitude and how small the landclass blobs are. +// More ground texture lookups may run slower on older generation GPUs - test and see. +// Possible values: 0,1,2. Default: 2. To see texture mixing transitions you need 1 or 2. + const int max_neighbor_landclass_texture_lookups = 2; + + + +// Small scale transition controls + +// Remove squareness due to landclass texture by growing higher priority neighbors. +// This adds 2 extra lookups of the landclass texture, and one math/noise lookup. +// Large scale transition searches do not use this - as it triples the number of +// landclass texture acceses, as well as adding 1 noise lookup per search point. +// This should be expected to be used on old GPUs, except when running at the absolute +// lowest graphics quality. It's faster than large scale transition searches. +// Possible values: 1:enabled, 0:disabled. Default:0 + const int remove_squareness_from_landclass_texture = 0; + +// Transition at landclass texel scale +// Mix in neighbor textures so landclass boundaries are not hard at the +// landclass texel scale. +// Note: Disable enable large scale transition search, if using this. +// This needs extra ground texture lookups. It looks fine with 1 extra lookup. +// This can be combined with removing squareness by growing borders. +// Possible values: 1:enabled, 0:disabled + const int use_landclass_texel_scale_transition_only = 0; + + + +// Large scale transition controls + +// Enable large scale transitions: 1=on, 0=off +// Disable use landclass texel scale transition, if using this. + const int enable_large_scale_transition_search = 0; + + +// The search pattern is center + n points in four directions forming a cross. +// e.g. 1 search point = 1 + 4 * 1 = 5 points total. +// 4 search points: 17 total. 10 search points = 41 +// The transition distance is the distance from the center to the furtherst +// point in any direction. + + +// Landclass transition search distance in meters +// Note: transitions occur on both sides of the landclass borders. +// The width of the transition is equal to 2x this value. +// Default: 100m + const float transition_search_distance_in_m = 100.0; + +// Number of points to search in any direction, in addition to this fragment +// Default:4 points. Fewer points results in a less smooth transition (more banding) +// Choose the lowest number of points to get a desired transition quality. + const int num_search_points_in_a_direction = 4; + + + + +// Landclass transition weightings options +// More options mean slightly more GPU math load +// +// Use 2nd closest neighbour for transition weighting. +// Note this won't lookup a ground texture by itself, just sort through results +// Possible values: 1:enable, 0=disable. Default: 1 + const int enable_2nd_closest_neighbor_for_large_scale_transition_weights = 0; + +// Enable dithering to smooth transitions by reducing visible banding +// Possible values: 1=enable, 0=disable. Default = 1 + const int enable_dithering_for_large_scale_transitions = 1; + +// Scale of dithering as a faction of the size of the bands - distance between +// search points (=transition distance / number of steps) +// 0.2 seems to work ok - not real need to tinker with this. +// Different values won't change performance. + const float dithering_noise_wavelength_as_fraction_of_step_size = 0.2; + +// Grow the borders of landclasses a bit when large scale transitions are used +// Higher priority landclasses grow onto lower priority ones. +// Landclass numbers are used as a placeholder for landclass priority. +// This works by changing the weighting in the transition region using a +// noise lookup +// Possibe values: 0=off, 1=on. Default:0 + const int grow_landclass_borders_with_large_scale_transition = 0; + + + +////////////////////////////////////////////////////////////////// +// Advanced controls - these are for testing scenery generation and rendering + + +// Landclass source: +// Possible values: Default=1; +// 0=Normal landclass texture, 1 = Random landclass squares along s and t axes. +// Choose 1 to test impact of searching a texture. You should normally leave +// it at default. + const int landclass_source = 0; + +// Random landclass square size in meters. Remember to adjust transition search distance. +// Default: 200m + const float random_landclass_square_size_in_m = 3.3*transition_search_distance_in_m; + +// Detiling noise source +// Possible values: 0 = texture source, 1 = math source +// The texture source still shows some tiling. The math source detiles better, but might +// be slightly slower. + const int detiling_noise_type = 0; + +// Development tools - 2 controls, now located at the top of WS30-ALS-ultra.frag: +// 1. Reduce haze to almost zero, while preserving lighting. Useful for observing distant tiles. +// Keeps the calculation overhead. This can be used for profiling. +// 2. Remove haze and lighting and shows just the texture. +// Useful for checking texture rendering and scenery. +// The compiler will likely optimise out the haze and lighting calculations. +// +// End of test phase controls +////////////////////////////////////////////////////////////////// + + + + + + + + + + + + + + + + + + + + + + + + + + + +////////////////////////// +// Test-phase code: + +// Uniforms used by landclass search functions. +// If any uniforms change name or form, remember to update here and in fragment shaders. + +uniform sampler2D landclass; +uniform sampler2DArray textureArray; +uniform sampler1D dimensionsArray; +uniform sampler1D diffuseArray; +uniform sampler1D specularArray; +uniform sampler2D perlin; + +// Passed from VPBTechnique, not the Effect +uniform int tile_level; +uniform float tile_width; +uniform float tile_height; + +// These should be sent as uniforms + +// Tile dimensions in meters +// vec2 tile_size = vec2(tile_width , tile_height); +// Testing: texture coords are sent flipped right now: +vec2 tile_size = vec2(tile_height , tile_width); + +// These are defined in noise.frag +float rand2D(in vec2 co); +float Noise2D(in vec2 coord, in float wavelength); + +// Create random landclasses without a texture lookup to stress test. +// Each square of square_size in m is assigned a random landclass value. +int get_random_landclass(in vec2 co, in vec2 tile_size) +{ + float r = rand2D( floor(vec2(co.s*tile_size.x, co.t*tile_size.y)/random_landclass_square_size_in_m) ); + int lc = int(r*48.0); // only 48 landclasses mapped so far + return lc; +} + + +// Look up texture coordinates and stretching scale of ground textures +void get_ground_texture_data(in float textureIndex, in vec2 tile_coord, + out vec2 st, out vec2 g_texture_scale, in out vec2 dx, in out vec2 dy) +{ + // Look up stretching dimensions of ground textures in m - scaled to + // fit in [0..1], so rescale + vec2 g_texture_stretch_dim = 10000.0 * texture(dimensionsArray, textureIndex).st; + g_texture_scale = tile_size.xy / g_texture_stretch_dim.xy; + // Correct partial derivatives to account for stretching of different textures + dx = dx * g_texture_scale; + dy = dy * g_texture_scale; + // Ground texture coords + st = g_texture_scale * tile_coord.st; +} + + +// Rotate texture using the perlin texture as a mask to reduce tiling. +// type=0: use perlin texture, type = 1: use Noise2D to avoid texture lookup +// Testing: if this or get_ground_texture_data used in final WS3 to handle +// many base texture lookups, see if optimising to handle many inputs helps +// (vectorising Noise2D versus just many texture calls) + +vec2 detile_texcoords_with_perlin_noise(in vec2 st, in vec2 ground_texture_scale, + in vec2 tile_coord, in out vec2 dx, in out vec2 dy) +{ + vec2 pnoise; + + // Ratio tile dimensions are stretched relative to s. + // Tiles may not have equal dimensions. + vec2 stretch_r = tile_size.st/tile_size.s; + + // Note: unresolved texture discontinuties (i.e. mipmap problems) with unequal stretch factors + const vec2 local_stretch_factors = vec2(8.0, 8.0 /*16.0*/); + + if (detiling_noise_type==1) + { + pnoise[0] = texture(perlin, st / local_stretch_factors[0]).r; + pnoise[1] = texture(perlin, - st / local_stretch_factors[1]).r; + } + else + { + //Testing: Non texture alternative + + // Estimate of wavelength in /Textures/perlin.png in normalised texture coords + const float ptex_wavelength = (1.0/7.0); + + pnoise[0] = Noise2D(st / (local_stretch_factors[0]), ptex_wavelength); + pnoise[1] = Noise2D(-st / (local_stretch_factors[1]), ptex_wavelength); + } + + if (pnoise[0] >= 0.5) + { + st = ground_texture_scale.st * (tile_coord * stretch_r).ts; + // Get back original partial derivatives by undoing + // previous texture stretching adjustment done in get_ground_data + dx = dx / ground_texture_scale.st; + dy = dy / ground_texture_scale.st; + // Recalculate new derivatives + dx = dx.ts * ground_texture_scale.st * stretch_r.ts; + dy = dy.ts * ground_texture_scale.st * stretch_r.ts; + + } + + if (pnoise[1] >= 0.5) + { + st = -st; dx = -dx; dy = -dy; + } + return st; +} + + +// Lookup a ground texture at a point based on the landclass at that point, without visible +// seams at coordinate discontinuities or at landclass boundaries where texture are switched. +// The partial derivatives of the tile_coord at the fragment is needed to adjust for +// the stretching of different textures, so that the correct mip-map level is looked +// up and there are no seams. + +vec4 lookup_ground_texture_array(in float index, in vec2 tile_coord, in int landclass_id, + in vec2 dx, in vec2 dy) +{ + // Testing: may be able to save 1 or 2 op slots by combining dx/dy in a vec4 and + // using swizzles which are free, but mostly operations are working independenly on s and t. + // Only 1 place so far that just multiplies everything by a scalar. + vec2 st; + vec2 g_texture_scale; + vec4 texel; + int lc = landclass_id; + + get_ground_texture_data(index, tile_coord, st, g_texture_scale, dx, dy); + + + st = detile_texcoords_with_perlin_noise(st, g_texture_scale, tile_coord, dx, dy); + + //texel = texture(textureArray, vec3(st, lc)); + //texel = textureLod(textureArray, vec3(st, lc), 12.0); + texel = textureGrad(textureArray, vec3(st, lc), dx, dy); + return texel; +} + +// Landclass sources: texture or random +int read_landclass_id(in vec2 tile_coord) +{ + vec2 dx = dFdx(tile_coord.st); + vec2 dy = dFdy(tile_coord.st); + int lc; + + if (landclass_source == 0) lc = (int(texture2D(landclass, tile_coord.st).g * 255.0 + 0.5)); + else lc = (get_random_landclass(tile_coord.st, tile_size)); + return lc; +} + + +int read_landclass_id_non_pixelated(in vec2 tile_coord, + const in float landclass_texel_size_m) +{ + vec2 c0 = tile_coord; + vec2 sz = tile_size; + vec2 tsz = vec2(landclass_texel_size_m)/tile_size; + + // Landclass sources: texture or random + int lc = read_landclass_id(c0); + + return lc; +} + + +// Determine whether to grow a neighbor landclass onto current. +// 1 = grow neighbor, 0 = don't grow neighbor +float get_growth_priority(in int current_landclass, in int neighbor_landclass) +{ + int lc1 = current_landclass; + int lc2 = neighbor_landclass; + return ((lc1 < lc2)?1.0:0.0); +} + + +// Determine whether to grow a one of 2 neighbor landclasses onto current. +// 1 = grow neighbor, 0 = don't grow neighbor +float get_growth_priority(in int current_landclass, in int neighbor_landclass1, in int neighbor_landclass2) +{ + int lc1 = current_landclass; + int lc2 = neighbor_landclass1; + int lc3 = neighbor_landclass2; + return ((lc1 < max(lc2,lc3))?1.0:0.0); +} + + + +int lookup_landclass_id(in vec2 tile_coord, in vec2 dx, in vec2 dy, + out ivec4 neighbor_texel_landclass_ids, + out int number_of_unique_neighbors_found, out vec4 landclass_neighbor_texel_weights) +{ + + // To do: fix landclass border artifacts, with all shaders. do small scale texel mixing for 2 neighbors + + // Number of unique neighbours found + int num_n = 0; + + vec2 c0 = tile_coord; + vec2 sz = tile_size; + + // Landclass sources: texture or random + int lc = read_landclass_id(c0); + int output_landclass = lc; + + // Landclasses of up to 4 neighbor texels + ivec4 lc_n = ivec4(lc); + + // Landclasses sorted + ivec4 lc_n_s; + + // Combined weight from 2 neighbor texels + float w = 0.0; + + // Landclass neighbor weights - for texel mixing only + vec4 lc_n_w = vec4(0.0); + +// Test phase controls +if ( (remove_squareness_from_landclass_texture == 1) || + ( (use_landclass_texel_scale_transition_only == 1) && + (enable_large_scale_transition_search == 0) ) + ) +{ + + + // Remove squareness from the landclass texture due to nearest neighbour interpolation + // A landclass with higher growth priority grows on to an adjacent landclass + // with lower priority + + // Landclass texture dimensions, in texels - Needs glsl 1.30+ + // Probably best to just send as uniforms if texture sizes don't vary, or are fixed per terrain LoD level. + vec2 texture_dim_tx = vec2(textureSize(landclass, 0)); + + // Coordinates of current fragment, in texels + vec2 c0_tx = c0 * texture_dim_tx; + + // Coordinates of the center of the current texel, in texels + // centers are n+(0.5,0.5) for n = 0,1,2... + vec2 ct_c0_tx = floor(c0_tx) + 0.5; + // center in normalised tex coords + vec2 ct_c0 = ct_c0_tx/texture_dim_tx; + + // Landclass at center of current texel - same anywhere within a texel + int lc_ct = lc; + + // Coords of centers of closest neighbors, in texels + vec2 c_n_tx[2]; + + + // Coordinate of fragment relative to center of texel, in texels + vec2 c0_rel_ct_tx = c0_tx - ct_c0_tx; + + float dist_ct = length(c0_rel_ct_tx); + + // need to avoid division by 0? + if (dist_ct < 0.00001) dist_ct += 0.00001; + + // Choose closest neighbor based on angle wrt. to + // c0, center of texel, and s & t axes. + // Choose the texel in the direction of the largest s & t component. + // NB: This method will select a diagonal neighbor if + // both components of c0_rel_ct_tx are equal to cos(45). + // Testing: look for a way that uses fewer instructions, + // maybe calculating 2 neighbors at once using a vec4. + + //vec2 a = abs(c0_rel_ct_tx); + //offset_ct0 = ((a.s > a.t)?vec2(1.0,0.0):vec2(0.0,1.0))*sign(c0_rel_ct_tx); + + // Vectorisable + const float cos45deg = cos(radians(45.0)); + vec2 offset_ct0 = step(cos45deg, abs(c0_rel_ct_tx/dist_ct)) + *sign(c0_rel_ct_tx); + + c_n_tx[0] = (ct_c0_tx + offset_ct0); + + // Landclass of closest neighbor + lc_n[0] = read_landclass_id(c_n_tx[0]/texture_dim_tx); + + + // Choose 2nd closest neighbor + // Choose texels in the direction of the smaller of s & t components + vec2 offset_ct1 = abs(offset_ct0.ts)*step(0.0, abs(c0_rel_ct_tx/dist_ct)) + * sign(c0_rel_ct_tx); + + c_n_tx[1] = (ct_c0_tx + offset_ct1); + + // Land class of 2nd closest neighbor + lc_n[1] = read_landclass_id(c_n_tx[1]/texture_dim_tx); + + + // Distinct neighbors found + // Testing: possible optimisation, use booleans. + // Needs ivec4/1.30+, or vec4/1.20 - reliably supported by old compilers? + // bvec4 n_found = notEqual(lc_n, ivec4(lc)); + + ivec4 n_found = ivec4(((lc_n[0] != lc)?1:0), ((lc_n[1] != lc)?1:0), 0, 0); + + num_n = n_found[0]+ n_found[1]; + + + //if (any(n_found)) + if ((n_found[0] == 1) || (n_found[1] == 1)) + { + + // Weights for influence from neighbor landclasses + // The distance away from the neighbor texel side is used to determine influence. + // w_n: 0.5 at minimum possible distance, 0.0 at maximum possible distance + + // Neighbor weights + vec4 w_n = vec4(0.0); + + + // Method 1: + // Use distance from side of neighbor texel for mixing + // This is has some issues, including with corners + + vec2 dir0 = offset_ct0; + vec2 dir1 = offset_ct1; + + // Distance from side of neighbor texel, in texels + vec2 d0 = dir0*c0_rel_ct_tx; + vec2 d1 =dir1*c0_rel_ct_tx; + + w_n[0] = max(d0.x, d0.y); + w_n[1] = max(d1.x, d1.y); + + + +/* + //Method 2: + // use distance from center of neighbor texel for mixing + // This doesn't really give better results than 1 + + // Distance from center of neighbor texels, in texels + vec2 dist_n_tx = vec2(0.0); + + dist_n_tx[0] = length(c0_tx - c_n_tx[0]); + dist_n_tx[1] = length(c0_tx - c_n_tx[1]); + + + // Weighting for closest neighbor - [0.5 to 0.0] as distance goes from [min to max] + const float max_dist = sqrt(0.5*0.5+1.0); + const float min_dist = 0.5; + w_n[0] = (dist_n_tx[0]-min_dist)/(max_dist - min_dist); + w_n[0] = mix(0.5, 0.0, w_n[0]); + + + // Weighting for 2nd closest neighbor - [0.5 to 0.0] as distance goes from [min to max] + //const float max_dist1 = sqrt(0.5*0.5+1.0); + //const float min_dist = 0.5; + w_n[1] = (dist_n_tx[1]-min_dist)/(max_dist-min_dist); + w_n[1] = mix(0.5, 0.0, w_n[1]); +*/ + + + // Use weighting only if neighbour is different from landclass for this fragment + // Testing: Can be omitted if not doing texture mixing as it doesn't really + // make a difference. + w_n = w_n * vec4(n_found); + + // Combined weighting - increase w_n[0] if the 2nd closest neigbour is + // different such that w_n[0] remains under 0.5 + w = w_n[0]; + w = w + (0.5-w)*2.0*w_n[1]; + + // Sort landclasses and weights + lc_n_s = lc_n; + // If closest neighbour is lc, move 2nd closest neighbour to closest slot, and + // clear the 2nd closest slot + if (n_found[0] == 0) + { + lc_n_s.xy = lc_n.yz; + w_n.xy = w_n.yz; + } + + +// Testing phase controls +if ( (use_landclass_texel_scale_transition_only == 1) && + (max_neighbor_landclass_texture_lookups > 0) && + (enable_large_scale_transition_search == 0) + ) +{ + // Assign mix factors for transitions by mixing texels + // [0]: 0 to 0.5 + // [1]: split [0 to 1] between closest and 2nd closest landclass + lc_n_w[0] = w; + lc_n_w[1] = w_n[1]/(w_n[0]+w_n[1]); +} + + +} // Testing controls: End if ((remove_squareness_from_landclass_texture == 1) || (use_landclass_texel_scale_transition_only == 1)) + + +if (remove_squareness_from_landclass_texture == 1) +{ + // Turn neighbor growth off at longer ranges, otherwise there is flickering noise + // Testing: The exact cutoff could be done sooner to save some performance - needs + // to be part of a larger solution to similar issues. User should set a tolerance factor. + float lod_factor = min(length(vec2(dx.s, dy.s)),length(vec2(dx.t, dy.t))); + // Estimate of frequency of growth noise in texels - i.e. how many peaks and troughs fit in one texel + const float frequency_g_n = 1000.0; + const float cutoff = 1.0/frequency_g_n; + + if (lod_factor < cutoff) + { + // Decide whether to grow neighbor on to lc + float grow_n = get_growth_priority(lc,lc_n[0]); + + // Noise on the scale of landclass texels in the texture + // Testing: reduce instructions if this method is to be used. + // To look at: corner visuals & sharp diagonals. + + // Minimum wavelength of transition noise + const float wl_tn = (1.0/8.0); + float tn = Noise2D(c0*texture_dim_tx, wl_tn); // old val 1.6 + + float threshold = mix(1.0,0.0, w); + + float neighbor_growth_weight = (0.3*2.0)*w*step(threshold-0.15, tn); + + // Growth factor + float g = ((grow_n > 0.0)?neighbor_growth_weight:-neighbor_growth_weight); + //g = sqrt(abs(g))*sign(g); + + // Neighbor growth value + float v; + //v=w+g; + v = w*(0.7+50.5*g); + + // Whether or not to grow neighbour onto nearby pixel + + // To do - mix factor between different neighbour lanclasses + // when using an extra ground texture lookup + + if (v > 0.5) output_landclass = lc_n_s[0]; + + + + +// Testing phase controls +if ( (use_landclass_texel_scale_transition_only == 1) && + (max_neighbor_landclass_texture_lookups > 0) && + (enable_large_scale_transition_search == 0) + ) +{ + + lc_n_w[0] = 0.0; +/* + // Adjust mix factor weights and swap landclasses for extrusions + + // Method 1: + + lc_n_w[0] = (w-0.5*neighbor_growth_weight); + if (v > 0.5) lc_n_s[0] = lc; +*/ + + + // Method 2: + // Mix in neighbour texel, instead of change output landclass. + + // Undo previous output class assignment + output_landclass = lc; + + // Reduce flickering noise due to small detail added when far away. Contrasting colors mean more visible issues. + // Fade 0 to 1 as lod_factor goes from 1.0 to 4.0 + // The goal is to avoid flickering with worst case texture filtering and supersampling. + // Testing: However, the quicker the detil fades, the more square distant ladnclasses look. + // Right now the noise function generates too many high frequency components (small detail) + + //const float mmax = 4000.0; const float mmin = mmax-1000.0; /* no flickering */ + const float mmax = 3000.0; const float mmin = mmax-1000.0; /* bit of filckering */ + float fade = smoothstep(mmin, mmax, 1.0/lod_factor); + + lc_n_w[0] = (w-0.5*3.333*0.9*(neighbor_growth_weight*fade)); + if (v > 0.5) lc_n_w[0] = w+0.4*fade; + + + +} + + + + } // End if (lod_factor > some value) + + +} // Testing code: End if (remove_squareness_from_landclass_texture == 1) + + + + } // End if (nfound[0] == 1) || (n_found[1] == 1) + + + + + landclass_neighbor_texel_weights = lc_n_w; + neighbor_texel_landclass_ids = lc_n_s; + number_of_unique_neighbors_found = num_n; + return output_landclass; + +} + + + +// Look up the landclass id [0 .. 255] for this particular fragment. +// Lookup id of any neighbouring landclass that is within the search distance. +// Searches are performed in upto 4 directions right now, but only one landclass is looked up +// Create a mix factor werighting the influences of nearby landclasses + +void get_landclass_id(in vec2 tile_coord, + const in float landclass_texel_size_m, in vec2 dx, in vec2 dy, + out int landclass_id, out ivec4 neighbor_landclass_ids, + out int num_unique_neighbors,out vec4 mix_factor + ) +{ + // Each tile has 1 texture containing landclass ids stetched over it + + // Landclass source type: 0=texture, 1=random squares + // Controls are defined at global scope. const int landclass_source + const float ts = landclass_texel_size_m; + vec2 sz = tile_size; + + // Number of unique neighbors found + int num_n = 0; + + // Only used for mixing textures of neighboring texels: + // Landclass ids of neigbors in neighboring texels + ivec4 lc_n_tx; + // Weights of neighbour landclass texels + vec4 lc_n_w; + // Number of unique neighbors in neighboring texels + int num_n_tx = 0; + + int lc = lookup_landclass_id(tile_coord, dx, dy, lc_n_tx, num_n_tx, lc_n_w); + + // Neighbor landclass ids + ivec4 lc_n = ivec4(lc); + + // Mix factors: texels are mixed in from furthest, to closest + // mfact[1]: [0 to 1] mixing 1st and 2nd closest texels + // mfact[0]: [0 to 0.5] texel and previous neighbour contributions + vec4 mfact = vec4(0.0); + + +// Testing phase controls +if ( (use_landclass_texel_scale_transition_only == 1) && + (max_neighbor_landclass_texture_lookups > 0) && + (enable_large_scale_transition_search == 0) + ) + +{ + // Use the ground texture lookups to do a transition on the scale of + // the landclass textures instead of doing a large scale transition + num_n = num_n_tx; + lc_n = lc_n_tx; + mfact = lc_n_w; +} + + +// Testing phase controls +if ( (enable_large_scale_transition_search == 1) && + (max_neighbor_landclass_texture_lookups > 0) && + (use_landclass_texel_scale_transition_only == 0) + ) +{ + + + // Transition search + + const int n = num_search_points_in_a_direction; + + const float search_dist = transition_search_distance_in_m; + vec2 step_size_m = vec2(search_dist/float(n)); + // step size in tile coords + vec2 steps = step_size_m.st / tile_size.st; + + vec2 c0 = tile_coord; + + // Min number of points (loop counter value (i)) before + // a different landclass is found + ivec4 mi = ivec4(n+1); + + // landclass - l can be accessed as an array e.g. l[0]=l.x + ivec4 l = ivec4(lc); + + // Search in 4 directions. These for loops likely need unrolling, + // and optimising to use minimum instructions, if they are + // to be used outside of testing the search concept. + // The texture access patterns may be suboptimal as well. + // Travelling along s and t axes might work better. + // Note: this returns the closest neighbor. There could be blobs + // of multiple neighbors, or a tiny islands of neighbors among this + // landclass. + + + // +s direction + vec2 dir = vec2(steps.s, 0.0); + + for (int i=1;i<=n;i++) { + vec2 c = c0+float(i)*dir; + int v = read_landclass_id(c); + if ((v != lc) && (mi[0] > n)) {l[0] = v; mi[0] = i; } + } + + + // -s direction + dir = vec2(-steps.s, 0.0); + for (int i=1;i<=n;i++) + { + vec2 c = c0+float(i)*dir; + int v = read_landclass_id(c); + if ((v != lc) && (mi[1] > n)) {l[1] = v; mi[1] = i; } + } + + + // +t direction + dir = vec2(0.0, steps.t); + for (int i=1;i<=n;i++) + { + vec2 c = c0+float(i)*dir; + int v = read_landclass_id(c); + if ((v != lc) && (mi[2] > n)) {l[2] = v; mi[2] = i; } + } + + + // -t direction + dir = vec2(0.0, -steps.t); + for (int i=1;i<=n;i++) + { + vec2 c = c0+float(i)*dir; + int v = read_landclass_id(c); + if ((v != lc) && (mi[3] > n)) {l[3] = v; mi[3] = i; } + } + + + // Set neighbour landclass + + // Choose closest neighbor + // min number of steps before a neighbor was found in any direction + int mns = n+1; + // index of mi[] with min number of steps + int idx1=-1; + for (int j=0;j<4;j++) + { + if (mi[j] < mns) {mns = mi[j]; idx1 = j; lc_n[0] = l[j]; num_n=1;} + } + + // Transitions: + // Possible landclass property: Transition distance or weighting + // e.g. larger transition between sand/grass terrain compared to forest/agriculture + + // Find mix factor and increase influence for 2, 3 or 4 nearby landclass blobs. + // If one neighbor landclass texture is looked up, even if the nearby landclasses + // are different only one texture will get prominence + + // At the boundary between landclasses there should be 50% influence. + // If needed it's possible to add a dominance factor. + + // mi ranges from n+1 to 1. Mix factor ranges from [0.0 to 0.5] + // 3 point search example: + // [Num steps=Mixfactor value]: [no neighbor found = 0.0], [1 = 0.25], [2 = 0.5] + + + // Calculate weights: map [n+1 to 1] to [0.0 to 0.5] + vec4 w = 0.5*(1.0-(vec4(mi-1)/float(n))); + + + // Calculate mix factor to draw one neighbor landclass + float mf1=0.0; + + // Method 1: + float max_w = max(max(max(w[0],w[1]),w[2]),w[3]); + //mf1 = max_w; + + // Method 2: add up the influence and clamp to 0.5 + //mf1 = min(w.x+w.y+w.z+w.w, 0.5); + + + // Method 3: weight influence without going over limit or needing to clamp + // Example with influence [0 to 1]: + // 2 neighbors with 0.5 influence: 0.75 . 3 neighbors with 0.5 = 87.25 + // of course influence is [0 to 0.5] but idea is the same + mf1 = w[0]; + mf1 += (0.5-mf1)*w[1]; + mf1 += (0.5-mf1)*w[2]; + mf1 += (0.5-mf1)*w[3]; + + // Mix factors: texels are mixed in from furthest, to closest + // mfact[0]: [0 to 0.5] texel and previous neighbour contributions + // mfact[1]: [0 to 1] mixing 1st and 2nd closest texels + mfact[0] = mf1; + + +// Test phase controls: +if (enable_2nd_closest_neighbor_for_large_scale_transition_weights == 1) +{ + + // Calculate mix factor for the case of two neighbour landclasses + + // index of mi[] with the 2nd lowest number of steps + int idx2=-1; + if (idx1 != -1) { + + // Choose 2nd closest neighbor + // Testing: look at a way to find 2 closest neighbors with less instructions + // 2nd lowest number of steps + int mns2 = n+1; + for (int j=0;j<4;j++) { + if ((mi[j] < mns2) && (mi[j] >= mns) && (j != idx1)) + {mns2 = mi[j]; lc_n[1] = l[j]; idx2=j; num_n=2;} + } + } + + + // If two neighbors are found split available mix factor (mf1) by relative weights + if (idx2 != -1) { + float rw = w[idx2]/(w[idx1]+w[idx2]); + mfact[1] = rw; + } + + +} // End if (enable_2nd_closest_neighbor_for_large_scale_transition_weights == 1) + + +// Test phase controls +if (enable_dithering_for_large_scale_transitions == 1) +{ + // Add noise to change transition + float tnoise1= Noise2D(tile_coord, dithering_noise_wavelength_as_fraction_of_step_size*steps.x); + float noise = 0.5*(1.0-tnoise1)/float(n); + mfact[0]=mfact[0]+noise; + mfact[0]=clamp(mfact[0],0.0,0.5); +} + + +// Test phase controls +if (grow_landclass_borders_with_large_scale_transition == 1) +{ + + // Grow landclass borders with noise so landclass blobs that are too artificial + // looking or coarse look natural. + // A landclass with higher growth priority grows on to an adjacent landclass + // with lower priority + + // Decide whether to grow neighbor on to lc + float grow_n = get_growth_priority(lc,lc_n[0],lc_n[1]); + + // Noise on the scale of landclass texels in the texture + float tnoise2 = Noise2D(tile_coord, 0.4*transition_search_distance_in_m/tile_size.x); + float threshold = mix(1.0,0.0, mfact[0]); + float neighbor_growth_mixf = 0.3*mix(0.0,1.0,mfact[0]*2.0)*step(threshold-0.15,tnoise2); + + mfact[0] = mfact[0]+((grow_n > 0.0)?neighbor_growth_mixf:-neighbor_growth_mixf); + mfact[0] = clamp(mfact[0],0.0,1.0); + + + // Decide whether to extrude furthest neighbor or closest neighbor onto lc + float grow_n1 = get_growth_priority(lc_n[0],lc_n[1]); + + mfact[1] = mfact[1]+((grow_n > 0.0)?neighbor_growth_mixf:+neighbor_growth_mixf); + mfact[1] = clamp(mfact[1],0.0,1.0); + + +} // Testing: End if (grow_landclass_borders_with_large_scale_transition == 1) + + +} // Testing: End if ((enable_large_scale_transition_search == 1) && (max_neighbor_landclass_texture_lookups > 0)) + + + //lc = int(t); + //mfact[2] = t; + + landclass_id = lc; + neighbor_landclass_ids=lc_n; + num_unique_neighbors = num_n; + mix_factor = mfact; +} + + + +// End Test-phase code +//////////////////////// + diff --git a/Shaders/ws30-ALS-ultra.frag b/Shaders/ws30-ALS-ultra.frag index 0b58a875b..3816109cd 100644 --- a/Shaders/ws30-ALS-ultra.frag +++ b/Shaders/ws30-ALS-ultra.frag @@ -3,183 +3,13 @@ // -*-C++-*- #version 130 #extension GL_EXT_texture_array : enable - +// written by Thorsten Renk, Oct 2011, based on default.frag ////////////////////////////////////////////////////////////////// -// TEST PHASE TOGGLES AND CONTROLS FOR PROFILING ON DIFFERENT GPUS +// TEST PHASE TOGGLES AND CONTROLS // -// Instructions for power users: -// Change the numbers for values and controls, save the file, and -// reload shaders to compare the difference. -// In-sim menu > debug > configure development extensions > reload shaders. -// It's safe to tinker with things in this section. -// At worst the terrain will look odd. If there's an error or a stray -// character, the shader will just not compile - the terrain changes -// appearance to black. -// Simply try again and hit reload shaders button. You can also save a backup -// of this file. -// "//" means everything on that line is a comment. -// Lines assigning numbers to variables end in a ";" - -// Testing performance: -// Use the UFO or video assistant. Turn all scenery layers off (vegetation, -// buildings, random scenery objects etc.). -// The terrain quality shader level determines the shaders used. Set it to ultra. -// The view should be 100% terrain. No sky. Try to minimise parts of terrain -// or scenery rendered by other shaders, as the results will be inaccurate. -// Avoid clouds in view. Minimise water in view. -// Going closer or reducing FoV works, but these also can change performance. -// Looking towards the horizon at high altitude may cause perfomance to be CPU -// bound due to OSG scene traveral. -// Remember to mention factors that change performance: -// resolution, the rough area (regional definitions at work), altitude, -// scenery package, AA settings, driver control panel settings and overrides -// - (you can set these to application controlled) - - -// Comparing performance: -// GPU bound: To use FPS to compare performance you must be bottlenecked by -// the GPU (GPU bound). -// When GPU bound (fragment bound), changing the window size slightly should -// result in a change in FPS. -// You can also tell if you are GPU bound, as GPU utilisation will be 100%. -// Change in performance = FPS2/FPS1. -// e.g. increase of 15 FPS to 30 FPS = 30/15 = 2x or 200% increase. -// Not GPU bound: If your bottleneck is not the GPU, you need to measure -// GPU utilisation. -// GPU utilisation is the GPU load - it will be less than 100%. -// Change in performance = utilisation1/utilisation2. -// e.g. Drop from 40% to 20% = 40/20 = 2x increase in performance or -// doubling of FPS. e.g 40% to 30% = 40/30 = 1.33x increase. -// CPU bound FPS limit: You can usually find your CPU bound FPS limit by -// reducing window size until FPS stops increasing. This depends on what's -// in view. -// To Compare performace with WS2: untick the WS3 tick box in render settings, -// and make sure both WS2 and WS3 are GPU bound, and not CPU bound. - - -// -// Note: -// Ensure in-sim menu > view > rendering options > throttle FPS is off. -// Ensure vsync is off. -// Make sure your power plan is set to maximum or balanced in Windows, or -// results could be inaccurate - laptops may be on a power saving plan -// by default. - -// To test: All transitions are off by default. Set both remove squareness and -// enable large scale transitions to 1 to get a quality with most features -// turned on. - -// Maximum number of neighbor landclass textures to lookup if neighbors are found. -// The more landclass textures are looked up the more pressure on VRAM. -// Performance hit varies by altitude and how small the landclass blobs are. -// More ground texture lookups may run slower on older generation GPUs - test and see. -// Possible values: 0,1,2. Default: 2. To see texture mixing transitions you need 1 or 2. - const int max_neighbor_landclass_texture_lookups = 2; - - - -// Small scale transition controls - -// Remove squareness due to landclass texture by growing higher priority neighbors. -// This adds 2 extra lookups of the landclass texture, and one math/noise lookup. -// Large scale transition searches do not use this - as it triples the number of -// landclass texture acceses, as well as adding 1 noise lookup per search point. -// This should be expected to be used on old GPUs, except when running at the absolute -// lowest graphics quality. It's faster than large scale transition searches. -// Possible values: 1:enabled, 0:disabled. Default:0 - const int remove_squareness_from_landclass_texture = 0; - -// Transition at landclass texel scale -// Mix in neighbor textures so landclass boundaries are not hard at the -// landclass texel scale. -// Note: Disable enable large scale transition search, if using this. -// This needs extra ground texture lookups. It looks fine with 1 extra lookup. -// This can be combined with removing squareness by growing borders. -// Possible values: 1:enabled, 0:disabled - const int use_landclass_texel_scale_transition_only = 0; - - - -// Large scale transition controls - -// Enable large scale transitions: 1=on, 0=off -// Disable use landclass texel scale transition, if using this. - const int enable_large_scale_transition_search = 0; - - -// The search pattern is center + n points in four directions forming a cross. -// e.g. 1 search point = 1 + 4 * 1 = 5 points total. -// 4 search points: 17 total. 10 search points = 41 -// The transition distance is the distance from the center to the furtherst -// point in any direction. - - -// Landclass transition search distance in meters -// Note: transitions occur on both sides of the landclass borders. -// The width of the transition is equal to 2x this value. -// Default: 100m - const float transition_search_distance_in_m = 100.0; - -// Number of points to search in any direction, in addition to this fragment -// Default:4 points. Fewer points results in a less smooth transition (more banding) -// Choose the lowest number of points to get a desired transition quality. - const int num_search_points_in_a_direction = 4; - - - - -// Landclass transition weightings options -// More options mean slightly more GPU math load -// -// Use 2nd closest neighbour for transition weighting. -// Note this won't lookup a ground texture by itself, just sort through results -// Possible values: 1:enable, 0=disable. Default: 1 - const int enable_2nd_closest_neighbor_for_large_scale_transition_weights = 0; - -// Enable dithering to smooth transitions by reducing visible banding -// Possible values: 1=enable, 0=disable. Default = 1 - const int enable_dithering_for_large_scale_transitions = 1; - -// Scale of dithering as a faction of the size of the bands - distance between -// search points (=transition distance / number of steps) -// 0.2 seems to work ok - not real need to tinker with this. -// Different values won't change performance. - const float dithering_noise_wavelength_as_fraction_of_step_size = 0.2; - -// Grow the borders of landclasses a bit when large scale transitions are used -// Higher priority landclasses grow onto lower priority ones. -// Landclass numbers are used as a placeholder for landclass priority. -// This works by changing the weighting in the transition region using a -// noise lookup -// Possibe values: 0=off, 1=on. Default:0 - const int grow_landclass_borders_with_large_scale_transition = 0; - - - -////////////////////////////////////////////////////////////////// -// Advanced controls - these are for testing scenery generation and rendering - - -// Landclass source: -// Possible values: Default=1; -// 0=Normal landclass texture, 1 = Random landclass squares along s and t axes. -// Choose 1 to test impact of searching a texture. You should normally leave -// it at default. - const int landclass_source = 0; - -// Random landclass square size in meters. Remember to adjust transition search distance. -// Default: 200m - const float random_landclass_square_size_in_m = 3.3*transition_search_distance_in_m; - -// Detiling noise source -// Possible values: 0 = texture source, 1 = math source -// The texture source still shows some tiling. The math source detiles better, but might -// be slightly slower. - const int detiling_noise_type = 0; - // Development tools: // Reduce haze to almost zero, while preserving lighting. Useful for observing distant tiles. // Keeps the calculation overhead. This can be used for profiling. @@ -209,7 +39,6 @@ -// written by Thorsten Renk, Oct 2011, based on default.frag // Ambient term comes in gl_Color.rgb. varying vec4 light_diffuse_comp; varying vec3 normal; @@ -288,91 +117,20 @@ float luminance(vec3 color) // Tile dimensions in meters // vec2 tile_size = vec2(tile_width , tile_height); // Testing: texture coords are sent flipped right now: -vec2 tile_size = vec2(tile_height , tile_width); +// Note tile_size is defined in the shader include: ws30-landclass-search-functions.frag. +// vec2 tile_size = vec2(tile_height , tile_width); +// From noise.frag float rand2D(in vec2 co); +// These functions, and other function they depend on, are defined +// in ws30-ALS-landclass-search.frag. + // Create random landclasses without a texture lookup to stress test. // Each square of square_size in m is assigned a random landclass value. -int get_random_landclass(in vec2 co, in vec2 tile_size) -{ - float r = rand2D( floor(vec2(co.s*tile_size.x, co.t*tile_size.y)/random_landclass_square_size_in_m) ); - int lc = int(r*48.0); // only 48 landclasses mapped so far - return lc; -} - - -// Look up texture coordinates and stretching scale of ground textures -void get_ground_texture_data(in float textureIndex, in vec2 tile_coord, - out vec2 st, out vec2 g_texture_scale, in out vec2 dx, in out vec2 dy) -{ - // Look up stretching dimensions of ground textures in m - scaled to - // fit in [0..1], so rescale - vec2 g_texture_stretch_dim = 10000.0 * texture(dimensionsArray, textureIndex).st; - g_texture_scale = tile_size.xy / g_texture_stretch_dim.xy; - // Correct partial derivatives to account for stretching of different textures - dx = dx * g_texture_scale; - dy = dy * g_texture_scale; - // Ground texture coords - st = g_texture_scale * tile_coord.st; -} - - -// Rotate texture using the perlin texture as a mask to reduce tiling. -// type=0: use perlin texture, type = 1: use Noise2D to avoid texture lookup -// Testing: if this or get_ground_texture_data used in final WS3 to handle -// many base texture lookups, see if optimising to handle many inputs helps -// (vectorising Noise2D versus just many texture calls) - -vec2 detile_texcoords_with_perlin_noise(in vec2 st, in vec2 ground_texture_scale, - in vec2 tile_coord, in out vec2 dx, in out vec2 dy) -{ - vec2 pnoise; - - // Ratio tile dimensions are stretched relative to s. - // Tiles may not have equal dimensions. - vec2 stretch_r = tile_size.st/tile_size.s; - - // Note: unresolved texture discontinuties (i.e. mipmap problems) with unequal stretch factors - const vec2 local_stretch_factors = vec2(8.0, 8.0 /*16.0*/); - - if (detiling_noise_type==1) - { - pnoise[0] = texture(perlin, st / local_stretch_factors[0]).r; - pnoise[1] = texture(perlin, - st / local_stretch_factors[1]).r; - } - else - { - //Testing: Non texture alternative - - // Estimate of wavelength in /Textures/perlin.png in normalised texture coords - const float ptex_wavelength = (1.0/7.0); - - pnoise[0] = Noise2D(st / (local_stretch_factors[0]), ptex_wavelength); - pnoise[1] = Noise2D(-st / (local_stretch_factors[1]), ptex_wavelength); - } - - if (pnoise[0] >= 0.5) - { - st = ground_texture_scale.st * (tile_coord * stretch_r).ts; - // Get back original partial derivatives by undoing - // previous texture stretching adjustment done in get_ground_data - dx = dx / ground_texture_scale.st; - dy = dy / ground_texture_scale.st; - // Recalculate new derivatives - dx = dx.ts * ground_texture_scale.st * stretch_r.ts; - dy = dy.ts * ground_texture_scale.st * stretch_r.ts; - - } - - if (pnoise[1] >= 0.5) - { - st = -st; dx = -dx; dy = -dy; - } - return st; -} +int get_random_landclass(in vec2 co, in vec2 tile_size); // Lookup a ground texture at a point based on the landclass at that point, without visible @@ -382,670 +140,24 @@ vec2 detile_texcoords_with_perlin_noise(in vec2 st, in vec2 ground_texture_scale // up and there are no seams. vec4 lookup_ground_texture_array(in float index, in vec2 tile_coord, in int landclass_id, - in vec2 dx, in vec2 dy) -{ - // Testing: may be able to save 1 or 2 op slots by combining dx/dy in a vec4 and - // using swizzles which are free, but mostly operations are working independenly on s and t. - // Only 1 place so far that just multiplies everything by a scalar. - vec2 st; - vec2 g_texture_scale; - vec4 texel; - int lc = landclass_id; - - get_ground_texture_data(index, tile_coord, st, g_texture_scale, dx, dy); - - - st = detile_texcoords_with_perlin_noise(st, g_texture_scale, tile_coord, dx, dy); - - //texel = texture(textureArray, vec3(st, lc)); - //texel = textureLod(textureArray, vec3(st, lc), 12.0); - texel = textureGrad(textureArray, vec3(st, lc), dx, dy); - return texel; -} - -// Landclass sources: texture or random -int read_landclass_id(in vec2 tile_coord) -{ - vec2 dx = dFdx(tile_coord.st); - vec2 dy = dFdy(tile_coord.st); - int lc; - - if (landclass_source == 0) lc = (int(texture2D(landclass, tile_coord.st).g * 255.0 + 0.5)); - else lc = (get_random_landclass(tile_coord.st, tile_size)); - return lc; -} - - -int read_landclass_id_non_pixelated(in vec2 tile_coord, - const in float landclass_texel_size_m) -{ - vec2 c0 = tile_coord; - vec2 sz = tile_size; - vec2 tsz = vec2(landclass_texel_size_m)/tile_size; - - // Landclass sources: texture or random - int lc = read_landclass_id(c0); - - return lc; -} - - -// Determine whether to grow a neighbor landclass onto current. -// 1 = grow neighbor, 0 = don't grow neighbor -float get_growth_priority(in int current_landclass, in int neighbor_landclass) -{ - int lc1 = current_landclass; - int lc2 = neighbor_landclass; - return ((lc1 < lc2)?1.0:0.0); -} - - -// Determine whether to grow a one of 2 neighbor landclasses onto current. -// 1 = grow neighbor, 0 = don't grow neighbor -float get_growth_priority(in int current_landclass, in int neighbor_landclass1, in int neighbor_landclass2) -{ - int lc1 = current_landclass; - int lc2 = neighbor_landclass1; - int lc3 = neighbor_landclass2; - return ((lc1 < max(lc2,lc3))?1.0:0.0); -} - - - -int lookup_landclass_id(in vec2 tile_coord, in vec2 dx, in vec2 dy, - out ivec4 neighbor_texel_landclass_ids, - out int number_of_unique_neighbors_found, out vec4 landclass_neighbor_texel_weights) -{ - - // To do: fix landclass border artifacts, with all shaders. do small scale texel mixing for 2 neighbors - - // Number of unique neighbours found - int num_n = 0; - - vec2 c0 = tile_coord; - vec2 sz = tile_size; - - // Landclass sources: texture or random - int lc = read_landclass_id(c0); - int output_landclass = lc; - - // Landclasses of up to 4 neighbor texels - ivec4 lc_n = ivec4(lc); - - // Landclasses sorted - ivec4 lc_n_s; - - // Combined weight from 2 neighbor texels - float w = 0.0; - - // Landclass neighbor weights - for texel mixing only - vec4 lc_n_w = vec4(0.0); - -// Test phase controls -if ( (remove_squareness_from_landclass_texture == 1) || - ( (use_landclass_texel_scale_transition_only == 1) && - (enable_large_scale_transition_search == 0) ) - ) -{ - - - // Remove squareness from the landclass texture due to nearest neighbour interpolation - // A landclass with higher growth priority grows on to an adjacent landclass - // with lower priority - - // Landclass texture dimensions, in texels - Needs glsl 1.30+ - // Probably best to just send as uniforms if texture sizes don't vary, or are fixed per terrain LoD level. - vec2 texture_dim_tx = vec2(textureSize(landclass, 0)); - - // Coordinates of current fragment, in texels - vec2 c0_tx = c0 * texture_dim_tx; - - // Coordinates of the center of the current texel, in texels - // centers are n+(0.5,0.5) for n = 0,1,2... - vec2 ct_c0_tx = floor(c0_tx) + 0.5; - // center in normalised tex coords - vec2 ct_c0 = ct_c0_tx/texture_dim_tx; - - // Landclass at center of current texel - same anywhere within a texel - int lc_ct = lc; - - // Coords of centers of closest neighbors, in texels - vec2 c_n_tx[2]; - - - // Coordinate of fragment relative to center of texel, in texels - vec2 c0_rel_ct_tx = c0_tx - ct_c0_tx; - - float dist_ct = length(c0_rel_ct_tx); - - // need to avoid division by 0? - if (dist_ct < 0.00001) dist_ct += 0.00001; - - // Choose closest neighbor based on angle wrt. to - // c0, center of texel, and s & t axes. - // Choose the texel in the direction of the largest s & t component. - // NB: This method will select a diagonal neighbor if - // both components of c0_rel_ct_tx are equal to cos(45). - // Testing: look for a way that uses fewer instructions, - // maybe calculating 2 neighbors at once using a vec4. - - //vec2 a = abs(c0_rel_ct_tx); - //offset_ct0 = ((a.s > a.t)?vec2(1.0,0.0):vec2(0.0,1.0))*sign(c0_rel_ct_tx); - - // Vectorisable - const float cos45deg = cos(radians(45.0)); - vec2 offset_ct0 = step(cos45deg, abs(c0_rel_ct_tx/dist_ct)) - *sign(c0_rel_ct_tx); - - c_n_tx[0] = (ct_c0_tx + offset_ct0); - - // Landclass of closest neighbor - lc_n[0] = read_landclass_id(c_n_tx[0]/texture_dim_tx); - - - // Choose 2nd closest neighbor - // Choose texels in the direction of the smaller of s & t components - vec2 offset_ct1 = abs(offset_ct0.ts)*step(0.0, abs(c0_rel_ct_tx/dist_ct)) - * sign(c0_rel_ct_tx); - - c_n_tx[1] = (ct_c0_tx + offset_ct1); - - // Land class of 2nd closest neighbor - lc_n[1] = read_landclass_id(c_n_tx[1]/texture_dim_tx); - - - // Distinct neighbors found - // Testing: possible optimisation, use booleans. - // Needs ivec4/1.30+, or vec4/1.20 - reliably supported by old compilers? - // bvec4 n_found = notEqual(lc_n, ivec4(lc)); - - ivec4 n_found = ivec4(((lc_n[0] != lc)?1:0), ((lc_n[1] != lc)?1:0), 0, 0); - - num_n = n_found[0]+ n_found[1]; - - - //if (any(n_found)) - if ((n_found[0] == 1) || (n_found[1] == 1)) - { - - // Weights for influence from neighbor landclasses - // The distance away from the neighbor texel side is used to determine influence. - // w_n: 0.5 at minimum possible distance, 0.0 at maximum possible distance - - // Neighbor weights - vec4 w_n = vec4(0.0); - - - // Method 1: - // Use distance from side of neighbor texel for mixing - // This is has some issues, including with corners - - vec2 dir0 = offset_ct0; - vec2 dir1 = offset_ct1; - - // Distance from side of neighbor texel, in texels - vec2 d0 = dir0*c0_rel_ct_tx; - vec2 d1 =dir1*c0_rel_ct_tx; - - w_n[0] = max(d0.x, d0.y); - w_n[1] = max(d1.x, d1.y); - - - -/* - //Method 2: - // use distance from center of neighbor texel for mixing - // This doesn't really give better results than 1 - - // Distance from center of neighbor texels, in texels - vec2 dist_n_tx = vec2(0.0); - - dist_n_tx[0] = length(c0_tx - c_n_tx[0]); - dist_n_tx[1] = length(c0_tx - c_n_tx[1]); - - - // Weighting for closest neighbor - [0.5 to 0.0] as distance goes from [min to max] - const float max_dist = sqrt(0.5*0.5+1.0); - const float min_dist = 0.5; - w_n[0] = (dist_n_tx[0]-min_dist)/(max_dist - min_dist); - w_n[0] = mix(0.5, 0.0, w_n[0]); - - - // Weighting for 2nd closest neighbor - [0.5 to 0.0] as distance goes from [min to max] - //const float max_dist1 = sqrt(0.5*0.5+1.0); - //const float min_dist = 0.5; - w_n[1] = (dist_n_tx[1]-min_dist)/(max_dist-min_dist); - w_n[1] = mix(0.5, 0.0, w_n[1]); -*/ - - - // Use weighting only if neighbour is different from landclass for this fragment - // Testing: Can be omitted if not doing texture mixing as it doesn't really - // make a difference. - w_n = w_n * vec4(n_found); - - // Combined weighting - increase w_n[0] if the 2nd closest neigbour is - // different such that w_n[0] remains under 0.5 - w = w_n[0]; - w = w + (0.5-w)*2.0*w_n[1]; - - // Sort landclasses and weights - lc_n_s = lc_n; - // If closest neighbour is lc, move 2nd closest neighbour to closest slot, and - // clear the 2nd closest slot - if (n_found[0] == 0) - { - lc_n_s.xy = lc_n.yz; - w_n.xy = w_n.yz; - } - - -// Testing phase controls -if ( (use_landclass_texel_scale_transition_only == 1) && - (max_neighbor_landclass_texture_lookups > 0) && - (enable_large_scale_transition_search == 0) - ) -{ - // Assign mix factors for transitions by mixing texels - // [0]: 0 to 0.5 - // [1]: split [0 to 1] between closest and 2nd closest landclass - lc_n_w[0] = w; - lc_n_w[1] = w_n[1]/(w_n[0]+w_n[1]); -} - - -} // Testing controls: End if ((remove_squareness_from_landclass_texture == 1) || (use_landclass_texel_scale_transition_only == 1)) - - -if (remove_squareness_from_landclass_texture == 1) -{ - // Turn neighbor growth off at longer ranges, otherwise there is flickering noise - // Testing: The exact cutoff could be done sooner to save some performance - needs - // to be part of a larger solution to similar issues. User should set a tolerance factor. - float lod_factor = min(length(vec2(dx.s, dy.s)),length(vec2(dx.t, dy.t))); - // Estimate of frequency of growth noise in texels - i.e. how many peaks and troughs fit in one texel - const float frequency_g_n = 1000.0; - const float cutoff = 1.0/frequency_g_n; - - if (lod_factor < cutoff) - { - // Decide whether to grow neighbor on to lc - float grow_n = get_growth_priority(lc,lc_n[0]); - - // Noise on the scale of landclass texels in the texture - // Testing: reduce instructions if this method is to be used. - // To look at: corner visuals & sharp diagonals. - - // Minimum wavelength of transition noise - const float wl_tn = (1.0/8.0); - float tn = Noise2D(c0*texture_dim_tx, wl_tn); // old val 1.6 - - float threshold = mix(1.0,0.0, w); - - float neighbor_growth_weight = (0.3*2.0)*w*step(threshold-0.15, tn); - - // Growth factor - float g = ((grow_n > 0.0)?neighbor_growth_weight:-neighbor_growth_weight); - //g = sqrt(abs(g))*sign(g); - - // Neighbor growth value - float v; - //v=w+g; - v = w*(0.7+50.5*g); - - // Whether or not to grow neighbour onto nearby pixel - - // To do - mix factor between different neighbour lanclasses - // when using an extra ground texture lookup - - if (v > 0.5) output_landclass = lc_n_s[0]; - - - - -// Testing phase controls -if ( (use_landclass_texel_scale_transition_only == 1) && - (max_neighbor_landclass_texture_lookups > 0) && - (enable_large_scale_transition_search == 0) - ) -{ - - lc_n_w[0] = 0.0; -/* - // Adjust mix factor weights and swap landclasses for extrusions - - // Method 1: - - lc_n_w[0] = (w-0.5*neighbor_growth_weight); - if (v > 0.5) lc_n_s[0] = lc; -*/ - - - // Method 2: - // Mix in neighbour texel, instead of change output landclass. - - // Undo previous output class assignment - output_landclass = lc; - - // Reduce flickering noise due to small detail added when far away. Contrasting colors mean more visible issues. - // Fade 0 to 1 as lod_factor goes from 1.0 to 4.0 - // The goal is to avoid flickering with worst case texture filtering and supersampling. - // Testing: However, the quicker the detil fades, the more square distant ladnclasses look. - // Right now the noise function generates too many high frequency components (small detail) - - //const float mmax = 4000.0; const float mmin = mmax-1000.0; /* no flickering */ - const float mmax = 3000.0; const float mmin = mmax-1000.0; /* bit of filckering */ - float fade = smoothstep(mmin, mmax, 1.0/lod_factor); - - lc_n_w[0] = (w-0.5*3.333*0.9*(neighbor_growth_weight*fade)); - if (v > 0.5) lc_n_w[0] = w+0.4*fade; - - - -} - - - - } // End if (lod_factor > some value) - - -} // Testing code: End if (remove_squareness_from_landclass_texture == 1) - - - - } // End if (nfound[0] == 1) || (n_found[1] == 1) - - - - - landclass_neighbor_texel_weights = lc_n_w; - neighbor_texel_landclass_ids = lc_n_s; - number_of_unique_neighbors_found = num_n; - return output_landclass; - -} - + in vec2 dx, in vec2 dy); // Look up the landclass id [0 .. 255] for this particular fragment. // Lookup id of any neighbouring landclass that is within the search distance. // Searches are performed in upto 4 directions right now, but only one landclass is looked up // Create a mix factor werighting the influences of nearby landclasses - void get_landclass_id(in vec2 tile_coord, const in float landclass_texel_size_m, in vec2 dx, in vec2 dy, out int landclass_id, out ivec4 neighbor_landclass_ids, out int num_unique_neighbors,out vec4 mix_factor - ) -{ - // Each tile has 1 texture containing landclass ids stetched over it - - // Landclass source type: 0=texture, 1=random squares - // Controls are defined at global scope. const int landclass_source - const float ts = landclass_texel_size_m; - vec2 sz = tile_size; - - // Number of unique neighbors found - int num_n = 0; - - // Only used for mixing textures of neighboring texels: - // Landclass ids of neigbors in neighboring texels - ivec4 lc_n_tx; - // Weights of neighbour landclass texels - vec4 lc_n_w; - // Number of unique neighbors in neighboring texels - int num_n_tx = 0; - - int lc = lookup_landclass_id(tile_coord, dx, dy, lc_n_tx, num_n_tx, lc_n_w); - - // Neighbor landclass ids - ivec4 lc_n = ivec4(lc); - - // Mix factors: texels are mixed in from furthest, to closest - // mfact[1]: [0 to 1] mixing 1st and 2nd closest texels - // mfact[0]: [0 to 0.5] texel and previous neighbour contributions - vec4 mfact = vec4(0.0); - - -// Testing phase controls -if ( (use_landclass_texel_scale_transition_only == 1) && - (max_neighbor_landclass_texture_lookups > 0) && - (enable_large_scale_transition_search == 0) - ) - -{ - // Use the ground texture lookups to do a transition on the scale of - // the landclass textures instead of doing a large scale transition - num_n = num_n_tx; - lc_n = lc_n_tx; - mfact = lc_n_w; -} - - -// Testing phase controls -if ( (enable_large_scale_transition_search == 1) && - (max_neighbor_landclass_texture_lookups > 0) && - (use_landclass_texel_scale_transition_only == 0) - ) -{ - - - // Transition search - - const int n = num_search_points_in_a_direction; - - const float search_dist = transition_search_distance_in_m; - vec2 step_size_m = vec2(search_dist/float(n)); - // step size in tile coords - vec2 steps = step_size_m.st / tile_size.st; - - vec2 c0 = tile_coord; - - // Min number of points (loop counter value (i)) before - // a different landclass is found - ivec4 mi = ivec4(n+1); - - // landclass - l can be accessed as an array e.g. l[0]=l.x - ivec4 l = ivec4(lc); - - // Search in 4 directions. These for loops likely need unrolling, - // and optimising to use minimum instructions, if they are - // to be used outside of testing the search concept. - // The texture access patterns may be suboptimal as well. - // Travelling along s and t axes might work better. - // Note: this returns the closest neighbor. There could be blobs - // of multiple neighbors, or a tiny islands of neighbors among this - // landclass. - - - // +s direction - vec2 dir = vec2(steps.s, 0.0); - - for (int i=1;i<=n;i++) { - vec2 c = c0+float(i)*dir; - int v = read_landclass_id(c); - if ((v != lc) && (mi[0] > n)) {l[0] = v; mi[0] = i; } - } - - - // -s direction - dir = vec2(-steps.s, 0.0); - for (int i=1;i<=n;i++) - { - vec2 c = c0+float(i)*dir; - int v = read_landclass_id(c); - if ((v != lc) && (mi[1] > n)) {l[1] = v; mi[1] = i; } - } - - - // +t direction - dir = vec2(0.0, steps.t); - for (int i=1;i<=n;i++) - { - vec2 c = c0+float(i)*dir; - int v = read_landclass_id(c); - if ((v != lc) && (mi[2] > n)) {l[2] = v; mi[2] = i; } - } - - - // -t direction - dir = vec2(0.0, -steps.t); - for (int i=1;i<=n;i++) - { - vec2 c = c0+float(i)*dir; - int v = read_landclass_id(c); - if ((v != lc) && (mi[3] > n)) {l[3] = v; mi[3] = i; } - } - - - // Set neighbour landclass - - // Choose closest neighbor - // min number of steps before a neighbor was found in any direction - int mns = n+1; - // index of mi[] with min number of steps - int idx1=-1; - for (int j=0;j<4;j++) - { - if (mi[j] < mns) {mns = mi[j]; idx1 = j; lc_n[0] = l[j]; num_n=1;} - } - - // Transitions: - // Possible landclass property: Transition distance or weighting - // e.g. larger transition between sand/grass terrain compared to forest/agriculture - - // Find mix factor and increase influence for 2, 3 or 4 nearby landclass blobs. - // If one neighbor landclass texture is looked up, even if the nearby landclasses - // are different only one texture will get prominence - - // At the boundary between landclasses there should be 50% influence. - // If needed it's possible to add a dominance factor. - - // mi ranges from n+1 to 1. Mix factor ranges from [0.0 to 0.5] - // 3 point search example: - // [Num steps=Mixfactor value]: [no neighbor found = 0.0], [1 = 0.25], [2 = 0.5] - - - // Calculate weights: map [n+1 to 1] to [0.0 to 0.5] - vec4 w = 0.5*(1.0-(vec4(mi-1)/float(n))); - - - // Calculate mix factor to draw one neighbor landclass - float mf1=0.0; - - // Method 1: - float max_w = max(max(max(w[0],w[1]),w[2]),w[3]); - //mf1 = max_w; - - // Method 2: add up the influence and clamp to 0.5 - //mf1 = min(w.x+w.y+w.z+w.w, 0.5); - - - // Method 3: weight influence without going over limit or needing to clamp - // Example with influence [0 to 1]: - // 2 neighbors with 0.5 influence: 0.75 . 3 neighbors with 0.5 = 87.25 - // of course influence is [0 to 0.5] but idea is the same - mf1 = w[0]; - mf1 += (0.5-mf1)*w[1]; - mf1 += (0.5-mf1)*w[2]; - mf1 += (0.5-mf1)*w[3]; - - // Mix factors: texels are mixed in from furthest, to closest - // mfact[0]: [0 to 0.5] texel and previous neighbour contributions - // mfact[1]: [0 to 1] mixing 1st and 2nd closest texels - mfact[0] = mf1; - - -// Test phase controls: -if (enable_2nd_closest_neighbor_for_large_scale_transition_weights == 1) -{ - - // Calculate mix factor for the case of two neighbour landclasses - - // index of mi[] with the 2nd lowest number of steps - int idx2=-1; - if (idx1 != -1) { - - // Choose 2nd closest neighbor - // Testing: look at a way to find 2 closest neighbors with less instructions - // 2nd lowest number of steps - int mns2 = n+1; - for (int j=0;j<4;j++) { - if ((mi[j] < mns2) && (mi[j] >= mns) && (j != idx1)) - {mns2 = mi[j]; lc_n[1] = l[j]; idx2=j; num_n=2;} - } - } - - - // If two neighbors are found split available mix factor (mf1) by relative weights - if (idx2 != -1) { - float rw = w[idx2]/(w[idx1]+w[idx2]); - mfact[1] = rw; - } - - -} // End if (enable_2nd_closest_neighbor_for_large_scale_transition_weights == 1) - - -// Test phase controls -if (enable_dithering_for_large_scale_transitions == 1) -{ - // Add noise to change transition - float tnoise1= Noise2D(tile_coord, dithering_noise_wavelength_as_fraction_of_step_size*steps.x); - float noise = 0.5*(1.0-tnoise1)/float(n); - mfact[0]=mfact[0]+noise; - mfact[0]=clamp(mfact[0],0.0,0.5); -} - - -// Test phase controls -if (grow_landclass_borders_with_large_scale_transition == 1) -{ - - // Grow landclass borders with noise so landclass blobs that are too artificial - // looking or coarse look natural. - // A landclass with higher growth priority grows on to an adjacent landclass - // with lower priority - - // Decide whether to grow neighbor on to lc - float grow_n = get_growth_priority(lc,lc_n[0],lc_n[1]); - - // Noise on the scale of landclass texels in the texture - float tnoise2 = Noise2D(tile_coord, 0.4*transition_search_distance_in_m/tile_size.x); - float threshold = mix(1.0,0.0, mfact[0]); - float neighbor_growth_mixf = 0.3*mix(0.0,1.0,mfact[0]*2.0)*step(threshold-0.15,tnoise2); - - mfact[0] = mfact[0]+((grow_n > 0.0)?neighbor_growth_mixf:-neighbor_growth_mixf); - mfact[0] = clamp(mfact[0],0.0,1.0); - - - // Decide whether to extrude furthest neighbor or closest neighbor onto lc - float grow_n1 = get_growth_priority(lc_n[0],lc_n[1]); - - mfact[1] = mfact[1]+((grow_n > 0.0)?neighbor_growth_mixf:+neighbor_growth_mixf); - mfact[1] = clamp(mfact[1],0.0,1.0); - - -} // Testing: End if (grow_landclass_borders_with_large_scale_transition == 1) - - -} // Testing: End if ((enable_large_scale_transition_search == 1) && (max_neighbor_landclass_texture_lookups > 0)) - - - - landclass_id = lc; - neighbor_landclass_ids=lc_n; - num_unique_neighbors = num_n; - mix_factor = mfact; -} - + ); // End Test-phase code //////////////////////// - void main() {