Jump to content

Specular Map to Metal-Roughness


reepblue
 Share

Recommended Posts

This extension is for those who have existing materials that were made for the Blinn-Phong shading (diffuse+normal+specular) such as the textures for Leadwerks. Right click on an image with a specular suffix in the name and a "Convert Specular to Metal-Roughness" option will show up. This will take the spec sheet, insert the existing pixels in the blue channel, then swap the pixels and store that copy into the green channel. (This seems to take a bit)

The result will be a blue and green texture. 

image.thumb.png.51e85ecdde38a659eb192c685c886b9b.png

In your material, you then can use the Metalness and Roughness spinners to adjust the values. I find setting the Metalness to 0.1 is what you want for most non-metal textures to mimic the specular effect. For things that are metal, well that's up to you to tweak and find out. It really helps if you know how the material should look.

image.thumb.png.6d78c8ee9f1070036c2e89175b80d137.png

The only thing you might need to consider is any roughness map you've made if you came from Leadwerks. That mask wasn't really conventional, and you may want to look into packing that yourself with the Channel Packing Tool to get the desired look. 

SpecMapToMetalRoughness.lua

  • Like 3

Cyclone - Ultra Game System - Component PreprocessorTex2TGA - Darkness Awaits Template (Leadwerks)

If you like my work, consider supporting me on Patreon!

Link to comment
Share on other sites

  • 2 weeks later...

Quick patch:

local extension = {}
extension.currentsuffix = ""
extension.suffix = "metalroughness"

function extension.IsSpecMap(file)
    if file == nil then return false end

    if FileType(file) == 0 then return false end

    -- Check the file extention, make sure it's an image.
    local ext = ExtractExt(string.lower(file))
    if ext ~= "tga" and ext ~= "png" and ext ~= "jpg" and ext ~= "bmp" then return false end

    -- Check to see if the file has a specular suffix at the end
    if Right(StripExt(file), 8) ==  string.lower("specular") then 
        extension.currentsuffix = string.lower("specular") 
    elseif Right(StripExt(file), 4) == string.lower("spec")  then
        extension.currentsuffix = string.lower("spec")
    elseif Right(StripExt(file), 1) ==  string.lower("s")  then
        extension.currentsuffix = string.lower("s")         
    else
        extension.currentsuffix = ""
        return false
    end

    -- Return true if all conditions met!
    return true
end

function extension.StorePixelsInChannel(src, dst, channel)
    if src then
        local sz = src.size                   
        if src.blocksize ~= 1 then
            src = src:Convert(TEXTURE_RGBA)
            if src == nil then
                Print("Error: Failed to convert pixmap format")
                return false
            end
        end
        if src.size ~= sz then
            src = src:Resize(sz.x, sz.y)
        end
        if not src:CopyChannel(channel, dst, channel) then
            Print("Error: Pixmap:CopyChannel failed on channel "..tostring(n))
        end
    end 
end

function extension.ConvertSpecToMetalRoughness(file)
    local pixmap = LoadPixmap(file)
    if pixmap==nil then Print("Error: Failed to generate texture \"" .. file .. "\"") return false end
    if pixmap.blocksize ~= 1 then pixmap = pixmap:Convert(TEXTURE_RGBA) end

    -- Define the channels
    local roughnesschannel = 2 -- Green
    local specchannel = 3 -- blue

    local dst = CreatePixmap(pixmap.size.x, pixmap.size.x)
    dst:Fill(Rgba(0,0,0,255))

    -- Store the spec in the blue channel.
    extension.StorePixelsInChannel(pixmap, dst, specchannel)

    -- Roughness is the specular but we invert the colors. (This is also really slow)
    local roughness = CreatePixmap(pixmap.size.x,  pixmap.size.y)
    roughness:Fill(Rgba(0,0,0,255))
    Print("Swapping colors from the specular map. This might take a while...")
    for x = 0, pixmap.size.x - 1 do
        for y = 0, pixmap.size.y - 1 do
            local rgba = pixmap:ReadPixel(x, y)
            local r = 255-Red(rgba)
            local g = 255-Green(rgba)
            local b = 255-Blue(rgba)
            rgba = Rgba(r, g, b, 255)
            roughness:WritePixel(x, y, rgba)
        end
    end
    extension.StorePixelsInChannel(roughness, dst, roughnesschannel)
    roughness = nil

    -- Save as a DDS texture
    local d = StripExt(file)
    local name = Left(d, Len(d) - Len(extension.currentsuffix))
    local output = name .. extension.suffix .. ".dds"

    --Build mipmap chain
    local mipchain = dst:BuildMipchain()
    local ddsformat = TEXTURE_BC7--Everything else
    local blocksize = 4

    local count = #mipchain
    local block = dst.blocksize

    --Convert to desired compression format
    if ddsformat ~= 0 then
        local mipchain2 = {}
        local n
        for n = 1, #mipchain do
            if mipchain[n].size.x < blocksize or mipchain[n].size.y < blocksize then
                break
            else
                mipchain2[n] = mipchain[n]:Convert(ddsformat)
                if mipchain2[n] == nil then
                    Print("Error: Failed to convert \""..path.."\" to format "..tostring(ddsformat))
                    return false
                end
            end
        end
        mipchain = mipchain2
    end

    --Save the texture
    return SaveTexture(output, TEXTURE_2D, mipchain)
end

function extension.hook(event, extension)
    if event.id == EVENT_WIDGETACTION then
        if event.source == extension.menu[1] then
            if program.assetbrowser.selectedfile then
                if program.assetbrowser.selectedfile.package == nil then
                    Print("Converting \""..StripDir(program.assetbrowser.selectedfile.path).."\"")
                    if extension.ConvertSpecToMetalRoughness(program.assetbrowser.selectedfile.path) then
                        extension.currentsuffix = ""
                        Print("Done!")
                    end
                    collectgarbage()
                end
            end
        end
    elseif event.id == EVENT_WIDGETOPEN then
        if event.source == program.assetbrowser.filepopupmenu then
            --Hide the menu items if they exist
            if extension.menu ~= nil then                
                extension.menu[1]:SetHidden(true)
                extension.menu[2]:SetHidden(true)
            end

            if program.assetbrowser.selectedfile then
                if program.assetbrowser.selectedfile.package == nil then           
                    if extension.IsSpecMap(program.assetbrowser.selectedfile.path) then
                        --Create menu items if needed
                        if extension.menu == nil then
                            widget = Widget(event.source)
                            extension.menu = {}
                            extension.menu[1] = CreateMenu("Convert Specular to Metal-Roughness", widget)
                            extension.menu[2] = CreateMenu("", widget)
                            extension.menu[2]:SetParent(widget, 0)
                            extension.menu[1]:SetParent(widget, 0)
                            ListenEvent(EVENT_WIDGETACTION, extension.menu[1], extension.hook, extension)
                        end
                        
                        --Show the menu item and divider
                        extension.menu[1]:SetHidden(false)
                        extension.menu[2]:SetHidden(false)
                    end
                end
            end
        end
    end
end

ListenEvent(EVENT_WIDGETOPEN, program.assetbrowser.filepopupmenu, extension.hook, extension)

 

Cyclone - Ultra Game System - Component PreprocessorTex2TGA - Darkness Awaits Template (Leadwerks)

If you like my work, consider supporting me on Patreon!

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...