Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion common.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
F64Material,
F64RenderState,
F64Light,
F64Fog,
)
from .material.cc import SOLID_CC
from .material.tile import get_tile_conf
Expand All @@ -41,6 +42,13 @@ def get_struct_ubo_size(s: struct.Struct):

UBO_SIZE = get_struct_ubo_size(UNIFORM_BUFFER_STRUCT)

SCENE_UNIFORM_BUFFER_STRUCT = struct.Struct(
"2i" # clipping planes
"f" # blender scale
)

SCENE_UNIFORM_BUFFER_SIZE = get_struct_ubo_size(SCENE_UNIFORM_BUFFER_STRUCT)


@dataclasses.dataclass
class ObjRenderInfo:
Expand Down Expand Up @@ -69,6 +77,8 @@ def get_scene_render_state(scene: bpy.types.Scene):
)
),
convert=quantize_tuple(f64render_rs.default_convert, 9.0, -1.0, 1.0),
fog=F64Fog(quantize_tuple(fast64_rs.fogPreviewColor, 8), pos=tuple(fast64_rs.fogPreviewPosition)),
blend_color=quantize_srgb(f64render_rs.default_blend_color),
cc=SOLID_CC,
tex_confs=([get_tile_conf(getattr(f64render_rs, f"default_tex{i}")) for i in range(0, 8)]),
tex_size=(32, 32),
Expand All @@ -84,7 +94,12 @@ def get_scene_render_state(scene: bpy.types.Scene):
return state


def draw_f64_obj(render_engine: "Fast64RenderEngine", render_state: F64RenderState, info: ObjRenderInfo):
def draw_f64_obj(
render_engine: "Fast64RenderEngine",
render_state: F64RenderState,
info: ObjRenderInfo,
area_fog_state: F64RenderState = None,
):
mvp = info.mvp_matrix
bbox = info.render_obj.bounding_box

Expand Down Expand Up @@ -121,6 +136,8 @@ def draw_f64_obj(render_engine: "Fast64RenderEngine", render_state: F64RenderSta

for mat_idx, indices_count, f64mat in info.mats:
render_state.set_values_from_cache(f64mat.state)
if f64mat.use_area_fog and area_fog_state is not None:
render_state.set_values_from_cache(area_fog_state)

gpu.state.face_culling_set(f64mat.cull)
if not render_engine.use_atomic_rendering:
Expand Down Expand Up @@ -248,3 +265,13 @@ def check_if_using_renderer(context: Context):
and space_data.type == "VIEW_3D"
and space_data.shading.type in {"MATERIAL", "RENDERED"}
)


def update_scene_ubo_generic(render_engine: "Fast64RenderEngine", scene: bpy.types.Scene):
from fast64_internal.utility import get_blender_to_game_scale

render_engine.scene_ubo.update(
SCENE_UNIFORM_BUFFER_STRUCT.pack(
*(round(x) for x in scene.fast64.renderSettings.clippingPlanes), get_blender_to_game_scale(bpy.context)
)
)
3 changes: 2 additions & 1 deletion globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ def clear(self):
self.materials_cache: dict[bpy.types.Material, "F64Material"] = {}
self.meshCache: dict["MeshBuffers"] = {}
self.obj_lights: dict[str, "F64Light"] = {}
self.sm64_area_lookup: dict | None = None
self.sm64_area_lookup: dict[str, "AreaRenderInfo"] | None = None # sm64
self.oot_room_lookup: dict | None = None # oot
self.oot_scene_lookup: dict | None = None
self.rebuild_shaders = True
self.current_ucode = self.world_lighting = self.current_gamemode = None

Expand Down
38 changes: 29 additions & 9 deletions material/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,9 @@ def parse_f3d_mat_rendermode(f3d_mat):
(TILE_STRUCT * 8) + (LIGHT_STRUCT * 8) + "8i" # texture configurations, lights, blender
"16i" # color-combiner settings
"i i i i" # geoMode, other-low, other-high, flags
"4f 4f 4f 4f" # prim, prim_lod, prim-depth, env, ambient
"4f 4f 4f 4f 4f 4f" # prim, prim_lod, prim-depth, env, ambient, blend, fog
"3f f 3f i 3f i" # ck center, alpha clip, ck scale, light count, width, mipmap count
"6f 2i" # k0-k5, tex size
"6f 2i 2f" # k0-k5, tex size, fog pos
)
# version of the uniform buffer with all ints
UNIFORM_BUFFER_MASK_STRUCT = struct.Struct(UNIFORM_BUFFER_STRUCT.format.replace("f", "I").replace("i", "I"))
Expand All @@ -208,15 +208,21 @@ class F64Rendermode:
blend: str = "NONE"
depth_test: str = "LESS_EQUAL"
depth_write: bool = True
alpha_clip: float = -1
alpha_clip: float = None


