Shader入门精要-2-shader入门/基础光照模型/纹理

shader入门

类型

在这里插入图片描述

内置文件

在这里插入图片描述

三种精度

在这里插入图片描述

光照模型

标准光照模型:

只关心直接光照,光源发出的光线经过物体表面的一次反射直接进入相机的光线。

方法:

把进入相机的光线分为四个部分,每个部分用一种方法计算贡献度:

  • 自发光emissive:等于物体材质的自发光颜色

  • 高光反射specular: 需要知道法线,视角方向,光源方向,反射方向

Phong模型:
在这里插入图片描述
在这里插入图片描述
Blinn模型:
避免计算反射方向,将入射方向和光源方向去平均值并归一。然后用法线和新得到的向量去计算:
在这里插入图片描述

  • 漫反射diffuse:【兰伯特定律:反射强度和法线与光源方向夹角的余弦值成正比】,注意去掉背面的光,即余弦值小于0
    这样光找不到的地方全是黑的,可以增加环境光来变亮,但是明暗程度还是一样的。出现了
    【半兰伯特光照模型:就是将原来兰伯特模型限制背光为0变为数值映射,即[-1,1]->[0,1] 在原模型的点积后*0.5再加0.5】

  • 环境光ambient:等于设置的一个固定环境光

半兰伯特光照模型

在这里插入图片描述

高光反射光照模型(Phong光照模型):

高光反射是视线方向和反射方向的夹角余弦值

高光反射中反射方向计算:

反射方向计算

高光反射光照模型(Blinn-Phong光照模型):

不关心反射光,将视角方向和入射光方向相加,然后归一化。
Blinn-Phong

计算光照模型

逐像素光照:

在这里插入图片描述

逐像素计算漫反射光(兰伯特光照模型)

Shader "Mytest/DiffuseLightFragShader"
{
	
	Properties{

		_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
	}

	SubShader{
		Pass{
			Tags{"LightMode"="ForwardBase"}

			CGPROGRAM 
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"
			#include "Lighting.cginc"
			
			fixed4 _DiffuseColor;
			struct v2f{
				float4 pos: SV_POSITION;
				//float3 color:COLOR;
				float3 worldNormal:TEXCOORD0;
			};

			v2f vert(appdata_base v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);

				///漫反射光
				//在世界坐标下进行
				//对法线转换  需要用到  变换矩阵的 逆转置 矩阵
				//_World2Object是Object2World的逆矩阵
				//转置,就变成左乘向量
				float3 normalWorld = mul(v.normal,(float3x3)unity_WorldToObject);
				//归一化
				o.worldNormal = normalize(normalWorld);

				return o;
			}

			fixed4 frag(v2f i):SV_Target{

				///环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				//光照方向 (只适用一个方向光)
				float3 LightWorld = normalize( _WorldSpaceLightPos0.xyz);
				//计算
				//法线和光照方向点乘  兰伯特定律
				float3 diffuseLight = _DiffuseColor.rgb* _LightColor0.rgb *saturate(dot(LightWorld,i.worldNormal));
				//光
				fixed3 color = diffuseLight+ambient;

				return fixed4(color,1);
			}
			ENDCG
		}
	}
	FallBack "Diffuse"
}

逐像素计算漫反射光(半兰伯特光照模型)

Shader "Mytest/DiffuseLightFragShader_HalfLambert"
{
	
	Properties{

		_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
	}

	SubShader{
		Pass{
			Tags{"LightMode"="ForwardBase"}

			CGPROGRAM 
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"
			#include "Lighting.cginc"
			
			fixed4 _DiffuseColor;
			struct v2f{
				float4 pos: SV_POSITION;
				//float3 color:COLOR;
				float3 worldNormal:TEXCOORD0;
			};

			v2f vert(appdata_base v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);

				///漫反射光
				//在世界坐标下进行
				//对法线转换  需要用到  变换矩阵的 逆转置 矩阵
				//_World2Object是Object2World的逆矩阵
				//转置,就变成左乘向量
				float3 normalWorld = mul(v.normal,(float3x3)unity_WorldToObject);
				//归一化
				o.worldNormal = normalize(normalWorld);

				return o;
			}

			fixed4 frag(v2f i):SV_Target{

				///环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				//光照方向 (只适用一个方向光)
				float3 LightWorld = normalize( _WorldSpaceLightPos0.xyz);
				//计算
				//法线和光照方向点乘  半兰伯特定律
				float halfLMBT = dot(LightWorld,i.worldNormal)*0.5+0.5;
				float3 diffuseLight = _DiffuseColor.rgb* _LightColor0.rgb *halfLMBT;
				//光
				fixed3 color = diffuseLight+ambient;

				return fixed4(color,1);
			}
			ENDCG
		}
	}
	FallBack "Diffuse"
}


