Index: ps/trunk/binaries/data/mods/public/shaders/glsl/water_high.fs =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/glsl/water_high.fs (revision 18442) +++ ps/trunk/binaries/data/mods/public/shaders/glsl/water_high.fs (revision 18443) @@ -1,398 +1,358 @@ #version 110 // Environment settings uniform vec3 ambient; uniform vec3 sunDir; uniform vec3 sunColor; uniform mat4 skyBoxRot; uniform vec3 cameraPos; uniform sampler2D losMap; uniform float waviness; // "Wildness" of the reflections and refractions; choose based on texture uniform vec3 color; // color of the water uniform vec3 tint; // Tint for refraction (used to simulate particles in water) uniform float murkiness; // Amount of tint to blend in with the refracted color uniform float windAngle; varying vec2 WindCosSin; uniform vec3 fogColor; uniform vec2 fogParams; uniform vec2 screenSize; uniform float time; varying vec3 worldPos; varying float waterDepth; varying vec2 waterInfo; varying vec4 normalCoords; varying vec3 reflectionCoords; varying vec3 refractionCoords; varying vec2 losCoords; varying float fwaviness; uniform float mapSize; uniform samplerCube skyCube; uniform sampler2D normalMap; uniform sampler2D normalMap2; #if USE_FANCY_EFFECTS uniform sampler2D waterEffectsTexNorm; uniform sampler2D waterEffectsTexOther; #endif uniform vec4 waveParams1; // wavyEffect, BaseScale, Flattenism, Basebump uniform vec4 waveParams2; // Smallintensity, Smallbase, Bigmovement, Smallmovement uniform sampler2D reflectionMap; #if USE_REFRACTION uniform sampler2D refractionMap; #endif #if USE_REAL_DEPTH uniform sampler2D depthTex; #endif #if USE_SHADOWS_ON_WATER && USE_SHADOW varying vec4 v_shadow; #if USE_SHADOW_SAMPLER uniform sampler2DShadow shadowTex; #if USE_SHADOW_PCF uniform vec4 shadowScale; #endif #else uniform sampler2D shadowTex; #endif float get_shadow(vec4 coords) { #if USE_SHADOWS_ON_WATER && !DISABLE_RECEIVE_SHADOWS #if USE_SHADOW_SAMPLER #if USE_SHADOW_PCF vec2 offset = fract(coords.xy - 0.5); vec4 size = vec4(offset + 1.0, 2.0 - offset); vec4 weight = (vec4(1.0, 1.0, -0.5, -0.5) + (coords.xy - 0.5*offset).xyxy) * shadowScale.zwzw; return (1.0/9.0)*dot(size.zxzx*size.wwyy, vec4(shadow2D(shadowTex, vec3(weight.zw, coords.z)).r, shadow2D(shadowTex, vec3(weight.xw, coords.z)).r, shadow2D(shadowTex, vec3(weight.zy, coords.z)).r, shadow2D(shadowTex, vec3(weight.xy, coords.z)).r)); #else return shadow2D(shadowTex, coords.xyz).r; #endif #else if (coords.z >= 1.0) return 1.0; return (coords.z <= texture2D(shadowTex, coords.xy).x ? 1.0 : 0.0); #endif #else return 1.0; #endif } #endif // TODO: convert this to something not only for AABBs struct Ray { vec3 Origin; vec3 Direction; }; float IntersectBox (in Ray ray, in vec3 minimum, in vec3 maximum) { vec3 OMIN = ( minimum - ray.Origin ) / ray.Direction; vec3 OMAX = ( maximum - ray.Origin ) / ray.Direction; vec3 MAX = max ( OMAX, OMIN ); return min ( MAX.x, min ( MAX.y, MAX.z ) ); } vec3 get_fog(vec3 color) { float density = fogParams.x; float maxFog = fogParams.y; const float LOG2 = 1.442695; float z = gl_FragCoord.z / gl_FragCoord.w; float fogFactor = exp2(-density * density * z * z * LOG2); fogFactor = fogFactor * (1.0 - maxFog) + maxFog; fogFactor = clamp(fogFactor, 0.0, 1.0); return mix(fogColor, color, fogFactor); } void main() -{ - //gl_FragColor = texture2D(waterEffectsTex, gl_FragCoord.xy/screenSize); - //return; - +{ float fresnel; - float t; // Temporary variable vec2 reflCoords, refrCoords; vec3 reflColor, refrColor, specular; - float losMod; + float losMod, reflMod; - vec3 l = -sunDir; vec3 v = normalize(cameraPos - worldPos); - vec3 h = normalize(l + v); // Calculate water normals. float wavyEffect = waveParams1.r; float baseScale = waveParams1.g; float flattenism = waveParams1.b; float baseBump = waveParams1.a; - - float smallIntensity = waveParams2.r; - float smallBase = waveParams2.g; float BigMovement = waveParams2.b; - float SmallMovement = waveParams2.a; float moddedTime = mod(time * 60.0, 8.0) / 8.0; // This method uses 60 animated water frames. We're blending between each two frames - // TODO: could probably have fewer frames thanks to this blending. // Scale the normal textures by waviness so that big waviness means bigger waves. vec3 ww1 = texture2D(normalMap, (normalCoords.st + normalCoords.zw * BigMovement*waviness/10.0) * (baseScale - waviness/wavyEffect)).xzy; vec3 ww2 = texture2D(normalMap2, (normalCoords.st + normalCoords.zw * BigMovement*waviness/10.0) * (baseScale - waviness/wavyEffect)).xzy; vec3 wwInterp = mix(ww1, ww2, moddedTime) - vec3(0.5,0.0,0.5); ww1.x = wwInterp.x * WindCosSin.x - wwInterp.z * WindCosSin.y; ww1.z = wwInterp.x * WindCosSin.y + wwInterp.z * WindCosSin.x; ww1.y = wwInterp.y; - vec3 smallWW = texture2D(normalMap, (normalCoords.st + normalCoords.zw * SmallMovement*waviness/10.0) * baseScale*3.0).xzy; - vec3 smallWW2 = texture2D(normalMap2, (normalCoords.st + normalCoords.zw * SmallMovement*waviness/10.0) * baseScale*3.0).xzy; - vec3 smallWWInterp = mix(smallWW, smallWW2, moddedTime) - vec3(0.5,0.0,0.5); - - smallWW.x = smallWWInterp.x * WindCosSin.x - smallWWInterp.z * WindCosSin.y; - smallWW.z = smallWWInterp.x * WindCosSin.y + smallWWInterp.z * WindCosSin.x; - smallWW.y = smallWWInterp.y; - - ww1 += vec3(smallWW)*(fwaviness/10.0*smallIntensity + smallBase); - - ww1 = mix(smallWW, ww1, waterInfo.r); - // Flatten them based on waviness. - vec3 n = normalize(mix(vec3(0.0,1.0,0.0),ww1, clamp(baseBump + fwaviness/flattenism,0.0,1.0))); + vec3 n = normalize(mix(vec3(0.0,1.0,0.0), ww1, clamp(baseBump + fwaviness/flattenism,0.0,1.0))); #if USE_FANCY_EFFECTS vec4 fancyeffects = texture2D(waterEffectsTexNorm, gl_FragCoord.xy/screenSize); n = mix(vec3(0.0,1.0,0.0), n,0.5 + waterInfo.r/2.0); n.xz = mix(n.xz, fancyeffects.rb,fancyeffects.a/2.0); #else - n = mix(vec3(0.0,1.0,0.0), n,0.5 + waterInfo.r/2.0); + n = mix(vec3(0.0,1.0,0.0), n, 0.5 + waterInfo.r/2.0); #endif - n = vec3(-n.x,n.y,-n.z); + n = vec3(-n.x,n.y,-n.z); // The final wave normal vector. - // simulates how parallel the "point->sun", "view->point" vectors are. - float ndoth = dot(n , h); - - // how perpendicular to the normal our view is. Used for fresnel. + // How perpendicular to the normal our view is. Used for fresnel. float ndotv = clamp(dot(n, v),0.0,1.0); - // diffuse lighting-like. used for shadows? - float ndotl = (dot(n, l) + 1.0)/2.0; + // Fresnel for "how much reflection vs how much refraction". + fresnel = clamp(((pow(1.1 - ndotv, 2.0)) * 1.5), 0.1, 0.75); // Approximation. I'm using 1.1 and not 1.0 because it causes artifacts, see #1714 + // Specular lighting vectors + vec3 specVector = reflect(sunDir, ww1); + float specIntensity = pow(dot(specVector, v), 100.0); + + specular = specIntensity*1.2 * mix(vec3(1.5), sunColor,0.5); + float depth; #if USE_REAL_DEPTH // Don't change these two. They should match the values in the config (TODO: dec uniforms). float zNear = 2.0; float zFar = 4096.0; // Okay so here it's a tad complicated. I want to distort the depth buffer along the waves for a nice effect. // However this causes a problem around underwater objects (think fishes): on some pixels, the depth will be seen as the same as the fishes' // and the color will be grass ( cause I don't distort the refraction coord by exactly the same stuff) // Also, things like towers with the feet in water would cause the buffer to see the depth as actually negative in some places. // So what I do is first check the undistorted depth, then I compare with the distorted value and fix. float water_b = gl_FragCoord.z; float water_n = 2.0 * water_b - 1.0; float waterDBuffer = 2.0 * zNear * zFar / (zFar + zNear - water_n * (zFar - zNear)); float undistortedBuffer = texture2D(depthTex, (gl_FragCoord.xy) / screenSize).x; float undisto_z_b = texture2D(depthTex, (gl_FragCoord.xy) / screenSize).x; float undisto_z_n = 2.0 * undisto_z_b - 1.0; float waterDepth_undistorted = (2.0 * zNear * zFar / (zFar + zNear - undisto_z_n * (zFar - zNear)) - waterDBuffer); vec2 depthCoord = clamp((gl_FragCoord.xy) / screenSize - n.xz*clamp( waterDepth_undistorted/400.0,0.0,0.05) , 0.001, 0.999); float z_b = texture2D(depthTex, depthCoord).x; if (z_b < undisto_z_b) z_b = undisto_z_b; float z_n = 2.0 * z_b - 1.0; depth = (2.0 * zNear * zFar / (zFar + zNear - z_n * (zFar - zNear)) - waterDBuffer); #else depth = waterDepth / (min(0.5,v.y)*1.5*min(0.5,v.y)*2.0); #endif #if USE_FANCY_EFFECTS depth = max(depth,fancyeffects.a); #endif - // Fresnel for "how much reflection vs how much refraction". - // Since we're not trying to simulate a realistic ocean 100%, aim for something that gives a little too much reflection - // because we're not used to seeing the see from above. - fresnel = clamp(pow(1.05 - ndotv, 1.1),0.0,0.8); // approximation. I'm using 1.05 and not 1.0 because it causes artifacts, see #1714 - // multiply by v.y so that in the distance refraction wins. - // TODO: this is a hack because reflections don't work in the distance. - fresnel = clamp(fresnel*1.5,0.0,0.9); - fresnel *= min(1.0,log(1.0 + v.y*5.0)); - - //gl_FragColor = vec4(fresnel,fresnel,fresnel,1.0); - //return; - #if USE_SHADOWS_ON_WATER && USE_SHADOW float shadow = get_shadow(vec4(v_shadow.xy, v_shadow.zw)); #endif // for refraction, we want to adjust the value by v.y slightly otherwise it gets too different between "from above" and "from the sides". // And it looks weird (again, we are not used to seeing water from above). - float fixedVy = max(v.y,0.1); - - float distoFactor = clamp(depth/2.0,0.0,7.0); + float fixedVy = max(v.y,0.01); float murky = mix(200.0,0.1,pow(murkiness,0.25)); #if USE_REFRACTION - refrCoords = clamp( (0.5*refractionCoords.xy - n.xz * distoFactor*7.0) / refractionCoords.z + 0.5,0.0,1.0); // Unbias texture coords + // distoFactor controls the amount of distortion relative to wave normals. + float distoFactor = 0.5 + clamp(depth/2.0,0.0,7.0); + + // Distort the texture coords under where the water is to simulate refraction. + refrCoords = (0.5 * refractionCoords.xy - n.xz * distoFactor) / refractionCoords.z + 0.5; vec3 refColor = texture2D(refractionMap, refrCoords).rgb; - if (refColor.r > refColor.g + refColor.b + 0.25) + + // Note, the refraction map is cleared using (255, 0, 0), so pixels outside of the water plane are pure red. + // If we get a pure red fragment, use an undistorted/less distorted coord instead. + if (refColor.r > 0.999 && refColor.g < 0.001) { - refrCoords = clamp( (0.5*refractionCoords.xy + n.xz) / refractionCoords.z + 0.5,0.0,1.0); // Unbias texture coords + refrCoords = (0.5*refractionCoords.xy) / refractionCoords.z + 0.5; refColor = texture2D(refractionMap, refrCoords).rgb; + + if (refColor.r > 0.999 && refColor.g < 0.001) + fresnel = 1.0; + } + else + { + // blur the refraction map, distoring using n so that it looks more random than it really is + // and thus looks much better. + float blur = (0.3+clamp(n.x,-0.1,0.1))/refractionCoords.z; + + vec3 blurColor = texture2D(refractionMap, refrCoords + vec2(blur+n.x, blur+n.z)).rgb; + blurColor += texture2D(refractionMap, refrCoords + vec2(-blur, blur+n.z)).rgb; + blurColor += texture2D(refractionMap, refrCoords + vec2(-blur, -blur+n.x)).rgb; + blurColor += texture2D(refractionMap, refrCoords + vec2(blur+n.z, -blur)).rgb; + blurColor /= 4.0; + float blurFactor = (distoFactor/7.0); + refColor = (refColor + blurColor * blurFactor) / (1.0+blurFactor); } - // TODO: make murkiness (both types rematter on that. - // linearly extinct the water. This is how quickly we see nothing but the pure water color + // Apply water tint and murk color. float extFact = max(0.0,1.0 - (depth*fixedVy/murky)); - // This is how tinted the water is, ie how quickly the refracted floor takes the tint of the water float ColextFact = max(0.0,1.0 - (depth*fixedVy/murky)); vec3 colll = mix(refColor*tint,refColor,ColextFact); -#if USE_SHADOWS_ON_WATER && USE_SHADOW - // TODO: refrColor = mix(color, colll, extFact); #else + // Apply water tint and murk color only. + float extFact = max(0.0,1.0 - (depth*fixedVy/murky)); + float ColextFact = max(0.0,1.0 - (depth*fixedVy/murky)); + vec3 colll = mix(color*tint,color,ColextFact); + refrColor = mix(color, colll, extFact); #endif -#else - // linearly extinct the water. This is how quickly we see nothing but the pure water color - float extFact = max(0.0,1.0 - (depth*fixedVy/20.0)); - // using both those factors, get our transparency. - // This will be our base transparency on top. - float base = 0.4 + depth*fixedVy/15.0; // TODO: murkiness. - float alphaCoeff = mix(1.0, base, extFact); - refrColor = color; -#endif #if USE_REFLECTION // Reflections - // we use real reflections against th skybox, and distort a texture of objects closer. + // We use real reflections against the skybox, and distort a texture of objects closer. vec3 eye = reflect(v,n); - //eye.y = min(-0.2,eye.y); - // let's calculate where we intersect with the skycube. - Ray myRay = Ray(vec3(worldPos.x/4.0,worldPos.y,worldPos.z/4.0),eye); - vec3 start = vec3(-1500.0 + mapSize/2.0,-100.0,-1500.0 + mapSize/2.0); - vec3 end = vec3(1500.0 + mapSize/2.0,500.0,1500.0 + mapSize/2.0); - float tmin = IntersectBox(myRay,start,end); - vec4 newpos = vec4(-worldPos.x/4.0,worldPos.y,-worldPos.z/4.0,1.0) + vec4(eye * tmin,0.0) - vec4(-mapSize/2.0,worldPos.y,-mapSize/2.0,0.0); - //newpos = normalize(newpos); - newpos *= skyBoxRot; - newpos.y *= 4.0; - reflColor = textureCube(skyCube, newpos.rgb).rgb; - - // Reflections appear more distorted when viewed from a lower angle. Simulate this. - float angleEffect = clamp(1.3 - dot(vec3(0.0,1.0,0.0), v),0.0,1.0); - reflCoords = clamp( (0.5*reflectionCoords.xy - 40.0 * n.zx * angleEffect) / reflectionCoords.z + 0.5,0.0,1.0); // Unbias texture coords + float refVY = clamp(v.y*2.0,0.05,1.0); + + // Distort the reflection coords based on waves. + reflCoords = (0.5*reflectionCoords.xy - 15.0 * n.zx / refVY) / reflectionCoords.z + 0.5; vec4 refTex = texture2D(reflectionMap, reflCoords); - fresnel = clamp(fresnel+refTex.a/3.0,0.0,1.0); - reflColor = refTex.rgb * refTex.a + reflColor*(1.0-refTex.a); - + + reflColor = refTex.rgb; + + if (refTex.a < 0.99) + { + // Calculate where we intersect with the skycube. + Ray myRay = Ray(vec3(worldPos.x/4.0,worldPos.y,worldPos.z/4.0),eye); + vec3 start = vec3(-1500.0 + mapSize/2.0,-100.0,-1500.0 + mapSize/2.0); + vec3 end = vec3(1500.0 + mapSize/2.0,500.0,1500.0 + mapSize/2.0); + float tmin = IntersectBox(myRay,start,end); + vec4 newpos = vec4(-worldPos.x/4.0,worldPos.y,-worldPos.z/4.0,1.0) + vec4(eye * tmin,0.0) - vec4(-mapSize/2.0,worldPos.y,-mapSize/2.0,0.0); + newpos *= skyBoxRot; + newpos.y *= 4.0; + // Interpolate between the sky color and nearby objects. + reflColor = mix(textureCube(skyCube, newpos.rgb).rgb, refTex.rgb, refTex.a); + } + + // reflMod is used to reduce the intensity of sky reflections, which otherwise are too extreme. + reflMod = max(refTex.a, 0.75); #else - // Temp fix for some ATI cards (see irc logs on th 1st of august betwee, fexor and wraitii) - //reflCoords = clamp( (0.5*reflectionCoords.xy - waviness * mix(1.0, 20.0,waviness/10.0) * n.zx) / reflectionCoords.z + 0.5,0.0,1.0); // Unbias texture coords - //vec3 refTex = texture2D(reflectionMap, reflCoords).rgb; - //reflColor = refTex.rgb; reflColor = vec3(0.15, 0.7, 0.82); #endif - // TODO: At very low angles the reflection stuff doesn't really work any more: - // IRL you would get a blur of the sky, but we don't have that precision (would require mad oversampling) - // So tend towards a predefined color (per-map) which looks like what the skybox would look like if you really blurred it. - // The TODO here would be to precompute a band (1x32?) that represents the average color around the map. - // TODO: another issue is that at high distances (half map) the texture blurs into flatness. Using better mipmaps won't really solve it - // So we'll need to stop showing reflections and default to sky color there too. - // Unless maybe waviness is so low that you would see like in a mirror anyways. - //float disttt = distance(worldPos,cameraPos); - //reflColor = mix(vec3(0.5,0.5,0.55), reflColor, clamp(1.0-disttt/600.0*disttt/600.0,0.0,1.0));//clamp(-0.05 + v.y*20.0,0.0,1.0)); - - // Specular. - specular = pow(ndoth, mix(5.0,2000.0, clamp(v.y*v.y*2.0,0.0,1.0)))*sunColor * 1.5;// * sunColor * 1.5 * ww.r; - losMod = texture2D(losMap, losCoords.st).a; losMod = losMod < 0.03 ? 0.0 : losMod; - float wavesFresnel = 1.0; - -#if USE_FANCY_EFFECTS - wavesFresnel = mix(1.0-fancyeffects.a,1.0,clamp(depth,0.0,1.0)); -#endif - vec3 color; #if USE_SHADOWS_ON_WATER && USE_SHADOW float fresShadow = mix(fresnel, fresnel*shadow, 0.05 + murkiness*0.2); - color = mix(refrColor, reflColor, fresShadow * wavesFresnel); + color = mix(refrColor, reflColor, fresShadow); #else - color = mix(refrColor, reflColor, fresnel * wavesFresnel); + color = mix(refrColor, reflColor, fresnel * reflMod); #endif - + #if USE_SHADOWS_ON_WATER && USE_SHADOW color += shadow*specular; #else color += specular; #endif #if USE_FANCY_EFFECTS vec4 FoamEffects = texture2D(waterEffectsTexOther, gl_FragCoord.xy/screenSize); vec3 foam1 = texture2D(normalMap, (normalCoords.st + normalCoords.zw * BigMovement*waviness/10.0) * (baseScale - waviness/wavyEffect)).aaa; vec3 foam2 = texture2D(normalMap2, (normalCoords.st + normalCoords.zw * BigMovement*waviness/10.0) * (baseScale - waviness/wavyEffect)).aaa; vec3 foam3 = texture2D(normalMap, normalCoords.st/6.0 - normalCoords.zw * 0.02).aaa; vec3 foam4 = texture2D(normalMap2, normalCoords.st/6.0 - normalCoords.zw * 0.02).aaa; vec3 foaminterp = mix(foam1, foam2, moddedTime); foaminterp *= mix(foam3, foam4, moddedTime); foam1.x = foaminterp.x * WindCosSin.x - foaminterp.z * WindCosSin.y; - //foam1.z = foaminterp.x * WindCosSin.y + foaminterp.z * WindCosSin.x; - //foam1.y = foaminterp.y; + float foam = FoamEffects.r * FoamEffects.a*0.4 + pow(foam1.x*(5.0+waviness),(2.6 - waviness/5.5)); - foam *= ndotl; - gl_FragColor.rgb = get_fog(color) * losMod + foam * losMod;// + fancyeffects.a * losMod; + gl_FragColor.rgb = get_fog(color) * losMod + foam * losMod; #else gl_FragColor.rgb = get_fog(color) * losMod; #endif +gl_FragColor.a = 1.0; + #if !USE_REFRACTION - gl_FragColor.a = clamp(depth*2.0,0.0,1.0) * alphaCoeff; -#else - gl_FragColor.a = clamp(depth*5.0,0.0,1.0); +gl_FragColor.a = 1.1-extFact; #endif #if USE_FANCY_EFFECTS if (fancyeffects.a < 0.05 && waterDepth < -1.0 ) gl_FragColor.a = 0.0; #endif - - //gl_FragColor = vec4(sunColor,1.0); } Index: ps/trunk/source/renderer/Renderer.cpp =================================================================== --- ps/trunk/source/renderer/Renderer.cpp (revision 18442) +++ ps/trunk/source/renderer/Renderer.cpp (revision 18443) @@ -1,2117 +1,2117 @@ /* Copyright (C) 2014 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * higher level interface on top of OpenGL to render basic objects: * terrain, models, sprites, particles etc. */ #include "precompiled.h" #include #include #include #include #include "Renderer.h" #include "lib/bits.h" // is_pow2 #include "lib/res/graphics/ogl_tex.h" #include "lib/allocators/shared_ptr.h" #include "maths/Matrix3D.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Game.h" #include "ps/Profile.h" #include "ps/Filesystem.h" #include "ps/World.h" #include "ps/Loader.h" #include "ps/ProfileViewer.h" #include "graphics/Camera.h" #include "graphics/Decal.h" #include "graphics/FontManager.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/LOSTexture.h" #include "graphics/MaterialManager.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" #include "graphics/ParticleManager.h" #include "graphics/Patch.h" #include "graphics/ShaderManager.h" #include "graphics/Terrain.h" #include "graphics/Texture.h" #include "graphics/TextureManager.h" #include "renderer/HWLightingModelRenderer.h" #include "renderer/InstancingModelRenderer.h" #include "renderer/ModelRenderer.h" #include "renderer/OverlayRenderer.h" #include "renderer/ParticleRenderer.h" #include "renderer/PostprocManager.h" #include "renderer/RenderModifiers.h" #include "renderer/ShadowMap.h" #include "renderer/SilhouetteRenderer.h" #include "renderer/SkyManager.h" #include "renderer/TerrainOverlay.h" #include "renderer/TerrainRenderer.h" #include "renderer/TimeManager.h" #include "renderer/VertexBufferManager.h" #include "renderer/WaterManager.h" #include "scriptinterface/ScriptInterface.h" extern bool g_GameRestarted; struct SScreenRect { GLint x1, y1, x2, y2; }; /////////////////////////////////////////////////////////////////////////////////// // CRendererStatsTable - Profile display of rendering stats /** * Class CRendererStatsTable: Implementation of AbstractProfileTable to * display the renderer stats in-game. * * Accesses CRenderer::m_Stats by keeping the reference passed to the * constructor. */ class CRendererStatsTable : public AbstractProfileTable { NONCOPYABLE(CRendererStatsTable); public: CRendererStatsTable(const CRenderer::Stats& st); // Implementation of AbstractProfileTable interface CStr GetName(); CStr GetTitle(); size_t GetNumberRows(); const std::vector& GetColumns(); CStr GetCellText(size_t row, size_t col); AbstractProfileTable* GetChild(size_t row); private: /// Reference to the renderer singleton's stats const CRenderer::Stats& Stats; /// Column descriptions std::vector columnDescriptions; enum { Row_DrawCalls = 0, Row_TerrainTris, Row_WaterTris, Row_ModelTris, Row_OverlayTris, Row_BlendSplats, Row_Particles, Row_VBReserved, Row_VBAllocated, Row_TextureMemory, Row_ShadersLoaded, // Must be last to count number of rows NumberRows }; }; // Construction CRendererStatsTable::CRendererStatsTable(const CRenderer::Stats& st) : Stats(st) { columnDescriptions.push_back(ProfileColumn("Name", 230)); columnDescriptions.push_back(ProfileColumn("Value", 100)); } // Implementation of AbstractProfileTable interface CStr CRendererStatsTable::GetName() { return "renderer"; } CStr CRendererStatsTable::GetTitle() { return "Renderer statistics"; } size_t CRendererStatsTable::GetNumberRows() { return NumberRows; } const std::vector& CRendererStatsTable::GetColumns() { return columnDescriptions; } CStr CRendererStatsTable::GetCellText(size_t row, size_t col) { char buf[256]; switch(row) { case Row_DrawCalls: if (col == 0) return "# draw calls"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_DrawCalls); return buf; case Row_TerrainTris: if (col == 0) return "# terrain tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_TerrainTris); return buf; case Row_WaterTris: if (col == 0) return "# water tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_WaterTris); return buf; case Row_ModelTris: if (col == 0) return "# model tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_ModelTris); return buf; case Row_OverlayTris: if (col == 0) return "# overlay tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_OverlayTris); return buf; case Row_BlendSplats: if (col == 0) return "# blend splats"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_BlendSplats); return buf; case Row_Particles: if (col == 0) return "# particles"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_Particles); return buf; case Row_VBReserved: if (col == 0) return "VB reserved"; sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesReserved() / 1024); return buf; case Row_VBAllocated: if (col == 0) return "VB allocated"; sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesAllocated() / 1024); return buf; case Row_TextureMemory: if (col == 0) return "textures uploaded"; sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_Renderer.GetTextureManager().GetBytesUploaded() / 1024); return buf; case Row_ShadersLoaded: if (col == 0) return "shader effects loaded"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)g_Renderer.GetShaderManager().GetNumEffectsLoaded()); return buf; default: return "???"; } } AbstractProfileTable* CRendererStatsTable::GetChild(size_t UNUSED(row)) { return 0; } /////////////////////////////////////////////////////////////////////////////////// // CRenderer implementation /** * Struct CRendererInternals: Truly hide data that is supposed to be hidden * in this structure so it won't even appear in header files. */ struct CRendererInternals { NONCOPYABLE(CRendererInternals); public: /// true if CRenderer::Open has been called bool IsOpen; /// true if shaders need to be reloaded bool ShadersDirty; /// Table to display renderer stats in-game via profile system CRendererStatsTable profileTable; /// Shader manager CShaderManager shaderManager; /// Water manager WaterManager waterManager; /// Sky manager SkyManager skyManager; /// Texture manager CTextureManager textureManager; /// Terrain renderer TerrainRenderer terrainRenderer; /// Overlay renderer OverlayRenderer overlayRenderer; /// Particle manager CParticleManager particleManager; /// Particle renderer ParticleRenderer particleRenderer; /// Material manager CMaterialManager materialManager; /// Time manager CTimeManager timeManager; /// Shadow map ShadowMap shadow; /// Postprocessing effect manager CPostprocManager postprocManager; CFontManager fontManager; SilhouetteRenderer silhouetteRenderer; /// Various model renderers struct Models { // NOTE: The current renderer design (with ModelRenderer, ModelVertexRenderer, // RenderModifier, etc) is mostly a relic of an older design that implemented // the different materials and rendering modes through extensive subclassing // and hooking objects together in various combinations. // The new design uses the CShaderManager API to abstract away the details // of rendering, and uses a data-driven approach to materials, so there are // now a small number of generic subclasses instead of many specialised subclasses, // but most of the old infrastructure hasn't been refactored out yet and leads to // some unwanted complexity. // Submitted models are split on two axes: // - Normal vs Transp[arent] - alpha-blended models are stored in a separate // list so we can draw them above/below the alpha-blended water plane correctly // - Skinned vs Unskinned - with hardware lighting we don't need to // duplicate mesh data per model instance (except for skinned models), // so non-skinned models get different ModelVertexRenderers ModelRendererPtr NormalSkinned; ModelRendererPtr NormalUnskinned; // == NormalSkinned if unskinned shader instancing not supported ModelRendererPtr TranspSkinned; ModelRendererPtr TranspUnskinned; // == TranspSkinned if unskinned shader instancing not supported ModelVertexRendererPtr VertexRendererShader; ModelVertexRendererPtr VertexInstancingShader; ModelVertexRendererPtr VertexGPUSkinningShader; LitRenderModifierPtr ModShader; } Model; CShaderDefines globalContext; CRendererInternals() : IsOpen(false), ShadersDirty(true), profileTable(g_Renderer.m_Stats), textureManager(g_VFS, false, false) { } /** * Load the OpenGL projection and modelview matrices and the viewport according * to the given camera. */ void SetOpenGLCamera(const CCamera& camera) { CMatrix3D view; camera.m_Orientation.GetInverse(view); const CMatrix3D& proj = camera.GetProjection(); #if CONFIG2_GLES #warning TODO: fix CRenderer camera handling for GLES (do not use global matrixes) #else glMatrixMode(GL_PROJECTION); glLoadMatrixf(&proj._11); glMatrixMode(GL_MODELVIEW); glLoadMatrixf(&view._11); #endif g_Renderer.SetViewport(camera.GetViewPort()); } /** * Renders all non-alpha-blended models with the given context. */ void CallModelRenderers(const CShaderDefines& context, int cullGroup, int flags) { CShaderDefines contextSkinned = context; if (g_Renderer.m_Options.m_GPUSkinning) { contextSkinned.Add(str_USE_INSTANCING, str_1); contextSkinned.Add(str_USE_GPU_SKINNING, str_1); } Model.NormalSkinned->Render(Model.ModShader, contextSkinned, cullGroup, flags); if (Model.NormalUnskinned != Model.NormalSkinned) { CShaderDefines contextUnskinned = context; contextUnskinned.Add(str_USE_INSTANCING, str_1); Model.NormalUnskinned->Render(Model.ModShader, contextUnskinned, cullGroup, flags); } } /** * Renders all alpha-blended models with the given context. */ void CallTranspModelRenderers(const CShaderDefines& context, int cullGroup, int flags) { CShaderDefines contextSkinned = context; if (g_Renderer.m_Options.m_GPUSkinning) { contextSkinned.Add(str_USE_INSTANCING, str_1); contextSkinned.Add(str_USE_GPU_SKINNING, str_1); } Model.TranspSkinned->Render(Model.ModShader, contextSkinned, cullGroup, flags); if (Model.TranspUnskinned != Model.TranspSkinned) { CShaderDefines contextUnskinned = context; contextUnskinned.Add(str_USE_INSTANCING, str_1); Model.TranspUnskinned->Render(Model.ModShader, contextUnskinned, cullGroup, flags); } } }; /////////////////////////////////////////////////////////////////////////////////// // CRenderer constructor CRenderer::CRenderer() { m = new CRendererInternals; m_WaterManager = &m->waterManager; m_SkyManager = &m->skyManager; g_ProfileViewer.AddRootTable(&m->profileTable); m_Width = 0; m_Height = 0; m_TerrainRenderMode = SOLID; m_ModelRenderMode = SOLID; m_ClearColor[0] = m_ClearColor[1] = m_ClearColor[2] = m_ClearColor[3] = 0; m_DisplayTerrainPriorities = false; m_SkipSubmit = false; m_Options.m_NoVBO = false; m_Options.m_RenderPath = RP_DEFAULT; m_Options.m_Shadows = false; m_Options.m_ShadowAlphaFix = true; m_Options.m_ARBProgramShadow = true; m_Options.m_ShadowPCF = false; m_Options.m_Particles = false; m_Options.m_Silhouettes = false; m_Options.m_PreferGLSL = false; m_Options.m_ForceAlphaTest = false; m_Options.m_GPUSkinning = false; m_Options.m_SmoothLOS = false; m_Options.m_Postproc = false; m_Options.m_ShowSky = false; m_Options.m_DisplayFrustum = false; // TODO: be more consistent in use of the config system CFG_GET_VAL("preferglsl", m_Options.m_PreferGLSL); CFG_GET_VAL("forcealphatest", m_Options.m_ForceAlphaTest); CFG_GET_VAL("gpuskinning", m_Options.m_GPUSkinning); CFG_GET_VAL("smoothlos", m_Options.m_SmoothLOS); CFG_GET_VAL("postproc", m_Options.m_Postproc); CStr skystring = "0 0 0"; CColor skycolor; CFG_GET_VAL("skycolor", skystring); if (skycolor.ParseString(skystring, 255.f)) SetClearColor(skycolor.AsSColor4ub()); #if CONFIG2_GLES // Override config option since GLES only supports GLSL m_Options.m_PreferGLSL = true; #endif m_ShadowZBias = 0.02f; m_ShadowMapSize = 0; m_LightEnv = NULL; m_CurrentScene = NULL; m_hCompositeAlphaMap = 0; m_Stats.Reset(); RegisterFileReloadFunc(ReloadChangedFileCB, this); } /////////////////////////////////////////////////////////////////////////////////// // CRenderer destructor CRenderer::~CRenderer() { UnregisterFileReloadFunc(ReloadChangedFileCB, this); // we no longer UnloadAlphaMaps / UnloadWaterTextures here - // that is the responsibility of the module that asked for // them to be loaded (i.e. CGameView). delete m; } /////////////////////////////////////////////////////////////////////////////////// // EnumCaps: build card cap bits void CRenderer::EnumCaps() { // assume support for nothing m_Caps.m_VBO = false; m_Caps.m_ARBProgram = false; m_Caps.m_ARBProgramShadow = false; m_Caps.m_VertexShader = false; m_Caps.m_FragmentShader = false; m_Caps.m_Shadows = false; m_Caps.m_PrettyWater = false; // now start querying extensions if (!m_Options.m_NoVBO && ogl_HaveExtension("GL_ARB_vertex_buffer_object")) m_Caps.m_VBO = true; if (0 == ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", NULL)) { m_Caps.m_ARBProgram = true; if (ogl_HaveExtension("GL_ARB_fragment_program_shadow")) m_Caps.m_ARBProgramShadow = true; } if (0 == ogl_HaveExtensions(0, "GL_ARB_shader_objects", "GL_ARB_shading_language_100", NULL)) { if (ogl_HaveExtension("GL_ARB_vertex_shader")) m_Caps.m_VertexShader = true; if (ogl_HaveExtension("GL_ARB_fragment_shader")) m_Caps.m_FragmentShader = true; } #if CONFIG2_GLES m_Caps.m_Shadows = true; #else if (0 == ogl_HaveExtensions(0, "GL_ARB_shadow", "GL_ARB_depth_texture", "GL_EXT_framebuffer_object", NULL)) { if (ogl_max_tex_units >= 4) m_Caps.m_Shadows = true; } #endif #if CONFIG2_GLES m_Caps.m_PrettyWater = true; #else if (0 == ogl_HaveExtensions(0, "GL_ARB_vertex_shader", "GL_ARB_fragment_shader", "GL_EXT_framebuffer_object", NULL)) m_Caps.m_PrettyWater = true; #endif } void CRenderer::RecomputeSystemShaderDefines() { CShaderDefines defines; if (GetRenderPath() == RP_SHADER && m_Caps.m_ARBProgram) defines.Add(str_SYS_HAS_ARB, str_1); if (GetRenderPath() == RP_SHADER && m_Caps.m_VertexShader && m_Caps.m_FragmentShader) defines.Add(str_SYS_HAS_GLSL, str_1); if (m_Options.m_PreferGLSL) defines.Add(str_SYS_PREFER_GLSL, str_1); m_SystemShaderDefines = defines; } void CRenderer::ReloadShaders() { ENSURE(m->IsOpen); m->globalContext = m_SystemShaderDefines; if (m_Caps.m_Shadows && m_Options.m_Shadows) { m->globalContext.Add(str_USE_SHADOW, str_1); if (m_Caps.m_ARBProgramShadow && m_Options.m_ARBProgramShadow) m->globalContext.Add(str_USE_FP_SHADOW, str_1); if (m_Options.m_ShadowPCF) m->globalContext.Add(str_USE_SHADOW_PCF, str_1); #if !CONFIG2_GLES m->globalContext.Add(str_USE_SHADOW_SAMPLER, str_1); #endif } if (m_LightEnv) m->globalContext.Add(CStrIntern("LIGHTING_MODEL_" + m_LightEnv->GetLightingModel()), str_1); m->Model.ModShader = LitRenderModifierPtr(new ShaderRenderModifier()); bool cpuLighting = (GetRenderPath() == RP_FIXED); m->Model.VertexRendererShader = ModelVertexRendererPtr(new ShaderModelVertexRenderer(cpuLighting)); m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer(false, m_Options.m_PreferGLSL)); if (GetRenderPath() == RP_SHADER && m_Options.m_GPUSkinning) // TODO: should check caps and GLSL etc too { m->Model.VertexGPUSkinningShader = ModelVertexRendererPtr(new InstancingModelRenderer(true, m_Options.m_PreferGLSL)); m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader)); m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader)); } else { m->Model.VertexGPUSkinningShader.reset(); m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexRendererShader)); m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexRendererShader)); } // Use instancing renderers in shader mode if (GetRenderPath() == RP_SHADER) { m->Model.NormalUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader)); m->Model.TranspUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader)); } else { m->Model.NormalUnskinned = m->Model.NormalSkinned; m->Model.TranspUnskinned = m->Model.TranspSkinned; } m->ShadersDirty = false; } bool CRenderer::Open(int width, int height) { m->IsOpen = true; // Must query card capabilities before creating renderers that depend // on card capabilities. EnumCaps(); // Dimensions m_Width = width; m_Height = height; // set packing parameters glPixelStorei(GL_PACK_ALIGNMENT,1); glPixelStorei(GL_UNPACK_ALIGNMENT,1); // setup default state glDepthFunc(GL_LEQUAL); glEnable(GL_DEPTH_TEST); glCullFace(GL_BACK); glFrontFace(GL_CCW); glEnable(GL_CULL_FACE); GLint bits; glGetIntegerv(GL_DEPTH_BITS,&bits); LOGMESSAGE("CRenderer::Open: depth bits %d",bits); glGetIntegerv(GL_STENCIL_BITS,&bits); LOGMESSAGE("CRenderer::Open: stencil bits %d",bits); glGetIntegerv(GL_ALPHA_BITS,&bits); LOGMESSAGE("CRenderer::Open: alpha bits %d",bits); // Validate the currently selected render path SetRenderPath(m_Options.m_RenderPath); RecomputeSystemShaderDefines(); // Let component renderers perform one-time initialization after graphics capabilities and // the shader path have been determined. m->overlayRenderer.Initialize(); if (m_Options.m_Postproc) m->postprocManager.Initialize(); return true; } // resize renderer view void CRenderer::Resize(int width, int height) { // need to recreate the shadow map object to resize the shadow texture m->shadow.RecreateTexture(); m_Width = width; m_Height = height; m->postprocManager.Resize(); m_WaterManager->Resize(); } ////////////////////////////////////////////////////////////////////////////////////////// // SetOptionBool: set boolean renderer option void CRenderer::SetOptionBool(enum Option opt,bool value) { switch (opt) { case OPT_NOVBO: m_Options.m_NoVBO = value; break; case OPT_SHADOWS: m_Options.m_Shadows = value; MakeShadersDirty(); break; case OPT_WATERUGLY: m_Options.m_WaterUgly = value; break; case OPT_WATERFANCYEFFECTS: m_Options.m_WaterFancyEffects = value; break; case OPT_WATERREALDEPTH: m_Options.m_WaterRealDepth = value; break; case OPT_WATERREFLECTION: m_Options.m_WaterReflection = value; break; case OPT_WATERREFRACTION: m_Options.m_WaterRefraction = value; break; case OPT_SHADOWSONWATER: m_Options.m_WaterShadows = value; break; case OPT_SHADOWPCF: m_Options.m_ShadowPCF = value; MakeShadersDirty(); break; case OPT_PARTICLES: m_Options.m_Particles = value; break; case OPT_PREFERGLSL: m_Options.m_PreferGLSL = value; MakeShadersDirty(); RecomputeSystemShaderDefines(); break; case OPT_SILHOUETTES: m_Options.m_Silhouettes = value; break; case OPT_SHOWSKY: m_Options.m_ShowSky = value; break; case OPT_SMOOTHLOS: m_Options.m_SmoothLOS = value; break; case OPT_POSTPROC: m_Options.m_Postproc = value; break; case OPT_DISPLAYFRUSTUM: m_Options.m_DisplayFrustum = value; break; default: debug_warn(L"CRenderer::SetOptionBool: unknown option"); break; } } ////////////////////////////////////////////////////////////////////////////////////////// // GetOptionBool: get boolean renderer option bool CRenderer::GetOptionBool(enum Option opt) const { switch (opt) { case OPT_NOVBO: return m_Options.m_NoVBO; case OPT_SHADOWS: return m_Options.m_Shadows; case OPT_WATERUGLY: return m_Options.m_WaterUgly; case OPT_WATERFANCYEFFECTS: return m_Options.m_WaterFancyEffects; case OPT_WATERREALDEPTH: return m_Options.m_WaterRealDepth; case OPT_WATERREFLECTION: return m_Options.m_WaterReflection; case OPT_WATERREFRACTION: return m_Options.m_WaterRefraction; case OPT_SHADOWSONWATER: return m_Options.m_WaterShadows; case OPT_SHADOWPCF: return m_Options.m_ShadowPCF; case OPT_PARTICLES: return m_Options.m_Particles; case OPT_PREFERGLSL: return m_Options.m_PreferGLSL; case OPT_SILHOUETTES: return m_Options.m_Silhouettes; case OPT_SHOWSKY: return m_Options.m_ShowSky; case OPT_SMOOTHLOS: return m_Options.m_SmoothLOS; case OPT_POSTPROC: return m_Options.m_Postproc; case OPT_DISPLAYFRUSTUM: return m_Options.m_DisplayFrustum; default: debug_warn(L"CRenderer::GetOptionBool: unknown option"); break; } return false; } ////////////////////////////////////////////////////////////////////////////////////////// // SetRenderPath: Select the preferred render path. // This may only be called before Open(), because the layout of vertex arrays and other // data may depend on the chosen render path. void CRenderer::SetRenderPath(RenderPath rp) { if (!m->IsOpen) { // Delay until Open() is called. m_Options.m_RenderPath = rp; return; } // Renderer has been opened, so validate the selected renderpath if (rp == RP_DEFAULT) { if (m_Caps.m_ARBProgram || (m_Caps.m_VertexShader && m_Caps.m_FragmentShader && m_Options.m_PreferGLSL)) rp = RP_SHADER; else rp = RP_FIXED; } if (rp == RP_SHADER) { if (!(m_Caps.m_ARBProgram || (m_Caps.m_VertexShader && m_Caps.m_FragmentShader && m_Options.m_PreferGLSL))) { LOGWARNING("Falling back to fixed function\n"); rp = RP_FIXED; } } m_Options.m_RenderPath = rp; MakeShadersDirty(); RecomputeSystemShaderDefines(); // We might need to regenerate some render data after changing path if (g_Game) g_Game->GetWorld()->GetTerrain()->MakeDirty(RENDERDATA_UPDATE_COLOR); } CStr CRenderer::GetRenderPathName(RenderPath rp) { switch(rp) { case RP_DEFAULT: return "default"; case RP_FIXED: return "fixed"; case RP_SHADER: return "shader"; default: return "(invalid)"; } } CRenderer::RenderPath CRenderer::GetRenderPathByName(const CStr& name) { if (name == "fixed") return RP_FIXED; if (name == "shader") return RP_SHADER; if (name == "default") return RP_DEFAULT; LOGWARNING("Unknown render path name '%s', assuming 'default'", name.c_str()); return RP_DEFAULT; } ////////////////////////////////////////////////////////////////////////////////////////// // BeginFrame: signal frame start void CRenderer::BeginFrame() { PROFILE("begin frame"); // zero out all the per-frame stats m_Stats.Reset(); // choose model renderers for this frame if (m->ShadersDirty) ReloadShaders(); m->Model.ModShader->SetShadowMap(&m->shadow); m->Model.ModShader->SetLightEnv(m_LightEnv); } ////////////////////////////////////////////////////////////////////////////////////////// void CRenderer::SetSimulation(CSimulation2* simulation) { // set current simulation context for terrain renderer m->terrainRenderer.SetSimulation(simulation); } // SetClearColor: set color used to clear screen in BeginFrame() void CRenderer::SetClearColor(SColor4ub color) { m_ClearColor[0] = float(color.R) / 255.0f; m_ClearColor[1] = float(color.G) / 255.0f; m_ClearColor[2] = float(color.B) / 255.0f; m_ClearColor[3] = float(color.A) / 255.0f; } void CRenderer::RenderShadowMap(const CShaderDefines& context) { PROFILE3_GPU("shadow map"); m->shadow.BeginRender(); { PROFILE("render patches"); glCullFace(GL_FRONT); glEnable(GL_CULL_FACE); m->terrainRenderer.RenderPatches(CULL_SHADOWS); glCullFace(GL_BACK); } CShaderDefines contextCast = context; contextCast.Add(str_MODE_SHADOWCAST, str_1); { PROFILE("render models"); m->CallModelRenderers(contextCast, CULL_SHADOWS, MODELFLAG_CASTSHADOWS); } { PROFILE("render transparent models"); // disable face-culling for two-sided models glDisable(GL_CULL_FACE); m->CallTranspModelRenderers(contextCast, CULL_SHADOWS, MODELFLAG_CASTSHADOWS); glEnable(GL_CULL_FACE); } m->shadow.EndRender(); m->SetOpenGLCamera(m_ViewCamera); } void CRenderer::RenderPatches(const CShaderDefines& context, int cullGroup) { PROFILE3_GPU("patches"); #if CONFIG2_GLES #warning TODO: implement wireface/edged rendering mode GLES #else // switch on wireframe if we need it if (m_TerrainRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } #endif // render all the patches, including blend pass if (GetRenderPath() == RP_SHADER) m->terrainRenderer.RenderTerrainShader(context, cullGroup, (m_Caps.m_Shadows && m_Options.m_Shadows) ? &m->shadow : 0); else m->terrainRenderer.RenderTerrain(cullGroup); #if !CONFIG2_GLES if (m_TerrainRenderMode == WIREFRAME) { // switch wireframe off again glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } else if (m_TerrainRenderMode == EDGED_FACES) { // edged faces: need to make a second pass over the data: // first switch on wireframe glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); // setup some renderstate .. pglActiveTextureARB(GL_TEXTURE0); glDisable(GL_TEXTURE_2D); glColor3f(0.5f, 0.5f, 1.0f); glLineWidth(2.0f); // render tiles edges m->terrainRenderer.RenderPatches(cullGroup); // set color for outline glColor3f(0, 0, 1); glLineWidth(4.0f); // render outline of each patch m->terrainRenderer.RenderOutlines(cullGroup); // .. and restore the renderstates glLineWidth(1.0f); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } #endif } void CRenderer::RenderModels(const CShaderDefines& context, int cullGroup) { PROFILE3_GPU("models"); int flags = 0; #if !CONFIG2_GLES if (m_ModelRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } #endif m->CallModelRenderers(context, cullGroup, flags); #if !CONFIG2_GLES if (m_ModelRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } else if (m_ModelRenderMode == EDGED_FACES) { CShaderDefines contextWireframe = context; contextWireframe.Add(str_MODE_WIREFRAME, str_1); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_TEXTURE_2D); m->CallModelRenderers(contextWireframe, cullGroup, flags); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } #endif } void CRenderer::RenderTransparentModels(const CShaderDefines& context, int cullGroup, ETransparentMode transparentMode, bool disableFaceCulling) { PROFILE3_GPU("transparent models"); int flags = 0; #if !CONFIG2_GLES // switch on wireframe if we need it if (m_ModelRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } #endif // disable face culling for two-sided models in sub-renders if (disableFaceCulling) glDisable(GL_CULL_FACE); CShaderDefines contextOpaque = context; contextOpaque.Add(str_ALPHABLEND_PASS_OPAQUE, str_1); CShaderDefines contextBlend = context; contextBlend.Add(str_ALPHABLEND_PASS_BLEND, str_1); if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_OPAQUE) m->CallTranspModelRenderers(contextOpaque, cullGroup, flags); if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_BLEND) m->CallTranspModelRenderers(contextBlend, cullGroup, flags); if (disableFaceCulling) glEnable(GL_CULL_FACE); #if !CONFIG2_GLES if (m_ModelRenderMode == WIREFRAME) { // switch wireframe off again glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } else if (m_ModelRenderMode == EDGED_FACES) { CShaderDefines contextWireframe = contextOpaque; contextWireframe.Add(str_MODE_WIREFRAME, str_1); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_TEXTURE_2D); m->CallTranspModelRenderers(contextWireframe, cullGroup, flags); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } #endif } /////////////////////////////////////////////////////////////////////////////////////////////////// // SetObliqueFrustumClipping: change the near plane to the given clip plane (in world space) // Based on code from Game Programming Gems 5, from http://www.terathon.com/code/oblique.html // - worldPlane is a clip plane in world space (worldPlane.Dot(v) >= 0 for any vector v passing the clipping test) void CRenderer::SetObliqueFrustumClipping(CCamera& camera, const CVector4D& worldPlane) const { // First, we'll convert the given clip plane to camera space, then we'll // Get the view matrix and normal matrix (top 3x3 part of view matrix) CMatrix3D normalMatrix = camera.m_Orientation.GetTranspose(); CVector4D camPlane = normalMatrix.Transform(worldPlane); CMatrix3D matrix = camera.GetProjection(); // Calculate the clip-space corner point opposite the clipping plane // as (sgn(camPlane.x), sgn(camPlane.y), 1, 1) and // transform it into camera space by multiplying it // by the inverse of the projection matrix CVector4D q; q.X = (sgn(camPlane.X) - matrix[8]/matrix[11]) / matrix[0]; q.Y = (sgn(camPlane.Y) - matrix[9]/matrix[11]) / matrix[5]; q.Z = 1.0f/matrix[11]; q.W = (1.0f - matrix[10]/matrix[11]) / matrix[14]; // Calculate the scaled plane vector CVector4D c = camPlane * (2.0f * matrix[11] / camPlane.Dot(q)); // Replace the third row of the projection matrix matrix[2] = c.X; matrix[6] = c.Y; matrix[10] = c.Z - matrix[11]; matrix[14] = c.W; // Load it back into the camera camera.SetProjection(matrix); } void CRenderer::ComputeReflectionCamera(CCamera& camera, const CBoundingBoxAligned& scissor) const { WaterManager& wm = m->waterManager; float fov = m_ViewCamera.GetFOV(); // Expand fov slightly since ripples can reflect parts of the scene that // are slightly outside the normal camera view, and we want to avoid any // noticeable edge-filtering artifacts fov *= 1.05f; camera = m_ViewCamera; // Temporarily change the camera to one that is reflected. // Also, for texturing purposes, make it render to a view port the size of the // water texture, stretch the image according to our aspect ratio so it covers // the whole screen despite being rendered into a square, and cover slightly more // of the view so we can see wavy reflections of slightly off-screen objects. camera.m_Orientation.Scale(1, -1, 1); camera.m_Orientation.Translate(0, 2*wm.m_WaterHeight, 0); camera.UpdateFrustum(scissor); camera.ClipFrustum(CVector4D(0, 1, 0, -wm.m_WaterHeight)); SViewPort vp; - vp.m_Height = wm.m_ReflectionTextureSize; - vp.m_Width = wm.m_ReflectionTextureSize; + vp.m_Height = wm.m_RefTextureSize; + vp.m_Width = wm.m_RefTextureSize; vp.m_X = 0; vp.m_Y = 0; camera.SetViewPort(vp); camera.SetProjection(m_ViewCamera.GetNearPlane(), m_ViewCamera.GetFarPlane(), fov); CMatrix3D scaleMat; scaleMat.SetScaling(m_Height/float(std::max(1, m_Width)), 1.0f, 1.0f); camera.m_ProjMat = scaleMat * camera.m_ProjMat; CVector4D camPlane(0, 1, 0, -wm.m_WaterHeight + 0.5f); SetObliqueFrustumClipping(camera, camPlane); } void CRenderer::ComputeRefractionCamera(CCamera& camera, const CBoundingBoxAligned& scissor) const { WaterManager& wm = m->waterManager; float fov = m_ViewCamera.GetFOV(); // Expand fov slightly since ripples can reflect parts of the scene that // are slightly outside the normal camera view, and we want to avoid any // noticeable edge-filtering artifacts fov *= 1.05f; camera = m_ViewCamera; // Temporarily change the camera to make it render to a view port the size of the // water texture, stretch the image according to our aspect ratio so it covers // the whole screen despite being rendered into a square, and cover slightly more // of the view so we can see wavy refractions of slightly off-screen objects. camera.UpdateFrustum(scissor); camera.ClipFrustum(CVector4D(0, -1, 0, wm.m_WaterHeight + 0.5f)); // add some to avoid artifacts near steep shores. SViewPort vp; - vp.m_Height = wm.m_RefractionTextureSize; - vp.m_Width = wm.m_RefractionTextureSize; + vp.m_Height = wm.m_RefTextureSize; + vp.m_Width = wm.m_RefTextureSize; vp.m_X = 0; vp.m_Y = 0; camera.SetViewPort(vp); camera.SetProjection(m_ViewCamera.GetNearPlane(), m_ViewCamera.GetFarPlane(), fov); CMatrix3D scaleMat; scaleMat.SetScaling(m_Height/float(std::max(1, m_Width)), 1.0f, 1.0f); camera.m_ProjMat = scaleMat * camera.m_ProjMat; } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderReflections: render the water reflections to the reflection texture void CRenderer::RenderReflections(const CShaderDefines& context, const CBoundingBoxAligned& scissor) { PROFILE3_GPU("water reflections"); // Save the post-processing framebuffer. GLint fbo; glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &fbo); WaterManager& wm = m->waterManager; // Remember old camera CCamera normalCamera = m_ViewCamera; ComputeReflectionCamera(m_ViewCamera, scissor); m->SetOpenGLCamera(m_ViewCamera); // Save the model-view-projection matrix so the shaders can use it for projective texturing wm.m_ReflectionMatrix = m_ViewCamera.GetViewProjection(); - float vpHeight = wm.m_ReflectionTextureSize; - float vpWidth = wm.m_ReflectionTextureSize; + float vpHeight = wm.m_RefTextureSize; + float vpWidth = wm.m_RefTextureSize; SScreenRect screenScissor; screenScissor.x1 = (GLint)floor((scissor[0].X*0.5f+0.5f)*vpWidth); screenScissor.y1 = (GLint)floor((scissor[0].Y*0.5f+0.5f)*vpHeight); screenScissor.x2 = (GLint)ceil((scissor[1].X*0.5f+0.5f)*vpWidth); screenScissor.y2 = (GLint)ceil((scissor[1].Y*0.5f+0.5f)*vpHeight); glEnable(GL_SCISSOR_TEST); glScissor(screenScissor.x1, screenScissor.y1, screenScissor.x2 - screenScissor.x1, screenScissor.y2 - screenScissor.y1); // try binding the framebuffer pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, wm.m_ReflectionFbo); glClearColor(0.5f,0.5f,1.0f,0.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glFrontFace(GL_CW); if (!m_Options.m_WaterReflection) { m->skyManager.RenderSky(); ogl_WarnIfError(); } else { // Render terrain and models RenderPatches(context, CULL_REFLECTIONS); ogl_WarnIfError(); RenderModels(context, CULL_REFLECTIONS); ogl_WarnIfError(); RenderTransparentModels(context, CULL_REFLECTIONS, TRANSPARENT, true); ogl_WarnIfError(); } glFrontFace(GL_CCW); // Particles are always oriented to face the camera in the vertex shader, // so they don't need the inverted glFrontFace if (m_Options.m_Particles) { RenderParticles(CULL_REFLECTIONS); ogl_WarnIfError(); } glDisable(GL_SCISSOR_TEST); // Reset old camera m_ViewCamera = normalCamera; m->SetOpenGLCamera(m_ViewCamera); // rebind post-processing frambuffer. pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); return; } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderRefractions: render the water refractions to the refraction texture void CRenderer::RenderRefractions(const CShaderDefines& context, const CBoundingBoxAligned &scissor) { PROFILE3_GPU("water refractions"); // Save the post-processing framebuffer. GLint fbo; glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &fbo); WaterManager& wm = m->waterManager; // Remember old camera CCamera normalCamera = m_ViewCamera; ComputeRefractionCamera(m_ViewCamera, scissor); CVector4D camPlane(0, -1, 0, wm.m_WaterHeight + 2.0f); SetObliqueFrustumClipping(m_ViewCamera, camPlane); m->SetOpenGLCamera(m_ViewCamera); // Save the model-view-projection matrix so the shaders can use it for projective texturing wm.m_RefractionMatrix = m_ViewCamera.GetViewProjection(); - float vpHeight = wm.m_RefractionTextureSize; - float vpWidth = wm.m_RefractionTextureSize; + float vpHeight = wm.m_RefTextureSize; + float vpWidth = wm.m_RefTextureSize; SScreenRect screenScissor; screenScissor.x1 = (GLint)floor((scissor[0].X*0.5f+0.5f)*vpWidth); screenScissor.y1 = (GLint)floor((scissor[0].Y*0.5f+0.5f)*vpHeight); screenScissor.x2 = (GLint)ceil((scissor[1].X*0.5f+0.5f)*vpWidth); screenScissor.y2 = (GLint)ceil((scissor[1].Y*0.5f+0.5f)*vpHeight); glEnable(GL_SCISSOR_TEST); glScissor(screenScissor.x1, screenScissor.y1, screenScissor.x2 - screenScissor.x1, screenScissor.y2 - screenScissor.y1); // try binding the framebuffer pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, wm.m_RefractionFbo); glClearColor(1.0f,0.0f,0.0f,0.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Render terrain and models RenderPatches(context, CULL_REFRACTIONS); ogl_WarnIfError(); RenderModels(context, CULL_REFRACTIONS); ogl_WarnIfError(); RenderTransparentModels(context, CULL_REFRACTIONS, TRANSPARENT_OPAQUE, false); ogl_WarnIfError(); glDisable(GL_SCISSOR_TEST); // Reset old camera m_ViewCamera = normalCamera; m->SetOpenGLCamera(m_ViewCamera); // rebind post-processing frambuffer. pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); return; } void CRenderer::RenderSilhouettes(const CShaderDefines& context) { PROFILE3_GPU("silhouettes"); CShaderDefines contextOccluder = context; contextOccluder.Add(str_MODE_SILHOUETTEOCCLUDER, str_1); CShaderDefines contextDisplay = context; contextDisplay.Add(str_MODE_SILHOUETTEDISPLAY, str_1); // Render silhouettes of units hidden behind terrain or occluders. // To avoid breaking the standard rendering of alpha-blended objects, this // has to be done in a separate pass. // First we render all occluders into depth, then render all units with // inverted depth test so any behind an occluder will get drawn in a constant // color. float silhouetteAlpha = 0.75f; // Silhouette blending requires an almost-universally-supported extension; // fall back to non-blended if unavailable if (!ogl_HaveExtension("GL_EXT_blend_color")) silhouetteAlpha = 1.f; glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glColorMask(0, 0, 0, 0); // Render occluders: { PROFILE("render patches"); // To prevent units displaying silhouettes when parts of their model // protrude into the ground, only occlude with the back faces of the // terrain (so silhouettes will still display when behind hills) glCullFace(GL_FRONT); m->terrainRenderer.RenderPatches(CULL_SILHOUETTE_OCCLUDER); glCullFace(GL_BACK); } { PROFILE("render model occluders"); m->CallModelRenderers(contextOccluder, CULL_SILHOUETTE_OCCLUDER, 0); } { PROFILE("render transparent occluders"); m->CallTranspModelRenderers(contextOccluder, CULL_SILHOUETTE_OCCLUDER, 0); } glDepthFunc(GL_GEQUAL); glColorMask(1, 1, 1, 1); // Render more efficiently if alpha == 1 if (silhouetteAlpha == 1.f) { // Ideally we'd render objects back-to-front so nearer silhouettes would // appear on top, but sorting has non-zero cost. So we'll keep the depth // write enabled, to do the opposite - far objects will consistently appear // on top. glDepthMask(0); } else { // Since we can't sort, we'll use the stencil buffer to ensure we only draw // a pixel once (using the color of whatever model happens to be drawn first). glEnable(GL_BLEND); glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); pglBlendColorEXT(0, 0, 0, silhouetteAlpha); glEnable(GL_STENCIL_TEST); glStencilFunc(GL_NOTEQUAL, 1, (GLuint)-1); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); } { PROFILE("render casters"); m->CallModelRenderers(contextDisplay, CULL_SILHOUETTE_CASTER, 0); // (This won't render transparent objects with SILHOUETTE_CASTER - will // we have any units that need that?) } // Restore state glDepthFunc(GL_LEQUAL); if (silhouetteAlpha == 1.f) { glDepthMask(1); } else { glDisable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); pglBlendColorEXT(0, 0, 0, 0); glDisable(GL_STENCIL_TEST); } } void CRenderer::RenderParticles(int cullGroup) { // Only supported in shader modes if (GetRenderPath() != RP_SHADER) return; PROFILE3_GPU("particles"); m->particleRenderer.RenderParticles(cullGroup); #if !CONFIG2_GLES if (m_ModelRenderMode == EDGED_FACES) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_TEXTURE_2D); glColor3f(0.0f, 0.5f, 0.0f); m->particleRenderer.RenderParticles(true); CShaderTechniquePtr shaderTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid); shaderTech->BeginPass(); CShaderProgramPtr shader = shaderTech->GetShader(); shader->Uniform(str_color, 0.0f, 1.0f, 0.0f, 1.0f); shader->Uniform(str_transform, m_ViewCamera.GetViewProjection()); m->particleRenderer.RenderBounds(cullGroup, shader); shaderTech->EndPass(); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } #endif } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderSubmissions: force rendering of any batched objects void CRenderer::RenderSubmissions(const CBoundingBoxAligned& waterScissor) { PROFILE3("render submissions"); GetScene().GetLOSTexture().InterpolateLOS(); if (m_Options.m_Postproc) { m->postprocManager.Initialize(); m->postprocManager.CaptureRenderOutput(); } CShaderDefines context = m->globalContext; int cullGroup = CULL_DEFAULT; ogl_WarnIfError(); // Set the camera m->SetOpenGLCamera(m_ViewCamera); // Prepare model renderers { PROFILE3("prepare models"); m->Model.NormalSkinned->PrepareModels(); m->Model.TranspSkinned->PrepareModels(); if (m->Model.NormalUnskinned != m->Model.NormalSkinned) m->Model.NormalUnskinned->PrepareModels(); if (m->Model.TranspUnskinned != m->Model.TranspSkinned) m->Model.TranspUnskinned->PrepareModels(); } m->terrainRenderer.PrepareForRendering(); m->overlayRenderer.PrepareForRendering(); m->particleRenderer.PrepareForRendering(context); if (m_Caps.m_Shadows && m_Options.m_Shadows && GetRenderPath() == RP_SHADER) { RenderShadowMap(context); } { PROFILE3_GPU("clear buffers"); glClearColor(m_ClearColor[0], m_ClearColor[1], m_ClearColor[2], m_ClearColor[3]); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } ogl_WarnIfError(); if (m_WaterManager->m_RenderWater) { if (waterScissor.GetVolume() > 0 && m_WaterManager->WillRenderFancyWater()) { PROFILE3_GPU("water scissor"); RenderReflections(context, waterScissor); if (m_Options.m_WaterRefraction) RenderRefractions(context, waterScissor); } } if (m_Options.m_ShowSky) { m->skyManager.RenderSky(); } // render submitted patches and models RenderPatches(context, cullGroup); ogl_WarnIfError(); // render debug-related terrain overlays ITerrainOverlay::RenderOverlaysBeforeWater(); ogl_WarnIfError(); // render other debug-related overlays before water (so they can be seen when underwater) m->overlayRenderer.RenderOverlaysBeforeWater(); ogl_WarnIfError(); RenderModels(context, cullGroup); ogl_WarnIfError(); // render water if (m_WaterManager->m_RenderWater && g_Game && waterScissor.GetVolume() > 0) { // render transparent stuff, but only the solid parts that can occlude block water RenderTransparentModels(context, cullGroup, TRANSPARENT_OPAQUE, false); ogl_WarnIfError(); m->terrainRenderer.RenderWater(context, cullGroup, &m->shadow); ogl_WarnIfError(); // render transparent stuff again, but only the blended parts that overlap water RenderTransparentModels(context, cullGroup, TRANSPARENT_BLEND, false); ogl_WarnIfError(); } else { // render transparent stuff, so it can overlap models/terrain RenderTransparentModels(context, cullGroup, TRANSPARENT, false); ogl_WarnIfError(); } // render debug-related terrain overlays ITerrainOverlay::RenderOverlaysAfterWater(cullGroup); ogl_WarnIfError(); // render some other overlays after water (so they can be displayed on top of water) m->overlayRenderer.RenderOverlaysAfterWater(); ogl_WarnIfError(); // particles are transparent so render after water if (m_Options.m_Particles) { RenderParticles(cullGroup); ogl_WarnIfError(); } if (m_Options.m_Postproc) { m->postprocManager.ApplyPostproc(); m->postprocManager.ReleaseRenderOutput(); } if (m_Options.m_Silhouettes) { RenderSilhouettes(context); } #if !CONFIG2_GLES // Clean up texture blend mode so particles and other things render OK // (really this should be cleaned up by whoever set it) glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); #endif // render debug lines if (m_Options.m_DisplayFrustum) { DisplayFrustum(); m->shadow.RenderDebugBounds(); m->shadow.RenderDebugTexture(); ogl_WarnIfError(); } m->silhouetteRenderer.RenderDebugOverlays(m_ViewCamera); // render overlays that should appear on top of all other objects m->overlayRenderer.RenderForegroundOverlays(m_ViewCamera); ogl_WarnIfError(); } /////////////////////////////////////////////////////////////////////////////////////////////////// // EndFrame: signal frame end void CRenderer::EndFrame() { PROFILE3("end frame"); // empty lists m->terrainRenderer.EndFrame(); m->overlayRenderer.EndFrame(); m->particleRenderer.EndFrame(); m->silhouetteRenderer.EndFrame(); // Finish model renderers m->Model.NormalSkinned->EndFrame(); m->Model.TranspSkinned->EndFrame(); if (m->Model.NormalUnskinned != m->Model.NormalSkinned) m->Model.NormalUnskinned->EndFrame(); if (m->Model.TranspUnskinned != m->Model.TranspSkinned) m->Model.TranspUnskinned->EndFrame(); ogl_tex_bind(0, 0); { PROFILE3("error check"); int err = glGetError(); if (err) { ONCE(LOGERROR("CRenderer::EndFrame: GL errors %i occurred", err)); } } } /////////////////////////////////////////////////////////////////////////////////////////////////// // DisplayFrustum: debug displays // - white: cull camera frustum // - red: bounds of shadow casting objects void CRenderer::DisplayFrustum() { #if CONFIG2_GLES #warning TODO: implement CRenderer::DisplayFrustum for GLES #else glDepthMask(0); glDisable(GL_CULL_FACE); glDisable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor4ub(255,255,255,64); m_CullCamera.Render(2); glDisable(GL_BLEND); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glColor3ub(255,255,255); m_CullCamera.Render(2); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glEnable(GL_CULL_FACE); glDepthMask(1); #endif } /////////////////////////////////////////////////////////////////////////////////////////////////// // Text overlay rendering void CRenderer::RenderTextOverlays() { PROFILE3_GPU("text overlays"); if (m_DisplayTerrainPriorities) m->terrainRenderer.RenderPriorities(CULL_DEFAULT); ogl_WarnIfError(); } /////////////////////////////////////////////////////////////////////////////////////////////////// // SetSceneCamera: setup projection and transform of camera and adjust viewport to current view // The camera always represents the actual camera used to render a scene, not any virtual camera // used for shadow rendering or reflections. void CRenderer::SetSceneCamera(const CCamera& viewCamera, const CCamera& cullCamera) { m_ViewCamera = viewCamera; m_CullCamera = cullCamera; if (m_Caps.m_Shadows && m_Options.m_Shadows && GetRenderPath() == RP_SHADER) m->shadow.SetupFrame(m_CullCamera, m_LightEnv->GetSunDir()); } void CRenderer::SetViewport(const SViewPort &vp) { m_Viewport = vp; glViewport((GLint)vp.m_X,(GLint)vp.m_Y,(GLsizei)vp.m_Width,(GLsizei)vp.m_Height); } SViewPort CRenderer::GetViewport() { return m_Viewport; } void CRenderer::Submit(CPatch* patch) { if (m_CurrentCullGroup == CULL_DEFAULT) { m->shadow.AddShadowReceiverBound(patch->GetWorldBounds()); m->silhouetteRenderer.AddOccluder(patch); } if (m_CurrentCullGroup == CULL_SHADOWS) { m->shadow.AddShadowCasterBound(patch->GetWorldBounds()); } m->terrainRenderer.Submit(m_CurrentCullGroup, patch); } void CRenderer::Submit(SOverlayLine* overlay) { // Overlays are only needed in the default cull group for now, // so just ignore submissions to any other group if (m_CurrentCullGroup == CULL_DEFAULT) m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlayTexturedLine* overlay) { if (m_CurrentCullGroup == CULL_DEFAULT) m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlaySprite* overlay) { if (m_CurrentCullGroup == CULL_DEFAULT) m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlayQuad* overlay) { if (m_CurrentCullGroup == CULL_DEFAULT) m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlaySphere* overlay) { if (m_CurrentCullGroup == CULL_DEFAULT) m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(CModelDecal* decal) { // Decals can't cast shadows since they're flat on the terrain. // They can receive shadows, but the terrain under them will have // already been passed to AddShadowCasterBound, so don't bother // doing it again here. m->terrainRenderer.Submit(m_CurrentCullGroup, decal); } void CRenderer::Submit(CParticleEmitter* emitter) { m->particleRenderer.Submit(m_CurrentCullGroup, emitter); } void CRenderer::SubmitNonRecursive(CModel* model) { if (m_CurrentCullGroup == CULL_DEFAULT) { m->shadow.AddShadowReceiverBound(model->GetWorldBounds()); if (model->GetFlags() & MODELFLAG_SILHOUETTE_OCCLUDER) m->silhouetteRenderer.AddOccluder(model); if (model->GetFlags() & MODELFLAG_SILHOUETTE_DISPLAY) m->silhouetteRenderer.AddCaster(model); } if (m_CurrentCullGroup == CULL_SHADOWS) { if (!(model->GetFlags() & MODELFLAG_CASTSHADOWS)) return; m->shadow.AddShadowCasterBound(model->GetWorldBounds()); } bool requiresSkinning = (model->GetModelDef()->GetNumBones() != 0); if (model->GetMaterial().UsesAlphaBlending()) { if (requiresSkinning) m->Model.TranspSkinned->Submit(m_CurrentCullGroup, model); else m->Model.TranspUnskinned->Submit(m_CurrentCullGroup, model); } else { if (requiresSkinning) m->Model.NormalSkinned->Submit(m_CurrentCullGroup, model); else m->Model.NormalUnskinned->Submit(m_CurrentCullGroup, model); } } /////////////////////////////////////////////////////////// // Render the given scene void CRenderer::RenderScene(Scene& scene) { m_CurrentScene = &scene; CFrustum frustum = m_CullCamera.GetFrustum(); m_CurrentCullGroup = CULL_DEFAULT; scene.EnumerateObjects(frustum, this); m->particleManager.RenderSubmit(*this, frustum); if (m_Options.m_Silhouettes) { m->silhouetteRenderer.ComputeSubmissions(m_ViewCamera); m_CurrentCullGroup = CULL_DEFAULT; m->silhouetteRenderer.RenderSubmitOverlays(*this); m_CurrentCullGroup = CULL_SILHOUETTE_OCCLUDER; m->silhouetteRenderer.RenderSubmitOccluders(*this); m_CurrentCullGroup = CULL_SILHOUETTE_CASTER; m->silhouetteRenderer.RenderSubmitCasters(*this); } if (m_Caps.m_Shadows && m_Options.m_Shadows && GetRenderPath() == RP_SHADER) { m_CurrentCullGroup = CULL_SHADOWS; CFrustum shadowFrustum = m->shadow.GetShadowCasterCullFrustum(); scene.EnumerateObjects(shadowFrustum, this); } CBoundingBoxAligned waterScissor; if (m_WaterManager->m_RenderWater) { waterScissor = m->terrainRenderer.ScissorWater(CULL_DEFAULT, m_ViewCamera.GetViewProjection()); if (waterScissor.GetVolume() > 0 && m_WaterManager->WillRenderFancyWater()) { if (m_Options.m_WaterReflection) { m_CurrentCullGroup = CULL_REFLECTIONS; CCamera reflectionCamera; ComputeReflectionCamera(reflectionCamera, waterScissor); scene.EnumerateObjects(reflectionCamera.GetFrustum(), this); } if (m_Options.m_WaterRefraction) { m_CurrentCullGroup = CULL_REFRACTIONS; CCamera refractionCamera; ComputeRefractionCamera(refractionCamera, waterScissor); scene.EnumerateObjects(refractionCamera.GetFrustum(), this); } } // Render the waves to the Fancy effects texture m_WaterManager->RenderWaves(frustum); } m_CurrentCullGroup = -1; ogl_WarnIfError(); RenderSubmissions(waterScissor); m_CurrentScene = NULL; } Scene& CRenderer::GetScene() { ENSURE(m_CurrentScene); return *m_CurrentScene; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // BindTexture: bind a GL texture object to current active unit void CRenderer::BindTexture(int unit, GLuint tex) { pglActiveTextureARB(GL_TEXTURE0+unit); glBindTexture(GL_TEXTURE_2D, tex); #if !CONFIG2_GLES if (tex) { glEnable(GL_TEXTURE_2D); } else { glDisable(GL_TEXTURE_2D); } #endif } /////////////////////////////////////////////////////////////////////////////////////////////////// // LoadAlphaMaps: load the 14 default alpha maps, pack them into one composite texture and // calculate the coordinate of each alphamap within this packed texture // NB: A variant of this function is duplicated in TerrainTextureEntry.cpp, for use with the Shader // renderpath. This copy is kept to load the 'standard' maps for the fixed pipeline and should // be removed if/when the fixed pipeline goes. int CRenderer::LoadAlphaMaps() { const wchar_t* const key = L"(alpha map composite)"; Handle ht = ogl_tex_find(key); // alpha map texture had already been created and is still in memory: // reuse it, do not load again. if(ht > 0) { m_hCompositeAlphaMap = ht; return 0; } // // load all textures and store Handle in array // Handle textures[NumAlphaMaps] = {0}; VfsPath path(L"art/textures/terrain/alphamaps/standard"); const wchar_t* fnames[NumAlphaMaps] = { L"blendcircle.png", L"blendlshape.png", L"blendedge.png", L"blendedgecorner.png", L"blendedgetwocorners.png", L"blendfourcorners.png", L"blendtwooppositecorners.png", L"blendlshapecorner.png", L"blendtwocorners.png", L"blendcorner.png", L"blendtwoedges.png", L"blendthreecorners.png", L"blendushape.png", L"blendbad.png" }; size_t base = 0; // texture width/height (see below) // for convenience, we require all alpha maps to be of the same BPP // (avoids another ogl_tex_get_size call, and doesn't hurt) size_t bpp = 0; for(size_t i=0;i data; AllocateAligned(data, total_w*total_h, maxSectorSize); // for each tile on row for (size_t i = 0; i < NumAlphaMaps; i++) { // get src of copy u8* src = 0; (void)ogl_tex_get_data(textures[i], &src); size_t srcstep = bpp/8; // get destination of copy u8* dst = data.get() + (i*tile_w); // for each row of image for (size_t j = 0; j < base; j++) { // duplicate first pixel *dst++ = *src; *dst++ = *src; // copy a row for (size_t k = 0; k < base; k++) { *dst++ = *src; src += srcstep; } // duplicate last pixel *dst++ = *(src-srcstep); *dst++ = *(src-srcstep); // advance write pointer for next row dst += total_w-tile_w; } m_AlphaMapCoords[i].u0 = float(i*tile_w+2) / float(total_w); m_AlphaMapCoords[i].u1 = float((i+1)*tile_w-2) / float(total_w); m_AlphaMapCoords[i].v0 = 0.0f; m_AlphaMapCoords[i].v1 = 1.0f; } for (size_t i = 0; i < NumAlphaMaps; i++) (void)ogl_tex_free(textures[i]); // upload the composite texture Tex t; (void)t.wrap(total_w, total_h, 8, TEX_GREY, data, 0); /*VfsPath filename("blendtex.png"); DynArray da; RETURN_STATUS_IF_ERR(tex_encode(&t, filename.Extension(), &da)); // write to disk //Status ret = INFO::OK; { shared_ptr file = DummySharedPtr(da.base); const ssize_t bytes_written = g_VFS->CreateFile(filename, file, da.pos); if(bytes_written > 0) ENSURE(bytes_written == (ssize_t)da.pos); //else // ret = (Status)bytes_written; } (void)da_free(&da);*/ m_hCompositeAlphaMap = ogl_tex_wrap(&t, g_VFS, key); (void)ogl_tex_set_filter(m_hCompositeAlphaMap, GL_LINEAR); (void)ogl_tex_set_wrap (m_hCompositeAlphaMap, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); int ret = ogl_tex_upload(m_hCompositeAlphaMap, GL_ALPHA, 0, 0); return ret; } /////////////////////////////////////////////////////////////////////////////////////////////////// // UnloadAlphaMaps: frees the resources allocates by LoadAlphaMaps void CRenderer::UnloadAlphaMaps() { ogl_tex_free(m_hCompositeAlphaMap); m_hCompositeAlphaMap = 0; } Status CRenderer::ReloadChangedFileCB(void* param, const VfsPath& path) { CRenderer* renderer = static_cast(param); // If an alpha map changed, and we already loaded them, then reload them if (boost::algorithm::starts_with(path.string(), L"art/textures/terrain/alphamaps/")) { if (renderer->m_hCompositeAlphaMap) { renderer->UnloadAlphaMaps(); renderer->LoadAlphaMaps(); } } return INFO::OK; } void CRenderer::MakeShadersDirty() { m->ShadersDirty = true; } CTextureManager& CRenderer::GetTextureManager() { return m->textureManager; } CShaderManager& CRenderer::GetShaderManager() { return m->shaderManager; } CParticleManager& CRenderer::GetParticleManager() { return m->particleManager; } TerrainRenderer& CRenderer::GetTerrainRenderer() { return m->terrainRenderer; } CTimeManager& CRenderer::GetTimeManager() { return m->timeManager; } CMaterialManager& CRenderer::GetMaterialManager() { return m->materialManager; } CPostprocManager& CRenderer::GetPostprocManager() { return m->postprocManager; } CFontManager& CRenderer::GetFontManager() { return m->fontManager; } Index: ps/trunk/source/renderer/WaterManager.cpp =================================================================== --- ps/trunk/source/renderer/WaterManager.cpp (revision 18442) +++ ps/trunk/source/renderer/WaterManager.cpp (revision 18443) @@ -1,1145 +1,1136 @@ /* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * Water settings (speed, height) and texture management */ #include "precompiled.h" #include "graphics/Terrain.h" #include "graphics/TextureManager.h" #include "graphics/ShaderManager.h" #include "graphics/ShaderProgram.h" #include "lib/bits.h" #include "lib/timer.h" #include "lib/tex/tex.h" #include "lib/res/graphics/ogl_tex.h" #include "maths/MathUtil.h" #include "maths/Vector2D.h" #include "ps/CLogger.h" #include "ps/Game.h" #include "ps/World.h" #include "renderer/WaterManager.h" #include "renderer/Renderer.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpWaterManager.h" #include "simulation2/components/ICmpRangeManager.h" /////////////////////////////////////////////////////////////////////////////////////////////// // WaterManager implementation struct CoastalPoint { CoastalPoint(int idx, CVector2D pos) : index(idx), position(pos) {}; int index; CVector2D position; }; struct SWavesVertex { // vertex position CVector3D m_BasePosition; CVector3D m_ApexPosition; CVector3D m_SplashPosition; CVector3D m_RetreatPosition; CVector2D m_PerpVect; u8 m_UV[3]; // pad to a power of two u8 m_Padding[5]; }; cassert(sizeof(SWavesVertex) == 64); struct WaveObject { CVertexBuffer::VBChunk* m_VBvertices; CBoundingBoxAligned m_AABB; size_t m_Width; float m_TimeDiff; }; /////////////////////////////////////////////////////////////////// // Construction/Destruction WaterManager::WaterManager() { // water m_RenderWater = false; // disabled until textures are successfully loaded m_WaterHeight = 5.0f; m_WaterCurrentTex = 0; m_ReflectionTexture = 0; m_RefractionTexture = 0; - m_ReflectionTextureSize = 0; - m_RefractionTextureSize = 0; + m_RefTextureSize = 0; m_ReflectionFbo = 0; m_RefractionFbo = 0; m_FancyEffectsFBO = 0; m_WaterTexTimer = 0.0; m_WindAngle = 0.0f; m_Waviness = 8.0f; m_WaterColor = CColor(0.3f, 0.35f, 0.7f, 1.0f); m_WaterTint = CColor(0.28f, 0.3f, 0.59f, 1.0f); m_Murkiness = 0.45f; m_RepeatPeriod = 16.0f; m_DistanceHeightmap = NULL; m_BlurredNormalMap = NULL; m_WindStrength = NULL; m_ShoreWaves_VBIndices = NULL; m_WaterUgly = false; m_WaterFancyEffects = false; m_WaterRealDepth = false; m_WaterRefraction = false; m_WaterReflection = false; m_WaterShadows = false; m_WaterType = L"ocean"; m_NeedsReloading = false; m_NeedInfoUpdate = true; m_depthTT = 0; m_FancyTextureNormal = 0; m_FancyTextureOther = 0; m_FancyTextureDepth = 0; m_ReflFboDepthTexture = 0; m_RefrFboDepthTexture = 0; m_MapSize = 0; m_updatei0 = 0; m_updatej0 = 0; m_updatei1 = 0; m_updatej1 = 0; } WaterManager::~WaterManager() { // Cleanup if the caller messed up UnloadWaterTextures(); for (WaveObject* const& obj : m_ShoreWaves) { if (obj->m_VBvertices) g_VBMan.Release(obj->m_VBvertices); delete obj; } if (m_ShoreWaves_VBIndices) g_VBMan.Release(m_ShoreWaves_VBIndices); delete[] m_DistanceHeightmap; delete[] m_BlurredNormalMap; delete[] m_WindStrength; if (!g_Renderer.GetCapabilities().m_PrettyWater) return; glDeleteTextures(1, &m_depthTT); glDeleteTextures(1, &m_FancyTextureNormal); glDeleteTextures(1, &m_FancyTextureOther); glDeleteTextures(1, &m_FancyTextureDepth); glDeleteTextures(1, &m_ReflFboDepthTexture); glDeleteTextures(1, &m_RefrFboDepthTexture); pglDeleteFramebuffersEXT(1, &m_FancyEffectsFBO); pglDeleteFramebuffersEXT(1, &m_RefractionFbo); pglDeleteFramebuffersEXT(1, &m_ReflectionFbo); } /////////////////////////////////////////////////////////////////// // Progressive load of water textures int WaterManager::LoadWaterTextures() { // TODO: this doesn't need to be progressive-loading any more // (since texture loading is async now) wchar_t pathname[PATH_MAX]; // Load diffuse grayscale images (for non-fancy water) for (size_t i = 0; i < ARRAY_SIZE(m_WaterTexture); ++i) { swprintf_s(pathname, ARRAY_SIZE(pathname), L"art/textures/animated/water/default/diffuse%02d.dds", (int)i+1); CTextureProperties textureProps(pathname); textureProps.SetWrap(GL_REPEAT); CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); texture->Prefetch(); m_WaterTexture[i] = texture; } if (!g_Renderer.GetCapabilities().m_PrettyWater) { // Enable rendering, now that we've succeeded this far m_RenderWater = true; return 0; } #if CONFIG2_GLES #warning Fix WaterManager::LoadWaterTextures on GLES #else // Load normalmaps (for fancy water) for (size_t i = 0; i < ARRAY_SIZE(m_NormalMap); ++i) { swprintf_s(pathname, ARRAY_SIZE(pathname), L"art/textures/animated/water/%ls/normal00%02d.png", m_WaterType.c_str(), (int)i+1); CTextureProperties textureProps(pathname); textureProps.SetWrap(GL_REPEAT); textureProps.SetMaxAnisotropy(4); CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); texture->Prefetch(); m_NormalMap[i] = texture; } // Load CoastalWaves { CTextureProperties textureProps(L"art/textures/terrain/types/water/coastalWave.png"); textureProps.SetWrap(GL_REPEAT); CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); texture->Prefetch(); m_WaveTex = texture; } // Load Foam { CTextureProperties textureProps(L"art/textures/terrain/types/water/foam.png"); textureProps.SetWrap(GL_REPEAT); CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); texture->Prefetch(); m_FoamTex = texture; } - m_ReflectionTextureSize = g_Renderer.GetHeight() * 0.66; // Higher settings give a better result - m_RefractionTextureSize = g_Renderer.GetHeight() * 0.33; // Lower settings actually sorta look better since it blurs. + // Use screen-sized textures for minimum artifacts. + m_RefTextureSize = g_Renderer.GetHeight(); - if (round_down_to_pow2(m_ReflectionTextureSize)/m_ReflectionTextureSize < 0.65) - m_ReflectionTextureSize = round_up_to_pow2(m_ReflectionTextureSize); - else - m_ReflectionTextureSize = round_down_to_pow2(m_ReflectionTextureSize); - - if (round_down_to_pow2(m_RefractionTextureSize)/m_RefractionTextureSize < 0.7) - m_RefractionTextureSize = round_up_to_pow2(m_RefractionTextureSize); - else - m_RefractionTextureSize = round_down_to_pow2(m_RefractionTextureSize); + m_RefTextureSize = round_up_to_pow2(m_RefTextureSize); // Create reflection texture glGenTextures(1, &m_ReflectionTexture); glBindTexture(GL_TEXTURE_2D, m_ReflectionTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT); - glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)m_ReflectionTextureSize, (GLsizei)m_ReflectionTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)m_RefTextureSize, (GLsizei)m_RefTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); // Create refraction texture glGenTextures(1, &m_RefractionTexture); glBindTexture(GL_TEXTURE_2D, m_RefractionTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT); - glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, (GLsizei)m_RefractionTextureSize, (GLsizei)m_RefractionTextureSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0); + glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, (GLsizei)m_RefTextureSize, (GLsizei)m_RefTextureSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0); // Create depth textures glGenTextures(1, &m_ReflFboDepthTexture); glBindTexture(GL_TEXTURE_2D, m_ReflFboDepthTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, (GLsizei)m_ReflectionTextureSize, (GLsizei)m_ReflectionTextureSize, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, NULL); + glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, (GLsizei)m_RefTextureSize, (GLsizei)m_RefTextureSize, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, NULL); glGenTextures(1, &m_RefrFboDepthTexture); glBindTexture(GL_TEXTURE_2D, m_RefrFboDepthTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, (GLsizei)m_RefractionTextureSize, (GLsizei)m_RefractionTextureSize, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, NULL); + glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, (GLsizei)m_RefTextureSize, (GLsizei)m_RefTextureSize, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, NULL); // Create the Fancy Effects texture glGenTextures(1, &m_FancyTextureNormal); glBindTexture(GL_TEXTURE_2D, m_FancyTextureNormal); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glGenTextures(1, &m_FancyTextureOther); glBindTexture(GL_TEXTURE_2D, m_FancyTextureOther); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glGenTextures(1, &m_FancyTextureDepth); glBindTexture(GL_TEXTURE_2D, m_FancyTextureDepth); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glBindTexture(GL_TEXTURE_2D, 0); Resize(); // Create the water framebuffers GLint currentFbo; glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, ¤tFbo); m_ReflectionFbo = 0; pglGenFramebuffersEXT(1, &m_ReflectionFbo); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_ReflectionFbo); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_ReflectionTexture, 0); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, m_ReflFboDepthTexture, 0); ogl_WarnIfError(); GLenum status = pglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { LOGWARNING("Reflection framebuffer object incomplete: 0x%04X", status); g_Renderer.m_Options.m_WaterReflection = false; } m_RefractionFbo = 0; pglGenFramebuffersEXT(1, &m_RefractionFbo); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_RefractionFbo); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_RefractionTexture, 0); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, m_RefrFboDepthTexture, 0); ogl_WarnIfError(); status = pglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { LOGWARNING("Refraction framebuffer object incomplete: 0x%04X", status); g_Renderer.m_Options.m_WaterRefraction = false; } pglGenFramebuffersEXT(1, &m_FancyEffectsFBO); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_FancyEffectsFBO); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_FancyTextureNormal, 0); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, m_FancyTextureOther, 0); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, m_FancyTextureDepth, 0); ogl_WarnIfError(); status = pglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { LOGWARNING("Fancy Effects framebuffer object incomplete: 0x%04X", status); g_Renderer.m_Options.m_WaterRefraction = false; } pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, currentFbo); // Enable rendering, now that we've succeeded this far m_RenderWater = true; #endif return 0; } /////////////////////////////////////////////////////////////////// // Resize: Updates the fancy water textures. void WaterManager::Resize() { glBindTexture(GL_TEXTURE_2D, m_FancyTextureNormal); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)g_Renderer.GetWidth(), (GLsizei)g_Renderer.GetHeight(), 0, GL_RGBA, GL_UNSIGNED_SHORT, NULL); glBindTexture(GL_TEXTURE_2D, m_FancyTextureOther); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)g_Renderer.GetWidth(), (GLsizei)g_Renderer.GetHeight(), 0, GL_RGBA, GL_UNSIGNED_SHORT, NULL); glBindTexture(GL_TEXTURE_2D, m_FancyTextureDepth); glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, (GLsizei)g_Renderer.GetWidth(), (GLsizei)g_Renderer.GetHeight(), 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, NULL); glBindTexture(GL_TEXTURE_2D, 0); } // This is for Atlas. TODO: this copies code from init, should reuse it. void WaterManager::ReloadWaterNormalTextures() { wchar_t pathname[PATH_MAX]; // Load normalmaps (for fancy water) for (size_t i = 0; i < ARRAY_SIZE(m_NormalMap); ++i) { swprintf_s(pathname, ARRAY_SIZE(pathname), L"art/textures/animated/water/%ls/normal00%02d.png", m_WaterType.c_str(), (int)i+1); CTextureProperties textureProps(pathname); textureProps.SetWrap(GL_REPEAT); textureProps.SetMaxAnisotropy(4); CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); texture->Prefetch(); m_NormalMap[i] = texture; } } /////////////////////////////////////////////////////////////////// // Unload water textures void WaterManager::UnloadWaterTextures() { for(size_t i = 0; i < ARRAY_SIZE(m_WaterTexture); i++) m_WaterTexture[i].reset(); if (!g_Renderer.GetCapabilities().m_PrettyWater) return; for(size_t i = 0; i < ARRAY_SIZE(m_NormalMap); i++) m_NormalMap[i].reset(); glDeleteTextures(1, &m_ReflectionTexture); glDeleteTextures(1, &m_RefractionTexture); pglDeleteFramebuffersEXT(1, &m_RefractionFbo); pglDeleteFramebuffersEXT(1, &m_ReflectionFbo); } /////////////////////////////////////////////////////////////////// // Calculate our binary heightmap from the terrain heightmap. void WaterManager::RecomputeDistanceHeightmap() { size_t SideSize = m_MapSize*2; if (m_DistanceHeightmap == NULL) m_DistanceHeightmap = new float[SideSize*SideSize]; CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); // Create a manhattan-distance heightmap. // This is currently upsampled by a factor of 2 to get more precision // This could be refined to only be done near the coast itself, but it's probably not necessary. for (size_t z = 0; z < SideSize; ++z) { float level = SideSize; for (size_t x = 0; x < SideSize; ++x) m_DistanceHeightmap[z*SideSize + x] = terrain->GetExactGroundLevel(x*2, z*2) >= m_WaterHeight ? level = 0.f : ++level; level = SideSize; for (size_t x = SideSize-1; x != (size_t)-1; --x) { if (terrain->GetExactGroundLevel(x*2, z*2) >= m_WaterHeight) level = 0.f; else { ++level; if (level < m_DistanceHeightmap[z*SideSize + x]) m_DistanceHeightmap[z*SideSize + x] = level; } } } for (size_t x = 0; x < SideSize; ++x) { float level = SideSize; for (size_t z = 0; z < SideSize; ++z) { if (terrain->GetExactGroundLevel(x*2, z*2) >= m_WaterHeight) level = 0.f; else if (level > m_DistanceHeightmap[z*SideSize + x]) level = m_DistanceHeightmap[z*SideSize + x]; else { ++level; if (level < m_DistanceHeightmap[z*SideSize + x]) m_DistanceHeightmap[z*SideSize + x] = level; } } level = SideSize; for (size_t z = SideSize-1; z != (size_t)-1; --z) { if (terrain->GetExactGroundLevel(x*2, z*2) >= m_WaterHeight) level = 0.f; else if (level > m_DistanceHeightmap[z*SideSize + x]) level = m_DistanceHeightmap[z*SideSize + x]; else { ++level; if (level < m_DistanceHeightmap[z*SideSize + x]) m_DistanceHeightmap[z*SideSize + x] = level; } } } } // This requires m_DistanceHeightmap to be defined properly. void WaterManager::CreateWaveMeshes() { size_t SideSize = m_MapSize*2; CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); for (WaveObject* const& obj : m_ShoreWaves) { if (obj->m_VBvertices) g_VBMan.Release(obj->m_VBvertices); delete obj; } m_ShoreWaves.clear(); if (m_ShoreWaves_VBIndices) { g_VBMan.Release(m_ShoreWaves_VBIndices); m_ShoreWaves_VBIndices = NULL; } if (m_Waviness < 5.0f && m_WaterType != L"ocean") return; // First step: get the points near the coast. std::set CoastalPointsSet; for (size_t z = 1; z < SideSize-1; ++z) for (size_t x = 1; x < SideSize-1; ++x) if (fabs(m_DistanceHeightmap[z*SideSize + x]-1.0f) < 0.2f) CoastalPointsSet.insert(z*SideSize + x); // Second step: create chains out of those coastal points. static const int around[8][2] = { { -1,-1 }, { -1,0 }, { -1,1 }, { 0,1 }, { 1,1 }, { 1,0 }, { 1,-1 }, { 0,-1 } }; std::vector > CoastalPointsChains; while (!CoastalPointsSet.empty()) { int index = *(CoastalPointsSet.begin()); int x = index % SideSize; int y = (index - x ) / SideSize; std::deque Chain; Chain.push_front(CoastalPoint(index,CVector2D(x*2,y*2))); // Erase us. CoastalPointsSet.erase(CoastalPointsSet.begin()); // We're our starter points. At most we can have 2 points close to us. // We'll pick the first one and look for its neighbors (he can only have one new) // Up until we either reach the end of the chain, or ourselves. // Then go down the other direction if there is any. int neighbours[2] = { -1, -1 }; int nbNeighb = 0; for (int i = 0; i < 8; ++i) { if (CoastalPointsSet.count(x + around[i][0] + (y + around[i][1])*SideSize)) { if (nbNeighb < 2) neighbours[nbNeighb] = x + around[i][0] + (y + around[i][1])*SideSize; ++nbNeighb; } } if (nbNeighb > 2) continue; for (int i = 0; i < 2; ++i) { if (neighbours[i] == -1) continue; // Move to our neighboring point int xx = neighbours[i] % SideSize; int yy = (neighbours[i] - xx ) / SideSize; int indexx = xx + yy*SideSize; int endedChain = false; if (i == 0) Chain.push_back(CoastalPoint(indexx,CVector2D(xx*2,yy*2))); else Chain.push_front(CoastalPoint(indexx,CVector2D(xx*2,yy*2))); // If there's a loop we'll be the "other" neighboring point already so check for that. // We'll readd at the end/front the other one to have full squares. if (CoastalPointsSet.count(indexx) == 0) break; CoastalPointsSet.erase(indexx); // Start checking from there. while(!endedChain) { bool found = false; nbNeighb = 0; for (int p = 0; p < 8; ++p) { if (CoastalPointsSet.count(xx+around[p][0] + (yy + around[p][1])*SideSize)) { if (nbNeighb >= 2) { CoastalPointsSet.erase(xx + yy*SideSize); continue; } ++nbNeighb; // We've found a new point around us. // Move there xx = xx + around[p][0]; yy = yy + around[p][1]; indexx = xx + yy*SideSize; if (i == 0) Chain.push_back(CoastalPoint(indexx,CVector2D(xx*2,yy*2))); else Chain.push_front(CoastalPoint(indexx,CVector2D(xx*2,yy*2))); CoastalPointsSet.erase(xx + yy*SideSize); found = true; break; } } if (!found) endedChain = true; } } if (Chain.size() > 10) CoastalPointsChains.push_back(Chain); } // (optional) third step: Smooth chains out. // This is also really dumb. for (size_t i = 0; i < CoastalPointsChains.size(); ++i) { // Bump 1 for smoother. for (int p = 0; p < 3; ++p) { for (size_t j = 1; j < CoastalPointsChains[i].size()-1; ++j) { CVector2D realPos = CoastalPointsChains[i][j-1].position + CoastalPointsChains[i][j+1].position; CoastalPointsChains[i][j].position = (CoastalPointsChains[i][j].position + realPos/2.0f)/2.0f; } } } // Fourth step: create waves themselves, using those chains. We basically create subchains. size_t waveSizes = 14; // maximal size in width. // Construct indices buffer (we can afford one for all of them) std::vector water_indices; for (size_t a = 0; a < waveSizes-1;++a) { for (size_t rect = 0; rect < 7; ++rect) { water_indices.push_back(a*9 + rect); water_indices.push_back(a*9 + 9 + rect); water_indices.push_back(a*9 + 1 + rect); water_indices.push_back(a*9 + 9 + rect); water_indices.push_back(a*9 + 10 + rect); water_indices.push_back(a*9 + 1 + rect); } } // Generic indexes, max-length m_ShoreWaves_VBIndices = g_VBMan.Allocate(sizeof(GLushort), water_indices.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER); m_ShoreWaves_VBIndices->m_Owner->UpdateChunkVertices(m_ShoreWaves_VBIndices, &water_indices[0]); float diff = (rand() % 50) / 5.0f; for (size_t i = 0; i < CoastalPointsChains.size(); ++i) { for (size_t j = 0; j < CoastalPointsChains[i].size()-waveSizes; ++j) { if (CoastalPointsChains[i].size()- 1 - j < waveSizes) break; size_t width = waveSizes; // First pass to get some parameters out. float outmost = 0.0f; // how far to move on the shore. float avgDepth = 0.0f; int sign = 1; CVector2D firstPerp(0,0), perp(0,0), lastPerp(0,0); for (size_t a = 0; a < waveSizes;++a) { lastPerp = perp; perp = CVector2D(0,0); int nb = 0; CVector2D pos = CoastalPointsChains[i][j+a].position; CVector2D posPlus; CVector2D posMinus; if (a > 0) { ++nb; posMinus = CoastalPointsChains[i][j+a-1].position; perp += pos-posMinus; } if (a < waveSizes-1) { ++nb; posPlus = CoastalPointsChains[i][j+a+1].position; perp += posPlus-pos; } perp /= nb; perp = CVector2D(-perp.Y,perp.X).Normalized(); if (a == 0) firstPerp = perp; if ( a > 1 && perp.Dot(lastPerp) < 0.90f && perp.Dot(firstPerp) < 0.70f) { width = a+1; break; } if (m_BlurredNormalMap[ (int)(pos.X/4) + (int)(pos.Y/4)*m_MapSize].Y < 0.9) { width = a-1; break; } if (terrain->GetExactGroundLevel(pos.X+perp.X*1.5f, pos.Y+perp.Y*1.5f) > m_WaterHeight) sign = -1; avgDepth += terrain->GetExactGroundLevel(pos.X+sign*perp.X*20.0f, pos.Y+sign*perp.Y*20.0f) - m_WaterHeight; float localOutmost = -2.0f; while (localOutmost < 0.0f) { float depth = terrain->GetExactGroundLevel(pos.X+sign*perp.X*localOutmost, pos.Y+sign*perp.Y*localOutmost) - m_WaterHeight; if (depth < 0.0f || depth > 0.6f) localOutmost += 0.2f; else break; } outmost += localOutmost; } if (width < 5) { j += 6; continue; } outmost /= width; if (outmost > -0.5f) { j += 3; continue; } outmost = -0.5f + outmost * m_Waviness/10.0f; avgDepth /= width; if (avgDepth > -1.3f) { j += 3; continue; } // we passed the checks, we can create a wave of size "width". WaveObject* shoreWave = new WaveObject; std::vector vertices; shoreWave->m_Width = width; shoreWave->m_TimeDiff = diff; diff += (rand() % 100) / 25.0f + 4.0f; for (size_t a = 0; a < width;++a) { CVector2D perp = CVector2D(0,0); int nb = 0; CVector2D pos = CoastalPointsChains[i][j+a].position; CVector2D posPlus; CVector2D posMinus; if (a > 0) { ++nb; posMinus = CoastalPointsChains[i][j+a-1].position; perp += pos-posMinus; } if (a < waveSizes-1) { ++nb; posPlus = CoastalPointsChains[i][j+a+1].position; perp += posPlus-pos; } perp /= nb; perp = CVector2D(-perp.Y,perp.X).Normalized(); SWavesVertex point[9]; float baseHeight = 0.04f; float halfWidth = (width-1.0f)/2.0f; float sideNess = sqrtf(clamp( (halfWidth - fabsf(a-halfWidth))/3.0f, 0.0f,1.0f)); point[0].m_UV[0] = a; point[0].m_UV[1] = 8; point[1].m_UV[0] = a; point[1].m_UV[1] = 7; point[2].m_UV[0] = a; point[2].m_UV[1] = 6; point[3].m_UV[0] = a; point[3].m_UV[1] = 5; point[4].m_UV[0] = a; point[4].m_UV[1] = 4; point[5].m_UV[0] = a; point[5].m_UV[1] = 3; point[6].m_UV[0] = a; point[6].m_UV[1] = 2; point[7].m_UV[0] = a; point[7].m_UV[1] = 1; point[8].m_UV[0] = a; point[8].m_UV[1] = 0; point[0].m_PerpVect = perp; point[1].m_PerpVect = perp; point[2].m_PerpVect = perp; point[3].m_PerpVect = perp; point[4].m_PerpVect = perp; point[5].m_PerpVect = perp; point[6].m_PerpVect = perp; point[7].m_PerpVect = perp; point[8].m_PerpVect = perp; static const float perpT1[9] = { 6.0f, 6.05f, 6.1f, 6.2f, 6.3f, 6.4f, 6.5f, 6.6f, 9.7f }; static const float perpT2[9] = { 2.0f, 2.1f, 2.2f, 2.3f, 2.4f, 3.0f, 3.3f, 3.6f, 9.5f }; static const float perpT3[9] = { 1.1f, 0.7f, -0.2f, 0.0f, 0.6f, 1.3f, 2.2f, 3.6f, 9.0f }; static const float perpT4[9] = { 2.0f, 2.1f, 1.2f, 1.5f, 1.7f, 1.9f, 2.7f, 3.8f, 9.0f }; static const float heightT1[9] = { 0.0f, 0.2f, 0.5f, 0.8f, 0.9f, 0.85f, 0.6f, 0.2f, 0.0 }; static const float heightT2[9] = { -0.8f, -0.4f, 0.0f, 0.1f, 0.1f, 0.03f, 0.0f, 0.0f, 0.0 }; static const float heightT3[9] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0 }; for (size_t t = 0; t < 9; ++t) { float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT1[t]+outmost), pos.Y+sign*perp.Y*(perpT1[t]+outmost)); point[t].m_BasePosition = CVector3D(pos.X+sign*perp.X*(perpT1[t]+outmost), baseHeight + heightT1[t]*sideNess + std::max(m_WaterHeight,terrHeight), pos.Y+sign*perp.Y*(perpT1[t]+outmost)); } for (size_t t = 0; t < 9; ++t) { float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT2[t]+outmost), pos.Y+sign*perp.Y*(perpT2[t]+outmost)); point[t].m_ApexPosition = CVector3D(pos.X+sign*perp.X*(perpT2[t]+outmost), baseHeight + heightT1[t]*sideNess + std::max(m_WaterHeight,terrHeight), pos.Y+sign*perp.Y*(perpT2[t]+outmost)); } for (size_t t = 0; t < 9; ++t) { float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT3[t]+outmost*sideNess), pos.Y+sign*perp.Y*(perpT3[t]+outmost*sideNess)); point[t].m_SplashPosition = CVector3D(pos.X+sign*perp.X*(perpT3[t]+outmost*sideNess), baseHeight + heightT2[t]*sideNess + std::max(m_WaterHeight,terrHeight), pos.Y+sign*perp.Y*(perpT3[t]+outmost*sideNess)); } for (size_t t = 0; t < 9; ++t) { float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT4[t]+outmost), pos.Y+sign*perp.Y*(perpT4[t]+outmost)); point[t].m_RetreatPosition = CVector3D(pos.X+sign*perp.X*(perpT4[t]+outmost), baseHeight + heightT3[t]*sideNess + std::max(m_WaterHeight,terrHeight), pos.Y+sign*perp.Y*(perpT4[t]+outmost)); } vertices.push_back(point[8]); vertices.push_back(point[7]); vertices.push_back(point[6]); vertices.push_back(point[5]); vertices.push_back(point[4]); vertices.push_back(point[3]); vertices.push_back(point[2]); vertices.push_back(point[1]); vertices.push_back(point[0]); shoreWave->m_AABB += point[8].m_SplashPosition; shoreWave->m_AABB += point[8].m_BasePosition; shoreWave->m_AABB += point[0].m_SplashPosition; shoreWave->m_AABB += point[0].m_BasePosition; shoreWave->m_AABB += point[4].m_ApexPosition; } if (sign == 1) { // Let's do some fancy reversing. std::vector reversed; for (int a = width-1; a >= 0; --a) { for (size_t t = 0; t < 9; ++t) reversed.push_back(vertices[a*9+t]); } vertices = reversed; } j += width/2-1; shoreWave->m_VBvertices = g_VBMan.Allocate(sizeof(SWavesVertex), vertices.size(), GL_STATIC_DRAW, GL_ARRAY_BUFFER); shoreWave->m_VBvertices->m_Owner->UpdateChunkVertices(shoreWave->m_VBvertices, &vertices[0]); m_ShoreWaves.push_back(shoreWave); } } } void WaterManager::RenderWaves(const CFrustum& frustrum) { #if CONFIG2_GLES #warning Fix WaterManager::RenderWaves on GLES #else if (g_Renderer.m_SkipSubmit || !m_WaterFancyEffects) return; pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_FancyEffectsFBO); GLuint attachments[2] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT }; pglDrawBuffers(2, attachments); glClearColor(0.0f,0.0f, 0.0f,0.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_ALWAYS); CShaderDefines none; CShaderProgramPtr shad = g_Renderer.GetShaderManager().LoadProgram("glsl/waves", none); shad->Bind(); shad->BindTexture(str_waveTex, m_WaveTex); shad->BindTexture(str_foamTex, m_FoamTex); shad->Uniform(str_time, (float)m_WaterTexTimer); shad->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection()); for (size_t a = 0; a < m_ShoreWaves.size(); ++a) { if (!frustrum.IsBoxVisible(m_ShoreWaves[a]->m_AABB)) continue; CVertexBuffer::VBChunk* VBchunk = m_ShoreWaves[a]->m_VBvertices; SWavesVertex* base = (SWavesVertex*)VBchunk->m_Owner->Bind(); // setup data pointers GLsizei stride = sizeof(SWavesVertex); shad->VertexPointer(3, GL_FLOAT, stride, &base[VBchunk->m_Index].m_BasePosition); shad->TexCoordPointer(GL_TEXTURE0, 2, GL_UNSIGNED_BYTE, stride, &base[VBchunk->m_Index].m_UV); // NormalPointer(gl_FLOAT, stride, &base[m_VBWater->m_Index].m_UV) pglVertexAttribPointerARB(2, 2, GL_FLOAT, GL_TRUE, stride, &base[VBchunk->m_Index].m_PerpVect); // replaces commented above because my normal is vec2 shad->VertexAttribPointer(str_a_apexPosition, 3, GL_FLOAT, false, stride, &base[VBchunk->m_Index].m_ApexPosition); shad->VertexAttribPointer(str_a_splashPosition, 3, GL_FLOAT, false, stride, &base[VBchunk->m_Index].m_SplashPosition); shad->VertexAttribPointer(str_a_retreatPosition, 3, GL_FLOAT, false, stride, &base[VBchunk->m_Index].m_RetreatPosition); shad->AssertPointersBound(); shad->Uniform(str_translation, m_ShoreWaves[a]->m_TimeDiff); shad->Uniform(str_width, (int)m_ShoreWaves[a]->m_Width); u8* indexBase = m_ShoreWaves_VBIndices->m_Owner->Bind(); glDrawElements(GL_TRIANGLES, (GLsizei) (m_ShoreWaves[a]->m_Width-1)*(7*6), GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*(m_ShoreWaves_VBIndices->m_Index)); shad->Uniform(str_translation, m_ShoreWaves[a]->m_TimeDiff + 6.0f); // TODO: figure out why this doesn't work. //g_Renderer.m_Stats.m_DrawCalls++; //g_Renderer.m_Stats.m_WaterTris += m_ShoreWaves_VBIndices->m_Count / 3; CVertexBuffer::Unbind(); } shad->Unbind(); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); glDisable(GL_BLEND); glDepthFunc(GL_LEQUAL); #endif } /////////////////////////////////////////////////////////////////// // Calculate The blurred normal map to get an idea of where water ought to go. void WaterManager::RecomputeBlurredNormalMap() { // used to cache terrain normals since otherwise we'd recalculate them a lot (I'm blurring the "normal" map). // this might be updated to actually cache in the terrain manager but that's not for now. if (m_BlurredNormalMap == NULL) m_BlurredNormalMap = new CVector3D[m_MapSize*m_MapSize]; CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); // It's really slow to calculate normals so cache them first. CVector3D* normals = new CVector3D[m_MapSize*m_MapSize]; // Not the edges, we won't care about them. float ii = 8.0f, jj = 8.0f; for (size_t j = 2; j < m_MapSize-2; ++j, jj += 4.0f) for (size_t i = 2; i < m_MapSize-2; ++i, ii += 4.0f) { CVector3D norm; terrain->CalcNormal(i,j,norm); normals[j*m_MapSize + i] = norm; } // We could be way fancier (and faster) for our blur but we probably don't need the complexity. // Two pass filter, nothing complicated here. CVector3D blurValue; ii = 8.0f; jj = 8.0f; size_t idx = 2; for (size_t j = 2; j < m_MapSize-2; ++j, jj += 4.0f) for (size_t i = 2; i < m_MapSize-2; ++i, ii += 4.0f,++idx) { blurValue = normals[idx-2]; blurValue += normals[idx-1]; blurValue += normals[idx]; blurValue += normals[idx+1]; blurValue += normals[idx+2]; m_BlurredNormalMap[idx] = blurValue * 0.2f; } // y direction, probably slower because of cache misses but I don't see an easy way around that. ii = 8.0f; jj = 8.0f; for (size_t i = 2; i < m_MapSize-2; ++i, ii += 4.0f) { for (size_t j = 2; j < m_MapSize-2; ++j, jj += 4.0f) { blurValue = normals[(j-2)*m_MapSize + i]; blurValue += normals[(j-1)*m_MapSize + i]; blurValue += normals[j*m_MapSize + i]; blurValue += normals[(j+1)*m_MapSize + i]; blurValue += normals[(j+2)*m_MapSize + i]; m_BlurredNormalMap[j*m_MapSize + i] = blurValue * 0.2f; } } delete[] normals; } /////////////////////////////////////////////////////////////////// // Calculate the strength of the wind at a given point on the map. // This is too slow and should support limited recomputation. void WaterManager::RecomputeWindStrength() { if (m_WindStrength == NULL) m_WindStrength = new float[m_MapSize*m_MapSize]; CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); float waterLevel = m_WaterHeight; CVector2D windDir = CVector2D(cos(m_WindAngle),sin(m_WindAngle)); CVector2D perp = CVector2D(-windDir.Y, windDir.X); // Our kernel will sample 5 points going towards the wind (generally). int kernel[5][2] = { {(int)windDir.X*2,(int)windDir.Y*2}, {(int)windDir.X*5,(int)windDir.Y*5}, {(int)windDir.X*9,(int)windDir.Y*9}, {(int)windDir.X*16,(int)windDir.Y*16}, {(int)windDir.X*25,(int)windDir.Y*25} }; float* Temp = new float[m_MapSize*m_MapSize]; std::fill(Temp, Temp + m_MapSize*m_MapSize, 1.0f); for (size_t j = 0; j < m_MapSize; ++j) for (size_t i = 0; i < m_MapSize; ++i) { float curHeight = terrain->GetVertexGroundLevel(i,j); if (curHeight >= waterLevel) { Temp[j*m_MapSize + i] = 0.3f; // blurs too strong otherwise continue; } if (terrain->GetVertexGroundLevel(i + ceil(windDir.X),j + ceil(windDir.Y)) < waterLevel) continue; // Calculate how dampened our waves should be. float tendency = 0.0f; float oldHeight = std::max(waterLevel,terrain->GetVertexGroundLevel(i+kernel[4][0],j+kernel[4][1])); float currentHeight = std::max(waterLevel,terrain->GetVertexGroundLevel(i+kernel[3][0],j+kernel[3][1])); float avgheight = oldHeight + currentHeight; tendency = currentHeight - oldHeight; oldHeight = currentHeight; currentHeight = std::max(waterLevel,terrain->GetVertexGroundLevel(i+kernel[2][0],j+kernel[2][1])); avgheight += currentHeight; tendency += currentHeight - oldHeight; oldHeight = currentHeight; currentHeight = std::max(waterLevel,terrain->GetVertexGroundLevel(i+kernel[1][0],j+kernel[1][1])); avgheight += currentHeight; tendency += currentHeight - oldHeight; oldHeight = currentHeight; currentHeight = std::max(waterLevel,terrain->GetVertexGroundLevel(i+kernel[0][0],j+kernel[0][1])); avgheight += currentHeight; tendency += currentHeight - oldHeight; float baseLevel = std::max(0.0f,1.0f - (avgheight/5.0f-waterLevel)/20.0f); baseLevel *= baseLevel; tendency /= 15.0f; baseLevel -= tendency; // if the terrain was sloping downwards, increase baselevel. Otherwise reduce. baseLevel = clamp(baseLevel,0.0f,1.0f); // Draw on map. This is pretty slow. float length = 35.0f * (1.0f-baseLevel/1.8f); for (float y = 0; y < length; y += 0.6f) { int xx = clamp(i - y * windDir.X,0.0f,(float)(m_MapSize-1)); int yy = clamp(j - y * windDir.Y,0.0f,(float)(m_MapSize-1)); Temp[yy*m_MapSize + xx] = Temp[yy*m_MapSize + xx] < (0.0f+baseLevel/1.5f) * (1.0f-y/length) + y/length * 1.0f ? Temp[yy*m_MapSize + xx] : (0.0f+baseLevel/1.5f) * (1.0f-y/length) + y/length * 1.0f; } } int blurKernel[4][2] = { {(int)ceil(windDir.X),(int)ceil(windDir.Y)}, {(int)windDir.X*3,(int)windDir.Y*3}, {(int)ceil(perp.X),(int)ceil(perp.Y)}, {(int)-ceil(perp.X),(int)-ceil(perp.Y)} }; float blurValue; for (size_t j = 2; j < m_MapSize-2; ++j) for (size_t i = 2; i < m_MapSize-2; ++i) { blurValue = Temp[(j+blurKernel[0][1])*m_MapSize + i+blurKernel[0][0]]; blurValue += Temp[(j+blurKernel[0][1])*m_MapSize + i+blurKernel[0][0]]; blurValue += Temp[(j+blurKernel[0][1])*m_MapSize + i+blurKernel[0][0]]; blurValue += Temp[(j+blurKernel[0][1])*m_MapSize + i+blurKernel[0][0]]; m_WindStrength[j*m_MapSize + i] = blurValue * 0.25f; } delete[] Temp; } //////////////////////////////////////////////////////////////////////// // TODO: This will always recalculate for now void WaterManager::SetMapSize(size_t size) { // TODO: Im' blindly trusting the user here. m_MapSize = size; m_NeedInfoUpdate = true; m_updatei0 = 0; m_updatei1 = size; m_updatej0 = 0; m_updatej1 = size; SAFE_ARRAY_DELETE(m_DistanceHeightmap); SAFE_ARRAY_DELETE(m_BlurredNormalMap); SAFE_ARRAY_DELETE(m_WindStrength); } //////////////////////////////////////////////////////////////////////// // This will set the bools properly void WaterManager::UpdateQuality() { if (g_Renderer.GetOptionBool(CRenderer::OPT_WATERUGLY) != m_WaterUgly) { m_WaterUgly = g_Renderer.GetOptionBool(CRenderer::OPT_WATERUGLY); m_NeedsReloading = true; } if (g_Renderer.GetOptionBool(CRenderer::OPT_WATERFANCYEFFECTS) != m_WaterFancyEffects) { m_WaterFancyEffects = g_Renderer.GetOptionBool(CRenderer::OPT_WATERFANCYEFFECTS); m_NeedsReloading = true; } if (g_Renderer.GetOptionBool(CRenderer::OPT_WATERREALDEPTH) != m_WaterRealDepth) { m_WaterRealDepth = g_Renderer.GetOptionBool(CRenderer::OPT_WATERREALDEPTH); m_NeedsReloading = true; } if (g_Renderer.GetOptionBool(CRenderer::OPT_WATERREFRACTION) != m_WaterRefraction) { m_WaterRefraction = g_Renderer.GetOptionBool(CRenderer::OPT_WATERREFRACTION); m_NeedsReloading = true; } if (g_Renderer.GetOptionBool(CRenderer::OPT_WATERREFLECTION) != m_WaterReflection) { m_WaterReflection = g_Renderer.GetOptionBool(CRenderer::OPT_WATERREFLECTION); m_NeedsReloading = true; } if (g_Renderer.GetOptionBool(CRenderer::OPT_SHADOWSONWATER) != m_WaterShadows) { m_WaterShadows = g_Renderer.GetOptionBool(CRenderer::OPT_SHADOWSONWATER); m_NeedsReloading = true; } } bool WaterManager::WillRenderFancyWater() { if (!g_Renderer.GetCapabilities().m_PrettyWater) return false; if (!m_RenderWater || m_WaterUgly) return false; return true; } Index: ps/trunk/source/renderer/WaterManager.h =================================================================== --- ps/trunk/source/renderer/WaterManager.h (revision 18442) +++ ps/trunk/source/renderer/WaterManager.h (revision 18443) @@ -1,198 +1,197 @@ /* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * Water settings (speed, height) and texture management */ #ifndef INCLUDED_WATERMANAGER #define INCLUDED_WATERMANAGER #include "graphics/Texture.h" #include "lib/ogl.h" #include "maths/Matrix3D.h" #include "maths/Vector2D.h" #include "ps/Shapes.h" #include "renderer/VertexBufferManager.h" class CSimulation2; class CFrustum; struct CoastalPoint; struct WaveObject; /** * Class WaterManager: Maintain rendering-related water settings and textures * Anything that affects gameplay should go in CcmpWaterManager.cpp and passed to this (possibly as copy). */ class WaterManager { public: CTexturePtr m_WaterTexture[60]; CTexturePtr m_NormalMap[60]; float* m_WindStrength; // How strong the waves are at point X. % of waviness. float* m_DistanceHeightmap; // How far from the shore a point is. Manhattan CVector3D* m_BlurredNormalMap; // Cache a slightly blurred map of the normals of the terrain. // Waves vertex buffers std::vector< WaveObject* > m_ShoreWaves; // TODO: once we get C++11, remove pointer // Waves indices buffer. Only one since All Wave Objects have the same. CVertexBuffer::VBChunk* m_ShoreWaves_VBIndices; size_t m_MapSize; ssize_t m_TexSize; CTexturePtr m_WaveTex; CTexturePtr m_FoamTex; GLuint m_depthTT; GLuint m_FancyTextureNormal; GLuint m_FancyTextureOther; GLuint m_FancyTextureDepth; GLuint m_ReflFboDepthTexture; GLuint m_RefrFboDepthTexture; // used to know what to update when updating parts of the terrain only. u32 m_updatei0; u32 m_updatej0; u32 m_updatei1; u32 m_updatej1; int m_WaterCurrentTex; bool m_RenderWater; // Force the use of the fixed function for rendering. bool m_WaterUgly; // Those variables register the current quality level. If there is a change, I have to recompile the shader. // Use real depth or use the fake precomputed one. bool m_WaterRealDepth; // Use fancy shore effects and show trails behind ships bool m_WaterFancyEffects; // Use refractions instead of simply making the water more or less transparent. bool m_WaterRefraction; // Use complete reflections instead of showing merely the sky. bool m_WaterReflection; // Show shadows on the water. bool m_WaterShadows; bool m_NeedsReloading; // requires also recreating the super fancy information. bool m_NeedInfoUpdate; float m_WaterHeight; double m_WaterTexTimer; float m_RepeatPeriod; // Reflection and refraction textures for fancy water GLuint m_ReflectionTexture; GLuint m_RefractionTexture; - size_t m_ReflectionTextureSize; - size_t m_RefractionTextureSize; + size_t m_RefTextureSize; // framebuffer objects GLuint m_RefractionFbo; GLuint m_ReflectionFbo; GLuint m_FancyEffectsFBO; // Model-view-projection matrices for reflected & refracted cameras // (used to let the vertex shader do projective texturing) CMatrix3D m_ReflectionMatrix; CMatrix3D m_RefractionMatrix; // Water parameters std::wstring m_WaterType; // Which texture to use. CColor m_WaterColor; // Color of the water without refractions. This is what you're seeing when the water's deep or murkiness high. CColor m_WaterTint; // Tint of refraction in the water. float m_Waviness; // How big the waves are. float m_Murkiness; // How murky the water is. float m_WindAngle; // In which direction the water waves go. public: WaterManager(); ~WaterManager(); /** * LoadWaterTextures: Load water textures from within the * progressive load framework. * * @return 0 if loading has completed, a value from 1 to 100 (in percent of completion) * if more textures need to be loaded and a negative error value on failure. */ int LoadWaterTextures(); /** * Resize: Updates the fancy water textures so that water will render correctly * with fancy water. */ void Resize(); /** * ReloadWaterNormalTextures: Reload the normal textures so that changing * water type in Atlas will actually do the right thing. */ void ReloadWaterNormalTextures(); /** * UnloadWaterTextures: Free any loaded water textures and reset the internal state * so that another call to LoadWaterTextures will begin progressive loading. */ void UnloadWaterTextures(); /** * RecomputeWindStrength: calculates the intensity of waves */ void RecomputeWindStrength(); /** * RecomputeDistanceHeightmap: recalculates (or calculates) the distance heightmap. */ void RecomputeDistanceHeightmap(); /** * RecomputeBlurredNormalMap: calculates the blurred normal map of the terrain. Slow. */ void RecomputeBlurredNormalMap(); /** * CreateWaveMeshes: Creates the waves objects (and meshes). */ void CreateWaveMeshes(); /** * Updates the map size. Will trigger a complete recalculation of fancy water information the next turn. */ void SetMapSize(size_t size); /** * Updates the settings to the one from the renderer, and sets m_NeedsReloading. */ void UpdateQuality(); /** * Returns true if fancy water shaders will be used (i.e. the hardware is capable * and it hasn't been configured off) */ bool WillRenderFancyWater(); void RenderWaves(const CFrustum& frustrum); }; #endif // INCLUDED_WATERMANAGER