文章目录
- SD处理贴图
- Shader代码
- C#代码
- 最后效果
SD处理贴图
呃呃感觉这节课,很大一部分都是在将怎么用SD来处理贴图,在这里就简单放一下课上的截图吧,我也跟着做了一下,虽然表盘十二个数排列间隔不一样,但还是稀碎地做出来了。
-
顶点色
这部分的模型其实还是需要自己来制作的,给时针分针秒针加上不同的顶点色,但是之前用Blender做了一下,还是不太明白怎么完整地给模型加上顶点色,我用的是选中顶点的方式,没有完全选中做出来的模型不能匹配贴图(苦笑
-
然后是SD部分,下面这张图是老师的:
-
然后这张图是我的:
这部分主要还是为了在学习SD之前有点了解,感觉还行,其实就是连连看,但是SD提供了非常多的计算节点,一些数据完全不需要自己写程序来算。
展示一下我稀碎的处理结果:
-
主纹理:
-
法线贴图
-
高光贴图
放一下老师对于这部分的解释:
-
这张图是要将原来模型上的眼睛鼻子嘴巴全部抹掉,无脸化处理。
-
这部分是要制作表盘,做出12个排列在圆盘上的数字,然后对这些数字分别进行主纹理,法线和高光贴图的表盘处理。
-
这部分是要处理之前做好的表盘的主纹理,法线和高光贴图,并制作遮罩。
最后在Unity里的样子:
表盘间隔不一样,后面针走起来也很怪。
Shader代码
-
解释下这个_RotateOffset,这个变量主要还是为了后面做位置偏移的时候用的。
-
这里我没用cginclude文件,直接写在片元着色器里了。具体可以看我代码。
-
具体解释下RotateZWithOffset函数中_RotateOffset的用法,它的意思是是:比如说我们一个模型的中心在它的身体正中间,儿如果我们直接对三根针进行计算的话,它只会绕着我们的身体正中间也就是模型中心进行旋转,这样就可以想象成直径是红线,沿着最大的那个大圆旋转,但我们只想让它呆在上面的小圆中旋转,所以我们先将这跟针的位置移动到红色直径的中心,旋转完了,再移回原本的位置,这样就不会出错了。
Shader "shader forge/L20_OldSchoolPlusWithClock"
{
Properties
{
[Header(Texture)]
_MainTex ("Main Tex RGB:Base Color A:AO_Tex", 2D) = "white" {}
_normal ("normal", 2D) = "bump" {}
_SpecTex ("Spec Tex RGB:Spec Color A:Spec Pow", 2D) = "white" {}
_EmittTex ("Emitt Tex RGB:Env Tex", 2D) = "black" {}
_cubemap ("cubemap", Cube) = "_Skybox" {}
[Header(Diffuse)]
_MainCol ("Main Color", Color) = (0.5,0.5,0.5,1.0)
_EnvDiffInt ("Env Diff Int", Range(0, 1)) = 0.2
_E_Lambert_UpColor ("E_Lambert_UpColor", Color) = (0.8679245,0.5444998,0.5444998,1)
_E_Lambert_DownColor ("E_Lambert_DownColor", Color) = (0.4400143,0.6626909,0.9056604,1)
_E_Lambert_MidColor ("E_Lambert_MidColor", Color) = (0.4800081,0.8962264,0.4016109,1)
[Header(Specular)]
[PowerSlider(2)] _SpecPow ("Spec Pow", Range(1, 90)) = 30
_EnvSpecInt ("Env_SpecInt", Range(0, 5)) = 0.7826087
_fresnel_exp ("fresnel_exp", Range(0, 90)) = 0.6956522
_mipmap_level ("Env Mipmap", Range(0, 7)) = 0
[Header(Emission)]
_EmittInt ("Emitt Int", Range(1,10)) = 1
[Header(Clock)]
_HourHandAngle ("Hour Angle", Range(0,360)) = 0
_MinuteHandAngle ("Minute Angle", Range(0,360)) = 0
_SecondHandAngle ("Second Angle", Range(0,360)) = 0
_RotateOffset ("Rotate Offset", Range(0,5)) = 0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "AutoLight.cginc"
#include "Lighting.cginc"
#pragma multi_compile_fwdbase_fullshadows
#pragma target 3.0
struct appdata
{
float4 vertex : POSITION;
float2 uv0 : TEXCOORD0;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 color : COLOR; //因为要做分针秒针的遮罩,所以需要顶点色
};
struct v2f
{
float2 uv0 : TEXCOORD0;
float4 pos : SV_POSITION;
float4 posWorld : TEXCOORD1;
float3 nDirWS : TEXCOORD2;
float3 tDirWS : TEXCOORD3;
float3 biDirWS : TEXCOORD4;
LIGHTING_COORDS(5,6)
};
//Texture
uniform sampler2D _MainTex;
uniform sampler2D _normal;
uniform sampler2D _SpecTex;
uniform sampler2D _EmittTex;
uniform samplerCUBE _cubemap;
//Diffuse
uniform float3 _MainCol;
uniform float _EnvDiffInt;
uniform float3 _E_Lambert_UpColor;
uniform float3 _E_Lambert_DownColor;
uniform float3 _E_Lambert_MidColor;
//Specular
uniform float _SpecPow;
uniform float _EnvSpecInt;
uniform float _fresnel_exp;
uniform float _mipmap_level;
//Emitt
uniform float _EmittInt;
//Clock
uniform float _HourHandAngle;
uniform float _MinuteHandAngle;
uniform float _SecondHandAngle;
uniform float _RotateOffset;
#define TWO_PI 3.1415926*2
//沿偏移中心做旋转
void RotateZWithOffset(float angle, float offset, float mask, inout float3 vertex){
//将遮罩顶点移动到模型中心点
vertex.y -= offset * mask;
//对遮罩顶点做旋转
float radZ = radians(angle * mask);
float sinZ, cosZ;
sincos(radZ, sinZ, cosZ);
vertex.xy = float2(vertex.x * cosZ - vertex.y * sinZ, vertex.x * sinZ + vertex.y * cosZ);
//将遮罩顶点从模型中心点移回到原来的位置上
vertex.y += offset * mask;
}
//时钟动画
void ClockAnim(float3 color, inout float3 vertex){
RotateZWithOffset(_HourHandAngle, _RotateOffset, color.r, vertex);
RotateZWithOffset(_MinuteHandAngle, _RotateOffset, color.g, vertex);
RotateZWithOffset(_SecondHandAngle, _RotateOffset, color.b, vertex);
}
v2f vert (appdata v)
{
v2f o;
ClockAnim(v.color.rgb, v.vertex.xyz);
o.pos = UnityObjectToClipPos(v.vertex);
o.uv0 = v.uv0;
o.posWorld = mul(unity_ObjectToWorld, v.vertex);
o.nDirWS = UnityObjectToWorldNormal(v.normal);
o.tDirWS = normalize(mul(unity_ObjectToWorld,float4(v.tangent.xyz,0.0)).xyz);
o.biDirWS = normalize(cross(o.nDirWS,o.tDirWS) * v.tangent.w);
TRANSFER_VERTEX_TO_FRAGMENT(o)
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//贴图采样
float3 nDirTS = UnpackNormal(tex2D(_normal,i.uv0)).rgb;
//向量准备
float3x3 TBN_Matrix = float3x3(i.tDirWS,i.biDirWS,i.nDirWS);
float3 nDirWS_FT = normalize(mul(nDirTS, TBN_Matrix));
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
float3 lrDirWS = normalize(reflect(-lightDir, nDirWS_FT));
float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);
float3 halfDir = normalize(lightDir + viewDir);
float3 vrDir = normalize(reflect(-viewDir,nDirWS_FT));
//准备点积结果
float NoL = max(0.0,dot(lightDir,nDirWS_FT));
float NoH = max(0.0,dot(nDirWS_FT,halfDir));
float NoV = max(0.0,dot(nDirWS_FT,viewDir));
float VoR = max(0.0,dot(viewDir, lrDirWS));
//采样纹理
float4 var_MainTex = tex2D(_MainTex,i.uv0);
float4 var_SpecTex = tex2D(_SpecTex,i.uv0);
float3 var_EmitTex = tex2D(_EmittTex,i.uv0);
//光照模型(直接光照部分)
float3 baseCol = var_MainTex.rgb * _MainCol;
float lambert = max(0.0,NoL);
float specCol = var_SpecTex.rgb;
float specPow = lerp(1, _SpecPow,var_SpecTex.a);
float phong = pow(max(0.0, lrDirWS), specPow);
float shadow = LIGHT_ATTENUATION(i);
float3 dirLighting = (baseCol * lambert + specCol * phong) * _LightColor0 * shadow;
//光照模型(环境光照部分)
//使用3Col环境色方法
/*下面是环境光的漫反射部分,也就是三色环境光*/
//上层光
float upNor = clamp(nDirWS_FT.g,0.0,1.0);
float3 upColor = upNor * _E_Lambert_UpColor;
//下层光
float downNor = clamp(nDirWS_FT.g * -1,0.0,1.0);
float3 downColor = downNor * _E_Lambert_DownColor;
//中层光
float midNor = clamp(1 - upNor - downNor,0.0,1.0);
float3 midColor = midNor * _E_Lambert_MidColor;
/*环境光的漫反射部分 三色环境光*/
float3 env_diff_all = clamp(upColor + downColor + midColor,0.0,1.0);
/*下面是环境镜面反射光部分*/
//cubemap
float3 cubemap_Dir = vrDir;
float3 cubemap_color = texCUBElod(_cubemap,float4(cubemap_Dir,_mipmap_level));
//fresnel
float OneMinusNoV = 1 - NoV;
float fresnel = pow(OneMinusNoV,_fresnel_exp);
float occlusion = var_MainTex.a;
float3 envLighting = (baseCol * env_diff_all * _EnvDiffInt + cubemap_color * fresnel * _EnvSpecInt * var_SpecTex.a) * occlusion;
//光照模型(自发光部分)
float3 emission = var_EmitTex * _EmittInt * (sin(_Time.z) * 0.5 + 0.5);
///返回结果
float3 finalRGB = dirLighting + envLighting + emission;
return float4(finalRGB, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
C#代码
然后是C#代码,这部分主要还是为了获取系统的实时时间并传送给我们做好的shader。这部分比较简单,就了解下获取属性ID和赋值的API就行了,不过多介绍。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class ClockAnim : MonoBehaviour
{
public Material Clock_Mat;
private bool vailed = false;
private int hourAnglePropID;
private int minuteAnglePropID;
private int secondAnglePropID;
void Start()
{
if (Clock_Mat == null) return;
hourAnglePropID = Shader.PropertyToID("_HourHandAngle");
minuteAnglePropID = Shader.PropertyToID("_MinuteHandAngle");
secondAnglePropID = Shader.PropertyToID("_SecondHandAngle");
if (Clock_Mat.HasProperty(hourAnglePropID) && Clock_Mat.HasProperty(minuteAnglePropID) && Clock_Mat.HasProperty(secondAnglePropID))
vailed = true;
}
void Update()
{
if (!vailed) return;
//处理秒针
float second = DateTime.Now.Second;
float sec_rad = second / 60.0f * 360.0f;
Clock_Mat.SetFloat(secondAnglePropID, sec_rad);
//处理分针
float minute = DateTime.Now.Minute;
float mit_rad = minute / 60.0f * 360.0f;
Clock_Mat.SetFloat(minuteAnglePropID, mit_rad);
//处理时针
float hour = DateTime.Now.Hour;
//24个小时对应到时钟只有12个数来表示,一圈分为12格。分针转一圈,时针在表盘上移动30度。
float hour_rad = (hour % 12) / 12.0f * 360.0f + mit_rad / 360.0f * 30.0f;
Clock_Mat.SetFloat(hourAnglePropID, hour_rad);
}
}
最后效果
最后想要一个完整动起来的效果还是需要我们手动挂载一下C#脚本的,选中我们的模型,将脚本拖到模型的Inspector面板下就挂载了,然后再选择我们的shader,点击运行就能看见三根针动起来了。
很离谱啊,这个间隔不精准的话时间肯定是错的,不过主要还是为了学习效果,这些细节有需要的时候再进行细调。