逐像素计算高光反射(Phong模型)

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Mytest/SpecularLightFragShader"{
	
	Properties{

		//漫反射颜色
		_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
		//高光颜色
		_SpecularColor("SpecularColor",Color) = (1,1,1,1)
		//高光大小
		_Gloss("Gloss",Range(8,256)) = 20
	}

	SubShader{
		Pass{
			Tags{"LightMode"="ForwardBase"}

			CGPROGRAM 
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"
			#include "Lighting.cginc"
			
			fixed4 _DiffuseColor;
			fixed4 _SpecularColor;
			float _Gloss;

			struct v2f{
				float4 pos: SV_POSITION;
				float3 worldNormal:TEXCOORD0;
				float3 worldPos:TEXCOORD1;
			};

			v2f vert(appdata_base v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);


				//在世界坐标下进行
				//对法线转换  需要用到  变换矩阵的 逆转置 矩阵
				//_World2Object是Object2World的逆矩阵
				//转置,就变成左乘向量
				float3 normalWorld = mul(v.normal,(float3x3)unity_WorldToObject);
				//归一化
				o.worldNormal = normalize(normalWorld);

				o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
				
			
				return o;
			}

			fixed4 frag(v2f i):SV_Target{
				///环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

				///漫反射光
				
				//光照方向 (只适用一个方向光)
				float3 LightWorld = normalize( _WorldSpaceLightPos0.xyz);
				//计算
				//法线和光照方向点乘  兰伯特定律
				float3 diffuseLight = _DiffuseColor.rgb* _LightColor0.rgb *saturate(dot(LightWorld,i.worldNormal));

				///高光反射
				//根据公式需要知道反射光方向和视角方向
				//反射光方向 并归一
				fixed3 reflectDir = normalize(reflect(-LightWorld,i.worldNormal));
				//视角方向  都放在世界坐标下,相机-点位  即视角方向   
				//获取视角方向,并归一化
				fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz-i.worldPos.xyz);
				//反射光
				fixed3 specularLight = _LightColor0.rgb*_SpecularColor.rgb* pow(saturate(dot(reflectDir,viewDir)),_Gloss);


				//光
				fixed3 color = diffuseLight + ambient + specularLight;


				return fixed4(color,1);
			}
			ENDCG
		}
	}
	FallBack "Specular"
}

逐像素计算高光反射(Blinn-Phong模型)

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Mytest/SpecularLightFragShader_BlinnPhong"{
	
	Properties{

		//漫反射颜色
		_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
		//高光颜色
		_SpecularColor("SpecularColor",Color) = (1,1,1,1)
		//高光大小
		_Gloss("Gloss",Range(8,256)) = 20
	}

	SubShader{
		Pass{
			Tags{"LightMode"="ForwardBase"}

			CGPROGRAM 
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"
			#include "Lighting.cginc"
			
			fixed4 _DiffuseColor;
			fixed4 _SpecularColor;
			float _Gloss;

			struct v2f{
				float4 pos: SV_POSITION;
				float3 worldNormal:TEXCOORD0;
				float3 worldPos:TEXCOORD1;
			};

			v2f vert(appdata_base v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);


				//在世界坐标下进行
				//对法线转换  需要用到  变换矩阵的 逆转置 矩阵
				//_World2Object是Object2World的逆矩阵
				//转置,就变成左乘向量
				float3 normalWorld = mul(v.normal,(float3x3)unity_WorldToObject);
				//归一化
				o.worldNormal = normalize(normalWorld);

				o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
				
			
				return o;
			}

			fixed4 frag(v2f i):SV_Target{
				///环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

				///漫反射光
				
				//光照方向 (只适用一个方向光)
				float3 LightWorld = normalize( _WorldSpaceLightPos0.xyz);
				//计算
				//法线和光照方向点乘  兰伯特定律
				float3 diffuseLight = _DiffuseColor.rgb* _LightColor0.rgb *saturate(dot(LightWorld,i.worldNormal));

				///高光反射
				//根据公式需要知道 法线 和 视角和入射光的和向量
				//视角方向  都放在世界坐标下,相机-点位  即视角方向   
				//获取视角方向,并归一化
				fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz-i.worldPos.xyz);
				float3 newDir =normalize( viewDir+LightWorld);
				//反射光
				//fixed3 specularLight = _LightColor0.rgb*_SpecularColor.rgb* pow(saturate(dot(reflectDir,viewDir)),_Gloss);
				fixed3 specularLight = _LightColor0.rgb*_SpecularColor.rgb* pow(saturate(dot(newDir,i.worldNormal)),_Gloss);

				//光
				fixed3 color = diffuseLight + ambient + specularLight;


				return fixed4(color,1);
			}
			ENDCG
		}
	}
	FallBack "Specular"
}

逐顶点光照:

在这里插入图片描述

逐顶点计算漫反射光(兰伯特光照模型)

// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Mytest/DiffuseLightShader"{
	
	Properties{

		_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
	}

	SubShader{
		Pass{
			Tags{"LightMode"="ForwardBase"}

			CGPROGRAM 
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"
			#include "Lighting.cginc"
			
			fixed4 _DiffuseColor;
			struct v2f{
				float4 pos: SV_POSITION;
				float3 color:COLOR;
			};

			v2f vert(appdata_base v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);

				///环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

				///漫反射光
				//在世界坐标下进行
				//对法线转换  需要用到  变换矩阵的 逆转置 矩阵
				//_World2Object是Object2World的逆矩阵
				//转置,就变成左乘向量
				float3 normalWorld = mul(v.normal,(float3x3)unity_WorldToObject);
				//归一化
				float3 NNormalWorld = normalize(normalWorld);
				//光照方向 (只适用一个方向光)
				float3 LightWorld = normalize( _WorldSpaceLightPos0.xyz);
				//计算
				//法线和光照方向点乘  兰伯特定律
				float3 diffuseLight = _DiffuseColor.rgb* _LightColor0.rgb *saturate(dot(LightWorld,NNormalWorld));

				//光
				o.color = diffuseLight+ambient;
			
				return o;
			}

			fixed4 frag(v2f i):SV_Target{
				return fixed4(i.color,1);
			}
			ENDCG
		}
	}
	FallBack "Diffuse"
}

