Browse Source

Mark light masks to G-buffer stencil for light culling.
Fixed OpenGL forward rendering.
Documentation fixes.

Lasse Öörni 14 years ago
parent
commit
644c37a099

+ 4 - 4
CMakeLists.txt

@@ -25,11 +25,11 @@ add_definitions (-DENABLE_PROFILING)
 # If not on Windows, enable use of OpenGL instead of Direct3D9 (so called "Turso3D" mode.) Setting
 # this on Windows is not recommended, as graphics card drivers are usually better optimized for
 # Direct3D.
-if (NOT WIN32)
+#if (NOT WIN32)
     set (USE_OPENGL 1)
     add_definitions (-DUSE_OPENGL)
-    add_definitions (-DUNIX)
-endif ()
+#    add_definitions (-DUNIX)
+#endif ()
 
 # Compiler-specific options
 if (MSVC)
@@ -97,7 +97,7 @@ if (USE_OPENGL)
         add_custom_command (
             OUTPUT ../../Bin/CoreData/Shaders/GLSL/${NAME}.vert
             COMMAND ../../Bin/GLShaderProcessor ${NAME}.xml ../../Bin/CoreData/Shaders/GLSL
-            DEPENDS GLShaderProcessor ${NAME}.vert ${NAME}.frag Uniforms.vert Transform.vert Uniforms.frag Samplers.frag Lighting.frag Fog.frag ${NAME}.xml
+            DEPENDS GLShaderProcessor ${NAME}.vert ${NAME}.frag Uniforms.vert Transform.vert ScreenPos.vert Uniforms.frag Samplers.frag Lighting.frag Fog.frag ${NAME}.xml
         )
         
         set (ALL_SHADERS ${ALL_SHADERS} ../../Bin/CoreData/Shaders/GLSL/${NAME}.vert)

+ 3 - 3
Docs/Reference.dox

@@ -433,7 +433,7 @@ The following techniques will be used to reduce the amount of CPU and GPU work w
 
 - Hardware instancing (Direct3D9 SM3.0 only): rendering operations with the same geometry, material and light will be grouped together and performed as one draw call. Objects with a large amount of triangles will not be rendered as instanced, as that could actually be detrimental to performance. Use \ref Renderer::SetMaxInstanceTriangles() "SetMaxInstanceTriangles()" to set the threshold. Note that even when instancing is not available, or the triangle count of objects is too large, they still benefit from the grouping, as render state only needs to be set once before rendering each group, reducing the CPU cost.
 
-- %Light stencil masking: before rendering objects lit by a spot or point light, the light's bounding shape is rendered to the stencil buffer to ensure pixels outside the light range are not processed.
+- %Light stencil masking: in forward rendering, before objects lit by a spot or point light are re-rendered additively, the light's bounding shape is rendered to the stencil buffer to ensure pixels outside the light range are not processed.
 
 Note that many more optimization opportunities are possible at the content level, for example using geometry & material LOD, grouping many static objects into one object for less draw calls, minimizing the amount of subgeometries (submeshes) per object for less draw calls, using texture atlases to avoid render state changes, using compressed (and smaller) textures, and setting maximum draw distances for objects, lights and shadows.
 
