How to set the saturation level of an entire color channel in Unity - unity3d

I would like to set the saturation of an entire color channel in my main camera. The closest option that I've found was the Hue vs. Sat(uration) Grading Curve. In the background of the scene is a palm tree that is colored teal. I want the green level of the tree to still show. Same with the top of the grass in the foreground, It's closer to yellow than green, but I'd still want to see the little bit of green value that it has.
I have been searching the Unity documentation and the asset store for a possible 3rd party shader for weeks, but have come up empty handed. My current result is the best I could come up with, any help would be greatly appreciated. Thank you
SOLVED
-by check-marked answer. Just wanted to share what the results look like for anyone in the future who stumbles across this issue. Compare the above screenshot, where the palm tree in the background and the grass tops in the foreground are just black and white, to the after screenshot below. Full control in the scene of RGB saturation!

Examples using this method:
Below is a postprocessing shader intended to let you set the saturation of each color channel.
It first takes the original pixel color, gets the hue, saturation, and luminance. That color is taken to its most saturated, neutral-luminance version. The rgb of that is then multiplied by the desaturation factor to compute the rgb of the new hue. The magnitude of that rgb is multiplied by the original saturation to get the new saturation. This new hue and saturation is fed back in with the original luminance to compute the new color.
Shader "Custom/ChannelSaturation" {
Properties{
_MainTex("Base", 2D) = "white" {}
_rSat("Red Saturation", Range(0, 1)) = 1
_gSat("Green Saturation", Range(0, 1)) = 1
_bSat("Blue Saturation", Range(0, 1)) = 1
}
SubShader{
Pass {
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#include "UnityCG.cginc"
uniform sampler2D _MainTex;
float _rSat;
float _gSat;
float _bSat;
/*
source: modified version of https://www.shadertoy.com/view/MsKGRW
written # https://gist.github.com/hiroakioishi/
c4eda57c29ae7b2912c4809087d5ffd0
*/
float3 rgb2hsl(float3 c) {
float epsilon = 0.00000001;
float cmin = min( c.r, min( c.g, c.b ) );
float cmax = max( c.r, max( c.g, c.b ) );
float cd = cmax - cmin;
float3 hsl = float3(0.0, 0.0, 0.0);
hsl.z = (cmax + cmin) / 2.0;
hsl.y = lerp(cd / (cmax + cmin + epsilon),
cd / (epsilon + 2.0 - (cmax + cmin)),
step(0.5, hsl.z));
float3 a = float3(1.0 - step(epsilon, abs(cmax - c)));
a = lerp(float3(a.x, 0.0, a.z), a, step(0.5, 2.0 - a.x - a.y));
a = lerp(float3(a.x, a.y, 0.0), a, step(0.5, 2.0 - a.x - a.z));
a = lerp(float3(a.x, a.y, 0.0), a, step(0.5, 2.0 - a.y - a.z));
hsl.x = dot( float3(0.0, 2.0, 4.0) + ((c.gbr - c.brg)
/ (epsilon + cd)), a );
hsl.x = (hsl.x + (1.0 - step(0.0, hsl.x) ) * 6.0 ) / 6.0;
return hsl;
}
/*
source: modified version of
https://stackoverflow.com/a/42261473/1092820
*/
float3 hsl2rgb(float3 c) {
float3 rgb = clamp(abs(fmod(c.x * 6.0 + float3(0.0, 4.0, 2.0),
6.0) - 3.0) - 1.0, 0.0, 1.0);
return c.z + c.y * (rgb - 0.5) * (1.0 - abs(2.0 * c.z - 1.0));
}
float4 frag(v2f_img i) : COLOR {
float3 sat = float3(_rSat, _gSat, _bSat);
float4 c = tex2D(_MainTex, i.uv);
float3 hslOrig = rgb2hsl(c.rgb);
float3 rgbFullSat = hsl2rgb(float3(hslOrig.x, 1, .5));
float3 diminishedrgb = rgbFullSat * sat;
float diminishedHue = rgb2hsl(diminishedrgb).x;
float diminishedSat = hslOrig.y * length(diminishedrgb);
float3 mix = float3(diminishedHue, diminishedSat, hslOrig.z);
float3 newc = hsl2rgb(mix);
float4 result = c;
result.rgb = newc;
return result;
}
ENDCG
}
}
}
If you're using URP (Universal Rendering Pipeline), which is recommended, you can create a new forward renderer pipeline asset, assign the shader to that asset, and configure it appropriately. Further information including diagrams can be found in the official unity tutorial for custom render passes with URP.
If you aren't using URP, you have other options. You could attach it to specific materials, or use the below script from Wikibooks to the camera's gameobject to apply a material using the above shader as a postprocessing effect to the camera:
using System;
using UnityEngine;
[RequireComponent(typeof(Camera))]
[ExecuteInEditMode]
public class PostProcessingEffectScript : MonoBehaviour {
public Material material;
void OnEnable()
{
if (null == material || null == material.shader ||
!material.shader.isSupported)
{
enabled = false;
}
}
void OnRenderImage(RenderTexture source, RenderTexture destination)
{
Graphics.Blit(source, destination, material);
}
}
If you use the postprocessing effect, you will want to render the things you want to exclude from the effect with a different camera, then put everything together. However, this is a bit out of scope for this answer.

