暂存服务端渲染功能,材质渲染计划迁移至前端

This commit is contained in:
2025-12-08 17:40:28 +08:00
parent 63ca7eff0d
commit 399e6f096f
9 changed files with 2528 additions and 17 deletions

View File

@@ -0,0 +1,591 @@
package skin_renderer
import (
"bytes"
"image"
"image/color"
"image/png"
"math"
)
// SkinRenderer 皮肤渲染器
type SkinRenderer struct {
playerSkin image.Image
isNewSkinType bool
isAlex bool
hdRatio int
// 旋转参数
ratio float64
headOnly bool
hR float64 // 水平旋转角度
vR float64 // 垂直旋转角度
hrh float64 // 头部水平旋转
vrll float64 // 左腿垂直旋转
vrrl float64 // 右腿垂直旋转
vrla float64 // 左臂垂直旋转
vrra float64 // 右臂垂直旋转
layers bool // 是否渲染第二层
// 计算后的三角函数值
cosAlpha, sinAlpha float64
cosOmega, sinOmega float64
// 边界
minX, maxX, minY, maxY float64
// 各部件的旋转角度
membersAngles map[string]angleSet
// 可见面
visibleFaces map[string]faceVisibility
frontFaces []string
backFaces []string
// 多边形
polygons map[string]map[string][]*Polygon
}
type angleSet struct {
cosAlpha, sinAlpha float64
cosOmega, sinOmega float64
}
type faceVisibility struct {
front []string
back []string
}
var allFaces = []string{"back", "right", "top", "front", "left", "bottom"}
// NewSkinRenderer 创建皮肤渲染器
func NewSkinRenderer(ratio float64, headOnly bool, horizontalRotation, verticalRotation float64) *SkinRenderer {
return &SkinRenderer{
ratio: ratio,
headOnly: headOnly,
hR: horizontalRotation,
vR: verticalRotation,
hrh: 0,
vrll: 0,
vrrl: 0,
vrla: 0,
vrra: 0,
layers: true,
}
}
// NewSkinRendererFull 创建带完整参数的皮肤渲染器
func NewSkinRendererFull(ratio float64, headOnly bool, hR, vR, hrh, vrll, vrrl, vrla, vrra float64, layers bool) *SkinRenderer {
return &SkinRenderer{
ratio: ratio,
headOnly: headOnly,
hR: hR,
vR: vR,
hrh: hrh,
vrll: vrll,
vrrl: vrrl,
vrla: vrla,
vrra: vrra,
layers: layers,
}
}
// Render 渲染皮肤
func (r *SkinRenderer) Render(skinData []byte, isAlex bool) (image.Image, error) {
// 解码皮肤图像
img, err := png.Decode(bytes.NewReader(skinData))
if err != nil {
return nil, err
}
r.playerSkin = img
r.isAlex = isAlex
// 计算 HD 比例
sourceWidth := img.Bounds().Dx()
sourceHeight := img.Bounds().Dy()
// 防止内存溢出,限制最大尺寸
if sourceWidth > 256 {
r.playerSkin = resizeImage(img, 256, sourceHeight*256/sourceWidth)
}
r.hdRatio = r.playerSkin.Bounds().Dx() / 64
// 检查是否为新版皮肤格式64x64
if r.playerSkin.Bounds().Dx() == r.playerSkin.Bounds().Dy() {
r.isNewSkinType = true
}
// 转换为 RGBA
r.playerSkin = convertToRGBA(r.playerSkin)
// 处理背景透明
r.makeBackgroundTransparent()
// 计算角度
r.calculateAngles()
// 确定可见面
r.facesDetermination()
// 生成多边形
r.generatePolygons()
// 部件旋转
r.memberRotation()
// 创建投影
r.createProjectionPlan()
// 渲染图像
return r.displayImage(), nil
}
// makeBackgroundTransparent 处理背景透明
func (r *SkinRenderer) makeBackgroundTransparent() {
rgba, ok := r.playerSkin.(*image.RGBA)
if !ok {
return
}
// 检查左上角 8x8 区域是否为纯色
var tempColor color.RGBA
needRemove := true
first := true
for y := 0; y < 8; y++ {
for x := 0; x < 8; x++ {
c := rgba.RGBAAt(x, y)
// 如果已有透明度,不需要处理
if c.A < 128 {
needRemove = false
break
}
if first {
tempColor = c
first = false
} else if c != tempColor {
needRemove = false
break
}
}
if !needRemove {
break
}
}
if !needRemove {
return
}
// 将该颜色设为透明
bounds := rgba.Bounds()
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
c := rgba.RGBAAt(x, y)
if c.R == tempColor.R && c.G == tempColor.G && c.B == tempColor.B {
rgba.SetRGBA(x, y, color.RGBA{0, 0, 0, 0})
}
}
}
}
// calculateAngles 计算旋转角度
func (r *SkinRenderer) calculateAngles() {
// 转换为弧度
alpha := r.vR * math.Pi / 180
omega := r.hR * math.Pi / 180
r.cosAlpha = math.Cos(alpha)
r.sinAlpha = math.Sin(alpha)
r.cosOmega = math.Cos(omega)
r.sinOmega = math.Sin(omega)
r.membersAngles = make(map[string]angleSet)
// 躯干不旋转
r.membersAngles["torso"] = angleSet{
cosAlpha: 1, sinAlpha: 0,
cosOmega: 1, sinOmega: 0,
}
// 头部旋转
omegaHead := r.hrh * math.Pi / 180
r.membersAngles["head"] = angleSet{
cosAlpha: 1, sinAlpha: 0,
cosOmega: math.Cos(omegaHead), sinOmega: math.Sin(omegaHead),
}
r.membersAngles["helmet"] = r.membersAngles["head"]
// 右臂旋转
alphaRightArm := r.vrra * math.Pi / 180
r.membersAngles["rightArm"] = angleSet{
cosAlpha: math.Cos(alphaRightArm), sinAlpha: math.Sin(alphaRightArm),
cosOmega: 1, sinOmega: 0,
}
// 左臂旋转
alphaLeftArm := r.vrla * math.Pi / 180
r.membersAngles["leftArm"] = angleSet{
cosAlpha: math.Cos(alphaLeftArm), sinAlpha: math.Sin(alphaLeftArm),
cosOmega: 1, sinOmega: 0,
}
// 右腿旋转
alphaRightLeg := r.vrrl * math.Pi / 180
r.membersAngles["rightLeg"] = angleSet{
cosAlpha: math.Cos(alphaRightLeg), sinAlpha: math.Sin(alphaRightLeg),
cosOmega: 1, sinOmega: 0,
}
// 左腿旋转
alphaLeftLeg := r.vrll * math.Pi / 180
r.membersAngles["leftLeg"] = angleSet{
cosAlpha: math.Cos(alphaLeftLeg), sinAlpha: math.Sin(alphaLeftLeg),
cosOmega: 1, sinOmega: 0,
}
r.minX, r.maxX = 0, 0
r.minY, r.maxY = 0, 0
}
// facesDetermination 确定可见面
func (r *SkinRenderer) facesDetermination() {
r.visibleFaces = make(map[string]faceVisibility)
parts := []string{"head", "torso", "rightArm", "leftArm", "rightLeg", "leftLeg"}
for _, part := range parts {
angles := r.membersAngles[part]
// 创建测试立方体点
cubePoints := r.createCubePoints()
var maxDepthPoint *Point
var maxDepthFaces []string
for _, cp := range cubePoints {
point := cp.point
point.PreProject(0, 0, 0, angles.cosAlpha, angles.sinAlpha, angles.cosOmega, angles.sinOmega)
point.Project(r.cosAlpha, r.sinAlpha, r.cosOmega, r.sinOmega, &r.minX, &r.maxX, &r.minY, &r.maxY)
if maxDepthPoint == nil {
maxDepthPoint = point
maxDepthFaces = cp.faces
} else {
_, _, z1 := maxDepthPoint.GetDestCoord()
_, _, z2 := point.GetDestCoord()
if z1 > z2 {
maxDepthPoint = point
maxDepthFaces = cp.faces
}
}
}
r.visibleFaces[part] = faceVisibility{
back: maxDepthFaces,
front: diffFaces(allFaces, maxDepthFaces),
}
}
// 确定全局前后面
cubePoints := r.createCubePoints()
var maxDepthPoint *Point
var maxDepthFaces []string
for _, cp := range cubePoints {
point := cp.point
point.Project(r.cosAlpha, r.sinAlpha, r.cosOmega, r.sinOmega, &r.minX, &r.maxX, &r.minY, &r.maxY)
if maxDepthPoint == nil {
maxDepthPoint = point
maxDepthFaces = cp.faces
} else {
_, _, z1 := maxDepthPoint.GetDestCoord()
_, _, z2 := point.GetDestCoord()
if z1 > z2 {
maxDepthPoint = point
maxDepthFaces = cp.faces
}
}
}
r.backFaces = maxDepthFaces
r.frontFaces = diffFaces(allFaces, maxDepthFaces)
}
type cubePoint struct {
point *Point
faces []string
}
func (r *SkinRenderer) createCubePoints() []cubePoint {
return []cubePoint{
{NewPoint(0, 0, 0), []string{"back", "right", "top"}},
{NewPoint(0, 0, 1), []string{"front", "right", "top"}},
{NewPoint(0, 1, 0), []string{"back", "right", "bottom"}},
{NewPoint(0, 1, 1), []string{"front", "right", "bottom"}},
{NewPoint(1, 0, 0), []string{"back", "left", "top"}},
{NewPoint(1, 0, 1), []string{"front", "left", "top"}},
{NewPoint(1, 1, 0), []string{"back", "left", "bottom"}},
{NewPoint(1, 1, 1), []string{"front", "left", "bottom"}},
}
}
func diffFaces(all, exclude []string) []string {
excludeMap := make(map[string]bool)
for _, f := range exclude {
excludeMap[f] = true
}
var result []string
for _, f := range all {
if !excludeMap[f] {
result = append(result, f)
}
}
return result
}
func contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}
// memberRotation 部件旋转
func (r *SkinRenderer) memberRotation() {
hd := float64(r.hdRatio)
// 头部和头盔旋转
angles := r.membersAngles["head"]
for _, face := range r.polygons["head"] {
for _, poly := range face {
poly.PreProject(4*hd, 8*hd, 2*hd, angles.cosAlpha, angles.sinAlpha, angles.cosOmega, angles.sinOmega)
}
}
for _, face := range r.polygons["helmet"] {
for _, poly := range face {
poly.PreProject(4*hd, 8*hd, 2*hd, angles.cosAlpha, angles.sinAlpha, angles.cosOmega, angles.sinOmega)
}
}
if r.headOnly {
return
}
// 右臂旋转
angles = r.membersAngles["rightArm"]
for _, face := range r.polygons["rightArm"] {
for _, poly := range face {
poly.PreProject(-2*hd, 8*hd, 2*hd, angles.cosAlpha, angles.sinAlpha, angles.cosOmega, angles.sinOmega)
}
}
// 左臂旋转
angles = r.membersAngles["leftArm"]
for _, face := range r.polygons["leftArm"] {
for _, poly := range face {
poly.PreProject(10*hd, 8*hd, 2*hd, angles.cosAlpha, angles.sinAlpha, angles.cosOmega, angles.sinOmega)
}
}
// 右腿旋转
angles = r.membersAngles["rightLeg"]
zOffset := 4 * hd
if angles.sinAlpha < 0 {
zOffset = 0
}
for _, face := range r.polygons["rightLeg"] {
for _, poly := range face {
poly.PreProject(2*hd, 20*hd, zOffset, angles.cosAlpha, angles.sinAlpha, angles.cosOmega, angles.sinOmega)
}
}
// 左腿旋转
angles = r.membersAngles["leftLeg"]
zOffset = 4 * hd
if angles.sinAlpha < 0 {
zOffset = 0
}
for _, face := range r.polygons["leftLeg"] {
for _, poly := range face {
poly.PreProject(6*hd, 20*hd, zOffset, angles.cosAlpha, angles.sinAlpha, angles.cosOmega, angles.sinOmega)
}
}
}
// createProjectionPlan 创建投影
func (r *SkinRenderer) createProjectionPlan() {
for _, piece := range r.polygons {
for _, face := range piece {
for _, poly := range face {
if !poly.IsProjected() {
poly.Project(r.cosAlpha, r.sinAlpha, r.cosOmega, r.sinOmega, &r.minX, &r.maxX, &r.minY, &r.maxY)
}
}
}
}
}
// displayImage 渲染最终图像
func (r *SkinRenderer) displayImage() image.Image {
width := r.maxX - r.minX
height := r.maxY - r.minY
ratio := r.ratio * 2
srcWidth := int(ratio*width) + 1
srcHeight := int(ratio*height) + 1
img := image.NewRGBA(image.Rect(0, 0, srcWidth, srcHeight))
// 按深度顺序绘制
displayOrder := r.getDisplayOrder()
for _, order := range displayOrder {
for piece, faces := range order {
for _, face := range faces {
if polys, ok := r.polygons[piece][face]; ok {
for _, poly := range polys {
poly.AddToImage(img, r.minX, r.minY, ratio)
}
}
}
}
}
// 抗锯齿2x 渲染后缩小
realWidth := srcWidth / 2
realHeight := srcHeight / 2
destImg := resizeImage(img, realWidth, realHeight)
return destImg
}
// getDisplayOrder 获取绘制顺序
func (r *SkinRenderer) getDisplayOrder() []map[string][]string {
var displayOrder []map[string][]string
if contains(r.frontFaces, "top") {
if contains(r.frontFaces, "right") {
displayOrder = append(displayOrder, map[string][]string{"leftLeg": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"leftLeg": r.visibleFaces["leftLeg"].front})
displayOrder = append(displayOrder, map[string][]string{"rightLeg": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"rightLeg": r.visibleFaces["rightLeg"].front})
displayOrder = append(displayOrder, map[string][]string{"leftArm": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"leftArm": r.visibleFaces["leftArm"].front})
displayOrder = append(displayOrder, map[string][]string{"torso": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"torso": r.visibleFaces["torso"].front})
displayOrder = append(displayOrder, map[string][]string{"rightArm": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"rightArm": r.visibleFaces["rightArm"].front})
} else {
displayOrder = append(displayOrder, map[string][]string{"rightLeg": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"rightLeg": r.visibleFaces["rightLeg"].front})
displayOrder = append(displayOrder, map[string][]string{"leftLeg": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"leftLeg": r.visibleFaces["leftLeg"].front})
displayOrder = append(displayOrder, map[string][]string{"rightArm": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"rightArm": r.visibleFaces["rightArm"].front})
displayOrder = append(displayOrder, map[string][]string{"torso": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"torso": r.visibleFaces["torso"].front})
displayOrder = append(displayOrder, map[string][]string{"leftArm": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"leftArm": r.visibleFaces["leftArm"].front})
}
displayOrder = append(displayOrder, map[string][]string{"helmet": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"head": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"head": r.visibleFaces["head"].front})
displayOrder = append(displayOrder, map[string][]string{"helmet": r.visibleFaces["head"].front})
} else {
displayOrder = append(displayOrder, map[string][]string{"helmet": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"head": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"head": r.visibleFaces["head"].front})
displayOrder = append(displayOrder, map[string][]string{"helmet": r.visibleFaces["head"].front})
if contains(r.frontFaces, "right") {
displayOrder = append(displayOrder, map[string][]string{"leftArm": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"leftArm": r.visibleFaces["leftArm"].front})
displayOrder = append(displayOrder, map[string][]string{"torso": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"torso": r.visibleFaces["torso"].front})
displayOrder = append(displayOrder, map[string][]string{"rightArm": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"rightArm": r.visibleFaces["rightArm"].front})
displayOrder = append(displayOrder, map[string][]string{"leftLeg": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"leftLeg": r.visibleFaces["leftLeg"].front})
displayOrder = append(displayOrder, map[string][]string{"rightLeg": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"rightLeg": r.visibleFaces["rightLeg"].front})
} else {
displayOrder = append(displayOrder, map[string][]string{"rightArm": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"rightArm": r.visibleFaces["rightArm"].front})
displayOrder = append(displayOrder, map[string][]string{"torso": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"torso": r.visibleFaces["torso"].front})
displayOrder = append(displayOrder, map[string][]string{"leftArm": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"leftArm": r.visibleFaces["leftArm"].front})
displayOrder = append(displayOrder, map[string][]string{"rightLeg": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"rightLeg": r.visibleFaces["rightLeg"].front})
displayOrder = append(displayOrder, map[string][]string{"leftLeg": r.backFaces})
displayOrder = append(displayOrder, map[string][]string{"leftLeg": r.visibleFaces["leftLeg"].front})
}
}
return displayOrder
}
// 辅助函数
func convertToRGBA(img image.Image) *image.RGBA {
if rgba, ok := img.(*image.RGBA); ok {
return rgba
}
bounds := img.Bounds()
rgba := image.NewRGBA(bounds)
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
rgba.Set(x, y, img.At(x, y))
}
}
return rgba
}
func resizeImage(img image.Image, width, height int) *image.RGBA {
bounds := img.Bounds()
srcW := bounds.Dx()
srcH := bounds.Dy()
dst := image.NewRGBA(image.Rect(0, 0, width, height))
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
srcX := bounds.Min.X + x*srcW/width
srcY := bounds.Min.Y + y*srcH/height
dst.Set(x, y, img.At(srcX, srcY))
}
}
return dst
}
// getPixelColor 从皮肤图像获取像素颜色
func (r *SkinRenderer) getPixelColor(x, y int) color.RGBA {
if x < 0 || y < 0 {
return color.RGBA{}
}
bounds := r.playerSkin.Bounds()
if x >= bounds.Dx() || y >= bounds.Dy() {
return color.RGBA{}
}
c := r.playerSkin.At(bounds.Min.X+x, bounds.Min.Y+y)
r32, g32, b32, a32 := c.RGBA()
return color.RGBA{
R: uint8(r32 >> 8),
G: uint8(g32 >> 8),
B: uint8(b32 >> 8),
A: uint8(a32 >> 8),
}
}