Очумелый саморучник — спрайты как в Doom'е без дорисовывания

И так. Можно и эдак.

8cd191940b068f93c7ace20e64bcc625.png

Но лучше все по порядку.

В далёкий 1993 год Raven Software решили лицензировать у ID Software движок id Tech 1.

c95c671f1bc5795364bea331b36c9d77.gif

Поскольку работали на время — часть спрайтов была отрендерена с дополнительной дорисовкой поверх отрендереных изображений. В большинстве спрайтов это не так заметно, но вот в некоторых — очень даже.

Решил попробовать свои силы в этом деле.

Для начала была скручена моделька

192721e1770d77f11487029780076ac4.pngbe715666daa00ae44190c1ee35342daa.png

Делалось это дело для замены старой, и довольно корявой версии пушки demon tech repeater для мода Complex Doom Invasion. В скриншотах одни из первых итерации, до того как были добавлены последние элементы.

Первые рендеры напрямую из Блендера вышли размытыми, понятное дело antialiasing делает своё дело.

Поскольку ранее я уже кодил рендеринг спрайтов на Unity — было принято решение работать дальше в нём.

Первые рендеры из Unity просто с размытием и даже с уменьшением через Lancoz фильтр все равно были довольно уродливы:

33530aa030a4a46e6fe97cfd00b572fd.pngc1cc17bc07c5dba975dbd1de08770ae8.png

Пришлось прибегнуть вдобавок к использованию Питона (Пайтона, как вам угодно) и дописать скрипты для уменьшения разрешения (код тут).

6c8eee3b3007e775716fd51c18be8237.png

Далее вдобавок был отрендерен outline. Работает так — берём legacy image effects из assetstore’а, и подставляем edge detection с режимом triangle depth normals. Не забываем уменьшить. В данном случае для уменьшения как раз таки использовался lancoz.

3b8187cf850030dee46bf3ac4cdb44df.png

Наложил одно на другое (все через скрипты):

489133c028fde6d0b3107dc9c49590d8.png

Чуть чуть дорисовал, добавил 50% постеризацию (не путать с пАстеризацией — на Русском термин называется Изогелия https://ru.wikipedia.org/wiki/Изогелия) со смешиванием (как эффект камеры), а также SSAO для теней.

Код шейдера постеризации и код скрипта для его применения поверх камеры:

Shader "Hidden/LightPosterize"
{
  //стандартная рыба CG шейдера, мы только добавляем 
  //дополнительные переменные для силы Изогелии и силы смешивания
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
		_Precision ("Precision", Range(0.001, 0.1)) = 0.15
		_Interp("Interp", Range(0,1)) = 1
    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            sampler2D _MainTex;
          //подхватываем карту глубин-нормалей
			sampler2D _CameraDepthNormalsTexture;
			float _Precision, _Interp;
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
				float3 normalValues;
				float depthValue;


				DecodeDepthNormal(tex2D(_CameraDepthNormalsTexture, i.uv), depthValue, normalValues);
                //обрезаем эффект если слишком глубоко - позволяет избавиться от артефактов
				if (depthValue > 0.99) return col;
              //собственно рабочий код - постеризуем
				float3 upscaled = col.rgb / _Precision + float3(0.5, 0.5, 0.5)*_Precision;

				float3 final = round( upscaled )* _Precision;

				col.rgb = lerp(col.rgb, final, _Interp);
                return col;
            }
            ENDCG
        }
    }
}
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
public class ShaderApply : MonoBehaviour
{
    public Material mat;
    public DepthTextureMode mode;
    Camera cam;
//как обычно для эффектов которые накладываются на стандарную камеру - 
//подсасываемся через OnRenderImage
    void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        cam = GetComponent();
//если материала на скрипте нет - прогоняем картинку дальше
        if (mat == null)
        {
  
            Graphics.Blit(source, destination);
            return;
        }
//если есть - берем что у нас сейчас в source rendertexture'е и прогоняем через наш шейдер
        cam.depthTextureMode = mode;
        //mat is the material containing your shader
        Graphics.Blit(source, destination, mat);
    }
}

Получилось вот так:

0b5128bd502ab1be06bd508074b71d41.png

Ну и вдобавок как все это выглядит когда вся анимация отрендеренна и прогнана через скрипты:

И теперь алгоритм по порядку:

3ece89a67a9c6503fc7ff9a433f659b1.png07b781bf9fa82235fd3dace7efcd5c10.png

  • Рендерим в разрешении 640×400 с повышенной резкостью, SSAO и постеризацией.

  • Рендерим эффект плазмы. В данном случае я рендерю на чёрном фоне без альфа канала, перекидываю в Питон, создаю альфа канал из чёрного цвета и нормализую цвет — таким образом избегая colorbanding

  • Рендерим outline в разрешении 1280×800 отдельно, перекидываю тоже в питон. Скрипт уменьшает спрайт аутлайна, множит основной спрайт на аутлайн (50% помножение), затем скрипт закидывает получившийся спрайт поверх эффекта плазмы, если тот присутствует в ряду кадров.

Готово. Надеюсь статья была полезной.

© Habrahabr.ru