@@ -594,9 +594,9 @@ For an example of shadow culling, imagine a house (which itself is a shadow cast
 
 The Renderer can be configured to either reuse shadow maps, or not. To reuse is the default, use \ref Renderer::SetReuseShadowMaps "SetReuseShadowMaps()" to change.
 
-When reuse is enabled, only one shadow texture of each shadow map size (full, half and quarter) needs to be reserved, and shadow maps are rendered "on the fly" before rendering a single shadowed light's contribution onto opaque geometry. This has the downside that shadow maps are no longer available during transparent geometry rendering, so transparent objects will not receive shadows.
+When reuse is enabled, only one shadow texture of each shadow map size needs to be reserved, and shadow maps are rendered "on the fly" before rendering a single shadowed light's contribution onto opaque geometry. This has the downside that shadow maps are no longer available during transparent geometry rendering, so transparent objects will not receive shadows.
 
-When reuse is disabled, all shadow maps are rendered before the actual scene rendering. Now multiple shadow textures need to be reserved based on the desired number of simultaneous shadow casting lights. See the function \ref Renderer::SetNumShadowMaps "SetNumShadowMaps()". If there are not enough shadow textures, they will be assigned to the closest/brightest lights, and the rest will be rendered unshadowed. Now more texture memory is needed, but the advantage is that also transparent objects can receive shadows. The exception is shadowed point lights: they need stencil masking to split into the 6 shadow map sides, which conflicts with the need to render transparent objects back-to-front, instead of rendering per light.
+When reuse is disabled, all shadow maps are rendered before the actual scene rendering. Now multiple shadow textures need to be reserved based on the number of simultaneous shadow casting lights. See the function \ref Renderer::SetNumShadowMaps "SetNumShadowMaps()". If there are not enough shadow textures, they will be assigned to the closest/brightest lights, and the rest will be rendered unshadowed. Now more texture memory is needed, but the advantage is that also transparent objects can receive shadows.
 
 
 \page Particles %Particle systems

+ 0 - 1
Docs/ScriptAPI.dox

@@ -2125,7 +2125,6 @@ Properties:<br>
 - int maxShadowCascades
 - int maxShadowMaps
 - bool reuseShadowMaps
-- bool lightStencilMasking
 - bool dynamicInstancing
 - int maxInstanceTriangles
 - int maxOccluderTriangles

+ 2 - 0
Engine/Graphics/Batch.h

@@ -99,6 +99,8 @@ struct Batch
     bool overrideView_;
     /// Base batch flag. This tells to draw the object fully without light optimizations.
     bool isBase_;
+    /// 8-bit light mask for stencil marking in light pre-pass rendering.
+    unsigned char lightMask_;
 };
 
 /// Data for one geometry instance.

+ 18 - 6
Engine/Graphics/View.cpp

@@ -715,7 +715,9 @@ void View::GetBatches()
                     pass = tech->GetPass(PASS_GBUFFER);
                     if (pass)
                     {
-                        FinalizeBatch(baseBatch, tech, pass);
+                        // Allow G-buffer pass instancing only if lightmask matches zone lightmask
+                        baseBatch.lightMask_ = GetLightMask(drawable);
+                        FinalizeBatch(baseBatch, tech, pass, baseBatch.lightMask_ == (baseBatch.zone_->GetLightMask() & 0xff));
                         gbufferQueue_.AddBatch(baseBatch);
                         
                         pass = tech->GetPass(PASS_MATERIAL);
@@ -2042,24 +2044,28 @@ void View::RenderBatchQueue(const BatchQueue& queue, bool useScissor)
 {
     graphics_->SetScissorTest(false);
     
-    // During G-buffer rendering, mark opaque pixels to scissor
-    /// \todo Use objects' light masks
-    if (&queue != &gbufferQueue_)
+    // During G-buffer rendering, mark opaque pixels to stencil buffer
+    bool isGBuffer = &queue == &gbufferQueue_;
+    if (!isGBuffer)
         graphics_->SetStencilTest(false);
-    else
-        graphics_->SetStencilTest(true, CMP_ALWAYS, OP_REF, OP_KEEP, OP_KEEP, 0xff);
     
     // Base instanced
     for (PODVector<BatchGroup*>::ConstIterator i = queue.sortedBaseBatchGroups_.Begin(); i !=
         queue.sortedBaseBatchGroups_.End(); ++i)
     {
         BatchGroup* group = *i;
+        if (isGBuffer)
+            graphics_->SetStencilTest(true, CMP_ALWAYS, OP_REF, OP_KEEP, OP_KEEP, group->lightMask_);
+        
         group->Draw(graphics_, renderer_);
     }
     // Base non-instanced
     for (PODVector<Batch*>::ConstIterator i = queue.sortedBaseBatches_.Begin(); i != queue.sortedBaseBatches_.End(); ++i)
     {
         Batch* batch = *i;
+        if (isGBuffer)
+            graphics_->SetStencilTest(true, CMP_ALWAYS, OP_REF, OP_KEEP, OP_KEEP, batch->lightMask_);
+        
         batch->Draw(graphics_, renderer_);
     }
     
@@ -2069,6 +2075,9 @@ void View::RenderBatchQueue(const BatchQueue& queue, bool useScissor)
         BatchGroup* group = *i;
         if (useScissor && group->lightQueue_)
             OptimizeLightByScissor(group->lightQueue_->light_);
+        if (isGBuffer)
+            graphics_->SetStencilTest(true, CMP_ALWAYS, OP_REF, OP_KEEP, OP_KEEP, group->lightMask_);
+        
         group->Draw(graphics_, renderer_);
     }
     // Non-base non-instanced
@@ -2082,6 +2091,9 @@ void View::RenderBatchQueue(const BatchQueue& queue, bool useScissor)
             else
                 graphics_->SetScissorTest(false);
         }
