Rotate a canvas to the user around only 1 axis

I’m currently working on a Virtual Ray Tracer; a program that visualizes how ray tracing works. I added a spotlight to this program, and all works fine, except for the canvas. The existing point light had a simple solution to make the canvas look at the user:

canvas.transform.forward = (Camera.main.transform.position - Position).normalized;

Makes sense and works perfectly. (Positon returns transform.position) But unlike the pointlight, the spotlight has a direction. So now it’s important that the canvas looks in the same direction as the light. In my case, it means rotating around the x and y-axis. After that, I’d like it to rotate around the z-axis to make the canvas look toward the user as much as possible. I thought this would be as simple as rotating the canvas towards the user and then undoing rotation around x and y:

canvas.transform.up = (Camera.main.transform.position - Position).normalized;
canvas.transform.localEulerAngles = new Vector3(0, 0, canvas.transform.localEulerAngles.z);

(The default looking direction is +z, the canvas is flat in the XZ plane, so I need the top (up) to point toward the user).

I then thought that maybe I should project the light-camera vector on the spotlight’s local XY plane, set the transform.up to that. I figured that that should only rotate around the z-axis:

canvas.transform.up = Vector3.ProjectOnPlane(Camera.main.transform.position - Position, transform.forward).normalized;

This didn’t work, so I tried to set the local x and y rotation to zero again. This almost worked, but now I couldn’t see it. Adding 90 to the z worked better:

canvas.transform.up = Vector3.ProjectOnPlane(Camera.main.transform.position - Position, transform.forward).normalized;
canvas.transform.localEulerAngles = new Vector3(0, 0, canvas.transform.localEulerAngles.z + 90);

This unfortunately still is not working correctly. It works perfectly when the light is also looking in the z+ direction, but when rotated, it does not rotate correctly. I disabled the possibility for the user to rotate around the z-axis.

I tried some other stuff which I do not all remember, but nothing worked, unfortunately. I hope someone can help me with this!

The program is an open-source application (MIT License), you can find the full code of my version on GitHub.
It works with WebGL and there is a ‘working’ version here. If you enable ‘Cheat Mode’ in the settings you can skip to the level ‘Advanced Lighting - Spot Lights’ where the spotlight makes its first appearance.

Tehre are some things not quite clear, especially what “user” means in this context. Is this a 2d application?

Anyways setting the direction vectors (forward, up, right) directly you always loose the 3rd freedom around that axis and Unity usually assumes some fix reference. When setting forward it assumes up should be towards world up. However you can always use Quaternion.LookRotation instead. It also takes a look direction (just like setting forward) but also has a second argument which is the “guide” reference for up. When not provided this defaults to Vector3.up so you should get the exact same behaviour as setting the forward property manually. However you can pass any other reference vector as up vector and it controls the rotation around z. Keep in mind that the actual look direction is the one that will match 100% and the up vector is just a reference / guide vector and Unity will rotate around z so the up vector is as close to that reference as possible. I guess that’s what you want?

Though I didn’t really follow you when you said the canvas should face the light because a canvas usually faces the camera.

User is the person controlling the application; it should face the out of the screen so to say.

I tried that as well but I didn’t get that working. Sometimes I wouldn’t see the canvas at all.

The canvas is an image of a spotlight. The actual Unity-spotlight has a direction and the canvas is there to visually indicate that direction of the spotlight. Here’s an example image:
8192406--1068129--upload_2022-6-9_15-3-4.png

The spotlight-canvas always points in the direction of the spotlight-light, but to make the canvas as visible as possible to the user, I’d like to rotate the canvas as much as possible to the user whilst the canvas-image still points in the same direction as the light.

Ok, now a lot of things are a bit clearer ^^. You actually talk about the spot light sprite that you display in your simulation. From your original post it sounded like you talk about the actual raytracer. I finally had an actual look at your build and Now it makes more sense. Though I think you have the wrong idea about having the billboard sprite always “facing” in the spotlight direction. billboard sprites are usually always facing the camera. Yes, there are some exceptions and this could be one of them. However I don’t know how you setup your sprite canvas for that icon… The easiest solution would be to wrap your canvas in an empty gameobject and rotate the canvas inside that empty gameobject so the “forward direction” of the icon is aligned with the forward axis of the empty gameobject parent and have the canvas surface face “up”. That way you only have to orient the parent object using LookRotation where forward is the spotlight direction and up is the direction towards your camera, done.

Prepare prefabs in a way they are easy to be used by code. Of course if your canvas has some other orientation it’s always possible to use some complex math (cross product, project on plane, rotating by 90° …) to achieve a similar result. However just setting up the objects properly saves a lot of math work and makes the usage so much easier.

I’ve seen people that wanted to stretch a cube from a start point to an endpoint and they calculated the required scale and then calculated the midpoint where the cube has to be positioned, etc… when it would be much easier to use an empty gameobject, have the unit cube as a child, offset it by 0.5 so the origin of our wrapper is at one of the sides of the cube. Now we just have to place the wrapper at the start point and set the scale of the wrapper to the desired length. This is much more flexible. This is the actual power of prefabs.

I have a prefab that has a transform and a spotlight as components. The canvas is a child in this prefab. This way it moves with the spotlight, and the x and y rotation are automatically inherited and the sprite always has the right direction w.r.t. the direction of the light. I only want to rotate in z so the sprite faces the user, such that it is as visible as possible.
8194680--1068651--upload_2022-6-10_11-44-0.png

With those functions you mentioned, I finally managed to make it work perfectly (thanks for the help!), it just looks a little weird in the code:

var dir = Vector3.ProjectOnPlane(Camera.main.transform.position - Position, transform.forward).normalized;
canvas.transform.rotation = Quaternion.LookRotation(transform.forward, dir);
canvas.transform.localEulerAngles = new Vector3(0, 0, canvas.transform.localEulerAngles.z + 90);

First I make a direction vector that is orthogonal to the spotlight’s direction; the canvas may rotate to any vector in this plane. I then set the rotation to look forward in the same direction as the light, and up in the direction vector. But somehow this direction is off by 90 and increasing the z by 90 solves it, and so far it looks perfect. I just don’t get why the +90 is needed