protected override double GetAttenuatedContribution(Point3D modelPosition, double nonAttenuatedValue) { // distance+falloff Attenuated raw contribution double attenuatedContribution = base.GetAttenuatedContribution(modelPosition, nonAttenuatedValue); Vector3D surfaceDirection = MathEx.Normalize(modelPosition - this.position); double cosSurfaceAngle = MathEx.DotProduct(surfaceDirection, direction); double surfaceAngle = MathEx.ToDegrees(Math.Acos(cosSurfaceAngle)); double angleFactor; if (surfaceAngle <= innerConeAngle) { angleFactor = 1.0; } // Make sure we render in the larger bound, since it will affect our final error else if (surfaceAngle > innerConeAngle && surfaceAngle < (outerConeAngle + RenderTolerance.SpotLightAngleTolerance)) { // Do DX light equation fade angleFactor = cosSurfaceAngle - cosInnerConeAngle; angleFactor /= cosOuterConeAngle - cosInnerConeAngle; angleFactor = Math.Max(0, angleFactor); // angleFactor = 1.0 - angleFactor; } else // Greater than outer cone angle { angleFactor = 0.0; } double totalContribution = attenuatedContribution * angleFactor; // If we're within SpotLightAngleTolerance degrees of being unlit, then set // error based on how much light we expect if (Math.Abs(surfaceAngle - outerConeAngle) < RenderTolerance.SpotLightAngleTolerance) { // This is so close, we may or may not light this point // So we compute the maximum possible error by scaling (255,255,255) by the // light contribution we would have if we DID light it. Color expected = ColorOperations.ScaleOpaque(lightColor, totalContribution); Color angleError = ColorOperations.Modulate(expected, lightToleranceColor); lastIlluminationError = ColorOperations.Add(lastIlluminationError, angleError); } return(totalContribution); }
protected virtual double GetAttenuatedContribution(Point3D modelPosition, double nonAttenuatedValue) { Vector3D lightDirection = this.position - modelPosition; double distance = MathEx.Length(lightDirection); double attenuation = constantAttenuation + linearAttenuation * distance + quadraticAttenuation * distance * distance; // We want to make sure the we have no augmenting or negative attenuation attenuation = Math.Max(1.0, attenuation); double finalContribution; if (distance > range) // We don't light things that are out of range { finalContribution = 0.0; } else { // We take specular and diffuse and divide to attenuate finalContribution = nonAttenuatedValue / attenuation; } // Prevent negative contributions finalContribution = Math.Max(finalContribution, 0); if (Math.Abs(distance - range) < RenderTolerance.LightingRangeTolerance) { // This is so close, we may or may not light this point // So we compute the maximum possible error by scaling (255,255,255) by the // light contribution we would have if we DID light it. Color expected = ColorOperations.ScaleOpaque(lightColor, finalContribution); Color rangeError = ColorOperations.Modulate(expected, lightToleranceColor); // We then add this error to our accumulated error for this illumination call lastIlluminationError = ColorOperations.Add(lastIlluminationError, rangeError); } // We don't clamp to 1, since this would break attenuating the falloff // of a spotlight. return(finalContribution); }
protected double GetSpecularContribution(Vector3D view, Vector3D normal, Vector3D light, double exponent) { System.Diagnostics.Debug.Assert(RenderTolerance.SpecularLightDotProductTolerance >= 0); // Make vectors unit length normal = MathEx.Normalize(normal); light = MathEx.Normalize(light); view = MathEx.Normalize(view); double spec = 0.0; double nDotl = MathEx.DotProduct(light, normal); if (nDotl >= -RenderTolerance.SpecularLightDotProductTolerance) { Vector3D half = MathEx.Normalize(light + view); spec = Math.Max(MathEx.DotProduct(normal, half), 0); spec = Math.Pow(spec, exponent); // We want a +/- tolerance bound on the dot product computation if (Math.Abs(nDotl) <= RenderTolerance.SpecularLightDotProductTolerance) { // This is so close, we may or may not light this point // So we compute the maximum possible error by scaling (0,255,255,255) by the // light contribution we would have if we DID light it. Color expected = ColorOperations.ScaleOpaque(lightColor, spec); Color specError = ColorOperations.Modulate(expected, lightToleranceColor); // We then add this error to our accumulated error for this illumination call lastIlluminationError = ColorOperations.Add(lastIlluminationError, specError); // ignore it for our rendering spec = 0.0; } } return(spec); }
/// <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 ); } }