+        if (isGBuffer)
+            graphics_->SetStencilTest(true, CMP_ALWAYS, OP_REF, OP_KEEP, OP_KEEP, batch->lightMask_);
+        
         batch->Draw(graphics_, renderer_);
     }
 }

+ 3 - 4
SourceAssets/GLSLShaders/Ambient.frag

@@ -3,7 +3,7 @@
 #include "Fog.frag"
 #include "Lighting.frag"
 
-varying vec4 vTexCoord;
+varying vec3 vTexCoord;
 #ifdef VERTEXCOLOR
     varying vec4 vColor;
 #endif
@@ -21,7 +21,6 @@ void main()
         diffColor *= vColor;
     #endif
 
-    vec3 finalColor = (GetAmbient(vTexCoord.z) + vVertexLighting) * diffColor.rgb;
-
-    gl_FragColor = vec4(GetFog(finalColor, vTexCoord.w), diffColor.a);
+    vec3 finalColor = vVertexLighting * diffColor.rgb;
+    gl_FragColor = vec4(GetFog(finalColor, vTexCoord.z), diffColor.a);
 }

+ 3 - 3
SourceAssets/GLSLShaders/Ambient.vert

@@ -2,7 +2,7 @@
 #include "Transform.vert"
 #include "Lighting.vert"
 
-varying vec4 vTexCoord;
+varying vec3 vTexCoord;
 #ifdef VERTEXCOLOR
     varying vec4 vColor;
 #endif
@@ -13,9 +13,9 @@ void main()
     mat4 modelMatrix = iModelMatrix;
     vec3 worldPos = GetWorldPos(modelMatrix);
     gl_Position = GetClipPos(worldPos);
-    vTexCoord = vec4(GetTexCoord(iTexCoord), GetZonePos(worldPos), GetDepth(gl_Position));
+    vTexCoord = vec3(GetTexCoord(iTexCoord), GetDepth(gl_Position));
 
-    vVertexLighting = vec3(0.0, 0.0, 0.0);
+    vVertexLighting = GetAmbient(GetZonePos(worldPos));
     #ifdef NUMVERTEXLIGHTS
     vec3 normal = GetWorldNormal(modelMatrix);
     for (int i = 0; i < NUMVERTEXLIGHTS; ++i)

+ 2 - 2
SourceAssets/GLSLShaders/CMakeLists.txt

@@ -2,10 +2,10 @@ set (ALL_SHADERS)
 
 add_shader (Ambient)
 add_shader (Basic)
-add_shader (BlinnPhong)
+add_shader (ForwardLit)
+add_shader (LitParticle)
 add_shader (Shadow)
 add_shader (Stencil)
 add_shader (Unlit)
-add_shader (Volumetric)
 
 add_custom_target (Shaders ALL DEPENDS ${ALL_SHADERS})

+ 4 - 9
SourceAssets/GLSLShaders/BlinnPhong.frag → SourceAssets/GLSLShaders/ForwardLit.frag

@@ -3,7 +3,7 @@
 #include "Lighting.frag"
 #include "Fog.frag"
 
-varying vec4 vTexCoord;
+varying vec3 vTexCoord;
 #ifdef VERTEXCOLOR
     varying vec4 vColor;
 #endif
@@ -66,8 +66,8 @@ void main()
 
     #ifdef SHADOW
         #if defined(DIRLIGHT)
-            vec4 shadowPos = GetDirShadowPos(vShadowPos, vTexCoord.w);
-            diff *= min(GetShadow(shadowPos) + GetShadowFade(vTexCoord.w), 1.0);
+            vec4 shadowPos = GetDirShadowPos(vShadowPos, vTexCoord.z);
+            diff *= min(GetShadow(shadowPos) + GetShadowFade(vTexCoord.z), 1.0);
         #elif defined(SPOTLIGHT)
             diff *= GetShadow(vShadowPos);
         #else
