[英]Black hole distortion shader in Unity
我發現着色器代碼具有使特定點周圍的空間變形的效果。 這是一個很酷的效果,但是缺少一些動畫,因此我添加了一些內容:
Shader "Marek/BlackHoleDistortion"
{
Properties {
_DistortionStrength ("Distortion Strength", Range(0, 10)) = 0
_Timer("Timer", Range(0, 10)) = 0
_HoleSize ("Hole Size", Range(0, 1)) = 0.1736101
_HoleEdgeSmoothness ("Hole Edge Smoothness", Range(1, 4)) = 4
_ObjectEdgeArtifactFix ("Object Edge Artifact Fix", Range(1, 10)) = 1
}
SubShader {
Tags {
"IgnoreProjector"="True"
"Queue"="Transparent"
"RenderType"="Transparent"
}
GrabPass{ }
Pass {
Name "FORWARD"
Tags {
"LightMode"="ForwardBase"
}
ZWrite Off
CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#pragma only_renderers d3d9 d3d11 glcore gles
#pragma target 3.0
uniform sampler2D _GrabTexture;
uniform float _DistortionStrength;
uniform float _HoleSize;
uniform float _HoleEdgeSmoothness;
uniform float _ObjectEdgeArtifactFix;
uniform float _Timer;
struct VertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float4 posWorld : TEXCOORD0;
float3 normalDir : TEXCOORD1;
float4 projPos : TEXCOORD2;
};
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.normalDir = UnityObjectToWorldNormal(v.normal);
o.posWorld = mul(unity_ObjectToWorld, v.vertex);
o.pos = UnityObjectToClipPos(v.vertex);
o.projPos = ComputeScreenPos(o.pos);
COMPUTE_EYEDEPTH(o.projPos.z);
return o;
}
float4 frag(VertexOutput i) : COLOR {
i.normalDir = normalize(i.normalDir);
float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);
float3 normalDirection = i.normalDir;
float2 sceneUVs = (i.projPos.xy / i.projPos.w);
float node_9892 = (_HoleSize * -1.0 + 1.0);
float node_3969 = (1.0 - pow(1.0 - max(0, dot(normalDirection, viewDirection)), clamp(_DistortionStrength - _Timer, 0, _DistortionStrength)));
float node_9136 = (length(float2(ddx(node_3969), ddy(node_3969))) * _HoleEdgeSmoothness);
float node_4918 = pow(node_3969, 6.0);
float node_1920 = (1.0 - smoothstep((node_9892 - node_9136), (node_9892 + node_9136), node_4918));
float3 finalColor = (
lerp(
float4(node_1920, node_1920, node_1920, node_1920),
float4(1, 1, 1, 1),
pow(
pow(1.0 - max(0, dot(normalDirection, viewDirection)), 1.0),
_ObjectEdgeArtifactFix
)
) * tex2D(_GrabTexture, ((node_4918 * (sceneUVs.rg * _Time * -2.0 + 1.0)) + sceneUVs.rg)).rgb).rgb;
return fixed4(finalColor, 1);
}
ENDCG
}
}
FallBack "Diffuse"
}
現在,問題在於,為了使失真在特定時間后消失,我需要在方程式中包含一些變量-在這里,我將其_Timer
。 由於明顯的原因,我沒有使用內置的_Time
它是一個不斷增長的值,並且每次使使用此着色器的對象處於活動狀態時,我都需要從0開始的值。 傳遞該參數的C#代碼處理如下所示:
public void Update() {
_timeElapsed += Time.deltaTime;
_renderer.material.SetFloat("_Timer", _timeElapsed);
}
問題是-我可以做得更好嗎? 我希望此着色器的代碼更加獨立,無需將cs腳本中的參數傳遞給它。
我可以做得更好嗎?
簡而言之,是和否。 如果您希望着色器對每種material
行為有所不同,則無法避免從C#傳遞屬性。 但是,可以通過在着色器中傳遞start
時間並計算elapse
時間來避免在Update
執行此操作。
C#
void OnEnable ()
{
_renderer.material.SetFloat("_StartTime", Time.timeSinceLevelLoad);
}
着色器
uniform float _StartTime;
float4 frag(VertexOutput i) : COLOR
{
float elapse = _Time.y - _StartTime;
}
現在,盡管這將直接綁定到您當前正在使用的設置中,但應注意,訪問.material
屬性將克隆材料(這可能會破壞批處理等)。 可以通過添加MaterialPropertyBlock來避免這種情況。
Unity為着色器提供了一些內置值:諸如當前對象的轉換矩陣,時間等之類的東西。
您就像在ShaderLab中使用它們一樣,就像使用其他任何屬性一樣,唯一的區別是您不必在某個地方聲明它-它們是“內置”的。
https://docs.unity3d.com/455/Documentation/Manual/SL-BuiltinValues.html
有一種巧妙的方法可以為您提供4種值的變化,通過為每個要渲染的像素重新使用預乘值,可以潛在地為您節省乘法操作。 有4個值。
_Time.x = time / 20
_Time.y = time
_Time.z = time * 2
_Time.w = time * 3
這是一個簡單的示例,向您展示其工作方式:
Shader "Example/Circle"
{
Properties
{
}
SubShader
{
Cull Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
float circle(in float2 _st, in float _radius){
float2 dist = distance(_st,float2(0.5,0.5));
float result = step(dist,_radius);
return result;
}
fixed4 frag (v2f i) : SV_Target
{
float WaveTime = sin(_Time.z);
float3 color = float3(1,1,1)*circle(i.uv,WaveTime);
return float4( color, 1.0 );
}
ENDCG
}
}
}
在注釋中,您提到要在啟用時間值時重置時間值,因此這里需要使用腳本初始化時間值。
因此,您應該使用自己的“ Time In”着色器:
Properties
{
_Timer("Timer",Float) = 0
}
float WaveTime = sin(_Timer);
using System.Collections;
using UnityEngine;
public class Circle : MonoBehaviour {
public float _timeElapsed;
void OnEnable(){
_timeElapsed = 0;
}
public void Update() {
_timeElapsed += Time.deltaTime;
var _renderer = GetComponent<MeshRenderer>();
_renderer.material.SetFloat("_Timer", _timeElapsed);
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.