mirror of
https://github.com/RHeavenStudio/HeavenStudio.git
synced 2025-06-12 15:07:39 +02:00
BTSDS: Outlines via Post Processing
This commit is contained in:
146
Assets/PostProcessingOutlines/Outline.shader
Normal file
146
Assets/PostProcessingOutlines/Outline.shader
Normal file
@ -0,0 +1,146 @@
|
||||
Shader "Hidden/Roystan/Outline Post Process"
|
||||
{
|
||||
SubShader
|
||||
{
|
||||
Cull Off ZWrite Off ZTest Always
|
||||
|
||||
Pass
|
||||
{
|
||||
// Custom post processing effects are written in HLSL blocks,
|
||||
// with lots of macros to aid with platform differences.
|
||||
// https://github.com/Unity-Technologies/PostProcessing/wiki/Writing-Custom-Effects#shader
|
||||
HLSLPROGRAM
|
||||
#pragma vertex Vert
|
||||
#pragma fragment Frag
|
||||
#include "Packages/com.unity.postprocessing/PostProcessing/Shaders/StdLib.hlsl"
|
||||
|
||||
TEXTURE2D_SAMPLER2D(_MainTex, sampler_MainTex);
|
||||
// _CameraNormalsTexture contains the view space normals transformed
|
||||
// to be in the 0...1 range.
|
||||
TEXTURE2D_SAMPLER2D(_CameraNormalsTexture, sampler_CameraNormalsTexture);
|
||||
TEXTURE2D_SAMPLER2D(_CameraDepthTexture, sampler_CameraDepthTexture);
|
||||
|
||||
// Data pertaining to _MainTex's dimensions.
|
||||
// https://docs.unity3d.com/Manual/SL-PropertiesInPrograms.html
|
||||
float4 _MainTex_TexelSize;
|
||||
|
||||
float _Scale;
|
||||
float4 _Color;
|
||||
|
||||
float _DepthThreshold;
|
||||
float _DepthNormalThreshold;
|
||||
float _DepthNormalThresholdScale;
|
||||
|
||||
float _NormalThreshold;
|
||||
|
||||
// This matrix is populated in PostProcessOutline.cs.
|
||||
float4x4 _ClipToView;
|
||||
|
||||
// Combines the top and bottom colors using normal blending.
|
||||
// https://en.wikipedia.org/wiki/Blend_modes#Normal_blend_mode
|
||||
// This performs the same operation as Blend SrcAlpha OneMinusSrcAlpha.
|
||||
float4 alphaBlend(float4 top, float4 bottom)
|
||||
{
|
||||
float3 color = (top.rgb * top.a) + (bottom.rgb * (1 - top.a));
|
||||
float alpha = top.a + bottom.a * (1 - top.a);
|
||||
|
||||
return float4(color, alpha);
|
||||
}
|
||||
|
||||
// Both the Varyings struct and the Vert shader are copied
|
||||
// from StdLib.hlsl included above, with some modifications.
|
||||
struct Varyings
|
||||
{
|
||||
float4 vertex : SV_POSITION;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
float2 texcoordStereo : TEXCOORD1;
|
||||
float3 viewSpaceDir : TEXCOORD2;
|
||||
#if STEREO_INSTANCING_ENABLED
|
||||
uint stereoTargetEyeIndex : SV_RenderTargetArrayIndex;
|
||||
#endif
|
||||
};
|
||||
|
||||
Varyings Vert(AttributesDefault v)
|
||||
{
|
||||
Varyings o;
|
||||
o.vertex = float4(v.vertex.xy, 0.0, 1.0);
|
||||
o.texcoord = TransformTriangleVertexToUV(v.vertex.xy);
|
||||
// Transform our point first from clip to view space,
|
||||
// taking the xyz to interpret it as a direction.
|
||||
o.viewSpaceDir = mul(_ClipToView, o.vertex).xyz;
|
||||
|
||||
#if UNITY_UV_STARTS_AT_TOP
|
||||
o.texcoord = o.texcoord * float2(1.0, -1.0) + float2(0.0, 1.0);
|
||||
#endif
|
||||
|
||||
o.texcoordStereo = TransformStereoScreenSpaceTex(o.texcoord, 1.0);
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
float4 Frag(Varyings i) : SV_Target
|
||||
{
|
||||
float halfScaleFloor = floor(_Scale * 0.5);
|
||||
float halfScaleCeil = ceil(_Scale * 0.5);
|
||||
|
||||
// Sample the pixels in an X shape, roughly centered around i.texcoord.
|
||||
// As the _CameraDepthTexture and _CameraNormalsTexture default samplers
|
||||
// use point filtering, we use the above variables to ensure we offset
|
||||
// exactly one pixel at a time.
|
||||
float2 bottomLeftUV = i.texcoord - float2(_MainTex_TexelSize.x, _MainTex_TexelSize.y) * halfScaleFloor;
|
||||
float2 topRightUV = i.texcoord + float2(_MainTex_TexelSize.x, _MainTex_TexelSize.y) * halfScaleCeil;
|
||||
float2 bottomRightUV = i.texcoord + float2(_MainTex_TexelSize.x * halfScaleCeil, -_MainTex_TexelSize.y * halfScaleFloor);
|
||||
float2 topLeftUV = i.texcoord + float2(-_MainTex_TexelSize.x * halfScaleFloor, _MainTex_TexelSize.y * halfScaleCeil);
|
||||
|
||||
float3 normal0 = SAMPLE_TEXTURE2D(_CameraNormalsTexture, sampler_CameraNormalsTexture, bottomLeftUV).rgb;
|
||||
float3 normal1 = SAMPLE_TEXTURE2D(_CameraNormalsTexture, sampler_CameraNormalsTexture, topRightUV).rgb;
|
||||
float3 normal2 = SAMPLE_TEXTURE2D(_CameraNormalsTexture, sampler_CameraNormalsTexture, bottomRightUV).rgb;
|
||||
float3 normal3 = SAMPLE_TEXTURE2D(_CameraNormalsTexture, sampler_CameraNormalsTexture, topLeftUV).rgb;
|
||||
|
||||
float depth0 = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, bottomLeftUV).r;
|
||||
float depth1 = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, topRightUV).r;
|
||||
float depth2 = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, bottomRightUV).r;
|
||||
float depth3 = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, topLeftUV).r;
|
||||
|
||||
// Transform the view normal from the 0...1 range to the -1...1 range.
|
||||
float3 viewNormal = normal0 * 2 - 1;
|
||||
float NdotV = 1 - dot(viewNormal, -i.viewSpaceDir);
|
||||
|
||||
// Return a value in the 0...1 range depending on where NdotV lies
|
||||
// between _DepthNormalThreshold and 1.
|
||||
float normalThreshold01 = saturate((NdotV - _DepthNormalThreshold) / (1 - _DepthNormalThreshold));
|
||||
// Scale the threshold, and add 1 so that it is in the range of 1..._NormalThresholdScale + 1.
|
||||
float normalThreshold = normalThreshold01 * _DepthNormalThresholdScale + 1;
|
||||
|
||||
// Modulate the threshold by the existing depth value;
|
||||
// pixels further from the screen will require smaller differences
|
||||
// to draw an edge.
|
||||
float depthThreshold = _DepthThreshold * depth0 * normalThreshold;
|
||||
|
||||
float depthFiniteDifference0 = depth1 - depth0;
|
||||
float depthFiniteDifference1 = depth3 - depth2;
|
||||
// edgeDepth is calculated using the Roberts cross operator.
|
||||
// The same operation is applied to the normal below.
|
||||
// https://en.wikipedia.org/wiki/Roberts_cross
|
||||
float edgeDepth = sqrt(pow(depthFiniteDifference0, 2) + pow(depthFiniteDifference1, 2)) * 100;
|
||||
edgeDepth = edgeDepth > depthThreshold ? 1 : 0;
|
||||
|
||||
float3 normalFiniteDifference0 = normal1 - normal0;
|
||||
float3 normalFiniteDifference1 = normal3 - normal2;
|
||||
// Dot the finite differences with themselves to transform the
|
||||
// three-dimensional values to scalars.
|
||||
float edgeNormal = sqrt(dot(normalFiniteDifference0, normalFiniteDifference0) + dot(normalFiniteDifference1, normalFiniteDifference1));
|
||||
edgeNormal = edgeNormal > _NormalThreshold ? 1 : 0;
|
||||
|
||||
float edge = max(edgeDepth, edgeNormal);
|
||||
|
||||
float4 edgeColor = float4(_Color.rgb, _Color.a * edge);
|
||||
|
||||
float4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord);
|
||||
|
||||
return alphaBlend(edgeColor, color);
|
||||
}
|
||||
ENDHLSL
|
||||
}
|
||||
}
|
||||
}
|
10
Assets/PostProcessingOutlines/Outline.shader.meta
Normal file
10
Assets/PostProcessingOutlines/Outline.shader.meta
Normal file
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 534727a34958a6b409102bf07fadffab
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
preprocessorOverride: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
84
Assets/PostProcessingOutlines/OutlinePostProfile.asset
Normal file
84
Assets/PostProcessingOutlines/OutlinePostProfile.asset
Normal file
@ -0,0 +1,84 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &-511430771441868654
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 3
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 957177531333f744abeb6082b4c44bc0, type: 3}
|
||||
m_Name: PostProcessOutline
|
||||
m_EditorClassIdentifier:
|
||||
active: 1
|
||||
enabled:
|
||||
overrideState: 1
|
||||
value: 1
|
||||
scale:
|
||||
overrideState: 1
|
||||
value: 4
|
||||
color:
|
||||
overrideState: 1
|
||||
value: {r: 0, g: 0, b: 0, a: 1}
|
||||
depthThreshold:
|
||||
overrideState: 1
|
||||
value: 0.6
|
||||
depthNormalThreshold:
|
||||
overrideState: 1
|
||||
value: 0.6
|
||||
depthNormalThresholdScale:
|
||||
overrideState: 0
|
||||
value: 7
|
||||
normalThreshold:
|
||||
overrideState: 1
|
||||
value: 0.4
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 8e6292b2c06870d4495f009f912b9600, type: 3}
|
||||
m_Name: OutlinePostProfile
|
||||
m_EditorClassIdentifier:
|
||||
settings:
|
||||
- {fileID: -511430771441868654}
|
||||
--- !u!114 &114178475910842986
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 3
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 7f161f1bfe8d69542a86f8d442c273b8, type: 3}
|
||||
m_Name: PostProcessOutline
|
||||
m_EditorClassIdentifier:
|
||||
active: 1
|
||||
enabled:
|
||||
overrideState: 1
|
||||
value: 1
|
||||
scale:
|
||||
overrideState: 0
|
||||
value: 1
|
||||
color:
|
||||
overrideState: 0
|
||||
value: {r: 1, g: 1, b: 1, a: 1}
|
||||
depthThreshold:
|
||||
overrideState: 0
|
||||
value: 1.5
|
||||
depthNormalThreshold:
|
||||
overrideState: 0
|
||||
value: 0.5
|
||||
depthNormalThresholdScale:
|
||||
overrideState: 0
|
||||
value: 7
|
||||
normalThreshold:
|
||||
overrideState: 0
|
||||
value: 0.4
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11527536cbbe79e46ae805ff65dfc703
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
42
Assets/PostProcessingOutlines/PostProcessOutline.cs
Normal file
42
Assets/PostProcessingOutlines/PostProcessOutline.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering.PostProcessing;
|
||||
|
||||
[Serializable]
|
||||
[PostProcess(typeof(PostProcessOutlineRenderer), PostProcessEvent.BeforeStack, "Roystan/Post Process Outline")]
|
||||
public sealed class PostProcessOutline : PostProcessEffectSettings
|
||||
{
|
||||
[Tooltip("Number of pixels between samples that are tested for an edge. When this value is 1, tested samples are adjacent.")]
|
||||
public IntParameter scale = new IntParameter { value = 1 };
|
||||
public ColorParameter color = new ColorParameter { value = Color.white };
|
||||
[Tooltip("Difference between depth values, scaled by the current depth, required to draw an edge.")]
|
||||
public FloatParameter depthThreshold = new FloatParameter { value = 1.5f };
|
||||
[Range(0, 1), Tooltip("The value at which the dot product between the surface normal and the view direction will affect " +
|
||||
"the depth threshold. This ensures that surfaces at right angles to the camera require a larger depth threshold to draw " +
|
||||
"an edge, avoiding edges being drawn along slopes.")]
|
||||
public FloatParameter depthNormalThreshold = new FloatParameter { value = 0.5f };
|
||||
[Tooltip("Scale the strength of how much the depthNormalThreshold affects the depth threshold.")]
|
||||
public FloatParameter depthNormalThresholdScale = new FloatParameter { value = 7 };
|
||||
[Range(0, 1), Tooltip("Larger values will require the difference between normals to be greater to draw an edge.")]
|
||||
public FloatParameter normalThreshold = new FloatParameter { value = 0.4f };
|
||||
}
|
||||
|
||||
public sealed class PostProcessOutlineRenderer : PostProcessEffectRenderer<PostProcessOutline>
|
||||
{
|
||||
public override void Render(PostProcessRenderContext context)
|
||||
{
|
||||
var sheet = context.propertySheets.Get(Shader.Find("Hidden/Roystan/Outline Post Process"));
|
||||
sheet.properties.SetFloat("_Scale", settings.scale);
|
||||
sheet.properties.SetColor("_Color", settings.color);
|
||||
sheet.properties.SetFloat("_DepthThreshold", settings.depthThreshold);
|
||||
sheet.properties.SetFloat("_DepthNormalThreshold", settings.depthNormalThreshold);
|
||||
sheet.properties.SetFloat("_DepthNormalThresholdScale", settings.depthNormalThresholdScale);
|
||||
sheet.properties.SetFloat("_NormalThreshold", settings.normalThreshold);
|
||||
sheet.properties.SetColor("_Color", settings.color);
|
||||
|
||||
Matrix4x4 clipToView = GL.GetGPUProjectionMatrix(context.camera.projectionMatrix, true).inverse;
|
||||
sheet.properties.SetMatrix("_ClipToView", clipToView);
|
||||
|
||||
context.command.BlitFullscreenTriangle(context.source, context.destination, sheet, 0);
|
||||
}
|
||||
}
|
11
Assets/PostProcessingOutlines/PostProcessOutline.cs.meta
Normal file
11
Assets/PostProcessingOutlines/PostProcessOutline.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 957177531333f744abeb6082b4c44bc0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/PostProcessingOutlines/Scripts.meta
Normal file
8
Assets/PostProcessingOutlines/Scripts.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91791aadbbc222442ad483fa480daeb6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,61 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class RenderReplacementShaderToTexture : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
bool deleteChildren = true;
|
||||
|
||||
[SerializeField]
|
||||
Shader replacementShader;
|
||||
|
||||
[SerializeField]
|
||||
RenderTextureFormat renderTextureFormat = RenderTextureFormat.ARGB32;
|
||||
|
||||
[SerializeField]
|
||||
FilterMode filterMode = FilterMode.Point;
|
||||
|
||||
[SerializeField]
|
||||
int renderTextureDepth = 24;
|
||||
|
||||
[SerializeField]
|
||||
CameraClearFlags cameraClearFlags = CameraClearFlags.Color;
|
||||
|
||||
[SerializeField]
|
||||
Color background = Color.black;
|
||||
|
||||
[SerializeField]
|
||||
string targetTexture = "_RenderTexture";
|
||||
|
||||
private RenderTexture renderTexture;
|
||||
private new Camera camera;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (deleteChildren)
|
||||
{
|
||||
foreach (Transform t in transform)
|
||||
{
|
||||
DestroyImmediate(t.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
Camera thisCamera = GetComponent<Camera>();
|
||||
|
||||
// Create a render texture matching the main camera's current dimensions.
|
||||
renderTexture = new RenderTexture(thisCamera.pixelWidth, thisCamera.pixelHeight, renderTextureDepth, renderTextureFormat);
|
||||
renderTexture.filterMode = filterMode;
|
||||
// Surface the render texture as a global variable, available to all shaders.
|
||||
Shader.SetGlobalTexture(targetTexture, renderTexture);
|
||||
|
||||
// Setup a copy of the camera to render the scene using the normals shader.
|
||||
GameObject copy = new GameObject("Camera" + targetTexture);
|
||||
camera = copy.AddComponent<Camera>();
|
||||
camera.CopyFrom(thisCamera);
|
||||
camera.transform.SetParent(transform);
|
||||
camera.targetTexture = renderTexture;
|
||||
camera.SetReplacementShader(replacementShader, "RenderType");
|
||||
camera.depth = thisCamera.depth - 1;
|
||||
camera.clearFlags = cameraClearFlags;
|
||||
camera.backgroundColor = background;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a7b4608cc97a8940ad8a9229d742532
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/PostProcessingOutlines/Shaders.meta
Normal file
8
Assets/PostProcessingOutlines/Shaders.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f4f903709ce9854ba9f1fd8341311bb
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,52 @@
|
||||
Shader "Hidden/Roystan/Normals Texture"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
}
|
||||
SubShader
|
||||
{
|
||||
Tags
|
||||
{
|
||||
"RenderType" = "Opaque"
|
||||
}
|
||||
|
||||
Pass
|
||||
{
|
||||
CGPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
|
||||
#include "UnityCG.cginc"
|
||||
|
||||
struct appdata
|
||||
{
|
||||
float4 vertex : POSITION;
|
||||
float3 normal : NORMAL;
|
||||
};
|
||||
|
||||
struct v2f
|
||||
{
|
||||
float4 vertex : SV_POSITION;
|
||||
float3 viewNormal : NORMAL;
|
||||
};
|
||||
|
||||
sampler2D _MainTex;
|
||||
float4 _MainTex_ST;
|
||||
|
||||
v2f vert (appdata v)
|
||||
{
|
||||
v2f o;
|
||||
o.vertex = UnityObjectToClipPos(v.vertex);
|
||||
o.viewNormal = COMPUTE_VIEW_NORMAL;
|
||||
//o.viewNormal = mul((float3x3)UNITY_MATRIX_M, v.normal);
|
||||
return o;
|
||||
}
|
||||
|
||||
float4 frag (v2f i) : SV_Target
|
||||
{
|
||||
return float4(normalize(i.viewNormal) * 0.5 + 0.5, 0);
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 95d6144eab187a34b929151454e2969d
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user