@@ -95,10 +95,5 @@ void main()
         finalColor = diff * lightColor * diffColor.rgb;
     #endif
 
-    #ifdef AMBIENT
-        finalColor += GetAmbient(vTexCoord.z) * diffColor.rgb;
-        gl_FragColor = vec4(GetFog(finalColor, vTexCoord.w), diffColor.a);
-    #else
-        gl_FragColor = vec4(GetLitFog(finalColor, vTexCoord.w), diffColor.a);
-    #endif
+    gl_FragColor = vec4(GetLitFog(finalColor, vTexCoord.z), diffColor.a);
 }

+ 9 - 9
SourceAssets/GLSLShaders/BlinnPhong.vert → SourceAssets/GLSLShaders/ForwardLit.vert

@@ -1,7 +1,7 @@
 #include "Uniforms.vert"
 #include "Transform.vert"
 
-varying vec4 vTexCoord;
+varying vec3 vTexCoord;
 #ifdef VERTEXCOLOR
     varying vec4 vColor;
 #endif
@@ -33,7 +33,7 @@ void main()
     mat4 modelMatrix = iModelMatrix;
     vec3 worldPos = GetWorldPos(modelMatrix);
     gl_Position = GetClipPos(worldPos);
-    vTexCoord = vec4(GetTexCoord(iTexCoord), GetZonePos(worldPos), GetDepth(gl_Position));
+    vTexCoord = vec3(GetTexCoord(iTexCoord), GetDepth(gl_Position));
 
     #ifdef VERTEXCOLOR
         vColor = iColor;
@@ -57,12 +57,12 @@ void main()
     #ifdef SHADOW
         // Shadow projection: transform from world space to shadow space
         #if defined(DIRLIGHT)
-            vShadowPos[0] = cShadowProj[0] * projWorldPos;
-            vShadowPos[1] = cShadowProj[1] * projWorldPos;
-            vShadowPos[2] = cShadowProj[2] * projWorldPos;
-            vShadowPos[3] = cShadowProj[3] * projWorldPos;
+            vShadowPos[0] = cLightMatrices[0] * projWorldPos;
+            vShadowPos[1] = cLightMatrices[1] * projWorldPos;
+            vShadowPos[2] = cLightMatrices[2] * projWorldPos;
+            vShadowPos[3] = cLightMatrices[3] * projWorldPos;
         #elif defined(SPOTLIGHT)
-            vShadowPos = cShadowProj[0] * projWorldPos;
+            vShadowPos = cLightMatrices[1] * projWorldPos;
         #else
             vShadowPos = worldPos - cLightPos.xyz;
         #endif
@@ -70,11 +70,11 @@ void main()
 
     #ifdef SPOTLIGHT
         // Spotlight projection: transform from world space to projector texture coordinates
-        vSpotPos = cSpotProj * projWorldPos;
+        vSpotPos = cLightMatrices[0] * projWorldPos;
     #endif
 
     #ifdef POINTLIGHT
-        vCubeMaskVec = cLightVecRot * vLightVec;
+        vCubeMaskVec = mat3(cLightMatrices[0][0].xyz, cLightMatrices[0][1].xyz, cLightMatrices[0][2].xyz) * vLightVec;
     #endif
 
     #ifdef NORMALMAP

+ 2 - 3
SourceAssets/GLSLShaders/BlinnPhong.xml → SourceAssets/GLSLShaders/ForwardLit.xml

@@ -1,5 +1,5 @@
 <shaders>
-    <shader name="BlinnPhong" type="vs">
+    <shader name="ForwardLit" type="vs">
         <option name="Normal" define="NORMALMAP" />
         <option name="VCol" define="VERTEXCOLOR" />
         <variation name="Dir" define="DIRLIGHT" />
@@ -12,12 +12,11 @@
         <variation name="Instanced" define="INSTANCED" />
         <variation name="Billboard" define="BILLBOARD" />
     </shader>
-    <shader name="BlinnPhong" type="ps">
+    <shader name="ForwardLit" type="ps">
         <option name="Diff" define="DIFFMAP" />
         <option name="Normal" define="NORMALMAP" require="DIFFMAP" />
         <option name="SpecMap" define="SPECMAP" require="DIFFMAP" />
         <option name="VCol" define="VERTEXCOLOR" />
