역자의 말.
일단 원본 링크 기억이 안납니다. -_-; 오래전에 노션에 한글로 정리 해 논 것만 남아 있네요. Zhihu 에서 발췌 된 내용입니다. 아무튼... 저번에 번역해서 공유 했던 원신 렌더링 리버스엔지니어링 기사에 연결 된 내용으로 함께 엮어 읽어보면 좋겠습니다.
이전 글 엮어서 보기. https://techartnomad.tistory.com/120
예전 정리 했던 내용 시작.
주석 1: 그림 1에서 조명은 텍스처 오른편에 있으며, 왼쪽과 오른쪽 모두 영향을 미치지 않으며, 최종 셰이더는 양쪽 모두 호환됩니다.
주석 2: 0은 얼굴 전체가 흰색인 경우를 의미하며, 그림1 h.png과 대응됩니다.
주석 3: 180.png 파일은 전체가 검은색이면 안 되며, 최소한 하나의 흰색 픽셀이 있어야 합니다. 이는 시작점에 빛이 비추는 것을 나타냅니다.
9개의 이미지를 모두 SDF 텍스처로 변환합니다.
GitHub - mattdesl/image-sdf: generate a signed distance field from an image 를 사용하여 SDF를 생성합니다.
https://github.com/leegoonz/image-sdf
매 두 인접한 SDF 텍스처에서 부드러운 이미지를 생성하십시오. 총 8 장입니다.
wow01.png를 예로 들면, PS에서 임계 값을 수정하여 미리보기를 조정할 수 있습니다. 결과는 다음과 같습니다.
8장의 스무딩 이미지를 각 픽셀마다 합한 후 8로 나눈 것이 최종 얼굴 그림자 텍스처입니다.
얼굴 그림자 맵 렌더링 그림자 코드:
float3 _Up= float3(0,1,0);//Direction on the character Pass in the code
float3 _Front= float3(0,0,-1);//Front direction of the figure Pass in the code
float3 Left= cross(_Up,_Front);
float3 Right=-Left;
//You can also take each direction directly from the model's world matrix
//This requires the model to be built with the correct orientation: X, Y, Z, right, top, front.
//float4 Front = mul(unity_ObjectToWorld,float4(0,0,1,0));
//float4 Right = mul(unity_ObjectToWorld,float4(1,0,0,0));
//float4 Up = mul(unity_ObjectToWorld,float4(0,1,0,0));
float FL= dot(normalize(_Front.xz), normalize(L.xz));
float LL= dot(normalize(Left.xz), normalize(L.xz));
float RL= dot(normalize(Right.xz), normalize(L.xz));
float faceLight= faceLightMap.r+ _FaceLightmpOffset ;//Used to align the light and dark transitions with the body of the hair
float faceLightRamp= (FL> 0)* min((faceLight> LL),(1> faceLight+RL ) ) ;
float3 Diffuse= lerp( _ShadowColor*BaseColor,BaseColor,faceLightRamp);
보충 자료
그림 2에서 생성된 SDF 매핑을 사용하여 셰이더 시뮬레이션 4. 부드러운 매핑 전환 및 블렌딩
셰이더 코드입니다:
왼쪽: sdf_thread.shader
Shader "sdf/thread"
{
Properties
{
_MainTex0 ("Texture", 2D) = "white" {}
_MainTex1 ("Texture", 2D) = "white" {}
_MainTex2 ("Texture", 2D) = "white" {}
_MainTex3 ("Texture", 2D) = "white" {}
_MainTex4 ("Texture", 2D) = "white" {}
_MainTex5 ("Texture", 2D) = "white" {}
_MainTex6 ("Texture", 2D) = "white" {}
_MainTex7 ("Texture", 2D) = "white" {}
_MainTex8 ("Texture", 2D) = "white" {}
_thread ("Thread", Range(0,1)) = 0.5
_delta("delta", Range(0,0.05)) = 0.01
}
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always
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;
}
sampler2D _MainTex0;
sampler2D _MainTex1;
sampler2D _MainTex2;
sampler2D _MainTex3;
sampler2D _MainTex4;
sampler2D _MainTex5;
sampler2D _MainTex6;
sampler2D _MainTex7;
sampler2D _MainTex8;
float _thread;
float _delta;
fixed4 frag (v2f i) : SV_Target
{
fixed4 col0 = tex2D(_MainTex0, i.uv);
fixed4 col1 = tex2D(_MainTex1, i.uv);
fixed4 col2 = tex2D(_MainTex2, i.uv);
fixed4 col3 = tex2D(_MainTex3, i.uv);
fixed4 col4 = tex2D(_MainTex4, i.uv);
fixed4 col5 = tex2D(_MainTex5, i.uv);
fixed4 col6 = tex2D(_MainTex6, i.uv);
fixed4 col7 = tex2D(_MainTex7, i.uv);
fixed4 col8 = tex2D(_MainTex8, i.uv);
float4 color = float4(0, 0, 0, 1);
float cols[9];
cols[0] = col0.a;
cols[1] = col1.a;
cols[2] = col2.a;
cols[3] = col3.a;
cols[4] = col4.a;
cols[5] = col5.a;
cols[6] = col6.a;
cols[7] = col7.a;
cols[8] = col8.a;
for (int i = 0; i < 8; i++) {
if (i/8.0 < _thread && _thread <= (i+1)/8.0) {
fixed r = lerp(cols[i], cols[i+1], _thread*8 - i);
r = smoothstep(0.5- _delta, 0.5+ _delta, r);
color = fixed4(r, r, r, 1);
return color;
}
}
return color;
}
ENDCG
}
}
}
sdf_blend.shader
Shader "sdf/blend"
{
Properties
{
_MainTex0 ("Texture", 2D) = "white" {}
_MainTex1 ("Texture", 2D) = "white" {}
_MainTex2 ("Texture", 2D) = "white" {}
_MainTex3 ("Texture", 2D) = "white" {}
_MainTex4 ("Texture", 2D) = "white" {}
_MainTex5 ("Texture", 2D) = "white" {}
_MainTex6 ("Texture", 2D) = "white" {}
_MainTex7 ("Texture", 2D) = "white" {}
_MainTex8 ("Texture", 2D) = "white" {}
_delta ("delta", Range(0,0.05)) = 0.01
}
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always
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;
}
sampler2D _MainTex0;
sampler2D _MainTex1;
sampler2D _MainTex2;
sampler2D _MainTex3;
sampler2D _MainTex4;
sampler2D _MainTex5;
sampler2D _MainTex6;
sampler2D _MainTex7;
sampler2D _MainTex8;
float _delta;
fixed4 frag (v2f i) : SV_Target
{
fixed4 col0 = tex2D(_MainTex0, i.uv);
fixed4 col1 = tex2D(_MainTex1, i.uv);
fixed4 col2 = tex2D(_MainTex2, i.uv);
fixed4 col3 = tex2D(_MainTex3, i.uv);
fixed4 col4 = tex2D(_MainTex4, i.uv);
fixed4 col5 = tex2D(_MainTex5, i.uv);
fixed4 col6 = tex2D(_MainTex6, i.uv);
fixed4 col7 = tex2D(_MainTex7, i.uv);
fixed4 col8 = tex2D(_MainTex8, i.uv);
float4 color = float4(0, 0, 0, 1);
float cols[9];
cols[0] = col0.a;
cols[1] = col1.a;
cols[2] = col2.a;
cols[3] = col3.a;
cols[4] = col4.a;
cols[5] = col5.a;
cols[6] = col6.a;
cols[7] = col7.a;
cols[8] = col8.a;
float4 color2 = float4(0, 0, 0, 1);
for (float j=1; j <= 256.0; j++) {
float _thread2 = j / 256.0;
for (int i = 0; i < 8; i++) {
if (i/8.0 < _thread2 && _thread2 <= (i+1)/8.0) {
fixed r = lerp(cols[i], cols[i+1], _thread2*8 - i);
r = smoothstep(0.5- _delta, 0.5+ _delta, r);
fixed4 tmp_color = fixed4(r, r, r, 1);
color2 = ((j-1) * color2 + tmp_color) / j;
break;
}
}
}
color = color2;
return color;
}
ENDCG
}
}
}
Unity에서 GPU 렌더링 SDF를 추가로 구현하면 SDF 스펙 생성이 1초도 걸리지 않습니다.
참고1: Texture는 입력 텍스처이며 Rt는 채우지 않아도 됩니다. sdfgenerate 쉐이더는 이 글의 뒷부분에 있습니다.
참고2: UP 컴퓨터에서 Unity 1024 * 1024 * 약 350 * 350 크기가 되면 GPU가 충돌합니다. 크기 제한이 있습니다.
출력 크기는 1024 * 1024입니다.
350 * 350 픽셀을 무차별 대상으로 하면 충돌합니다.
물론 아래 그림의 512 * 512 * 256 * 256 크기는 충돌 값보다 훨씬 작습니다.
모노 비헤이비어 컴포넌트로 사용하면 Rt를 채우지 않아도 되며, gen을 클릭하십시오.
1초도 안 걸렸네요, 정말 빠르네요
sdfgenerate.shader
Shader "ShaderMan/sdfgenerate"
{
Properties{
_MainTex ("MainTex", 2D) = "white" {}
_range ("range", Range(16, 256)) = 16
}
SubShader
{
Tags { "RenderType" = "Transparent" "Queue" = "Transparent" }
Pass
{
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct VertexInput {
fixed4 vertex : POSITION;
fixed2 uv:TEXCOORD0;
fixed4 tangent : TANGENT;
fixed3 normal : NORMAL;
//VertexInput
};
struct VertexOutput {
fixed4 pos : SV_POSITION;
fixed2 uv:TEXCOORD0;
//VertexOutput
};
//Variables
sampler2D _MainTex;
uniform float4 _MainTex_TexelSize;
float _range;
bool isIn(fixed2 uv) {
fixed4 texColor = tex2D(_MainTex, uv);
return texColor.r > 0.5;
}
float squaredDistanceBetween(fixed2 uv1, fixed2 uv2)
{
fixed2 delta = uv1 - uv2;
float dist = (delta.x * delta.x) + (delta.y * delta.y);
return dist;
}
VertexOutput vert (VertexInput v)
{
VertexOutput o;
o.pos = UnityObjectToClipPos (v.vertex);
o.uv = v.uv;
//VertexFactory
return o;
}
fixed4 frag(VertexOutput i) : SV_Target
{
fixed2 uv = i.uv;
const float range = _range;
const int iRange = int(range);
float halfRange = range / 2.0;
fixed2 startPosition = fixed2(i.uv.x - halfRange * _MainTex_TexelSize.x, i.uv.y - halfRange * _MainTex_TexelSize.y);
bool fragIsIn = isIn(uv);
float squaredDistanceToEdge = (halfRange* _MainTex_TexelSize.x*halfRange*_MainTex_TexelSize.y)*2.0;
// [unroll(100)]
for (int dx = 0; dx < iRange; dx++) {
// [unroll(100)]
for (int dy = 0; dy < iRange; dy++) {
fixed2 scanPositionUV = startPosition + float2(dx * _MainTex_TexelSize.x, dy* _MainTex_TexelSize.y);
bool scanIsIn = isIn(scanPositionUV / 1);
if (scanIsIn != fragIsIn) {
float scanDistance = squaredDistanceBetween(i.uv, scanPositionUV);
if (scanDistance < squaredDistanceToEdge) {
squaredDistanceToEdge = scanDistance;
}
}
}
}
float normalised = squaredDistanceToEdge / ((halfRange * _MainTex_TexelSize.x*halfRange * _MainTex_TexelSize.y)*2.0);
float distanceToEdge = sqrt(normalised);
if (fragIsIn)
distanceToEdge = -distanceToEdge;
normalised = 0.5 - distanceToEdge;
return fixed4(normalised, normalised, normalised, 1.0);
}
ENDCG
}
}
}
SdfGenerate.cs
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(SdfGenerate))]
public class SdfGenerateInsp : Editor
{
public override void OnInspectorGUI() {
base.DrawDefaultInspector();
var sdfGenerate = target as SdfGenerate;
if (GUILayout.Button("gen")) {
sdfGenerate.gen();
}
}
}
public class SdfGenerate : MonoBehaviour
{
public RenderTexture rt;
public Texture texture;
public Shader shader;
public int spread = 16;
public string savePath;
public Vector2 size = new Vector2(1024, 1024);
public void gen() {
var sdfGenerate = this;
if (sdfGenerate.texture == null) {
Debug.LogErrorFormat("texture is null ");
return;
}
if (sdfGenerate.shader == null) {
Debug.LogErrorFormat("shader is null ");
return;
}
if (sdfGenerate.rt != null) {
sdfGenerate.rt.Release();
sdfGenerate.rt = null;
}
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
//init();计算耗时的方法
sdfGenerate.rt = new RenderTexture((int)sdfGenerate.size.x, (int)sdfGenerate.size.y, 32, RenderTextureFormat.ARGB32);
Material mat = new Material(sdfGenerate.shader);
mat.hideFlags = HideFlags.DontSave;
mat.SetFloat("_range", sdfGenerate.spread);
var input_rt = RenderTexture.GetTemporary(new RenderTextureDescriptor(sdfGenerate.rt.width, sdfGenerate.rt.height, sdfGenerate.rt.format));
Graphics.Blit(texture, input_rt);
Graphics.Blit(input_rt, sdfGenerate.rt, mat);
RenderTexture.ReleaseTemporary(input_rt);
var rt = sdfGenerate.rt;
Texture2D tex = new Texture2D(rt.width, rt.height, TextureFormat.RGB24, false);
tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
tex.Apply();
var directory = Path.GetDirectoryName(sdfGenerate.savePath);
var fileName = Path.GetFileName(sdfGenerate.savePath);
//Debug.LogErrorFormat("path: {0}", sdfGenerate.savePath);
//Debug.LogErrorFormat("directory: {0}", directory);
//Debug.LogErrorFormat("fileName: {0}", fileName);
if (!string.IsNullOrEmpty(directory)) {
if (!Directory.Exists(directory)) {
Directory.CreateDirectory(directory);
}
}
else {
Debug.LogErrorFormat("savePath directory no exist {0}", sdfGenerate.savePath);
return;
}
File.WriteAllBytes(sdfGenerate.savePath, tex.EncodeToPNG());
Debug.LogFormat("save png: {0}", sdfGenerate.savePath);
watch.Stop();
var mSeconds = watch.ElapsedMilliseconds / 1000.0;
Debug.LogErrorFormat("耗时:{0}秒", mSeconds.ToString());
}
}
참고 문서: https://zhuanlan.zhihu.com/p/337944099
'TECH.ART.FLOW.IO' 카테고리의 다른 글
[번역]Why Talking About Render Graphs (1) | 2023.12.12 |
---|---|
[번역]Tone Mapping (0) | 2023.12.04 |
(참조)Lineage2m 개발 사례 언리얼 오픈데이 2019 발표. (1) | 2023.11.27 |
[번역]CI/CD 파이프라인이란 무엇일까요? (0) | 2023.11.27 |
[번역]원신 셰이더 렌더링 복원 해석 (0) | 2023.11.22 |