2025年10月25日星期六

Tech Art - Week10 - Army of Darkness


Anlysis

Before explaining the approach, let’s first confirm our assignment requirements.

Assignment Requirements

The assignment brief is already quite clear. In the previous assignment, we essentially resolved the common hurdles when using the basic Houdini Python API, and this time we hardly need any new APIs. Therefore, this post will mainly explain the implementation approach.

Because we ultimately want the soldiers’ formation to be neat—and preferably a square—we should analyze the problem first. The formula can be split into two parts: compute the Unit first, then compute the Soldiers Number. Our implementation follows the same idea.

Decompose The Formula

Step 1: Generate the Unit. Its geometric meaning is straightforward: a formation with Base rows by Base columns of soldiers.

Step 2: Since we want the final result to be organized as an array of Units with M rows by N columns, we need a way to determine how many Units are required to assemble the final square formation. The answer is intuitive:


Once we obtain the value of Total Units, we naturally have M × N = Total Units. We want the final array to be as close to a square as possible (for better aesthetics), so M and N should be as close as they can be, and both will be near √(Total Units). Ideally, Total Units has an integer square root.

Since we already know Unit = Base², we can conclude that Total Units can indeed yield an integer square root.


At this point, the basic algorithmic idea is clear: implement it according to the assignment requirements and generate the result. 

Tips: When implementing this in Houdini, I like to treat both “Soldier” and “Unit” as discrete entities. In my implementation, one “Geo” serves as a “Unit,” and each “Shape” inside that “Geo” is treated as a “Soldier.” This hierarchy makes the overall implementation clearer.


2025年10月22日星期三

Common Art - Week 09 - Initial Block Out - 'F' Stage

 

Tree

First, I completed the procedural modeling of the tree in Houdini, and made sure to bake spherical normals into every leaf vertex.

Houdini

For shading: the leaves use a simple half-Lambert setup, plus a UV-based gradient that darkens from bottom to top. In the next version I plan to add a wind system. The trunk is rendered with the default PBR material.

Leaves


Result Version 1.0

Tree Version 1.0


Water

I built an initial water effect using Unreal’s Water System. So far I’ve implemented a basic moving water surface (normal-based). Thanks to Lumen, refraction and reflection come largely packaged in Unreal—unlike Unity, where I needed more custom code. That said, there are many differences between Unity and Unreal, so directly porting my Unity water will still take some time. Unreal’s strong built-ins also introduce constraints that make certain custom tweaks less straightforward. Still, Unreal’s water rendering approach is very interesting and worth studying.

Water

Result Version 1.0

Water Vesion 1.0


Grass

The billboard-grass shading is quite similar to the leaves, but the grass already has a wind system. The core idea is to sample a Wind Flow Map using world-space XY to offset vertices. I also use the grass card’s UVs so that the farther a point is from the ground, the stronger the wind influence it receives.

Grass

Result Version 1.0


Fire and Smoke

I created multiple versions of fire and smoke to handle different scenarios.

First, I ported the fire effect I made in Unity over to Unreal. Because the coordinate systems differ, getting the billboard behavior right took some time. I also found that Unreal’s Custom node is essentially just a function, so I couldn’t directly transplant my procedural texture functions from Unity—I had to write a lot of boilerplate (which is why I dislike node graphs for reuse and maintenance). It took extra time, but the result looks good.

Single Fire Flame

Next, to pair with that flame, I combined Unreal’s Starter Content particle effects to build my first smoke for use alongside the Single Fire Flame.

Smoke

With the Single Fire Flame and smoke in place, I wanted to port my Unity heat distortion effect. However, modifying the render pipeline in Unreal is difficult, which means I can’t directly access the post-transparency render buffer (Unreal only exposes the opaque pass buffer—Scene Color). So I couldn’t do a noise-based screen-space offset. Instead, inspired by the water work earlier, I used Unreal’s built-in Refraction: animate a noise in UVs, convert height to normals, then drive refraction to create the heat distortion. Since distortion looks odd at a distance and diverges from real-world behavior, I clamp it by world-space distance so it’s disabled beyond a certain range.

Heat Distortion Effect

Result Version 1.0

Fire Version 1.0

We also needed smoke for house chimneys, so I used the Niagara System to create stylized smoke. The particles are mesh-based—since it’s very easy in C4D to make those blobby “metaball” meshes, I voxelized and converted them to mesh there.

Stylized Smoke Mesh

The smoke dissolution uses VoroNoise, driven by particle lifetime in Niagara.

Stylized Smoke

Result Version 1.0

Stylized Smoke Version 1.0


Since the Single Fire Flame might not always meet project needs, I rushed a 2.0 version: using Niagara together with the existing Single Fire Flame to create a new fire effect, and I rewrote the smoke in Niagara as well. Performance cost increased, but visual quality improved significantly.

Result Version 2.0

Multiple Fire Version 2.0

Summary






2025年10月17日星期五

Tech Art - Week09 - Python Intro


Final Effect

This assignment is essentially an introduction to Python. Although I used Pygame to make a game three years ago, I'd forgotten most of the language (just didn't use it enough). After reviewing Python’s data types and basic syntax, I started this project.

