Class for recording Quake3 shaders.
This is a temporary holding area since shaders are actually converted into Material objects for use in the engine proper. However, because we have to read in shader definitions en masse (because they are stored in shared .shader files) without knowing which will actually be used, we store their definitions here temporarily since their instantiations as Materials would use precious resources because of the automatic loading of textures etc.
Inheritance: Axiom.Core.Resource
コード例 #1
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="name"></param>
        /// <param name="handle"></param>
        /// <param name="group"></param>
        /// <param name="isManual"></param>
        /// <param name="loader"></param>
        /// <param name="createParams"></param>
        /// <returns></returns>
        protected override Resource _create(string name, ulong handle, string group, bool isManual,
                                            IManualResourceLoader loader, NameValuePairList createParams)
        {
            var s = new Quake3Shader(this, name, handle, ResourceGroupManager.Instance.WorldResourceGroupName);

            return(s);
        }
コード例 #2
0
        public override Resource Create(string name)
        {
            Quake3Shader s = new Quake3Shader(name);

            Load(s, 1);

            return(s);
        }
コード例 #3
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="line"></param>
        /// <param name="shader"></param>
        protected void ParseShaderAttrib(string line, Quake3Shader shader)
        {
            string[] attribParams = line.Replace("(", "").Replace(")", "").Split(' ', '\t');

            if (attribParams[0] == "skyparms")
            {
                if (attribParams[1] != "-")
                {
                    shader.Farbox     = true;
                    shader.FarboxName = attribParams[1];
                }
                if (attribParams[2] != "-")
                {
                    shader.SkyDome = true;

                    if (attribParams[2] == "full")
                    {
                        shader.CloudHeight = 512;
                    }
                    else
                    {
                        shader.CloudHeight = StringConverter.ParseFloat(attribParams[2]);
                    }
                }

                // nearbox not supported
            }
            else if (attribParams[0] == "cull")
            {
                if ((attribParams[1] == "disable") || (attribParams[1] == "none"))
                {
                    shader.CullingMode = ManualCullingMode.None;
                }
                else if (attribParams[1] == "front")
                {
                    shader.CullingMode = ManualCullingMode.Front;
                }
                else if (attribParams[1] == "back")
                {
                    shader.CullingMode = ManualCullingMode.Back;
                }
            }
            else if (attribParams[0] == "deformvertexes")
            {
                // TODO
            }
            else if (attribParams[0] == "fogparms")
            {
                var fogValues = new string[4];
                Array.Copy(attribParams, 1, fogValues, 0, 4);

                shader.Fog         = true;
                shader.FogColor    = StringConverter.ParseColor(fogValues);
                shader.FogDistance = StringConverter.ParseFloat(attribParams[4]);
            }
        }
コード例 #4
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="shader"></param>
        protected void ParseNewShaderPass(StreamReader stream, Quake3Shader shader)
        {
            string line;
            var    pass = new ShaderPass();

            // Default pass details
            pass.animNumFrames     = 0;
            pass.blend             = LayerBlendOperation.Replace;
            pass.blendDest         = SceneBlendFactor.Zero;
            pass.blendSrc          = SceneBlendFactor.One;
            pass.depthFunc         = CompareFunction.LessEqual;
            pass.flags             = 0;
            pass.rgbGenFunc        = ShaderGen.Identity;
            pass.tcModRotate       = 0;
            pass.tcModScale[0]     = pass.tcModScale[1] = 1.0f;
            pass.tcModScroll[0]    = pass.tcModScroll[1] = 0.0f;
            pass.tcModStretchWave  = ShaderWaveType.None;
            pass.tcModTransform[0] = pass.tcModTransform[1] = 0.0f;
            pass.tcModTurbOn       = false;
            pass.tcModTurb[0]      = pass.tcModTurb[1] = pass.tcModTurb[2] = pass.tcModTurb[3] = 0.0f;
            pass.texGen            = ShaderTextureGen.Base;
            pass.addressMode       = TextureAddressing.Wrap;
            pass.customBlend       = false;
            pass.alphaVal          = 0;
            pass.alphaFunc         = CompareFunction.AlwaysPass;

            shader.Pass.Add(pass);

            while ((line = stream.ReadLine()) != null)
            {
                line = line.Trim();

                // Ignore comments & blanks
                if ((line != String.Empty) && !line.StartsWith("//"))
                {
                    if (line == "}")
                    {
                        return;
                    }
                    else
                    {
                        ParseShaderPassAttrib(line, shader, pass);
                    }
                }
            }
        }