My best guess would be to use a custom shader or camera FX that would gives you control over each channel.
Hope that helped ;)

Related

Unity/HLSL: Odd results when trying to use different parts of texture atlas depending on surface normal

I'm new to 3D code and writing shaders so I'm probably doing something stupid here. I'm working on a simple Minecraft clone just to learn Unity, and I thought the easiest way to texture the ground would be to use a texture atlas with a grass + dirt texture and use a shader to choose which part of the atlas to get the texture from depending on the surface normal. i.e. for the face of a cube that points upwards, the grass texture will be used, and for all other faces the dirt texture will be used. The texture looks like this:
In my shader, if I do the following, I get the dirt texture on every face as expected:
float y = (IN.uv_MainTex.y / 2.0f);
float2 uv = {IN.uv_MainTex.x, y};
fixed4 c = tex2D (_MainTex, uv) * _Color;
o.Albedo = c.rgb;
If I use the same code but always add 0.5f to y to get the top half, i.e. float y = (IN.uv_MainTex.y / 2.0f) + 0.5f; I get the grass texture everywhere as expected:
But when I try to set y based on the normal, i.e. float y = (IN.uv_MainTex.y / 2.0f) + floor(IN.worldNormal.y) * 0.5f; I get this weird result where the top face is mostly grass but with parts of the dirt texture showing in diagonal lines:
Is IN.worldNormal the right normal to be using, or do I need to transform it into some other space? Or is the problem with how I'm using floor(), maybe? Any advice is appreciated.
Full shader code:
Shader "Custom/GroundShader"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Standard fullforwardshadows
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
sampler2D _MainTex;
struct Input
{
float2 uv_MainTex;
float3 worldNormal;
};
half _Glossiness;
half _Metallic;
fixed4 _Color;
// Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
// See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
// #pragma instancing_options assumeuniformscaling
UNITY_INSTANCING_BUFFER_START(Props)
// put more per-instance properties here
UNITY_INSTANCING_BUFFER_END(Props)
void surf (Input IN, inout SurfaceOutputStandard o)
{
float y = (IN.uv_MainTex.y / 2.0f) + floor(IN.worldNormal.y) * 0.5f;
float2 uv = {IN.uv_MainTex.x, y};
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, uv) * _Color;
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
Edit: the original answer is below, but I was approaching this problem the wrong way. The correct way to do this is just to set the UVs of the mesh using the Mesh.uv property. There is no need to use a shader in this case.
Copying an answer I got from Namey5 on the Unity forum:
This looks to be a precision issue (potentially from vertex normal
interpolation). There are probably smarter ways to do this, but as a
simple fix you could just add a small bias to the normal, i.e;
float y = (IN.uv_MainTex.y / 2.0f) + floor (IN.worldNormal.y + 0.01) * 0.5f;
I modified this slightly to float y = (IN.uv_MainTex.y / 2.0f) + floor (IN.worldNormal.y * 1.1f) * 0.5f; This ensures the bottom face also shows the dirt texture because the normal will get floored down to -2 instead of -1.

How to add emission to Unity custom shader?