逐顶点计算漫反射光(半兰伯特光照模型)

区别就只是half

// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Mytest/DiffuseLightShader_HalfLambert"{
	
	Properties{

		_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
	}

	SubShader{
		Pass{
			Tags{"LightMode"="ForwardBase"}

			CGPROGRAM 
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"
			#include "Lighting.cginc"
			
			fixed4 _DiffuseColor;
			struct v2f{
				float4 pos: SV_POSITION;
				float3 color:COLOR;
			};

			v2f vert(appdata_base v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);

				///环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

				///漫反射光
				//在世界坐标下进行
				//对法线转换  需要用到  变换矩阵的 逆转置 矩阵
				//_World2Object是Object2World的逆矩阵
				//转置,就变成左乘向量
				float3 normalWorld = mul(v.normal,(float3x3)unity_WorldToObject);
				//归一化
				float3 NNormalWorld = normalize(normalWorld);
				//光照方向 (只适用一个方向光)
				float3 LightWorld = normalize( _WorldSpaceLightPos0.xyz);
				//计算
				//法线和光照方向点乘  半兰伯特定律
				float halfLMBT = dot(LightWorld,NNormalWorld)*0.5 + 0.5;
				float3 diffuseLight = _DiffuseColor.rgb* _LightColor0.rgb * halfLMBT;

				//光
				o.color = diffuseLight+ambient;
			
				return o;
			}

			fixed4 frag(v2f i):SV_Target{
				return fixed4(i.color,1);
			}
			ENDCG
		}
	}
	FallBack "Diffuse"
}

逐顶点计算高光反射(Phong模型)

这线性不线性给老子弄不会了,,,

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Mytest/SpecularLightShader"{
	
	Properties{

		//漫反射颜色
		_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
		//高光颜色
		_SpecularColor("SpecularColor",Color) = (1,1,1,1)
		//高光大小
		_Gloss("Gloss",Range(8,256)) = 20
	}

	SubShader{
		Pass{
			Tags{"LightMode"="ForwardBase"}

			CGPROGRAM 
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"
			#include "Lighting.cginc"
			
			fixed4 _DiffuseColor;
			fixed4 _SpecularColor;
			float _Gloss;

			struct v2f{
				float4 pos: SV_POSITION;
				fixed3 color:COLOR;
			};

			v2f vert(appdata_base v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);

				///环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

				///漫反射光
				//在世界坐标下进行
				//对法线转换  需要用到  变换矩阵的 逆转置 矩阵
				//_World2Object是Object2World的逆矩阵
				//转置,就变成左乘向量
				float3 normalWorld = mul(v.normal,(float3x3)unity_WorldToObject);
				//归一化
				float3 NNormalWorld = normalize(normalWorld);
				//光照方向 (只适用一个方向光)
				float3 LightWorld = normalize( _WorldSpaceLightPos0.xyz);
				//计算
				//法线和光照方向点乘  兰伯特定律
				float3 diffuseLight = _DiffuseColor.rgb* _LightColor0.rgb *saturate(dot(LightWorld,NNormalWorld));

				///高光反射
				//根据公式需要知道反射光方向和视角方向
				//反射光方向 并归一     此处reflect需要光源指向交点,所以取反
				fixed3 reflectDir = normalize(reflect(-LightWorld,NNormalWorld));
				//视角方向  都放在世界坐标下,相机-点位  即视角方向   
				//需要将点转移到世界坐标
				fixed3 vertexWorldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
				//获取视角方向,并归一化
				fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz-vertexWorldPos);
				//反射光
				float3 specularLight = _LightColor0.rgb*_SpecularColor.rgb* pow(saturate(dot(reflectDir,viewDir)),_Gloss);


				//光
				o.color = diffuseLight + ambient + specularLight;
			
				return o;
			}

			fixed4 frag(v2f i):SV_Target{
				return fixed4(i.color,1);
			}
			ENDCG
		}
	}
	FallBack "Specular"
}

逐顶点计算高光反射(Blinn-Phong模型)

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Mytest/SpecularLightShader_BlinnPhong"{
	
	Properties{

		//漫反射颜色
		_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
		//高光颜色
		_SpecularColor("SpecularColor",Color) = (1,1,1,1)
		//高光大小
		_Gloss("Gloss",Range(8,256)) = 20
	}

	SubShader{
		Pass{
			Tags{"LightMode"="ForwardBase"}

			CGPROGRAM 
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"
			#include "Lighting.cginc"
			
			fixed4 _DiffuseColor;
			fixed4 _SpecularColor;
			float _Gloss;

			struct v2f{
				float4 pos: SV_POSITION;
				fixed3 color:COLOR;
			};

			v2f vert(appdata_base v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);

				///环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

				///漫反射光
				//在世界坐标下进行
				//对法线转换  需要用到  变换矩阵的 逆转置 矩阵
				//_World2Object是Object2World的逆矩阵
				//转置,就变成左乘向量
				float3 normalWorld = mul(v.normal,(float3x3)unity_WorldToObject);
				//归一化
				float3 NNormalWorld = normalize(normalWorld);
				//光照方向 (只适用一个方向光)
				float3 LightWorld = normalize( _WorldSpaceLightPos0.xyz);
				//计算
				//法线和光照方向点乘  兰伯特定律
				float3 diffuseLight = _DiffuseColor.rgb* _LightColor0.rgb *saturate(dot(LightWorld,NNormalWorld));

				///高光反射
				//根据公式需要知道反射光方向和视角方向
				//反射光方向 并归一     此处reflect需要光源指向交点,所以取反
				//fixed3 reflectDir = normalize(reflect(-LightWorld,NNormalWorld));
				//视角方向  都放在世界坐标下,相机-点位  即视角方向   
				//需要将点转移到世界坐标
				fixed3 vertexWorldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
				//获取视角方向,并归一化
				fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz-vertexWorldPos);
				//blinn模型需要的视角方向和入射光方向相加 再归一化
				float3 newDir = normalize(viewDir + LightWorld);

				float3 specularLight = _LightColor0.rgb*_SpecularColor.rgb* pow(saturate(dot(newDir,NNormalWorld)),_Gloss);
				//反射光
				//float3 specularLight = _LightColor0.rgb*_SpecularColor.rgb* pow(saturate(dot(reflectDir,viewDir)),_Gloss);


				//光
				o.color = diffuseLight + ambient + specularLight;
			
				return o;
			}

			fixed4 frag(v2f i):SV_Target{
				return fixed4(i.color,1);
			}
			ENDCG
		}
	}
	FallBack "Specular"
}

