enigma-dev / enigma-dev/enigma-dev
Procedural Fragment Shader Generation Using Classic Machine Learning - 2nd Phase
- Dominant language
- C++
- Stars
- 379
- Forks
- 125
- PR merge metrics
- No merged PRs in 30d
Description
You can check #2390 for more information.
This part of the project is split into two major parts:
1. Move the work inside [https://github.com/k0T0z/shader-gen](https://github.com/k0T0z/shader-gen) to RGM.
2. Writing the generation algorithm. This part is the project itself (recheck the subject, heh). If you are familiar with Genetic Algorithms, you will know that you need to encode the shader graph somehow in order to survive Selection, Mutation, and Crossover. The YAML representation of the graph will be encoded first, or whatever approach you like. Take this idea as an example, which just came to mind:
```yaml
nodes:
- id: 0
xCoordinate: 713.60357366478547
yCoordinate: -6.9198548747969468
output: {}
- id: 1
xCoordinate: -470.22680087174678
yCoordinate: -330.5770781070517
input:
type: INPUT_TYPE_UV
connections:
- id: 0
fromNodeId: 1
fromPortIndex: 0
toNodeId: 3
toPortIndex: 0
- id: 1
fromNodeId: 2
fromPortIndex: 0
toNodeId: 5
toPortIndex: 0
```
to
```
0;1;4;1,1;1;2;0;5;0
```
This is a comma-separated entities where each one represents a node/connection, where a node:
- ``1st location`` -> 0 for node and 1 for connection
- ``2nd`` -> id
- ``3rd`` -> type field number (Check the .proto files [here](https://github.com/k0T0z/shader-gen/tree/master/gui/model/schema) to know what this means.)
- ``4th ... nth`` -> values inside the each field
The example above is just a single individual in the population. If you came up with the perfect encoding format please mention the reasons why it will be a great format in your proposal.
The second thing is the Fitness Function, and this can be as simple as comparing two images and trying to minimize the output of this function.
Example:
```python
from PIL import Image
import numpy as np
def calculate_image_difference(output_image_path, target_image_path):
# Open the output image
output_image = Image.open(output_image_path)
# Open the target image
target_image = Image.open(target_image_path)
# Ensure both images have the same mode (e.g., RGB)
output_image = output_image.convert('RGB')
target_image = target_image.convert('RGB')
# Resize the output image to match the target image's dimensions
output_image = output_image.resize(target_image.size)
# Convert images to numpy arrays
output_array = np.array(output_image)
target_array = np.array(target_image)
# Calculate the difference (MSE)
mse = np.mean((output_array - target_array) ** 2)
return mse
# Calculate the difference between 'output.jpg' and 'target.jpg'
difference = calculate_image_difference('output.jpg', 'target.jpg')
print(f"The Mean Squared Error (MSE) is: {difference}")
```
```
The Mean Squared Error (MSE) is: 111.69857791274818
```
Note that I used the built-in serialization feature of protobuf in shader-gen, and that's why you will see the graph as JSON; however, in ENIGMA, we use YAML. Check [libEGM](https://github.com/enigma-dev/enigma-dev/tree/master/CommandLine/libEGM).
Note that the size of both images should be the same; otherwise, you will have to do some resizing and so on.
## Mandatory Work Before The Genetic Algorithm
1. Remove the coordinates from the model and replace it with an algorithm that calculates coordinates on every launch.
2. We need to be able to import resources such as backgrounds and sprites to be able to match them. Note, Josh requests to get started with backgrounds ONLY for now.
3. We need to have a function that converts the shader's output to an image. This is needed for Fitness Function.
## Next Steps (This is for the next year or for a LLM nerd this year. I think I should put it in a separate idea.)
The YAML representation for a graph can be generated using LLM. We can start trying it with a non-Vision model. We will use a transfer learning approach for the training.
Inputs: Prompt. Example: 'grass'
Outputs: YAML representation of a graph. Note that we don't need to generate locations for the node.
```yaml
nodes:
- id: 0
output: {}
- id: 1
input:
type: INPUT_TYPE_UV
connections:
- id: 0
fromNodeId: 1
fromPortIndex: 0
toNodeId: 3
toPortIndex: 0
- id: 1
fromNodeId: 2
fromPortIndex: 0
toNodeId: 5
toPortIndex: 0
```
NOTE: Input can be improved with a Vision model (example: [Llama-3.2-11B-Vision](https://huggingface.co/meta-llama/Llama-3.2-11B-Vision)) to include both the prompt and the image.
### Dataset
I think you will work on the Visual Shader Editor I created and try to implement a 'graph' that generates a 'grass' output for you, and that's your first sample. Note that this has nothing to do with the shader code; however, the `Generator` will take your graph and give you a code, so the LLM should generate the graph.
## Resources
- Book: Hands-On Genetic Algorithms with Python
Mentors: Josh, Seif
Difficulty: Medium - Hard
Expected size: 350h
Skills required: C++ fundamentals, Shader Scripting, Classic Machine Learning, Protocol Buffers, and Qt
Skills preferred: Knowledge of Genetic Programming is preferred, as well as Deep Learning theory and Multi-threading
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.