Apply camera mapping to a texture

Forum for OctaneRender Lua scripting examples, discussion and support.
Post Reply
User avatar
roeland
OctaneRender Team
Posts: 1822
Joined: Wed Mar 09, 2011 10:09 pm

Note: this script requires Octane version 1.24 or later.

Today we are applying camera mapping to a texture. The new projection system includes a perspective projection, which can be useful if you want to model a projector (using the distribution pin of a light source). You can also use it for camera mapping but at the moment you have to manually match the transform with the current camera position. But of course you can also doing it through Lua.
camera map.lua
(2.46 KiB) Downloaded 784 times
To use this script, start rendering, select a material or image texture (you can use the material picker) and then run this script.

The first step is to get the current camera. This only works after you start rendering.

Code: Select all

-- current render target and camera

rendertarget = octane.render.getRenderTargetNode()
camera = rendertarget:getInputNode(octane.P_CAMERA)
Then we derive the transform from the target, position and up vectors. We need a rotation which aligns the Z axis to the view vector, and the X axis to the vector pointing right. The Y axis is the up vector, but it needs to be perpendicular to the view vector. In addition the transform should translate the origin to the camera position.

Code: Select all

-- camera transform

pos = camera:getPinValue(octane.P_POSITION)
target = camera:getPinValue(octane.P_TARGET)
up = octane.vec.normalized(camera:getPinValue(octane.P_UP))

Z = octane.vec.normalized(octane.vec.sub(target, pos))
X = octane.vec.normalized(octane.vec.cross(up, Z))
Y = octane.vec.normalized(octane.vec.cross(Z, X))

matrix = {
 {X[1], Y[1], Z[1], pos[1]},
 {X[2], Y[2], Z[2], pos[2]},
 {X[3], Y[3], Z[3], pos[3]},
}
In addition we need to scale the projection plane to account for the field of view. This makes sure exactly one texture tile fits in the camera view.

We also construct the matrix for the UV transform. We need a (-0.5, -0.5) offset to center the image (by default the origin is on the projection axis, which is mapped to the middle of the image), plus compensation for the lens shift.

Code: Select all

-- field of view, lens shift and resolution

fov = camera:getPinValue(octane.P_FOV)
shift = camera:getPinValue(octane.P_LENS_SHIFT)
resolution = rendertarget:getPinValue(octane.P_RESOLUTION)

w = 2 * math.tan(math.rad(fov / 2))
h = w * resolution[2] / resolution[1]

matrix = octane.matrix.mul(matrix, octane.matrix.makeScale{-w, h, 1})

uvmatrix = octane.matrix.makeTranslation{-.5 + shift[1], -.5 + shift[2], 0}
Next comes a function to apply the camera mapping to an image texture. We overwrite the P_PROJECTIONand P_TRANSFORMinputs with new nodes, and fill in the matrices we just calculated.

Code: Select all

-- get selected texture or material node, and apply transform

function project(tex)
    proj = octane.node.create{
        type = octane.NT_PROJ_PERSPECTIVE,
        pinOwnerNode = tex,
        pinOwnerId   = octane.P_PROJECTION,
    }
    
    octane.node.create{
        type = octane.NT_TRANSFORM_VALUE,
        pinOwnerNode = tex,
        pinOwnerId   = octane.P_TRANSFORM,
    }

    tex:setPinValue(octane.P_TRANSFORM, uvmatrix)
    proj:setPinValue(octane.P_TRANSFORM, matrix)
end
Now we get the nodes we want to change from the selection. First two small helper functions: the first one checks if a pin is present (we may add a better way to the API later), the second checks for the presence of the P_PROJECTIONand P_TRANSFORMpins, which probably means the given node is an image texture or procedural texture.

Code: Select all

-- octane.node.getPinInfo raises an error when the pin doesn't exist.
function hasPin(n, pin)
    local r = pcall(n.getPinInfo, n, pin)
    return r
end

function hasProjection(n)
    return hasPin(n, octane.P_PROJECTION) and hasPin(n, octane.P_TRANSFORM)
end
Then we loop over the selection. If we find a texture node, we can apply the mapping, otherwise if we find a material node, we loop over all inputs and apply the mapping to any image textures we find. Note that octane.node.getInputNodeIx raises an error when the pin isn't connected, so we wrap this call with pcall() so this error doesn't terminate our script.

Code: Select all

foundtex = false
for _, n in ipairs(octane.project.getSelection()) do
    if hasProjection(n) then
        foundtex = true
        project(n)
    elseif n:getProperties().outputType == octane.PT_MATERIAL then
        for i = 1, n:getPinCount() do
            m = nil
            pcall(function() m = n:getInputNodeIx(i) end)
            if m ~= nil and hasProjection(m) then
                foundtex = true
                project(m)
            end
        end
    end