I use the following shader in Unity.
Ring like sonar is spreading from the place where game objects collided.
With reference to the underlying image.
I would like to add emission to make this ring visible even in the dark, but I am new to shaders. I am in trouble because I do not know where to change.
The setting for making it dark is as follows.
Set the Intensity Multiplier of Environment Lightning to 0.
Remove skybox.
Set the Background of Camera to black.
The shader I use is below.
Shader "MadeByProfessorOakie/SimpleSonarShader" {
Properties{
_Color("Color", Color) = (1,1,1,1)
_MainTex("Albedo (RGB)", 2D) = "white" {}
_Glossiness("Smoothness", Range(0,1)) = 0.5
_Metallic("Metallic", Range(0,1)) = 0.0
_RingColor("Ring Color", Color) = (1,1,1,1)
_RingColorIntensity("Ring Color Intensity", float) = 2
_RingSpeed("Ring Speed", float) = 1
_RingWidth("Ring Width", float) = 0.1
_RingIntensityScale("Ring Range", float) = 1
_RingTex("Ring Texture", 2D) = "white" {}
}
SubShader{
Tags{ "RenderType" = "Opaque" }
LOD 200
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Standard fullforwardshadows
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
sampler2D _MainTex;
sampler2D _RingTex;
struct Input {
float2 uv_MainTex;
float3 worldPos;
float3 uv_Illum;
};
// The size of these arrays is the number of rings that can be rendered at once.
// If you want to change this, you must also change QueueSize in SimpleSonarShader_Parent.cs
half4 _hitPts[20];
half _StartTime;
half _Intensity[20];
half _Glossiness;
half _Metallic;
fixed4 _Color;
fixed4 _RingColor;
// 追加
fixed4 _EmissionLM;
half _RingColorIntensity;
half _RingSpeed;
half _RingWidth;
half _RingIntensityScale;
void surf(Input IN, inout SurfaceOutputStandard o) {
fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
half DiffFromRingCol = abs(o.Albedo.r - _RingColor.r) + abs(o.Albedo.b - _RingColor.b) + abs(o.Albedo.g - _RingColor.g);
// Check every point in the array
// The goal is to set RGB to highest possible values based on current sonar rings
for (int i = 0; i < 20; i++) {
half d = distance(_hitPts[i], IN.worldPos);
half intensity = _Intensity[i] * _RingIntensityScale;
half val = (1 - (d / intensity));
if (d < (_Time.y - _hitPts[i].w) * _RingSpeed && d >(_Time.y - _hitPts[i].w) * _RingSpeed - _RingWidth && val > 0) {
half posInRing = (d - ((_Time.y - _hitPts[i].w) * _RingSpeed - _RingWidth)) / _RingWidth;
// Calculate predicted RGB values sampling the texture radially
float angle = acos(dot(normalize(IN.worldPos - _hitPts[i]), float3(1,0,0)));
val *= tex2D(_RingTex, half2(1 - posInRing, angle));
half3 tmp = _RingColor * val + c * (1 - val);
// Determine if predicted values will be closer to the Ring color
half tempDiffFromRingCol = abs(tmp.r - _RingColor.r) + abs(tmp.b - _RingColor.b) + abs(tmp.g - _RingColor.g);
if (tempDiffFromRingCol < DiffFromRingCol)
{
// Update values using our predicted ones.
DiffFromRingCol = tempDiffFromRingCol;
o.Albedo.r = tmp.r;
o.Albedo.g = tmp.g;
o.Albedo.b = tmp.b;
o.Albedo.rgb *= _RingColorIntensity;
}
}
}
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
}
ENDCG
}
FallBack "Diffuse"
}
It's always worth to have a look at the Built-In shaders when you create custom shaders.
Just pick those which behave the way you want and see what they do.
Here is an example for the BlinnPhong surface:
//Properties
_EmissionLM ("Emission (Lightmapper)", Float) = 0
[Toggle] _DynamicEmissionLM ("Dynamic Emission (Lightmapper)", Int) = 0
//Output
o.Emission = c.rgb * tex2D(_Illum, IN.uv_Illum).a;
Hope it helps you
The problem was solved. I rewrote the code as follows. Thank you very much for those who thought.
I rewrote as follow.
// Determine if predicted values will be closer to the Ring color
half tempDiffFromRingCol = abs(tmp.r - _RingColor.r) + abs(tmp.b - _RingColor.b) + abs(tmp.g - _RingColor.g);
if (tempDiffFromRingCol < DiffFromRingCol)
{
// Update values using our predicted ones.
//DiffFromRingCol = tempDiffFromRingCol;
/*
o.Albedo.r = tmp.r;
o.Albedo.g = tmp.g;
o.Albedo.b = tmp.b;
o.Albedo.rgb *= _RingColorIntensity;
*/
// I Changed here
o.Emission = tmp;
}

How to compute the radial distance of an object in a postprocessing vertex and fragment shader

