diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 2bd231bcdc23ecc71a3cb973d577c23fcecdd448..e7aa884f6bee0331ebe817c394d11ce4737af912 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -187,6 +187,7 @@ set(viewer_SOURCE_FILES
     lldrawpoolbump.cpp
     lldrawpoolground.cpp
     lldrawpoolmaterials.cpp
+    lldrawpoolpbropaque.cpp
     lldrawpoolsimple.cpp
     lldrawpoolsky.cpp
     lldrawpoolterrain.cpp
@@ -824,6 +825,7 @@ set(viewer_HEADER_FILES
     lldrawpoolavatar.h
     lldrawpoolbump.h
     lldrawpoolmaterials.h
+    lldrawpoolpbropaque.h
     lldrawpoolground.h
     lldrawpoolsimple.h
     lldrawpoolsky.h
diff --git a/indra/newview/app_settings/shaders/class1/deferred/pbropaqueF.glsl b/indra/newview/app_settings/shaders/class1/deferred/pbropaqueF.glsl
new file mode 100644
index 0000000000000000000000000000000000000000..cb9cc4958ad126392980af4892f9bab25fd5c3e0
--- /dev/null
+++ b/indra/newview/app_settings/shaders/class1/deferred/pbropaqueF.glsl
@@ -0,0 +1,109 @@
+/** 
+ * @file pbropaqueF.glsl
+ *
+ * $LicenseInfo:firstyear=2022&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2022, Linden Research, Inc.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ * 
+ * This library 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
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+/*[EXTRA_CODE_HERE]*/
+
+#define DEBUG_BASIC         1
+#define DEBUG_COLOR         0
+#define DEBUG_NORMAL        0
+#define DEBUG_POSITION      0
+#define DEBUG_REFLECT_VEC   0
+#define DEBUG_REFLECT_COLOR 0
+
+#ifdef HAS_SPECULAR_MAP
+uniform sampler2D specularMap;
+#endif
+uniform samplerCube environmentMap;
+uniform mat3        env_mat;
+
+#ifdef DEFINE_GL_FRAGCOLOR
+out vec4 frag_data[3];
+#else
+#define frag_data gl_FragData
+#endif
+
+VARYING vec3 vary_position;
+VARYING vec3 vary_normal;
+VARYING vec4 vertex_color;
+VARYING vec2 vary_texcoord0;
+#ifdef HAS_SPECULAR_MAP
+VARYING vec2 vary_texcoord2;
+#endif
+
+vec2 encode_normal(vec3 n);
+vec3 linear_to_srgb(vec3 c);
+
+struct PBR
+{
+    float LdotH; // Light and Half
+    float NdotL; // Normal and Light
+    float NdotH; // Normal and Half
+    float VdotH; // View and Half
+};
+
+const float M_PI = 3.141592653589793;
+
+void main()
+{
+    vec3 col = vertex_color.rgb * diffuseLookup(vary_texcoord0.xy).rgb;
+
+//#ifdef HAS_SPECULAR_MAP
+//#else
+    vec4 norm  = vec4(0,0,0,1.0);
+    vec3 tnorm = vary_normal;
+//#endif
+    norm.xyz = normalize(tnorm.xyz);
+
+    vec3 spec;
+    spec.rgb = vec3(vertex_color.a);
+
+    vec3 pos = vary_position;
+    vec3 refnormpersp = normalize(reflect(pos.xyz, norm.xyz));
+    vec3 env_vec = env_mat * refnormpersp;
+    vec3 reflected_color = textureCube(environmentMap, env_vec).rgb;
+
+#if DEBUG_BASIC
+    col.rgb = vec3( 1, 0, 1 ); // DEBUG
+#endif
+#if DEBUG_COLOR
+    col.rgb = vertex_color.rgb;
+#endif
+#if DEBUG_NORMAL
+    col.rgb = vary_normal;
+#endif
+#if DEBUG_POSITION
+    col.rgb = vary_position.xyz;
+#endif
+#if DEBUG_REFLECT_VEC
+    col.rgb = refnormpersp;
+#endif
+#if DEBUG_REFLECT_COLOR
+    col.rgb = reflected_color;
+#endif
+
+    frag_data[0] = vec4(col, 0.0);
+    frag_data[1] = vec4(spec, vertex_color.a); // spec
+    frag_data[2] = vec4(encode_normal(norm.xyz), vertex_color.a, 0.0);
+}
diff --git a/indra/newview/app_settings/shaders/class1/deferred/pbropaqueV.glsl b/indra/newview/app_settings/shaders/class1/deferred/pbropaqueV.glsl
new file mode 100644
index 0000000000000000000000000000000000000000..72bae808e02734d4031a618ea9d14b6adb2864e4
--- /dev/null
+++ b/indra/newview/app_settings/shaders/class1/deferred/pbropaqueV.glsl
@@ -0,0 +1,54 @@
+/** 
+ * @file pbropaqueV.glsl
+ *
+ * $LicenseInfo:firstyear=2022&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2022, Linden Research, Inc.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ * 
+ * This library 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
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+uniform mat3 normal_matrix;
+uniform mat4 texture_matrix0;
+uniform mat4 modelview_matrix;
+uniform mat4 modelview_projection_matrix;
+
+ATTRIBUTE vec3 position;
+ATTRIBUTE vec4 diffuse_color;
+ATTRIBUTE vec3 normal;
+ATTRIBUTE vec2 texcoord0;
+
+VARYING vec3 vary_position;
+VARYING vec3 vary_normal;
+VARYING vec4 vertex_color;
+VARYING vec2 vary_texcoord0;
+
+void passTextureIndex();
+
+void main()
+{
+    //transform vertex
+    gl_Position = modelview_projection_matrix * vec4(position.xyz, 1.0);
+    vary_position = (modelview_matrix * vec4(position.xyz,1.0)).xyz;
+    vary_texcoord0 = (texture_matrix0 * vec4(texcoord0,0,1)).xy;
+
+    passTextureIndex();
+    vary_normal = normalize(normal_matrix * normal);
+
+    vertex_color = diffuse_color;
+}
diff --git a/indra/newview/lldrawpool.cpp b/indra/newview/lldrawpool.cpp
index a3837fe10c68d3758bfbdada979edbe4402d7f84..1e548141c8199a93e51e2737ca2404f58cd774ac 100644
--- a/indra/newview/lldrawpool.cpp
+++ b/indra/newview/lldrawpool.cpp
@@ -37,6 +37,7 @@
 #include "lldrawpoolbump.h"
 #include "lldrawpoolmaterials.h"
 #include "lldrawpoolground.h"
+#include "lldrawpoolpbropaque.h"
 #include "lldrawpoolsimple.h"
 #include "lldrawpoolsky.h"
 #include "lldrawpooltree.h"
@@ -117,6 +118,9 @@ LLDrawPool *LLDrawPool::createPool(const U32 type, LLViewerTexture *tex0)
 	case POOL_WL_SKY:
 		poolp = new LLDrawPoolWLSky();
 		break;
+	case POOL_PBR_OPAQUE:
+		poolp = new LLDrawPoolPBROpaque();
+		break;
 	default:
 		LL_ERRS() << "Unknown draw pool type!" << LL_ENDL;
 		return NULL;
diff --git a/indra/newview/lldrawpool.h b/indra/newview/lldrawpool.h
index fd1b022e5bf3d7b513de8f35c1b5b85a149098e0..b73ae94bbb6fc99e1253c53b29e193b7498785c1 100644
--- a/indra/newview/lldrawpool.h
+++ b/indra/newview/lldrawpool.h
@@ -67,6 +67,7 @@ class LLDrawPool
 		POOL_WATER,
 		POOL_GLOW,
 		POOL_ALPHA,
+		POOL_PBR_OPAQUE,
 		NUM_POOL_TYPES,
 		// * invisiprims work by rendering to the depth buffer but not the color buffer, occluding anything rendered after them
 		// - and the LLDrawPool types enum controls what order things are rendered in
diff --git a/indra/newview/lldrawpoolpbropaque.cpp b/indra/newview/lldrawpoolpbropaque.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..64850424f4bdde75da645f92de4139d3fad98a6a
--- /dev/null
+++ b/indra/newview/lldrawpoolpbropaque.cpp
@@ -0,0 +1,92 @@
+/** 
+ * @file lldrawpoolpbropaque.cpp
+ * @brief LLDrawPoolPBROpaque class implementation
+ *
+ * $LicenseInfo:firstyear=2022&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2022, Linden Research, Inc.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ * 
+ * This library 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
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "lldrawpool.h"
+#include "lldrawpoolpbropaque.h"
+#include "llviewershadermgr.h"
+#include "pipeline.h"
+
+LLDrawPoolPBROpaque::LLDrawPoolPBROpaque() :
+    LLRenderPass(POOL_PBR_OPAQUE)
+{
+}
+
+void LLDrawPoolPBROpaque::prerender()
+{
+    mShaderLevel = LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_OBJECT); 
+}
+
+// Forward
+void LLDrawPoolPBROpaque::beginRenderPass(S32 pass)
+{
+}
+
+void LLDrawPoolPBROpaque::endRenderPass( S32 pass )
+{
+}
+
+void LLDrawPoolPBROpaque::render(S32 pass)
+{
+}
+
+// Deferred
+void LLDrawPoolPBROpaque::beginDeferredPass(S32 pass)
+{
+    gDeferredPBROpaqueProgram.bind();
+}
+
+void LLDrawPoolPBROpaque::endDeferredPass(S32 pass)
+{
+    gDeferredPBROpaqueProgram.unbind();
+    LLRenderPass::endRenderPass(pass);
+}
+
+void LLDrawPoolPBROpaque::renderDeferred(S32 pass)
+{
+    if (!gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_MATERIALS))
+    {
+         return;
+    }
+
+    gGL.flush();
+
+    LLGLDisable blend(GL_BLEND);
+    LLGLDisable alpha_test(GL_ALPHA_TEST);
+
+    // TODO: handle HUDs?
+    //if (LLPipeline::sRenderingHUDs)
+    //    mShader->uniform1i(LLShaderMgr::NO_ATMO, 1);
+    //else
+    //    mShader->uniform1i(LLShaderMgr::NO_ATMO, 0);
+
+    // TODO: handle under water?
+    // if (LLPipeline::sUnderWaterRender)
+    // PASS_SIMPLE or PASS_MATERIAL
+    pushBatches(LLRenderPass::PASS_SIMPLE, getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX, TRUE, TRUE);
+}
+
diff --git a/indra/newview/lldrawpoolpbropaque.h b/indra/newview/lldrawpoolpbropaque.h
new file mode 100644
index 0000000000000000000000000000000000000000..ada806a3bf2bec8f41efc61d8adddc9b185f704e
--- /dev/null
+++ b/indra/newview/lldrawpoolpbropaque.h
@@ -0,0 +1,61 @@
+/** 
+ * @file lldrawpoolpbropaque.h
+ * @brief LLDrawPoolPBrOpaque class definition
+ *
+ * $LicenseInfo:firstyear=2022&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2022, Linden Research, Inc.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ * 
+ * This library 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
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLDRAWPOOLPBROPAQUE_H
+#define LL_LLDRAWPOOLPBROPAQUE_H
+
+#include "lldrawpool.h"
+
+class LLDrawPoolPBROpaque : public LLRenderPass
+{
+public:
+    enum
+    {
+       VERTEX_DATA_MASK = 0
+                        | LLVertexBuffer::MAP_VERTEX
+                        | LLVertexBuffer::MAP_NORMAL
+                        | LLVertexBuffer::MAP_TEXCOORD0
+                        | LLVertexBuffer::MAP_COLOR
+    };
+    virtual U32 getVertexDataMask() { return VERTEX_DATA_MASK; }
+
+    LLDrawPoolPBROpaque();
+
+    /*virtual*/ S32 getNumDeferredPasses() { return 1; }
+    /*virtual*/ void beginDeferredPass(S32 pass);
+    /*virtual*/ void endDeferredPass(S32 pass);
+    /*virtual*/ void renderDeferred(S32 pass);
+
+    // Non ALM isn't supported
+    /*virtual*/ void beginRenderPass(S32 pass);
+    /*virtual*/ void endRenderPass(S32 pass);
+    /*virtual*/ S32  getNumPasses() { return 0; }
+    /*virtual*/ void render(S32 pass = 0);
+    /*virtual*/ void prerender();
+
+};
+
+#endif // LL_LLDRAWPOOLPBROPAQUE_H
diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp
index 1cb2c6b9eee1b88c7b83ab47b1a67329d2c88a25..50a0ff07fc27cbff8abb11c0065b7f0cdd40dfb4 100644
--- a/indra/newview/llviewershadermgr.cpp
+++ b/indra/newview/llviewershadermgr.cpp
@@ -258,6 +258,7 @@ LLGLSLShader			gNormalMapGenProgram;
 // Deferred materials shaders
 LLGLSLShader			gDeferredMaterialProgram[LLMaterial::SHADER_COUNT*2];
 LLGLSLShader			gDeferredMaterialWaterProgram[LLMaterial::SHADER_COUNT*2];
+LLGLSLShader			gDeferredPBROpaqueProgram;
 
 //helper for making a rigged variant of a given shader
 bool make_rigged_variant(LLGLSLShader& shader, LLGLSLShader& riggedShader)
@@ -1300,6 +1301,9 @@ BOOL LLViewerShaderMgr::loadShadersDeferred()
 			gDeferredMaterialProgram[i].unload();
 			gDeferredMaterialWaterProgram[i].unload();
 		}
