Cg 程式設計/Unity/鏡子

本教程介紹了平面鏡的渲染。
它不需要任何著色器程式設計(除非你想將其用於立體渲染),但它確實需要對“頂點變換”部分和“紋理球體”部分中討論的紋理化有所瞭解。
在計算機圖形學中,有各種渲染平面鏡的方法。如果可以渲染到紋理,最常見的方法包括以下步驟:
- 在鏡子上映象主攝像機的位置,並將“鏡子攝像機”放置在映象位置的鏡子後面。
- 從鏡子攝像機的視角渲染場景,使用鏡面作為視平面。將此影像渲染到渲染紋理中。
- 在使用主攝像機渲染場景時,將渲染紋理用作鏡子的紋理。
這是基本方法。讓我們來實現它。
第一個問題是如何獲得主攝像機的位置。對於單眼攝像機,這僅僅是攝像機的transform.position。對於立體攝像機,我們必須決定使用右眼攝像機的位置還是左眼攝像機的位置。我們可以使用以下程式碼獲取mainCamera的右眼檢視矩陣:
Matrix4x4 viewMatrix = mainCamera.GetStereoViewMatrix (Camera.StereoscopicEye.Right);
以及左眼檢視矩陣:
Matrix4x4 viewMatrix = mainCamera.GetStereoViewMatrix (Camera.StereoscopicEye.Left);
檢視矩陣的逆矩陣將攝像機座標系原點轉換為第四列的向量(如果從 0 開始計數,則為第三列)。由於攝像機座標系原點是攝像機的位置,並且檢視矩陣的逆矩陣將攝像機座標轉換為世界座標,因此檢視矩陣的逆矩陣的第四列指定了攝像機在世界座標系中的位置。如果檢視矩陣儲存在viewMatrix中,我們可以使用以下程式碼獲得此向量:
mainCameraPosition = viewMatrix.inverse.GetColumn (3);
可以透過多種方式在平面上映象主攝像機的位置。在 Unity 中,一種簡單的方法是將主攝像機的位置轉換為四邊形遊戲物件的物件座標系。然後,可以透過更改其座標的符號來輕鬆映象此位置。然後將此映象位置轉換回世界空間。
以下是一個實現此過程的 C# 程式碼:
// This script should be called "SetMirroredPosition"
// and should be attached to a Camera object
// in Unity which acts as a mirror camera behind a
// mirror. Once a Quad object is specified as the
// "mirrorQuad" and a "mainCamera" is set, the script
// computes the mirrored position of the "mainCamera"
// and places the script's camera at that position.
using UnityEngine;
[ExecuteInEditMode]
public class SetMirroredPosition : MonoBehaviour {
public GameObject mirrorQuad;
public Camera mainCamera;
public bool isMainCameraStereo;
public bool useRightEye;
void LateUpdate () {
if (null != mirrorQuad && null != mainCamera &&
null != mainCamera.GetComponent<Camera> ()) {
Vector3 mainCameraPosition;
if (!isMainCameraStereo) {
mainCameraPosition = mainCamera.transform.position;
} else {
Matrix4x4 viewMatrix = mainCamera.GetStereoViewMatrix (
useRightEye ? Camera.StereoscopicEye.Right :
Camera.StereoscopicEye.Left);
mainCameraPosition = viewMatrix.inverse.GetColumn (3);
}
Vector3 positionInMirrorSpace =
mirrorQuad.transform.InverseTransformPoint (mainCameraPosition);
positionInMirrorSpace.z = -positionInMirrorSpace.z;
transform.position =
mirrorQuad.transform.TransformPoint (
positionInMirrorSpace);
}
}
}
該指令碼應附加到一個新的攝像機物件(在主選單中選擇遊戲物件 > 攝像機)並命名為SetMirroredPosition.cs。mirrorQuad應設定為一個表示鏡子的四邊形遊戲物件,mainCamera應設定為主遊戲攝像機。
為了使用mirrorQuad作為視平面,我們可以使用“虛擬現實投影”部分中的指令碼,該指令碼應附加到我們新的鏡子攝像機。在該指令碼中包含[ExecuteInEditMode]行,使其在編輯器中執行。確保選中setNearClipPlane,以便進入鏡子的物體被裁剪。如果在物體與鏡面相交處存在偽影,請減小引數nearClipDistanceOffset,直到這些偽影消失。
為了將鏡子攝像機的渲染影像儲存在渲染紋理中,請透過在專案視窗中選擇建立 > 渲染紋理來建立一個新的渲染紋理。在檢查器視窗中,確保渲染紋理的大小不要太小(否則鏡子中的影像將顯得畫素化)。獲得渲染紋理後,選擇鏡子攝像機,並在檢查器視窗中將目標紋理設定為新的渲染紋理。現在,你應該能夠在渲染紋理的檢查器視窗中看到鏡子中的影像。
為了將渲染紋理用作鏡子的紋理影像,請將帶有紋理的著色器應用於鏡子四邊形,例如,標準著色器或 Unlit/Texture 著色器。像使用任何其他紋理物件一樣使用渲染紋理進行紋理化。請注意,你可能需要旋轉鏡子四邊形,使其正面朝向主攝像機可見。預設情況下,紋理影像將水平映象。但是,使用紋理的著色器屬性有一個簡單的解決方法:將平鋪的X座標設定為-1,將偏移的X座標設定為1。
對於立體渲染,我們需要兩個映象攝像機:一個用於左眼,一個用於右眼。我們還需要每個眼睛一個渲染紋理。鏡子的紋理化必須確保每個眼睛訪問其對應的渲染紋理。為此,Unity 提供了一個內建的著色器變數unity_StereoEyeIndex,它對於左眼為 0,對於右眼為 1。
一個基本的著色器,它接受兩種紋理並返回當前渲染眼睛的紋理顏色,可能看起來像這樣:
Shader "BasicStereoTexture"
{
Properties
{
_LeftTex ("Left Texture", 2D) = "white" {}
_RightTex ("Right Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct vertexInput
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct vertexOutput
{
float2 uvLeft : TEXCOORD0;
float2 uvRight : TEXCOORD1;
float4 vertex : SV_POSITION;
};
sampler2D _LeftTex;
uniform float4 _LeftTex_ST;
sampler2D _RightTex;
uniform float4 _RightTex_ST;
vertexOutput vert (vertexInput i)
{
vertexOutput o;
o.vertex = UnityObjectToClipPos(i.vertex);
o.uvLeft = TRANSFORM_TEX(i.uv, _LeftTex);
o.uvRight = TRANSFORM_TEX(i.uv, _RightTex);
return o;
}
float4 frag (vertexOutput i) : SV_Target
{
return lerp(tex2D(_LeftTex, i.uvLeft),
tex2D(_RightTex, i.uvRight),
unity_StereoEyeIndex);
}
ENDCG
}
}
FallBack "Diffuse"
}
Shader "StereoTexture"
{
Properties
{
_LeftTex ("Left Texture", 2D) = "white" {}
_RightTex ("Right Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
CGPROGRAM
#pragma surface surf Standard
uniform sampler2D _LeftTex;
uniform sampler2D _RightTex;
struct Input
{
float2 uv_LeftTex;
float2 uv_RightTex;
};
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = lerp(tex2D(_LeftTex, IN.uv_LeftTex),
tex2D(_RightTex, IN.uv_RightTex),
unity_StereoEyeIndex);
o.Emission = c.rgb;
}
ENDCG
}
FallBack "Diffuse"
}
此實現有幾個我們尚未解決的侷限性。例如:
- 多個鏡子(你可能需要為所有鏡子共享相同的渲染紋理)
- 多個鏡子中的多次反射(這很複雜,因為你需要呈指數增長的鏡子攝像機數量)
- 鏡面中的光線反射(每個光源都應該有一個映象伴侶,以考慮首先在鏡面中反射然後照亮物體的光線)
- 不均勻的鏡子(例如,使用法線貼圖)
- 等等。
恭喜!幹得好。我們已經瞭解了一些內容:
- 如何透過將位置轉換為平面的物件座標系並更改物件座標的符號來在平面上映象位置。
- 如何將攝像機檢視渲染到渲染紋理中。
- 如何使用映象的渲染紋理進行紋理化。
如果你想了解更多
- 有關使用模板緩衝區渲染鏡子的資訊,可以閱讀 Tom McReynolds 組織的 SIGGRAPH '98 課程“使用 OpenGL 的高階圖形程式設計技術”的第 9.3.1 節,該課程可以在網上獲得。