After hours of Google, copy-pasting codes and playing around, I still could not find a solution to my problem.
I try to write a postprocessing shader using the vertex and fragment functions. My problem is that I do not know how to compute the radial distance of the current vertex to the camera position (or any other given position) in world coordinates.
My goal is the following:
Consider a very big 3D plane where the camera is on top and looks exactly down to the plane. I now want a postprocessing shader that draws a white line onto the plane, such that only those pixels that have a certain radial distance to the camera are painted white. The expected result would be a white circle (in this specific setup).
I know how to do this in principal, but the problem is that I cannot find out how to compute the radial distance to the vertex.
The problem here might be that this is a POSTPROCESSING shader. So this shader is not applied to a certain object. If I would do so, I could get the world coordinates of the vertex by using mul(unity_ObjectToWorld, v.vertex), but for postprocessing shaders this gives a nonsense value.
This is my debug code for this issue:
Shader "NonHidden/TestShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Transparent" "Queue"="Transparent-1"}
LOD 100
ZWrite Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#include "UnityCG.cginc"
sampler2D _MainTex;
sampler2D _CameraDepthTexture;
uniform float4 _MainTex_TexelSize;
// V2F
struct v2f {
float4 outpos : SV_POSITION;
float4 worldPos : TEXCOORD0;
float3 rayDir : TEXCOORD1;
float3 camNormal : TEXCOORD2;
};
// Sample Depth
float sampleDepth(float2 uv) {
return Linear01Depth(
UNITY_SAMPLE_DEPTH(
tex2D(_CameraDepthTexture, uv)));
}
// VERTEX
v2f vert (appdata_tan v)
{
TANGENT_SPACE_ROTATION;
v2f o;
o.outpos = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.rayDir = mul(rotation, ObjSpaceViewDir(v.vertex));
o.camNormal = UNITY_MATRIX_IT_MV[2].xyz;
return o;
}
// FRAGMENT
fixed4 frag (v2f IN) : SV_Target
{
// Get uv coordinates
float2 uv = IN.outpos.xy * (_ScreenParams.zw - 1.0f);
// Flip y if necessary
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
{
uv.y = 1 - uv.y;
}
#endif
// Get depth
float depth = sampleDepth(uv);
// Set color
fixed4 color = 0;
if(depth.x < 1)
{
color.r = IN.worldPos.x;
color.g = IN.worldPos.y;
color.b = IN.worldPos.z;
}
return color;
}
ENDCG
}
}
}
CurrentState
This image shows the result when the camera looks down on the plane:
Image 1: Actual result
The blue value is (for whatever reason) 25 in every pixel. The red and green areas reflect the x-y coordinates of the screen.
Even if I rotate the camera a little bit, I get the exact same shading at the same screen coordinates:
That shows me that the computed "worldPos" coordinates are screen coordinates and have nothing to do with the world coordinates of the plane.
Expected Result
The result I expect to see is the following:
Here, pixels that have the same (radial) distance to the camera have the same color.
How do I need to change the above code to achieve this effect? With rayDir (computed in the vert function) I tried to get at least the direction vector from the camera center to the current pixel, such that I could compute the radial distance using the depth information. But rayDir has a constant value for all pixels ...
At this point I also have to say that I don't really understand what is computed inside the vert function. This is just stuff that I found on the internet and that I tried out.
Alright, I found some solution to my problem, since I found this video here: Shaders Case Study - No Man's Sky: Topographic Scanner
In the video description is a link to the corresponding GIT repository. I downloaded, analyzed and rewrote the code, such that it fits my purpose, is easier to read and understand.
The major thing I learned is, that there is no built-in way to compute the radial distance using post-processing shaders (correct me if I'm wrong!). So in order to get the radial distance, the only way seems to be in fact to use the direction vector from the camera to the vertex and the depth buffer. Since the direction vector is also not available in a built-in way, a trick is used:
Instead of using the Graphics.Blit function in the post-processing script, a custom Blit function can be used to set some additional shader variables. In this case, the frustum of the camera is stored in a second set of texture coordinates, which are then available in the shader code as TEXCOORD1. The trick here is that the according shader variable automatically contains an interpolated uv value, that is identical to the direction vector ("frustum ray") I was looking for.
The code of the calling script now looks as follows:
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
public class TestShaderEffect : MonoBehaviour
{
private Material material;
private Camera cam;
void OnEnable()
{
// Create a material that uses the desired shader
material = new Material(Shader.Find("Test/RadialDistance"));
// Get the camera object (this script must be assigned to a camera)
cam = GetComponent<Camera>();
// Enable depth buffer generation#
// (writes to the '_CameraDepthTexture' variable in the shader)
cam.depthTextureMode = DepthTextureMode.Depth;
}
[ImageEffectOpaque] // Draw after opaque, but before transparent geometry
void OnRenderImage(RenderTexture source, RenderTexture destination)
{
// Call custom Blit function
// (usually Graphics.Blit is used)
RaycastCornerBlit(source, destination, material);
}
void RaycastCornerBlit(RenderTexture source, RenderTexture destination, Material mat)
{
// Compute (half) camera frustum size (at distance 1.0)
float angleFOVHalf = cam.fieldOfView / 2 * Mathf.Deg2Rad;
float heightHalf = Mathf.Tan(angleFOVHalf);
float widthHalf = heightHalf * cam.aspect; // aspect = width/height
// Compute helper vectors (camera orientation weighted with frustum size)
Vector3 vRight = cam.transform.right * widthHalf;
Vector3 vUp = cam.transform.up * heightHalf;
Vector3 vFwd = cam.transform.forward;
// Custom Blit
// ===========
// Set the given destination texture as the active render texture
RenderTexture.active = destination;
// Set the '_MainTex' variable to the texture given by 'source'
mat.SetTexture("_MainTex", source);
// Store current transformation matrix
GL.PushMatrix();
// Load orthographic transformation matrix
// (sets viewing frustum from [0,0,-1] to [1,1,100])
GL.LoadOrtho();
// Use the first pass of the shader for rendering
mat.SetPass(0);
// Activate quad draw mode and draw a quad
GL.Begin(GL.QUADS);
{
// Using MultiTexCoord2 (TEXCOORD0) and Vertex3 (POSITION) to draw on the whole screen
// Using MultiTexCoord to write the frustum information into TEXCOORD1
// -> When the shader is called, the TEXCOORD1 value is automatically an interpolated value
// Bottom Left
GL.MultiTexCoord2(0, 0, 0);
GL.MultiTexCoord(1, (vFwd - vRight - vUp) * cam.farClipPlane);
GL.Vertex3(0, 0, 0);
// Bottom Right
GL.MultiTexCoord2(0, 1, 0);
GL.MultiTexCoord(1, (vFwd + vRight - vUp) * cam.farClipPlane);
GL.Vertex3(1, 0, 0);
// Top Right
GL.MultiTexCoord2(0, 1, 1);
GL.MultiTexCoord(1, (vFwd + vRight + vUp) * cam.farClipPlane);
GL.Vertex3(1, 1, 0);
// Top Left
GL.MultiTexCoord2(0, 0, 1);
GL.MultiTexCoord(1, (vFwd - vRight + vUp) * cam.farClipPlane);
GL.Vertex3(0, 1, 0);
}
GL.End(); // Finish quad drawing
// Restore original transformation matrix
GL.PopMatrix();
}
}
The shader code looks like this:
Shader "Test/RadialDistance"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
}
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct VertIn
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float4 ray : TEXCOORD1;
};
struct VertOut
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float4 interpolatedRay : TEXCOORD1;
};
// Parameter variables
sampler2D _MainTex;
// Auto filled variables
float4 _MainTex_TexelSize;
sampler2D _CameraDepthTexture;
// Generate jet-color-sheme color based on a value t in [0, 1]
half3 JetColor(half t)
{
half3 color = 0;
color.r = min(1, max(0, 4 * t - 2));
color.g = min(1, max(0, -abs( 4 * t - 2) + 2));
color.b = min(1, max(0, -4 * t + 2));
return color;
}
// VERT
VertOut vert(VertIn v)
{
VertOut o;
// Get vertex and uv coordinates
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv.xy;
// Flip uv's if necessary
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
o.uv.y = 1 - o.uv.y;
#endif
// Get the interpolated frustum ray
// (generated the calling script custom Blit function)
o.interpolatedRay = v.ray;
return o;
}
// FRAG
float4 frag (VertOut i) : SV_Target
{
// Get the color from the texture
half4 colTex = tex2D(_MainTex, i.uv);
// flat depth value with high precision nearby and bad precision far away???
float rawDepth = DecodeFloatRG(tex2D(_CameraDepthTexture, i.uv));
// flat depth but with higher precision far away and lower precision nearby???
float linearDepth = Linear01Depth(rawDepth);
// Vector from camera position to the vertex in world space
float4 wsDir = linearDepth * i.interpolatedRay;
// Position of the vertex in world space
float3 wsPos = _WorldSpaceCameraPos + wsDir;
// Distance to a given point in world space coordinates
// (in this case the camera position, so: dist = length(wsDir))
float dist = distance(wsPos, _WorldSpaceCameraPos);
// Get color by distance (same distance means same color)
half4 color = 1;
half t = saturate(dist/100.0);
color.rgb = JetColor(t);
// Set color to red at a hard-coded distance -> red circle
if (dist < 50 && dist > 50 - 1 && linearDepth < 1)
{
color.rgb = half3(1, 0, 0);
}
return color * colTex;
}
ENDCG
}
}
}
I'm now able to achieve the desired effect:
But there are still some questions I have and I would be thankful if anyone could answer them for me:
Is there really no other way to get the radial distance? Using a direciton vector and the depth buffer is inefficient and inaccurate
I don't really understand the content of the rawDepth variable. I mean yes, it's some depth information, but if you use the depth information as texture color, you basically get a black image if you are not ridiculously close to an object. That leads to a very bad resolution for objects that are further away. How can anyone work with that?
I don't understand what exactly the Linear01Depth function does. Since the Unity documentation sucks in general, it also doesn't offer any information about this one as well

