Depth test or depth write on material settings

Do I have to use render script to make the mesh always visible on screen? I want the pickaxe to always be visible

recording_20260215_204705

1 Like

You want to draw your view models onto their own render predicate/render target to avoid this. Common thing in first person games.

1 Like

Yes. Change the tag in voxelizer.material to somethingelse
Then in the render script add:

...
self.predicates = create_predicates("tile", "gui", "particle", "model", "debug_text","somethingelse")
...
    render.enable_state(graphics.STATE_CULL_FACE)
    render.draw(predicates.somethingelse, draw_options_world)
    render.set_depth_mask(false)
    render.disable_state(graphics.STATE_CULL_FACE)
    
    render.draw_debug3d()
...

This works; although I don’t properly know what all the render settings do :slight_smile:

2 Likes

The render script is a little bit of a pain. I solved

I added “hand” tag to voxelizer.material on right side

-- custom.render_script
-- Advanced render script optimized with local variable access to eliminate table lookups in update().
-- Transparency and the "hand" (voxelizer) layer are preserved.

--------------------------------------------------------------------------------
-- Localized Modules (Optimization: Upvalues)
--------------------------------------------------------------------------------
local render                 = render
local vmath                  = vmath
local sys                    = sys
local math                   = math

--------------------------------------------------------------------------------
-- Cached Message Hashes
--------------------------------------------------------------------------------
local MSG_CLEAR_COLOR        = hash("clear_color")
local MSG_WINDOW_RESIZED     = hash("window_resized")
local MSG_SET_VIEW_PROJ      = hash("set_view_projection")
-- local MSG_SET_CAMERA_PROJ =     hash("use_camera_projection")
local MSG_USE_STRETCH_PROJ   = hash("use_stretch_projection")
local MSG_USE_FIXED_PROJ     = hash("use_fixed_projection")
local MSG_USE_FIXED_FIT_PROJ = hash("use_fixed_fit_projection")

--------------------------------------------------------------------------------
-- Render State & Predicates (Local Scope for Performance)
--------------------------------------------------------------------------------
local model_pred
local transparent_pred
local hand_pred
local gui_pred
local particle_pred
local debug_text_pred

-- Camera objects
local world_cam              = { view = vmath.matrix4(), proj = vmath.matrix4(), near = -1, far = 1, zoom = 1, proj_fn = nil }
local gui_cam                = { view = vmath.matrix4(), proj = vmath.matrix4(), near = -1, far = 1, zoom = 1, proj_fn = nil }

-- Active primary camera reference (defaults to world_cam)
local primary_camera         = world_cam

-- Global Render State
local window_width           = 0
local window_height          = 0
local main_width             = 0
local main_height            = 0
local last_win_w             = 0
local last_win_h             = 0
local is_valid               = false

local clear_buffers          = {
    [render.BUFFER_COLOR_BIT] = vmath.vector4(0, 0, 0, 0),
    [render.BUFFER_DEPTH_BIT] = 1,
    [render.BUFFER_STENCIL_BIT] = 0
}

--------------------------------------------------------------------------------
-- Performance Constants
--------------------------------------------------------------------------------
local DEFAULT_NEAR           = -1
local DEFAULT_FAR            = 1
local DEFAULT_ZOOM           = 1

--------------------------------------------------------------------------------
-- Projection Logic
--------------------------------------------------------------------------------

-- Helper: Update a specific camera's projection based on its assigned function
local function update_camera_projection(camera)
    if camera.proj_fn then
        camera.proj = camera.proj_fn(camera)
    end
end

-- Fixed projection (centered, fixed zoom)
local function calculate_fixed_projection(camera)
    local zoom = camera.zoom or DEFAULT_ZOOM
    local p_w = window_width / zoom
    local p_h = window_height / zoom
    local left = -(p_w - main_width) / 2
    local bottom = -(p_h - main_height) / 2
    return vmath.matrix4_orthographic(left, left + p_w, bottom, bottom + p_h, camera.near, camera.far)
end

-- Fit projection (centers and fits to window)
local function calculate_fit_projection(camera)
    camera.zoom = math.min(window_width / main_width, window_height / main_height)
    return calculate_fixed_projection(camera)
end

-- Stretched projection (fills entire window)
local function calculate_stretched_projection(camera)
    return vmath.matrix4_orthographic(0, main_width, 0, main_height, camera.near, camera.far)
end

-- GUI projection (matches window coordinates)
local function calculate_gui_projection(camera)
    return vmath.matrix4_orthographic(0, window_width, 0, window_height, camera.near, camera.far)
end

-- Refreshes window dimensions and updates all cameras
local function refresh_render_state()
    window_width = render.get_window_width()
    window_height = render.get_window_height()

    is_valid = window_width > 0 and window_height > 0
    if not is_valid then return false end

    -- Optimization: Skip if dimensions haven't changed
    if window_width == last_win_w and window_height == last_win_h then
        return true
    end

    last_win_w = window_width
    last_win_h = window_height
    main_width = render.get_width()
    main_height = render.get_height()

    -- Update both cameras
    update_camera_projection(world_cam)
    update_camera_projection(gui_cam)

    return true