end
Finally we check if we found a texture to map. We still have to call octane.changemanager.update() to propagate the changes to the renderer. If we didn't find anything we inform the caller of the problem.

Code: Select all

if foundtex then
    octane.changemanager.update()
else
    error("You have to select an image texture node or material node.")
end
And now the texture should be rendered with a camera mapping until the next time the camera moves.

--
Roeland
User avatar
stratified
OctaneRender Team
Posts: 945
Joined: Wed Aug 15, 2012 6:32 am
Location: Auckland, New Zealand

Can you attach a render as a teaser? I don't have 1.24 yet (at home) ;)

cheers,
Thomas
r-username
Licensed Customer
Posts: 217
Joined: Thu Nov 24, 2011 3:39 pm

Very nice
i7 960 - W7x64 - 12 GB - 2x GTX 780ti
http://www.startsimple.com/ - http://www.gigavr.com/
User avatar
roeland
OctaneRender Team
Posts: 1822
Joined: Wed Mar 09, 2011 10:09 pm

Updated script for version 1.30 here.

--
Roeland
r-username
Licensed Customer
Posts: 217
Joined: Thu Nov 24, 2011 3:39 pm

roeland wrote:Updated script for version 1.30 here.
Any chance this script can be revisited for 2.02?
Thanks in advance
i7 960 - W7x64 - 12 GB - 2x GTX 780ti
http://www.startsimple.com/ - http://www.gigavr.com/
jitendra
Licensed Customer
Posts: 143
Joined: Sat Oct 16, 2010 6:09 am

r-username wrote:
roeland wrote:Updated script for version 1.30 here.
Any chance this script can be revisited for 2.02?
Thanks in advance
The version 1.30 already is working nicely with Octane Standalone 2.02.
Regards.
Best Regards, Jitendra
User avatar
linvanchene
Licensed Customer
Posts: 783
Joined: Mon Mar 25, 2013 10:58 pm
Location: Switzerland

Reading a comment in another thread reminded me of this script.
Redowloaded the script posted in the first post and tried to apply it to an RGB image texture connected to the Starfield in OR 4 RC2.

My idea was to have the starfield texture to just match the viewport.
camera mapping - script error.png
This triggered the script error message:

:26: No Pin P_RESOLUTION in nodes with type NT_RENDERTARGET

- - -
Update:
I get the same error message when trying it with the provided orbx of the Render Layer example:

viewtopic.php?f=51&t=44267
camera mapping script - render layers example.png

Have some parameters changed since OR 2?

or did I mess up some steps?
Win 10 Pro 64bit | Rendering: 2 x ASUS GeForce RTX 2080 Ti TURBO | Asus RTX NVLink Bridge 4-Slot | Intel Core i7 5820K | ASUS X99-E WS| 64 GB RAM
FAQ: OctaneRender for DAZ Studio - FAQ link collection
User avatar
roeland
OctaneRender Team
Posts: 1822
Joined: Wed Mar 09, 2011 10:09 pm

Yes those parameters changed a while ago, you get the resolution as follows: rendertarget:getInputNode(octane.P_FILM_SETTINGS):getPinValue(octane.P_RESOLUTION).
User avatar
linvanchene
Licensed Customer
Posts: 783
Joined: Mon Mar 25, 2013 10:58 pm
Location: Switzerland

roeland wrote:Yes those parameters changed a while ago, you get the resolution as follows: rendertarget:getInputNode(octane.P_FILM_SETTINGS):getPinValue(octane.P_RESOLUTION).
Thank you for helping out even though you may have more urgent matters at the moment.

camera mapping script - which lines need updating.jpg
It seems it is line 24 that needed updating:

before:

Code: Select all

local resolution = rendertarget:getPinValue(octane.P_RESOLUTION)
after:

Code: Select all

local resolution = rendertarget:getInputNode(octane.P_FILM_SETTINGS):getPinValue(octane.P_RESOLUTION)
My experience with scripting is very basic...

It seems to have worked. No more error messages and I can use the UV transform to scale the starfield image.

scene after applying script and UV scale transform
scene after applying script and UV scale transform
Attached unofficial updated script for OR 4 RC2 based on the 1.30 script from roeland.
Attachments
camera map OR4 RC2 custom v1002.lua
updated script for OR 4 RC2
(3.69 KiB) Downloaded 521 times
Win 10 Pro 64bit | Rendering: 2 x ASUS GeForce RTX 2080 Ti TURBO | Asus RTX NVLink Bridge 4-Slot | Intel Core i7 5820K | ASUS X99-E WS| 64 GB RAM
FAQ: OctaneRender for DAZ Studio - FAQ link collection
Post Reply

Return to “Lua Scripting”