• 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
Question by Sartoris · Mar 03, 2021 at 12:57 PM · 3dimagemapuv coordinates

How to draw a pixel on a raw image that corresponds to a 3D object's coordinates?

What I am trying to do is use the SetPixel function and draw pixels on a 2d image within a canvas that correspond to the position of a 3d object in the world. The 3d object is a model of a town attached to a terrain chunk. I can successfully get the image representing the terrain.

I currently have this code, and it works to some extent: the pixels are drawn on the image, but their position is incorrect.

Vector3 pos = obj.TransformPoint(obj.position);

image.SetPixel((int)pos.x, (int)pos.z, Color.red);

I assume it's not as easy as that, could anyone provide any help? I'd be grateful for any assistance, even some pseudocode would be great.

Comment
andrew-lukasik

People who like this

1 Show 2
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 HoweToGaming · Mar 03, 2021 at 03:48 PM 0
Share

I have not done any thing like this but I'm guessing you will have to do some scaling down. Say your map scale is 100 times larger than the radar image you would have to scale the position down by 100 try dividing x and y by your scale.

avatar image Sartoris HoweToGaming · Mar 03, 2021 at 08:55 PM 0
Share

Thanks for the reply, but this doesn't seem to work. It seems like more should be done, I suppose the position of the object needs to be mapped to the image within its bounds, but I'm stuck beyond calculating the bounds...

2 Replies

· Add your reply
  • Sort: 
avatar image

Answer by unity_ek98vnTRplGj8Q · Mar 03, 2021 at 11:59 PM

