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