上边光照计算都是基于只有一个平行光,其他光源计算方式不同。且上边计算高光反射时,使用的漫反射都是兰伯特模型。

告别基础运算,使用内置方法

计算光照模型常用函数:

在这里插入图片描述

纹理!

模型的色彩通过纹理映射来展现,纹理坐标又称UV坐标(U横,V纵)范围是[0,1],存储在顶点信息中.

unity纹理的一些属性:

Texture Type纹理类型

Wrap Mode 决定纹理坐标超过[0,1]范围后的值

Repeat:超出部分计算小数部分采样。即不断重复
Clamp:超出1的都是1,小于0的都是0

Filter Mode 纹理由于变换产生拉伸时将会采用那种滤波模式

point,bilinear,trilinear 效果依次提升,但性能耗费增加
在这里插入图片描述

mipmapping 多级渐远纹理技术

抗锯齿问题:提前处理纹理,生成很多小的图像,像金字塔一样,每一层都是对上一层降级采样。
需要耗费空间去存储 多占用33%内存 空间换时间

纹理的最大尺寸

为不同平台选择不同纹理尺寸

纹理长宽尽量为2的幂
NPOT(non power of two)非2的幂

纹理格式:

纹理格式精度越高,效果越好,占用和耗费越大
不需要高精度时,尽量用压缩格式的纹理,例如漫反射纹理

OpenGL和DirectX中差异

OpenGL:原点在左下角
DirectX:原点在左上角

unity使用OpenGL的在左下角,且它已经处理好不同平台的问题

纹理实践

在shader中,properties中定义的纹理名称+_ST 可以获取到纹理的缩放xy和平移zw值
在这里插入图片描述

在这里插入图片描述

基本采样(纹理的使用):

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Mytest/TextureBaseUseShader"{

	Properties{

		_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
		_DiffuseTexture("DiffuseTex",2D) = "white"{}
		_SpecularColor("SpecularColor",Color) = (1,1,1,1)
		_Gloss("Gloss",Range(8,256))=20
	}

	SubShader{
		Pass{

			Tags{"LightMode"="ForwardBase"}

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"
			#include "Lighting.cginc"


			fixed4 _DiffuseColor;
			sampler2D _DiffuseTexture;
			//纹理名+_ST  获取该纹理的缩放xy和平移zw值
			float4 _DiffuseTexture_ST;
			fixed4 _SpecularColor;
			float _Gloss;


			struct v2f{
				float4 pos:SV_POSITION;
				float3 worldNormal: TEXCOORD0;
				float4 worldPos:TEXCOORD1;
				float2 uv:TEXCOORD2;
			};

			v2f vert(appdata_base v){

				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);

				//时刻记住,用变换矩阵的逆转置矩阵来获得法线,  否则法线方向会被拉伸时改变
				o.worldNormal = normalize( UnityObjectToWorldNormal(v.normal));

				o.worldPos = mul( unity_ObjectToWorld,v.vertex);

				o.uv = v.texcoord.xy * _DiffuseTexture_ST.xy +_DiffuseTexture_ST.zw;
				//内置函数
				//o.uv = TRANSFORM_TEX(v.texcoord,_DiffuseTexture);

				return o;
			}

			fixed4 frag(v2f i):SV_Target{
				//入射光
				fixed3 worldLightDir = normalize( UnityWorldSpaceLightDir(i.worldPos) );
				//将纹理的对应坐标颜色和漫反射光颜色混合
				fixed3 albedo = tex2D(_DiffuseTexture,i.uv).rgb * _DiffuseColor.rgb;
				///这边跟书上不太一样,按自己理解写了
				//环境光
				fixed3 ambientLight = UNITY_LIGHTMODEL_AMBIENT.xyz ;

				//漫反射光  入射光和法线点积
				fixed3 diffuseLight = albedo * _LightColor0.rgb * max(0,(dot(worldLightDir,i.worldNormal)));

				//高光反射计算 入射光和视角方向的合  以及  法线
				fixed3 viewDir = normalize( UnityWorldSpaceViewDir(i.worldPos) );
				fixed3 NewDir = normalize( viewDir + worldLightDir);
				fixed3 specularLight = _SpecularColor.rgb * _LightColor0.rgb * pow( max(0,dot(NewDir,i.worldNormal)),_Gloss  );

				fixed3 color = ambientLight + diffuseLight + specularLight;

				return fixed4(color,1);
			}
			ENDCG
		}
	}
	FallBack "Specular"
}

