/// <summary> /// Processes a line that describes how a skybox should look. /// </summary> private static void ProcessSkyparms(string line, ref Q3BSPMaterialContent shader) { shader.IsSky = true; string[] split = line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (split[0] != "-") { shader.FarBoxName = split[0]; } writer.WriteLine(split[0] + "" + split[1] + "" + split[2]); writer.Flush(); if (split[1] != "-") { shader.SkyHeight = int.Parse(split[1]); } if (split[2] != "-") { shader.NearBoxName = split[2]; } }
string GenerateEffectFromShader(Q3BSPMaterialContent shader, StreamWriter sw) { StringBuilder sb = new StringBuilder(); int stageNumber = 0; int texCoordNumber = 0; string externs = ""; string samplers = ""; string vertexShaderInput = ""; string vertexShaderOutput = ""; string stageInputStructs = ""; string vertexShaderProgram = ""; string pixelShaderProgram = ""; string technique = ""; #region Generate Generic External Variables sb.AppendLine("// Generic Externs"); sb.AppendLine("uniform float4x4 worldViewProj;"); sb.AppendLine("uniform float gameTime;"); externs += sb.ToString(); sb.Remove(0, sb.ToString().Length); #endregion foreach (Q3BSPMaterialStageContent stage in shader.stages) { stage.InitializeEffectNames(stageNumber); #region Generate External Variables sb.AppendLine("// " + stage.StageName + "'s externs"); sb.AppendLine("uniform texture " + stage.TextureEffectParameterName + ";"); sb.AppendLine("uniform float3x2 " + stage.TcModName + ";"); externs += sb.ToString(); sb.Remove(0, sb.ToString().Length); #endregion #region Generate Samplers sb.AppendLine("sampler " + stage.SamplerName + " = sampler_state"); sb.AppendLine("{"); sb.AppendLine("\tTexture = <" + stage.TextureEffectParameterName + ">;"); sb.AppendLine("\tminfilter = LINEAR;"); sb.AppendLine("\tmagfilter = LINEAR;"); sb.AppendLine("\tmipfilter = NONE;"); sb.AppendLine("\tAddressU = Wrap;"); sb.AppendLine("\tAddressV = Wrap;"); sb.AppendLine("};"); samplers += sb.ToString(); sb.Remove(0, sb.ToString().Length); #endregion #region Generate Input Structures sb.AppendLine("struct " + stage.InputName); sb.AppendLine("{"); sb.AppendLine("\tfloat2 uv : TEXCOORD" + texCoordNumber + ";"); sb.AppendLine("};"); stageInputStructs += sb.ToString(); sb.Remove(0, sb.ToString().Length); texCoordNumber++; #endregion stageNumber++; } #region Generate VertexShaderInput sb.AppendLine("struct VertexShaderInput"); sb.AppendLine("{"); if (shader.IsSky) { sb.AppendLine("\tfloat4 position : POSITION0;"); } else { sb.AppendLine("\tfloat4 position : POSITION0;"); sb.AppendLine("\tfloat3 normal : NORMAL0;"); sb.AppendLine("\tfloat2 texcoords : TEXCOORD0;"); sb.AppendLine("\tfloat2 lightmapcoords : TEXCOORD1;"); sb.AppendLine("\tfloat4 diffuse : COLOR0;"); } sb.AppendLine("};"); vertexShaderInput = sb.ToString(); sb.Remove(0, sb.ToString().Length); #endregion #region Generate VertexShaderOutput sb.AppendLine("struct VertexShaderOutput"); sb.AppendLine("{"); sb.AppendLine("\tfloat4 position : POSITION0;"); foreach (Q3BSPMaterialStageContent stage in shader.stages) { sb.AppendLine("\t" + stage.InputName + " " + stage.StageName + ";"); } sb.AppendLine("};"); vertexShaderOutput = sb.ToString(); sb.Remove(0, sb.ToString().Length); #endregion #region Generate VertexShaderProgram sb.AppendLine("VertexShaderOutput VertexShaderProgram(VertexShaderInput input)"); sb.AppendLine("{"); sb.AppendLine("\tVertexShaderOutput output;"); sb.AppendLine("\toutput.position = mul(input.position, worldViewProj);"); if (shader.IsSky) { float zScale = shader.SkyHeight / 128.0f; float zOffset = 0.707f; sb.AppendLine("\tfloat3 S = normalize(float3(input.position.x, input.position.z, input.position.y));"); sb.AppendLine("\tS.z = " + zScale + " * (S.z + " + zOffset + ");"); sb.AppendLine("\tS = normalize(S);"); sb.AppendLine(); foreach (Q3BSPMaterialStageContent stage in shader.stages) { sb.AppendLine("\toutput." + stage.StageName + ".uv = float2(S.x, S.y);"); sb.Append(WriteTcModEffectCode(stage)); } } else { foreach (Q3BSPMaterialStageContent stage in shader.stages) { if (stage.IsLightmapStage) { sb.AppendLine("\toutput." + stage.StageName + ".uv = input.lightmapcoords;"); } else { sb.AppendLine("\toutput." + stage.StageName + ".uv = input.texcoords;"); } sb.Append(WriteTcModEffectCode(stage)); } } sb.AppendLine(); sb.AppendLine("\treturn output;"); sb.AppendLine("};"); vertexShaderProgram = sb.ToString(); sb.Remove(0, sb.ToString().Length); #endregion #region Generate PixelShaderProgram sb.AppendLine("float4 PixelShaderProgram(VertexShaderOutput input) : COLOR0"); sb.AppendLine("{"); sb.AppendLine("\tfloat4 destination = (float4)0;"); sb.AppendLine("\tfloat4 source = (float4)0;"); sb.AppendLine(); sb.Append(WriteBlendFuncEffectCode(shader)); sb.AppendLine(); sb.AppendLine("\treturn destination;"); sb.AppendLine("}"); pixelShaderProgram = sb.ToString(); sb.Remove(0, sb.ToString().Length); #endregion #region Generate Technique string shaderModel = "1_1"; if (this.shaderModel == ShaderModel.ShaderModel2 || shader.stages.Count > 4) { shaderModel = "2_0"; } sb.AppendLine("technique Technique1"); sb.AppendLine("{"); sb.AppendLine("\tpass Pass1"); sb.AppendLine("\t{"); sb.AppendLine("\t\tVertexShader = compile vs_" + shaderModel + " VertexShaderProgram();"); sb.AppendLine("\t\tPixelShader = compile ps_" + shaderModel + " PixelShaderProgram();"); sb.AppendLine("\t}"); sb.AppendLine("}"); technique = sb.ToString(); sb.Remove(0, sb.ToString().Length); #endregion if (sw != null) { sw.WriteLine("Shader: " + shader.shaderName); sw.Write(externs); sw.WriteLine(); sw.Write(samplers); sw.WriteLine(); sw.Write(stageInputStructs); sw.WriteLine(); sw.Write(vertexShaderInput); sw.WriteLine(); sw.Write(vertexShaderOutput); sw.WriteLine(); sw.Write(vertexShaderProgram); sw.WriteLine(); sw.Write(pixelShaderProgram); sw.WriteLine(); sw.Write(technique); sw.WriteLine(); sw.Flush(); } return(externs + samplers + stageInputStructs + vertexShaderInput + vertexShaderOutput + vertexShaderProgram + pixelShaderProgram + technique); }
public override TImport Import(string filename, ContentImporterContext context) { //System.Diagnostics.Debugger.Launch(); TImport shaderList = new TImport(); StreamReader sr = new StreamReader(filename); Q3BSPMaterialContent shader = new Q3BSPMaterialContent(); Q3BSPMaterialStageContent currentStage = new Q3BSPMaterialStageContent(null); string shaderName = ""; bool insideShaderBlock = false; bool insideStageBlock = false; List <Q3BSPMaterialStageTcMod> currentStageTcMods = new List <Q3BSPMaterialStageTcMod>(); while (!sr.EndOfStream) { string line = sr.ReadLine(); int comment; #region Clean up line and throw out useless lines // Remove comments and whitespace if ((comment = line.IndexOf("//")) > -1) { line = line.Substring(0, comment); } line = line.Trim(); // Line is empty don't process it if (line == "") { continue; } // Line is important only when the BSP is compiled, don't process it if (line.Length > 3 && line.Substring(0, 4) == "qer_") { continue; } if (line.Length > 4 && line.Substring(0, 5) == "q3map") { continue; } if (line.Length > 10 && line.Substring(0, 11) == "surfaceparm") { continue; } #endregion #region Read lines outside any blocks if (!insideShaderBlock) { if (line.Substring(0, 1) == "{") { if (shaderName == "") { throw new Exception("Error loading .shader: there is a block without a shader name."); } shader = new Q3BSPMaterialContent(); shader.shaderName = shaderName; shader.stages = new List <Q3BSPMaterialStageContent>(); insideShaderBlock = true; } else { if (shaderName != "") { throw new Exception("Error loading .shader: Shader " + shaderName + " has no information describing it."); } shaderName = line; } } #endregion #region Read lines inside the shader block but outside the stage blocks else if (insideShaderBlock && !insideStageBlock) { if (line.Substring(0, 1) == "{") { insideStageBlock = true; currentStage = new Q3BSPMaterialStageContent(shader); currentStageTcMods.Clear(); } else if (line.Length > 8 && line.Substring(0, 8) == "skyparms") { ProcessSkyparms(line.Substring(8).Trim(), ref shader); } else if (line.Substring(0, 1) == "}") { #region If no shader stages are defined, this is a very basic shader and uses the shader name as the sole texture map if (shader.stages.Count < 1) { currentStage = new Q3BSPMaterialStageContent(shader); currentStage.TextureEffectParameterName = shader.shaderName.Substring(shader.shaderName.LastIndexOf('/') + 1); currentStage.TextureFilename = shader.shaderName; currentStageTcMods.Clear(); currentStage.Finalize(ref currentStageTcMods); shader.stages.Add(currentStage); currentStage = new Q3BSPMaterialStageContent(shader); currentStage.TextureEffectParameterName = "lightmap"; currentStage.TextureFilename = "lightmap"; currentStage.IsLightmapStage = true; ProcessBlendFunc("filter", ref currentStage); currentStage.Finalize(ref currentStageTcMods); shader.stages.Add(currentStage); } #endregion shaderList.Add(shader); shader = null; shaderName = ""; insideShaderBlock = false; } } #endregion #region Read lines inside stage blocks else if (insideStageBlock) { if (line.Length > 2 && line.Substring(0, 3) == "map") { string textureFilename = line.Substring(4); // Account for special texures (ie $lightmap) if (textureFilename.Substring(0, 1) == "$") { if (String.Compare(textureFilename.ToLower(), "$lightmap", true) == 0) { currentStage.IsLightmapStage = true; } textureFilename = textureFilename.Substring(1); } if (textureFilename.LastIndexOf('.') > -1) { textureFilename = textureFilename.Substring(0, textureFilename.LastIndexOf('.')); } currentStage.TextureFilename = textureFilename; } /* * else if (line.Length > 7 && line.Substring(0, 7).ToLower() == "animmap") * { * string[] arguments = line.Split(' '); * string textureFilename = arguments[2]; * if (textureFilename.LastIndexOf('.') > -1) * { * textureFilename = textureFilename.Substring(0, textureFilename.LastIndexOf('.')); * } * currentStage.TextureFilename = textureFilename; * } */ else if (line.Length > 8 && line.Substring(0, 9).ToLower() == "blendfunc") { ProcessBlendFunc(line.Substring(9).Trim(), ref currentStage); } else if (line.Length > 4 && line.Substring(0, 5).ToLower() == "tcmod") { Q3BSPMaterialStageTcMod tcMod = ParseTcMod(line.Substring(5).Trim(), ref currentStage); currentStageTcMods.Add(tcMod); // These tcmods require a time variable in their function if (tcMod.Function == Q3BSPTcModFunction.Scroll || tcMod.Function == Q3BSPTcModFunction.Rotate || tcMod.Function == Q3BSPTcModFunction.Turbulence) { shader.NeedsTime = true; } } /* * else if (line.Length > 5 && line.Substring(0, 6).ToLower() == "rgbgen") * { * currentStage.TextureFilename = line.Substring(7); * } */ else if (line.Substring(0, 1) == "}") { currentStage.Finalize(ref currentStageTcMods); shader.stages.Add(currentStage); insideStageBlock = false; } } #endregion } return(shaderList); }
public override TOutput Process(TInput input, ContentProcessorContext context) { Dictionary <string, Q3BSPMaterialContent> processedDictionary = new Dictionary <string, Q3BSPMaterialContent>(); #region Initialize StreamWriter, if necessary #if DEBUG StreamWriter sw = new StreamWriter(context.OutputFilename.Substring(0, context.OutputFilename.LastIndexOf('.')) + ".compiled.txt"); #else StreamWriter sw = null; #endif #endregion for (int i = 0; i < input.Count; i++) { Q3BSPMaterialContent shader = input[i]; #region Throw any errors in the parsed shader if (shader.stages.Count > 8) { throw new InvalidContentException(shader.shaderName + " has " + shader.stages.Count + " stages, but the maximum supported is 8."); } if (processedDictionary.ContainsKey(shader.shaderName)) { context.Logger.LogWarning("", new ContentIdentity(), "Material " + shader.shaderName + " is defined more than once."); continue; //throw new InvalidContentException("Material " + shader.shaderName + " is defined more than once."); } #endregion #region Log any needed warnings if (shader.stages.Count > 4 && shaderModel == ShaderModel.ShaderModel1) { context.Logger.LogWarning("", new ContentIdentity(), shader.shaderName + " has more than 4 stages, Shader Model 2.0 is required."); } #endregion EffectProcessor processor = new EffectProcessor(); processor.DebugMode = EffectProcessorDebugMode.Debug; EffectContent effectContent = new EffectContent(); effectContent.EffectCode = GenerateEffectFromShader(shader, sw); #region Compile the Effect #if DEBUG CompiledEffectContent compiledEffect = processor.Process(effectContent, context); //CompiledEffect compiledEffect = Effect.CompileEffectFromSource(GenerateEffectFromShader(shader, sw), null, null, CompilerOptions.Debug, TargetPlatform.Windows); #else processor.DebugMode = EffectProcessorDebugMode.Auto; CompiledEffectContent compiledEffect = processor.Process(effectContent, context); //CompiledEffect compiledEffect = Effect.CompileEffectFromSource(GenerateEffectFromShader(shader, sw), null, null, CompilerOptions.None, TargetPlatform.Windows); #endif #endregion /* * if (compiledEffect.ErrorsAndWarnings.Contains("error")) * { * throw new InvalidContentException(shader.shaderName + ": " + compiledEffect.ErrorsAndWarnings); * } */ shader.compiledEffect = compiledEffect; shader.ShaderCode = compiledEffect.GetEffectCode(); processedDictionary.Add(shader.shaderName, shader); } #if DEBUG sw.Flush(); sw.Dispose(); #endif return(processedDictionary); }