Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 13 Next capture
2021 2022 2023
1 capture
13 Jun 22 - 13 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
2
Question by Incarnadine · Nov 20, 2011 at 03:15 AM · cameraorthographichealthbarperspectivedepth-buffer

Draw orthographic visuals (e.g. health bar) in perspective scene?

I would like to draw a 2D health bar above my characters that doesn't change in perspective or scale as the 3D camera zooms and pans (so regular billboards aren't what I'm after).

The general approach I tried was:

  • Setup a main camera with perspective projection, depth only clear flag, near/far clip planes .3 : 1000, camera depth -1.

  • Setup a GUI camera with orthographic projection, don't clear flag, size 50, near/far clip planes 0 : 100, camera depth 0.

  • Get a world position vector for the health bar prefab at a spot above character position

  • Convert the perspective position vector to viewport coordinates via mainCamera.WorldToViewportPoint()

  • Convert the viewport point back to a world point via guiCamera.ViewportToWorldPoint()

This works OK as far as X/Y, but the ortho visuals are always rendered on top of the perspective visuals in terms of Z order. I want elements in my perspective scene to occlude these. I assumed the depth buffer would take care of this during drawing, but maybe I'm not getting consistent Z values between the perspective and ortho cameras.

Any suggestions on the best way to achieve the effect? Thanks!

Comment
Add comment
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

4 Replies

· Add your reply
  • Sort: 
avatar image
4
Best Answer

Answer by Incarnadine · Nov 24, 2011 at 09:38 PM

Here is the solution I arrived at which does the job of rendering ortho into a perspective scene.

  • Setup a main camera with perspective projection, depth only clear flag, near/far clip planes 1: 100, camera depth -1.

  • Setup a GUI camera with orthographic projection, don't clear flag, size 100, near/far clip planes 1 : 100, camera depth 0.

  • Get a world position vector for the health bar prefab at a spot above character position

  • Convert the perspective position vector to normalized viewport coordinates via mainCamera.WorldToNormalizedViewportPoint() (see below)

  • Convert the viewport point back to a world point via guiCamera.NormalizedViewportToWorldPoint() (see below)

Camera Extensions:

 public static class CameraExtensions
 {
     /// [summary]
     /// The resulting value of z' is normalized between the values of -1 and 1, 
     /// where the near plane is at -1 and the far plane is at 1. Values outside of 
     /// this range correspond to points which are not in the viewing frustum, and 
     /// shouldn't be rendered.
     /// 
     /// See: http://en.wikipedia.org/wiki/Z-buffering
     /// [/summary]
     /// [param name="camera"]
     /// The camera to use for conversion.
     /// [/param]
     /// [param name="point"]
     /// The point to convert.
     /// [/param]
     /// [returns]
     /// A world point converted to view space and normalized to values between -1 and 1.
     /// [/returns]
     public static Vector3 WorldToNormalizedViewportPoint(this Camera camera, Vector3 point)
     {
         // Use the default camera matrix to normalize XY, 
         // but Z will be distance from the camera in world units
         point = camera.WorldToViewportPoint(point);
 
          if(camera.isOrthoGraphic)
         {
             // Convert world units into a normalized Z depth value
             // based on orthographic projection
             point.z = (2 * (point.z - camera.nearClipPlane) / (camera.farClipPlane - camera.nearClipPlane)) - 1f;
         }
         else
         {
             // Convert world units into a normalized Z depth value
             // based on perspective projection
             point.z = ((camera.farClipPlane + camera.nearClipPlane) / (camera.farClipPlane - camera.nearClipPlane))
                 + (1/point.z) * (-2 * camera.farClipPlane * camera.nearClipPlane / (camera.farClipPlane - camera.nearClipPlane));
         }
         
         return point;
     }
     
     /// [summary]
     /// Takes as input a normalized viewport point with values between -1 and 1,
     /// and outputs a point in world space according to the given camera.
     /// [/summary]
     /// [param name="camera"]
     /// The camera to use for conversion.
     /// [/param]
     /// [param name="point"]
     /// The point to convert.
     /// [/param]
     /// [returns]
     /// A normalized viewport point converted to world space according to the given camera.
     /// [/returns]
     public static Vector3 NormalizedViewportToWorldPoint(this Camera camera, Vector3 point)
     {
         if(camera.isOrthoGraphic)
         {
             // Convert normalized Z depth value into world units
             // based on orthographic projection
             point.z = (point.z + 1f) * (camera.farClipPlane - camera.nearClipPlane) * 0.5f + camera.nearClipPlane;
         }
         else
         {
             // Convert normalized Z depth value into world units
             // based on perspective projection
             point.z = ((-2 * camera.farClipPlane * camera.nearClipPlane) / (camera.farClipPlane - camera.nearClipPlane)) /
                 (point.z - ((camera.farClipPlane + camera.nearClipPlane) / (camera.farClipPlane - camera.nearClipPlane)));
         }
 
         // Use the default camera matrix which expects normalized XY but world unit Z 
         return camera.ViewportToWorldPoint(point);
     }
 }

