0

So I tried to create an unlit shader in unity using the built-in render pipeline and blit it to the screen using the function: OnRenderImage(RenderTexture src, RenderTexture dest) in my C# code. I have been kind of loosely following this video: (https://www.youtube.com/watch?v=DxfEbulyFcY&t=1138s) among other things because my goal is to create a volumetric atmosphere shader. However, I seem to have multiple problems.

  1. The shader draws an oval shape, not a sphere (I suspect that this has something to do with the aspect ratio of the window, it's still not welcome though)
  2. The "sphere" is only drawn when the camera is at a certain angle or position. Meaning that if I tried to look at it from the back that it would just disappear
  3. The position seems to be way off from what the position and radius of the volume sphere seems too not be correct

I have done my best to read up and do my research on volumetric rendering, but I still don't quite understand what's going on here.

C# code (Attached to camera)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExecuteInEditMode, ImageEffectAllowedInSceneView]
public class ImageEffectManager : MonoBehaviour
{
    public Shader effectShader;
    public Transform volume;
    Material mat;
    private void OnRenderImage(RenderTexture src, RenderTexture dest) 
    {
        if (mat == null && effectShader != null)
        {
            mat = new Material(effectShader);
        }
        //Set shader properties here:
        mat.SetVector("_SphereCenter", volume.position);
        mat.SetFloat("_SphereRadius", volume.GetComponent<SphereCollider>().radius);
        //Draw the shader to the screen
        Graphics.Blit(src, dest, mat);
    }
}

HLSL Shader (goes into property effectShader)

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

// Upgrade NOTE: replaced '\_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unlit/ImageEffectTest"
{
Properties
{
\_MainTex ("Texture", 2D) = "white" {}
\_SphereCenter ("Sphere Center", Vector) = (0, 0, 0, 0) // editor's note: I dont know why the '\' got put at the beginning of each property when I copy+pasted the code, but I guess it's there now.
\_SphereRadius ("Sphere Radius", Float) = 1
}
SubShader
{
Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Opaque" }
Blend One Zero
Cull Off Lighting Off ZWrite Off
LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog
    
            #include "UnityCG.cginc"
    
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
    
            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 worldPos : TEXCOORD1;
            };
    
            sampler2D _MainTex;
            float4 _MainTex_ST;
    
            float3 _SphereCenter;
            float _SphereRadius;
    
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                return o;
            }
    
            float2 raySphere(float3 sphereCenter, float sphereRadius, float3 rayOrigin, float3 rayDir)
            {
                float3 offset = rayOrigin - sphereCenter;
                float a = 1;
                float b = 2 * dot(offset, rayDir);
                float c = dot(offset, offset) - sphereRadius * sphereRadius;
                float d = b * b - 4 * a * c;
    
                if (d > 0)
                {
                    float s = sqrt(d);
                    float dstToSphereNear = max(0, (-b - s) / (2 * a));
                    float dstToSphereFar = (-b + s) / (2 * a);
    
                    if (dstToSphereFar >= 0)
                    {
                        return float2(dstToSphereNear, dstToSphereFar - dstToSphereNear);
                    }
                }
                return float2(3.402823466e+38, 0);
            }
    
            float4 frag (v2f i) : SV_Target
            {
                float4 col = tex2D(_MainTex, i.uv);
                
                float3 rayOrigin = _WorldSpaceCameraPos;
                float3 rayDir = normalize(i.worldPos - _WorldSpaceCameraPos);
    
                float2 hitInfo = raySphere(_SphereCenter, _SphereRadius, rayOrigin, rayDir);
                float dstToAtmosphere = hitInfo.x;
                float dstThroughAtmosphere = hitInfo.y;
    
                return dstThroughAtmosphere / (_SphereRadius * 2);
            }
            ENDCG
        }
    }

}

Here's what I was trying to describe above

Here's an unlisted video I uploaded to make it easier to understand what's happening

I tried to switch some operators in the shader code, some macros, along with double-checking my work across some different sources such as this pretty helpful article: (https://www.alanzucconi.com/2016/07/01/volumetric-rendering/)

However, none of these made any kind of significant difference on the result that I was getting, so I finally decided to make this post.

1 Answers1

0

The issue seems to be that the multiplication by unity_ObjectToWorld doesn't actually convert to a proper world position in the context of a fullscreen quad.

The easiest solution here would be to compute the 4 screen/frustum corner positions on the cpu side, and pass them as properties to your shader (4 x float4 vectors). Then use the texture coordinates to interpolate between them and reconstruct the expected world position.

https://docs.unity3d.com/ScriptReference/Camera.ViewportToWorldPoint.html using viewport space the input corners are just (0,0), (0,1), (1,0) and (1,1)

on the shader-side you might need to account for which corner is the origin depending on your platform using UNITY_UV_STARTS_AT_TOP https://docs.unity3d.com/Manual/SL-BuiltinMacros.html

Brice V.
  • 861
  • 4
  • 7