-        <option name="Ambient" define="AMBIENT" />
         <variation name="Dir" define="DIRLIGHT" />
         <variation name="Spot" define="SPOTLIGHT" />
         <variation name="Point" define="POINTLIGHT" />

+ 0 - 5
SourceAssets/GLSLShaders/Lighting.frag

@@ -27,11 +27,6 @@ float GetSpecular(vec3 normal, vec3 eyeVec, vec3 lightDir, float specularPower)
     return pow(max(dot(normal, halfVec), 0.0), specularPower);
 }
 
-vec3 GetAmbient(float zonePos)
-{
-    return cAmbientStartColor + zonePos * cAmbientEndColor;
-}
-
 float GetShadow(vec4 shadowPos)
 {
     // Note: in case of sampling a point light cube shadow, we optimize out the w divide as it has already been performed

+ 39 - 18
SourceAssets/GLSLShaders/Lighting.vert

@@ -7,21 +7,42 @@ float GetVertexLight(int index, vec3 worldPos, vec3 normal)
     float invCutoff = cVertexLights[index * 3 + 2].w;
 
     // Directional light
-    if (invRange == 0.0)
-    {
-        float NdotL = max(dot(normal, lightDir), 0.0);
-        return NdotL;
-    }
-    // Point/spot light
-    else
-    {
-        vec3 lightVec = (lightPos - worldPos) * invRange;
-        float lightDist = length(lightVec);
-        vec3 localDir = lightVec / lightDist;
-        float NdotL = max(dot(normal, localDir), 0.0);
-        float atten = clamp(1.0 - lightDist * lightDist, 0.0, 1.0);
-        float spotEffect = dot(localDir, lightDir);
-        float spotAtten = clamp((spotEffect - cutoff) * invCutoff, 0.0, 1.0);
-        return NdotL * atten * spotAtten;
-    }
-}
+    #ifndef BILLBOARD
+        if (invRange == 0.0)
+        {
+            float NdotL = max(dot(normal, lightDir), 0.0);
+            return NdotL;
+        }
+        // Point/spot light
+        else
+        {
+            vec3 lightVec = (lightPos - worldPos) * invRange;
+            float lightDist = length(lightVec);
+            vec3 localDir = lightVec / lightDist;
+            float NdotL = max(dot(normal, localDir), 0.0);
+            float atten = clamp(1.0 - lightDist * lightDist, 0.0, 1.0);
+            float spotEffect = dot(localDir, lightDir);
+            float spotAtten = clamp((spotEffect - cutoff) * invCutoff, 0.0, 1.0);
+            return NdotL * atten * spotAtten;
+        }
+    #else
+        if (invRange == 0.0)
+            return 1.0;
+        // Point/spot light
+        else
+        {
+            vec3 lightVec = (lightPos - worldPos) * invRange;
+            float lightDist = length(lightVec);
+            vec3 localDir = lightVec / lightDist;
+            float atten = clamp(1.0 - lightDist * lightDist, 0.0, 1.0);
+            float spotEffect = dot(localDir, lightDir);
+            float spotAtten = clamp((spotEffect - cutoff) * invCutoff, 0.0, 1.0);
+            return atten * spotAtten;
+        }
+    #endif
+}
+
+vec3 GetAmbient(float zonePos)
+{
+    return cAmbientStartColor + zonePos * cAmbientEndColor;
+}

+ 0 - 0
SourceAssets/GLSLShaders/Volumetric.frag → SourceAssets/GLSLShaders/LitParticle.frag


+ 2 - 2
SourceAssets/GLSLShaders/Volumetric.vert → SourceAssets/GLSLShaders/LitParticle.vert

@@ -37,10 +37,10 @@ void main()
 
     #ifdef SPOTLIGHT
         // Spotlight projection: transform from world space to projector texture coordinates
-        vSpotPos = cSpotProj * projWorldPos;
+        vSpotPos = cLightMatrices[0] * projWorldPos;
     #endif
 
     #ifdef POINTLIGHT
-        vCubeMaskVec = cLightVecRot * vLightVec.xyz;
+        vCubeMaskVec = mat3(cLightMatrices[0][0].xyz, cLightMatrices[0][1].xyz, cLightMatrices[0][2].xyz) * vLightVec;
     #endif
 }

