/// <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); } } }
/// <summary> /// Writes a pixel to the buffer according to depth-test for single pass rendering. /// </summary> /// <param name="x">x index</param> /// <param name="y">y index</param> /// <param name="z">Depth buffer value</param> /// <param name="premultColor">Premultiplied Frame buffer value</param> /// <param name="premultTolerance">Premultiplied Tolerance buffer value</param> public void SetPixel(int x, int y, float z, Color premultColor, Color premultTolerance) { if (x >= 0 && x < width && y >= 0 && y < height && z >= 0 && z <= 1.0f) { bool isZFightingPossible = IsPixelRendered(x, y); bool drawPixel = DepthTest(z, zBuffer[x, y]); if (drawPixel) { // Write to frame buffer according to alpha value of pixel frameBuffer[x, y] = ColorOperations.PreMultipliedAlphaBlend( premultColor, frameBuffer[x, y]); // We have a pre-multiplied tolerance value toleranceBuffer[x, y] = ColorOperations.PreMultipliedToleranceBlend( premultTolerance, toleranceBuffer[x, y], premultColor.A); } // See if we were within tolerance of another test result if (isZFightingPossible) { if (DepthTestWithinTolerance(z, zBuffer[x, y], RenderTolerance.ZBufferTolerance)) { // If so, ignore this pixel, since we can't be sure if it's right toleranceBuffer[x, y] = Color.FromArgb(0, 255, 255, 255); } } else if (z < RenderTolerance.NearPlaneTolerance || 1.0 - RenderTolerance.FarPlaneTolerance < z) { // If we're right on the near/far clipping plane, we can't be sure if this pixel is right. // Note that the tolerance at the near plane will be considerably more than that of the // far plane if we are using a perspective camera to view this scene. toleranceBuffer[x, y] = Color.FromArgb(0, 255, 255, 255); } if (drawPixel && writeToZBuffer) { zBuffer[x, y] = z; } } }
/// <summary> /// Blend the contents of the RenderBuffer with a background color /// </summary> public void AddBackground(Color opaqueBackgroundColor) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // We keep our colors and tolerances premultiplied. // Do the tolerance blend first so that we have the correct alpha value from the framebuffer toleranceBuffer[x, y] = ColorOperations.PreMultipliedToleranceBlend( toleranceBuffer[x, y], toleranceBufferClearValue, frameBuffer[x, y].A); frameBuffer[x, y] = ColorOperations.PreMultipliedAlphaBlend( frameBuffer[x, y], opaqueBackgroundColor); } } }
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> /// 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 ); } }