994 lines
31 KiB
Plaintext
Executable File
994 lines
31 KiB
Plaintext
Executable File
-- Scene shader-optimiser:
|
|
-- Looks for materials in scene that can be auto-optimised, and offers to apply optimisations.
|
|
|
|
-- Neal D Corbett 13/07/2012
|
|
|
|
try (destroyDialog RsShaderOptimiserRoll) catch ()
|
|
|
|
rollout RsShaderOptimiserRoll "Shader Optimiser [v1.19:DidacticWire]" width:320 height:100
|
|
(
|
|
-- Phrase generated via: (filein (RsConfigGetWildWestDir() + "script/3dsmax/_config_files/wildwest_header.ms"); print (RsRandomPhrase count:100))
|
|
|
|
local shadersPath = RsConfigGetCommonDir() + "shaders/db/*.sps"
|
|
local shaderNames = for filename in (getFiles shadersPath) collect (toLower (getFilenameFile filename))
|
|
|
|
local bannerHeight = 32
|
|
dotNetControl RsBannerPanel "Panel" pos:[0,0] height:bannerHeight width:RsShaderOptimiserRoll.width
|
|
local banner = makeRsBanner dn_Panel:RsBannerPanel wiki:"ShaderOptimiser" filename:(getThisScriptFilename())
|
|
|
|
button btnCheckForNonoptimal "Find Non-Optimal Shaders" pos:(RsBannerPanel.pos + [5, bannerHeight + 5]) width:(RsShaderOptimiserRoll.width - 10) height:50
|
|
button btnOpenCSV "Edit Alternative-Shaders List" tooltip:"Opens CSV-file containing alternative shadernames used by tool" pos:(btnCheckForNonoptimal.pos + [0, 55]) width:(RsShaderOptimiserRoll.width - 10) height:30
|
|
|
|
-- This CSV-file contains a list of alternative shader-names:
|
|
local listFilename = RsConfigGetProjRootDir() + "/assets/maps/shader_optimiser.csv"
|
|
|
|
-- Suggested changes are added to this array:
|
|
local suggestionList = #()
|
|
|
|
local shaderLookupList, shaderLookupListNames
|
|
fn loadShaderLookupList =
|
|
(
|
|
local readFile = openFile listFilename
|
|
if (readFile == undefined) do return false
|
|
|
|
struct shaderLookupItem
|
|
(
|
|
shaderName,
|
|
|
|
-- Data loaded from csv:
|
|
noBump, noSpec, noAlpha, noReflect,
|
|
noBumpSpec, noBumpAlpha, noBumpReflect,
|
|
noSpecAlpha, noSpecReflect, noAlphaReflect,
|
|
noBumpSpecAlpha, noBumpSpecReflect, noSpecAlphaReflect, noBumpAlphaReflect,
|
|
noBumpSpecAlphaReflect,
|
|
|
|
-- Used to say what features this shader supports:
|
|
processed = False,
|
|
isAlphaShader = False,
|
|
isCutOut = False, isAlpha = False, isBump = False, isSpec = False, isReflect = False,
|
|
|
|
fn initFeatures mat =
|
|
(
|
|
-- Only needs to be run once per shadername:
|
|
if processed do return True
|
|
|
|
-- Is this is a Cutout Alpha shader? Check name:
|
|
isCutout = (matchPattern shaderName pattern:"*cutout*")
|
|
isAlpha = (isCutout)
|
|
|
|
-- Set features to True if CSV included alternates for shaders missing particular features:
|
|
for propName in (getPropNames This) where (matchPattern propName pattern:"no*") do
|
|
(
|
|
if ((getProperty This propName) != undefined) do
|
|
(
|
|
if (not isAlpha) and (matchPattern propName pattern:"*alpha*") do
|
|
(
|
|
isAlpha = True
|
|
)
|
|
if (not isSpec) and (matchPattern propName pattern:"*spec*") do
|
|
(
|
|
isSpec = True
|
|
)
|
|
if (not isBump) and (matchPattern propName pattern:"*bump*") do
|
|
(
|
|
isBump = True
|
|
)
|
|
if (not isReflect) and (matchPattern propName pattern:"*reflect*") do
|
|
(
|
|
isReflect = True
|
|
)
|
|
)
|
|
)
|
|
|
|
if isAlpha do
|
|
(
|
|
isAlphaShader = RstGetIsAlphaShader mat
|
|
)
|
|
|
|
processed = True
|
|
)
|
|
)
|
|
|
|
shaderLookupList = #()
|
|
|
|
local headerNames
|
|
local validHeaders = #{}
|
|
|
|
while not eof readFile do
|
|
(
|
|
local thisLine = (toLower (trimRight (RsRemoveCharacterFromString (readLine readFile) "\"")))
|
|
|
|
case of
|
|
(
|
|
(thisLine == ""):() -- Ignore empty/whitespace lines
|
|
(thisLine[1] == "#"):() -- Ignore hashed comments
|
|
(headerNames == undefined): -- First line is header
|
|
(
|
|
headerNames = for token in (filterString thisLine ",") collect ((trimLeft (trimRight token)) as name)
|
|
|
|
-- Only use header-names that are in struct:
|
|
local structPropNames = (getPropNames shaderLookupItem)
|
|
for n = 1 to headerNames.count do
|
|
(
|
|
validHeaders[n] = (findItem structPropNames headerNames[n] != 0)
|
|
)
|
|
)
|
|
Default:
|
|
(
|
|
-- Load the actual data-lines:
|
|
local lineTokens = for token in (filterString thisLine "," splitEmptyTokens:True) collect (trimLeft (trimRight token))
|
|
|
|
if (lineTokens.count != 0) do
|
|
(
|
|
local newItem = shaderLookupItem()
|
|
|
|
for n = validHeaders do
|
|
(
|
|
local thisToken = lineTokens[n]
|
|
|
|
if (thisToken != undefined) and (thisToken != "") do
|
|
(
|
|
setProperty newItem headerNames[n] thisToken
|
|
)
|
|
)
|
|
|
|
append shaderLookupList newItem
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
close readFile
|
|
|
|
shaderLookupListNames = for item in shaderLookupList collect item.shaderName
|
|
|
|
return true
|
|
)
|
|
|
|
-- Used to find the maximum value, or range of values, used on a bitmap:
|
|
fn getBmpRange filename bmpResize:64 maxValue:false getAlphaType:false rgbaChans:#{1..3} slotNum:1 =
|
|
(
|
|
if getAlphaType do
|
|
(
|
|
rgbaChans = #{4}
|
|
)
|
|
|
|
local fileBmp = openBitmap filename
|
|
local fileBmpHasAlpha = fileBmp.hasAlpha
|
|
|
|
-- Abort check if doing alpha-check on non-alpha bitmap in first slot:
|
|
if (getAlphaType) and (slotNum == 1) and (not fileBmpHasAlpha) do
|
|
(
|
|
close fileBmp
|
|
return #none
|
|
)
|
|
|
|
local doResize = (isKindOf bmpResize number)
|
|
local readBmp
|
|
|
|
-- Scale down bitmap, to speed up processing:
|
|
if doResize then
|
|
(
|
|
local bmpSize = [bmpResize,bmpResize]
|
|
local RGBOutputIdx = if (getAlphaType and (slotNum == 1)) then 1 else 0
|
|
|
|
readBmp = renderMap (bitmapTexture bitmap:fileBmp RGBOutput:RGBOutputIdx) size:bmpSize filter:false
|
|
|
|
close fileBmp
|
|
)
|
|
else
|
|
(
|
|
readBmp = fileBmp
|
|
)
|
|
|
|
local bmpWidth = readBmp.width
|
|
|
|
local usedValsR = #{}
|
|
local usedValsG = #{}
|
|
local usedValsB = #{}
|
|
local usedValsA = #{}
|
|
|
|
local usedVals = #(usedValsR, usedValsG, usedValsB, usedValsA)
|
|
for item in usedVals do (item.count = 256)
|
|
|
|
local doR = rgbaChans[1]
|
|
local doG = rgbaChans[2]
|
|
local doB = rgbaChans[3]
|
|
local doA = getAlphaType or rgbaChans[4]
|
|
|
|
local alphaLineA = #{}
|
|
local alphaLineB = #{}
|
|
local alphaLineC = #{}
|
|
|
|
local isCutout = true
|
|
|
|
for rowNum = 0 to (readBmp.height - 1) do
|
|
(
|
|
local readRow = getPixels readBmp [0, rowNum] bmpWidth
|
|
|
|
if doR do
|
|
(
|
|
for clr in readRow do (usedValsR[clr.R + 1] = true)
|
|
)
|
|
if doG do
|
|
(
|
|
for clr in readRow do (usedValsG[clr.G + 1] = true)
|
|
)
|
|
if doB do
|
|
(
|
|
for clr in readRow do (usedValsB[clr.B + 1] = true)
|
|
)
|
|
|
|
if doA do
|
|
(
|
|
local alphaVals = if (fileBmpHasAlpha and (not doResize)) then
|
|
(
|
|
for clr in readRow collect clr.a
|
|
)
|
|
else
|
|
(
|
|
-- Assuming rgb is monochrome, so just need to read one colour-channel:
|
|
for clr in readRow collect clr.r
|
|
)
|
|
|
|
for val in alphaVals do (usedValsA[val + 1] = true)
|
|
|
|
-- Set as non-cutout if bitmap contains non-monochrome pixels with no monochrome neighbours:
|
|
if getAlphaType and isCutout do
|
|
(
|
|
alphaLineA = alphaLineB
|
|
alphaLineB = alphaLineC
|
|
|
|
-- Set alphaLineC as bitarray of monochrome/non-monochrome pixels:
|
|
for n = 1 to alphaVals.count do
|
|
(
|
|
alphaLineC[n] = ((alphaVals[n] <= 10) or (alphaVals[n] >= 245))
|
|
)
|
|
|
|
-- Only start checking once all alphaLine arrays are filled:
|
|
if (rowNum > 1) do
|
|
(
|
|
-- Check non-monochrome pixels:
|
|
for n = 2 to (alphaLineB.count - 1) where (not alphaLineB[n]) while isCutout do
|
|
(
|
|
-- True if any neighbour is monochrome:
|
|
isCutout = alphaLineA[n - 1] or alphaLineA[n] or alphaLineA[n + 1] or alphaLineB[n - 1] or alphaLineB[n + 1] or alphaLineC[n - 1] or alphaLineC[n] or alphaLineC[n + 1]
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
if doResize then
|
|
(
|
|
close readBmp
|
|
)
|
|
else
|
|
(
|
|
close fileBmp
|
|
)
|
|
|
|
-- Return a point4 if showing alpha, else return a point3:
|
|
local valRange = if (rgbaChans.count == 4) then [0,0,0,0] else [0,0,0]
|
|
|
|
local retAlphaType
|
|
|
|
-- Find value-range for requested channels:
|
|
for n = rgbaChans do
|
|
(
|
|
-- Get bitarray of values, as array:
|
|
local chanVals = usedVals[n] as array
|
|
|
|
-- Get max value:
|
|
valRange[n] = chanVals[chanVals.count] - 1
|
|
|
|
-- Check for alpha/cutout:
|
|
if getAlphaType and (n == 4) do
|
|
(
|
|
-- Bno has suitable alpha-channel if lowest value is noticably less than white, or it has a reasonable difference between its dark/bright values:
|
|
local hasAlpha = ((chanVals[1] <= 245) or ((valRange[n] - chanVals[1] - 1) > 10))
|
|
|
|
-- Has Cutout if bmp only contains very dark/bright values:
|
|
local hasCutout = hasAlpha
|
|
for val in chanVals while hasCutout do
|
|
(
|
|
hasCutout = ((val <= 10) or (val >= 245))
|
|
)
|
|
|
|
retAlphaType = case of
|
|
(
|
|
hasCutout:#cutout
|
|
hasAlpha:#alpha
|
|
default:#none
|
|
)
|
|
)
|
|
|
|
-- Get max/min range:
|
|
if not maxValue do
|
|
(
|
|
valRange[n] -= (chanVals[1] - 1)
|
|
)
|
|
)
|
|
|
|
-- Return alpha-type or 0->1 ranged values:
|
|
if getAlphaType then (return retAlphaType) else (return (valRange / 255.0))
|
|
)
|
|
|
|
-- Function to replace instances of mat is scene with a Rage version:
|
|
fn replaceStdWithRage mat unused =
|
|
(
|
|
local newMat = RstCreateRstMtlFromStdMtl mat
|
|
|
|
for obj in geometry do
|
|
(
|
|
if isKindOf obj.material multiMaterial then
|
|
(
|
|
local matList = obj.material.materialList
|
|
for n = 1 to matList.count where (matList[n] == mat) do
|
|
(
|
|
matList[n] = newMat
|
|
)
|
|
)
|
|
else
|
|
(
|
|
if obj.material == mat do
|
|
(
|
|
obj.material = newMat
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
-- Called when list is double-clicked:
|
|
fn listDoubleClick Sender Args =
|
|
(
|
|
local rowNum = args.RowIndex + 1
|
|
if (rowNum <= 0) do return false
|
|
|
|
-- Open new material-editor (closing first, as existing one can't be focused directly)
|
|
-- (also sets to non-Slate mode, as I don't think Slate can be controlled from script)
|
|
MatEditor.Close()
|
|
MatEditor.mode = #basic
|
|
MatEditor.Open()
|
|
|
|
local clickedMat = suggestionList[rowNum].mat
|
|
|
|
if (clickedMat != undefined) do
|
|
(
|
|
local matEditSlot = medit.GetActiveMtlSlot()
|
|
medit.PutMtlToMtlEditor clickedMat matEditSlot
|
|
)
|
|
|
|
Sender.Parent.Dispose()
|
|
)
|
|
|
|
fn checkForNonOptimal =
|
|
(
|
|
if (loadShaderLookupList() == False) do
|
|
(
|
|
messageBox ("Aborting: Failed to load list:\n\n" + listFilename) title:"Error: List-file not loaded"
|
|
return false
|
|
)
|
|
|
|
local timeStart = timeStamp()
|
|
local notCancelled = true
|
|
|
|
|
|
local objs = for obj in geometry where (isEditPolyMesh obj) collect obj
|
|
local objsCount = objs.count
|
|
|
|
progressStart ("Collecting mats from " + (objsCount as string) + " objs:")
|
|
local matList = #()
|
|
local objFaceMats = for objNum = 1 to objsCount while (notCancelled = progressUpdate (100.0 * objNum / objsCount)) collect
|
|
(
|
|
local obj = objs[objNum]
|
|
local matFaces = (for item in matList collect #{})
|
|
RsGetMaterialsOnObjFaces obj materials:matList faceLists:matFaces
|
|
|
|
dataPair obj:obj matFaces:matFaces
|
|
)
|
|
progressEnd()
|
|
|
|
pushPrompt "Filtering down to unique materials..."
|
|
(
|
|
local uniqueMats = makeUniqueArray matList
|
|
|
|
if (matList.count != uniqueMats.count) do
|
|
(
|
|
-- Generate lookup-list:
|
|
local newIdxList = for matNum = 1 to matList.count collect (findItem uniqueMats matList[matNum])
|
|
|
|
local nonUniques = #{}
|
|
nonUniques.count = matList.count
|
|
|
|
for n = 1 to newIdxList.count where (newIdxList[n] != n) do
|
|
(
|
|
nonUniques[n] = True
|
|
)
|
|
|
|
-- Change matFaces for each object to use new indexes:
|
|
for item in objFaceMats do
|
|
(
|
|
local matFaces = item.matFaces
|
|
|
|
for matNum = 1 to matFaces.count where nonUniques[matNum] do
|
|
(
|
|
local theseMatFaces = matFaces[matNum]
|
|
|
|
if (theseMatFaces.numberSet != 0) do
|
|
(
|
|
local newIdx = newIdxList[matNum]
|
|
|
|
-- Move these faces to the face-list for equivalent unique material:
|
|
matFaces[newIdx] += theseMatFaces
|
|
theseMatFaces.count = 0
|
|
)
|
|
)
|
|
)
|
|
|
|
matList = uniqueMats
|
|
)
|
|
)
|
|
popPrompt()
|
|
|
|
format "Data-aquisition time: %s\n" ((timeStamp() - timeStart) / 1000.0)
|
|
if (not notCancelled) do (return false)
|
|
|
|
struct matSuggest (mat, action = "", reason = "", func, arg, ticked = true)
|
|
suggestionList = #()
|
|
|
|
-- Threshold for low-value multipliers:
|
|
local varThreshold = 0.02 -- Multiplier-value
|
|
local texThreshold = 0.03 -- Bitmap-channel value
|
|
local varTexThreshold = 0.01 -- Combined value
|
|
|
|
local matCount = matList.count
|
|
|
|
progressStart ("Examining " + (matCount as string) + " materials:")
|
|
|
|
local timeStart = timeStamp()
|
|
|
|
listedMats = #()
|
|
|
|
for matNum = 1 to matCount while (notCancelled = progressUpdate (100.0 * matNum / matCount)) do
|
|
(
|
|
local mat = matList[matNum]
|
|
|
|
case classOf mat of
|
|
(
|
|
StandardMaterial:
|
|
(
|
|
append suggestionList (matSuggest mat:mat action:"Replace with Rage material" reason:"Uses StandardMaterial" func:replaceStdWithRage)
|
|
)
|
|
Rage_Shader:
|
|
(
|
|
local matShaderName = toLower (getFilenameFile (RstGetShaderName mat))
|
|
local lookupNum = findItem shaderLookupListNames matShaderName
|
|
|
|
-- Check spec/bump data for listed shaders:
|
|
if (lookupNum != 0) do
|
|
(
|
|
local shaderItem = shaderLookupList[lookupNum]
|
|
|
|
-- Make sure shader's fingerprint has been taken, to see what features need to be tested for:
|
|
shaderItem.initFeatures mat
|
|
|
|
local missingFiles = #()
|
|
|
|
local noDiffTex = true
|
|
local hasDiffSlot = false
|
|
|
|
local noAlphaTex = true
|
|
local alphaType = #none
|
|
|
|
local noSpecTex = true
|
|
local noSpecVal = false
|
|
local hasSpecSlot = false
|
|
|
|
local noBumpTex = true
|
|
local noBumpVal = false
|
|
local hasBumpSlot = false
|
|
|
|
local noReflectTex = true
|
|
local noReflectVal = false
|
|
local hasReflectSlot = false
|
|
|
|
local numMaps = getNumSubTexmaps mat
|
|
local numVars = RstGetVariableCount mat
|
|
local texMapNum = 0
|
|
|
|
local bumpVal = 1.0
|
|
local specVal = 1.0
|
|
local reflectVal = 1.0
|
|
local specChanMult = [1.0, 1.0, 1.0]
|
|
local alphaBmpVals, specBmpVals, bumpBmpRanges
|
|
|
|
local alphaTexNums = #{}
|
|
local alphaTexOffset = (numMaps / 2)
|
|
|
|
-- Check variables:
|
|
for varNum = 1 to numVars do
|
|
(
|
|
local varType = RstGetVariableType mat varNum
|
|
local varName = RstGetVariableName mat varNum
|
|
|
|
case varType of
|
|
(
|
|
"texmap":
|
|
(
|
|
-- No-Specular/Bump flags are turned OFF if matching bitmaps are found:
|
|
texMapNum += 1
|
|
|
|
local texMap = getSubTexmap mat texMapNum
|
|
local filename = ""
|
|
local fileExists = False
|
|
|
|
-- Does this slot set a valid filename?
|
|
if (isKindOf texMap Bitmaptexture) and (texMap.filename != "") do
|
|
(
|
|
filename = texMap.filename
|
|
fileExists = (doesFileExist filename)
|
|
|
|
if not fileExists do
|
|
(
|
|
append missingFiles filename
|
|
)
|
|
)
|
|
|
|
case of
|
|
(
|
|
(RsIsDiffuseMap varName):
|
|
(
|
|
hasDiffSlot = True
|
|
|
|
if fileExists do
|
|
(
|
|
if (shaderItem.isAlphaShader) do
|
|
(
|
|
local alphaIdx = (texMapNum + alphaTexOffset)
|
|
alphaTexNums[alphaIdx] = True
|
|
)
|
|
|
|
noDiffTex = False
|
|
)
|
|
)
|
|
((noSpecTex) and (shaderItem.isSpec) and (RsIsSpecMap varName)):
|
|
(
|
|
hasSpecSlot = True
|
|
|
|
if fileExists do
|
|
(
|
|
specBmpVals = getBmpRange texMap.filename maxValue:true
|
|
noSpecTex = false
|
|
)
|
|
)
|
|
((noBumpTex) and (shaderItem.isBump) and (RsIsBumpMap varName)):
|
|
(
|
|
hasBumpSlot = True
|
|
|
|
if fileExists do
|
|
(
|
|
bumpBmpRanges = getBmpRange texMap.filename maxValue:false rgbaChans:#{1,2}
|
|
noBumpTex = false
|
|
)
|
|
)
|
|
((noReflectTex) and (shaderItem.isReflect) and (matchPattern varName pattern:"Environment*")):
|
|
(
|
|
hasReflectSlot = True
|
|
|
|
if fileExists do
|
|
(
|
|
noReflectTex = false
|
|
)
|
|
)
|
|
)
|
|
)
|
|
"vector3":
|
|
(
|
|
case varName of
|
|
(
|
|
"specular map intensity mask color":
|
|
(
|
|
specChanMult = RstGetVariable mat varNum
|
|
)
|
|
)
|
|
)
|
|
"float":
|
|
(
|
|
-- No-Specular/Bump-value flags are turned ON if matching values are zero:
|
|
case varName of
|
|
(
|
|
"Bumpiness":
|
|
(
|
|
bumpVal = RstGetVariable mat varNum
|
|
if (bumpVal <= varThreshold) do (noBumpVal = true)
|
|
)
|
|
"Specular Intensity":
|
|
(
|
|
specVal = RstGetVariable mat varNum
|
|
if (specVal <= varThreshold) do (noSpecVal = true)
|
|
)
|
|
"Reflectivity":
|
|
(
|
|
reflectVal = RstGetVariable mat varNum
|
|
if (reflectVal <= varThreshold) do (noReflectVal = true)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
-- Check diffuse-alphas, if found:
|
|
if shaderItem.isAlpha do
|
|
(
|
|
for alphaIdx in alphaTexNums while noAlphaTex do
|
|
(
|
|
local texMapNums = #((alphaIdx - alphaTexOffset), (alphaIdx))
|
|
|
|
for slotNum in #(2,1) while noAlphaTex do
|
|
(
|
|
local texMapNum = texMapNums[slotNum]
|
|
local texMap = getSubTexmap mat texMapNum
|
|
|
|
if (isKindOf texMap Bitmaptexture) and (texMap.filename != "") do
|
|
(
|
|
local filename = texMap.filename
|
|
|
|
if doesFileExist filename then
|
|
(
|
|
alphaType = getBmpRange filename getAlphaType:True slotNum:slotNum
|
|
|
|
-- Will be set to False if texture was found in slot 2, or if slot 1 provides alpha:
|
|
noAlphaTex = ((slotNum == 1) and (alphaType == #None))
|
|
)
|
|
else
|
|
(
|
|
append missingFiles filename
|
|
)
|
|
)
|
|
)
|
|
|
|
-- If Alpha-texture wasn't found, check vert-alphas:
|
|
local uvChannel_vertAlpha = -2
|
|
|
|
for item in objFaceMats while noAlphaTex do
|
|
(
|
|
local thisMatFaces = item.matFaces[matNum]
|
|
|
|
-- If this object uses this material, test its vert-alpha:
|
|
if (thisMatFaces != undefined) and (thisMatFaces.numberSet != 0) do
|
|
(
|
|
local thisObj = item.obj
|
|
local objOp = RsMeshPolyOp thisObj
|
|
|
|
if (objOp.getMapSupport thisObj uvChannel_vertAlpha) do
|
|
(
|
|
local objGetMapFace = RsGetMapFaceFunc thisObj
|
|
local objGetMapVert = objOp.getMapVert
|
|
|
|
for faceNum = thisMatFaces while noAlphaTex do
|
|
(
|
|
local alphaVerts = objGetMapFace thisObj uvChannel_vertAlpha faceNum
|
|
|
|
for vertNum in alphaVerts while noAlphaTex do
|
|
(
|
|
local vertVal = objGetMapVert thisObj uvChannel_vertAlpha vertNum
|
|
|
|
-- If vert-alpha is non-white, we no longer want to warn about lack of alpha-texture:
|
|
if (vertVal[1] != 1) do
|
|
(
|
|
noAlphaTex = False
|
|
alphaType = #vertex
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
if (missingFiles.count == 0) then
|
|
(
|
|
local badAlpha = false
|
|
local badSpec = false
|
|
local badBump = false
|
|
local badReflect = false
|
|
|
|
-- Reasons are given a line each:
|
|
local reasons = #()
|
|
|
|
if shaderItem.isAlpha do
|
|
(
|
|
-- Does material have a suitable alpha-channel?
|
|
if ((not noAlphaTex) and (alphaType == #none)) do (badAlpha = true; append reasons "Alpha-channel all is all white (or close to it)")
|
|
|
|
if (noAlphaTex and shaderItem.isAlphaShader) do (badAlpha = true; append reasons "Unused diffuse-alpha texture slot.")
|
|
)
|
|
if shaderItem.isSpec do
|
|
(
|
|
-- Does bitmap have all specular-channel multipliers turned down too low?
|
|
local validSpecMults = false
|
|
for n = 1 to 3 while not validSpecMults do
|
|
(
|
|
validSpecMults = (specChanMult[n] > varThreshold)
|
|
)
|
|
|
|
if not validSpecMults do (badSpec = true; append reasons "Specular-channel multipliers are set to negligible values.")
|
|
if noSpecVal do (badSpec = true; append reasons "Specular Intensity value is negligible.")
|
|
|
|
-- Check bitmap values, if multipliers are valid:
|
|
if (not badSpec) and (specBmpVals != undefined) do
|
|
(
|
|
local ineffectiveTex = true
|
|
local ineffectiveMultTex = true
|
|
|
|
-- Check rgb values:
|
|
for n = 1 to 3 while (ineffectiveTex or ineffectiveMultTex) do
|
|
(
|
|
local bmpChanVal = specBmpVals[n]
|
|
local bmpChanMult = specChanMult[n]
|
|
|
|
-- Does this specular-texture have a channel above the threshold?
|
|
if ineffectiveTex and (bmpChanVal > texThreshold) do
|
|
(
|
|
ineffectiveTex = false
|
|
)
|
|
|
|
-- See if the bitmap is still effective once the multipliers are applied, if they were accepted:
|
|
if ineffectiveMultTex and ((bmpChanVal * bmpChanMult * specVal) > varTexThreshold) do
|
|
(
|
|
ineffectiveMultTex = false
|
|
)
|
|
)
|
|
|
|
case of
|
|
(
|
|
ineffectiveTex:(badSpec = true; append reasons "Specular Texture is too dark to be effective.")
|
|
ineffectiveMultTex:(badSpec = true; append reasons "Specular Texture's values are ineffective once multiplier-values are applied.")
|
|
)
|
|
)
|
|
|
|
if (hasSpecSlot and noSpecTex) do
|
|
(
|
|
badSpec = true; append reasons "Unused Specular Texture slot."
|
|
)
|
|
)
|
|
if shaderItem.isBump do
|
|
(
|
|
if noBumpTex do (badBump = true; append reasons "Unused Bump Texture slot.")
|
|
if noBumpVal do (badBump = true; append reasons "Bumpiness value is negligible.")
|
|
|
|
-- Check bitmap values, if multipliers are valid:
|
|
if (not badBump) and (bumpBmpRanges != undefined) do
|
|
(
|
|
local ineffectiveTex = true
|
|
local ineffectiveMultTex = true
|
|
|
|
-- Check red/green ranges:
|
|
for n = 1 to 2 while (ineffectiveTex or ineffectiveMultTex) do
|
|
(
|
|
local bmpChanVal = bumpBmpRanges[n]
|
|
|
|
-- Does this bump-texture have a channel-range above the threshold?
|
|
if ineffectiveTex and (bmpChanVal > texThreshold) do
|
|
(
|
|
ineffectiveTex = false
|
|
)
|
|
|
|
-- See if the bitmap is still effective once the multipliers are applied, if they were accepted:
|
|
if ineffectiveMultTex and ((bmpChanVal * bumpVal) > varTexThreshold) do
|
|
(
|
|
ineffectiveMultTex = false
|
|
)
|
|
)
|
|
|
|
case of
|
|
(
|
|
ineffectiveTex:(badBump = true; append reasons "Bump Texture's value-ranges are too small to be effective.")
|
|
ineffectiveMultTex:(badBump = true; append reasons "Bump Texture's value-ranges are too small to be effective once Bumpiness multiplier is applied.")
|
|
)
|
|
)
|
|
)
|
|
if shaderItem.isReflect do
|
|
(
|
|
if noReflectVal then (badReflect = true; append reasons "Reflectivity value is negligible.") else
|
|
(
|
|
if (not noReflectTex) do
|
|
(
|
|
append suggestionList (matSuggest action:"Check suitability?\nThis is mainly used for interiors" mat:mat reason:"Uses Environment Texture" ticked:false)
|
|
)
|
|
)
|
|
)
|
|
|
|
--format "%: % (%) badSpec:% badBump:% badAlpha:% badReflect:%\n" mat.name matShaderName lookupNum badSpec badBump badAlpha badReflect
|
|
|
|
-- See if shader has a suggested alternative name, if it's missing texturemaps:
|
|
local suggestedName
|
|
local testList =
|
|
#(
|
|
datapair test:(badSpec and badBump and badAlpha and badReflect) val:(shaderItem.noBumpSpecAlphaReflect),
|
|
datapair test:(badSpec and badBump and badReflect) val:(shaderItem.noBumpSpecReflect),
|
|
datapair test:(badSpec and badAlpha and badReflect) val:(shaderItem.noSpecAlphaReflect),
|
|
datapair test:(badBump and badAlpha and badReflect) val:(shaderItem.noBumpAlphaReflect),
|
|
datapair test:(badSpec and badReflect) val:(shaderItem.noSpecReflect),
|
|
datapair test:(badBump and badReflect) val:(shaderItem.noBumpReflect),
|
|
datapair test:(badAlpha and badReflect) val:(shaderItem.noAlphaReflect),
|
|
datapair test:(badReflect) val:(shaderItem.noReflect),
|
|
|
|
datapair test:(badSpec and badBump and badAlpha) val:(shaderItem.noBumpSpecAlpha),
|
|
datapair test:(badSpec and badBump) val:(shaderItem.noBumpSpec),
|
|
datapair test:(badSpec and badAlpha) val:(shaderItem.noSpecAlpha),
|
|
datapair test:(badBump and badAlpha) val:(shaderItem.noBumpAlpha),
|
|
datapair test:(badSpec) val:(shaderItem.noSpec),
|
|
datapair test:(badBump) val:(shaderItem.noBump),
|
|
datapair test:(badAlpha) val:(shaderItem.noAlpha)
|
|
)
|
|
for item in testList where item.test while (suggestedName == undefined) do
|
|
(
|
|
suggestedName = item.val
|
|
)
|
|
|
|
-- If possible, swap alpha out for cutout shader:
|
|
if shaderItem.isAlpha and (not shaderItem.isCutout) and (alphaType == #Cutout) do
|
|
(
|
|
local checkName = if (suggestedName == undefined) then matShaderName else suggestedName
|
|
local alphaIdx = findString checkName "alpha"
|
|
|
|
-- Generate alternative shader-name with "cutout" replacing "alpha":
|
|
local newNameStart = (substring checkName 1 (alphaIdx - 1))
|
|
local newNameEnd = (substring checkName (alphaIdx + 5) -1)
|
|
local newSuggestion = (newNameStart + "cutout" + newNameEnd)
|
|
|
|
-- Only swap for this name if it's on our list:
|
|
if (findItem shaderLookupListNames newSuggestion) != 0 do
|
|
(
|
|
suggestedName = newSuggestion
|
|
append reasons "Diffuse-alpha contains only black/white (or near enough) values"
|
|
)
|
|
)
|
|
|
|
-- Generate change-reason string:
|
|
local reason = stringStream ""
|
|
for n = 1 to reasons.count do
|
|
(
|
|
format "%" reasons[n] to:reason
|
|
|
|
if (n != reasons.count) do
|
|
(
|
|
format "\n" to:reason
|
|
)
|
|
)
|
|
|
|
if (suggestedName != undefined) do
|
|
(
|
|
local action = ("Change shader: \n'" + matShaderName + "' -> '" + suggestedName + "'")
|
|
|
|
append suggestionList (matSuggest mat:mat action:action reason:(reason as string) func:RstSetShaderName arg:suggestedName)
|
|
)
|
|
)
|
|
else
|
|
(
|
|
for filename in missingFiles do
|
|
(
|
|
local msg = "Missing file: " + filename
|
|
|
|
append suggestionList (matSuggest action:"Sync/find textures" mat:mat reason:msg ticked:false)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
format "Material-processing time: %s\n" ((timeStamp() - timeStart) / 1000.0)
|
|
|
|
progressEnd()
|
|
|
|
if (not notCancelled) do (return false)
|
|
|
|
if (suggestionList.count == 0) then
|
|
(
|
|
messageBox "No auto-optimisable materials were found.\n\nHooray!" title:"No optimisations found" beep:false
|
|
)
|
|
else
|
|
(
|
|
local queryListLabels = #(" ", "Material", "Warning", "Recommended Action")
|
|
local queryList = for item in suggestionList collect #(item.mat.name, item.reason, item.action)
|
|
|
|
local queryListTicks = #{}
|
|
|
|
local queryVal = RsQueryBoxMultiBtn "Possible material-optimisations were found.\n\nDo you want to perform these changes?\n\n(double-click to open in Material Editor)" title:"Change materials?" \
|
|
timeout:-1 width:800 labels:#("Perform ticked actions", "Cancel") tooltips:#("Perform the suggested material optimisations ticked on the list") \
|
|
listItems:queryList listTitles:queryListLabels listCheckStates:queryListTicks listDoubleClickFunc:listDoubleClick
|
|
|
|
if (queryVal == 1) do
|
|
(
|
|
-- Perform ticked actions:
|
|
for itemNum = queryListTicks do
|
|
(
|
|
local actionItem = suggestionList[itemNum]
|
|
|
|
if (actionItem.func != undefined) do
|
|
(
|
|
actionItem.func actionItem.mat actionItem.arg
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
-- Clear suggestion-list:
|
|
suggestionList.count = 0
|
|
|
|
return OK
|
|
)
|
|
|
|
on btnCheckForNonoptimal pressed do
|
|
(
|
|
checkForNonOptimal()
|
|
setFocus RsShaderOptimiserRoll
|
|
)
|
|
|
|
on btnOpenCSV pressed do
|
|
(
|
|
local isCheckedOut = (gRsPerforce.checkedOutForEdit #(listFilename))[1]
|
|
|
|
if (not isCheckedOut) and (querybox "Check out file before editing?" title:"Perforce checkout") do
|
|
(
|
|
gRsPerforce.edit listFilename silent:true
|
|
)
|
|
|
|
-- Create a new Excel instance, and use it to open the CSV:
|
|
local excel = CreateOLEObject "Excel.Application"
|
|
excel.application.Workbooks.Open listFilename
|
|
excel.ActiveSheet.Name = "Shader Alternatives"
|
|
|
|
local worksheet = excel.ActiveSheet
|
|
|
|
-- Set up column-filters:
|
|
workSheet.EnableAutoFilter = True
|
|
workSheet.Cells.AutoFilter 1
|
|
|
|
-- Freeze header-rows/columns, and set to bold:
|
|
(workSheet.Range "B2" "B2").Select()
|
|
excel.ActiveWindow.FreezePanes = True
|
|
(workSheet.Range "A1" "A1").EntireRow.Font.Bold = True
|
|
(workSheet.Range "A1" "A1").EntireColumn.Font.Bold = True
|
|
|
|
-- Adjust all columns:
|
|
workSheet.Columns.AutoFit();
|
|
|
|
-- Show Excel, then give it focus:
|
|
excel.visible = True
|
|
|
|
SC_MINIMIZE = 0xF020
|
|
SC_RESTORE = 0xF120
|
|
WM_SYSCOMMAND = 0x112
|
|
windows.SendMessage excel.Hwnd WM_SYSCOMMAND SC_MINIMIZE 0
|
|
windows.SendMessage excel.Hwnd WM_SYSCOMMAND SC_RESTORE 0
|
|
|
|
releaseOLEObject excel
|
|
)
|
|
|
|
on RsShaderOptimiserRoll open do
|
|
(
|
|
RsShaderOptimiserRoll.height = btnOpenCSV.pos.Y + 35
|
|
banner.setup()
|
|
|
|
-- Make sure we have latest list-file (if it's not writable)
|
|
if (not (doesFileExist listFilename)) or (getfileattribute listFilename #readOnly) do
|
|
(
|
|
pushPrompt ("Syncing list-file: "+ (RsMakeBackSlashes listFilename))
|
|
gRsPerforce.sync listFilename silent:true
|
|
popPrompt()
|
|
)
|
|
)
|
|
|
|
on RsShaderOptimiserRoll close do
|
|
(
|
|
--print "CLOSING OPTIMISER"
|
|
)
|
|
)
|
|
|
|
createDialog RsShaderOptimiserRoll
|