/// <summary> /// Constructor that takes a background color. /// </summary> /// <param name="width">Buffer width, in pixels.</param> /// <param name="height">Buffer height, in pixels.</param> /// <param name="backgroundColor">Color for lowest blend layer. Must not be premultiplied.</param> public RenderBuffer(int width, int height, Color backgroundColor) : this(width, height) { Color background = ColorOperations.PreMultiplyColor(backgroundColor); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { frameBuffer[x, y] = background; } } }
/// <summary> /// Dictate the behavior of the shader on a per-pixel basis. /// </summary> /// <param name="v">Interpolated output from the Vertex program</param> virtual protected void ComputePixelProgram(Vertex v) { // Move color and tolerance to premultiplied space Color color = ColorOperations.PreMultiplyColor(v.Color); Color tolerance = ColorOperations.PreMultiplyTolerance(v.ColorTolerance, v.Color.A); // Send the pixel to be rendered buffer.SetPixel( (int)Math.Floor(v.ProjectedPosition.X), (int)Math.Floor(v.ProjectedPosition.Y), (float)v.ProjectedPosition.Z, color, tolerance ); }
/// <summary> /// Constructor that blends and image with a background color. /// </summary> /// <param name="image">An image that will be premultiplied and blended with the background color.</param> /// <param name="backgroundColor">Color for lowest blend layer. Must not be premultiplied.</param> public RenderBuffer(Color[,] image, Color backgroundColor) : this(image.GetLength(0), image.GetLength(1)) { Color background = ColorOperations.PreMultiplyColor(backgroundColor); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // copy image over background color using SRC over alpha blend frameBuffer[x, y] = ColorOperations.PreMultipliedAlphaBlend( ColorOperations.PreMultiplyColor(image[x, y]), background); } } }
private void ApplyDropShadow(DropShadowBitmapEffect effect, Rect effectInput) { RenderBuffer clone = Clone(); clone.boundsOverride = effectInput; double depth = MathEx.ConvertToAbsolutePixels(effect.ShadowDepth); double angle = MathEx.ToRadians(effect.Direction); Vector pixelOffset = new Vector(depth * Math.Cos(angle), depth * Math.Sin(-angle)); Color effectColor = effect.Color; effectColor = ColorOperations.ScaleAlpha(effectColor, effect.Opacity); double blurRadius = 1.0 + (effect.Softness * 9.0); int xEnd = (int)effectInput.Right; int yEnd = (int)effectInput.Bottom; for (int y = (int)effectInput.Y; y < yEnd; y++) { for (int x = (int)effectInput.X; x < xEnd; x++) { Point point = new Point(x + Const.pixelCenterX, y + Const.pixelCenterY) - pixelOffset; Color?color = clone.GetPixelSample(point, blurRadius); if (color.HasValue) { double opacity = ColorOperations.ByteToDouble(color.Value.A); Color shadow = ColorOperations.ScaleAlpha(effectColor, opacity); shadow = ColorOperations.PreMultiplyColor(shadow); frameBuffer[x, y] = ColorOperations.PreMultipliedAlphaBlend(clone.frameBuffer[x, y], shadow); if (frameBuffer[x, y] != clone.frameBuffer[x, y]) { // Transfer the tolerance (silhouette, etc) over too Color tolerance = clone.GetToleranceSample(point, blurRadius); toleranceBuffer[x, y] = ColorOperations.Max(tolerance, clone.toleranceBuffer[x, y]); } } } } }
/// <summary> /// Gouraud shading computes lighting per-vertex, in this method. /// Final pixel values are gathered from interpolating between pixels. /// Lighting is precomputed in this step per material. /// </summary> /// <param name="v">Input/Output per-vertex data.</param> protected override void ComputeVertexProgram(Vertex v) { // No color here ... v.Color = emptyColor; // Tolerance v.ColorTolerance = emptyColor; v.PrecomputedLight = new Color[textures.Length]; v.PrecomputedLightTolerance = new Color[textures.Length]; int textureIndex = 0; Point3D position = v.PositionAsPoint3D; Vector3D normal = v.Normal; // Precompute lighting for all textures foreach (TextureFilter texture in textures) { HdrColor color = emptyColor; HdrColor tolerance = emptyColor; if (texture != null) { // Lighting is done in Premultiplied color space. // // - BIG NOTE! - We do not actually premultiply the light contribution // because lighting is always opaque and premultiplying opaque colors // is a no-op. // // - Tolerance CANNOT be done in Premultiplied color space because // it needs to know the final pixel color in order to premultiply properly. // We won't know the final pixel color until ComputePixelProgram is called. // We ignore Alpha values during the computation and set the final value to // materialColor.A at the end. This is why you will not see any clamping of // light values, etc in the code below. Color materialColor = ColorOperations.PreMultiplyColor(texture.MaterialColor); switch (texture.MaterialType) { case MaterialType.Diffuse: foreach (LightEquation light in lightEquations) { Color lightContribution = light.IluminateDiffuse(position, normal); if (light is AmbientLightEquation) { // AmbientColor knobs are reminiscent of additive material passes // because the alpha value will not be considered in the final color value // (i.e. premultiply to scale RGB by alpha, then never use alpha again) Color ambientColor = ColorOperations.PreMultiplyColor(texture.AmbientColor); color += ColorOperations.Modulate(lightContribution, ambientColor); } else { color += ColorOperations.Modulate(lightContribution, materialColor); } tolerance += light.GetLastError(); } break; case MaterialType.Specular: foreach (LightEquation light in lightEquations) { // Don't need to check light equation type, since Ambient will return black Color lightContribution = light.IluminateSpecular(position, normal, texture.SpecularPower); color += ColorOperations.Modulate(lightContribution, materialColor); tolerance += light.GetLastError(); } break; case MaterialType.Emissive: color = materialColor; break; } // Alpha is only considered at the end. Overwrite whatever happened during the precomputation. // - Note that the alpha of the AmbientColor knob is NOT considered in the final value. color.A = ColorOperations.ByteToDouble(materialColor.A); } v.PrecomputedLight[textureIndex] = color.ClampedValue; v.PrecomputedLightTolerance[textureIndex] = tolerance.ClampedValue; textureIndex++; } }
/// <summary> /// Lights this pixel using precomputed lighting information. /// </summary> /// <param name="v">Interpolated vertex for this pixel position.</param> protected override void ComputePixelProgram(Vertex v) { bool rendered = false; Color totalTexturingTolerance = emptyColor; for (int pass = 0; pass < textures.Length; pass++) { // A Filter can be null if the Material or the Brush are null. // For those cases, we skip the material entirely. if (textures[pass] == null) { continue; } TextureFilter currentTexture = textures[pass]; rendered = true; // We need extra information for trilinear if (currentTexture is TrilinearTextureFilter) { ((TrilinearTextureFilter)currentTexture).MipMapFactor = v.MipMapFactor; } // Textures are not stored in premultiplied color space. // This means that we have to wait until we find the lookup tolerance before we can premultiply // (otherwise Alpha will be way off) Color texel = currentTexture.FilteredTextureLookup(v.TextureCoordinates); Color texelTolerance = emptyColor; if (currentTexture.HasErrorEstimation) { texelTolerance = currentTexture.FilteredErrorLookup( v.UVToleranceMin, v.UVToleranceMax, texel); } // Now we can premultiply. Color premultTexel = ColorOperations.PreMultiplyColor(texel); Color premultTexelTolerance = ColorOperations.PreMultiplyTolerance(texelTolerance, texel.A); // Modulate precomputed lighting (which is also premultiplied) by the Brush value Color premultColor = ColorOperations.Modulate(v.PrecomputedLight[pass], premultTexel); Color premultTolerance = ColorOperations.Modulate(v.PrecomputedLight[pass], premultTexelTolerance); // PrecomputedLightTolerance is NOT premultipled yet (see ComputeVertexProgram above). // This is because we needed to know the final alpha value of lighting * texture. // Color premultLightTolerance = ColorOperations.PreMultiplyTolerance(v.PrecomputedLightTolerance[pass], premultColor.A); premultTolerance = ColorOperations.Add(premultTolerance, premultLightTolerance); // For additive materials, we need to force the alpha channel to zero. // See notes on premultiplied blending in ColorOperations.cs if (currentTexture.MaterialType != MaterialType.Diffuse) { premultColor.A = 0x00; // Nothing needs to be done to tolerance's alpha // because the framebuffer's alpha value will be used in the blend } // we need to blend // Write to frame buffer according to alpha value of pixel v.Color = ColorOperations.PreMultipliedAlphaBlend(premultColor, v.Color); // Accumulate tolerance for each material pass totalTexturingTolerance = ColorOperations.PreMultipliedToleranceBlend( premultTolerance, totalTexturingTolerance, premultColor.A); } // Only set a pixel if we actually rendered at least one material for it ... if (rendered) { // Add texturing tolerance to our existing lighting tolerance. v.ColorTolerance = ColorOperations.Add(v.ColorTolerance, totalTexturingTolerance); // Send the pixel to be rendered buffer.SetPixel( (int)Math.Floor(v.ProjectedPosition.X), (int)Math.Floor(v.ProjectedPosition.Y), (float)v.ProjectedPosition.Z, v.Color, v.ColorTolerance ); } }