效果:(文末附shader)
基本流程:
使用HOUDINI 生成树木主体,处理模型球型法线AO等信息,Unity着色顶点动画处理 。
Houdini流程:
使用Tree_generator创建主体,具体细节直接查看官方文档即可
这里创建的三向树叶,并且把原始法线储存在可顶点色中(方便之后做裁切)
做好树叶整体,开始制作球形法线使得树木看起来是一个整体,这里用到了vdb,remesh,transfer做法线传递处理。
之后用maskbyfeature节点获取AO,一起放到顶点色中,下面贴一下节点流程图,当初截图的时候有点乱,现在找不到源文件了。(尴尬)
贴图准备:
三通道混合:R:rim,G:树叶纹理,B:透贴
unity着色:
主要的点就是球形法线和插片裁剪,球形法线来实现基本光照
原始法线储存在顶点色中(要变换-1~1<-->0~1)用来做切片裁剪,减少切片感
AO存储在顶点色的a通道中
shader代码:
Shader "Unlit/TreeLeaves"
{
Properties
{
_Color("Color",COLOR)=(1,1,1,1)
_MainTex("MainTex",2D)="white"{}
_ShadowColor("ShadowColor",COLOR) = (0,0,0,1)
_ShadowIntensity("ShadowIntensity",Range(0,1)) = 1
_BackSubsurfaceDistortion("BSFD",float) = 1
_cutOff("Cutoff",Range(0,1)) = 0.5
_SpecularPow("SpecularPow",Range(0,50)) = 0.5
_LeafSSSPow("LeafSSSPow",Range(0,3)) = 0.5
_TreeSSSPow("TreeSSSPow",Range(0,5)) = 3
}
SubShader
{
Tags
{
"RenderPipeline"="UniversalPipeline"//渲染管线标记,标注当前SubShader是给URP渲染管线使用的
"RenderType"="Opaque"//渲染方式为不透明
"UniversalMaterialType" = "Unlit"
"Queue"="Geometry"
}
Pass //主Pass
{
Name "Pass"
Tags
{
// LightMode: <None>
}
//Cull Back
//Cull Front
Cull Off
AlphaToMask Off
//Blend One Zero
//Blend SrcAlpha OneMinusSrcAlpha
//ZTest LEqual
//ZWrite On
HLSLPROGRAM
#pragma target 4.5
//只在以下平台进行编译
//#pragma exclude_renderers gles gles3 glcore
//定义顶点着色器
#pragma vertex vert
//定义片段着色器
#pragma fragment frag
//自阴影添加声明
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/TextureStack.hlsl"
//顶点着色器的输入(模型的数据信息)类似于appdate
struct Attributes
{
float3 positionOS : POSITION;
float2 uv : TEXCOORD;
//更改后的球形法线
float3 normal : NORMAL;
//原始法线,AO信息烘焙在顶点色
float4 color : COLOR;
};
//顶点着色器输出
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
float3 viewDir : TEXCOORD1;
float3 lightDir : TEXCOORD2;
float3 positionWS : TEXCOORD3;
float4 color : COLOR;
};
CBUFFER_START(UnityPerMaterial)
half4 _Color;
half4 _ShadowColor;
half _BackSubsurfaceDistortion;
half _cutOff;
half _ShadowIntensity;
half _SpecularPow;
half _LeafSSSPow;
half _TreeSSSPow;
float4 _MainTex_ST;
TEXTURE2D(_MainTex);//纹理的定义,如果是编译到GLES2.0平台,则相当于sampler2D _MainTex;否则就相当于Texture2D _MainTex
SAMPLER(sampler_MainTex);//采样器的定义,如果是编译到GLES2.0平台,就相当于空,否则就相当于SamplerState sampler_MainTex
// SAMPLER(SamplerState_Point_Repeat);根据传入的名称选择采样模式,比如该传入的名称代表贴图采样的Wrap Mode为Repeat,Fiter Mode为Point
CBUFFER_END
//v2f vert(appdata v)
//顶点着色器
Varyings vert(Attributes v)
{
Varyings o = (Varyings)0;
o.positionWS = TransformObjectToWorld(v.positionOS);
o.positionCS = TransformWorldToHClip(o.positionWS);
o.normal = TransformObjectToWorldNormal(v.normal,true);
o.viewDir = GetWorldSpaceNormalizeViewDir(o.positionWS.xyz);
o.lightDir = normalize(_MainLightPosition.xyz);
o.uv = TRANSFORM_TEX(v.uv,_MainTex);
o.color = v.color;
return o;
}
//fixed4 frag(v2f i):SV_TARGET
//片断着色器
half4 frag(Varyings i) : SV_TARGET
{
//采样Main贴图(R通道:Rim透光贴图,G通道:纹理贴图,B通道:透贴)
half4 mainTex = SAMPLE_TEXTURE2D(_MainTex,sampler_MainTex,i.uv);
//获得自阴影
float4 SHADOW_COORDS = TransformWorldToShadowCoord(i.positionWS);
Light mainLight = GetMainLight(SHADOW_COORDS);
float shadowAttenuation = mainLight.shadowAttenuation;
shadowAttenuation = lerp(1, shadowAttenuation, _ShadowIntensity);
//AO
float AO = i.color.a;
原始法线
//float3 YSnormal = TransformObjectToWorldNormal((i.color.rgb-.5)*2);
//lambert light
float lambert = dot(i.lightDir, i.normal);
//ReflectDir
float3 ReflectDir = normalize(reflect(-mainLight.direction,i.normal));
//Specular
float Blinphong = normalize(dot(i.normal,mainLight.direction + i.viewDir));
//Specular
float Specular = pow(saturate(dot(ReflectDir,i.viewDir)),_SpecularPow);
NdotV
//float YSNdV = dot(YSnormal,i.viewDir);
//float QSNdV = dot(i.normal,i.viewDir);
finel
//float finel = 1-pow(1-abs(YSNdV),1);
//ShadowColor
float4 ShadowColor = lerp(_ShadowColor,half4(1.4,1.4,1.4,1),lambert);
//Back light SSS
float3 backLitDir = i.normal * _BackSubsurfaceDistortion + i.lightDir;
float backSSS = saturate(dot(i.viewDir, -backLitDir));
backSSS = saturate(pow(backSSS, _TreeSSSPow));
//Rim
float Rim = pow(mainTex.r,_LeafSSSPow);
//裁切
clip((mainTex.b) - _cutOff);
return float4(lerp(_ShadowColor,_Color.rgb,AO + backSSS * Rim + Specular + lambert + i.normal.y) * max(0.75, mainTex.g) * shadowAttenuation,mainTex.b);
}
ENDHLSL
}
pass {
Tags{ "LightMode" = "ShadowCaster" }
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
//sampler2D _MainTex;
half _cutOff;
float4 _MainTex_ST;
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
v2f vert(appdata v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
o.uv = TRANSFORM_TEX(v.uv,_MainTex);
return o;
}
float4 frag(v2f i) : SV_Target
{
float4 color;
half4 mainTex=SAMPLE_TEXTURE2D(_MainTex,sampler_MainTex,i.uv);
color.xyz = float3(0.0, 0.0, 0.0);
clip(mainTex.b - _cutOff);
return color;
}
ENDHLSL
}
}
FallBack "Hidden/Shader Graph/FallbackError"
}
拓展:
创建树木主干,方法众多,可使用drawcurve节点和quick_basic_tree节点生成,制作成HDA,直接在引擎中创建更方便,更定制化(比如临时需要在悬崖创建一颗弯曲的树,可以使用样条线绘制生成)。当然speed tree更方便专业制作。还有可扩展的LOD制作,批量生成LOD更加方便。(讲道理这个截图里面的树很简陋,我记得有做过更复杂的树干,但是找不到了|挠头|)
声明:
最近发表的文章都是从业以来零散在各处的笔记再做整合(主要是想水专栏),有些方法可能参考了很多地方的文章,但是找不到原文链接,万分抱歉。