@dataclass
class F64Light:
color: F64Color = (0, 0, 0, 0)
color: F64Color | None = None
direction: tuple[float, float, float] | None = None


@dataclass
class F64Fog:
color: F64Color = None
pos: tuple[float, float] = None


@dataclass
class F64RenderState:
tex_confs: list[F64Texture | None] = None
Expand All @@ -226,16 +232,18 @@ class F64RenderState:
prim_color: F64Color | None = None
prim_lod: tuple[float, float] | None = None
env_color: F64Color | None = None
blend_color: F64Color | None = None
fog: F64Fog = dataclasses.field(default_factory=F64Fog)
ck: tuple[float, float, float, float, float, float, float, float] | None = None
convert: tuple[float, float, float, float, float, float] | None = None
cc: tuple[
float, float, float, float, float, float, float, float, float, float, float, float, float, float, float, float
] | None = None
render_mode: F64Rendermode | None = None
flags: int = 0
geo_mode: int = 0
othermode_l: int = 0
othermode_h: int = 0
flags: int = None
geo_mode: int = None
othermode_l: int = None
othermode_h: int = None
prim_depth: tuple[float, float] = None
tex_size: tuple[int, int] = None
mip_count: int = None
Expand All @@ -248,6 +256,7 @@ def __post_init__(self):
self.lights = [None] * 8
if self.tex_confs is None:
self.tex_confs = [None] * 8
self.save_cache()

def save_cache(self):
self.cached_values = self.np_array(False)
Expand Down Expand Up @@ -289,10 +298,12 @@ def mask_single(value: None | object, _mask_value=0):
else:
tex_data.extend(mask(t.values, 9))

blender, alpha_clip, flags = None, -1, self.flags
blender, alpha_clip, flags = None, None, self.flags
if self.render_mode is not None:
blender = self.render_mode.blender
alpha_clip = self.render_mode.alpha_clip
if flags is None:
flags = 0
flags |= self.render_mode.flags

ck = self.ck
Expand All @@ -314,6 +325,8 @@ def mask_single(value: None | object, _mask_value=0):
*mask(self.prim_depth, 2),
*mask(self.env_color, 4),
*mask(self.ambient_color, 4),
*mask(self.fog.color, 4),
*mask(self.blend_color, 4),
*mask(ck[:3], 3),
mask_single(alpha_clip),
*mask(ck[3:6], 3),
Expand All @@ -322,6 +335,7 @@ def mask_single(value: None | object, _mask_value=0):
mask_single(self.mip_count),
*mask(self.convert, 6),
*mask(self.tex_size, 2),
*mask(self.fog.pos, 2),
),
dtype=np.uint64,
)
Expand Down Expand Up @@ -358,6 +372,7 @@ def set_from_rendermode(self, rendermode: RenderMode):
@dataclass
class F64Material:
state: F64RenderState = dataclasses.field(default_factory=F64RenderState)
use_area_fog: bool = False
cull: str = "NONE"
layer: int | str | None = None

