/// <summary> /// Calculates where the image will be in the cookie and creates the whole cookie texture /// </summary> void UpdateCookie(bool doBlacks = true) { if (pixelByPixel) { if (redColors == null) // have we already processed? { return; } if (doBlacks) { float resultWidth, resultHeight; resultWidth = distance / data.ratio; resultHeight = resultWidth / data.aspect; // calculate image position in pixels // calculate shift in meters Vector2 shift = new Vector2(resultWidth * (data.shift_h / 200.0f), resultHeight * (data.shift_v / 200.0f)); // position of image in meters, relative to projector centre float imageLeftMeters = maxImageEdgeDistance - (resultWidth / 2.0f) + shift.x; float imageTopMeters = maxImageEdgeDistance - (resultHeight / 2.0f) + shift.y; // posisiton of image in the cookie texture int imageLeftPixels = (int)(imageLeftMeters * metersToPixels); int imageTopPixels = (int)(imageTopMeters * metersToPixels); // size of image in the cookie texture int imageWidthPixels = (int)(resultWidth * metersToPixels); int imageHeightPixels = (int)(resultHeight * metersToPixels); // keystone width float keystoneH = data.keystone_h / 100f; if (keystoneH < 0) { keystoneH *= -1; } float keystoneMinWidth = imageWidthPixels * (1f - keystoneH); float keystoneV = data.keystone_v / 100f; if (keystoneV < 0) { keystoneV *= -1; } float keystoneMinHeight = imageHeightPixels * (1f - keystoneV); // data about where the image is located in the cookie projectedImageData = new ProjectedImageInCookieData(imageLeftPixels, imageTopPixels, imageWidthPixels, imageHeightPixels, keystoneMinWidth, keystoneMinHeight, data.keystone_h < 0, data.keystone_v < 0, imageType == ImageType.Colour, textureSize); } PixelCalcArgs args; if (projectedImage) { args = new PixelCalcArgs(projectedImage.width, projectedImage.height, imageType, doBlacks, 0, redColors.Length); } else { args = new PixelCalcArgs(0, 0, imageType, doBlacks, 0, redColors.Length); } CalculatePixels(args); // apply new colours once cookies are calculated redCookie2D.SetPixels32(redColors); redCookie2D.Apply(); if (imageType == ImageType.Colour) { greenCookie2D.SetPixels32(greenColors); greenCookie2D.Apply(); blueCookie2D.SetPixels32(blueColors); blueCookie2D.Apply(); } } else // shader-based approach { // safety nets if (projectedImage == null) // should never happen, as we create a white image when null is passed in { Debug.Log("ProjectedImage is null!"); return; } if (projectedImage as Texture2D != null) { if (((Texture2D)projectedImage).format != TextureFormat.RGBA32) { Debug.Log("Texture " + projectedImage.name + " is not correct TextureFormat RGBA32"); return; } } if (projectedImage as RenderTexture != null) { if (((RenderTexture)projectedImage).format != RenderTextureFormat.ARGB32) { Debug.Log("RenderTexture " + projectedImage.name + " is not correct RenderTextureFormat ARGB32"); return; } } // imageWithBorder is a full-size colour copy of the original image, but with a 1px black border to enable the lens shift effect Graphics.CopyTexture(projectedImage, 0, 0, 0, 0, projectedImage.width, projectedImage.height, imageWithBorder, 0, 0, blackBorderSize, blackBorderSize); // apply the shift in the cookie Graphics.Blit(imageWithBorder, imageShifted, cookieSpaceScale, cookieSpaceOffset); // split the shifted image into its 3 channels Graphics.Blit(imageShifted, (RenderTexture)redCookie, stripRed); Graphics.Blit(imageShifted, (RenderTexture)greenCookie, stripGreen); Graphics.Blit(imageShifted, (RenderTexture)blueCookie, stripBlue); } // garbage collection, otherwise RAM usage goes waaaaaaay up #if UNITY_EDITOR if (EditorApplication.isPlaying) { #endif // deallocate memory now that cookie has been created if (projectedImage as Texture2D != null) { projectedImage = null; } imageColours = null; if (!supportLiveUpdate) { redColors = greenColors = blueColors = null; } #if UNITY_EDITOR } #endif }
void CalculatePixels(PixelCalcArgs args) { int x, y, pi_x, pi_y; int rowWidth, colHeight; float f; float rowProgress, colProgress; byte red, green, blue; red = green = blue = 255; // set pixel colors for (int i = 0; i < args.endIndex; i++) { x = i % projectedImageData.textureSize; y = i / projectedImageData.textureSize; // if no H keystone, make row width constant if (projectedImageData.imageWidthInCookie == projectedImageData.keystoneMinWidth) { rowWidth = projectedImageData.imageWidthInCookie; } else { // calculate row width after keystone correction float verticalProgress = (y - projectedImageData.imageTopEdgeInCookie) / (float)projectedImageData.imageHeightInCookie; if (projectedImageData.keystoneH_flip) { verticalProgress = 1f - verticalProgress; } rowWidth = Mathf.RoundToInt(Mathf.Lerp(projectedImageData.imageWidthInCookie, projectedImageData.keystoneMinWidth, verticalProgress)); } rowProgress = (x - (projectedImageData.imageCentreH - rowWidth / 2)) / (float)rowWidth; // if no V keystone, make col height constant if (projectedImageData.imageHeightInCookie == projectedImageData.keystoneMinHeight) { colHeight = projectedImageData.imageHeightInCookie; } else { // calculate column height after keystone correction float horizontalProgress = (x - projectedImageData.imageLeftEdgeInCookie) / (float)projectedImageData.imageWidthInCookie; if (projectedImageData.keystoneV_flip) { horizontalProgress = 1f - horizontalProgress; } colHeight = Mathf.RoundToInt(Mathf.Lerp(projectedImageData.imageHeightInCookie, projectedImageData.keystoneMinHeight, horizontalProgress)); } colProgress = (y - (projectedImageData.imageCentreV - colHeight / 2)) / (float)colHeight; // inside the image row? if (y > projectedImageData.imageCentreV - colHeight / 2 && y < projectedImageData.imageCentreV + colHeight / 2 && // inside the image column? x > projectedImageData.imageCentreH - rowWidth / 2 && x < projectedImageData.imageCentreH - 1 + rowWidth / 2) { // white if no projected image if (imageColours == null) { redColors[i] = new Color32(255, 255, 255, 255); // Also set green and blue channels otherwise we get artefacts if colour box is checked if (imageType == ImageType.Colour) { greenColors[i] = new Color32(255, 255, 255, 255); blueColors[i] = new Color32(255, 255, 255, 255); } } else // use the given texture { // select which pixel to take from the image pi_x = Mathf.RoundToInt(Mathf.Lerp(0, args.srcImgWidth - 1, rowProgress)); pi_y = Mathf.RoundToInt(Mathf.Lerp(0, args.srcImgHeight - 1, colProgress)); int flatindex = (args.srcImgWidth * pi_y) + pi_x; // Colour or greyscale? switch (imageType) { case ImageType.Colour: red = (byte)Mathf.Clamp((imageColours[flatindex].r), 0, 255); redColors[i] = new Color32(255, 255, 255, red); green = (byte)Mathf.Clamp((imageColours[flatindex].g), 0, 255); greenColors[i] = new Color32(255, 255, 255, green); blue = (byte)Mathf.Clamp((imageColours[flatindex].b), 0, 255); blueColors[i] = new Color32(255, 255, 255, blue); break; default: // greyscale f = ((float)imageColours[flatindex].r + imageColours[flatindex].g + imageColours[flatindex].b) / 3f; red = (byte)Mathf.Clamp((int)f, 0, 255); redColors[i] = new Color32(255, 255, 255, red); break; } } } else if (args.doBlacks) // BLACK PIXELS { redColors[i] = new Color32(255, 255, 255, 0); if (imageType == ImageType.Colour) { greenColors[i] = new Color32(255, 255, 255, 0); blueColors[i] = new Color32(255, 255, 255, 0); } } } }