You need to know
a) The size of the terrain you are trying to map b) The resolution of the image that you are trying to map c) The origin location of the town in the world (either its upper left or lower left corner, I don't remember)


Then to calculate the corresponding pixel you need to get the position of the object relative to the terrain, normalize that position relative to the terrain size (in other words, on a scale of 0 to 1 how far to the right side of the terrain is my object), then scale back up by the resolution of your texture to find the specific pixel.

 float terrainSizeX;
 float terrainSizeZ;
 Vector3 terrainCornerPosition;
 int texResX;
 int texResY;
 
 Vector3 worldPos = obj.TransformPoint(obj.position);
 Vector3 posRelativeToTerrain = worldPos - TerrainCornerPosition;
 Vector3 normalizedPositionRelativeToTerrain = posRelativeToTerrain / new Vector3(terrainSizeX, 0, terrainSizeZ);
 int pixelPosX = Mathf.RoundToInt(normalizedPositionRelativeToTerrain.x) * texResX;
 int pixelPosY = Mathf.RoundToInt(normalizedPositionRelativeToTerrain.z)  * texResY;

You also may or may not have to flip the sign of the pixelPosY value

Comment
andrew-lukasik

People who like this

1 Show 11 · 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 Sartoris · Mar 04, 2021 at 02:20 PM 0
Share

Thank you for replying. I tried implementing your idea, but I'm not sure how to proceed. First, I'm not sure how to get the "terrainCornerPosition" and, secondly, normalizedPositionRelativeToTerrain tells me that the "/" operator cannot be applied to Vector3...

avatar image unity_ek98vnTRplGj8Q Sartoris · Mar 04, 2021 at 03:57 PM 0
Share

If you were to place an object in your world that corresponds to the upper left pixel on your texture, what would the position of that object be?


And oops I guess you have to divide out each component of the vector - = new Vector3(posRelativeToTerrain.x / terrainSizeX, 0, posRelativeToTerrain.z / terrainSizeZ);

avatar image Sartoris unity_ek98vnTRplGj8Q · Mar 04, 2021 at 09:57 PM 0
Share

Thank you for your help! I've made some progress, here's the code I have so far:

 //I get the bounds of the terrain chunk
 float terrainSizeX = terrainChunk.GetComponent().bounds.size.x;
 float terrainSizeZ = terrainChunk.GetComponent().bounds.size.z;
                 
 //This step I am still unsure of. The upper left corner of a terrain chunk will vary //depending on which terrain chunk it is. For one chunk this might be Vector3(-61, 0, 61) and another one could be Vector3(61, 0, 61), etc... But for testing, I am using the center chunk's upper left corner position:  
 //Vector3 terrainCornerPosition = new Vector3(-61f, 0f, -61f); 
                 
 //This is the resolution of the image (i.e. its width and height as seen in the inspector. Is that what you were referring to?)
 int texResX = 500;
 int texResY = 500;
                 
 // Now I iterate over all the towns on the terrain chunk and set the pixels on the image according to the town's position
 foreach (Transform child in terrainChunk.transform) {
 Vector3 pos = child.TransformPoint(child.position);
 Vector3 posRelativeToTerrain = pos - terrainCornerPosition;
 Vector3 normalizedPositionRelativeToTerrain = new 
 Vector3(posRelativeToTerrain.x / terrainSizeX, 0, posRelativeToTerrain.z / terrainSizeZ); 
                 
 int pixelPosX = Mathf.RoundToInt(normalizedPositionRelativeToTerrain.x) * texResX;
 int pixelPosY = Mathf.RoundToInt(normalizedPositionRelativeToTerrain.z) * texResY;
 topoMap.SetPixel(pixelPosX, pixelPosY, Color.red);
 }

However, now I get no pixels drawn for towns at all... I must be making a mistake somewhere with regards to the position of the town or the upper left corner. I even tried the other corners of the chunk, but no luck...

Show more comments
avatar image

Answer by andrew-lukasik · Mar 04, 2021 at 12:32 AM


Unity3DPaint.cs

This code is an example of how to translate world point into a texture coordinate that's being projected as a rectangular shape of arbitrary rotation. It proves it by allowing you to draw with mouse in game view - but the general idea will stay the same whatever you want to do here exactly (to replace Material with RawImage I guess).

Unity3DPaint preview

 using UnityEngine;
 using UnityEngine.InputSystem;
 
 public class Unity3DPaint : MonoBehaviour
 {
     [SerializeField][Min(4)] int _textureWidth = 64;
     [SerializeField][Min(4)] int _textureHeight = 64;
     [SerializeField] Vector2 _regionWorldSize = new Vector2{ x=100 , y=100 };
     Texture2D _texture;
     [SerializeField] Material _dstMaterial = null;
 
     #if UNITY_EDITOR
     void OnValidate () { if( _texture!=null && ( _texture.width!=_textureWidth || _texture.height!=_textureHeight ) ) { Dispose(); CreateNewTexture(); } }
     void OnDrawGizmos ()
     {
         GetAreaWorldCorners( region:_regionWorldSize , forTransform:transform , TR:out var tr , TL:out var tl , BR:out var br , BL:out var bl );
         Gizmos.color = Color.red;                Gizmos.DrawLine(tl,tr);
         Gizmos.color = new Color(0,1,1,1);        Gizmos.DrawLine(bl,br);
         Gizmos.color = Color.green;                Gizmos.DrawLine(bl,tl);
         Gizmos.color = new Color(1,0,1,1);        Gizmos.DrawLine(br,tr);
     }
     #endif
     
     void OnEnable () => CreateNewTexture();
     void OnDisable () => Dispose();
 
     void Update ()
     {
         var mouse = Mouse.current;
         if( mouse!=null && mouse.leftButton.isPressed )
         {
             Vector3 origin = transform.position;
             Vector3 normal = transform.forward;
             var camera = Camera.main;
             var plane = new Plane( inNormal:normal , inPoint:origin );
             var ray = camera.ScreenPointToRay(Input.mousePosition);
             if( plane.Raycast(ray,out float hitDist) )
             {
                 Vector3 point = ray.origin + ray.direction * hitDist;
                 bool isPointInsideRegion = PointRegionProjection( out Vector2 coord , point:point , region:_regionWorldSize , center:origin , horizontal:-transform.right , vertical:transform.up );
                 if( isPointInsideRegion )
                 {
                     Debug.DrawLine( camera.transform.position , point , Color.white , 0.1f );
                     _texture.SetPixel( (int)( _texture.width * coord.x ) , (int)( _texture.height * coord.y ) , Color.red );
                     _texture.Apply();
                 }
                 else Debug.DrawLine( camera.transform.position , point , Color.black , 0.1f );
             }
         }
     }
 
     void CreateNewTexture ()
     {
         _texture = new Texture2D( width:_textureWidth , height:_textureHeight , textureFormat:TextureFormat.ARGB32 , mipCount:3 , linear:true );
         _texture.filterMode = FilterMode.Point;// no smooth pixels
         _texture.SetPixel( 0 , 0 , Color.black );
         _texture.SetPixel( x:_texture.width-1 , y:_texture.height-1 , Color.magenta );
         _texture.Apply();
         if( _dstMaterial!=null ) _dstMaterial.mainTexture = _texture;
     }
 
     void Dispose () => Dispose( _texture );
     void Dispose ( Object obj )
     {
         #if UNITY_EDITOR
         if( Application.isPlaying ) { UnityEngine.Object.Destroy( obj ); }
         else { UnityEngine.Object.DestroyImmediate( obj ); }
         #else
         UnityEngine.Object.Destroy( thisObject );
         #endif
     }
 
     /// <summary>Projects world point onto a region of a plane with arbitrary rotation.</summary>
     /// <returns>True when coordinate is inside given region.</returns>
     /// <param name="coordinates">UV-type coordinates.</param>
     /// <param name="point">World point you want to project.</param>
     /// <param name="region">Region absolute world space size.</param>
     /// <param name="center">Region center (world space).</param>
     /// <param name="horizontal">Horizontal axis (world space).</param>
     /// <param name="vertical">Vertical axis (world space).</param>
     public static bool PointRegionProjection ( out Vector2 coordinates , Vector3 point , Vector2 region , Vector3 center , Vector3 horizontal , Vector3 vertical )
     {
         GetAreaWorldCorners( region:region , center:center , horizontal:horizontal , vertical:vertical , TR:out var TR , TL:out var TL , BR:out var BR , BL:out var BL );
         Vector3 pointRelativeToBLCorner = point - BL;
         Vector3 H = Vector3.Project( vector:pointRelativeToBLCorner , onNormal:horizontal );
         Vector3 V = Vector3.Project( vector:pointRelativeToBLCorner , onNormal:vertical );
         Vector2 regionalCoords = new Vector2{
             x = H.magnitude * Mathf.Sign( Vector3.Dot(H,horizontal) ) ,
             y = V.magnitude * Mathf.Sign( Vector3.Dot(V,vertical) )
         };
         coordinates = regionalCoords / region;
         return( coordinates.x>0 && coordinates.y>0 && coordinates.x<1f && coordinates.y<1f );
     }
 
     /// <param name="region">Region absolute world space size.</param>
     /// <param name="center">Region center (world space).</param>
     /// <param name="horizontal">Horizontal axis (world space).</param>
     /// <param name="vertical">Vertical axis (world space).</param>
     /// <param name="TR">Top-right corner (world space).</param>
     /// <param name="TL">Top-left corner (world space).</param>
     /// <param name="BR">Bottom-right corner (world space).</param>
     /// <param name="BL">Bottom-left corner (world space).</param>
     public static void GetAreaWorldCorners (
         Vector2 region ,
         Vector3 center , Vector3 horizontal , Vector3 vertical ,
         out Vector3 TR , out Vector3 TL , out Vector3 BR , out Vector3 BL
     )
     {
         horizontal = horizontal.normalized;
         vertical = vertical.normalized;
         Vector3 horizontalExtent = horizontal * region.x * 0.5f;
         Vector3 verticalExtent = vertical * region.y * 0.5f;
 
         TR = center + horizontalExtent + verticalExtent;
         TL = center + -horizontalExtent + verticalExtent;
         BR = center + horizontalExtent + -verticalExtent;
         BL = center + -horizontalExtent + -verticalExtent;
     }
     /// <inheritdoc/>
     /// <param name="forTransform">Transform to source <paramref name="center"/>, <paramref name="horizontal"/> and <paramref name="vertical"/> values from.</param>
     public static void GetAreaWorldCorners (
         Vector2 region ,
         Transform forTransform ,
         out Vector3 TR , out Vector3 TL , out Vector3 BR , out Vector3 BL
     )
     {
         Vector3 center = forTransform.position;
         Vector3 left = -forTransform.right;
         Vector3 up = forTransform.up;
         GetAreaWorldCorners( region:region , center:center , horizontal:left , vertical:up , TR:out TR , TL:out TL , BR:out BR , BL:out BL );
     }
 
 }

TransformTexturePosition.cs

till overcomplicated example but fits a bit more what you're trying to do here. (it calls methods defined in Unity3DPaint class)

TransformTexturePosition preview

 using UnityEngine;
 public class TransformTexturePosition : MonoBehaviour
 {
     [SerializeField] Vector3 _textureWorldHorizontalAxis = new Vector3( 1 , 0 , 0 );
     [SerializeField] Vector3 _textureWorldVerticalAxis = new Vector3( 0 , 0 , 1 );
     [SerializeField] Vector2 _textureWorldSize = new Vector2{ x=100 , y=100 };
     [SerializeField] Vector3 _textureWorldCenter = Vector3.zero;
     [SerializeField][Min(4)] int _textureWidth = 64;
     [SerializeField][Min(4)] int _textureHeight = 64;
     Texture2D _texture;
     [SerializeField] Material _dstMaterial = null;
 
     #if UNITY_EDITOR
     void OnValidate () { if( _texture!=null && ( _texture.width!=_textureWidth || _texture.height!=_textureHeight ) ) { Dispose(); CreateNewTexture(); } }
     void OnDrawGizmos ()
     {
         Unity3DPaint.GetAreaWorldCorners(
             region:            _textureWorldSize ,
             center:            _textureWorldCenter ,
             horizontal:        _textureWorldHorizontalAxis ,
             vertical:        _textureWorldVerticalAxis ,
             TR:out var tr , TL:out var tl , BR:out var br , BL:out var bl
         );
         Gizmos.color = Color.red;                Gizmos.DrawLine(tl,tr);
         Gizmos.color = new Color(0,1,1,1);        Gizmos.DrawLine(bl,br);
         Gizmos.color = Color.green;                Gizmos.DrawLine(bl,tl);
         Gizmos.color = new Color(1,0,1,1);        Gizmos.DrawLine(br,tr);
     }
     #endif
     
     void OnEnable () => CreateNewTexture();
     void OnDisable () => Dispose();
 
     void Update ()
     {
         Vector3 objectWorldPosition = transform.position;
         bool isObjectCoordInBounds = Unity3DPaint.PointRegionProjection(
             coordinates:    out Vector2 coord ,
             point:            objectWorldPosition ,
             region:            _textureWorldSize ,
             center:            _textureWorldCenter ,
             horizontal:        _textureWorldHorizontalAxis ,
             vertical:        _textureWorldVerticalAxis
         );
         if( isObjectCoordInBounds )
         {
             _texture.SetPixel( (int)(_texture.width*coord.x) , (int)(_texture.height*coord.y) , Color.red );
             _texture.Apply();
         }
     }
 
     void CreateNewTexture ()
     {
         _texture = new Texture2D( width:_textureWidth , height:_textureHeight , textureFormat:TextureFormat.ARGB32 , mipCount:3 , linear:true );
         _texture.filterMode = FilterMode.Point;// no smooth pixels
         _texture.SetPixel( 0 , 0 , Color.black );
         _texture.SetPixel( x:_texture.width-1 , y:_texture.height-1 , Color.magenta );
         _texture.Apply();
         if( _dstMaterial!=null ) _dstMaterial.mainTexture = _texture;
     }
 
     void Dispose () => Dispose( _texture );
     void Dispose ( Object obj )
     {
         #if UNITY_EDITOR
         if( Application.isPlaying ) { UnityEngine.Object.Destroy( obj ); }
         else { UnityEngine.Object.DestroyImmediate( obj ); }
         #else
         UnityEngine.Object.Destroy( thisObject );
         #endif
     }
 
 }

Comment
Niter88

People who like this

1 Show 2 · 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 Sartoris · Mar 04, 2021 at 03:22 PM 0
Share

Hey, thank you for sharing your code! I'm going through it and trying to apply parts of it to my case. Since I need to map the position of my 3d object (a town) to the texture, I assume what you meant by "translate world point into a texture coordinate" is basically represented by this part: Vector3 point = ray.origin + ray.direction * hitDist -> where, if I understand it correctly, you are shooting a ray from the camera to the plane, recording the point where it hits, and then remapping that point to the texture? Is this what you suggest I do, only by shooting a ray from the camera to the object I wish to record on the texture?

avatar image andrew-lukasik Sartoris · Mar 04, 2021 at 03:46 PM 0
Share

if I understand it correctly, you are shooting a ray from the camera to the plane, recording the point where it hits, and then remapping that point to the texture?

Exactly

Is this what you suggest I do, only by shooting a ray from the camera to the object I wish to record on the texture?

No need for raycasting when you know gameObject's position already. Do something like this:

 Vector3 textureHorizontalAxis = new Vector3( 1 , 0 , 0 );
 Vector3 textureVerticalAxis = new Vector3( 0 , 0 , 1 );
 Vector3 textureWorldCenter = Vector3.zero;// << SET THIS VALUE
 Vector2 textureWorldSize = Vector3.zero;// << SET THIS VALUE
 Vector3 objectWorldPosition = Vector3.zero;// << SET THIS VALUE
 
 bool isObjectCoordInsideTexture = PointRegionProjection(
     coordinates:    out Vector2 coord ,
     point:            objectWorldPosition ,
     region:            textureWorldSize ,
     center:            textureWorldCenter ,
     horizontal:        textureHorizontalAxis ,
     vertical:        textureVerticalAxis
 );
 
 if( isObjectCoordInsideTexture )
 {
     _texture.SetPixel( (int)(_texture.width*coord.x) , (int)(_texture.height*coord.y) , Color.red );
     _texture.Apply();
 }

Overall I think my answer is vastly overcomplicated here. @unity_ek98vnTRplGj8Q 's answer is much easier to understand and fits your requirements best.


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.

Welcome to Unity Answers

If you’re new to Unity Answers, please check our User Guide to help you navigate through our website and refer to our FAQ for more information.

Before posting, make sure to check out our Knowledge Base for commonly asked Unity questions.

Check our Moderator Guidelines if you’re a new moderator and want to work together in an effort to improve Unity Answers and support our users.

Follow this Question

Answers Answers and Comments

149 People are following this question.

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

Related Questions

How can i make terrains from 2d map images 1 Answer

How to make Stellaris Map in Unity3D? 1 Answer

How to Display Map 1 Answer

Map an Image onto an object from a URL 0 Answers

How to insert a 2D image in the 2D mode that I can zoom in and out of? (Very very new to Unity) 0 Answers


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