end

local function setup_camera_properties(camera, proj_fn, near, far, zoom)
    camera.near    = near or DEFAULT_NEAR
    camera.far     = far or DEFAULT_FAR
    camera.zoom    = zoom or DEFAULT_ZOOM
    camera.proj_fn = proj_fn
end

--------------------------------------------------------------------------------
-- Engine Lifecycle
--------------------------------------------------------------------------------

function init(self)
    -- Initialize predicates
    model_pred                             = render.predicate({ "model" })
    transparent_pred                       = render.predicate({ "transparent" })
    hand_pred                              = render.predicate({ "hand" })
    gui_pred                               = render.predicate({ "gui" })
    particle_pred                          = render.predicate({ "particle" })
    debug_text_pred                        = render.predicate({ "debug_text" })

    -- Initial clear color from config
    clear_buffers[render.BUFFER_COLOR_BIT] = vmath.vector4(
        sys.get_config_number("render.clear_color_red", 0),
        sys.get_config_number("render.clear_color_green", 0),
        sys.get_config_number("render.clear_color_blue", 0),
        sys.get_config_number("render.clear_color_alpha", 0)
    )

    -- Setup initial camera states
    setup_camera_properties(world_cam, calculate_stretched_projection)
    setup_camera_properties(gui_cam, calculate_gui_projection)

    refresh_render_state()
end

function update(self)
    if not is_valid then
        if not refresh_render_state() then return end
    end

    -- 1. Preparation & Clear
    render.set_depth_mask(true)
    render.set_stencil_mask(0xff)
    render.clear(clear_buffers)

    -- Viewport & Global Camera View
    render.set_viewport(0, 0, window_width, window_height)
    render.set_view(primary_camera.view)
    render.set_projection(primary_camera.proj)

    -- 2. Opaque Pass
    render.enable_state(render.STATE_DEPTH_TEST)
    render.enable_state(render.STATE_CULL_FACE)
    render.set_cull_face(render.FACE_BACK)
    render.disable_state(render.STATE_BLEND)

    render.draw(model_pred)

    -- 3. Transparent Pass
    render.enable_state(render.STATE_BLEND)
    render.set_blend_func(render.BLEND_SRC_ALPHA, render.BLEND_ONE_MINUS_SRC_ALPHA)
    render.draw(transparent_pred)

    -- Particle Pass
    render.draw(particle_pred)
    render.disable_state(render.STATE_BLEND)

    -- 4. Overlay Pass (Hand/Voxelizer)
    -- Reset depth to ensure hand item is always on top
    render.set_depth_mask(true)
    render.clear({ [render.BUFFER_DEPTH_BIT] = 1 })
    render.draw(hand_pred)

    -- Debug Lines
    render.disable_state(render.STATE_DEPTH_TEST)
    render.draw_debug3d()

    -- 5. GUI & Debug Post-Process
    render.set_view(gui_cam.view)
    render.set_projection(gui_cam.proj)
    render.set_depth_mask(false)
    render.disable_state(render.STATE_CULL_FACE)

    -- UI Drawing (requires blend)
    render.enable_state(render.STATE_BLEND)
    render.draw(gui_pred)

    -- Debug Text (Blend disabled as requested)
    render.disable_state(render.STATE_BLEND)
    render.draw(debug_text_pred)
end

function on_message(self, message_id, message)
    if message_id == MSG_SET_VIEW_PROJ then
        primary_camera.view = message.view
        primary_camera.proj = message.projection
    elseif message_id == MSG_WINDOW_RESIZED then
        refresh_render_state()
    elseif message_id == MSG_CLEAR_COLOR then
        clear_buffers[render.BUFFER_COLOR_BIT] = message.color
    elseif message_id == MSG_USE_STRETCH_PROJ then
        setup_camera_properties(world_cam, calculate_stretched_projection, message.near, message.far)
        update_camera_projection(world_cam)
    elseif message_id == MSG_USE_FIXED_PROJ then
        setup_camera_properties(world_cam, calculate_fixed_projection, message.near, message.far, message.zoom)
        update_camera_projection(world_cam)
    elseif message_id == MSG_USE_FIXED_FIT_PROJ then
        setup_camera_properties(world_cam, calculate_fit_projection, message.near, message.far)
        update_camera_projection(world_cam)
    end
end


2 Likes

Is there a missing important part in custom render script? I saw built in render script which is longer than my custom, I didn’t have time to examine it in detail.

2 Likes

ow ye I was testing render script fonts became more readable on bright background
render.disable_state(render.STATE_BLEND)
before debug text predicate

1 Like