+
+        gDeferredPBROpaqueProgram.unload();
+
 		return TRUE;
 	}
 
@@ -1584,6 +1588,22 @@ BOOL LLViewerShaderMgr::loadShadersDeferred()
             success = gDeferredMaterialWaterProgram[i].createShader(NULL, NULL);//&mWLUniforms);
             llassert(success);
 		}
+
+        if (success)
+        {
+            gDeferredPBROpaqueProgram.mName = "Deferred PBR Opaque Shader";
+            gDeferredPBROpaqueProgram.mFeatures.encodesNormal = true;
+            gDeferredPBROpaqueProgram.mFeatures.hasSrgb = true;
+
+            gDeferredPBROpaqueProgram.mShaderFiles.clear();
+            gDeferredPBROpaqueProgram.mShaderFiles.push_back(make_pair("deferred/pbropaqueV.glsl", GL_VERTEX_SHADER_ARB));
+            gDeferredPBROpaqueProgram.mShaderFiles.push_back(make_pair("deferred/pbropaqueF.glsl", GL_FRAGMENT_SHADER_ARB));
+            gDeferredPBROpaqueProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels;
+            gDeferredPBROpaqueProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED];
+            //gDeferredPBROpaqueProgram.addPermutation("HAS_NORMAL_MAP", "1");
+            success = gDeferredPBROpaqueProgram.createShader(NULL, NULL);
+            llassert(success);
+        }
 	}
 
 	gDeferredMaterialProgram[1].mFeatures.hasLighting = true;
