Reputation: 1
I'm working on a Python script to generate images from EXR channel data for a larger project that requires accurate normal pass mappings. The script processes the X, Y, and Z channels from an EXR file and maps them to the RGB channels of the resulting image. However, the output image exhibits significant artifacts and distortions, making the normal pass mapping incorrect compared to the expected result.
Here's a minimal example script that demonstrates the issue. This script is intended to be run within Blender's Python environment. It uses OpenEXR
to read the 16 bit EXR file, processes the normal pass, and saves the result as a PNG image using Pillow
. You can use this 16 bit EXR:https://wetransfer.com/downloads/2855df23fed4b0c348e1cfa4371bc35020241110001843/0172f8
import bpy
import OpenEXR
import Imath
import numpy as np
from PIL import Image
import os
def process_normal_pass(exr_path, output_path):
# Open EXR file
exr = OpenEXR.InputFile(exr_path)
header = exr.header()
channels = header['channels']
# Get image dimensions
dw = header['dataWindow']
width = dw.max.x - dw.min.x + 1
height = dw.max.y - dw.min.y + 1
# Process Normal Pass channels (assuming 16-bit)
channel_data = []
for channel in ['X', 'Y', 'Z']:
full_channel_name = f'ViewLayer.Normal.{channel}'
pixel_type = Imath.PixelType(Imath.PixelType.HALF) # 16-bit
channel_str = exr.channel(full_channel_name, pixel_type)
channel_array = np.frombuffer(channel_str, dtype=np.float16).reshape(height, width)
channel_data.append(channel_array)
# Convert normal vectors to RGB colors
x, y, z = channel_data
# Normalize the vectors
magnitude = np.sqrt(x*x + y*y + z*z)
magnitude = np.where(magnitude > 0, magnitude, 1)
x = x / magnitude
y = y / magnitude
z = z / magnitude
# Map normal components to RGB (world space)
r = np.abs(x) # X component to Red
g = np.zeros_like(y) # Suppress green channel
b = np.abs(z) # Z component to Blue
# Create the RGB representation
layer_data = np.dstack([r, g, b])
# Scale to 16-bit range and convert
layer_data = layer_data * 65535.0
layer_data = np.clip(layer_data, 0, 65535.0)
layer_data = layer_data.astype(np.uint16)
# Save as 16-bit PNG
img = Image.fromarray(layer_data, mode='RGB')
img.save(output_path, format='PNG', optimize=False)
print(f"Normal pass image saved to {output_path}")
# Example usage
exr_file = bpy.path.abspath('//normal_pass.exr')
output_png = bpy.path.abspath('//normal_pass_output.png')
process_normal_pass(exr_file, output_png)
Dependencies:
OpenEXR
Imath
numpy
Pillow
bpy
module. This optional, only use this if you are running the example in blender beacuse the bpy.path.abspath of the codeEnvironment:
Instructions to Run:
OpenEXR
, Imath
, numpy
, Pillow
) are installed in Blender's Python environment or your own envoroment.'//path_to_your_exr_file.exr'
with the actual path to your EXR file.Expected Result: A smooth and accurate representation of the normal vectors without visible artifacts or distortions. IMAGE:https://new.imagehostx.com/upload/2024/11/09/normalexr_1731171391.png
Actual Result: The output image contains concentrated lines of red and blue, disrupting the uniformity and smoothness of the normal pass mapping. IMAGE: https://new.imagehostx.com/upload/2024/11/09/putitoREAL_1731171538.png
I attempted two modifications to the normal pass processing in the script to resolve the artifacts:
Preserving the Green Channel:
Instead of suppressing the green channel (g = np.zeros_like(y)
), I changed it to preserve the Y component by using g = np.abs(y)
. However, this resulted in an output that was ten times worse, with the image not resembling the expected normal pass at all. IMAGE:https://new.imagehostx.com/upload/2024/11/09/vicentesHOY_1731171598.png
# Original code
r = np.abs(x) # X component to Red
g = np.zeros_like(y) # Suppress green channel
b = np.abs(z) # Z component to Blue
# Modification 1
r = np.abs(x)
g = np.abs(y) # Preserving the Y component
b = np.abs(z)
Alternate Mapping Approach:
I tried remapping the normal components from the range [-1, 1] to [0, 1] using the following formulas: r = (x + 1.0) * 0.5
, g = (y + 1.0) * 0.5
, and b = (z + 1.0) * 0.5
. This approach also led to undesirable results, as the normal pass interpretation should be in world space, not camera space or tangent space. IMAGE:https://new.imagehostx.com/upload/2024/11/09/Basta_1731171625.png
# Modification 2
r = (x + 1.0) * 0.5
g = (y + 1.0) * 0.5
b = (z + 1.0) * 0.5
Despite these attempts, the output images still do not accurately represent the normal pass as intended. I need assistance in identifying the root cause of these distortions and finding a reliable solution to correctly map the X, Y, and Z channels to RGB without introducing artifacts.
Upvotes: 0
Views: 129