凹凸纹理(使用纹理表现凹凸效果):

原理:
使用一张纹理来修改法线,让模型看起来是凹凸的,但顶点位置不变,也就是轮廓是不变的。

两种方式:

  • 高度映射:
    使用一张高度纹理,模拟表面位移,来得到一个模拟有位移的法线值。

高度纹理:存储强度值,表示模型表面局部的高度,越浅越凸起,越暗越凹陷;

优点:直观,看以看出凹凸
缺点:计算复杂,需要根据灰度值计算,耗费性能。

  • 法线映射:
    使用一张法线纹理,直接存储着法线值。

法线纹理:存储表面法线方向。 法线范围[-1,1] 像素分量[0,1]
所以需要映射 +1 /2;
所以在计算时,需要反向映射,获取原来法线的真实值
因为存储的时单位向量,所以只需要xy两个值即可,z可以通过计算得到

模型空间的法线纹理:
切线空间的法线纹理:顶点的切线空间
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在切线空间下计算法线:
Shader "Mytest/NormalInTangentSpaceShader"{
	Properties{

		_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
		_DiffuseTexture("DiffuseTexture",2D) = "white"{}
		_NormalMap("NormalMap",2D)="bump"{}
		_BumpScale("BumpScale",Float)=1
		_SpecularColor("SpecularColor",Color) = (1,1,1,1)
		_Gloss("Gloss",Range(8,256)) = 20
	}
	SubShader{

		Pass{
			Tags{"LightMode"="ForwardBase"}
			
			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"
			#include "Lighting.cginc"

			fixed3 _DiffuseColor;
			sampler2D _DiffuseTexture;
			float4 _DiffuseTexture_ST;
			sampler2D _NormalMap;
			float4 _NormalMap_ST;
			float _BumpScale;
			fixed3 _SpecularColor;
			float _Gloss;


		

			struct v2f{
				float4 pos:SV_POSITION;
				float4 uv:TEXCOORD0;
				float3 viewDir: TEXCOORD1;//切线空间下的视角方向
				float3 lightDir:TEXCOORD2;//切线空间下的入射光方向
			};


			v2f vert(appdata_tan v){
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);

				//纹理映射和法线纹理映射
				o.uv.xy = v.texcoord.xy * _DiffuseTexture_ST.xy + _DiffuseTexture_ST.zw;
				o.uv.zw = v.texcoord.xy * _NormalMap_ST.xy + _NormalMap_ST.zw;

				//内置函数,切线空间
				TANGENT_SPACE_ROTATION;
				//#define TANGENT_SPACE_ROTATION \
				//float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w; \
				//float3x3 rotation = float3x3( v.tangent.xyz, binormal, v.normal )
				
				o.lightDir = mul(rotation,ObjSpaceLightDir(v.vertex)).xyz;
				o.viewDir = mul(rotation,ObjSpaceViewDir(v.vertex)).xyz;


				return o;
			}


			fixed4 frag(v2f i):SV_Target{

				//切线空间下的入射光方向
				fixed3 tangentLightDir = normalize(i.lightDir);
				//切线空间下的视角方向
				fixed3 tangentViewDir = normalize(i.viewDir);

				//获取法线纹理数据
				fixed4 getNormal = tex2D(_NormalMap,i.uv.zw);
				//获取切线空间下的法线
				fixed3 tangentNormal;
				//图片未设置为法线贴图需要反向映射
				//解除法线到颜色的映射关系得到真实法线  和自己加的法线系数相乘
				//tangentNormal.xy = (getNormal * 2 - 1) * _BumpScale;
				//tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy,tangentNormal.xy)));
				//内置api
				tangentNormal = UnpackNormal(getNormal);
				tangentNormal.xy *=_BumpScale;
				tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy,tangentNormal.xy)));
				//混合纹理颜色
				fixed3 albedo = tex2D(_DiffuseTexture,i.uv.zy);




				//光计算
				//环境光
				fixed3 ambientLight = UNITY_LIGHTMODEL_AMBIENT.xyz;

				//漫反射光
				//新的法线和入射光
				fixed3 diffuseLight = _DiffuseColor.rgb * albedo * _LightColor0.rgb * saturate( dot(tangentNormal,tangentLightDir));

				//高光反射
				//新的法线 和 入射光和视角合
				float3 newDir = normalize(tangentLightDir + tangentViewDir);
				fixed3 specularLight = _SpecularColor.rgb * _LightColor0.rgb * pow( saturate(dot(newDir,tangentNormal)),_Gloss);


				fixed3 color = ambientLight + diffuseLight +specularLight;


				return fixed4(color.rgb,1);
			}




			ENDCG
		}
	}
	FallBack "Specular"
}
在世界空间下计算法线:
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Mytest/NormalInWorldSpaceShader"{
	Properties{
		_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
		_DiffuseTexture("DiffuseTexture",2D) = "white"{}
		_NormalMap("NormalMap",2D) = "bump"{}
		_BumpScale("BumpScale",Float) = 1
		_SpecularColor("SpecularColor",Color)= (1,1,1,1)
		_Gloss("Gloss",Range(8,256)) = 20
	}
	SubShader{
		Pass{
			Tags{"LightMode"="ForwardBase"}

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"
			#include "Lighting.cginc"

			fixed4 _DiffuseColor;
			sampler2D _DiffuseTexture;
			float4 _DiffuseTexture_ST;
			sampler2D _NormalMap;
			float4 _NormalMap_ST;
			float _BumpScale;
			fixed4 _SpecularColor;
			float _Gloss;

			struct v2f{
				float4 pos: SV_POSITION;
				float4 uv:TEXCOORD0;
				float4 TtoW0:TEXCOORD1;
				float4 TtoW1:TEXCOORD2;
				float4 TtoW2:TEXCOORD3;
			};

			v2f vert(appdata_tan v){
				v2f o;

				o.pos = UnityObjectToClipPos(v.vertex);

				//顶点的纹理信息
				o.uv.xy = v.texcoord.xy * _DiffuseTexture_ST.xy + _DiffuseTexture_ST.zw;
				o.uv.zw = v.texcoord.xy * _NormalMap_ST.xy + _NormalMap_ST.zw;

				//获取世界坐标下数据
				float3 worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
				fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
				//世界坐标下的切线
				fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
				//副切线
				fixed3 worldBinormal = cross(worldNormal,worldTangent) * v.tangent.w;

				//获取切线空间转世界的矩阵  顺便把世界坐标塞进去,节省寄存器
				o.TtoW0 = float4(worldTangent.x,worldBinormal.x,worldNormal.x,worldPos.x);
				o.TtoW1 = float4(worldTangent.y,worldBinormal.y,worldNormal.y,worldPos.y);
				o.TtoW2 = float4(worldTangent.z,worldBinormal.z,worldNormal.z,worldPos.z);

				return o; 
			}


			fixed4 frag(v2f i):SV_Target{
				//基础数据获得
				//漫反射纹理采样
				fixed3 albedo = tex2D(_DiffuseTexture,i.uv.zy);

				float3 worldPos = normalize( float3(i.TtoW0.w,i.TtoW1.w,i.TtoW2.w) );//世界位置
				fixed3 worldLightDir = normalize( UnityWorldSpaceLightDir(worldPos));//入射光
				fixed3 worldViewDir = normalize( UnityWorldSpaceViewDir(worldPos));

				//法线纹理提取 先采样获取颜色,再解析颜色到法线
				fixed3 normalMap = UnpackNormal( tex2D(_NormalMap,i.uv.zw) );
				normalMap.xy *= _BumpScale;//法线 加上自己设置的深度缩放
				normalMap.z = sqrt(1-max(0,dot(normalMap.xy,normalMap.xy)));//由xy计算z值

				//法线从切线空间转换到世界空间
				fixed3 worldNormal = normalize(half3(dot(i.TtoW0.xyz,normalMap),dot(i.TtoW1.xyz,normalMap),dot(i.TtoW2.xyz,normalMap)));

				还是光计算 再来N遍!

				//环境光
				fixed3 ambientLight = UNITY_LIGHTMODEL_AMBIENT.xyz;

				//漫反射光  入射光和法线
				fixed3 diffuseLight = _LightColor0.rgb * _DiffuseColor.rgb * albedo * max(0,dot(worldLightDir,worldNormal));

				//高光反射  法线和 入射光和视角合
				float3 newDir = normalize(worldLightDir + worldViewDir);
				fixed3 specularLight = _LightColor0.rgb * _SpecularColor.rgb * pow( max(0,dot(newDir,worldNormal)) ,_Gloss);
				
				//合
				fixed3 color = ambientLight + diffuseLight + specularLight;
				
				return fixed4(color.rgb,1);
			}






			ENDCG
		}
	}
	FallBack "Specular"
}

