Files
blog/fal-image-generation.html
T

346 lines
19 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Art at Every Price Point: How We Generate Game Assets with fal.ai — Tinqs Blog</title>
<meta name="description" content="From $0.002 to $0.09 per image, across 12 models. How we built a prompt pattern that actually produces usable game art, and a model-picking strategy that keeps costs at $8/month.">
<meta name="robots" content="index, follow">
<link rel="canonical" href="https://www.tinqs.com/blog/fal-image-generation">
<meta property="og:type" content="article">
<meta property="og:url" content="https://www.tinqs.com/blog/fal-image-generation">
<meta property="og:title" content="AI Art at Every Price Point: How We Generate Game Assets with fal.ai">
<meta property="og:description" content="12 fal.ai models, $0.002-$0.09/image, 4-layer prompt pattern. Game art that actually ships.">
<meta property="og:image" content="https://www.tinqs.com/img/og-cover.jpg">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="AI Art at Every Price Point: How We Generate Game Assets with fal.ai">
<meta name="twitter:description" content="12 fal.ai models, $0.002-$0.09/image, 4-layer prompt pattern. Game art that actually ships.">
<meta name="twitter:image" content="https://www.tinqs.com/img/og-cover.jpg">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "AI Art at Every Price Point: How We Generate Game Assets with fal.ai",
"datePublished": "2026-05-25",
"author": {
"@type": "Person",
"name": "Ozan Bozkurt"
},
"publisher": {
"@type": "Organization",
"name": "Tinqs Limited",
"url": "https://www.tinqs.com"
},
"description": "From $0.002 to $0.09 per image, across 12 models. How we built a prompt pattern that actually produces usable game art, and a model-picking strategy that keeps costs at $8/month."
}
</script>
<!-- PostHog (EU) -->
<script>
!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host.replace(".i.posthog.com","-assets.i.posthog.com")+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="init capture register register_once register_for_session unregister unregister_for_session getFeatureFlag getFeatureFlagPayload isFeatureEnabled reloadFeatureFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSessionId getSurveys getActiveMatchingSurveys renderSurvey canRenderSurvey getNextSurveyStep identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException loadToolbar get_property getSessionProperty createPersonProfile opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing clear_opt_in_out_capturing debug".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
posthog.init('phc_teG6p5oxf6poQHPThq5AGKzWQNhw4bHW9arLwWAVXm3f',{api_host:'https://eu.i.posthog.com',ui_host:'https://eu.posthog.com',person_profiles:'identified_only',defaults:'2026-01-30'})
</script>
<link rel="icon" type="image/svg+xml" href="/img/favicon.svg">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../style.css">
2026-06-02 22:50:20 +01:00
<style>
/* ── Team guide aesthetic: self-contained overrides ── */
/* ── Gradient title (amber → warm gold, hint of blue) ── */
.post__title {
background: linear-gradient(90deg, #c9935a, #f59e0b 40%, #38bdf8);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
font-weight: 800;
}
/* ── Date pill ── */
.post__date {
display: inline-block;
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', Consolas, monospace;
font-size: 0.72rem;
letter-spacing: 0.22em;
text-transform: uppercase;
color: #38bdf8;
border: 1px solid rgba(147, 140, 129, 0.25);
border-radius: 999px;
padding: 4px 14px;
margin-bottom: 16px;
}
/* ── Lead ── */
.post__lead {
color: #9aa7b4;
font-size: 1.08rem;
line-height: 1.7;
}
/* ── H2: left accent bar ── */
.post__body h2 {
font-size: 1.7rem;
margin: 54px 0 6px;
padding-left: 16px;
border-left: 4px solid #c9935a;
}
/* ── H3: purple secondary accent ── */
.post__body h3 {
color: #a855f7;
font-size: 1.18rem;
margin: 30px 0 4px;
}
/* ── Inline code ── */
.post__body code {
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', Consolas, monospace;
font-size: 0.86em;
background: #1c2230;
color: #9fe6c0;
padding: 2px 6px;
border-radius: 5px;
border: 1px solid #2a3340;
}
/* ── Code blocks (dark panel) ── */
.post__body pre {
background: #0a0e14;
border: 1px solid #2a3340;
border-radius: 10px;
padding: 16px 18px;
overflow-x: auto;
margin: 14px 0;
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', Consolas, monospace;
font-size: 0.85rem;
line-height: 1.55;
color: #e6edf3;
}
/* Reset inline-code double-up inside pre */
.post__body pre code {
background: transparent;
padding: 0;
border: none;
font-size: inherit;
color: inherit;
border-radius: 0;
}
/* ── Blockquote callout (ready for future use; build.js does not emit blockquote yet) ── */
.post__body blockquote {
background: rgba(245, 158, 11, 0.08);
border: 1px solid rgba(245, 158, 11, 0.25);
border-left: 4px solid #f59e0b;
border-radius: 0 12px 12px 0;
padding: 16px 18px;
margin: 18px 0;
color: #f4e3c4;
font-size: 0.94rem;
}
/* ── Links ── */
.post__body a {
color: #38bdf8;
}
.post__body a:hover {
color: #a855f7;
}
/* ── Strong ── */
.post__body strong {
color: #f59e0b;
}
/* ── HR ── */
.post__body hr {
border: none;
border-top: 1px solid #2a3340;
margin: 32px 0;
}
/* ── Figures ── */
.post__body figure img {
border-radius: 12px;
border: 1px solid #2a3340;
}
.post__body figcaption {
color: #9aa7b4;
font-size: 0.85rem;
margin-top: 6px;
}
/* ── List spacing ── */
.post__body li {
margin: 4px 0;
}
</style>
</head>
<body>
<!-- NAV -->
<nav class="nav nav--scrolled" id="nav">
<a href="/" class="nav__logo" aria-label="Tinqs home">
<span class="nav__wordmark">TINQS</span>
</a>
<div class="nav__links">
<a href="/#game" class="nav__link">Games</a>
<a href="/#tech" class="nav__link">Technology</a>
<a href="/#about" class="nav__link">About</a>
<a href="/blog/" class="nav__link" style="color: var(--c-accent-l);">Blog</a>
<a href="/#signup" class="nav__link">Contact</a>
<a href="/press" class="nav__link">Press</a>
</div>
<button class="nav__burger" aria-label="Open menu" id="navBurger">
<span></span><span></span><span></span>
</button>
</nav>
<!-- MOBILE MENU -->
<div class="mobile-menu" id="mobileMenu">
<a href="/#game" class="mobile-menu__link">Games</a>
<a href="/#tech" class="mobile-menu__link">Technology</a>
<a href="/#about" class="mobile-menu__link">About</a>
<a href="/blog/" class="mobile-menu__link">Blog</a>
<a href="/#signup" class="mobile-menu__link">Contact</a>
<a href="/press" class="mobile-menu__link">Press</a>
</div>
<!-- POST -->
<article class="post">
<a href="/blog/" class="post__back">&larr; All Posts</a>
<span class="post__date">25 May 2026</span>
<h1 class="post__title">AI Art at Every Price Point: How We Generate Game Assets with fal.ai</h1>
<p class="post__lead">Every visual asset in our game — character art, app icons, trailer frames, logo variants, Steam capsules — was generated through a single API. No Photoshop. No concept artist on staff. Last month: 673 images, $8.30 total. Here's the prompt pattern that makes AI art actually usable for game development, and how we pick between 12 models spanning $0.002 to $0.09 per image.</p>
<div class="post__body">
<h2>The problem with AI art for games</h2>
<p>Most AI-generated images look beautiful on social media and useless in a game. The character looks different from every angle. The art style drifts between generations. The text in the logo is garbled. The icon doesn't read at 64×64.</p>
<p>The issue isn't the models — Flux, Ideogram, and Recraft are genuinely good. The issue is prompting. "Warrior on a beach" gives you a different art style, different skin tone, different proportions every time. You can't build a game from one-offs.</p>
<p>We spent three months iterating before we found a prompt structure that anchors the model to a consistent art direction and produces images you can actually ship. It has four layers.</p>
<h2>The 4-layer prompt pattern</h2>
<h3>Layer 1: Design context (the anchor)</h3>
<p>This is the most important paragraph and the one most people skip. It sets the art direction for every single generation:</p>
<pre><code>Art direction: stylized 3D render for a survival colony sim. Warm earthy
palette — browns, tans, dark reds, cream, ocean blues. Carved wood
textures, traditional patterns, woven natural fibres. Game engine quality,
not photorealistic.</code></pre>
<p>Same paragraph whether you're generating a character, a landscape, or an icon. It's your art bible compressed into 50 words. Every time we skipped it — "just a quick test" — the output drifted into generic fantasy art.</p>
<h3>Layer 2: Scene description (be specific)</h3>
<p>Not "tribal clothing" — "woven wrap skirt, mid-thigh length." Not "jewelry" — "shell necklace with a carved bone pendant." Vague prompts produce vague results. Specific prompts produce usable assets. Describe element by element.</p>
<h3>Layer 3: Negative prompt (prevent drift)</h3>
<p>Always include what you don't want:</p>
<pre><code>Do not include: cartoon style, anime style, photorealistic render,
extra text or taglines, watermark, deformed elements, modern or sci-fi.
No extra fingers, no merged limbs, no floating accessories.</code></pre>
<p>AI models have strong defaults — they want to make things shiny, symmetrical, and photorealistic. If your game isn't those things, say so explicitly.</p>
<h3>Layer 4: Reference images (consistency)</h3>
<p>When you need the same character from different angles, pass the first approved image as reference. Without it, every generation is independent — a different person every time. With it, every generation builds on the last approved output. This is how you get a roster of characters that look like they belong in the same game.</p>
<h2>The model lineup (and when to use each)</h2>
<p>Not every image needs the best model. A throwaway mockup doesn't justify $0.09. A final logo doesn't deserve $0.002.</p>
<p>| Model | Cost | Speed | Use for |</p>
<p>|&mdash;&mdash;-|&mdash;&mdash;|&mdash;&mdash;-|&mdash;&mdash;&mdash;|</p>
<p>| Flux 2 Pro | $0.03 | 15s | Final art, characters, environments |</p>
<p>| Flux Schnell | $0.003 | 3s | Exploration drafts, 20-variant grids |</p>
<p>| Ideogram v3 Quality | $0.09 | 12s | Anything with readable text |</p>
<p>| Recraft v3 | $0.04-0.08 | 10s | Logos, brand assets, SVG vectors |</p>
<p>| Seedream v4.5 | $0.04 | 8s | Photorealistic scenes |</p>
<p>| Flux Dev | $0.025 | 10s | LoRA fine-tuning base |</p>
<p>| Nano Banana Edit | $0.039 | 12s | Style transfer, material variants |</p>
<p>| BiRefNet | $0.001 | 3s | Background removal |</p>
<h3>The Schnell-to-Pro pipeline (never iterate on expensive models)</h3>
<p>Every generation session follows the same pattern:</p>
<p>1. <strong>Explore with Schnell</strong> ($0.003) — 10-20 variants, different angles, color palettes. Cost: $0.03-0.06</p>
<p>2. <strong>Pick 2-3 directions.</strong> Human looks at the grid, picks winners.</p>
<p>3. <strong>Refine with Flux 2 Pro</strong> ($0.03) — regenerate winners at full quality. Cost: $0.06-0.09</p>
<p>4. <strong>Post-process</strong> — BiRefNet for background removal ($0.001), Recraft for vector ($0.08)</p>
<p>A full session — blank canvas to final assets — costs under $0.20. Most of the creative work happens at $0.003/image. The expensive model just polishes a decision you already made.</p>
<h3>Typography: one model rules them all</h3>
<p>Every model except Ideogram fails at text. Flux gives you beautiful art with garbled letters. SDXL doesn't try. If your image has words in it, Ideogram v3 Quality is the only answer. We learned to accept the $0.09 cost rather than waste $0.30 on ten failed Flux attempts.</p>
<h3>Logo variants at scale</h3>
<p>Our game logo has 18 material variants — mahogany, mother-of-pearl, obsidian, molten lava, bronze with verdigris. Each generated with Nano Banana Edit ($0.039) + BiRefNet ($0.001) for transparency. Total: $0.72. A designer would quote hundreds of dollars and a week.</p>
<h2>The numbers (one month of generation)</h2>
<p>| Category | Images | Cost | Avg/Image |</p>
<p>|&mdash;&mdash;&mdash;-|&mdash;&mdash;&ndash;|&mdash;&mdash;|&mdash;&mdash;&mdash;&ndash;|</p>
<p>| Concept art (flux-2-pro) | 120 | $3.60 | $0.03 |</p>
<p>| Exploration (schnell) | 400 | $1.20 | $0.003 |</p>
<p>| Logo variants | 18 | $0.72 | $0.04 |</p>
<p>| Icons | 30 | $1.20 | $0.04 |</p>
<p>| Typography (ideogram) | 25 | $1.50 | $0.06 |</p>
<p>| Background removal | 80 | $0.08 | $0.001 |</p>
<p>| <strong>Total</strong> | <strong>673</strong> | <strong>$8.30</strong> | <strong>$0.012</strong> |</p>
<p>Six hundred images. Eight dollars.</p>
<h2>The pipeline: from prompt to in-game asset</h2>
<p>fal.ai is step one of a pipeline that goes from idea to walking character in about two hours:</p>
<pre><code>Brief → fal.ai (2D concept) → Tripo Studio (3D model) → Blender (decimate) → Godot (in-game)</code></pre>
<p>1. Designer describes the character</p>
<p>2. Generate 3 variants with Flux 2 Pro, score on 5 criteria (style match, cultural accuracy, silhouette, expression, animatability)</p>
<p>3. Generate front/side/three-quarter reference views using the winner</p>
<p>4. Tripo Studio image-to-3D (~1.5M faces, PBR textures)</p>
<p>5. Blender CLI decimates to 25k faces</p>
<p>6. Auto-rig, import into engine, done</p>
<p>Quality isn't AAA, but for an indie game with a stylized art style, it's more than good enough. Ten characters designed, total fal.ai spend: $6.</p>
<h2>What we learned</h2>
<p><strong>The design context block is worth more than the rest of the prompt combined.</strong> Without it, every image is a one-off. With it, every image belongs to the same game.</p>
<p><strong>Never iterate on expensive models.</strong> Schnell at $0.003/image is for exploration. Flux 2 Pro at $0.03 is for final output. The cheap model does 90% of the creative work.</p>
<p><strong>Aggregation beats loyalty.</strong> No single model is best at everything. Flux for art, Ideogram for text, Recraft for design, Nano Banana for edits, BiRefNet for masks. Use the right tool for each job.</p>
<p><strong>Let the agent handle prompting.</strong> We encode the 4-layer pattern, art style guide, and model selection rules in an <a href="../skills/image-generation.md" style="color: var(&ndash;c-accent-l);">agent skill file</a>. The AI writes the full prompt, generates images, displays them, and asks for scores. The human's job is creative direction.</p>
<p>AI art isn't magic and it isn't free. But at a penny per image, with the right prompt structure and model strategy, it eliminates the most expensive bottleneck in indie game development: the gap between "I know what this should look like" and "I have an asset I can actually use."</p>
<hr>
<p><em>Image generation is built into <a href="https://tinqs.com" style="color: var(&ndash;c-accent-l);">Tinqs Studio</a>. We've open-sourced the <a href="../skills/image-generation.md" style="color: var(&ndash;c-accent-l);">prompt engineering skill</a> and <a href="../skills/concept-art-pipeline.md" style="color: var(&ndash;c-accent-l);">concept art pipeline skill</a>. We're building <a href="https://arikigame.com" style="color: var(&ndash;c-accent-l);">Ariki</a> with these tools.</em></p>
</div>
<div class="post__author">
<div class="post__author-avatar">OB</div>
<div class="post__author-info">
<span class="post__author-name">Ozan Bozkurt</span><br>
CTO & Developer, Tinqs
</div>
</div>
</article>
<!-- FOOTER -->
<footer class="footer">
<div class="footer__inner">
<span class="footer__wordmark">TINQS</span>
<div class="footer__links">
<a href="/#game">Games</a>
<a href="/#tech">Technology</a>
<a href="/#about">About</a>
<a href="/blog/">Blog</a>
<a href="mailto:hello@tinqs.com">hello@tinqs.com</a>
<a href="/press">Press Kit</a>
</div>
<p class="footer__copy">Tinqs Limited &mdash; London, est. 2020</p>
</div>
</footer>
<script>
const burger = document.getElementById('navBurger');
const mobileMenu = document.getElementById('mobileMenu');
burger.addEventListener('click', () => {
const open = mobileMenu.classList.toggle('mobile-menu--open');
burger.classList.toggle('nav__burger--open', open);
document.body.style.overflow = open ? 'hidden' : '';
});
mobileMenu.querySelectorAll('a').forEach(link => {
link.addEventListener('click', () => {
mobileMenu.classList.remove('mobile-menu--open');
burger.classList.remove('nav__burger--open');
document.body.style.overflow = '';
});
});
</script>
</body>
</html>