private static void GenerateLightChannels(StringBuilder stream, Material mat) { stream.AppendLine($"\t// Color channels"); for (int i = 0; i < mat.NumChannelControls; i++) { ColorChannelControl color = mat.ColorChannelControls[i * 2]; ColorChannelControl alpha = mat.ColorChannelControls[(i * 2) + 1]; if (color.Equals(alpha)) { stream.AppendLine($"\t// Color and Alpha for control { i } were the same! Optimizing..."); stream.AppendLine(GenerateColorChannel(color, i, $"v_Color{ i }")); } else { stream.AppendLine($"\t// Color { i }"); stream.Append(GenerateColorChannel(color, i, "t_ColorChanTemp")); stream.AppendLine($"\tv_Color{ i }.rgb = t_ColorChanTemp.rgb;"); stream.AppendLine(); stream.AppendLine($"\t// Alpha { i }"); stream.Append(GenerateColorChannel(alpha, i, "t_ColorChanTemp")); stream.AppendLine($"\tv_Color{ i }.a = t_ColorChanTemp.a;"); stream.AppendLine(); } } }
private static string GenerateAttnFn(ColorChannelControl chan, string light_name) { string attn = $"max(0.0, dot(t_LightDeltaDir, {light_name}.Direction.xyz))"; string cosAttn = $"max(0.0, ApplyAttenuation({ light_name }.CosAtten.xyz, { attn }))"; string output = "\t// This is the falloff behavior of the light as an object moves *around* it.\n"; output += $"\tangle_attenuation = { cosAttn };\n\n"; output += "\t// This is the falloff behavior of the light as an object moves *away from* it.\n"; output += "\tdist_attenuation = "; switch (chan.AttenuationFunction) { case GXAttenuationFunction.None: output += "1.0;\n\n"; break; case GXAttenuationFunction.Spot: output += $"dot({ light_name }.DistAtten.xyz, vec3(1.0, t_LightDeltaDist, t_LightDeltaDist2));\n\n"; break; case GXAttenuationFunction.Spec: output += $"ApplyAttenuation({ light_name }.DistAtten.xyz, { attn });\n\n"; break; default: return(""); } return(output); }
private static string GenerateColorChannel(ColorChannelControl chan, int index, string output_name) { StringBuilder stream = new StringBuilder(); string matColorSource = chan.MaterialSrc == GXColorSrc.Vertex ? $"a_Color{ index }" : $"MaterialColors[{ index }]"; string ambColorSource = chan.AmbientSrc == GXColorSrc.Vertex ? $"a_Color{ index }" : $"AmbientColors[{ index }]"; string generateLightAccum = ""; if (chan.LightingEnabled) { generateLightAccum = $"\tt_LightAccum = { ambColorSource };\n"; generateLightAccum += "\n\t// Lighting calculations! Trig ahoy!\n\n"; for (int i = 0; i < 8; i++) { GXLightMask cur_mask = GXLightMask.Light0 + i; if (!chan.LitMask.HasFlag(cur_mask)) { continue; } string light_name = $"LightParams[{ i }]"; generateLightAccum += "\t// t_LightDelta is a vector pointing from the light to the vertex.\n"; generateLightAccum += "\t// Because the dot product of a vector with itself is the vector's length squared,\n"; generateLightAccum += "\t// we can square root the dot product to get the distance between the light and the vertex.\n"; generateLightAccum += $"\tt_LightDelta = { light_name }.Position.xyz - v_Position.xyz;\n"; generateLightAccum += "\tt_LightDeltaDist2 = dot(t_LightDelta, t_LightDelta);\n"; generateLightAccum += "\tt_LightDeltaDist = sqrt(t_LightDeltaDist2);\n\n"; generateLightAccum += "\t// Dividing a vector by its length normalizes it.\n"; generateLightAccum += "\t// Doing this with our difference vector gives us the direction of the vertex from the light, without the magnitude.\n"; generateLightAccum += "\tt_LightDeltaDir = t_LightDelta / t_LightDeltaDist;\n\n"; generateLightAccum += "\t// This is how much light is hitting the vertex - the cosine of the angle of incidence between the vertex and the light.\n"; generateLightAccum += $"\tdiffuse_coeff = { GetDiffFn(chan) };\n\n"; generateLightAccum += GenerateAttnFn(chan, light_name); generateLightAccum += "\t// The final color is the ambient color (the base or 'black') with the light's color added to it.\n"; generateLightAccum += "\t// But the color of the light is dampened by the angle at which the light is hitting our vertex (diffuse_coeff)\n"; generateLightAccum += "\t// and the ratio of the angle and distance attenuation behaviors (angle / dist).\n"; generateLightAccum += $"\tt_LightAccum += diffuse_coeff * (angle_attenuation / dist_attenuation) * { light_name }.Color;\n"; } } else { generateLightAccum = "\t// Lighting isn't being applied, so instead of calculating it we'll say the vertex is getting a full blast of white light.\n"; generateLightAccum += "\tt_LightAccum = vec4(1.0);"; } stream.AppendLine(generateLightAccum); stream.AppendLine($"\t{ output_name } = { matColorSource } * clamp(t_LightAccum, 0.0, 1.0);"); return(stream.ToString()); }
private static void GenerateLightVertexShader(StringBuilder stream, ColorChannelControl channelControl, int lightIndex, string lightAccumSwizzle, int numSwizzleComponents) { switch (channelControl.AttenuationFunction) { case GXAttenuationFunction.None: stream.AppendFormat("\tldir = normalize(Lights[{0}].Position.xyz - worldPos.xyz);\n", lightIndex); stream.AppendLine("\tattn = 1.0;"); stream.AppendLine("\tif(length(ldir) == 0.0)\n\t\tldir = _norm0;"); break; case GXAttenuationFunction.Spec: stream.AppendFormat("\tldir = normalize(Lights[{0}].Position.xyz - worldPos.xyz);\n", lightIndex); stream.AppendFormat("\tattn = (dot(_norm0, ldir) >= 0.0) ? max(0.0, dot(_norm0, Lights[{0}].Direction.xyz)) : 0.0;\n", lightIndex); stream.AppendFormat("\tcosAttn = Lights[{0}].CosAtten.xyz;\n", lightIndex); stream.AppendFormat("\tdistAttn = {1}(Lights[{0}].DistAtten.xyz);\n", lightIndex, (channelControl.DiffuseFunction == GXDiffuseFunction.None) ? "" : "normalize"); stream.AppendFormat("\tattn = max(0.0f, dot(cosAttn, vec3(1.0, attn, attn*attn))) / dot(distAttn, vec3(1.0, attn, attn*attn));"); break; case GXAttenuationFunction.Spot: stream.AppendFormat("\tldir = normalize(Lights[{0}].Position.xyz - worldPos.xyz);\n", lightIndex); stream.AppendLine("\tdist2 = dot(ldir, ldir);"); stream.AppendLine("\tdist = sqrt(dist2);"); stream.AppendLine("\tldir = ldir/dist;"); stream.AppendFormat("\tattn = max(0.0, dot(ldir, Lights[{0}].Direction.xyz));\n", lightIndex); stream.AppendFormat("\tattn = max(0.0, Lights[{0}].CosAtten.x + Lights[{0}].CosAtten.y*attn + Lights[{0}].CosAtten.z*attn*attn) / dot(Lights[{0}].DistAtten.xyz, vec3(1.0, dist, dist2));\n", lightIndex); break; default: Console.WriteLine("Unsupported AttenuationFunction Value: {0}", channelControl.AttenuationFunction); break; } switch (channelControl.DiffuseFunction) { case GXDiffuseFunction.None: stream.AppendFormat("\tlightAccum{1} += attn * Lights[{0}].Color;\n", lightIndex, lightAccumSwizzle); break; case GXDiffuseFunction.Signed: case GXDiffuseFunction.Clamp: stream.AppendFormat("\tlightAccum{1} += attn * {2}dot(ldir, _norm0)) * vec{3}(Lights[{0}].Color{1});\n", lightIndex, lightAccumSwizzle, channelControl.DiffuseFunction != GXDiffuseFunction.Signed ? "max(0.0," : "(", numSwizzleComponents); break; default: Console.WriteLine("Unsupported DiffuseFunction Value: {0}", channelControl.AttenuationFunction); break; } stream.AppendLine(); }
private static string GetDiffFn(ColorChannelControl chan) { string dot = "dot(t_Normal, t_LightDeltaDir)"; switch (chan.DiffuseFunction) { case GXDiffuseFunction.None: return("1.0"); case GXDiffuseFunction.Clamp: return($"max({ dot }, 0.0)"); case GXDiffuseFunction.Signed: return(dot); default: return(""); } }
public static string GenerateVertexShader(Material mat, MAT3 data) { StringBuilder stream = new StringBuilder(); // Shader Header stream.AppendLine("// Automatically Generated File. All changes will be lost."); stream.AppendLine("#version 330 core"); stream.AppendLine(); // Examine the attributes the mesh has so we can ensure the shader knows about the incoming data. // I don't think this is technically right, but meh. stream.AppendLine("// Per-Vertex Input"); if (mat.VtxDesc.AttributeIsEnabled(ShaderAttributeIds.Position)) { stream.AppendLine("in vec3 RawPosition;"); } if (mat.VtxDesc.AttributeIsEnabled(ShaderAttributeIds.Normal)) { stream.AppendLine("in vec3 RawNormal;"); } if (mat.VtxDesc.AttributeIsEnabled(ShaderAttributeIds.Color0)) { stream.AppendLine("in vec4 RawColor0;"); } if (mat.VtxDesc.AttributeIsEnabled(ShaderAttributeIds.Color1)) { stream.AppendLine("in vec4 RawColor1;"); } if (mat.VtxDesc.AttributeIsEnabled(ShaderAttributeIds.Tex0)) { stream.AppendLine("in vec2 RawTex0;"); } if (mat.VtxDesc.AttributeIsEnabled(ShaderAttributeIds.Tex1)) { stream.AppendLine("in vec2 RawTex1;"); } if (mat.VtxDesc.AttributeIsEnabled(ShaderAttributeIds.Tex2)) { stream.AppendLine("in vec2 RawTex2;"); } if (mat.VtxDesc.AttributeIsEnabled(ShaderAttributeIds.Tex3)) { stream.AppendLine("in vec2 RawTex3;"); } if (mat.VtxDesc.AttributeIsEnabled(ShaderAttributeIds.Tex4)) { stream.AppendLine("in vec2 RawTex4;"); } if (mat.VtxDesc.AttributeIsEnabled(ShaderAttributeIds.Tex5)) { stream.AppendLine("in vec2 RawTex5;"); } if (mat.VtxDesc.AttributeIsEnabled(ShaderAttributeIds.Tex6)) { stream.AppendLine("in vec2 RawTex6;"); } if (mat.VtxDesc.AttributeIsEnabled(ShaderAttributeIds.Tex7)) { stream.AppendLine("in vec2 RawTex7;"); } stream.AppendLine(); stream.AppendLine("// Output (Interpolated)"); stream.AppendLine(); // TEV uses up to 4 channels to accumulate the result of Per-Vertex Lighting/Material/Ambient lighting. // Color0, Alpha0, Color1, and Alpha1 are the four possible channel names. stream.AppendFormat("// NumChannelControls: {0}\n", mat.NumChannelControls); stream.AppendFormat("out vec4 colors_0;\n"); stream.AppendFormat("out vec4 colors_1;\n"); stream.AppendLine(); // TEV can generate up to 16 (?) sets of Texture Coordinates by taking an incoming data value (UV, POS, NRM, BINRM, TNGT) and transforming it by a matrix. stream.AppendFormat("// NumTexGens: {0}\n", mat.NumTexGensIndex); for (int i = 0; i < mat.NumTexGensIndex; i++) { stream.AppendFormat("out vec3 TexGen{0};\n", i); } stream.AppendLine(); // Declare shader Uniforms coming in from the CPU. stream.AppendLine("// Uniforms"); stream.AppendLine ( "uniform mat4 ModelMtx;\n" + "uniform mat4 ViewMtx;\n" + "uniform mat4 ProjMtx;\n" + "\n" + "uniform mat4 TexMtx[10];\n" + "uniform mat4 PostMtx[20];\n" + "uniform vec4 COLOR0_Amb;\n" + "uniform vec4 COLOR0_Mat;\n" + "uniform vec4 COLOR1_Mat;\n" + "uniform vec4 COLOR1_Amb;\n" + "\n" + "struct GXLight\n" + "{\n" + " vec4 Position;\n" + " vec4 Direction;\n" + " vec4 Color;\n" + " vec4 CosAtten; //AngleAtten\n" + // 1.875000, 0, 0 ? " vec4 DistAtten;\n" + // 1.875000, 0, 0 ? "};\n" + "\n" + "layout(std140) uniform LightBlock\n" + "{\n\tGXLight Lights[8];\n};\n" ); stream.AppendLine(); // Main Shader Code stream.AppendLine("// Main Vertex Shader"); stream.AppendLine("void main()\n{"); stream.AppendLine("\tmat4 MVP = ProjMtx * ViewMtx * ModelMtx;"); stream.AppendLine("\tmat4 MV = ViewMtx * ModelMtx;"); if (mat.VtxDesc.AttributeIsEnabled(ShaderAttributeIds.Position)) { stream.AppendLine("\tgl_Position = MVP * vec4(RawPosition, 1);"); stream.AppendLine("\tvec4 worldPos = ModelMtx * vec4(RawPosition, 1);"); } stream.AppendLine(); // Do Color Channel Fixups if (mat.NumChannelControls < 2) { if (mat.VtxDesc.AttributeIsEnabled(ShaderAttributeIds.Color1)) { stream.AppendFormat("\tcolors_1 = RawColor1;\n"); } else { stream.AppendFormat("\tcolors_1 = vec4(1, 1, 1, 1);\n"); } } stream.AppendLine(); // TEV Channel Colors. // A vertex can have two colors each (Color0, Color1) and each color has two channels - RGB and A. This gives us // up to 4 channels, color0, color1, alpha0, and alpha1. Channels are associated with an ambient color/alpha which can // come from a variety of sources - vertex colors, or special ambient and material registers. The register colors // are set in GX via the command: GXSetChanAmbColor(GXChanneLID chan, GXColor amb_color), and GXSetChanMatColor(GXChannelID chan, GXColor mat_color); // Now, the source for each channel can be controlled by another command: // GXSetChanCtrl(GXCHannelID chan, bool enable, GXColorSrc amb_src, GXColorSrc mat_src, GXLightID light_mask, GXDiffuseFn diff_fn, GXAttnFn attn_fn); // // If the lighting channel is disabled, then the material color for that channel is passed through unmodified. The mat_src parameter specifies if the // material color comes from the Vertex Color, or from the Material Register. If the channel is enabled, then lighting needs to be computed for each light // enabled in the light_mask. stream.AppendLine("\tvec4 ambColor = vec4(1,1,1,1);\n\tvec4 matColor = vec4(1,1,1,1);\n\tvec4 lightAccum = vec4(0,0,0,0);\n\tvec4 lightFunc;"); stream.AppendLine("\tvec3 ldir; float dist; float dist2; float attn;"); // Declaring these all anyways in case we use lighting. if (mat.VtxDesc.AttributeIsEnabled(ShaderAttributeIds.Normal)) { stream.AppendLine("\tvec3 _norm0 = RawNormal.xyz;"); } else { stream.AppendLine("\tvec3 _norm0 = vec3(0.0, 0.0, 0.0);"); } stream.AppendFormat("\t// {0} Channel Controller(s).\n", mat.NumChannelControls); for (int i = 0; i < mat.NumChannelControls; i++) { ColorChannelControl channelControl = mat.ColorChannelControls[i]; stream.AppendFormat("\t// Channel Control: {0} - LightingEnabled: {1} MaterialSrc: {2} LightMask: {3} DiffuseFn: {4} AttenuationFn: {5} AmbientSrc: {6}\n", i, channelControl.LightingEnabled, channelControl.MaterialSrc, channelControl.LitMask, channelControl.DiffuseFunction, channelControl.AttenuationFunction, channelControl.AmbientSrc); string swizzle, channel; switch (i) { case /* Color0 */ 0: channel = "0"; swizzle = ".rgb"; break; case /* Alpha0 */ 1: channel = "0"; swizzle = ".a"; break; case /* Color1 */ 2: channel = "1"; swizzle = ".rgb"; break; case /* Alpha1 */ 3: channel = "1"; swizzle = ".a"; break; // ToDo: This is wrong. There's a maximum of 2 color channels default: Console.WriteLine("Unknown Color Channel Control Index: {0}", i); continue; } bool isAlphaChannel = i % 2 != 0; string channelTarget = string.Format("colors_{0}", channel); bool ambSrcVtx = channelControl.AmbientSrc == GXColorSrc.Vertex; bool matSrcVtx = channelControl.MaterialSrc == GXColorSrc.Vertex; string ambColorSrc = string.Format("{0}{1}", (ambSrcVtx ? "RawColor" : "COLOR"), channel + (ambSrcVtx ? "" : "_Amb")); string matColorSrc = string.Format("{0}{1}", (matSrcVtx ? "RawColor" : "COLOR"), channel + (matSrcVtx ? "" : "_Mat")); stream.AppendFormat("\tambColor = {0};\n", ambColorSrc); stream.AppendFormat("\tmatColor = {0};\n", matColorSrc); for (int l = 0; l < 8; l++) { bool isLit = channelControl.LitMask.HasFlag((GXLightMask)(1 << l)); if (isLit) { stream.AppendFormat("\t// ChannelControl: {0} Light: {1}\n", i, l); GenerateLightVertexShader(stream, channelControl, l, swizzle, isAlphaChannel ? 1 : 3); } } if (channelControl.LightingEnabled) { stream.AppendLine("\tvec4 illum = clamp(ambColor + lightAccum, 0, 1);"); } stream.AppendFormat("\tlightFunc = {0};\n", channelControl.LightingEnabled ? "illum" : "vec4(1.0, 1.0, 1.0, 1.0)"); stream.AppendFormat("\t{0}{1} = (matColor * lightFunc){1};\n", channelTarget, swizzle); // Not sure if this is right, but if a single color channel is enabled then the alpha component of color_0 never gets assigned // and then something tries to use it and it's empty instead of being the ambSrc/matSrc alpha. if (mat.NumChannelControls == 1 || mat.NumChannelControls == 3) { // ToDo: https://github.com/dolphin-emu/dolphin/blob/master/Source/Core/VideoCommon/LightingShaderGen.h#L184 looks like a better implementation stream.AppendLine("\t// Doing an unknown fixup. There's only one color channel enabled, so we never write to the alpha of the color_*, and thus it never gets initialized."); stream.AppendFormat("\t{0}.a = matColor.a;\n", channelTarget); } } // TEV "TexGen" Texture Coordinate Generation // TEV can generate texture coordinates on the fly from a variety of sources. The various ways all follow the form of: // dst_coord = func(src_param, mtx) - that is, the destination coordinate is generated by multiplying an input source by a 2x4 or 3x4 matrix. // The input coordinates can come from one of the following locations: TEX0-7, POS, NRM, BINRM, TANGENT. // GX has a default set of texture matrices (GXTexMtx enum). stream.AppendFormat("\t// {0} Texture Coordinate Generators.\n", mat.NumTexGensIndex); stream.Append("\tvec4 coord;\n"); for (int i = 0; i < mat.NumTexGensIndex; i++) { TexCoordGen texGen = mat.TexGenInfoIndexes[i]; stream.AppendFormat("\t// TexGen: {0} Type: {1} Source: {2} TexMatrixIndex: {3}\n", i, texGen.Type, texGen.Source, texGen.TexMatrixSource); stream.AppendLine("\t{"); // False scope block so we can re-declare variables string texGenSource; switch (texGen.Source) { case GXTexGenSrc.Position: texGenSource = "vec4(RawPosition.xyz, 1.0)"; break; case GXTexGenSrc.Normal: texGenSource = "vec4(_norm0.xyz, 1.0)"; break; case GXTexGenSrc.Color0: texGenSource = "colors_0"; break; case GXTexGenSrc.Color1: texGenSource = "colors_1"; break; case GXTexGenSrc.Binormal: texGenSource = "vec4(RawBinormal.xyz, 1.0)"; break; case GXTexGenSrc.Tangent: texGenSource = "vec4(RawTangent.xyz, 1.0)"; break; case GXTexGenSrc.Tex0: case GXTexGenSrc.Tex1: case GXTexGenSrc.Tex2: case GXTexGenSrc.Tex3: case GXTexGenSrc.Tex4: case GXTexGenSrc.Tex5: case GXTexGenSrc.Tex6: case GXTexGenSrc.Tex7: texGenSource = string.Format("vec4(RawTex{0}.xy, 1.0, 1.0)", ((int)texGen.Source - (int)GXTexGenSrc.Tex0)); break; // This implies using a texture coordinate set already generated by TEV. case GXTexGenSrc.TexCoord0: case GXTexGenSrc.TexCoord1: case GXTexGenSrc.TexCoord2: case GXTexGenSrc.TexCoord3: case GXTexGenSrc.TexCoord4: case GXTexGenSrc.TexCoord5: case GXTexGenSrc.TexCoord6: texGenSource = string.Format("vec4(TexGen{0}.xy, 1.0, 1.0)", ((int)texGen.Source - (int)GXTexGenSrc.TexCoord0)); break; default: Console.WriteLine("Unsupported TexGenSrc: {0}, defaulting to TexCoord0.", texGen.Source); texGenSource = "RawTex0"; break; } stream.AppendFormat("\t\tcoord = {0};\n", texGenSource); TexMatrixProjection matrixProj = TexMatrixProjection.TexProj_ST; if (texGen.TexMatrixSource != GXTexMatrix.Identity) { matrixProj = mat.TexMatrixIndexes[(((int)texGen.TexMatrixSource) - 30) / 3].Projection; } // TEV Texture Coordinate generation takes the general form: // dst_coord = func(src_param, mtx), where func is GXTexGenType, src_param is GXTexGenSrc, and mtx is GXTexMtx. string destCoord = string.Format("TexGen{0}", i); switch (texGen.Type) { case GXTexGenType.Matrix3x4: case GXTexGenType.Matrix2x4: //if(mat.VtxDesc.AttributeIsEnabled(ShaderAttributeIds.TexMtxId)) //{ // stream.AppendFormat("int temp = {0}.z\n", destCoord); // if (texMtx.Projection == TexMatrixProjection.TexProj_STQ) // stream.AppendFormat("{0}.xyz = vec3(dot({1}, TexMtx[temp]), dot({1}, TexMtx[temp+1]), dot({1}, TexMtx[temp+2]));\n", destCoord, texGenSource); // else // stream.AppendFormat("{0}.xyz = vec3(dot({1}, TexMtx[temp]), dot({1}, TexMtx[temp+1]), 1);\n", destCoord, texGenSource); //} //else { if (texGen.TexMatrixSource != GXTexMatrix.Identity) { if (matrixProj == TexMatrixProjection.TexProj_STQ) { stream.AppendFormat("\t\t{0}.xyz = vec3(dot(coord, TexMtx[{1}][0]), dot(coord, TexMtx[{1}][1]), dot(coord, TexMtx[{1}][2]));\n", destCoord, i); //3x4 } else { stream.AppendFormat("\t\t{0}.xyz = vec3(dot(coord, TexMtx[{1}][0]), dot(coord, TexMtxs[{1}][1]), 1);\n", destCoord, i); //2x4 } } else { stream.AppendFormat("\t\t{0} = coord.xyz;\n", destCoord); } } break; case GXTexGenType.SRTG: stream.AppendFormat("\t{0} = vec3({1}.rg, 1);\n", destCoord, texGenSource); break; case GXTexGenType.Bump0: case GXTexGenType.Bump1: case GXTexGenType.Bump2: case GXTexGenType.Bump3: case GXTexGenType.Bump4: case GXTexGenType.Bump5: case GXTexGenType.Bump6: case GXTexGenType.Bump7: // Transform the light dir into tangent space. // ldir = normalize(Lights[{0}.Position.xyz - RawPosition.xyz);\n {0} = "texInfo.embosslightshift"; // destCoord = TexGen{0} + float3(dot(ldir, _norm0), dot(ldir, RawBinormal), 0.0);\n", {0} = i, {1} = "texInfo.embosssourceshift"; default: Console.WriteLine("Unsupported TexGenType: {0}", texGen.Type); break; } // Dual Tex Transforms if (mat.PostTexMatrixIndexes.Length > 0) { //TexMatrix postTexMtx = mat.PostTexMatrixIndexes // ToDo: Should this just be... i? lol Console.WriteLine("PostMtx transforms are... not really anything supported?"); //TexMatrix postTexMtx = mat.PostTexMatrixIndexes[((int)texGen.TexMatrixSource) - 30]; //int postIndex = postTexMtx. mat.PostTexMatrixIndexes[i]; //stream.AppendFormat("float4 P0 = PostMtx[{0}];\n", postIndex); //stream.AppendFormat("float4 P1 = PostMtx[{0}];\n", postIndex + 1); //stream.AppendFormat("float4 P2 = PostMtx[{0}];\n", postIndex + 2); //stream.AppendFormat("{0}.xyz = vec3(dot(P0.xyz, {0}.xyz) + P0.w, dot(P1.xyz, {0}.xyz) + P1.w, dot(P2.xyz, {0}.xyz) + P2.w);\n", destCoord); } stream.AppendLine("\t}"); // End of false-scope block. } // Append the tail end of our shader file. stream.AppendLine("}"); stream.AppendLine(); return(stream.ToString()); }