// projgrid-flat-fft.glsl #define PRECISION_GUARD 2.0 #define PI 3.14159265 #define TWOPI (2.0 * 3.14159265) #ifdef OPENGL32 in vec2 vertex; out vec3 V; out vec2 foamTexCoords; out vec2 texCoords; out vec2 noiseTexCoords; out vec3 wakeSlopeAndFoam; #ifdef PROPELLER_WASH out vec3 washTexCoords; out float activeWashWidth; #endif out float fogFactor; out float transparency; out float depth; #ifdef BREAKING_WAVES out float breaker; out float breakerFade; out vec2 breakerTexCoords; #endif #ifdef LEEWARD_DAMPENING out float leewardDampening; #endif out vec4 vVertex_Eye_Space; out vec4 vVertex_Projection_Space; #ifdef PER_FRAGMENT_PROP_WASH out vec3 propWashCoord; #endif #else varying vec3 V; varying vec2 foamTexCoords; varying vec2 texCoords; varying vec2 noiseTexCoords; varying vec3 wakeSlopeAndFoam; #ifdef PROPELLER_WASH varying vec3 washTexCoords; varying float activeWashWidth; #endif varying float fogFactor; varying float transparency; varying float depth; #ifdef BREAKING_WAVES varying float breaker; varying float breakerFade; varying vec2 breakerTexCoords; #endif #ifdef LEEWARD_DAMPENING varying float leewardDampening; #endif varying vec4 vVertex_Eye_Space; varying vec4 vVertex_Projection_Space; #ifdef PER_FRAGMENT_PROP_WASH varying vec3 propWashCoord; #endif #endif void user_intercept(in vec3 worldPosition, in vec3 localPosition, in vec4 eyePosition, in vec4 projectedPosition); vec4 overridePosition(in vec4 position); float user_get_depth( in vec3 worldPos ); void getDepthFromHeightmap(in vec3 worldPos) { vec2 texCoord = (trit_heightMapMatrix * vec4(worldPos, 1.0)).xy; if (clamp(texCoord, vec2(0.0, 0.0), vec2(1.0, 1.0)) == texCoord) { #ifdef OPENGL32 float height = texture(trit_heightMap, texCoord).x; #else float height = texture2D(trit_heightMap, texCoord).x; #endif height = height*trit_heightMapRangeOffset.x + trit_heightMapRangeOffset.y; depth = -(height - trit_seaLevel); } } void getDepthFromDepthmap(in vec3 localPos) { vec4 clipPos = trit_projection * (trit_modelview * vec4(localPos, 1.0)); vec3 ndcPos = clipPos.xyz / clipPos.w; vec2 texCoord = ndcPos.xy * 0.5 + 0.5; #ifdef OPENGL32 float terrainZ = texture(trit_depthMap, texCoord).x; #else float terrainZ = texture2D(trit_depthMap, texCoord).x; #endif ndcPos.z = mix(trit_zNearFar.x, trit_zNearFar.y, terrainZ); clipPos.w = trit_projection[3][2] / (ndcPos.z - (trit_projection[2][2] / trit_projection[2][3])); clipPos.xyz = ndcPos * clipPos.w; vec4 terrainWorld = trit_invModelviewProj * clipPos; depth = length(terrainWorld.xyz) - length(localPos); } void computeTransparency(in vec3 worldPos, in vec3 localPos) { depth = DEFAULT_DEPTH; // Compute depth at this position if ( trit_hasUserHeightMap ) { depth = user_get_depth(worldPos); } else if (trit_hasHeightMap) { getDepthFromHeightmap(worldPos); } else if (trit_hasDepthMap) { getDepthFromDepthmap(localPos); } else { vec3 up = trit_invBasis * vec3(0.0, 0.0, 1.0); vec3 l = -up; vec3 l0 = worldPos; vec3 n = trit_floorPlaneNormal; vec3 p0 = trit_floorPlanePoint; float numerator = dot((p0 - l0), n); float denominator = dot(l, n); if (abs(denominator) > 0.0001) { depth = numerator / denominator; } } // Compute fog at this distance underwater float fogExponent = abs(depth) * trit_fogDensityBelow; transparency = clamp(exp(-abs(fogExponent)), 0.0, 1.0); } float applyBreakingWaves(in vec3 v, in float fade, in vec3 worldPos) { float finalz = 0.0; #ifdef BREAKING_WAVES breaker = 0.0; breakerTexCoords = vec2(0.0,1.0); bool hasHeightMap = (trit_hasHeightMap || trit_hasUserHeightMap); if (hasHeightMap && trit_breakerAmplitude > 0 && depth > 1.0 && depth < trit_breakerWavelength * 0.5) { vec3 direction = trit_breakerDirection; float alpha = 1.0; #ifdef BREAKING_WAVES_MAP bool gotDirection = false; if (trit_hasBreakingWaveMap) { vec2 texCoord = (trit_breakingWaveMapMatrix * vec4(worldPos, 1.0)).xy; if (clamp(texCoord, vec2(0.0, 0.0), vec2(1.0, 1.0)) == texCoord) { #ifdef OPENGL32 vec4 t = texture(trit_breakingWaveMap, texCoord); #else vec4 t = texture2D(trit_breakingWaveMap, texCoord); #endif vec3 d = t.xyz; alpha = t.w; if (dot(d,d) == 0.0) { direction = normalize(d); gotDirection = true; } } } if (!gotDirection) return 0; #endif float halfWavelength = trit_breakerWavelength * 0.5; float scaleFactor = ((depth - halfWavelength) / halfWavelength); float wavelength = trit_breakerWavelength + scaleFactor * trit_breakerWavelengthVariance; float halfKexp = trit_kexp * 0.5; scaleFactor = (depth - halfKexp) / halfKexp; scaleFactor *= 1.0 + trit_steepnessVariance; float k = (trit_kexp + scaleFactor) * (1.0 - fade); vec3 localDir = trit_basis * direction; localDir.z = 0.0; localDir = normalize(localDir); float dotResult = dot(-localDir.xy, v.xy) * TWOPI / wavelength; finalz = (dotResult + trit_breakerPhaseConstant * trit_time); vec3 binormal = cross(vec3(0.0, 0.0, 1.0), localDir); breakerTexCoords.x = dot(binormal.xy, v.xy); breakerTexCoords.x /= trit_foamScale * 8.0; #define OFFSET (PI * 0.3) float y = mod(finalz, TWOPI); if (y < OFFSET) return 0; float num = PI - y; float den = PI - OFFSET; breakerTexCoords.y = num / den; float sinz = sin(finalz); finalz = (sinz + 1.0) * 0.5; finalz = trit_breakerAmplitude * pow(finalz, max(1.0, k)); finalz *= 1.0 - min(depth * trit_breakerDepthFalloff / halfWavelength, 1.0); finalz *= alpha; finalz = max(0, finalz); breaker = clamp(sinz, 0.0, 1.0); breaker *= 1.0 - min((depth * 3.0 * trit_breakerDepthFalloff) / halfWavelength, 1.0); breaker *= alpha; // Hide the backs of waves if we're transparent float opacity = 1.0 - transparency; finalz = mix(0.0, finalz, pow(opacity, 6.0)); } #endif return finalz; } float applyCircularWaves(in vec3 v, float fade) { vec2 slope = vec2(0.0, 0.0); float disp = 0.0; int i; for (i = 0; i < trit_numCircularWaves; i++) { vec2 D = (v - trit_circularWaves[i].position).xy; float dist = length(D); float r = dist - trit_circularWaves[i].radius; if (abs(r) < trit_circularWaves[i].halfWavelength) { float amplitude = trit_circularWaves[i].amplitude; float theta = trit_circularWaves[i].k * r; disp += amplitude * cos(theta); float derivative = amplitude * -sin(theta); slope += D * (derivative / dist); } } float dispZ = disp * fade; wakeSlopeAndFoam.z += max(0.0, dispZ); wakeSlopeAndFoam.xy += slope * fade; return dispZ; } void applyLeewardDampening(in vec3 v) { #ifdef LEEWARD_DAMPENING int i; float maxDampening = 0.0; for (i = 0; i < trit_numLeewardDampeners; i++) { vec3 bowPos = trit_leewardDampeners[i].bowPos; vec3 sternPos = trit_leewardDampeners[i].sternPos; vec3 center = (bowPos + sternPos) * 0.5; vec3 P = v - center; vec3 axis = normalize(bowPos - sternPos); float radius = length(bowPos - sternPos) * 0.5; float blockage = 1.0 - abs(dot(axis, trit_windDir)); float directional = max(dot(P, trit_windDir), 0.0); float distance = max(1.0 - length(P) / radius, 0.0); float dampening = blockage * directional * distance * trit_leewardDampeningStrength; dampening *= trit_leewardDampeners[i].velocityDampening; maxDampening = max(dampening, maxDampening); } leewardDampening = 1.0 - maxDampening; leewardDampening = clamp(leewardDampening, 0.0, 1.0); #endif } float applyKelvinWakes(in vec3 v, float fade) { float displacementZ = 0.0; #ifdef KELVIN_WAKES vec2 slope = vec2(0.0, 0.0); float foam = 0; float hullWakeFoam = 0; int i; for (i = 0; i < trit_numKelvinWakes; i++) { vec3 X0 = trit_wakes[i].position - trit_wakes[i].shipPosition; vec3 T = normalize(X0); vec3 N = vec3(0,0,1); vec3 B = normalize(cross(N, T)); vec3 P = v - trit_wakes[i].shipPosition; vec3 X; X.x = dot(P.xy, T.xy); X.y = dot(P.xy, B.xy); float xLen = length(X0); vec2 tc; tc.x = X.x / (1.54 * xLen); tc.y = (X.y) / (1.54 * xLen) + 0.5; if (clamp(tc, 0.01, 0.99) == tc) { #ifdef OPENGL32 vec4 displacementSample = texture(trit_displacementTexture, tc); #else vec4 displacementSample = texture2D(trit_displacementTexture, tc); #endif float displacement = displacementSample.w; displacement *= trit_wakes[i].amplitude;// * fade; foam += displacement * displacement * trit_wakes[i].foamAmount; vec3 normal = normalize(displacementSample.xyz * 2.0 - 1.0); float invmax = inversesqrt( max( dot(T,T), dot(B,B) ) ); mat3 TBN = mat3( T * invmax, B * invmax, N ); normal = TBN * normal; if (clamp(normal.xy, vec2(-0.05, -0.05), vec2(0.05, 0.05)) == normal.xy) { normal = vec3(0.0,0.0,1.0); } normal.xy *= min(1.0, trit_wakes[i].amplitude); normal = normalize(normal); displacementZ = max(displacementZ, displacement); vec2 s = vec2(normal.x / normal.z, normal.y / normal.z); slope = max(slope, s); //foam += length(s) * trit_wakes[i].foamAmount; } if(trit_wakes[i].hullWakeLengthReciprocal > 0.0) { tc.y = X.x * (trit_wakes[i].hullWakeLengthReciprocal); tc.x = (X.y) / (trit_wakes[i].hullWakeWidth) + 0.5; if (clamp(tc, 0.01, 0.99) == tc) { #ifdef OPENGL32 vec4 hullWakeSample = texture(trit_hullWakeTexture, tc); #else vec4 hullWakeSample = texture2D(trit_hullWakeTexture, tc); #endif if(hullWakeSample.z > 0.0) { float ty = X.x * trit_wakes[i].hullWakeLengthReciprocal; float t = length(P) * trit_wakes[i].hullWakeLengthReciprocal; if(ty < 0.1) { float tScale = 10.0*ty; hullWakeFoam = hullWakeSample.z * trit_wakes[i].foamAmount * tScale; } else if(ty > 0.9) { float tScale = 1.0-(10.0*(ty-0.9)); hullWakeFoam = hullWakeSample.z * trit_wakes[i].foamAmount * tScale; } else { hullWakeFoam = hullWakeSample.z * trit_wakes[i].foamAmount; } } } } } foam = min(1.0, foam+hullWakeFoam); wakeSlopeAndFoam.z += foam; wakeSlopeAndFoam.xy += slope * fade; #endif return displacementZ; } void applyPropWash(in vec3 v) { #ifdef PROPELLER_WASH washTexCoords = vec3(0.0, 0.0, 0.0); activeWashWidth = -1.0; for (int i = 0; i < trit_numPropWashes; i++) { vec3 C = trit_washes[i].deltaPos; vec3 A = v - trit_washes[i].propPosition; float segmentLength = length(C); // Compute t float t0 = dot(C, A) / dot(C, C); // Compute enough overlap to account for curved paths. float overlap = (trit_washes[i].washWidth / segmentLength) * 0.5; if (t0 >= -overlap && t0 <= 1.0 + overlap) { // Compute distance from source float distFromSource = trit_washes[i].distFromSource - (1.0 - t0) * segmentLength; // Compute wash width float washWidth = (trit_washes[i].washWidth * pow(distFromSource, 1.0 / 4.5)) * 0.5; // Compute distance to line vec3 B = A - C; vec3 aCrossB = cross(A, B); float d = length(aCrossB) / segmentLength; // The direction of A X B indicates if we're 'left' or 'right' of the path float nd = d / washWidth; if (clamp(nd, 0.0, 1.0) == nd) { float t = nd; vec3 up = vec3(0.0,0.0,1.0); if(dot(up, aCrossB) >= 0) { t = (1.0-nd*0.5)-0.5; } else { t = 0.5+(nd*0.5); } nd = t; washTexCoords.x = nd; // The t0 parameter from our initial distance test to the line segment makes // for a handy t texture coordinate //scale texture by 4 to reduce tiling washTexCoords.y = (trit_washes[i].washLength - distFromSource) / (4.0*trit_washes[i].washWidth); float blend = mix(trit_washes[i].alphaEnd, trit_washes[i].alphaStart, t0); blend = clamp(blend, 0.0, 1.0); if(nd <= 0.1 || nd >= 0.9) { blend = 0; } else if(nd <= 0.2) { blend *= (nd-.1)*10.0; } else if (nd >= .8) { blend *= (.9-nd)*10.0; } washTexCoords.z = blend; activeWashWidth = trit_washes[i].washWidth; } } } #endif } void displace(in vec3 vWorld, inout vec3 vLocal) { float fade = 1.0 - smoothstep(0.0, 1.0, length(vWorld - trit_cameraPos) * trit_invDampingDistance); #ifdef BREAKING_WAVES // Fade out waves in the surge zone float depthFade = 1.0; if (trit_surgeDepth > 0) { depthFade = min(trit_surgeDepth, depth) / trit_surgeDepth; depthFade = clamp(depthFade, 0.0, 1.0); } fade *= depthFade; breakerFade = depthFade; #endif // Transform so z is up vec3 localVWorld = trit_basis * vWorld; vec3 localVLocal = trit_basis * vLocal; texCoords = localVWorld.xy / trit_textureSize; #ifdef OPENGL32 vec3 displacement = texture(trit_displacementMap, texCoords).xyz; #else vec3 displacement = texture2D(trit_displacementMap, texCoords).xyz; #endif // Hide the backs of waves if we're transparent float opacity = 1.0 - transparency; displacement.z = mix(0.0, displacement.z, pow(opacity, 6.0)); localVLocal.xy += displacement.xy * fade; #if (defined(KELVIN_WAKES) || defined(PROPELLER_WASH)) if (trit_doWakes) { wakeSlopeAndFoam.xyz = vec3(0.0, 0.0, 0.0); localVLocal.z += applyKelvinWakes(localVWorld, fade); localVLocal.z += applyCircularWaves(localVWorld, fade); #ifdef PER_FRAGMENT_PROP_WASH propWashCoord = localVWorld; #else applyPropWash(localVWorld); #endif applyLeewardDampening(localVWorld); } else { #ifdef PROPELLER_WASH washTexCoords = vec3(0.0, 0.0, 0.0); #endif } #endif #ifdef LEEWARD_DAMPENING localVLocal.z += displacement.z * fade * leewardDampening; #else localVLocal.z += displacement.z * fade; #endif localVLocal.z += applyBreakingWaves(localVWorld, fade, vWorld); foamTexCoords = localVWorld.xy / trit_foamScale; noiseTexCoords = texCoords * 0.03; vLocal = trit_invBasis * localVLocal; } bool projectToSea(in vec4 v, out vec4 vLocal, out vec4 vWorld) { // Get the line this screen position projects to vec4 p0 = v; vec4 p1 = v; p0.z = trit_zNearFar.x; p1.z = trit_zNearFar.y; // Transform into world coords p0 = trit_invModelviewProj * p0; p1 = trit_invModelviewProj * p1; // Intersect with the sea level vec3 up = trit_invBasis * vec3(0.0, 0.0, 1.0); vec4 p = trit_plane; float altitude = dot(trit_cameraPos, up) - trit_seaLevel; float offset = 0; if (clamp(altitude, 0.0, PRECISION_GUARD) == altitude) { p.w += PRECISION_GUARD; offset = PRECISION_GUARD; } else if (clamp(altitude, -PRECISION_GUARD, 0.0) == altitude) { p.w -= PRECISION_GUARD; offset = -PRECISION_GUARD; } vec4 dp = p1 - p0; float t = -dot(p0, p) / dot( dp, p); if (t > 0.0 && t < 1.0) { vLocal = dp * t + p0; vLocal /= vLocal.w; vLocal.xyz += up * offset; vWorld = vLocal + vec4(trit_cameraPos, 1.0); return true; } else { vLocal = vec4(0.0, 0.0, 0.0, 0.0); vWorld = vec4(0.0, 0.0, 0.0, 0.0); return false; } } void main() { wakeSlopeAndFoam = vec3( 0.0 ); transparency = 0.0; #ifdef LEEWARD_DAMPENING leewardDampening = 1.0; #endif vec4 worldPos, localPos; #ifdef OPENGL32 vec4 gridPos = trit_gridScale * vec4(vertex.x, vertex.y, 0.0, 1.0); #else vec4 gridPos = trit_gridScale * gl_Vertex; #endif if (projectToSea(gridPos, localPos, worldPos)) { computeTransparency(worldPos.xyz, localPos.xyz); // Displace displace(worldPos.xyz, localPos.xyz); V = localPos.xyz; // Project it back again, apply depth offset. vec4 v = trit_modelview * localPos; vVertex_Eye_Space = v; v.w -= trit_depthOffset; vec4 vVertInProjSpc = trit_projection * v; vVertex_Projection_Space = vVertInProjSpc; if (trit_bypassOverridePosition && trit_depthOnly) { gl_Position = vVertInProjSpc; } else { gl_Position = overridePosition(vVertInProjSpc); } user_intercept(V + trit_cameraPos, V, vVertex_Eye_Space, vVertex_Projection_Space); } else { V = vec3(0.0, 0.0, 0.0); foamTexCoords = vec2(0.0, 0.0); texCoords = vec2(0.0, 0.0); noiseTexCoords = vec2(0.0, 0.0); wakeSlopeAndFoam = vec3(0.0, 0.0, 0.0); #ifdef PROPELLER_WASH washTexCoords = vec3(0.0, 0.0, 0.0); #endif fogFactor = 1.0; transparency = 0.0; vVertex_Eye_Space = vec4(0.0,0.0,0.0,1.0); vec4 vVertInProjSpc = vec4(gridPos.x, gridPos.y, 1000.0, 1.0); vVertex_Projection_Space = vVertInProjSpc; gl_Position = vVertInProjSpc; } float fogExponent = length(V.xyz) * trit_fogDensity; fogFactor = clamp(exp(-(fogExponent * fogExponent)), 0.0, 1.0); }