本示例基于 Unity2018.4.11f1,示例下载在本篇博客结尾处。
一、创建游戏物体(示例中创建了 Unity 中五个基本物体)
二、创建脚本 CreateMouseRay - 将该脚本挂载到摄像机上(挂载到其他游戏物体上也可以,建议挂载到相机上)
三、创建Shader和材质球(如下图)
四、本实例涉及到的其他技术点:
- 基于鼠标位置,创建从相机指向鼠标的射线(Scene视图可见)
五、实现思路:
- 创建一个物体,该物体带有边缘效果的材质球,取消碰撞器,隐藏该物体
- 基于鼠标创建射线,得到射线检测到的物体
- 将射线检测到的物体的坐标、旋转、缩放,赋值给第 1 步创建的物体,并取消该物体的隐藏,更换材质球
- 检测到的物体不同(可以通过name判断)时,更新边缘效果物体的 Transform,返还刚刚被选中物体的材质球
- 射线检测不到物体时隐藏边缘效果物体,返还刚刚被选中物体的材质球
六、脚本 和 Shader
C#脚本 CreateMouseRay
using System.Collections; using System.Collections.Generic; using UnityEngine; public class CreateMouseRay : MonoBehaviour { // 鼠标位置在世界坐标系中的 实例 private Transform mousePoint; // 高亮物体 private Transform highLightObj; // 物体本身的材质球 private Material oldMaterial; // 当前射线检测到的物体 private GameObject nowObj; void Start() { // 鼠标的屏幕坐标转成世界坐标的点 mousePoint = GameObject.CreatePrimitive(PrimitiveType.Sphere).GetComponent<Transform>(); Destroy(mousePoint.GetComponent<Collider>()); mousePoint.localScale = Vector3.one * 0.1f; // 用于边缘效果展示的物体 highLightObj = GameObject.CreatePrimitive(PrimitiveType.Sphere).GetComponent<Transform>(); Destroy(highLightObj.GetComponent<Collider>()); highLightObj.GetComponent<MeshRenderer>().material = Resources.Load<Material>("MT_Silhouette"); highLightObj.gameObject.SetActive(false); } void Update() { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit raycastHit = new RaycastHit(); if (Physics.Raycast(ray, out raycastHit)) { if (!nowObj || nowObj.name != raycastHit.transform.name) { if (nowObj) { // 换回原来的材质 nowObj.GetComponent<MeshRenderer>().material = oldMaterial; } // 射线当前检测到的物体 nowObj = raycastHit.transform.gameObject; // 更换材质球 oldMaterial = raycastHit.transform.GetComponent<MeshRenderer>().material; nowObj.GetComponent<MeshRenderer>().material = Resources.Load<Material>("MT_HighLinght"); // 显示选中效果 highLightObj.position = raycastHit.transform.position; highLightObj.rotation = raycastHit.transform.rotation; highLightObj.localScale = raycastHit.transform.localScale; highLightObj.GetComponent<MeshFilter>().mesh = raycastHit.collider.GetComponent<MeshFilter>().mesh; highLightObj.gameObject.SetActive(true); } } else { if (highLightObj.gameObject.activeSelf) { // 隐藏选中效果 highLightObj.gameObject.SetActive(false); // 换回原来的材质 nowObj.GetComponent<MeshRenderer>().material = oldMaterial; // 置空射线检测到的物体 nowObj = null; } } Debug.DrawRay(Camera.main.transform.position, GetMousePositionOnWorld() - Camera.main.transform.position, Color.red); mousePoint.position = GetMousePositionOnWorld(); } private Vector3 GetMousePositionOnWorld() { Vector3 mousePos = Input.mousePosition; // Z 值不能为零,Z 值为零该方法返回值为相机的世界坐标 // 鼠标坐标值转换为世界坐标时,该方法返回值的 Z 值为:相机的 Z 值加上下面一行代码的赋值 // 例如:相机 Z 值为-10,经过下面一行代码赋值后,该方法返回值的 Z 值为 0 mousePos.z = 10; return Camera.main.ScreenToWorldPoint(mousePos); } }
Shader
//======= Copyright (c) Valve Corporation, All rights reserved. =============== // // Purpose: Used to show the outline of the object // //============================================================================= // UNITY_SHADER_NO_UPGRADE Shader "Silhouette" { //------------------------------------------------------------------------------------------------------------------------------------------------------------- Properties { g_vOutlineColor( "Outline Color", Color ) = ( .5, .5, .5, 1 ) g_flOutlineWidth( "Outline width", Range ( .001, 0.03 ) ) = .005 g_flCornerAdjust( "Corner Adjustment", Range( 0, 2 ) ) = .5 } //------------------------------------------------------------------------------------------------------------------------------------------------------------- CGINCLUDE //------------------------------------------------------------------------------------------------------------------------------------------------------------- #pragma target 5.0 //------------------------------------------------------------------------------------------------------------------------------------------------------------- #include "UnityCG.cginc" //------------------------------------------------------------------------------------------------------------------------------------------------------------- float4 g_vOutlineColor; float g_flOutlineWidth; float g_flCornerAdjust; //------------------------------------------------------------------------------------------------------------------------------------------------------------- struct VS_INPUT { float4 vPositionOs : POSITION; float3 vNormalOs : NORMAL; }; //------------------------------------------------------------------------------------------------------------------------------------------------------------- struct PS_INPUT { float4 vPositionOs : TEXCOORD0; float3 vNormalOs : TEXCOORD1; float4 vPositionPs : SV_POSITION; }; //------------------------------------------------------------------------------------------------------------------------------------------------------------- PS_INPUT MainVs( VS_INPUT i ) { PS_INPUT o; o.vPositionOs.xyzw = i.vPositionOs.xyzw; o.vNormalOs.xyz = i.vNormalOs.xyz; #if UNITY_VERSION >= 540 o.vPositionPs = UnityObjectToClipPos( i.vPositionOs.xyzw ); #else o.vPositionPs = mul( UNITY_MATRIX_MVP, i.vPositionOs.xyzw ); #endif return o; } //------------------------------------------------------------------------------------------------------------------------------------------------------------- PS_INPUT Extrude( PS_INPUT vertex ) { PS_INPUT extruded = vertex; // Offset along normal in projection space float3 vNormalVs = mul( ( float3x3 )UNITY_MATRIX_IT_MV, vertex.vNormalOs.xyz ); float2 vOffsetPs = TransformViewToProjection( vNormalVs.xy ); vOffsetPs.xy = normalize( vOffsetPs.xy ); // Calculate position #if UNITY_VERSION >= 540 extruded.vPositionPs = UnityObjectToClipPos( vertex.vPositionOs.xyzw ); #else extruded.vPositionPs = mul( UNITY_MATRIX_MVP, vertex.vPositionOs.xyzw ); #endif extruded.vPositionPs.xy += vOffsetPs.xy * extruded.vPositionPs.w * g_flOutlineWidth; return extruded; } //------------------------------------------------------------------------------------------------------------------------------------------------------------- [maxvertexcount(18)] void ExtrudeGs( triangle PS_INPUT inputTriangle[3], inout TriangleStream<PS_INPUT> outputStream ) { float3 a = normalize(inputTriangle[0].vPositionOs.xyz - inputTriangle[1].vPositionOs.xyz); float3 b = normalize(inputTriangle[1].vPositionOs.xyz - inputTriangle[2].vPositionOs.xyz); float3 c = normalize(inputTriangle[2].vPositionOs.xyz - inputTriangle[0].vPositionOs.xyz); inputTriangle[0].vNormalOs = inputTriangle[0].vNormalOs + normalize( a - c) * g_flCornerAdjust; inputTriangle[1].vNormalOs = inputTriangle[1].vNormalOs + normalize(-a + b) * g_flCornerAdjust; inputTriangle[2].vNormalOs = inputTriangle[2].vNormalOs + normalize(-b + c) * g_flCornerAdjust; PS_INPUT extrudedTriangle0 = Extrude( inputTriangle[0] ); PS_INPUT extrudedTriangle1 = Extrude( inputTriangle[1] ); PS_INPUT extrudedTriangle2 = Extrude( inputTriangle[2] ); outputStream.Append( inputTriangle[0] ); outputStream.Append( extrudedTriangle0 ); outputStream.Append( inputTriangle[1] ); outputStream.Append( extrudedTriangle0 ); outputStream.Append( extrudedTriangle1 ); outputStream.Append( inputTriangle[1] ); outputStream.Append( inputTriangle[1] ); outputStream.Append( extrudedTriangle1 ); outputStream.Append( extrudedTriangle2 ); outputStream.Append( inputTriangle[1] ); outputStream.Append( extrudedTriangle2 ); outputStream.Append( inputTriangle[2] ); outputStream.Append( inputTriangle[2] ); outputStream.Append( extrudedTriangle2 ); outputStream.Append(inputTriangle[0]); outputStream.Append( extrudedTriangle2 ); outputStream.Append( extrudedTriangle0 ); outputStream.Append( inputTriangle[0] ); } //------------------------------------------------------------------------------------------------------------------------------------------------------------- fixed4 MainPs( PS_INPUT i ) : SV_Target { return g_vOutlineColor; } //------------------------------------------------------------------------------------------------------------------------------------------------------------- fixed4 NullPs( PS_INPUT i ) : SV_Target { return float4( 1.0, 0.0, 1.0, 1.0 ); } ENDCG SubShader { Tags { "RenderType"="Outline" "Queue" = "Geometry-1" } //------------------------------------------------------------------------------------------------------------------------------------------------------------- // Render the object with stencil=1 to mask out the part that isn't the silhouette //------------------------------------------------------------------------------------------------------------------------------------------------------------- Pass { Tags { "LightMode" = "Always" } ColorMask 0 Cull Off ZWrite Off Stencil { Ref 1 Comp always Pass replace } CGPROGRAM #pragma vertex MainVs #pragma fragment NullPs ENDCG } //------------------------------------------------------------------------------------------------------------------------------------------------------------- // Render the outline by extruding along vertex normals and using the stencil mask previously rendered. Only render depth, so that the final pass executes // once per fragment (otherwise alpha blending will look bad). //------------------------------------------------------------------------------------------------------------------------------------------------------------- Pass { Tags { "LightMode" = "Always" } Cull Off ZWrite On Stencil { Ref 1 Comp notequal Pass keep Fail keep } CGPROGRAM #pragma vertex MainVs #pragma geometry ExtrudeGs #pragma fragment MainPs ENDCG } } }
点击下载示例文件,解压后为 unitypackage 文件 https://files.cnblogs.com/files/kao-la-bao-bei/Select.rar