渐变纹理:

一开始纹理只是为了定义颜色,但后来发现,纹理可以存储任何表面信息。

使用渐变纹理,控制漫反射光照效果

Shader "Mytest/RampShader"{
	Properties{

		_DiffuseColor("DiffuseColor",Color)=(1,1,1,1)
		_RampMap("RampMap",2D)="white"{}
		_SpecularColor("SpecularColor",Color) = (1,1,1,1)
		_Gloss("Gloss",Range(8,256)) = 20
	}
	SubShader{
		Pass{
			Tags{"LightMode"="ForwardBase"}
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"
			#include "Lighting.cginc"

			fixed4 _DiffuseColor;
			sampler2D _RampMap;
			float4 _RampMap_ST;
			fixed4 _SpecularColor;
			float _Gloss;

			struct v2f{
				float4 pos:SV_POSITION;
				float3 worldNormal:TEXCOORD0;
				float3 worldPos:TEXCOORD1;
				float2 uv:TEXCOORD2;
			};

			v2f vert(appdata_base v){
				v2f o;
				
				o.pos = UnityObjectToClipPos(v.vertex);

				o.worldNormal = normalize( UnityObjectToWorldNormal(v.normal) );

				o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;

				//内置函数 不用自己再写了:(tex.xy * name##_ST.xy + name##_ST.zw)
				o.uv =  TRANSFORM_TEX(v.texcoord,_RampMap);
				return o;
			}

			fixed4 frag(v2f i):SV_Target{
				//入射光
				fixed3 worldLight = UnityWorldSpaceLightDir(i.worldPos);


				//环境光
				fixed3 ambientLight = UNITY_LIGHTMODEL_AMBIENT.xyz;
				//漫反射  
				//半兰伯特
				fixed halfLMBT = dot(worldLight,i.worldNormal) * 0.5 + 0.5;
				fixed3 changeColor = tex2D(_RampMap, fixed2(halfLMBT,halfLMBT)).rgb;
				fixed3 diffuseLight = _LightColor0.rgb * _DiffuseColor.rgb * changeColor;

				//高光反射
				fixed3 worldView = normalize( UnityWorldSpaceViewDir(i.worldPos) );
				fixed3 newDir = normalize( worldView + worldLight);
				fixed3 specularLight = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0,dot(newDir,i.worldNormal)),_Gloss);


				return fixed4(ambientLight+diffuseLight+specularLight,1);
			}

			ENDCG
		}
	}
	FallBack "Specular"
}

遮罩纹理:

提供一种表面数据,来控制其他数据的变化。

Shader "Mytest/MaskTextureShader"{
	Properties{
		_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
		_SpecularColor("SpecularColor",Color)=(1,1,1,1)
		_Gloss("Gloss",Range(8,256)) = 20
		_DiffuseTexture("DiffuseTexture",2D)="white"{}
		_BumpMap("BumpMap",2D)="bump"{}
		_BumpScale("BumpScale",Float)=1
		_MaskMap("MaskMap",2D)="white"{}
		_MaskScale("MaskScale",Float)=1
	}
	SubShader{
		Pass{
			Tags{"LightMode"="ForwardBase"}
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"
			#include "Lighting.cginc"

			fixed3 _DiffuseColor;
			fixed3 _SpecularColor;
			float _Gloss;
			sampler2D _MaskMap;//遮罩纹理
			sampler2D _DiffuseTexture;
			float4 _DiffuseTexture_ST;
			sampler2D _BumpMap;//法线纹理
			float _BumpScale;//法线纹理控制影响程度
			float _MaskScale;//遮罩纹理控制影响程度

			struct v2f{
				float4 pos:SV_POSITION;
				float2 uv:TEXCOORD0;
				//fixed3 worldNormal:TEXCOORD1;
				float3 lightDir:TEXCOORD1;
				float3 viewDir:TEXCOORD2;
			};

			v2f vert(appdata_tan v){
				v2f o;
					
				o.pos = UnityObjectToClipPos(v.vertex);

				o.uv.xy = v.texcoord.xy * _DiffuseTexture_ST.xy + _DiffuseTexture_ST.zw;

				TANGENT_SPACE_ROTATION;
				o.lightDir = mul(rotation,ObjSpaceLightDir(v.vertex)).xyz;
				o.viewDir = mul(rotation,ObjSpaceViewDir(v.vertex)).xyz;
				return o;
			}

			fixed4 frag(v2f i):SV_Target{
				fixed3 tangentLightDir = normalize(i.lightDir);
				fixed3 tangentViewDir = normalize(i.viewDir);
				
				fixed3 normalMapData = UnpackNormal( tex2D(_BumpMap,i.uv));

				normalMapData.xy *= _BumpScale;
				normalMapData.z = sqrt(1-max(0,dot(normalMapData.xy,normalMapData.xy)));

				//贴图纹理
				fixed3 albedo = tex2D(_DiffuseTexture,i.uv).rgb * _DiffuseColor.rgb;

				//环境光
				fixed3 ambientLight = UNITY_LIGHTMODEL_AMBIENT.rgb;
				//漫反射光
				fixed3 diffuseLight = _LightColor0.rgb * albedo * max(0, dot(tangentLightDir,normalMapData));
				//高光反射
				fixed3 newDir = normalize(tangentLightDir + tangentViewDir);
				//遮罩纹理采样
				float maskData = tex2D(_MaskMap,i.uv).r * _MaskScale;
				fixed3 specularLight = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0,dot(newDir,normalMapData)),_Gloss) * maskData;


				return fixed4(ambientLight + diffuseLight + specularLight,1);
			}
			ENDCG
		}

	}
	FallBack "Specular"
}

透明

深度缓存(Z-Buffer)

解决物体可见性问题,物体的遮挡关系;
在这里插入图片描述

透明度测试(Alpha Test)

在这里插入图片描述

Shader "Mytest/AlphaTestShader"{
	Properties{
		_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
		_DiffuseTexture("DiffuseTexture",2D)="white"{}
		_CutOff("cutoff",Range(0,1))=0.5
	}
	SubShader{
		Tags{"Quene"="AlphaTest"
		"IgnoreProjector"="True"
		"RenderType"="TransparentCutout"
		}
		Pass{
			Tags{"LightMode"="ForwardBase"}

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"
			#include "Lighting.cginc"

			fixed3 _DiffuseColor;
			sampler2D _DiffuseTexture;
			float4 _DiffuseTexture_ST;
			float _CutOff;

			struct v2f{
				float4 pos:SV_POSITION;
				float3 worldPos:TEXCOORD0;
				fixed3 worldNormal:TEXCOORD1;
				float2 uv:TEXCOORD2;
			};

			v2f vert(appdata_base v){
				v2f o;
					
				o.pos = UnityObjectToClipPos(v.vertex);
				o.worldNormal = normalize( UnityObjectToWorldNormal(v.normal));
				o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;

				o.uv = TRANSFORM_TEX(v.texcoord,_DiffuseTexture);

				return o;
			}

			fixed4 frag(v2f i):SV_Target
			{
				fixed3 worldLight = normalize( UnityWorldSpaceLightDir(i.worldPos) );
				//fixed3 viewLight = normalize( UnityWorldSpaceViewDir(i.worldPos) );

				//
				fixed3 ambientLight = UNITY_LIGHTMODEL_AMBIENT.rgb;

				fixed4 textureColor = tex2D(_DiffuseTexture,i.uv);

				//Alpha Test
				clip(textureColor.a - _CutOff);
				if((textureColor.a - _CutOff)<0.0){
					discard;
				}


				//
				fixed3 diffuseLight = _LightColor0.rgb * _DiffuseColor.rgb * textureColor.rgb * max(0,dot(i.worldNormal,worldLight));

				//
				//fixed3 newDir = normalize( worldLight + )
				return fixed4(ambientLight + diffuseLight,1);
			}
			ENDCG
		}
	}
	FallBack ""
}

透明度混合(Alpha Blending)

需要关闭深度写入
将当前像素作为混合因子和深度缓存里的像素做混合,混合方式可以配置

在这里插入图片描述
混合标签

Shader "Mytest/AlphaBlendShader"{
	Properties{
		_DiffuseColor("DiffuseColor",Color)=(1,1,1,1)
		_DiffuseTexture("DiffuseTexture",2D)="white"{}
		_AlphaScale("AlphaScale",Range(0,1)) = 1
	}
	SubShader{
		Tags{"Quene"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
		Pass{
			Tags{"LightMode"="ForwardBase"}
			ZWrite Off
			Blend SrcAlpha OneMinusSrcAlpha


			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"
			#include "Lighting.cginc"

			fixed4 _DiffuseColor;
			sampler2D _DiffuseTexture;
			float4 _DiffuseTexture_ST;
			float _AlphaScale;

			struct v2f{
				float4 pos:SV_POSITION;//顶点的剪裁空间坐标
				float3 worldPos:TEXCOORD0;//世界空间下的顶点
				fixed3 worldNormal:TEXCOORD1;//世界空间下的法线 归一化后
				float2 uv:TEXCOORD2;//顶点UV坐标
			};

			v2f vert(appdata_base v){
				v2f o;

				o.pos = UnityObjectToClipPos(v.vertex);
				o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
				o.worldNormal = normalize( UnityObjectToWorldNormal(v.normal) );

				o.uv.xy = v.texcoord.xy * _DiffuseTexture_ST.xy + _DiffuseTexture_ST.zw;
				return o;
			}

			fixed4 frag(v2f i):SV_Target{
				fixed3 worldLightDir = normalize( UnityWorldSpaceLightDir(i.worldPos) );

				fixed4 textureColor = tex2D(_DiffuseTexture,i.uv);



				//
				fixed3 ambientColor = UNITY_LIGHTMODEL_AMBIENT.rgb;

				//漫反射 入射光和法线
				fixed3 diffuseColor = _LightColor0.rgb * _DiffuseColor.rgb * textureColor.rgb * max(0,dot(i.worldNormal,worldLightDir)); 


				return fixed4(ambientColor + diffuseColor,textureColor.a* _AlphaScale);
			}
			ENDCG
		}
	}
	FallBack "Transparent/VertexLit"
}

开启深度写入的半透明效果

在这里插入图片描述
使用两个pass来渲染模型,第一个Pass开启深度写入,不输出颜色,只是为了把模型的深度值写入深度缓存;第二个Pass进行透明混合,上一个pass得到的深度信息,在这个pass中按照像素深度信息来进行混合。
模型自身的透明关系并不能表现

Shader "Mytest/AlphaBlendUseZWriteShader"{
	Properties{

		_DiffuseColor("DiffuseColor",Color)=(1,1,1,1)
		_DiffuseTexture("DiffuseTexture",2D)="white"{}
		_AlphaScale("AlphaScale",Range(0,1)) = 1
	}
	SubShader{
		Tags{"Quene"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}

		Pass{
			ZWrite On
			ColorMask 0//不写入任何颜色
		}
		Pass{
			Tags{"LightMode"="ForwardBase"}
			ZWrite off
			Blend SrcAlpha OneMinusSrcAlpha


			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"
			#include "Lighting.cginc"

			fixed4 _DiffuseColor;
			sampler _DiffuseTexture;
			float4 _DiffuseTexture_ST;
			float _AlphaScale;

			struct v2f{
				float4 pos:SV_POSITION;
				fixed3 worldNormal:TEXCOORD0;
				float3 worldPos:TEXCOORD1;
				float2 uv:TEXCOORD2;
			};

			v2f vert(appdata_base v){
				v2f o;

				o.pos = UnityObjectToClipPos(v.vertex);
				o.worldNormal = normalize( UnityObjectToWorldNormal(v.normal) );
				o.worldPos = mul(unity_ObjectToWorld,v.vertex ).xyz;

				o.uv.xy = TRANSFORM_TEX(v.texcoord,_DiffuseTexture);

				return o;
			}
			fixed4 frag(v2f i):SV_Target{
				//
				fixed4 textureColor = tex2D(_DiffuseTexture,i.uv);
				
				//入射光
				fixed3 worldLightDir = normalize( UnityWorldSpaceLightDir(i.worldPos) );

				fixed3 ambientLight = UNITY_LIGHTMODEL_AMBIENT.rgb;

				fixed3 diffuseLight = _LightColor0.rgb * _DiffuseColor.rgb * textureColor.rgb * max(0,dot(worldLightDir,i.worldNormal));



				return fixed4(ambientLight + diffuseLight,_AlphaScale * textureColor.a);
			}



			ENDCG
		}

	}
	Fallback ""
}

混合

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

双面透明效果:

Cull 剔除某个面的渲染
Cull Back | Front | Off
剔除背面,前面,关闭剔除

透明度测试 直接关闭剔除
透明度混合 通过使用两个Pass 第一个关闭前面渲染 第二个关闭后面渲染 先渲染背面,再渲染前面

渲染顺序:

在这里插入图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值