Before writing any code, even though the assignment brief already provided quite a lot of APIs, I wanted to learn more—so the official documentation was my best source; it explains what I needed in detail:
https://www.sidefx.com/docs/houdini/hom/hou/ui.html?utm_source

For development, I felt Houdini's built-in editor was hard to use, so following tutorials and the official docs I configured PyCharm to work with Houdini (the setup is fairly involved, so I' ll skip the details here).


After the environment was set up, the next step was to clarify the program flow (this is very helpful for development).


Once I began actual development, I ran into a problem: input() isn't well supported in Houdini and can easily block execution, potentially crashing Houdini. To fix this, I looked up the relevant docs—the correct approach is to use Houdini's own UI input to avoid crashes: hou.ui.readInput(). This function pops up a dialog in Houdini so we can enter values there.

After solving the input issue, here's a quick recap of the requirements:

  • Write a Houdini Python script that does the following:

    • Let the user choose one of four shapes (e.g., tube, torus, sphere, box).

    • Let the user input the scale of each object, from 0.1 to 10.

    • Let the user input how many objects to place along the cube array's length, width, and height.

    • Generate a cube array based on the chosen shape and scale using those counts.

    • Assign a unique color to each object.

The core logic wasn't difficult, so I finished it quickly; to push it further, I made a series of improvements:

  1. Used hou.ui.readMultiInput() and hou.ui.selectFromList() to implement a single-shape picker and one-shot input of the cube array’s length/width/height.

  2. Wrapped logic into functions to improve readability.

  3. Added safety checks to ensure all inputs are valid.

  4. Ensured the generated array’s geometric center is at (0, 0, 0).

During generation I found another issue: when creating via nodes vs. via code, the default radii for tube, torus, and sphere were inconsistent (Houdini 19.5). I therefore adjusted the defaults (using a 1×1×1 box as the baseline):

if name == "tube":
    shape.parmTuple("rad").set((0.5, 0.5))
elif name == "torus":
    shape.parmTuple("rad").set((0.35, 0.15))
elif name == "sphere":
    shape.parmTuple("rad").set((0.5, 0.5, 0.5))

With that, the implementation was complete.



You can find the source code in my perforce!



2025年10月6日星期一

Tech Art - Week07 - Animation State Machines

Preparation

Download a character you like from Mixamo, along with a set of animations, and import them into Unreal to prepare for building the animation state machine.



Duplicate the existing Character Blueprint in your project, replace its skeletal mesh with the one you prepared, and adjust capsule/ collision parameters as needed.


Create an Animation Blueprint and a Blend Space as shown.



Then assign the newly created Animation Blueprint to the Character Blueprint.


Next, create a GameMode Blueprint so we can direct the game to use our custom character.


Create a new level and open World Settings.


In World Settings, set the GameMode Blueprint you created, and configure your Character Blueprint.


Set Up the Animation State Machine

Idle, SlowRun, FastRun Animations

Open the Blend Space you created and place the Idle, SlowRun, and FastRun animations. You can hold Ctrl and move the mouse in the preview to observe how the animations blend.


In the Animation Blueprint, create your first state — Locomotion — by dragging the asset into the state machine.


Create a variable BS_Speed_Input and wire it as the input for Horizontal Speed in the Blend Space.


With the basics in place, the last step is to pass the character’s Velocity into BS_Speed_Input so the animation state machine actually runs.
Go back to the Blend Space graph and add the blueprint logic (this also contains the upcoming logic for pressing F to enter Fight). The general idea is: first fetch the character’s Velocity via component queries; then get the character’s Animation Blueprint Class and set a Fighting Bool there; finally propagate this Bool to isFighting, which we’ll use to drive fighting state transitions.


After connecting everything, click Play to do a first pass test for Idle / SlowRun / FastRun.


Fighting Animation

Next, build out the complete Animation State Machine logic and set up proper transition rules/frames. The overall layout is as follows:


After that, configure the Fighting input logic in the Character Blueprint. Earlier in the Animation Blueprint we ensured that the Fighting value is forwarded into isFighting, so changing Fighting will also change isFighting. Here we simply set it so that pressing F sets isFighting to True and releasing F sets it to False. Now the character can attack by pressing F.



However, there’s still a bug: the attack animation is an on-the-spot attack, but we didn’t prevent movement during attack. We need to fix that. To also ensure there is no movement during transitional animations (e.g., recovering from fighting back to idle), we add a new variable isRecovering. The character can move only when not isFighting and not isRecovering. Also, when not isFighting, isRecovering will delay and then become False. The blueprint logic is as follows:



2025年10月5日星期日

Common Art - Week 07 - Let’s Make It Stick

 




3D Art - Week07 - Simple Crate and Barrel Part 3

 Final:




This time the workflow was mainly painting the textures in Substance Painter, pushing for a stylized look. I imagined it as a wooden crate used for maritime shipping, so I added water stains and grime. Since it would stay in a damp, water-rich environment, it wouldn't accumulate much dust. In Unreal, I simply followed Substance Painter's standard packed-map export workflow and assigned the textures accordingly.