Distance fields for font rendering (Part 2)

To properly test the font rendering from distance fields,  I adapted chunks of code from Here and Here, to make a Python signed distance field creator:

from PIL import Image
import os
from subprocess import call
from time import time
from math import sqrt


def check(limit, ox, oy, px, py, current):
    lp = getpixel(px, py)
    if lp == -1:
        return current
    if lp != limit:
        nw = float(ox - px)
        nh = float(oy - py)
        new = (nw * nw) + (nh * nh)
        if new < current:
            current = new
    return current


def getField(limit, searchLen):
    print('Calculating field {0}...'.format(str(limit + 1)))
    if limit == 1:
        limit = 255
    distances = []
    for w in range(oim.size[0]):
        distances.append([])
        for h in range(oim.size[1]):
            current = limit if limit > 0 else (searchLen * searchLen)
            for x in range(w - searchLen, w + searchLen):
                for y in range(h - searchLen, h + searchLen):
                    current = check(limit, w, h, x, y, current)
            current = int(sqrt(current))
            current *= int(g)
            distances[w].append(current)
    return distances


def getpixel(x, y):
    try:
        p = inpixels[x, y][0]
        return p
    except IndexError:
        return -1


inPath = os.path.dirname(__file__)
outPath = os.path.join(inPath, 'out.png')
inPath = os.path.join(inPath, 'in.png')
oim = Image.open(inPath).convert('RGBA')
img = Image.new('RGBA', oim.size, (0, 0, 0, 0))

ct = time()

inpixels = oim.load()
outpixels = img.load()

search = 8
last = 0.0
g = 255 / search

field1 = getField(0, search)
ct = time() - ct
print('Took: {0}'.format(str(round(ct, 1))))
ct = time()
field2 = getField(1, search)
ct = time() - ct
print('Took: {0}'.format(str(round(ct, 1))))
ct = time()
print('Mixing fields...')

for cw in range(oim.size[0]):
    for ch in range(oim.size[1]):
        fc1 = 255 - field1[cw][ch]
        fc2 = field2[cw][ch]
        fc = int((fc1 + fc2)/2)
        outpixels[cw, ch] = (fc, fc, fc, 255)

ct = time() - ct
print('Took: {0}'.format(str(round(ct, 1))))
ct = time()

print('Resizing and saving output image (128 width)...')
rf = float(oim.size[0]) / 128
img = img.resize((128, int(oim.size[1] / rf)), Image.BILINEAR)

img.save(outPath)

ct = time() - ct
print('Took: {0}'.format(str(round(ct, 1))))

if os.name.startswith('darwin'):
    call(('open', outPath))
elif os.name == 'nt':
    os.startfile(outPath)
elif os.name == 'posix':
    call(('xdg-open', outPath))

(Since this is only a test, the input and output file paths must be changed manually as needed).

The resulting image is certainly better suited for the font rendering:

SDF:

Signed Distance Field Test

Result (same shader shown in previous part):

SDF font render

Zoomed in (no weird artifacts like with the faked DF):

SDF font render zoomed

Unfortunately, the creation time for the whole DF is around 2 minutes for small images (256*256), and that is too much for the usage that I’m thinking. Now,  that usage involves the GPU so, could be faster to do this with the graphics card? Isn’t the purpose of the GPU to do per-pixel work anyway?

 

In the next part I will publish a complete tool to calculate the distance fields in the GPU, hopefully, in real time .

Edit:

For Python 3 compatibility, data passed to ‘outpixels’ needs to be send as int. Code updated to reflect it (and improved file paths).

Advertisements

Distance fields for font rendering (Part 1)

Pushing myself forward again, I will publish some entries about implementing font rendering into a texture atlas, encoded with distance fields and with support for Unicode chars (the possibility to reduce the texture size will probably allow large amounts of glyphs in one single texture) to finally show proper labels and text boxes in Engendro3D.


 

By checking info about how to render fonts in OpenGL, I found this question, which lead me to the Valve’s paper (.pdf).

The results, along with the need to have huge amounts of chars pre-rendered to a font atlas for certain languages, got my interest.

Since the Distance fields looks slightly like blurred images, I made a quick png texture in Gimp containing 6 letters (with Eufm10 font) and I used the Gaussian Blur filter (10 radius) to produce this faked distance Field:

wtx_blured

Then, with a very simple GLSL fragment shader, this is the result:

dfblured

No Bilinear interpolation:

gblured_noint

Zoomed into the ‘B’:

zoomblur

No Bilinear interpolation:

zoomblur_noint

Has outline and glow and, while the result is not the best, the logic works.

The shader I wrote can probably be improved a lot, but I will use the same for all the tests I’ll do:

#version 110

uniform float time;
uniform sampler2D tex;

void main()
{
float color = texture2D(tex,gl_TexCoord[0].st).r;
	if (color <= 0.5)
		gl_FragColor = vec4(0,0,1,1);
	else
		if (color <= 0.6){
			gl_FragColor = vec4(1,1,1,1);}
		else
		{
			if (color < 1.0)
				gl_FragColor = max(0.0,1.0 - color) * 
				vec4(.9,.4,.2,1) * 2.0 * max(sin(-time),sin(time));
			else
				gl_FragColor = vec4(0.0);
		}
}

 

In the next part I will show a Distance Field generator in Python and Pillow (PIL).

Engendro3D game engine / Motor de juego

-Engendro (spanish):

  1. Fetus.
  2. Deformed creature born without due proportion.
  3. Very ugly person.
  4. Plan, design or intellectual work poorly designed.

e_logo_proto_TM

Today I announce the conception of Engendro3D™ , a .net based game engine with planned support for DX9, DX11 and OpenGL. But why to start a game engine of my own if it is (I’ve been told) an insanely hard (and perhaps impossible) project for a single person?

Well, basically, to learn.  While I was making my second plugin for DX Studio, I realized that I have to know a lot more about how game engines work. And what is the best way for me to learn? Doing things. So I will put together all that is needed to make functional games to at least be able to improve the appearance of my first game (now in paper), which will use DX Studio.

Right now, lets say that the engine is in the differentiation stage, where the various systems are being formed:

  • Rendering manager
    • Starts and updates the selected device (DX9,DX11,OGL). Also should handle occlusion and instancing.
  • Textures manager
    • Everything related to textures, including atlases and such.
  • Effects manager
    • .Fx file loading, compiling, reload if changed, and parsing.
  • Models manager
    • Meshes loader and importer.
  • Sounds manager
    • Loader (and updater?).
  • Physics manager
    • Setting and updating.
  • Scenes manager
    • Loading and writing to disk of complete scenes. Addition and removal of scene objects.

I have received some critics because of the language I chose: Basic .net. But the facts are that using that language has many advantages for me, so I will stick to it. Anyway, is not like I’m trying to compete against Unity, Cryengine or UDK, right?

Currently I’m working on the rendering manager over DX9. Soon, I will come back with updates.

And about the name… Clever, isn’t it? 😀

Lightmaps and shadows test on DX Studio

Showing here some testing I made using DX Studio and SweetHome 3D, free programs for making games, 3D rooms, accessories and house plans.

Starting the room.

sweet_zps4cae1265

Modifying on 3DS Max.

3ds1_zps76a8a664

I discovered that is necessary to flip the normals of the roof on rooms since they come backwards. Strange.

Here the videos of the tests and the links so you can download this micro levels to play throwing some apples (Windows only).

Download dynshadow.exe

Download LMshadow.exe