FFT Water - How to get TBN matrix

I'm following the J. Tessendorf paper "Simulating Ocean Water", I managed to get the heightmap and the normal map using the finite difference method but I struggle to use these normals.
As far as I understand, these normals are in tangent space thus I need a way to convert them to world space for lighting calculation. The problem is, I'm not sure how to generate the TBN matrix which will allow me to transform the normals.
I tried Normal Mapping Without Precomputed Tangents but I have been unsuccessful, I'm not sure it can be applied here.
I don't really know what to try next, I'm using a plan deformed in a vertex shader, so the TBN cannot really be computed on CPU, anyway it would have been slow to compute it every frame as the water change.
In the paper the author suggests to use another iFFT to compute the slope vector (not sure of what a slope vector is, compared to tangent/bitangent) and then give a formula to get the normal (eq. 36 in the paper). I'm pretty sure these normals come in tangent space too.
Could you help me to transform these normals in world space ? Should I use the finite difference method, or use another iFFT to get the normals ?
Here is what I have now so you can check how I compute my normals and use them (cg shader). i'm using unity and _WorldSpaceCameraPos is a builtin shader variable with the position of the camera in world space.
vertex shader :
v2f vert(appdata_full v) {
v2f o;
float3 D = tex2Dlod(_MainTex, float4(v.texcoord.xy, 0, 0));
//128.0 hard coded to be sure it's working, I'm using 128x128 textures
float3 off = float3(-1.0 / 128.0, 0.0, 1.0 / 128.0);
float s11 = D.x;
float s01 = height(texcoord + off.xyyy);
float s21 = height(texcoord + off.zyyy);
float s10 = height(texcoord + off.yxyy);
float s12 = height(texcoord + off.yzyy);
float3 va = normalize(float3(2, 0, s21 - s01));
float3 vb = normalize(float3(0, 2, s12 - s10));
float4 bump = float4(cross(va, vb), s11);
o.pos = mul(UNITY_MATRIX_MVP, (float4(v.vertex.x + D.x, v.vertex.y + D.y, v.vertex.z + D.z, 1.0)));
o.color = float4(bump.xyz * 0.5 + 0.5, bump.w);
o.texcoord = v.texcoord.xy;
o.viewVector = _WorldSpaceCameraPos - o.pos.xyz;
return o;
}
fragment shader :
fixed4 frag(v2f i) : COLOR {
float4 N = i.color * 2.0 - 1.0;
float3x3 tbn = cotangent_frame(N.xyz, -i.viewVector, i.texcoord);
N.xyz = normalize(mul(tbn, N.xyz));
return float4(N.xyz * 0.5 + 0.5, 1.0);
}
cotangent_frame function is from the article, i'm just trying to visualize my normal in world space but all I get is blue normals.
The code in the vertex shader to get the normal from the heightmap is from this post