コード例 #5
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="groupName"></param>
        /// <param name="fileName"></param>
        public override void ParseScript(Stream stream, string groupName, string fileName)
        {
            var          file = new StreamReader(stream, Encoding.UTF8);
            string       line;
            Quake3Shader shader = null;

            while ((line = file.ReadLine()) != null)
            {
                line = line.Trim();

                // Ignore comments & blanks
                if ((line != String.Empty) && !line.StartsWith("//"))
                {
                    if (shader == null)
                    {
                        LogManager.Instance.Write("Creating {0}...", line);
                        // No current shader
                        // So first valid data should be a shader name
                        shader = (Quake3Shader)Create(line, groupName);

                        // Skip to and over next brace
                        ParseHelper.SkipToNextOpenBrace(file);
                    }
                    else
                    {
                        // Already in a shader
                        if (line == "}")
                        {
                            LogManager.Instance.Write("End of shader.");
                            shader = null;
                        }
                        else if (line == "{")
                        {
                            LogManager.Instance.Write("New pass...");
                            ParseNewShaderPass(file, shader);
                        }
                        else
                        {
                            LogManager.Instance.Write("New attrib, {0}...", line);
                            ParseShaderAttrib(line.ToLower(), shader);
                        }
                    }
                }
            }
        }
コード例 #6
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="line"></param>
        /// <param name="shader"></param>
        /// <param name="pass"></param>
        protected void ParseShaderPassAttrib(string line, Quake3Shader shader, ShaderPass pass)
        {
            string[] attribParams = line.Split(' ', '\t');
            attribParams[0] = attribParams[0].ToLower();

            LogManager.Instance.Write("Attrib {0}", attribParams[0]);
            if ((attribParams[0] != "map") && (attribParams[0] != "clampmap") && (attribParams[0] != "animmap"))
            {
                // lower case all except textures
                for (int i = 1; i < attribParams.Length; ++i)
                {
                    attribParams[i] = attribParams[i].ToLower();
                }
            }

            // MAP
            if (attribParams[0] == "map")
            {
                pass.textureName = attribParams[1];

                if (attribParams[1].ToLower() == "$lightmap")
                {
                    pass.texGen = ShaderTextureGen.Lightmap;
                }
            }
            // CLAMPMAP
            else if (attribParams[0] == "clampmap")
            {
                pass.textureName = attribParams[1];

                if (attribParams[1].ToLower() == "$lightmap")
                {
                    pass.texGen = ShaderTextureGen.Lightmap;
                }

                pass.addressMode = TextureAddressing.Clamp;
            }
            // ANIMMAP
            else if (attribParams[0] == "animmap")
            {
                pass.animFps       = StringConverter.ParseFloat(attribParams[1]);
                pass.animNumFrames = attribParams.Length - 2;

                for (uint frame = 0; frame < pass.animNumFrames; ++frame)
                {
                    pass.frames[frame] = attribParams[frame + 2];
                }
            }
            // BLENDFUNC
            else if (attribParams[0] == "blendfunc")
            {
                if ((attribParams[1] == "add") || (attribParams[1] == "gl_add"))
                {
                    pass.blend     = LayerBlendOperation.Add;
                    pass.blendDest = SceneBlendFactor.One;
                    pass.blendSrc  = SceneBlendFactor.One;
                }
                else if ((attribParams[1] == "filter") || (attribParams[1] == "gl_filter"))
                {
                    pass.blend     = LayerBlendOperation.Modulate;
                    pass.blendDest = SceneBlendFactor.Zero;
                    pass.blendSrc  = SceneBlendFactor.DestColor;
                }
                else if ((attribParams[1] == "blend") || (attribParams[1] == "gl_blend"))
                {
                    pass.blend     = LayerBlendOperation.AlphaBlend;
                    pass.blendDest = SceneBlendFactor.OneMinusSourceAlpha;
                    pass.blendSrc  = SceneBlendFactor.SourceAlpha;
                }
                else
                {
                    // Manual blend
                    pass.blendSrc  = ConvertBlendFunc(attribParams[1]);
                    pass.blendDest = ConvertBlendFunc(attribParams[2]);

                    // Detect common blends
                    if ((pass.blendSrc == SceneBlendFactor.One) && (pass.blendDest == SceneBlendFactor.Zero))
                    {
                        pass.blend = LayerBlendOperation.Replace;
                    }
                    else if ((pass.blendSrc == SceneBlendFactor.One) && (pass.blendDest == SceneBlendFactor.One))
                    {
                        pass.blend = LayerBlendOperation.Add;
                    }
                    else if (((pass.blendSrc == SceneBlendFactor.Zero) && (pass.blendDest == SceneBlendFactor.SourceColor)) ||
                             ((pass.blendSrc == SceneBlendFactor.DestColor) && (pass.blendDest == SceneBlendFactor.Zero)))
                    {
                        pass.blend = LayerBlendOperation.Modulate;
                    }
                    else if ((pass.blendSrc == SceneBlendFactor.SourceAlpha) &&
                             (pass.blendDest == SceneBlendFactor.OneMinusSourceAlpha))
                    {
                        pass.blend = LayerBlendOperation.AlphaBlend;
                    }
                    else
                    {
                        pass.customBlend = true;
                    }

                    // NB other custom blends might not work due to OGRE trying to use multitexture over multipass
                }
            }
            // RGBGEN
            else if (attribParams[0] == "rgbgen")
            {
                // TODO
            }
            // ALPHAGEN
            else if (attribParams[0] == "alphagen")
            {
                // TODO
            }
            // TCGEN
            else if (attribParams[0] == "tcgen")
            {
                if (attribParams[1] == "base")
                {
                    pass.texGen = ShaderTextureGen.Base;
                }
                else if (attribParams[1] == "lightmap")
                {
                    pass.texGen = ShaderTextureGen.Lightmap;
                }
                else if (attribParams[1] == "environment")
                {
                    pass.texGen = ShaderTextureGen.Environment;
                }
            }
            // TCMOD
            else if (attribParams[0] == "tcmod")
            {
                if (attribParams[1] == "rotate")
                {
                    pass.tcModRotate = -StringConverter.ParseFloat(attribParams[2]) / 360;
                    // +ve is clockwise degrees in Q3 shader, anticlockwise complete rotations in Ogre
                }
                else if (attribParams[1] == "scroll")
                {
                    pass.tcModScroll[0] = StringConverter.ParseFloat(attribParams[2]);
                    pass.tcModScroll[1] = StringConverter.ParseFloat(attribParams[3]);
                }
                else if (attribParams[1] == "scale")
                {
                    pass.tcModScale[0] = StringConverter.ParseFloat(attribParams[2]);
                    pass.tcModScale[1] = StringConverter.ParseFloat(attribParams[3]);
                }
                else if (attribParams[1] == "stretch")
                {
                    if (attribParams[2] == "sin")
                    {
                        pass.tcModStretchWave = ShaderWaveType.Sin;
                    }
                    else if (attribParams[2] == "triangle")
                    {
                        pass.tcModStretchWave = ShaderWaveType.Triangle;
                    }
                    else if (attribParams[2] == "square")
                    {
                        pass.tcModStretchWave = ShaderWaveType.Square;
                    }
                    else if (attribParams[2] == "sawtooth")
                    {
                        pass.tcModStretchWave = ShaderWaveType.SawTooth;
                    }
                    else if (attribParams[2] == "inversesawtooth")
                    {
                        pass.tcModStretchWave = ShaderWaveType.InverseSawtooth;
                    }

                    pass.tcModStretchParams[0] = StringConverter.ParseFloat(attribParams[3]);
                    pass.tcModStretchParams[1] = StringConverter.ParseFloat(attribParams[4]);
                    pass.tcModStretchParams[2] = StringConverter.ParseFloat(attribParams[5]);
                    pass.tcModStretchParams[3] = StringConverter.ParseFloat(attribParams[6]);
                }
            }
            // TURB
            else if (attribParams[0] == "turb")
            {
                pass.tcModTurbOn  = true;
                pass.tcModTurb[0] = StringConverter.ParseFloat(attribParams[2]);
                pass.tcModTurb[1] = StringConverter.ParseFloat(attribParams[3]);
                pass.tcModTurb[2] = StringConverter.ParseFloat(attribParams[4]);
                pass.tcModTurb[3] = StringConverter.ParseFloat(attribParams[5]);
            }
            // DEPTHFUNC
            else if (attribParams[0] == "depthfunc")
            {
                // TODO
            }
            // DEPTHWRITE
            else if (attribParams[0] == "depthwrite")
            {
                // TODO
            }
            // ALPHAFUNC
            else if (attribParams[0] == "alphafunc")
            {
                if (attribParams[1] == "gt0")
                {
                    pass.alphaVal  = 0;
                    pass.alphaFunc = CompareFunction.Greater;
                }
                else if (attribParams[1] == "ge128")
                {
                    pass.alphaVal  = 128;
                    pass.alphaFunc = CompareFunction.GreaterEqual;
                }
                else if (attribParams[1] == "lt128")
                {
                    pass.alphaVal  = 128;
                    pass.alphaFunc = CompareFunction.Less;
                }
            }
        }