Expand Down Expand Up @@ -401,6 +416,11 @@ def f64_material_parse(f3d_mat: "F3DMaterialProperty", always_set: bool, set_lig
state.ck = tuple((*quantize_srgb(f3d_mat.key_center, False), *f3d_mat.key_scale, *f3d_mat.key_width))
if always_set or (f3d_mat.set_k0_5 and cc_uses["Convert"]):
state.convert = tuple(getattr(f3d_mat, f"k{i}") for i in range(0, 6))
if always_set or (f3d_mat.set_fog and f3d_mat.rdp_settings.using_fog):
f64mat.use_area_fog = f3d_mat.use_global_fog
state.fog = F64Fog(quantize_tuple(f3d_mat.fog_color, 8), tuple(f3d_mat.fog_position))
if always_set or f3d_mat.set_blend:
state.blend_color = quantize_srgb(f3d_mat.blend_color)
if always_set or (cc_uses["Shade"] and rdp.g_lighting and f3d_mat.set_lights):
state.ambient_color = quantize_srgb(f3d_mat.ambient_light_color, force_alpha=True)
if f3d_mat.use_default_lighting:
Expand Down
112 changes: 82 additions & 30 deletions oot.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,23 @@
import mathutils

from .material.parser import parse_f3d_rendermode_preset, F64RenderState
from .common import ObjRenderInfo, draw_f64_obj, collect_obj_info, get_scene_render_state
from .common import (
SCENE_UNIFORM_BUFFER_STRUCT,
ObjRenderInfo,
draw_f64_obj,
collect_obj_info,
get_scene_render_state,
update_scene_ubo_generic,
)
from .properties import F64RenderSettings
from .globals import F64_GLOBALS


def get_time_of_day_settings(scene: bpy.types.Object):
time_of_day_lights = scene.ootSceneHeader.timeOfDayLights
return getattr(time_of_day_lights, time_of_day_lights.menuTab.lower())


class RoomRenderInfo(NamedTuple):
render_state: F64RenderState
name: str
Expand All @@ -18,30 +30,39 @@ def __hash__(self):
return hash(self.name)


class SceneRenderInfo(NamedTuple):
render_state: F64RenderState
name: str
obj: bpy.types.Object


def get_oot_room_childrens(scene: bpy.types.Scene):
if F64_GLOBALS.oot_room_lookup is not None:
return F64_GLOBALS.oot_room_lookup
if F64_GLOBALS.oot_room_lookup is not None and F64_GLOBALS.oot_scene_lookup is not None:
return F64_GLOBALS.oot_room_lookup, F64_GLOBALS.oot_scene_lookup

oot_room_lookup = {}
oot_room_lookup, oot_scene_lookup = {}, {}
render_state = get_scene_render_state(scene)
scene_objs: list[bpy.types.Object] = []
room_objs: list[bpy.types.Object] = []

def get_room_children(obj: bpy.types.Object, name: str):
def get_room_info(obj: bpy.types.Object):
return RoomRenderInfo(render_state, obj.name)

def get_room_children(obj: bpy.types.Object, render_info: RoomRenderInfo, scene_info: SceneRenderInfo = None):
for child in sorted(obj.children, key=lambda item: item.name):
if scene_info is not None:
oot_scene_lookup[child.name] = scene_info
if child not in room_objs:
oot_room_lookup[child.name] = RoomRenderInfo(render_state, name)
get_room_children(child, name)
oot_room_lookup[child.name] = render_info
get_room_children(child, render_info, scene_info)
else:
get_room_children(child, child.name)
get_room_children(child, get_room_info(child), scene_info)

def get_scene_children(obj: bpy.types.Object, name: str):
def get_scene_children(obj: bpy.types.Object, scene_info: SceneRenderInfo):
for child in sorted(obj.children, key=lambda item: item.name):
oot_scene_lookup[child.name] = scene_info
if child in room_objs:
get_room_children(child, child.name)
else:
oot_room_lookup[child.name] = RoomRenderInfo(render_state, name)
get_scene_children(child, name)
get_room_children(child, get_room_info(child), scene_info)

for obj in bpy.data.objects:
if obj.type == "EMPTY":
Expand All @@ -51,15 +72,24 @@ def get_scene_children(obj: bpy.types.Object, name: str):
room_objs.append(obj)

for scene_obj in scene_objs:
get_scene_children(scene_obj, scene_obj.name)

fake_room = RoomRenderInfo(render_state, "")
render_state_copy = render_state.copy()
time_of_day = get_time_of_day_settings(scene_obj)
render_state_copy.fog.pos = (time_of_day.fogNear, 1000)
render_state.save_cache()
oot_scene_lookup[scene_obj.name] = SceneRenderInfo(render_state, scene_obj.name, scene_obj)
get_scene_children(scene_obj, oot_scene_lookup[scene_obj.name])

fake_room = oot_room_lookup[""] = RoomRenderInfo(render_state, "")
fake_scene = oot_scene_lookup[""] = SceneRenderInfo(render_state, "", None)
for obj in bpy.data.objects:
if obj.name not in oot_room_lookup:
oot_room_lookup[obj.name] = fake_room
if obj.name not in oot_scene_lookup:
oot_scene_lookup[obj.name] = fake_scene

F64_GLOBALS.oot_room_lookup = oot_room_lookup
return oot_room_lookup
F64_GLOBALS.oot_scene_lookup = oot_scene_lookup
return oot_room_lookup, oot_scene_lookup


# TODO if porting to fast64, reuse existing default layer dict
Expand All @@ -79,6 +109,8 @@ def draw_oot_scene(
view_matrix: mathutils.Matrix,
always_set: bool,
):
from fast64_internal.utility import get_blender_to_game_scale

f64render_rs: F64RenderSettings = depsgraph.scene.f64render.render_settings

layer_rendermodes = {} # TODO: should this be cached globally?
Expand All @@ -94,12 +126,13 @@ def draw_oot_scene(

ignore, collision = f64render_rs.render_type == "IGNORE", f64render_rs.render_type == "COLLISION"
specific_room = f64render_rs.oot_specific_room.name if f64render_rs.oot_specific_room else None
room_lookup = get_oot_room_childrens(depsgraph.scene)
layer_queue: dict[str, dict[RoomRenderInfo, dict[str, ObjRenderInfo]]] = {}
room_lookup, scene_lookup = get_oot_room_childrens(depsgraph.scene)
layer_queue: dict[str, dict[RoomRenderInfo, dict[SceneRenderInfo, dict[str, ObjRenderInfo]]]] = {}

for obj in depsgraph.objects:
obj_name = obj.name
room = room_lookup[obj_name]
scene = scene_lookup[obj_name]
if (
(ignore and obj.ignore_render)
or (collision and obj.ignore_collision)
Expand All @@ -114,21 +147,40 @@ def draw_oot_scene(

for mat_info in obj_info.mats:
mat = mat_info[2]
room_queue = layer_queue.setdefault(mat.layer or "Opaque", {}) # if layer has no room queue, create it
obj_queue = room_queue.setdefault(room, {}) # if current room has no obj queue in this layer, create it
scene_queue = layer_queue.setdefault(mat.layer or "Opaque", {}) # if layer has no room queue, create it
room_queue = scene_queue.setdefault(scene.name, {})
obj_queue = room_queue.setdefault(
room.name, {}
) # if current room has no obj queue in this layer, create it
if obj_name not in obj_queue: # if obj not already present in the layer's obj queue, create a shallow copy
obj_info = obj_queue[obj_name] = copy.copy(obj_info)
obj_info.mats = []
obj_queue[obj_name].mats.append(mat_info)

for layer in ("Opaque", "Transparent", "Overlay"):
room_queue = layer_queue.get(layer)
if room_queue is None:
scene_queue = layer_queue.get(layer)
if scene_queue is None:
continue
# sort by room name, this doesn't correspond to something the fast64 exporter or the game rendering does
# but it at least helps make the behavior reproducible
for room, obj_queue in sorted(room_queue.items(), key=lambda item: item[0].name):
render_state = room.render_state.copy()
render_state.set_values_from_cache(layer_rendermodes.get(layer, layer_rendermodes["Opaque"]))
for info in dict(sorted(obj_queue.items(), key=lambda item: item[0])): # sort by obj name
draw_f64_obj(render_engine, render_state, obj_queue[info])
for scene_name in sorted(scene_queue.keys(), key=lambda item: item):
scene = scene_lookup[scene_name]
render_state = scene.render_state.copy()
if scene_name == "":
update_scene_ubo_generic(render_engine, depsgraph.scene)
else:
current = get_time_of_day_settings(scene.obj)
render_engine.scene_ubo.update(
SCENE_UNIFORM_BUFFER_STRUCT.pack(
*(round(x) for x in (10, current.z_far)), get_blender_to_game_scale(bpy.context)
)
)
room_queue = scene_queue.get(scene_name)
if room_queue is None:
continue
# sort by room name, this doesn't correspond to something the fast64 exporter or the game rendering does
# but it at least helps make the behavior reproducible
for room_name, obj_queue in sorted(room_queue.items(), key=lambda item: item[0]):
room = room_lookup[room_name]
render_state.set_values_from_cache(room.render_state)
render_state.set_values_from_cache(layer_rendermodes.get(layer, layer_rendermodes["Opaque"]))
for info in dict(sorted(obj_queue.items(), key=lambda item: item[0])): # sort by obj name
draw_f64_obj(render_engine, render_state, obj_queue[info])
4 changes: 4 additions & 0 deletions properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,9 @@ class F64RenderSettings(bpy.types.PropertyGroup):
min=0,
max=1,
)
default_blend_color: bpy.props.FloatVectorProperty(
description="Blend Color", subtype="COLOR", size=4, min=0, max=1, default=(1, 1, 1, 1)
)

chroma_tab: bpy.props.BoolProperty(name="Chroma Key")
default_key_center: bpy.props.FloatVectorProperty(
Expand Down Expand Up @@ -271,6 +274,7 @@ def draw_props(self, layout: bpy.types.UILayout, gameEditorMode: str):
prim_lod_row.prop(self, "default_lod_frac", text="Frac")
prim_lod_row.prop(self, "default_lod_min", text="Min")
prop_split(sources_box, self, "default_env_color", "Environment")
prop_split(sources_box, self, "default_blend_color", "Blend")

sources_box.prop(self, "chroma_tab", icon="TRIA_DOWN" if self.chroma_tab else "TRIA_RIGHT")
if self.chroma_tab:
Expand Down
Loading