How do you write z-depth in a shader?

This shader (code at the end) uses raymarching to render procedural geometry:
However, in the image (above) the cube in the background should be partially occluding the pink solid; it isn't because of this:
struct fragmentOutput {
float4 color : SV_Target;
float zvalue : SV_Depth;
};
fragmentOutput frag(fragmentInput i) {
fragmentOutput o;
...
o.zvalue = IF(output[1] > 0, 0, 1);
}
However, I cannot for the life of my figure out how to correctly generate a depth value here that correctly allows raymarched solids to obscure / not obscure the other geometry in the scene.
I know it's possible, because there's a working example here: https://github.com/i-saint/RaymarchingOnUnity5 (associated japanese language blog http://i-saint.hatenablog.com/)
However, it's in japanese, and largely undocumented, as well as being extremely complex.
I'm looking for an extremely simplified version of the same thing, from which to build on.
In the shader I'm currently using the fragment program line:
float2 output = march_raycast(i.worldpos, i.viewdir, _far, _step);
Maps an input point p on the quad need the camera (which this shader attached to it), into an output float2 (density, distance), where distance is the distance from the quad to the 'point' on the procedural surface.
The question is, how do I map that into a depth buffer in any useful way?
The complete shader is here, to use it, create a new scene with a sphere at 0,0,0 with a size of at least 50 and assign the shader to it:
Shader "Shaders/Raymarching/BasicMarch" {
Properties {
_sun ("Sun", Vector) = (0, 0, 0, 0)
_far ("Far Depth Value", Float) = 20
_edgeFuzz ("Edge fuzziness", Range(1, 20)) = 1.0
_lightStep ("Light step", Range(0.1, 5)) = 1.0
_step ("Raycast step", Range(0.1, 5)) = 1.0
_dark ("Dark value", Color) = (0, 0, 0, 0)
_light ("Light Value", Color) = (1, 1, 1, 1)
[Toggle] _debugDepth ("Display depth field", Float) = 0
[Toggle] _debugLight ("Display light field", Float) = 0
}
SubShader {
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
Blend SrcAlpha OneMinusSrcAlpha
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#include "UnityCG.cginc"
#include "UnityLightingCommon.cginc" // for _LightColor0
#define IF(a, b, c) lerp(b, c, step((fixed) (a), 0));
uniform float _far;
uniform float _lightStep;
uniform float3 _sun;
uniform float4 _light;
uniform float4 _dark;
uniform float _debugDepth;
uniform float _debugLight;
uniform float _edgeFuzz;
uniform float _step;
/**
* Sphere at origin c, size s
* #param center_ The center of the sphere
* #param radius_ The radius of the sphere
* #param point_ The point to check
*/
float geom_soft_sphere(float3 center_, float radius_, float3 point_) {
float rtn = distance(center_, point_);
return IF(rtn < radius_, (radius_ - rtn) / radius_ / _edgeFuzz, 0);
}
/**
* A rectoid centered at center_
* #param center_ The center of the cube
* #param halfsize_ The halfsize of the cube in each direction
*/
float geom_rectoid(float3 center_, float3 halfsize_, float3 point_) {
float rtn = IF((point_[0] < (center_[0] - halfsize_[0])) || (point_[0] > (center_[0] + halfsize_[0])), 0, 1);
rtn = rtn * IF((point_[1] < (center_[1] - halfsize_[1])) || (point_[1] > (center_[1] + halfsize_[1])), 0, 1);
rtn = rtn * IF((point_[2] < (center_[2] - halfsize_[2])) || (point_[2] > (center_[2] + halfsize_[2])), 0, 1);
rtn = rtn * distance(point_, center_);
float radius = length(halfsize_);
return IF(rtn > 0, (radius - rtn) / radius / _edgeFuzz, 0);
}
/**
* Calculate procedural geometry.
* Return (0, 0, 0) for empty space.
* #param point_ A float3; return the density of the solid at p.
* #return The density of the procedural geometry of p.
*/
float march_geometry(float3 point_) {
return
geom_rectoid(float3(0, 0, 0), float3(7, 7, 7), point_) +
geom_soft_sphere(float3(10, 0, 0), 7, point_) +
geom_soft_sphere(float3(-10, 0, 0), 7, point_) +
geom_soft_sphere(float3(0, 0, 10), 7, point_) +
geom_soft_sphere(float3(0, 0, -10), 7, point_);
}
/** Return a randomish value to sample step with */
float rand(float3 seed) {
return frac(sin(dot(seed.xyz ,float3(12.9898,78.233,45.5432))) * 43758.5453);
}
/**
* March the point p along the cast path c, and return a float2
* which is (density, depth); if the density is 0 no match was
* found in the given depth domain.
* #param point_ The origin point
* #param cast_ The cast vector
* #param max_ The maximum depth to step to
* #param step_ The increment to step in
* #return (denity, depth)
*/
float2 march_raycast(float3 point_, float3 cast_, float max_, float step_) {
float origin_ = point_;
float depth_ = 0;
float density_ = 0;
int steps = floor(max_ / step_);
for (int i = 0; (density_ <= 1) && (i < steps); ++i) {
float3 target_ = point_ + cast_ * i * step_ + rand(point_) * cast_ * step_;
density_ += march_geometry(target_);
depth_ = IF((depth_ == 0) && (density_ != 0), distance(point_, target_), depth_);
}
density_ = IF(density_ > 1, 1, density_);
return float2(density_, depth_);
}
/**
* Simple lighting; raycast from depth point to light source, and get density on path
* #param point_ The origin point on the render target
* #param cast_ The original cast (ie. camera view direction)
* #param raycast_ The result of the original raycast
* #param max_ The max distance to cast
* #param step_ The step increment
*/
float2 march_lighting(float3 point_, float3 cast_, float2 raycast_, float max_, float step_) {
float3 target_ = point_ + cast_ * raycast_[1];
float3 lcast_ = normalize(_sun - target_);
return march_raycast(target_, lcast_, max_, _lightStep);
}
struct fragmentInput {
float4 position : SV_POSITION;
float4 worldpos : TEXCOORD0;
float3 viewdir : TEXCOORD1;
};
struct fragmentOutput {
float4 color : SV_Target;
float zvalue : SV_Depth;
};
fragmentInput vert(appdata_base i) {
fragmentInput o;
o.position = mul(UNITY_MATRIX_MVP, i.vertex);
o.worldpos = mul(_Object2World, i.vertex);
o.viewdir = -normalize(WorldSpaceViewDir(i.vertex));
return o;
}
fragmentOutput frag(fragmentInput i) {
fragmentOutput o;
// Raycast
float2 output = march_raycast(i.worldpos, i.viewdir, _far, _step);
float2 light = march_lighting(i.worldpos, i.viewdir, output, _far, _step);
float lvalue = 1.0 - light[0];
float depth = output[1] / _far;
// Generate fragment color
float4 color = lerp(_light, _dark, lvalue);
// Debugging: Depth
float4 debug_depth = float4(depth, depth, depth, 1);
color = IF(_debugDepth, debug_depth, color);
// Debugging: Color
float4 debug_light = float4(lvalue, lvalue, lvalue, 1);
color = IF(_debugLight, debug_light, color);
// Always apply the depth map
color.a = output[0];
o.zvalue = IF(output[1] > 0, 0, 1);
o.color = IF(output[1] <= 0, 0, color);
return o;
}
ENDCG
}
}
}
(Yes, I know it's quite complex, but it's very difficult to reduce this kind of shader into a 'simple test case' to play with)
I'll happy accept any answer which is a modification of the shader above that allows the procedural solid to be obscured / obscure other geometry in the scene as though is was 'real geometry'.
--
Edit: You can get this 'working' by explicitly setting the depth value on the other geometry in the scene using the same depth function as the raymarcher:
...however, I still cannot get this to work correctly with geometry using the 'standard' shader. Still hunting for a working solution...
Looking at the project you linked to, the most important difference I see is that their raycast march function uses a pass-by-reference parameter to return a fragment position called ray_pos. That position appears to be in object space, so they transform it using the view-projection matrix to get clip space and read a depth value.
The project also has a compute_depth function, but it looks pretty simple.
Your march_raycast function is already calculating a target_ position, so you could refactor a bit, apply the out keyword to return it to the caller, and use it in depth calculations:
//get position using pass-by-ref
float3 ray_pos = i.worldpos;
float2 output = march_raycast(ray_pos, i.viewdir, _far, _step);
...
//convert position to clip space, read depth
float4 clip_pos = mul(UNITY_MATRIX_VP, float4(ray_pos, 1.0));
o.zvalue = clip_pos.z / clip_pos.w;
There might be a problem with render setup.
To allow your shader to output per-pixel depth, its depth-tests must be disabled. Otherwise, GPU would - for optimization - assume that all your pixels' depths are the interpolated depths from your vertices.
As your shader does not do depth-tests, it must be rendered before the geometry that does, or it will just overwrite whatever the other geometry wrote to depth buffer.
It must however have depth-write enabled, or the depth output of your pixel shader will be ignored and not written to depth-buffer.
Your RenderType is Transparent, which, I assume, should disable depth-write. That would be a problem.
Your Queue is Transparent as well, which should have it render after all solid Geometry, and back to front, which would be a problem as well, as we already concluded we have to render before.
So
put your shader in an early render queue that will render before solid geometry
have depth-write enabled
have depth-test disabled