diff --git a/indra/newview/llviewershadermgr.h b/indra/newview/llviewershadermgr.h
index 93bb29a355b1609a16d7fdcd71ad5732f3364e10..50a3daebaa10f604da50b94a79c99c0c6ce6c9cb 100644
--- a/indra/newview/llviewershadermgr.h
+++ b/indra/newview/llviewershadermgr.h
@@ -313,4 +313,6 @@ extern LLGLSLShader			gNormalMapGenProgram;
 // Deferred materials shaders
 extern LLGLSLShader			gDeferredMaterialProgram[LLMaterial::SHADER_COUNT*2];
 extern LLGLSLShader			gDeferredMaterialWaterProgram[LLMaterial::SHADER_COUNT*2];
+
+extern LLGLSLShader			gDeferredPBROpaqueProgram;
 #endif
diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp
index 126a25115d64707d3b6702186c7a3e184198115d..17d92fda38bdf094403dfc187a16e59720c0202c 100644
--- a/indra/newview/llvovolume.cpp
+++ b/indra/newview/llvovolume.cpp
@@ -5674,6 +5674,14 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group)
 					continue;
 				}
 
+#if LL_RELEASE_WITH_DEBUG_INFO
+                const LLUUID pbr_id( "49c88210-7238-2a6b-70ac-92d4f35963cf" );
+                const LLUUID obj_id( vobj->getID() );
+                bool is_pbr = (obj_id == pbr_id);
+#else
+                bool is_pbr = false;
+#endif
+
 				//ALWAYS null out vertex buffer on rebuild -- if the face lands in a render
 				// batch, it will recover its vertex buffer reference from the spatial group
 				facep->setVertexBuffer(NULL);