Usage:

 void LateUpdate()
 {        
     var position = perspectiveCamera.WorldToNormalizedViewportPoint(worldTransform.position);
     objectTransform.position = orthographicCamera.NormalizedViewportToWorldPoint(position);
 }

Comment
Add comment · Show 4 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image rocket5tim · Oct 02, 2013 at 10:20 PM 0
Share

This is really useful, thanks for sharing!

avatar image Neiist · Jan 23, 2014 at 05:20 PM 0
Share

great! Thanks for this

avatar image russelljahn · Dec 18, 2014 at 02:05 AM 0
Share

Thanks, this is fantastic! I also really appreciate the heavy documentation.

avatar image Kim-Shin-Wook-a-k-a-Forward-of-South-Korea · Mar 23, 2015 at 02:03 AM 0
Share

Gr8 code. You save my life

avatar image
1

Answer by WillTAtl · Nov 20, 2011 at 04:42 AM

I'm guessing you're used to more low-level, DIY programming tools? Unity has this sort of functionality built-in for you! Place to start looking is the GUITexture class. Some tips to get you started.

There are two basic ways to deal with positioning GUITextures. If you want them to scale and position themselves relative to the view, you can just set the object's transform position and scale, and they will resize automatically with the window. Note, in this case, that GUITextures treat their object's transform as being in Viewport Space, which is normalized, meaning (0,0) is the bottom-left corner and (1,1) the top-right, regardless of the resolution or aspect ratio of the game screen. This approach is quick and easy, but it will give wonky results if your aspect ratios change.

If you want per-pixel control and no scaling of the GUITextures, you'll want to set the pixelInset values of the GUITexture. These are in Screen space, meaning bottom-left is (0,0), and top-right corner is (Screen.width-1,Screen.height-1). You'll want to set the transform position and scale to (0,0,0), and then just set up the GUITexture component's pixelInset, which will then directly control the position and size of the sprite in pixels.

:edit: forgot to mention, either way, you control relative depth of GUITextures with the transform's z position. GUITextures will always render in front of any objects in world space, with the highest z value being on top, regardless of the camera direction or setup. Camera's clip planes are ignored for GUITextures as well.

Comment
Add comment · Show 5 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Incarnadine · Nov 20, 2011 at 05:14 AM 0
Share

Hi, thanks for the info. It's true, I'm used to working a little more low-level. :-)

I've used the GUITexture class in the past for overlays, but I'm using a sprite manager with texture atlas to send everything in batches and am looking for a solution I can hook into that pipeline.

Even assu$$anonymous$$g GUITexture were to perform well enough outside of the batching system I'm using, I don't believe it's going to give me the Z-depth sorting I'm after so that it can be both in front of and in some cases occluded by what's going on in my perspective camera scene.

I just ran a quick test and looks like it renders on top ins$$anonymous$$d of merging with my scene elements. $$anonymous$$aybe I'm missing something, but either way, thanks for the suggestion.

avatar image WillTAtl · Nov 20, 2011 at 05:59 AM 0
Share

oh! I missed that you want elements from the scene to be able to occlude gui elements, sorry!

Hmm. I'm thinking billboarded sprites might be your best bet, but keeping them pixel-perfect in scale at varying depths could be problematic.

A thought, this might be easily solvable with a custom pixel shader, which ignores the transformed z positions and just uses a provided depth value ins$$anonymous$$d?

avatar image WillTAtl · Nov 20, 2011 at 07:31 AM 0
Share

I should be sleeping, but this got me thinking so I've been diddling about ins$$anonymous$$d. I'm currently using the 2D Toolkit for my sprites management and batching. Reproduced the same issue with 2 cameras that you described, did some testing and seems unity either uses separate depth buffers for ortho and persp. or force-clears the depth buffer when switching between them, so that approach is out.

