Lowpoly3D to 2D Animations

Lowpoly3D to 2D Animations

posted 3 min read

Hi all,

Intro

So I've been working on a personal project of mine for 3 month or so now and I have reached a point where I am prepping for a public facing demo. I'm currently refining some visuals at this point in time and specifically I am focused in on re-working workflow for character visuals.

A little context beforehand. The project, Rebel Hearts: "Eclypse" is inspired by classic Tactical-RPGs such as Vandal Hearts. Vandal Hearts 1 was developed & published by Konami, released in 1996. The goal of this project was to explore grid-based, turn-based combat and multiplayer networking whilst building a foundation for larger game systems. I enjoyed Vandal Hearts and wanted to reproduce that, whilst expanding further with modern day technology and mulitplayer capability.

You can see more about this over here: Rebel Hearts Eclypse, here: Tactical battleground gameplay & here: Free roam gameplay

Now given the above, we'll know the general theme and style of "squad members" as art assets. I opted for a more up-to-date visual and wanted to breifly touch upon it's work flow and process.

Lowpoly3D to 2D Animations

Here, we can harness a more modern asthetic by rendering lowpoly3D models in our chosen game engine/renderer and recording that output. We are basically rasterizing and recording. Why this way? We can optimize and save huge processing power by re-importing the 3D capture of our low-poly 3D models and running them as simple 2D sprite animations. This VS computing and rendering many 3D models is huge in terms of CPU-GPU cycles. It isn't however a all encompassing fit solution. This just makes sense in the context of this particular project' theme and style.

So here, we are using the Unity Engine, specifically version 2022.3.20f1currently. We setup a fresh new scene to setup our low-poly 3D models.

We will need to make use of a "RenderTexture" in order to enable us to record what is rendered to the camera.This render texture must be assigned to the camera we are using and our C# script that will assist with the 3D capture.

The script is light weight and effective. We supply a public bool checkbox for the inspector when we'd like to capture what the camera renders upon running. When we play, we effectively record until we stop. This is output to a folder-directory named "Frames". It is here, where we grab our raw .png output frames and throw into another tool for refinements and adjustments. I usually use Aseprite, as it's light weight and effective for these types of assets.

The below is our simple script for aiding us in this work flow.

using System;
using System.IO;
using UnityEngine;

namespace RebelHeartEclipse
{
    public class _3DLowpolyTo2DRecorder : MonoBehaviour
    {

        public float PixelsPerUnit;// = 256f;//128f;
        public Camera CaptureCamera;
        public RenderTexture RenderTexture;
        public int FrameIndex = 0;
        public bool RecordFramesUponPlay;

        private Transform _transform;

        private void Awake() => Setup();

        private void Setup()
        {
            _transform = this.transform;


        }

        private void LateUpdate()
        {
            PixelSnapLateUpdate();
            RecordFramesLateUpdate();
        }
        private void PixelSnapLateUpdate()
        {
            Vector3 pos = transform.position;
            float snap = 1f / PixelsPerUnit;

            pos.x = Mathf.Round(pos.x / snap) * snap;
            pos.y = Mathf.Round(pos.y / snap) * snap;
            pos.z = Mathf.Round(pos.z / snap) * snap;

            _transform.position = pos;
        }

        private void RecordFramesLateUpdate()
        {
            if (!RecordFramesUponPlay) return;
            if (CaptureCamera == null) return;
            if (RenderTexture == null) return;



            RenderTexture.active = RenderTexture;

            Texture2D tex = new Texture2D(
                RenderTexture.width,
                RenderTexture.height,
                TextureFormat.RGBA32,
                false
            );

            tex.ReadPixels(
                new Rect(0, 0, RenderTexture.width, RenderTexture.height),
                0,
                0
            );
            tex.Apply();

            byte[] bytes = tex.EncodeToPNG();
            File.WriteAllBytes(
                Application.dataPath + $"/Resources/Frames/frame_{FrameIndex:D4}.png",
                bytes
            );

            FrameIndex++;

            RenderTexture.active = null;
            Destroy(tex);
        }

        private void Update()
        {
            
        }


    }
}

This concludes this breif post. It is a relatively short post and somewhat surface level but I intend on posting more further along down the line.

Any thoughts and comments are welcome, please do share if any!

Have a great day,

Rick

2 Comments

2 votes
0
0 votes

More Posts

I’m a Senior Dev and I’ve Forgotten How to Think Without a Prompt

Karol Modelskiverified - Mar 19

TypeScript Complexity Has Finally Reached the Point of Total Absurdity

Karol Modelskiverified - Apr 23

Your Tech Stack Isn’t Your Ceiling. Your Story Is

Karol Modelskiverified - Apr 9

Tuesday Coding Tip 02 - Template with type-specific API

Jakub Neruda - Mar 10

The Analyst's Problem: Volume III

Jason Mullings - Apr 16
chevron_left

Related Jobs

View all jobs →

Commenters (This Week)

1 comment
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!