Back to Resources

Editable Image Perspective Grid & Vanishing Line

This script creates a perspective grid and vanishing line on a GUI object using the EditableImage class. The grid is created by drawing lines at regular intervals, and the vanishing line is created using the camera perspective. The grid and vanishing line can be used to create a 3D effect on a flat surface. To utilize this script ensure the parent is correct at the screenGUI portion on line 27.

LocalScript - Goes inside StarterPlayerScripts

local Players = game:GetService("Players")
local AssetService = game:GetService("AssetService")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")

local localPlayer = Players.LocalPlayer
local playerGui = localPlayer:WaitForChild("PlayerGui")
local camera = Workspace.CurrentCamera

-- Constants for perspective grid visualization
local CANVAS_SIZE = Vector2.new(512, 512)
local GRID_LINES = 12
local GRID_COLOR = Color3.new(0.2, 0.6, 0.9)
local HORIZON_COLOR = Color3.new(0.9, 0.4, 0.6)
local BACKGROUND_COLOR = Color3.new(0.05, 0.05, 0.1)
local GRID_TRANSPARENCY = 0.2
local HORIZON_TRANSPARENCY = 0
local MOVEMENT_SENSITIVITY = 1
local ROTATION_SENSITIVITY = 0.5

-- Create a new EditableImage
local editableImage = AssetService:CreateEditableImage({
	["Size"] = CANVAS_SIZE
})

-- Screen GUI setup
local part = workspace:WaitForChild("Part")
local screenGui = part:WaitForChild("SurfaceGui")

local imageLabel = Instance.new("ImageLabel")
imageLabel.Size = UDim2.new(1, 0, 1, 0)
imageLabel.Position = UDim2.new(0.5, 0, 0.5, 0)
imageLabel.AnchorPoint = Vector2.new(0.5, 0.5)
imageLabel.BackgroundTransparency = 1
imageLabel.Parent = screenGui

-- Store initial camera position and part position
local initialCameraPosition = camera.CFrame.Position
local initialPartPosition = part.Position
local lastVanishingPoint = Vector2.new(CANVAS_SIZE.X / 2, CANVAS_SIZE.Y / 2)

-- Clear the canvas
local function clearCanvas()
	editableImage:DrawRectangle(
		Vector2.new(0, 0), 
		CANVAS_SIZE, 
		BACKGROUND_COLOR, 
		0, 
		Enum.ImageCombineType.Overwrite
	)
end

-- Draw a line with bounds checking
local function safeDrawLine(p1, p2, color, transparency, combineType)
	local p1x = math.clamp(p1.X, 0, CANVAS_SIZE.X - 1)
	local p1y = math.clamp(p1.Y, 0, CANVAS_SIZE.Y - 1)
	local p2x = math.clamp(p2.X, 0, CANVAS_SIZE.X - 1)
	local p2y = math.clamp(p2.Y, 0, CANVAS_SIZE.Y - 1)

	editableImage:DrawLine(
		Vector2.new(p1x, p1y),
		Vector2.new(p2x, p2y),
		color,
		transparency,
		combineType
	)
end

-- Draw the perspective grid
local function drawPerspectiveGrid(vanishingPoint)
	-- Draw horizon line
	safeDrawLine(
		Vector2.new(0, vanishingPoint.Y),
		Vector2.new(CANVAS_SIZE.X - 1, vanishingPoint.Y),
		HORIZON_COLOR,
		HORIZON_TRANSPARENCY,
		Enum.ImageCombineType.AlphaBlend
	)

	-- Draw vertical grid lines
	local spacing = CANVAS_SIZE.X / (GRID_LINES + 1)
	for i = 1, GRID_LINES do
		local x = i * spacing
		safeDrawLine(
			Vector2.new(x, 0),
			Vector2.new(x, CANVAS_SIZE.Y - 1),
			GRID_COLOR,
			GRID_TRANSPARENCY,
			Enum.ImageCombineType.AlphaBlend
		)
	end

	-- Draw perspective lines from bottom to vanishing point
	spacing = CANVAS_SIZE.X / (GRID_LINES + 1)
	for i = 0, GRID_LINES + 1 do
		local x = i * spacing
		safeDrawLine(
			Vector2.new(x, CANVAS_SIZE.Y - 1),
			vanishingPoint,
			GRID_COLOR,
			GRID_TRANSPARENCY,
			Enum.ImageCombineType.AlphaBlend
		)
	end

	-- Draw horizontal perspective grid lines
	local divisions = 8
	for i = 1, divisions - 1 do
		local t = i / divisions
		local y = vanishingPoint.Y + (CANVAS_SIZE.Y - vanishingPoint.Y) * t

		-- Calculate left and right points on the perspective lines
		local leftX = (0 - vanishingPoint.X) * t + vanishingPoint.X
		local rightX = (CANVAS_SIZE.X - vanishingPoint.X) * t + vanishingPoint.X

		safeDrawLine(
			Vector2.new(leftX, y),
			Vector2.new(rightX, y),
			GRID_COLOR,
			GRID_TRANSPARENCY * (1 - t * 0.5), -- Fade out with distance
			Enum.ImageCombineType.AlphaBlend
		)
	end

	-- Draw vanishing point indicator
	editableImage:DrawCircle(
		vanishingPoint,
		3,
		HORIZON_COLOR,
		0,
		Enum.ImageCombineType.AlphaBlend
	)
end

-- Update the ImageLabel
local function updateImageLabel()
	imageLabel.ImageContent = Content.fromObject(editableImage)
end

-- Calculate vanishing point based on camera position, angle, and part position
local function calculateVanishingPoint(cameraPosition, cameraLookVector, partPosition)
	local center = Vector2.new(CANVAS_SIZE.X / 2, CANVAS_SIZE.Y / 2)

	-- Calculate relative movement from initial positions
	local relativeCameraX = cameraPosition.X - initialCameraPosition.X
	local relativeCameraZ = cameraPosition.Z - initialCameraPosition.Z
	local relativePartX = partPosition.X - initialPartPosition.X
	local relativePartZ = partPosition.Z - initialPartPosition.Z

	-- Calculate relative position between camera and part
	local relativeX = (relativeCameraX - relativePartX) * MOVEMENT_SENSITIVITY
	local relativeZ = (relativeCameraZ - relativePartZ) * MOVEMENT_SENSITIVITY

	-- Calculate rotation offset based on camera look vector
	local rotationOffsetX = cameraLookVector.X * ROTATION_SENSITIVITY * CANVAS_SIZE.X
	local rotationOffsetY = -cameraLookVector.Y * ROTATION_SENSITIVITY * CANVAS_SIZE.Y

	-- Calculate final vanishing point
	local vanishingPoint = Vector2.new(
		center.X + relativeX + rotationOffsetX,
		center.Y + relativeZ + rotationOffsetY
	)

	-- Apply smoothing between frames (lerp)
	vanishingPoint = Vector2.new(
		lastVanishingPoint.X + (vanishingPoint.X - lastVanishingPoint.X) * 0.1,
		lastVanishingPoint.Y + (vanishingPoint.Y - lastVanishingPoint.Y) * 0.1
	)

	lastVanishingPoint = vanishingPoint
	return vanishingPoint
end

-- Main render loop
RunService.RenderStepped:Connect(function()
	local cameraPosition = camera.CFrame.Position
	local cameraLookVector = camera.CFrame.LookVector
	local partPosition = part.Position

	-- Calculate vanishing point based on camera and part positions
	local vanishingPoint = calculateVanishingPoint(cameraPosition, cameraLookVector, partPosition)

	-- Render the grid
	clearCanvas()
	drawPerspectiveGrid(vanishingPoint)
	updateImageLabel()
end)

Author: StarVSK

Posted: March 1, 2025