Dunno what sprite manager you are using, but I've discovered that 2D Toolkit makes this pretty easy. Using a single camera, I attached a sprite to the camera, set it at the desired z-distance, and there's a "make pixel perfect" button in the custom inspector for the sprite component that seems to work perfectly. If you're using something different without this feature, you could presumably accomplish the same thing by tweaking the scale by hand.

avatar image Incarnadine · Nov 20, 2011 at 08:08 PM 0
Share

Cool, thanks for taking a look. I'm using 2D Toolkit too. :-)

It looks like calling $$anonymous$$akePixelPerfect() every frame does a good job keeping the sprite scaled to the right size as things move. That leaves 2 problems, making sure the sprite is in the correct world position and rotated to face the camera.

For that, I'm not sure if parenting to the camera is the way to go though. I need the sprite to stay above the character's head so would need to override its position each frame if attached to the camera. $$anonymous$$aybe that's not a bad way to pick up the camera rotations I need to keep the sprites oriented, but I was concerned about skewing that could occur from this kind of billboarding.

For billboarding, I'm aware of these 2 techniques:

  1. Orient the sprite to look along the camera forward vector (this is equivalent to matching rotations with the camera). Does a pretty good job when the camera is at about the same height as the sprite, skews when not.

  2. Orient the sprite to look along the vector from sprite to camera. This does a better job when the camera and sprite are at different heights, but it doesn't take the camera forward/look vector into account, so sprites still skew.

I could lerp between the two based on where an object falls in the FOV, but that seems overly complicated. :-)

For now, my camera movement is fairly restricted, so $$anonymous$$akePixelPerfect() + billboarding #1 is giving pretty good results.

Thanks again for the help!

avatar image Incarnadine · Nov 24, 2011 at 07:12 PM 0
Share

It turns out this didn't work. $$anonymous$$akePixelPerfect() needs some tweaking to be called every frame, but more importantly, my sprites have multiple layers and this shows up or results in Z-fighting when using billboarding. I figured out the depth buffer calculations to render orthographic and will post them in a bit.

avatar image
1

Answer by darkhog · Apr 15, 2017 at 09:23 PM

I'm using the following script that can be used to get exact thing you want. No additional camera necessary, unless you want the bars to be "always on top".

"cam" is the game object containing main camera. Then check orientate/scale depending on what you want to achieve (scale gives constant scale regardless of distance, orientate gives billboard behavior). You can also adjust scale multiplier ("objectScale") if the object turns out too big/too small.

Apply to the healthbar object/healthbar ui canvas.

 using UnityEngine;
 using System.Collections;
 
 public class CanvasTripod : MonoBehaviour {
     public GameObject cam;
     public float objectScale = 1.0f; 
     public bool orientate =true;
     public bool scale = true;
     private Vector3 initialScale; 
     // Use this for initialization
     void Start () {
         initialScale = transform.localScale; 
     }
     void Update(){
         //billboarding the canvas
         if (orientate){
             transform.LookAt(transform.position + cam.transform.rotation * Vector3.back, cam.transform.rotation * Vector3.up);
             this.transform.Rotate(0,180,0);
         }
         //making it properly scaled
         if (scale) {
             Plane plane = new Plane(cam.transform.forward, cam.transform.position); 
             float dist = plane.GetDistanceToPoint(transform.position); 
             transform.localScale = initialScale * dist * objectScale; 
         }
     }
     // Update is called once per frame
     void LateUpdate () {
         
         
     }
 }
 
Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image
0

Answer by smoggach · Oct 10, 2014 at 02:11 PM

Hi. You can use sorting layers and sorting orders on any renderer.

Here's a script I wrote to help with meshes. This also works with particles.

 public class MeshSortingOrder : MonoBehaviour {
 
     public string layerName;
     public int order;
 
     private MeshRenderer rend;
     void Awake()
     {
         rend = GetComponent<MeshRenderer>();
         rend.sortingLayerName = layerName;
         rend.sortingOrder = order;
     }
 
     public void OnValidate()
     {
         rend = GetComponent<MeshRenderer>();
         rend.sortingLayerName = layerName;
         rend.sortingOrder = order;
     }
 }
Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

7 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Drawing on the same screen with different cameras? 1 Answer

one perspective camera and one orthographic in the same scene 1 Answer

Calculate approximate or same size and position of an 2d sprite(plane) object so it appears the same rendered with orthographic and perspective camera 0 Answers

the default camera makes everything look like its moving towards the center? 2 Answers

Can I make a camera that has an orthographic vertical axis, but a perspective on the horzontal axis? 1 Answer


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges