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.

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_PROJECTION`

and `P_TRANSFORM`

inputs 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_PROJECTION`

and `P_TRANSFORM`

pins, 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