/// <summary> /// Specifies the vertex attributes for the <see cref="ShaderProgram"/> /// </summary> /// <param name="attribs">The vertex attributes in order of index.</param> /// <param name="attribNames">The names of the attributes orderd by attribute index.</param> public void SpecifyVertexAttribs(ReadOnlySpan <VertexAttribSource> attribs, ReadOnlySpan <string> attribNames) { int length = 0; for (int i = 0; i < attribs.Length; i++) { if (!attribs[i].IsPadding) { length++; } } specifiedAttribs = new SpecifiedShaderAttrib[length]; int index = 0; for (int i = 0; i < attribs.Length; i++) { if (!attribs[i].IsPadding) { specifiedAttribs[index] = new SpecifiedShaderAttrib(attribNames[index], attribs[i].AttribDescription.AttribType); index++; } } }
/// <summary> /// Creates a <see cref="SimpleShaderProgram"/> using the current values on this /// <see cref="SimpleShaderProgramBuilder"/>. /// </summary> /// <param name="graphicsDevice">The <see cref="GraphicsDevice"/> the <see cref="SimpleShaderProgram"/> will use.</param> public SimpleShaderProgram Create(GraphicsDevice graphicsDevice) { const string DifferentVertexAttribIndicesError = "All specified vertex attribute indices must be different."; VertexShaderLog = null; FragmentShaderLog = null; ProgramLog = null; if (graphicsDevice == null) { throw new ArgumentNullException(nameof(graphicsDevice)); } if (PositionAttributeIndex < 0) { throw new InvalidOperationException("A vertex attribute index for Position must always be specified."); } if (DiscardTransparentFragments && !float.IsFinite(TransparentFragmentThreshold)) { throw new InvalidOperationException(nameof(TransparentFragmentThreshold) + " must be a finite value."); } if (LightingEnabled) { if (NormalAttributeIndex < 0) { throw new InvalidOperationException("Using lighting requires a vertex Normal attribute."); } if (NormalAttributeIndex == PositionAttributeIndex) { throw new InvalidOperationException(DifferentVertexAttribIndicesError); } } if (VertexColorsEnabled) { if (ColorAttributeIndex < 0) { throw new InvalidOperationException("Using vertex colors requires a vertex Color attribute."); } if (ColorAttributeIndex == PositionAttributeIndex || ColorAttributeIndex == NormalAttributeIndex) { throw new InvalidOperationException(DifferentVertexAttribIndicesError); } } if (TextureEnabled) { if (TexCoordsAttributeIndex < 0) { throw new InvalidOperationException("Using textures requires a vertex TexCoords attribute."); } if (TexCoordsAttributeIndex == PositionAttributeIndex || TexCoordsAttributeIndex == NormalAttributeIndex || TexCoordsAttributeIndex == ColorAttributeIndex) { throw new InvalidOperationException(DifferentVertexAttribIndicesError); } } StringBuilder builder = GetStringBuilder(); ShaderProgramBuilder programBuilder = new ShaderProgramBuilder(); uint programHandle = 0; try { bool useLighting = LightingEnabled; builder.Append("#version "); builder.Append(GLSLVersionString ?? graphicsDevice.GLMajorVersion.ToString() + graphicsDevice.GLMinorVersion.ToString() + "0 core"); builder.Append("\n\n"); if (!ExcludeWorldMatrix) { builder.Append("uniform mat4 World;\n"); } builder.Append("uniform mat4 View, Projection;\n"); builder.Append("\nin vec3 vPosition;\n"); if (useLighting) { builder.Append("in vec3 vNormal;\n"); } if (VertexColorsEnabled) { builder.Append("in vec4 vColor;\n"); } if (TextureEnabled) { builder.Append("in vec2 vTexCoords;\n"); } builder.Append('\n'); if (useLighting) { builder.Append("out vec3 fPosition;\nout vec3 fNormal;\n"); } if (VertexColorsEnabled) { builder.Append("out vec4 fColor;\n"); } if (TextureEnabled) { builder.Append("out vec2 fTexCoords;\n"); } builder.Append("\nvoid main() {\n"); if (ExcludeWorldMatrix) { builder.Append("vec4 worldPos = vec4(vPosition, 1.0);\n"); } else { builder.Append("vec4 worldPos = World * vec4(vPosition, 1.0);\n"); } builder.Append("gl_Position = Projection * View * worldPos;\n\n"); if (useLighting) { builder.Append("fPosition = worldPos.xyz;\n"); if (ExcludeWorldMatrix) { builder.Append("fNormal = vNormal;\n"); } else { builder.Append("fNormal = (World * vec4(vNormal, 0.0)).xyz;\n"); } } if (VertexColorsEnabled) { builder.Append("fColor = vColor;\n"); } if (TextureEnabled) { builder.Append("fTexCoords = vTexCoords;\n"); } builder.Append('}'); programBuilder.VertexShaderCode = builder.ToString(); builder.Clear(); builder.Append("#version "); builder.Append(GLSLVersionString ?? graphicsDevice.GLMajorVersion.ToString() + graphicsDevice.GLMinorVersion.ToString() + "0 core"); builder.Append("\n\n"); builder.Append("uniform vec4 Color;\n\n"); if (TextureEnabled) { builder.Append("uniform sampler2D samp;\n\n"); } if (useLighting) { builder.Append("uniform vec3 cameraPos;\n"); builder.Append("uniform vec3 ambientLightColor;\n"); builder.Append("uniform float reflectivity;\n"); builder.Append("uniform float specularPower;\n"); for (int i = 0; i < DirectionalLights; i++) { string itostring = i.ToString(); builder.Append("uniform vec3 dLightDir"); builder.Append(itostring); builder.Append(";\nuniform vec3 dLightDiffColor"); builder.Append(itostring); builder.Append(";\nuniform vec3 dLightSpecColor"); builder.Append(itostring); builder.Append(";\n"); } for (int i = 0; i < PositionalLights; i++) { string itostring = i.ToString(); builder.Append("uniform vec3 pLightPos"); builder.Append(itostring); builder.Append(";\nuniform vec3 pLightDiffColor"); builder.Append(itostring); builder.Append(";\nuniform vec3 pLightSpecColor"); builder.Append(itostring); builder.Append(";\nuniform vec3 pAttConfig"); builder.Append(itostring); builder.Append(";\n"); } } if (useLighting) { builder.Append("\nin vec3 fPosition;\nin vec3 fNormal;\n"); } if (VertexColorsEnabled) { builder.Append("in vec4 fColor;\n"); } if (TextureEnabled) { builder.Append("in vec2 fTexCoords;\n"); } builder.Append("\nout vec4 FragColor;\n"); if (useLighting) { builder.Append("\nvec3 calcDirLight(in vec3 norm, in vec3 toCamVec, in vec3 lDir, in vec3 diffCol, in vec3 specCol) {\n"); builder.Append("float brightness = max(0.0, dot(norm, -lDir));\n"); builder.Append("vec3 reflectedDir = reflect(lDir, norm);\n"); builder.Append("float specFactor = max(0.0, dot(reflectedDir, toCamVec));\n"); builder.Append("float dampedFactor = pow(specFactor, specularPower);\n"); builder.Append("return brightness * diffCol + (dampedFactor * reflectivity) * specCol;\n}\n"); if (PositionalLights > 0) { builder.Append("\nvec3 calcPosLight(in vec3 norm, in vec3 toCamVec, in vec3 lPos, in vec3 diffCol, in vec3 specCol, in vec3 attConfig) {\n"); builder.Append("float d = distance(fPosition, lPos);\n"); builder.Append("return calcDirLight(norm, toCamVec, normalize(fPosition - lPos), diffCol, specCol) * clamp(1.0 / (attConfig.x + attConfig.y*d + attConfig.z*d*d), 0.0, 1.0);\n"); builder.Append("}\n"); } } builder.Append("\nvoid main() {\n"); builder.Append("vec4 finalColor = Color;\n"); if (VertexColorsEnabled) { builder.Append("finalColor *= fColor;\n"); } if (TextureEnabled) { builder.Append("finalColor *= texture(samp, fTexCoords);\n"); } if (DiscardTransparentFragments) { builder.Append("if (finalColor.A < "); builder.Append(TransparentFragmentThreshold); builder.Append(")\ndiscard;\n"); } if (useLighting) { builder.Append("vec3 unitNormal = normalize(fNormal);\n"); builder.Append("vec3 unitToCameraVec = normalize(cameraPos - fPosition);\n\n"); builder.Append("vec3 light = ambientLightColor;\n"); for (int i = 0; i < DirectionalLights; i++) { string itostring = i.ToString(); builder.Append("light += calcDirLight(unitNormal, unitToCameraVec, dLightDir"); builder.Append(itostring); builder.Append(", dLightDiffColor"); builder.Append(itostring); builder.Append(", dLightSpecColor"); builder.Append(itostring); builder.Append(");\n"); } for (int i = 0; i < PositionalLights; i++) { string itostring = i.ToString(); builder.Append("light += calcPosLight(unitNormal, unitToCameraVec, pLightPos"); builder.Append(itostring); builder.Append(", pLightDiffColor"); builder.Append(itostring); builder.Append(", pLightSpecColor"); builder.Append(itostring); builder.Append(", pAttConfig"); builder.Append(itostring); builder.Append(");\n"); } builder.Append("finalColor.xyz *= light;\n"); } builder.Append("FragColor = finalColor;\n}"); programBuilder.FragmentShaderCode = builder.ToString(); int maxAttribs = Math.Max(PositionAttributeIndex, Math.Max(NormalAttributeIndex, Math.Max(ColorAttributeIndex, TexCoordsAttributeIndex))); SpecifiedShaderAttrib[] attribs = new SpecifiedShaderAttrib[maxAttribs + 1]; for (int i = 0; i < attribs.Length; i++) { attribs[i] = new SpecifiedShaderAttrib(null, AttributeType.Float); } attribs[PositionAttributeIndex] = new SpecifiedShaderAttrib("vPosition", AttributeType.FloatVec3); if (NormalAttributeIndex >= 0) { attribs[NormalAttributeIndex] = new SpecifiedShaderAttrib(useLighting ? "vNormal" : null, AttributeType.FloatVec3); } if (ColorAttributeIndex >= 0) { attribs[ColorAttributeIndex] = new SpecifiedShaderAttrib(VertexColorsEnabled ? "vColor" : null, AttributeType.FloatVec4); } if (TexCoordsAttributeIndex >= 0) { attribs[TexCoordsAttributeIndex] = new SpecifiedShaderAttrib(TextureEnabled ? "vTexCoords" : null, AttributeType.FloatVec2); } programBuilder.SpecifyVertexAttribs(attribs); // TODO: Change the getLogs parameter to false // Also, once we know the SimpleShaderProgram's code is flawless we should change this // somehow so it doesn't raise the GraphicsDevice.ShaderCompiled event. programHandle = programBuilder.CreateInternal(graphicsDevice, out ActiveVertexAttrib[] activeAttribs, out bool hasVs, out bool hasGs, out bool hasFs, true); return(new SimpleShaderProgram(graphicsDevice, programHandle, activeAttribs, hasVs, hasGs, hasFs, VertexColorsEnabled, TextureEnabled, DirectionalLights, PositionalLights)); } catch { // If anything goes wrong, we delete the program handle (since we're not returning a // ShaderProgram so it'd get lost into oblivion otherwise) and re-throw the exception. graphicsDevice.GL.DeleteProgram(programHandle); throw; } finally { VertexShaderLog = programBuilder.VertexShaderLog; FragmentShaderLog = programBuilder.FragmentShaderLog; ProgramLog = programBuilder.ProgramLog; builder.Clear(); ReturnStringBuilder(builder); } }