diff --git a/data/base/shaders/terrain_water_high.frag b/data/base/shaders/terrain_water_high.frag index 49b1b9ae05c..b1a09173d82 100644 --- a/data/base/shaders/terrain_water_high.frag +++ b/data/base/shaders/terrain_water_high.frag @@ -5,12 +5,9 @@ #define WZ_MIP_LOAD_BIAS 0.f // -uniform sampler2D tex1; -uniform sampler2D tex2; -uniform sampler2D tex1_nm; -uniform sampler2D tex2_nm; -uniform sampler2D tex1_sm; -uniform sampler2D tex2_sm; +uniform sampler2DArray tex; +uniform sampler2DArray tex_nm; +uniform sampler2DArray tex_sm; uniform sampler2D lightmap_tex; // light colors/intensity: @@ -27,6 +24,7 @@ uniform float fogStart; #if (!defined(GL_ES) && (__VERSION__ >= 130)) || (defined(GL_ES) && (__VERSION__ >= 300)) #define NEWGL #define FRAGMENT_INPUT in +#define texture2DArray(tex,coord,bias) texture(tex,coord,bias) #else #define texture(tex,uv,bias) texture2D(tex,uv,bias) #define FRAGMENT_INPUT varying @@ -56,8 +54,8 @@ vec4 main_bumpMapping() vec2 uv1 = uv1_uv2.xy; vec2 uv2 = uv1_uv2.zw; - vec3 N1 = texture(tex1_nm, uv2, WZ_MIP_LOAD_BIAS).xzy; // y is up in modelSpace - vec3 N2 = texture(tex2_nm, uv1, WZ_MIP_LOAD_BIAS).xzy; + vec3 N1 = texture2DArray(tex_nm, vec3(uv2, 0.f), WZ_MIP_LOAD_BIAS).xzy; // y is up in modelSpace + vec3 N2 = texture2DArray(tex_nm, vec3(uv1, 1.f), WZ_MIP_LOAD_BIAS).xzy; vec3 N; //use overlay blending to mix normal maps properly N.x = N1.x < 0.5 ? (2.0 * N1.x * N2.x) : (1.0 - 2.0 * (1.0 - N1.x) * (1.0 - N2.x)); N.z = N1.z < 0.5 ? (2.0 * N1.z * N2.z) : (1.0 - 2.0 * (1.0 - N1.z) * (1.0 - N2.z)); @@ -71,12 +69,12 @@ vec4 main_bumpMapping() float lambertTerm = max(dot(N, lightDir), 0.0); // diffuse lighting // Gaussian specular term computation - float gloss = texture(tex1_sm, uv1, WZ_MIP_LOAD_BIAS).r * texture(tex2_sm, uv2, WZ_MIP_LOAD_BIAS).r; + float gloss = texture2DArray(tex_sm, vec3(uv1, 0.f), WZ_MIP_LOAD_BIAS).r * texture2DArray(tex_sm, vec3(uv2, 1.f), WZ_MIP_LOAD_BIAS).r; vec3 H = normalize(halfVec); float exponent = acos(dot(H, N)) / (gloss + 0.05); float gaussianTerm = exp(-(exponent * exponent)); - vec4 fragColor = (texture(tex1, uv1, WZ_MIP_LOAD_BIAS)+texture(tex2, uv2, WZ_MIP_LOAD_BIAS)) * (gloss+vec4(0.08,0.13,0.15,1.0)); + vec4 fragColor = (texture2DArray(tex, vec3(uv1, 0.f), WZ_MIP_LOAD_BIAS)+texture2DArray(tex, vec3(uv2, 1.f), WZ_MIP_LOAD_BIAS)) * (gloss+vec4(0.08,0.13,0.15,1.0)); fragColor = fragColor*(ambientLight+diffuseLight*lambertTerm) + specularLight*(1.0-gloss)*gaussianTerm*vec4(1.0,0.843,0.686,1.0); vec4 lightmap_vec4 = texture(lightmap_tex, uvLightmap, 0.f); vec4 color = fragColor * vec4(vec3(lightmap_vec4.a), 1.f); // ... * tile brightness / ambient occlusion (stored in lightmap.a); diff --git a/data/base/shaders/vk/terrain_water_high.frag b/data/base/shaders/vk/terrain_water_high.frag index a881aa4a5cd..0dc63317b9c 100644 --- a/data/base/shaders/vk/terrain_water_high.frag +++ b/data/base/shaders/vk/terrain_water_high.frag @@ -2,13 +2,10 @@ layout (constant_id = 0) const float WZ_MIP_LOAD_BIAS = 0.f; -layout(set = 1, binding = 0) uniform sampler2D tex1; -layout(set = 1, binding = 1) uniform sampler2D tex2; -layout(set = 1, binding = 2) uniform sampler2D tex1_nm; -layout(set = 1, binding = 3) uniform sampler2D tex2_nm; -layout(set = 1, binding = 4) uniform sampler2D tex1_sm; -layout(set = 1, binding = 5) uniform sampler2D tex2_sm; -layout(set = 1, binding = 6) uniform sampler2D lightmap_tex; +layout(set = 1, binding = 0) uniform sampler2DArray tex; +layout(set = 1, binding = 1) uniform sampler2DArray tex_nm; +layout(set = 1, binding = 2) uniform sampler2DArray tex_sm; +layout(set = 1, binding = 3) uniform sampler2D lightmap_tex; layout(std140, set = 0, binding = 0) uniform cbuffer { mat4 ModelViewProjectionMatrix; @@ -47,8 +44,8 @@ vec4 main_bumpMapping() vec2 uv1 = uv1_uv2.xy; vec2 uv2 = uv1_uv2.zw; - vec3 N1 = texture(tex1_nm, uv2, WZ_MIP_LOAD_BIAS).xzy; // y is up in modelSpace - vec3 N2 = texture(tex2_nm, uv1, WZ_MIP_LOAD_BIAS).xzy; + vec3 N1 = texture(tex_nm, vec3(uv2, 0.f), WZ_MIP_LOAD_BIAS).xzy; // y is up in modelSpace + vec3 N2 = texture(tex_nm, vec3(uv1, 1.f), WZ_MIP_LOAD_BIAS).xzy; vec3 N; //use overlay blending to mix normal maps properly N.x = N1.x < 0.5 ? (2.0 * N1.x * N2.x) : (1.0 - 2.0 * (1.0 - N1.x) * (1.0 - N2.x)); N.z = N1.z < 0.5 ? (2.0 * N1.z * N2.z) : (1.0 - 2.0 * (1.0 - N1.z) * (1.0 - N2.z)); @@ -61,12 +58,12 @@ vec4 main_bumpMapping() float lambertTerm = max(dot(N, lightDir), 0.0); // diffuse lighting // Gaussian specular term computation - float gloss = texture(tex1_sm, uv1, WZ_MIP_LOAD_BIAS).r * texture(tex2_sm, uv2, WZ_MIP_LOAD_BIAS).r; + float gloss = texture(tex_sm, vec3(uv1, 0.f), WZ_MIP_LOAD_BIAS).r * texture(tex_sm, vec3(uv2, 1.f), WZ_MIP_LOAD_BIAS).r; vec3 H = normalize(halfVec); float exponent = acos(dot(H, N)) / (gloss + 0.05); float gaussianTerm = exp(-(exponent * exponent)); - vec4 fragColor = (texture(tex1, uv1, WZ_MIP_LOAD_BIAS)+texture(tex2, uv2, WZ_MIP_LOAD_BIAS)) * (gloss+vec4(0.08,0.13,0.15,1.0)); + vec4 fragColor = (texture(tex, vec3(uv1, 0.f), WZ_MIP_LOAD_BIAS)+texture(tex, vec3(uv2, 1.f), WZ_MIP_LOAD_BIAS)) * (gloss+vec4(0.08,0.13,0.15,1.0)); fragColor = fragColor*(ambientLight+diffuseLight*lambertTerm) + specularLight*(1.0-gloss)*gaussianTerm*vec4(1.0,0.843,0.686,1.0); vec4 lightmap_vec4 = texture(lightmap_tex, uvLightmap, 0.f); vec4 color = fragColor * vec4(vec3(lightmap_vec4.a), 1.f); // ... * tile brightness / ambient occlusion (stored in lightmap.a); diff --git a/lib/ivis_opengl/gfx_api.h b/lib/ivis_opengl/gfx_api.h index b8c66e37ca8..0c8a358356c 100644 --- a/lib/ivis_opengl/gfx_api.h +++ b/lib/ivis_opengl/gfx_api.h @@ -1080,13 +1080,10 @@ namespace gfx_api std::tuple< vertex_buffer_description<16, gfx_api::vertex_attribute_input_rate::vertex, vertex_attribute_description> // WaterVertex, w is depth >, std::tuple< - texture_description<0, sampler_type::anisotropic_repeat>, // tex1 - texture_description<1, sampler_type::anisotropic_repeat>, // tex2 - texture_description<2, sampler_type::anisotropic_repeat>, // normal map1 - texture_description<3, sampler_type::anisotropic_repeat>, // normal map2 - texture_description<4, sampler_type::anisotropic_repeat>, // specular map1 - texture_description<5, sampler_type::anisotropic_repeat>, // specular map2 - texture_description<6, sampler_type::bilinear> // lightmap + texture_description<0, sampler_type::anisotropic_repeat, pixel_format_target::texture_2d_array>, // textures + texture_description<1, sampler_type::anisotropic_repeat, pixel_format_target::texture_2d_array>, // normal maps + texture_description<2, sampler_type::anisotropic_repeat, pixel_format_target::texture_2d_array>, // specular maps + texture_description<3, sampler_type::bilinear> // lightmap >, SHADER_WATER_HIGH>; template<> diff --git a/lib/ivis_opengl/gfx_api_gl.cpp b/lib/ivis_opengl/gfx_api_gl.cpp index da0cb73d932..034a49eefec 100644 --- a/lib/ivis_opengl/gfx_api_gl.cpp +++ b/lib/ivis_opengl/gfx_api_gl.cpp @@ -720,7 +720,7 @@ static const std::map shader_to_file_table = "cameraPos", "sunPos", "emissiveLight", "ambientLight", "diffuseLight", "specularLight", "fogColor", "fogEnabled", "fogEnd", "fogStart", "timeSec", - "tex1", "tex2", "tex1_nm", "tex2_nm", "tex1_sm", "tex2_sm", "lightmap_tex" } }), + "tex", "tex_nm", "tex_sm", "lightmap_tex" } }), std::make_pair(SHADER_WATER_CLASSIC, program_data{ "classic water program", "shaders/terrain_water_classic.vert", "shaders/terrain_water_classic.frag", { "ModelViewProjectionMatrix", "ModelUVLightmapMatrix", "ShadowMapMVPMatrix", "ModelUV1Matrix", "ModelUV2Matrix", "cameraPos", "sunPos", @@ -2027,10 +2027,7 @@ void gl_pipeline_state_object::set_constants(const gfx_api::constant_buffer_type setUniforms(i++, 0); setUniforms(i++, 1); setUniforms(i++, 2); - setUniforms(i++, 3); - setUniforms(i++, 4); - setUniforms(i++, 5); - setUniforms(i++, 6); // lightmap_tex + setUniforms(i++, 3); // lightmap_tex } void gl_pipeline_state_object::set_constants(const gfx_api::constant_buffer_type& cbuf) diff --git a/src/terrain.cpp b/src/terrain.cpp index 1311959c730..9e1ba953e41 100644 --- a/src/terrain.cpp +++ b/src/terrain.cpp @@ -812,23 +812,16 @@ struct WaterTextures_Normal struct WaterTextures_High { - gfx_api::texture* tex1 = nullptr; - gfx_api::texture* tex2 = nullptr; - // water optional textures. null if none - gfx_api::texture* tex1_nm = nullptr; - gfx_api::texture* tex2_nm = nullptr; - gfx_api::texture* tex1_sm = nullptr; - gfx_api::texture* tex2_sm = nullptr; + gfx_api::texture_array* tex = nullptr; + gfx_api::texture_array* tex_nm = nullptr; + gfx_api::texture_array* tex_sm = nullptr; public: void clear() { - delete tex1; tex1 = nullptr; - delete tex2; tex2 = nullptr; - delete tex1_nm; tex1_nm = nullptr; - delete tex2_nm; tex2_nm = nullptr; - delete tex1_sm; tex1_sm = nullptr; - delete tex2_sm; tex2_sm = nullptr; + delete tex; tex = nullptr; + delete tex_nm; tex_nm = nullptr; + delete tex_sm; tex_sm = nullptr; } }; @@ -856,30 +849,92 @@ void loadWaterTextures(int maxTerrainTextureSize) waterTexturesNormal.clear(); waterTexturesHigh.clear(); - auto checkTex = [maxTerrainTextureSize](const WzString &fileName, gfx_api::texture_type type) -> gfx_api::texture* { - WzString fullName = "texpages/" + fileName; - auto imageLoadFilename = gfx_api::imageLoadFilenameFromInputFilename(fullName); - if (!PHYSFS_exists(imageLoadFilename)) - { - return nullptr; - } - return gfx_api::context::get().loadTextureFromFile(imageLoadFilename.toUtf8().c_str(), type, maxTerrainTextureSize, maxTerrainTextureSize); - }; - if (terrainShaderQuality == TerrainShaderQuality::MEDIUM) { + auto checkTex = [maxTerrainTextureSize](const WzString &fileName, gfx_api::texture_type type) -> gfx_api::texture* { + WzString fullName = "texpages/" + fileName; + auto imageLoadFilename = gfx_api::imageLoadFilenameFromInputFilename(fullName); + if (!PHYSFS_exists(imageLoadFilename)) + { + return nullptr; + } + return gfx_api::context::get().loadTextureFromFile(imageLoadFilename.toUtf8().c_str(), type, maxTerrainTextureSize, maxTerrainTextureSize); + }; + waterTexturesNormal.tex1 = checkTex("page-80-water-1.png", gfx_api::texture_type::game_texture); waterTexturesNormal.tex2 = checkTex("page-81-water-2.png", gfx_api::texture_type::specular_map); } else if (terrainShaderQuality == TerrainShaderQuality::NORMAL_MAPPING) { - waterTexturesHigh.tex1 = checkTex("page-80-water-1.png", gfx_api::texture_type::game_texture); - waterTexturesHigh.tex2 = checkTex("page-81-water-2.png", gfx_api::texture_type::game_texture); - // check water optional textures - waterTexturesHigh.tex1_nm = checkTex("page-80-water-1_nm.png", gfx_api::texture_type::normal_map); - waterTexturesHigh.tex2_nm = checkTex("page-81-water-2_nm.png", gfx_api::texture_type::normal_map); - waterTexturesHigh.tex1_sm = checkTex("page-80-water-1_sm.png", gfx_api::texture_type::specular_map); - waterTexturesHigh.tex2_sm = checkTex("page-81-water-2_sm.png", gfx_api::texture_type::specular_map); + std::vector waterTextureFilenames; + std::vector waterTextureFilenames_nm; + std::vector waterTextureFilenames_sm; + + auto optTexturenameToPath = [](const WzString& textureFilename) -> WzString { + if (textureFilename.isEmpty()) + { + return WzString(); + } + WzString fullName = "texpages/" + textureFilename; + auto imageLoadFilename = gfx_api::imageLoadFilenameFromInputFilename(fullName); + if (!PHYSFS_exists(imageLoadFilename)) + { + return WzString(); + } + return fullName; + }; + + // check water optional textures.push_back(optTexturenameToPath("page-80-water-1.png")); + waterTextureFilenames.push_back(optTexturenameToPath("page-81-water-2.png")); + waterTextureFilenames_nm.push_back(optTexturenameToPath("page-80-water-1_nm.png")); + waterTextureFilenames_nm.push_back(optTexturenameToPath("page-81-water-2_nm.png")); + waterTextureFilenames_sm.push_back(optTexturenameToPath("page-80-water-1_sm.png")); + waterTextureFilenames_sm.push_back(optTexturenameToPath("page-81-water-2_sm.png")); + + if (!std::all_of(waterTextureFilenames.begin(), waterTextureFilenames.end(), [](const WzString& texturePath) -> bool { return !texturePath.isEmpty(); })) + { + debug(LOG_FATAL, "Missing one or more base water textures?"); + return; + } + + waterTexturesHigh.tex = gfx_api::context::get().loadTextureArrayFromFiles(waterTextureFilenames, gfx_api::texture_type::game_texture, maxTerrainTextureSize, maxTerrainTextureSize, nullptr, []() { resDoResLoadCallback(); }); + waterTexturesHigh.tex_nm = nullptr; + waterTexturesHigh.tex_sm = nullptr; + + if (std::any_of(waterTextureFilenames_nm.begin(), waterTextureFilenames_nm.end(), [](const WzString& filename) { + return !filename.isEmpty(); + })) + { + waterTexturesHigh.tex_nm = gfx_api::context::get().loadTextureArrayFromFiles(waterTextureFilenames_nm, gfx_api::texture_type::normal_map, maxTerrainTextureSize, maxTerrainTextureSize, [](int width, int height, int channels) -> std::unique_ptr { + std::unique_ptr pDefaultNormalMap = std::unique_ptr(new iV_Image); + pDefaultNormalMap->allocate(width, height, channels, true); + // default normal map: (0,0,1) + unsigned char* pBmpWrite = pDefaultNormalMap->bmp_w(); + memset(pBmpWrite, 0x7f, pDefaultNormalMap->data_size()); + if (channels >= 3) + { + size_t pixelIncrement = static_cast(channels); + for (size_t b = 0; b < pDefaultNormalMap->data_size(); b += pixelIncrement) + { + pBmpWrite[b+2] = 0xff; // blue=z + } + } + return pDefaultNormalMap; + }, []() { resDoResLoadCallback(); }); + ASSERT(waterTexturesHigh.tex_nm != nullptr, "Failed to load water normals"); + } + if (std::any_of(waterTextureFilenames_sm.begin(), waterTextureFilenames_sm.end(), [](const WzString& filename) { + return !filename.isEmpty(); + })) + { + waterTexturesHigh.tex_sm = gfx_api::context::get().loadTextureArrayFromFiles(waterTextureFilenames_sm, gfx_api::texture_type::specular_map, maxTerrainTextureSize, maxTerrainTextureSize, [](int width, int height, int channels) -> std::unique_ptr { + std::unique_ptr pDefaultSpecularMap = std::unique_ptr(new iV_Image); + // default specular map: 0 + pDefaultSpecularMap->allocate(width, height, channels, true); + return pDefaultSpecularMap; + }, []() { resDoResLoadCallback(); }); + ASSERT(waterTexturesHigh.tex_sm != nullptr, "Failed to load water specular maps"); + } } else { @@ -2022,13 +2077,13 @@ void drawWaterHighImpl(const glm::mat4 &ModelViewProjection, const Vector3f &cam const auto ModelUV2 = glm::transpose(glm::mat4(paramsX2, paramsY2, glm::vec4(0,0,1,0), glm::vec4(0,0,0,1))); const auto &renderState = getCurrentRenderState(); - ASSERT_OR_RETURN(, waterTexturesHigh.tex1 && waterTexturesHigh.tex2, "Failed to load water texture"); + ASSERT_OR_RETURN(, waterTexturesHigh.tex, "Failed to load water textures"); PSO::get().bind(); PSO::get().bind_textures( - waterTexturesHigh.tex1, waterTexturesHigh.tex2, - waterTexturesHigh.tex1_nm, waterTexturesHigh.tex2_nm, - waterTexturesHigh.tex1_sm, waterTexturesHigh.tex2_sm, + waterTexturesHigh.tex, + waterTexturesHigh.tex_nm, + waterTexturesHigh.tex_sm, lightmap_texture); PSO::get().bind_vertex_buffers(waterVBO); PSO::get().bind_constants({