コード例 #7
0
        public override Resource Create(string name)
        {
            Quake3Shader s = new Quake3Shader(name);
            Load(s, 1);

            return s;
        }
コード例 #8
0
        protected void ParseShaderPassAttrib(string line, Quake3Shader shader, ShaderPass pass)
        {
            string[] attribParams = line.Split(' ', '\t');
            attribParams[0] = attribParams[0].ToLower();

            LogManager.Instance.Write("Attrib {0}", attribParams[0]);
            if((attribParams[0] != "map") && (attribParams[0] != "clampmap") && (attribParams[0] != "animmap"))
            {
                // lower case all except textures
                for (int i = 1; i < attribParams.Length; i++) {
                    attribParams[i] = attribParams[i].ToLower();
                }
            }

            // MAP
            if(attribParams[0] == "map")
            {
                pass.textureName = attribParams[1];

                if (attribParams[1].ToLower() == "$lightmap") {
                    pass.texGen = ShaderTextureGen.Lightmap;
                }
            }
            // CLAMPMAP
            else if(attribParams[0] == "clampmap")
            {
                pass.textureName = attribParams[1];

                if(attribParams[1].ToLower() == "$lightmap")
                    pass.texGen = ShaderTextureGen.Lightmap;

                pass.addressMode = TextureAddressing.Clamp;
            }
            // ANIMMAP
            else if(attribParams[0] == "animmap")
            {
                pass.animFps = StringConverter.ParseFloat(attribParams[1]);
                pass.animNumFrames = attribParams.Length - 2;

                for(uint frame = 0; frame < pass.animNumFrames; frame++)
                    pass.frames[frame] = attribParams[frame + 2];
            }
            // BLENDFUNC
            else if(attribParams[0] == "blendfunc")
            {
                if((attribParams[1] == "add") || (attribParams[1] == "gl_add"))
                {
                    pass.blend = LayerBlendOperation.Add;
                    pass.blendDest = SceneBlendFactor.One;
                    pass.blendSrc = SceneBlendFactor.One;
                }
                else if((attribParams[1] == "filter") || (attribParams[1] == "gl_filter"))
                {
                    pass.blend = LayerBlendOperation.Modulate;
                    pass.blendDest = SceneBlendFactor.Zero;
                    pass.blendSrc = SceneBlendFactor.DestColor;
                }
                else if((attribParams[1] == "blend") || (attribParams[1] == "gl_blend"))
                {
                    pass.blend = LayerBlendOperation.AlphaBlend;
                    pass.blendDest = SceneBlendFactor.OneMinusSourceAlpha;
                    pass.blendSrc = SceneBlendFactor.SourceAlpha;
                }
                else
                {
                    // Manual blend
                    pass.blendSrc = ConvertBlendFunc(attribParams[1]);
                    pass.blendDest = ConvertBlendFunc(attribParams[2]);

                    // Detect common blends
                    if((pass.blendSrc == SceneBlendFactor.One) && (pass.blendDest == SceneBlendFactor.Zero))
                        pass.blend = LayerBlendOperation.Replace;
                    else if((pass.blendSrc == SceneBlendFactor.One) && (pass.blendDest == SceneBlendFactor.One))
                        pass.blend = LayerBlendOperation.Add;
                    else if(((pass.blendSrc == SceneBlendFactor.Zero) && (pass.blendDest == SceneBlendFactor.SourceColor)) ||
                        ((pass.blendSrc == SceneBlendFactor.DestColor) && (pass.blendDest == SceneBlendFactor.Zero)))
                        pass.blend = LayerBlendOperation.Modulate;
                    else if((pass.blendSrc == SceneBlendFactor.SourceAlpha) && (pass.blendDest == SceneBlendFactor.OneMinusSourceAlpha))
                        pass.blend = LayerBlendOperation.AlphaBlend;
                    else
                        pass.customBlend = true;

                    // NB other custom blends might not work due to OGRE trying to use multitexture over multipass
                }
            }
            // RGBGEN
            else if(attribParams[0] == "rgbgen")
            {
                // TODO
            }
            // ALPHAGEN
            else if(attribParams[0] == "alphagen")
            {
                // TODO
            }
            // TCGEN
            else if(attribParams[0] == "tcgen")
            {
                if(attribParams[1] == "base")
                    pass.texGen = ShaderTextureGen.Base;
                else if(attribParams[1] == "lightmap")
                    pass.texGen = ShaderTextureGen.Lightmap;
                else if(attribParams[1] == "environment")
                    pass.texGen = ShaderTextureGen.Environment;
            }
            // TCMOD
            else if(attribParams[0] == "tcmod")
            {
                if(attribParams[1] == "rotate")
                {
                    pass.tcModRotate = -StringConverter.ParseFloat(attribParams[2]) / 360; // +ve is clockwise degrees in Q3 shader, anticlockwise complete rotations in Ogre
                }
                else if(attribParams[1] == "scroll")
                {
                    pass.tcModScroll[0] = StringConverter.ParseFloat(attribParams[2]);
                    pass.tcModScroll[1] = StringConverter.ParseFloat(attribParams[3]);
                }
                else if(attribParams[1] == "scale")
                {
                    pass.tcModScale[0] = StringConverter.ParseFloat(attribParams[2]);
                    pass.tcModScale[1] = StringConverter.ParseFloat(attribParams[3]);
                }
                else if(attribParams[1] == "stretch")
                {
                    if(attribParams[2] == "sin")
                        pass.tcModStretchWave = ShaderWaveType.Sin;
                    else if(attribParams[2] == "triangle")
                        pass.tcModStretchWave = ShaderWaveType.Triangle;
                    else if(attribParams[2] == "square")
                        pass.tcModStretchWave = ShaderWaveType.Square;
                    else if(attribParams[2] == "sawtooth")
                        pass.tcModStretchWave = ShaderWaveType.SawTooth;
                    else if(attribParams[2] == "inversesawtooth")
                        pass.tcModStretchWave = ShaderWaveType.InverseSawtooth;

                    pass.tcModStretchParams[0] = StringConverter.ParseFloat(attribParams[3]);
                    pass.tcModStretchParams[1] = StringConverter.ParseFloat(attribParams[4]);
                    pass.tcModStretchParams[2] = StringConverter.ParseFloat(attribParams[5]);
                    pass.tcModStretchParams[3] = StringConverter.ParseFloat(attribParams[6]);
                }
            }
            // TURB
            else if(attribParams[0] == "turb")
            {
                pass.tcModTurbOn = true;
                pass.tcModTurb[0] = StringConverter.ParseFloat(attribParams[2]);
                pass.tcModTurb[1] = StringConverter.ParseFloat(attribParams[3]);
                pass.tcModTurb[2] = StringConverter.ParseFloat(attribParams[4]);
                pass.tcModTurb[3] = StringConverter.ParseFloat(attribParams[5]);
            }
            // DEPTHFUNC
            else if(attribParams[0] == "depthfunc")
            {
                // TODO
            }
            // DEPTHWRITE
            else if(attribParams[0] == "depthwrite")
            {
                // TODO
            }
            // ALPHAFUNC
            else if(attribParams[0] == "alphafunc")
            {
                if(attribParams[1] == "gt0")
                {
                    pass.alphaVal = 0;
                    pass.alphaFunc = CompareFunction.Greater;
                }
                else if(attribParams[1] == "ge128")
                {
                    pass.alphaVal = 128;
                    pass.alphaFunc = CompareFunction.GreaterEqual;
                }
                else if(attribParams[1] == "lt128")
                {
                    pass.alphaVal = 128;
                    pass.alphaFunc = CompareFunction.Less;
                }
            }
        }