@@ -5739,6 +5747,12 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group)
 
 					BOOL force_simple = (facep->getPixelArea() < FORCE_SIMPLE_RENDER_AREA);
 					U32 type = gPipeline.getPoolTypeFromTE(te, tex);
+
+                    if (is_pbr)
+                    {
+                        type = LLDrawPool::POOL_PBR_OPAQUE;
+                    }
+                    else
 					if (type != LLDrawPool::POOL_ALPHA && force_simple)
 					{
 						type = LLDrawPool::POOL_SIMPLE;
diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp
index e93ff95ed4b3097b3534ebbbe9e2fb7daba04dcd..ef616f5d835c44e0fff5a5eba37b87b6012aa536 100644
--- a/indra/newview/pipeline.cpp
+++ b/indra/newview/pipeline.cpp
@@ -462,6 +462,7 @@ void LLPipeline::init()
 	getPool(LLDrawPool::POOL_BUMP);
 	getPool(LLDrawPool::POOL_MATERIALS);
 	getPool(LLDrawPool::POOL_GLOW);
+	getPool(LLDrawPool::POOL_PBR_OPAQUE);
 
 	resetFrameStats();
 
@@ -1570,6 +1571,10 @@ LLDrawPool *LLPipeline::findPool(const U32 type, LLViewerTexture *tex0)
 		poolp = mWLSkyPool;
 		break;
 
+	case LLDrawPool::POOL_PBR_OPAQUE:
+		poolp = mPBROpaquePool;
+		break;
+
 	default:
 		llassert(0);
 		LL_ERRS() << "Invalid Pool Type in  LLPipeline::findPool() type=" << type << LL_ENDL;
@@ -5671,6 +5676,18 @@ void LLPipeline::addToQuickLookup( LLDrawPool* new_poolp )
 		}
 		break;
 
