From 6958071f497c8f059429343339ad3818ed3cfed1 Mon Sep 17 00:00:00 2001 From: Danila Shutov Date: Sun, 16 Feb 2020 22:37:28 +0300 Subject: [PATCH] Basic model shading (#9374) --- .../object_shader/opengl_fragment.glsl | 131 ++++++++++++++++++ .../shaders/object_shader/opengl_vertex.glsl | 44 ++++++ src/client/client.cpp | 2 +- src/client/client.h | 2 +- src/client/clientenvironment.cpp | 57 ++++++++ src/client/content_cao.cpp | 129 ++++++++++++----- src/client/content_cao.h | 4 + src/client/shader.cpp | 8 +- src/client/shader.h | 2 + 9 files changed, 338 insertions(+), 41 deletions(-) create mode 100644 client/shaders/object_shader/opengl_fragment.glsl create mode 100644 client/shaders/object_shader/opengl_vertex.glsl diff --git a/client/shaders/object_shader/opengl_fragment.glsl b/client/shaders/object_shader/opengl_fragment.glsl new file mode 100644 index 000000000..bb9e40637 --- /dev/null +++ b/client/shaders/object_shader/opengl_fragment.glsl @@ -0,0 +1,131 @@ +uniform sampler2D baseTexture; +uniform sampler2D normalTexture; +uniform sampler2D textureFlags; + +uniform vec4 emissiveColor; +uniform vec4 skyBgColor; +uniform float fogDistance; +uniform vec3 eyePosition; + +varying vec3 vNormal; +varying vec3 vPosition; +varying vec3 worldPosition; + +varying vec3 eyeVec; +varying vec3 lightVec; +varying float vIDiff; + +bool normalTexturePresent = false; +bool texTileableHorizontal = false; +bool texTileableVertical = false; +bool texSeamless = false; + +const float e = 2.718281828459; +const float BS = 10.0; +const float fogStart = FOG_START; +const float fogShadingParameter = 1 / ( 1 - fogStart); + +void get_texture_flags() +{ + vec4 flags = texture2D(textureFlags, vec2(0.0, 0.0)); + if (flags.r > 0.5) { + normalTexturePresent = true; + } + if (flags.g > 0.5) { + texTileableHorizontal = true; + } + if (flags.b > 0.5) { + texTileableVertical = true; + } + if (texTileableHorizontal && texTileableVertical) { + texSeamless = true; + } +} + +float intensity(vec3 color) +{ + return (color.r + color.g + color.b) / 3.0; +} + +float get_rgb_height(vec2 uv) +{ + if (texSeamless) { + return intensity(texture2D(baseTexture, uv).rgb); + } else { + return intensity(texture2D(baseTexture, clamp(uv, 0.0, 0.999)).rgb); + } +} + +vec4 get_normal_map(vec2 uv) +{ + vec4 bump = texture2D(normalTexture, uv).rgba; + bump.xyz = normalize(bump.xyz * 2.0 - 1.0); + return bump; +} + +void main(void) +{ + vec3 color; + vec4 bump; + vec2 uv = gl_TexCoord[0].st; + bool use_normalmap = false; + get_texture_flags(); + +#if USE_NORMALMAPS == 1 + if (normalTexturePresent) { + bump = get_normal_map(uv); + use_normalmap = true; + } +#endif + +#if GENERATE_NORMALMAPS == 1 + if (normalTexturePresent == false) { + float tl = get_rgb_height(vec2(uv.x - SAMPLE_STEP, uv.y + SAMPLE_STEP)); + float t = get_rgb_height(vec2(uv.x - SAMPLE_STEP, uv.y - SAMPLE_STEP)); + float tr = get_rgb_height(vec2(uv.x + SAMPLE_STEP, uv.y + SAMPLE_STEP)); + float r = get_rgb_height(vec2(uv.x + SAMPLE_STEP, uv.y)); + float br = get_rgb_height(vec2(uv.x + SAMPLE_STEP, uv.y - SAMPLE_STEP)); + float b = get_rgb_height(vec2(uv.x, uv.y - SAMPLE_STEP)); + float bl = get_rgb_height(vec2(uv.x -SAMPLE_STEP, uv.y - SAMPLE_STEP)); + float l = get_rgb_height(vec2(uv.x - SAMPLE_STEP, uv.y)); + float dX = (tr + 2.0 * r + br) - (tl + 2.0 * l + bl); + float dY = (bl + 2.0 * b + br) - (tl + 2.0 * t + tr); + bump = vec4(normalize(vec3 (dX, dY, NORMALMAPS_STRENGTH)), 1.0); + use_normalmap = true; + } +#endif + + vec4 base = texture2D(baseTexture, uv).rgba; + +#ifdef ENABLE_BUMPMAPPING + if (use_normalmap) { + vec3 L = normalize(lightVec); + vec3 E = normalize(eyeVec); + float specular = pow(clamp(dot(reflect(L, bump.xyz), E), 0.0, 1.0), 1.0); + float diffuse = dot(-E,bump.xyz); + color = (diffuse + 0.1 * specular) * base.rgb; + } else { + color = base.rgb; + } +#else + color = base.rgb; +#endif + + vec4 col = vec4(color.rgb, base.a); + + col.rgb *= emissiveColor.rgb * vIDiff; + // Due to a bug in some (older ?) graphics stacks (possibly in the glsl compiler ?), + // the fog will only be rendered correctly if the last operation before the + // clamp() is an addition. Else, the clamp() seems to be ignored. + // E.g. the following won't work: + // float clarity = clamp(fogShadingParameter + // * (fogDistance - length(eyeVec)) / fogDistance), 0.0, 1.0); + // As additions usually come for free following a multiplication, the new formula + // should be more efficient as well. + // Note: clarity = (1 - fogginess) + float clarity = clamp(fogShadingParameter + - fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0); + col = mix(skyBgColor, col, clarity); + + gl_FragColor = vec4(col.rgb, base.a); +} diff --git a/client/shaders/object_shader/opengl_vertex.glsl b/client/shaders/object_shader/opengl_vertex.glsl new file mode 100644 index 000000000..99b9b2709 --- /dev/null +++ b/client/shaders/object_shader/opengl_vertex.glsl @@ -0,0 +1,44 @@ +uniform mat4 mWorldViewProj; +uniform mat4 mWorld; + +uniform vec3 eyePosition; +uniform float animationTimer; + +varying vec3 vNormal; +varying vec3 vPosition; +varying vec3 worldPosition; + +varying vec3 eyeVec; +varying vec3 lightVec; +varying float vIDiff; + +const float e = 2.718281828459; +const float BS = 10.0; + +float directional_ambient(vec3 normal) +{ + vec3 v = normal * normal; + + if (normal.y < 0) + return dot(v, vec3(0.670820f, 0.447213f, 0.836660f)); + + return dot(v, vec3(0.670820f, 1.000000f, 0.836660f)); +} + +void main(void) +{ + gl_TexCoord[0] = gl_MultiTexCoord0; + gl_Position = mWorldViewProj * gl_Vertex; + + vPosition = gl_Position.xyz; + vNormal = gl_Normal; + worldPosition = (mWorld * gl_Vertex).xyz; + + vec3 sunPosition = vec3 (0.0, eyePosition.y * BS + 900.0, 0.0); + + lightVec = sunPosition - worldPosition; + eyeVec = -(gl_ModelViewMatrix * gl_Vertex).xyz; + vIDiff = directional_ambient(normalize(gl_Normal)); + + gl_FrontColor = gl_BackColor = gl_Color; +} diff --git a/src/client/client.cpp b/src/client/client.cpp index 6016f940e..c6d28ce80 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -1860,7 +1860,7 @@ ITextureSource* Client::getTextureSource() { return m_tsrc; } -IShaderSource* Client::getShaderSource() +IWritableShaderSource* Client::getShaderSource() { return m_shsrc; } diff --git a/src/client/client.h b/src/client/client.h index b9807a11e..2933a46e0 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -367,7 +367,7 @@ public: const NodeDefManager* getNodeDefManager() override; ICraftDefManager* getCraftDefManager() override; ITextureSource* getTextureSource(); - virtual IShaderSource* getShaderSource(); + virtual IWritableShaderSource* getShaderSource(); u16 allocateUnknownNodeId(const std::string &name) override; virtual ISoundManager* getSoundManager(); MtEventManager* getEventManager(); diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp index 5eb033302..52d133781 100644 --- a/src/client/clientenvironment.cpp +++ b/src/client/clientenvironment.cpp @@ -32,10 +32,65 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "raycast.h" #include "voxelalgorithms.h" #include "settings.h" +#include "shader.h" #include "content_cao.h" #include #include "client/renderingengine.h" +/* + CAOShaderConstantSetter +*/ + +//! Shader constant setter for passing material emissive color to the CAO object_shader +class CAOShaderConstantSetter : public IShaderConstantSetter +{ +public: + CAOShaderConstantSetter(): + m_emissive_color_setting("emissiveColor") + {} + + ~CAOShaderConstantSetter() override = default; + + void onSetConstants(video::IMaterialRendererServices *services, + bool is_highlevel) override + { + if (!is_highlevel) + return; + + // Ambient color + video::SColorf emissive_color(m_emissive_color); + + float as_array[4] = { + emissive_color.r, + emissive_color.g, + emissive_color.b, + emissive_color.a, + }; + m_emissive_color_setting.set(as_array, services); + } + + void onSetMaterial(const video::SMaterial& material) override + { + m_emissive_color = material.EmissiveColor; + } + +private: + video::SColor m_emissive_color; + CachedPixelShaderSetting m_emissive_color_setting; +}; + +class CAOShaderConstantSetterFactory : public IShaderConstantSetterFactory +{ +public: + CAOShaderConstantSetterFactory() + {} + + virtual IShaderConstantSetter* create() + { + return new CAOShaderConstantSetter(); + } +}; + /* ClientEnvironment */ @@ -47,6 +102,8 @@ ClientEnvironment::ClientEnvironment(ClientMap *map, m_texturesource(texturesource), m_client(client) { + auto *shdrsrc = m_client->getShaderSource(); + shdrsrc->addShaderConstantSetterFactory(new CAOShaderConstantSetterFactory()); } ClientEnvironment::~ClientEnvironment() diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index c76f05bbb..3b203bc07 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -46,6 +46,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "wieldmesh.h" #include #include +#include "client/shader.h" class Settings; struct ToolCapabilities; @@ -352,6 +353,8 @@ void GenericCAO::initialize(const std::string &data) player->setCAO(this); } } + + m_enable_shaders = g_settings->getBool("enable_shaders"); } void GenericCAO::processInitData(const std::string &data) @@ -577,8 +580,17 @@ void GenericCAO::addToScene(ITextureSource *tsrc) return; } - video::E_MATERIAL_TYPE material_type = (m_prop.use_texture_alpha) ? - video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + if (m_enable_shaders) { + IShaderSource *shader_source = m_client->getShaderSource(); + u32 shader_id = shader_source->getShader( + "object_shader", + TILE_MATERIAL_BASIC, + NDT_NORMAL); + m_material_type = shader_source->getShaderInfo(shader_id).material; + } else { + m_material_type = (m_prop.use_texture_alpha) ? + video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + } auto grabMatrixNode = [this] { infostream << "GenericCAO::addToScene(): " << m_prop.visual << std::endl; @@ -587,6 +599,18 @@ void GenericCAO::addToScene(ITextureSource *tsrc) m_matrixnode->grab(); }; + auto setSceneNodeMaterial = [this] (scene::ISceneNode *node) { + node->setMaterialFlag(video::EMF_LIGHTING, false); + node->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); + node->setMaterialFlag(video::EMF_FOG_ENABLE, true); + node->setMaterialType(m_material_type); + + if (m_enable_shaders) { + node->setMaterialFlag(video::EMF_GOURAUD_SHADING, false); + node->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, true); + } + }; + if (m_prop.visual == "sprite") { grabMatrixNode(); m_spritenode = RenderingEngine::get_scene_manager()->addBillboardSceneNode( @@ -594,10 +618,9 @@ void GenericCAO::addToScene(ITextureSource *tsrc) m_spritenode->grab(); m_spritenode->setMaterialTexture(0, tsrc->getTextureForMesh("unknown_node.png")); - m_spritenode->setMaterialFlag(video::EMF_LIGHTING, false); - m_spritenode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); - m_spritenode->setMaterialType(material_type); - m_spritenode->setMaterialFlag(video::EMF_FOG_ENABLE, true); + + setSceneNodeMaterial(m_spritenode); + u8 li = m_last_light; m_spritenode->setColor(video::SColor(255,li,li,li)); m_spritenode->setSize(v2f(m_prop.visual_size.X, @@ -619,10 +642,10 @@ void GenericCAO::addToScene(ITextureSource *tsrc) { // Front scene::IMeshBuffer *buf = new scene::SMeshBuffer(); video::S3DVertex vertices[4] = { - video::S3DVertex(-dx, -dy, 0, 0,0,0, c, 1,1), - video::S3DVertex( dx, -dy, 0, 0,0,0, c, 0,1), - video::S3DVertex( dx, dy, 0, 0,0,0, c, 0,0), - video::S3DVertex(-dx, dy, 0, 0,0,0, c, 1,0), + video::S3DVertex(-dx, -dy, 0, 0,0,1, c, 1,1), + video::S3DVertex( dx, -dy, 0, 0,0,1, c, 0,1), + video::S3DVertex( dx, dy, 0, 0,0,1, c, 0,0), + video::S3DVertex(-dx, dy, 0, 0,0,1, c, 1,0), }; if (m_is_player) { // Move minimal Y position to 0 (feet position) @@ -635,7 +658,14 @@ void GenericCAO::addToScene(ITextureSource *tsrc) buf->getMaterial().setFlag(video::EMF_LIGHTING, false); buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); - buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + buf->getMaterial().MaterialType = m_material_type; + + if (m_enable_shaders) { + buf->getMaterial().EmissiveColor = c; + buf->getMaterial().setFlag(video::EMF_GOURAUD_SHADING, false); + buf->getMaterial().setFlag(video::EMF_NORMALIZE_NORMALS, true); + } + // Add to mesh mesh->addMeshBuffer(buf); buf->drop(); @@ -643,10 +673,10 @@ void GenericCAO::addToScene(ITextureSource *tsrc) { // Back scene::IMeshBuffer *buf = new scene::SMeshBuffer(); video::S3DVertex vertices[4] = { - video::S3DVertex( dx,-dy, 0, 0,0,0, c, 1,1), - video::S3DVertex(-dx,-dy, 0, 0,0,0, c, 0,1), - video::S3DVertex(-dx, dy, 0, 0,0,0, c, 0,0), - video::S3DVertex( dx, dy, 0, 0,0,0, c, 1,0), + video::S3DVertex( dx,-dy, 0, 0,0,-1, c, 1,1), + video::S3DVertex(-dx,-dy, 0, 0,0,-1, c, 0,1), + video::S3DVertex(-dx, dy, 0, 0,0,-1, c, 0,0), + video::S3DVertex( dx, dy, 0, 0,0,-1, c, 1,0), }; if (m_is_player) { // Move minimal Y position to 0 (feet position) @@ -659,7 +689,14 @@ void GenericCAO::addToScene(ITextureSource *tsrc) buf->getMaterial().setFlag(video::EMF_LIGHTING, false); buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); - buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + buf->getMaterial().MaterialType = m_material_type; + + if (m_enable_shaders) { + buf->getMaterial().EmissiveColor = c; + buf->getMaterial().setFlag(video::EMF_GOURAUD_SHADING, false); + buf->getMaterial().setFlag(video::EMF_NORMALIZE_NORMALS, true); + } + // Add to mesh mesh->addMeshBuffer(buf); buf->drop(); @@ -683,10 +720,7 @@ void GenericCAO::addToScene(ITextureSource *tsrc) u8 li = m_last_light; setMeshColor(m_meshnode->getMesh(), video::SColor(255,li,li,li)); - m_meshnode->setMaterialFlag(video::EMF_LIGHTING, false); - m_meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); - m_meshnode->setMaterialType(material_type); - m_meshnode->setMaterialFlag(video::EMF_FOG_ENABLE, true); + setSceneNodeMaterial(m_meshnode); } else if (m_prop.visual == "mesh") { grabMatrixNode(); scene::IAnimatedMesh *mesh = m_client->getMesh(m_prop.mesh, true); @@ -704,10 +738,8 @@ void GenericCAO::addToScene(ITextureSource *tsrc) setAnimatedMeshColor(m_animated_meshnode, video::SColor(255,li,li,li)); - m_animated_meshnode->setMaterialFlag(video::EMF_LIGHTING, true); - m_animated_meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); - m_animated_meshnode->setMaterialType(material_type); - m_animated_meshnode->setMaterialFlag(video::EMF_FOG_ENABLE, true); + setSceneNodeMaterial(m_animated_meshnode); + m_animated_meshnode->setMaterialFlag(video::EMF_BACK_FACE_CULLING, m_prop.backface_culling); } else @@ -789,17 +821,41 @@ void GenericCAO::updateLightNoCheck(u8 light_at_pos) return; u8 li = decode_light(light_at_pos + m_glow); + if (li != m_last_light) { m_last_light = li; video::SColor color(255,li,li,li); - if (m_meshnode) { - setMeshColor(m_meshnode->getMesh(), color); - } else if (m_animated_meshnode) { - setAnimatedMeshColor(m_animated_meshnode, color); - } else if (m_wield_meshnode) { - m_wield_meshnode->setColor(color); - } else if (m_spritenode) { - m_spritenode->setColor(color); + + if (m_enable_shaders) { + scene::ISceneNode *node = getSceneNode(); + + if (node == nullptr) { + return; + } + + if (m_prop.visual == "upright_sprite") { + scene::IMesh *mesh = m_meshnode->getMesh(); + for (u32 i = 0; i < mesh->getMeshBufferCount(); ++i) { + scene::IMeshBuffer* buf = mesh->getMeshBuffer(i); + video::SMaterial& material = buf->getMaterial(); + material.EmissiveColor = color; + } + } else { + for (u32 i = 0; i < node->getMaterialCount(); ++i) { + video::SMaterial& material = node->getMaterial(i); + material.EmissiveColor = color; + } + } + } else { + if (m_meshnode) { + setMeshColor(m_meshnode->getMesh(), color); + } else if (m_animated_meshnode) { + setAnimatedMeshColor(m_animated_meshnode, color); + } else if (m_wield_meshnode) { + m_wield_meshnode->setColor(color); + } else if (m_spritenode) { + m_spritenode->setColor(color); + } } } } @@ -1101,16 +1157,13 @@ void GenericCAO::updateTextures(std::string mod) m_current_texture_modifier = mod; m_glow = m_prop.glow; - video::E_MATERIAL_TYPE material_type = (m_prop.use_texture_alpha) ? - video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; - if (m_spritenode) { if (m_prop.visual == "sprite") { std::string texturestring = "unknown_node.png"; if (!m_prop.textures.empty()) texturestring = m_prop.textures[0]; texturestring += mod; - m_spritenode->getMaterial(0).MaterialType = material_type; + m_spritenode->getMaterial(0).MaterialType = m_material_type; m_spritenode->getMaterial(0).MaterialTypeParam = 0.5f; m_spritenode->setMaterialTexture(0, tsrc->getTextureForMesh(texturestring)); @@ -1146,7 +1199,7 @@ void GenericCAO::updateTextures(std::string mod) // Set material flags and texture video::SMaterial& material = m_animated_meshnode->getMaterial(i); - material.MaterialType = material_type; + material.MaterialType = m_material_type; material.MaterialTypeParam = 0.5f; material.TextureLayer[0].Texture = texture; material.setFlag(video::EMF_LIGHTING, true); @@ -1193,7 +1246,7 @@ void GenericCAO::updateTextures(std::string mod) // Set material flags and texture video::SMaterial& material = m_meshnode->getMaterial(i); - material.MaterialType = material_type; + material.MaterialType = m_material_type; material.MaterialTypeParam = 0.5f; material.setFlag(video::EMF_LIGHTING, false); material.setFlag(video::EMF_BILINEAR_FILTER, false); diff --git a/src/client/content_cao.h b/src/client/content_cao.h index 6f3b2f06b..a1098305a 100644 --- a/src/client/content_cao.h +++ b/src/client/content_cao.h @@ -125,6 +125,10 @@ private: u8 m_last_light = 255; bool m_is_visible = false; s8 m_glow = 0; + // Material + video::E_MATERIAL_TYPE m_material_type; + // Settings + bool m_enable_shaders = false; public: GenericCAO(Client *client, ClientEnvironment *env); diff --git a/src/client/shader.cpp b/src/client/shader.cpp index f36ff3d85..eda415ce6 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -188,7 +188,7 @@ public: delete setter; } - virtual void OnSetConstants(video::IMaterialRendererServices *services, s32 userData) + virtual void OnSetConstants(video::IMaterialRendererServices *services, s32 userData) override { video::IVideoDriver *driver = services->getVideoDriver(); sanity_check(driver != NULL); @@ -198,6 +198,12 @@ public: for (IShaderConstantSetter *setter : m_setters) setter->onSetConstants(services, is_highlevel); } + + virtual void OnSetMaterial(const video::SMaterial& material) override + { + for (IShaderConstantSetter *setter : m_setters) + setter->onSetMaterial(material); + } }; diff --git a/src/client/shader.h b/src/client/shader.h index 583c776f4..109d39336 100644 --- a/src/client/shader.h +++ b/src/client/shader.h @@ -67,6 +67,8 @@ public: virtual ~IShaderConstantSetter() = default; virtual void onSetConstants(video::IMaterialRendererServices *services, bool is_highlevel) = 0; + virtual void onSetMaterial(const video::SMaterial& material) + { } };