コード例 #9
0
        protected void ParseShaderAttrib(string line, Quake3Shader shader)
        {
            string[] attribParams = line.Replace("(", "").Replace(")", "").Split(' ', '\t');

            if(attribParams[0] == "skyparms")
            {
                if(attribParams[1] != "-")
                {
                    shader.Farbox = true;
                    shader.FarboxName = attribParams[1];
                }
                if(attribParams[2] != "-")
                {
                    shader.SkyDome = true;

                    if(attribParams[2] == "full")
                        shader.CloudHeight = 512;
                    else
                        shader.CloudHeight = StringConverter.ParseFloat(attribParams[2]);
                }

                // nearbox not supported
            }
            else if(attribParams[0] == "cull")
            {
                if((attribParams[1] == "diable") || (attribParams[1] == "none"))
                    shader.CullingMode = ManualCullingMode.None;
                else if(attribParams[1] == "front")
                    shader.CullingMode = ManualCullingMode.Front;
                else if(attribParams[1] == "back")
                    shader.CullingMode = ManualCullingMode.Back;
            }
            else if (attribParams[0] == "deformvertexes")
            {
                // TODO
            }
            else if(attribParams[0] == "fogparms")
            {
                string[] fogValues = new string[4];
                Array.Copy(attribParams, 1, fogValues, 0, 4);

                /*shader.Fog = true;
                shader.FogColour = StringConverter.ParseColor(fogValues);
                shader.FogDistance = StringConverter.ParseFloat(attribParams[4]);*/
            }
        }
