本篇文章实际上是一个类似阅读笔记的东西。
这里主要讲一个反射shader,就是反射场景到一个物体表面。那么等于光线照到物体上计算的颜色要去周围环境去采样。
按道理这个东西是怎么实现的呢,我认为是光线照到场景反射到物体表面,然后再反射到摄像机内的,那么在物体表面的点应该显示场景什么内容,那就是要从摄像机方向发射一束光线到这个点然后反射到场景纹理上,然后获取场景纹理上的这个点。
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 10/Reflection" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_ReflectColor ("Reflection Color", Color) = (1, 1, 1, 1) //反射颜色
_ReflectAmount ("Reflect Amount", Range(0, 1)) = 1 //反射程度,如果是0就是一个白茶壶
_Cubemap ("Reflection Cubemap", Cube) = "_Skybox" {} //天空盒
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Color;
fixed4 _ReflectColor;
fixed _ReflectAmount;
samplerCUBE _Cubemap; //立方体纹理
struct a2v {
float4 vertex : POSITION; //模型空间pos
float3 normal : NORMAL; //法线
};
struct v2f {
float4 pos : SV_POSITION; //裁剪空间法线
float3 worldPos : TEXCOORD0; //世界空间坐标
fixed3 worldNormal : TEXCOORD1; //世界空间内法线
fixed3 worldViewDir : TEXCOORD2; //世界空间内视角方向向量
fixed3 worldRefl : TEXCOORD3; //
SHADOW_COORDS(4) //阴影及衰减效果计算1,在片元着色器的输入结构体中加
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); //这里注意,这个函数toClipPos是直接replace掉了UNITY_MATRIX_MVP矩阵,即从模型空间到世界空间到视角空间到裁剪空间的
o.worldNormal = UnityObjectToWorldNormal(v.normal); //这个函数是unityshader封装的把法线转换到世界坐标(模型空间转换世界空间)
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; //这个是乘一个模型到世界空间的矩阵
o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos); //emmm这个函数是获取世界上某个点到摄像机的向量
// Compute the reflect dir in world space
o.worldRefl = reflect(-o.worldViewDir, o.worldNormal); //这是reflect函数,这个函数是计算反射方向的函数,需求的是光线射入方向和法线,这里要计算的是从这个点上反射入我们眼睛的光线,那么就是视角向量在这个点的反射
//我们知道worldViewDir是从这个点到摄像机的向量,那么这里就是求的光线从摄像机射过去的反射向量,
//TRANSFER_SHADOW(o); 阴影及衰减效果计算2,在顶点着色器里这个实际上我关了也没有任何影响
return o;
}
//真正计算颜色
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal); //法线标准化
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); //获取该点的标准化光方向,从该点射向光源
fixed3 worldViewDir = normalize(i.worldViewDir); //视角方向的标准化
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; //环境光
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir)); //Color是材质的漫反射颜色 这个_LightColor0.rgb是第一个直射光的颜色 这里实际只是算了一个漫反射
// Use the reflect dir in world space to access the cubemap
fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb * _ReflectColor.rgb; //正方形纹理采样
//采样位置是用worldRefl这个是世界坐标下的反射函数的返回值,可以理解为从A点发一束光线反射后进入viewDir,那么这个反射方向
//就是从view发射一束光线的反射方向的反方向
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); //unity内置函数,在片元着色器里,需求结构体和世界坐标,然后atten就是一个衰减和阴影结果的综合
// Mix the diffuse color with the reflected color
fixed3 color = ambient + lerp(diffuse, reflection, _ReflectAmount) * atten;
//fixed3 color = ambient + diffuse; //不加反射光就是一个单纯的白茶壶
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Reflective/VertexLit"
}
这里的核心就是根据视角方向和法线方向求出实际射入这个点的光线的反方向,然后根据这个反方向可以通过texCUBE中获取到
_Cubemap的对应位置的纹理,这里_Cubemap是一个sampleCUBE变量。
此外这里的fragment shader里的UNITY_LIGHT_ATTENUATION实际是一个内置函数,用于计算衰减和阴影的因子,实际上注释掉然后设置atten等于1你会发现没什么差距的。
fragment shader里计算出纹理,然后用RefectAmount在diffuse和reflection里插个值,就可以根据RefectAmount来进行(1-x)*a + x*b这种操作,获得一个相加后的颜色
这个是一个类似的效果