目录
蛮牛原文:http://www.manew.com/thread-143613-1-1.html
制作过程视频:https://www.bilibili.com/video/BV1Cf4y127ba
源码资源:https://github.com/AMikeW/BStandShaderResources
一、效果图
二、实战
1、制作Shader
2、创建Plane,赋予Shader所在的材质
3、创建C#脚本,挂在Plane物体身上
4、创建3个Cube拉长放到扫描范围内
Shader "Unlit/SaomiaoShader2"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color ("Color", Color) = (1,1,1,1)
_StrengthFloat("_StrengthFloat", Float) = -0.5
_AlphaDownFloat("_AlphaDownFloat", Range(0,3)) = 0.5
_LerpFloat("_LerpFloat", Range(0,1)) = 0.8
_Angle("_Angle", Float) = 45
_SlerpFloat("_SlerpFloat", Range(1, 80)) = 20
}
SubShader
{
Blend SrcAlpha OneMinusSrcAlpha
Tags { "RenderType"="Transparent" "Queue"="Transparent" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
float _StrengthFloat;
float _AlphaDownFloat;
float _LerpFloat;
float _Angle;
float _SlerpFloat;
uniform float _FloatArray[256];
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
float3 nor = UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col;
//1.UV坐标偏移(UV中心点为(0,0),左下角为(-0.5,-0.5),右上角为(0.5, 0.5))
float2 uv = i.uv;
uv.x = uv.x - 0.5;
uv.y = uv.y - 0.5;
//2.根据UV来计算出一个变化区域:一个以UV中心点为中心,半径为0.5的圆形,圆形外到内从0渐变到1
fixed refDis = 1.0 - sqrt(uv.x * uv.x + uv.y * uv.y) * 2;
//3.中心点向周围发射的向量(归一化)
fixed2 fragmentDir = normalize(uv.xy);
//4.半圆弧(正X轴方向): 圆弧中心向两旁的值从1逐渐变为0,cos正好满足
fixed rightHalfCircle = clamp(dot(float2(1, 0), fragmentDir.xy), 0, 1);
//5.渐变半圆弧颜色(内到外)
col = lerp(_Color, fixed4(1, 1, 1, 1), refDis * _LerpFloat);
6_1.增强边缘效果
//fixed strengthF = pow(refDis, _StrengthFloat);
//col.a = col.a * refDis * rightHalfCircle * strengthF;
//7.衰减半圆弧两旁,固定衰减25°角后的
fixed tempAngleCos = cos(radians(_Angle)); //25度角的余弦值 0.906 31
//6_2.增强边缘效果 + 润滑边缘
fixed strengthF = pow(refDis, _StrengthFloat);
col.a = col.a * refDis * pow(rightHalfCircle / max(0.001, tempAngleCos), _SlerpFloat) * strengthF;
//大于_Angle角度区域的像素衰减 (在视野之外的), _Angle其实是一个视野角度的一半.
if (rightHalfCircle < tempAngleCos)
{
col.a *= _AlphaDownFloat;
}
else {
//核心:视野角度内(_Angle, -Angle)范围内 进行一个遮挡处理 (这里就是所说的Shader弧度变化在(angle,-angle))
//计算出index
//fragmentDir.y是归一化后的向量Y值
//因 sqrt(fragmentDir.y^2 + fragmentDir.x^2) = 1
// sin(fragmentDIr与UV正X轴(0.5,0.5)的角度弧度) 为 (fragmentDir.y / 1) 即fragmentDir.y ,反过来说fragmentDir.y就是sin(角度)
float curRad = asin(fragmentDir.y); //asin 反正弦函数(正弦值)获取角度对应的弧度.
curRad += radians(_Angle); //偏移到正数(上面的弧度是指(_Angle, Angle)角度的当前片元所在的角度弧度..有点绕
float f = curRad / radians(_Angle * 2); //当前弧度/总弧度 得到一个系数
float index = f * 256; //系数乘上索引最大值 获取索引
//因为C#计算出的当index为0时,应该是照射区域上方,而此时Shader,想象一下是不是0时为上方。。。
//答案不是,上方index为0时,curRad是0,在没有经过偏移时,它位于照射区域最下。所以应该取反索引 (这里很绕但是要理解,它就是这样子..)
index = 256 - index;//日 我SB了
float curFloat = _FloatArray[index];
float curFragmentDistance = sqrt(uv.x * uv.x + uv.y * uv.y) * 2; //[0, 0.5]变为[0,1] 因为curFloat是[0,1]范围的
if (curFloat > 0 && curFragmentDistance > curFloat) {
col *= 0;
}
}
return col;
}
ENDCG
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Saomiao2 : MonoBehaviour
{
public Material mat;
//旋转速度
public float speed;
//角度(与Shader一样,它只是视角角度的一半)
public float angle;
float[] arrayFloat;
//射线长度
public float rayLength;
void Start()
{
arrayFloat = new float[256];
}
void Update()
{
transform.Rotate(transform.up, Time.deltaTime * speed);
UpdateRay();
}
void UpdateRay()
{
//角度转弧度
float rad = Mathf.Deg2Rad * angle;
//每一次射线递增步长(一个弧度) //视角角度的弧度/256
float step = rad * 2f / 256;
int index = 0;
for (int i = 1; i <= 256; i++)
{
//step * i 是视角范围内的一个弧度变化 + 自身角度弧度进行旋转 注意:要减去一半
//step * i + Mathf.Deg2Rad * (transform.eulerAngles.y + 180) 这部分是 [0,angle*2] 度数的弧度变化
//Shader里是[-angle, angle]的变化
float curRad = step * i + Mathf.Deg2Rad * (transform.eulerAngles.y + 180) - rad;
//根据当前弧度计算出坐标
float x = rayLength * Mathf.Cos(-curRad);
float z = rayLength * Mathf.Sin(-curRad);
Vector3 pos = new Vector3(x, 0, z);
//发射射线
Ray ray = new Ray(transform.position, pos);
RaycastHit raycastHit;
if(Physics.Raycast(ray, out raycastHit))
{
arrayFloat[index] = raycastHit.distance * 1.0f / rayLength; //射线碰到物体后拿到的一个射线到物体之间的距离
Debug.DrawLine(transform.position, pos, Color.red);
}
else
{
arrayFloat[index] = -1;
Debug.DrawLine(transform.position, pos);
}
index++;
//此时index为0时,射线是位于照射范围的最上,index=255时,射线位于照射范围的最下..(一定要理解这一点)
//transform.eulerAngles.y是从0递增,顺时针旋转,然而实际情况我们的射线会逆时针转,所以在我取反了curRad进行获取坐标..
//此时当index逐渐增大时,是从照射区域最上方渐变到最下方,到Shader我们就通过这个关系计算出index!
}
mat.SetFloatArray("_FloatArray", arrayFloat);
}
}
注意:角度要保持一样