コード例 #10
0
        protected void ParseNewShaderPass(StreamReader stream, Quake3Shader shader)
        {
            string line;
            ShaderPass pass = new ShaderPass();

            // Default pass details
            pass.animNumFrames = 0;
            pass.blend = LayerBlendOperation.Replace;
            pass.blendDest = SceneBlendFactor.Zero;
            pass.depthFunc = CompareFunction.LessEqual;
            pass.flags = 0;
            pass.rgbGenFunc = ShaderGen.Identity;
            pass.tcModRotate = 0;
            pass.tcModScale[0] = pass.tcModScale[1] = 1.0f;
            pass.tcModScroll[0] = pass.tcModScroll[1] = 0.0f;
            pass.tcModStretchWave = ShaderWaveType.None;
            pass.tcModTransform[0] = pass.tcModTransform[1] = 0.0f;
            pass.tcModTurbOn = false;
            pass.tcModTurb[0] = pass.tcModTurb[1] = pass.tcModTurb[2] = pass.tcModTurb[3] = 0.0f;
            pass.texGen = ShaderTextureGen.Base;
            pass.addressMode = TextureAddressing.Wrap;
            pass.customBlend = false;
            pass.alphaVal = 0;
            pass.alphaFunc = CompareFunction.AlwaysPass;

            shader.Pass.Add(pass);

            while((line = stream.ReadLine()) != null)
            {
                line = line.Trim();

                // Ignore comments & blanks
                if((line != String.Empty) && !line.StartsWith("//"))
                {
                    if(line == "}")
                        return;
                    else
                        ParseShaderPassAttrib(line, shader, pass);
                }
            }
        }
