Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
o-l-l-i authored May 3, 2020
0 parents commit d59459d
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 0 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# ScreenSpaceDecal

![Screen Space Decal](decal.png)

## What is it

My simple implementation of screen space decal shader in Unity.

It does not support lighting or shadow receiving.

This was mainly a study project, where I wanted to force myself to learn a bit more about the different spatial transforms that can be performed in shaders.

## Features

- Screen space decal implementation
- Color filter for the splat
- Angle threshold to cut off streaks (user adjustable)
- I left verbose comments to the code in case someone wants to study how screen space decals can be implemented.

Developed on Unity 2018.4.12f1.
177 changes: 177 additions & 0 deletions ScreenSpaceDecal.shader
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// Screen Space Decal Shader by Olli S.
Shader "Custom/ScreenSpaceDecal"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color ("Color", color) = (1,1,1,1)
_CutOff ("CutOff Threshold", Range(0, 1)) = 0
}

SubShader
{
Tags { "RenderType"= "Transparent" "Queue" = "Transparent" }
LOD 100

Pass
{
ZWrite Off
ZTest Off
Cull Front

// Alpha blending
Blend SrcAlpha OneMinusSrcAlpha

CGPROGRAM

#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog

#include "UnityCG.cginc"


struct appdata
{
float4 vertex : POSITION;
float3 uv : TEXCOORD0;
};


struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
float4 screenPos : TEXCOORD1;
float4 viewRay : TEXCOORD2;
};


sampler2D _MainTex;
float4 _MainTex_ST;

sampler2D _CameraDepthTexture;
float4 _CameraDepthTexture_TexelSize;

float4 _Color;
float _CutOff;

// Screen Space Decals a la War Hammer 40k Space Marine
// https://www.slideshare.net/blindrenderer/screen-space-decals-in-warhammer-40000-space-marine-14699854
// 1. Draw underlying geometry
// 2. Rasterize a SSD box
// 3. Read the scene depth of each pixel
// 4. Calculate 3D position from depth
// 5. If the position is outside of the SSD box, reject
// 6. Otherwise, draw the pixel with the decal texture

v2f vert (appdata v)
{
v2f o;

// Regular object UVs
o.uv = TRANSFORM_TEX(v.uv, _MainTex);

// CLIP SPACE / PROJECTION POSITION
// Multiply model-view-projection matrix with model/object space position
// Manual about float4 UnityObjectToClipPos(float3 pos):
// Transforms a point from object space to the camera’s clip space in homogeneous coordinates
// This is the equivalent of mul(UNITY_MATRIX_MVP, float4(pos, 1.0)) and should be used in its place
o.vertex = UnityObjectToClipPos(v.vertex.xyz);

// SCREEN SPACE POSITION
// Calculate screen position of the vertex (we get a 2D location on screen surface)
// Used to sample depth texture on screen surface (uses screen UV of course, we can't use object UVs)
// Manual:
// float4 ComputeScreenPos (float4 clipPos) Computes texture coordinate for doing a screenspace-mapped texture sample
// Input is clip space position
// https://forum.unity.com/threads/what-does-the-function-computescreenpos-in-unitycg-cginc-do.294470/
o.screenPos = ComputeScreenPos(o.vertex);

// VIEW / CAMERA SPACE POSITION
// Camera centered coordinate system
float3 viewPos = UnityObjectToViewPos(v.vertex);

// WORLD POSITION
// Vertex position in the world space
float4 worldPos = mul(unity_ObjectToWorld, v.vertex);

// VIEW RAY
// Camera to vertex ray (also later in texel camera to texel)
// Should be from camera to the vertex, but in view/camera space?
// Create a view direction by multiplying view position by far plane
float3 viewRay = viewPos.xyz * _ProjectionParams.z;
// Or by first creating a world ray, and then transforming it to the view space
// float3 worldRay = worldPos.xyz - _WorldSpaceCameraPos.xyz;
// float3 viewRay = mul(UNITY_MATRIX_V, worldRay) * _ProjectionParams.z;

// Flip x and y-axis, becaus of Unity's right hand(?) coordinates
viewRay *= float3(-1,-1, 1);

// Perform a divide by the view position z in the fragment shader
// Otherwise coordinates will be all wrong
o.viewRay.xyzw = float4(viewRay.xyz, viewPos.z);

UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}


fixed4 frag (v2f i) : SV_Target
{
// SCREEN SPACE POSITION
// Remember to divide with w
// https://forum.unity.com/threads/what-does-the-function-computescreenpos-in-unitycg-cginc-do.294470/
float2 screenUV = i.screenPos.xy / i.screenPos.w;

// DEPTH
// Sample depth using screen UVs
// Linearize, remap to 0-1 range
float depth = Linear01Depth(tex2D(_CameraDepthTexture, screenUV));

// VIEW RAY
// Divide viewRay by the stored view position z
float3 viewRay = i.viewRay.xyz / i.viewRay.w;

// DECAL PROJECTION POSITION
// Scale the view ray to texel depth:
// View space is 0-1, depth is 0-1, by multiplying the view ray position, we move it to the depth distance
// So we get position we see in the world, and not on the decal projector object's surface
float4 decalProjectionPos = float4(viewRay.xyz * depth, 1);
// Now transform the projection position from view/camera space to the world space
float3 worldSpacePos = mul(unity_CameraToWorld, decalProjectionPos).xyz;
// Then transfer the surface hit world space position to the object's space
float3 objectPos = mul(unity_WorldToObject, float4(worldSpacePos, 1)).xyz;
// Apply stretching and backface cut off
float sideStrechThreshold = 0.999 - _CutOff;
// Calculate normals from screen space derivatives
// https://forum.unity.com/threads/flat-lighting-without-separate-smoothing-groups.280183/
float3 objectSpaceNormal = normalize(cross(ddx(objectPos), ddy(objectPos)));
// Generate a mask from the derived normal
float mask = objectSpaceNormal.y > sideStrechThreshold ? 1.0 : 0.0;

// CLIP
// Remove all non-decal area texels, using also masking
clip(0.5 * mask - abs(objectPos.xyz));

// DECAL-SPACE UVs
// Generate UVs in decal object's space, using decal xz-plane as the projector plane:
float2 decalUV = objectPos.xz + 0.5;
// Apply Unity texture tiling and offset
decalUV.xy * _MainTex_ST.xy + _MainTex_ST.zw;

// SAMPLE DECAL TEXTURE
// Also apply the Inspector color
float4 col = tex2D(_MainTex, decalUV) * _Color;

// Apply fog
UNITY_APPLY_FOG(i.fogCoord, col);

return col;
}
ENDCG

}
}
}
Binary file added decal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit d59459d

Please sign in to comment.