+ 2 - 2
SourceAssets/GLSLShaders/Volumetric.xml → SourceAssets/GLSLShaders/LitParticle.xml

@@ -1,5 +1,5 @@
 <shaders>
-    <shader name="Volumetric" type="vs">
+    <shader name="LitParticle" type="vs">
         <option name="VCol" define="VERTEXCOLOR" />
         <variation name="Dir" define="DIRLIGHT" />
         <variation name="Spot" define="SPOTLIGHT" />
@@ -10,7 +10,7 @@
         <variation name="Instanced" define="INSTANCED" />
         <variation name="Billboard" define="BILLBOARD" />
     </shader>
-    <shader name="Volumetric" type="ps">
+    <shader name="LitParticle" type="ps">
         <option name="Diff" define="DIFFMAP" />
         <option name="VCol" define="VERTEXCOLOR" />
         <option name="Ambient" define="AMBIENT" />

+ 3 - 2
SourceAssets/GLSLShaders/Uniforms.frag

@@ -1,8 +1,8 @@
-uniform vec3 cAmbientStartColor;
-uniform vec3 cAmbientEndColor;
 uniform vec4 cFogParams;
 uniform vec3 cFogColor;
 uniform vec4 cLightColor;
+uniform vec3 cLightDirPS;
+uniform vec4 cLightPosPS;
 uniform vec4 cMatDiffColor;
 uniform vec3 cMatEmissiveColor;
 uniform vec2 cMatSpecProperties;
@@ -11,3 +11,4 @@ uniform vec4 cShadowCubeAdjust;
 uniform vec4 cShadowDepthFade;
 uniform vec4 cShadowIntensity;
 uniform vec4 cShadowSplits;
+uniform mat4 cLightMatricesPS[4];

+ 3 - 3
SourceAssets/GLSLShaders/Uniforms.vert

@@ -1,17 +1,17 @@
+uniform vec3 cAmbientStartColor;
+uniform vec3 cAmbientEndColor;
 uniform vec3 cCameraPos;
 uniform mat3 cCameraRot;
 uniform vec4 cDepthMode;
 uniform vec3 cLightDir;
 uniform vec4 cLightPos;
-uniform mat3 cLightVecRot;
 uniform mat4 cModel;
-uniform mat4 cSpotProj;
 uniform mat4 cViewProj;
 uniform vec4 cUOffset;
 uniform vec4 cVOffset;
 uniform vec3 cViewRightVector;
 uniform vec3 cViewUpVector;
 uniform mat4 cZone;
-uniform mat4 cShadowProj[4];
+uniform mat4 cLightMatrices[4];
 uniform vec4 cSkinMatrices[64*3];
 uniform vec4 cVertexLights[6*3];

+ 4 - 3
SourceAssets/HLSLShaders/LightVolume.hlsl

@@ -103,6 +103,7 @@ void PS(
         #endif
     #endif
 
+    float4 projWorldPos = float4(worldPos, 1.0);
     float3 lightColor;
     float3 lightDir;
     float diff;
@@ -119,10 +120,10 @@ void PS(
     #ifdef SHADOW
         #if defined(DIRLIGHT)
             float4x4 shadowMatrix = GetDirShadowMatrix(depth, cLightMatricesPS);
-            float4 shadowPos = mul(float4(worldPos, 1.0), shadowMatrix);
+            float4 shadowPos = mul(projWorldPos, shadowMatrix);
             diff *= saturate(GetShadow(shadowPos) + GetShadowFade(depth));
         #elif defined(SPOTLIGHT)
-            float4 shadowPos = mul(float4(worldPos, 1.0), cLightMatricesPS[1]);
+            float4 shadowPos = mul(projWorldPos, cLightMatricesPS[1]);
             diff *= GetShadow(shadowPos);
         #else
             float3 shadowPos = worldPos - cLightPosPS.xyz;
@@ -131,7 +132,7 @@ void PS(
     #endif
 
     #ifdef SPOTLIGHT
-        float4 spotPos = mul(float4(worldPos, 1.0), cLightMatricesPS[0]);
+        float4 spotPos = mul(projWorldPos, cLightMatricesPS[0]);
         lightColor = spotPos.w > 0.0 ? tex2Dproj(sLightSpotMap, spotPos).rgb * cLightColor.rgb : 0.0;
     #else
         #ifdef CUBEMASK