コード例 #11
0
        private void CreateShaderMaterials(Quake3Level q3lvl, SceneManager sm)
        {
            // NB this only works for the 'default' shaders for now
            // i.e. those that don't have a .shader script and thus default
            // to just texture + lightmap
            // TODO: pre-parse all .shader files and create lookup for next stage (use ROGL shader_file_t)

            // Material names are shadername#lightmapnumber
            // This is because I like to define materials up front completely
            // rather than combine lightmap and shader dynamically (it's
            // more generic). It results in more materials, but they're small
            // beer anyway. Texture duplication is prevented by infrastructure.
            // To do this I actually need to parse the faces since they have the
            // shader/lightmap combo (lightmap number is not in the shader since
            // it can be used with multiple lightmaps)
            string shaderName;
            int    face = q3lvl.Faces.Length;

            while (face-- > 0)
            {
                // Check to see if existing material
                // Format shader#lightmap
                int shadIdx = q3lvl.Faces[face].shader;

                shaderName = String.Format("{0}#{1}", q3lvl.Shaders[shadIdx].name, q3lvl.Faces[face].lmTexture);
                Material shadMat = sm.GetMaterial(shaderName);

                if (shadMat == null && !bspOptions.useLightmaps)
                {
                    // try the no-lightmap material
                    shaderName = String.Format("{0}#n", q3lvl.Shaders[shadIdx].name);
                    shadMat    = sm.GetMaterial(shaderName);
                }

                if (shadMat == null)
                {
                    // Colour layer
                    // NB no extension in Q3A(doh), have to try shader, .jpg, .tga
                    string tryName = q3lvl.Shaders[shadIdx].name;

                    // Try shader first
                    Quake3Shader shader = (Quake3Shader)Quake3ShaderManager.Instance.GetByName(tryName);

                    if (shader != null)
                    {
                        shadMat = shader.CreateAsMaterial(sm, q3lvl.Faces[face].lmTexture);
                    }
                    else
                    {
                        // No shader script, try default type texture
                        shadMat = sm.CreateMaterial(shaderName);
                        Pass shadPass = shadMat.GetTechnique(0).GetPass(0);

                        // Try jpg
                        TextureUnitState tex = shadPass.CreateTextureUnitState(tryName + ".jpg");
                        tex.Load();

                        if (tex.IsBlank)
                        {
                            // Try tga
                            tex.SetTextureName(tryName + ".tga");
                        }

                        // Set replace on all first layer textures for now
                        tex.SetColorOperation(LayerBlendOperation.Replace);
                        tex.TextureAddressing = TextureAddressing.Wrap;

                        // for ambient lighting
                        tex.ColorBlendMode.source2 = LayerBlendSource.Manual;

                        if (bspOptions.useLightmaps && q3lvl.Faces[face].lmTexture != -1)
                        {
                            // Add lightmap, additive blending
                            tex = shadPass.CreateTextureUnitState(String.Format("@lightmap{0}", q3lvl.Faces[face].lmTexture));

                            // Blend
                            tex.SetColorOperation(LayerBlendOperation.Modulate);

                            // Use 2nd texture co-ordinate set
                            tex.TextureCoordSet = 1;

                            // Clamp
                            tex.TextureAddressing = TextureAddressing.Clamp;
                        }

                        shadMat.CullingMode = CullingMode.None;
                        shadMat.Lighting    = false;
                    }
                }

                shadMat.Load();

                // Copy face data
                BspStaticFaceGroup dest = CopyShaderFaceData(q3lvl, face, shadMat, shadIdx);

                faceGroups[face] = dest;
            }
        }