+    case LLDrawPool::POOL_PBR_OPAQUE:
+        if( mPBROpaquePool )
+        {
+            llassert(0);
+            LL_WARNS() << "LLPipeline::addPool(): Ignoring duplicate PBR Opaque Pool" << LL_ENDL;
+        }
+        else
+        {
+            mPBROpaquePool = new_poolp;
+        }
+        break;
+
 	default:
 		llassert(0);
 		LL_WARNS() << "Invalid Pool Type in  LLPipeline::addPool()" << LL_ENDL;
@@ -5787,6 +5804,11 @@ void LLPipeline::removeFromQuickLookup( LLDrawPool* poolp )
 		mGroundPool = NULL;
 		break;
 
+    case LLDrawPool::POOL_PBR_OPAQUE:
+        llassert( poolp == mPBROpaquePool );
+        mPBROpaquePool = NULL;
+        break;
+
 	default:
 		llassert(0);
 		LL_WARNS() << "Invalid Pool Type in  LLPipeline::removeFromQuickLookup() type=" << poolp->getType() << LL_ENDL;
diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h
index 29c93c199b83675af6281066470319015328df59..975380929d7b1117482e4956a590dc2dbc21fc9f 100644
--- a/indra/newview/pipeline.h
+++ b/indra/newview/pipeline.h
@@ -867,6 +867,7 @@ class LLPipeline
 	LLDrawPool*					mBumpPool;
 	LLDrawPool*					mMaterialsPool;
 	LLDrawPool*					mWLSkyPool;
+	LLDrawPool*					mPBROpaquePool;
 	// Note: no need to keep an quick-lookup to avatar pools, since there's only one per avatar
 	
 public: